ARM: unwind: fix unwinding when prel31 offset is used
Current version had a bug when prel31 offsets to an EHT (Exception
Handling Table) were used. We used to allocate unwind index table
and EHT table as a single chunk and calculate EHT entry addresses
via pointers from our copy of the index table.
Problem with this approach is that it is not guaranteed that the EHT
table always follows the unwind index table, rendering address of
the EHT entry invalid. This is true in particular with module unwind
tables.
In addition we didn't even read EHT tables for modules.
Fix for this is that we only populate unwind index tables and use
kernel virtual addresses added with prel31 offset to read the EHT
entries from the dump.
diff --git a/unwind_arm.c b/unwind_arm.c
index 8352e2d..cc5eb7c 100644
--- a/unwind_arm.c
+++ b/unwind_arm.c
@@ -38,10 +38,6 @@
* (prel31 offset)
* 2. if bit31 is set, this contains the EHT entry itself
* 3. if 0x1, cannot unwind.
- *
- * In case 1. @insn points to the EH table that comes directly after index
- * table. This offset is relative to address of @insn which implies that we must
- * allocate both index table and EH table in single chunk.
*/
struct unwind_idx {
ulong addr;
@@ -55,6 +51,7 @@ struct unwind_idx {
* @end: pointer to the last element +1 of the index table
* @begin_addr: start address which this table covers
* @end_addr: end address which this table covers
+ * @kv_base: kernel virtual address of the start of the index table
*
* Kernel stores per-module unwind tables in this format. There can be more than
* one table per module as we have different ELF sections in the module.
@@ -65,6 +62,7 @@ struct unwind_table {
struct unwind_idx *end;
ulong begin_addr;
ulong end_addr;
+ ulong kv_base;
};
- /*
- * Now read in both the index table and the EH table. We need to read in
- * both because prel31 offsets in the index table are relative to the
- * index address.
- */
- if (!readmem(idx_start, KVADDR, kernel_unwind_table->idx, tab_size,
+ /* now read in the index table */
+ if (!readmem(idx_start, KVADDR, kernel_unwind_table->idx, idx_size,
"master kernel unwind table", RETURN_ON_ERROR))
goto fail;
/*
+ * Read next unwind instruction pointed by ctrl->insn_kvaddr into
+ * ctrl->insn. As a side-effect, increase the ctrl->insn_kvaddr to
+ * point to the next instruction.
+ */
+static int
+unwind_get_insn(struct unwind_ctrl_block *ctrl)
+{
+ if (readmem(ctrl->insn_kvaddr, KVADDR, &ctrl->insn, sizeof(ctrl->insn),
+ "unwind insn", RETURN_ON_ERROR)) {
+ ctrl->insn_kvaddr += sizeof(ctrl->insn);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/*
* Return next insn byte from ctl or 0 in case of failure. As a side-effect,
* changes ctrl according the next byte.
*/
@@ -373,11 +381,11 @@ unwind_get_byte(struct unwind_ctrl_block *ctrl)
return 0;
}
- ret = (*ctrl->insn >> (ctrl->byte * 8)) & 0xff;
+ ret = (ctrl->insn >> (ctrl->byte * 8)) & 0xff;
-static struct unwind_idx *
-search_index(ulong ip)
+static struct unwind_table *
+search_table(ulong ip)
{
- struct unwind_idx *start = NULL;
- struct unwind_idx *end = NULL;
-
/*
* First check if this address is in the master kernel unwind table or
* some of the module unwind tables.
*/
if (is_core_kernel_text(ip)) {
- start = kernel_unwind_table->start;
- end = kernel_unwind_table->end;
+ return kernel_unwind_table;
} else {
struct unwind_table *tbl;
for (tbl = &module_unwind_tables[0]; tbl->idx; tbl++) {
- if (ip >= tbl->begin_addr && ip < tbl->end_addr) {
- start = tbl->start;
- end = tbl->end;
- break;
- }
+ if (ip >= tbl->begin_addr && ip < tbl->end_addr)
+ return tbl;
}
}
- if (start && end) {
- /*
- * Do a binary search for the addresses in the index table.
- * Addresses are guaranteed to be sorted in ascending order.
- */
- while (start < end - 1) {
- struct unwind_idx *mid = start + ((end - start + 1) >> 1);
+ return NULL;
+}
- return start;
+ /*
+ * Do a binary search for the addresses in the index table.
+ * Addresses are guaranteed to be sorted in ascending order.
+ */
+ while (start < end - 1) {
+ struct unwind_idx *mid = start + ((end - start + 1) >> 1);
+
+ if (ip < mid->addr)
+ end = mid;
+ else
+ start = mid;
}
- return NULL;
+ return start;
}
-
/*
- * Convert a prel31 symbol to an absolute address.
+ * Convert a prel31 symbol to an absolute kernel virtual address.
*/
-static ulong *
-prel31_to_addr(ulong *ptr)
+static ulong
+prel31_to_addr(ulong addr, ulong insn)
{
/* sign extend to 32 bits */
- long offset = (((long)*ptr) << 1) >> 1;
- return (ulong *)((ulong)ptr + offset);
+ long offset = ((long)insn << 1) >> 1;
+ return addr + offset;
}