summaryrefslogtreecommitdiffstats
path: root/runtime/unwind.c
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/unwind.c')
-rw-r--r--runtime/unwind.c208
1 files changed, 120 insertions, 88 deletions
diff --git a/runtime/unwind.c b/runtime/unwind.c
index aacd56f1..cf0bc2f3 100644
--- a/runtime/unwind.c
+++ b/runtime/unwind.c
@@ -8,9 +8,9 @@
*
* This code is released under version 2 of the GNU GPL.
*
- * This code currently does stack unwinding in the
- * kernel and modules. It will need some extension to handle
- * userspace unwinding.
+ * This code currently does stack unwinding in the kernel and modules.
+ * It has been extended to handle userspace unwinding using systemtap
+ * data structures.
*/
#include "unwind/unwind.h"
@@ -87,7 +87,8 @@ static sleb128_t get_sleb128(const u8 **pcur, const u8 *end)
}
/* given an FDE, find its CIE */
-static const u32 *cie_for_fde(const u32 *fde, const struct _stp_module *m)
+static const u32 *cie_for_fde(const u32 *fde, void *unwind_data,
+ int is_ehframe)
{
const u32 *cie;
@@ -96,7 +97,7 @@ static const u32 *cie_for_fde(const u32 *fde, const struct _stp_module *m)
return &bad_cie;
/* CIE id for eh_frame is 0, otherwise 0xffffffff */
- if (m->unwind_is_ehframe && fde[1] == 0)
+ if (is_ehframe && fde[1] == 0)
return &not_fde;
else if (fde[1] == 0xffffffff)
return &not_fde;
@@ -104,18 +105,18 @@ static const u32 *cie_for_fde(const u32 *fde, const struct _stp_module *m)
/* OK, must be an FDE. Now find its CIE. */
/* CIE_pointer must be a proper offset */
- if ((fde[1] & (sizeof(*fde) - 1)) || fde[1] > (unsigned long)(fde + 1) - (unsigned long)m->unwind_data) {
+ if ((fde[1] & (sizeof(*fde) - 1)) || fde[1] > (unsigned long)(fde + 1) - (unsigned long)unwind_data) {
dbug_unwind(1, "fde[1]=%lx fde+1=%lx, unwind_data=%lx %lx\n",
(unsigned long)fde[1], (unsigned long)(fde + 1),
- (unsigned long)m->unwind_data, (unsigned long)(fde + 1) - (unsigned long)m->unwind_data);
+ (unsigned long)unwind_data, (unsigned long)(fde + 1) - (unsigned long)unwind_data);
return NULL; /* this is not a valid FDE */
}
/* cie pointer field is different in eh_frame vs debug_frame */
- if (m->unwind_is_ehframe)
+ if (is_ehframe)
cie = fde + 1 - fde[1] / sizeof(*fde);
else
- cie = m->unwind_data + fde[1];
+ cie = unwind_data + fde[1];
if (*cie <= sizeof(*cie) + 4 || *cie >= fde[1] - sizeof(*fde)
|| (*cie & (sizeof(*cie) - 1))
@@ -427,21 +428,81 @@ static int processCFI(const u8 *start, const u8 *end, unsigned long targetLoc, s
return result && ptr.p8 == end && (targetLoc == 0 || state->label == NULL);
}
+#ifdef DEBUG_UNWIND
+static const char *_stp_enc_hi_name[] = {
+ "DW_EH_PE",
+ "DW_EH_PE_pcrel",
+ "DW_EH_PE_textrel",
+ "DW_EH_PE_datarel",
+ "DW_EH_PE_funcrel",
+ "DW_EH_PE_aligned"
+};
+static const char *_stp_enc_lo_name[] = {
+ "_absptr",
+ "_uleb128",
+ "_udata2",
+ "_udata4",
+ "_udata8",
+ "_sleb128",
+ "_sdata2",
+ "_sdata4",
+ "_sdata8"
+};
+static char *_stp_eh_enc_name(signed type)
+{
+ static char buf[64];
+ int hi, low;
+ if (type == DW_EH_PE_omit)
+ return "DW_EH_PE_omit";
+
+ hi = (type & DW_EH_PE_ADJUST) >> 4;
+ low = type & DW_EH_PE_FORM;
+ if (hi > 5 || low > 4 || (low == 0 && (type & DW_EH_PE_signed))) {
+ sprintf(buf, "ERROR:encoding=0x%x", type);
+ return buf;
+ }
+
+ buf[0] = 0;
+ if (type & DW_EH_PE_indirect)
+ strlcpy(buf, "DW_EH_PE_indirect|", sizeof(buf));
+ strlcat(buf, _stp_enc_hi_name[hi], sizeof(buf));
+
+ if (type & DW_EH_PE_signed)
+ low += 4;
+ strlcat(buf, _stp_enc_lo_name[low], sizeof(buf));
+ return buf;
+}
+#endif /* DEBUG_UNWIND */
+
// If this is an address inside a module, adjust for section relocation
// and the elfutils base relocation done during loading of the .dwarf_frame
// in translate.cxx.
static unsigned long
adjustStartLoc (unsigned long startLoc,
struct _stp_module *m,
- struct _stp_section *s)
+ struct _stp_section *s,
+ unsigned ptrType, int is_ehframe)
{
/* XXX - some, or all, of this should really be done by
- _stp_module_relocate. */
+ _stp_module_relocate and/or read_pointer. */
+ dbug_unwind(2, "adjustStartLoc=%lx, ptrType=%s, m=%s, s=%s eh=%d\n",
+ startLoc, _stp_eh_enc_name(ptrType), m->name, s->name, is_ehframe);
if (startLoc == 0
|| strcmp (m->name, "kernel") == 0
- || strcmp (s->name, ".absolute") == 0)
+ || (strcmp (s->name, ".absolute") == 0 && !is_ehframe))
return startLoc;
+ /* eh_frame data has been loaded in the kernel, so readjust offset. */
+ if (is_ehframe) {
+ dbug_unwind(2, "eh_frame=%lx, eh_frame_addr=%lx\n", (unsigned long) m->eh_frame, m->eh_frame_addr);
+ if ((ptrType & DW_EH_PE_ADJUST) == DW_EH_PE_pcrel) {
+ startLoc -= (unsigned long) m->eh_frame;
+ startLoc += m->eh_frame_addr;
+ }
+ if (strcmp (s->name, ".absolute") == 0)
+ return startLoc;
+ }
+
if (strcmp (s->name, ".dynamic") == 0)
return startLoc + s->addr;
@@ -451,7 +512,7 @@ adjustStartLoc (unsigned long startLoc,
}
/* If we previously created an unwind header, then use it now to binary search */
-/* for the FDE corresponding to pc. */
+/* for the FDE corresponding to pc. XXX FIXME not currently supported. */
static u32 *_stp_search_unwind_hdr(unsigned long pc,
struct _stp_module *m,
@@ -488,7 +549,7 @@ static u32 *_stp_search_unwind_hdr(unsigned long pc,
ptr = hdr + 4;
end = hdr + m->unwind_hdr_len;
- if (read_pointer(&ptr, end, hdr[1]) != (unsigned long)m->unwind_data) {
+ if (read_pointer(&ptr, end, hdr[1]) != (unsigned long)m->debug_frame) {
dbug_unwind(1, "eh_frame_ptr not valid");
return NULL;
}
@@ -502,7 +563,7 @@ static u32 *_stp_search_unwind_hdr(unsigned long pc,
do {
const u8 *cur = ptr + (num / 2) * (2 * tableSize);
startLoc = read_pointer(&cur, cur + tableSize, hdr[3]);
- startLoc = adjustStartLoc(startLoc, m, s);
+ startLoc = adjustStartLoc(startLoc, m, s, hdr[3], 1);
if (pc < startLoc)
num /= 2;
else {
@@ -511,64 +572,19 @@ static u32 *_stp_search_unwind_hdr(unsigned long pc,
}
} while (startLoc && num > 1);
- if (num == 1 && (startLoc = adjustStartLoc(read_pointer(&ptr, ptr + tableSize, hdr[3]), m, s)) != 0 && pc >= startLoc)
+ if (num == 1 && (startLoc = adjustStartLoc(read_pointer(&ptr, ptr + tableSize, hdr[3]), m, s, hdr[3], 1)) != 0 && pc >= startLoc)
fde = (void *)read_pointer(&ptr, ptr + tableSize, hdr[3]);
dbug_unwind(1, "returning fde=%lx startLoc=%lx", fde, startLoc);
return fde;
}
-#ifdef DEBUG_UNWIND
-static const char *_stp_enc_hi_name[] = {
- "",
- "DW_EH_PE_pcrel",
- "DW_EH_PE_textrel",
- "DW_EH_PE_datarel",
- "DW_EH_PE_funcrel",
- "DW_EH_PE_aligned"
-};
-static const char *_stp_enc_lo_name[] = {
- "DW_EH_PE_absptr",
- "DW_EH_PE_uleb128",
- "DW_EH_PE_udata2",
- "DW_EH_PE_udata4",
- "DW_EH_PE_udata8",
- "DW_EH_PE_sleb128",
- "DW_EH_PE_sdata2",
- "DW_EH_PE_sdata4",
- "DW_EH_PE_sdata8"
-};
-static char *_stp_eh_enc_name(signed type)
-{
- static char buf[64];
- int hi, low;
- if (type == DW_EH_PE_omit)
- return "DW_EH_PE_omit";
-
- hi = (type & DW_EH_PE_ADJUST) >> 4;
- low = type & DW_EH_PE_FORM;
- if (hi > 5 || low > 4 || (low == 0 && (type & DW_EH_PE_signed))) {
- sprintf(buf, "ERROR:encoding=0x%x", type);
- return buf;
- }
-
- buf[0] = 0;
- if (type & DW_EH_PE_indirect)
- strlcpy(buf, "DW_EH_PE_indirect|", sizeof(buf));
- if (hi)
- strlcat(buf, _stp_enc_hi_name[hi], sizeof(buf));
-
- if (type & DW_EH_PE_signed)
- low += 4;
- strlcat(buf, _stp_enc_lo_name[low], sizeof(buf));
- return buf;
-}
-#endif /* DEBUG_UNWIND */
-
/* Unwind to previous to frame. Returns 0 if successful, negative
* number in case of an error. A positive return means unwinding is finished;
* don't try to fallback to dumping addresses on the stack. */
-static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
+static int unwind_frame(struct unwind_frame_info *frame,
+ struct _stp_module *m, struct _stp_section *s,
+ void *table, uint32_t table_len, int is_ehframe)
{
#define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs])
const u32 *fde, *cie = NULL;
@@ -578,23 +594,10 @@ static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
unsigned i;
signed ptrType = -1;
uleb128_t retAddrReg = 0;
- struct _stp_module *m;
- struct _stp_section *s = NULL;
struct unwind_state state;
- dbug_unwind(1, "pc=%lx, %lx", pc, UNW_PC(frame));
-
- if (UNW_PC(frame) == 0)
- return -EINVAL;
-
- m = _stp_mod_sec_lookup (pc, tsk, &s);
- if (unlikely(m == NULL)) {
- dbug_unwind(1, "No module found for pc=%lx", pc);
- return -EINVAL;
- }
-
- if (unlikely(m->unwind_data_len == 0 || m->unwind_data_len & (sizeof(*fde) - 1))) {
- dbug_unwind(1, "Module %s: unwind_data_len=%d", m->name, m->unwind_data_len);
+ if (unlikely(table_len == 0 || table_len & (sizeof(*fde) - 1))) {
+ dbug_unwind(1, "Module %s: frame_len=%d", m->name, table_len);
goto err;
}
@@ -603,14 +606,14 @@ static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
/* found the fde, now set startLoc and endLoc */
if (fde != NULL) {
- cie = cie_for_fde(fde, m);
+ cie = cie_for_fde(fde, table, is_ehframe);
if (likely(cie != NULL && cie != &bad_cie && cie != &not_fde)) {
ptr = (const u8 *)(fde + 2);
ptrType = fde_pointer_type(cie);
startLoc = read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType);
- startLoc = adjustStartLoc(startLoc, m, s);
+ startLoc = adjustStartLoc(startLoc, m, s, ptrType, is_ehframe);
- dbug_unwind(2, "startLoc=%lx, ptrType=%s", startLoc, _stp_eh_enc_name(ptrType));
+ dbug_unwind(2, "startLoc=%lx, ptrType=%s\n", startLoc, _stp_eh_enc_name(ptrType));
if (!(ptrType & DW_EH_PE_indirect))
ptrType &= DW_EH_PE_FORM | DW_EH_PE_signed;
endLoc = startLoc + read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType);
@@ -626,10 +629,10 @@ static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
/* did not a good fde find with binary search, so do slow linear search */
if (fde == NULL) {
- for (fde = m->unwind_data, tableSize = m->unwind_data_len; cie = NULL, tableSize > sizeof(*fde)
- && tableSize - sizeof(*fde) >= *fde; tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) {
+ for (fde = table, tableSize = table_len; cie = NULL, tableSize > sizeof(*fde)
+ && tableSize - sizeof(*fde) >= *fde; tableSize -= sizeof(*fde) + *fde, fde += 1 + *fde / sizeof(*fde)) {
dbug_unwind(3, "fde=%lx tableSize=%d\n", (long)*fde, (int)tableSize);
- cie = cie_for_fde(fde, m);
+ cie = cie_for_fde(fde, table, is_ehframe);
if (cie == &bad_cie) {
cie = NULL;
break;
@@ -639,8 +642,8 @@ static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
ptr = (const u8 *)(fde + 2);
startLoc = read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType);
- startLoc = adjustStartLoc(startLoc, m, s);
- dbug_unwind(2, "startLoc=%lx, ptrType=%s", startLoc, _stp_eh_enc_name(ptrType));
+ startLoc = adjustStartLoc(startLoc, m, s, ptrType, is_ehframe);
+ dbug_unwind(2, "startLoc=%lx, ptrType=%s\n", startLoc, _stp_eh_enc_name(ptrType));
if (!startLoc)
continue;
if (!(ptrType & DW_EH_PE_indirect))
@@ -652,7 +655,7 @@ static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
}
}
- dbug_unwind(1, "cie=%lx fde=%lx startLoc=%lx endLoc=%lx\n", cie, fde, startLoc, endLoc);
+ dbug_unwind(1, "cie=%lx fde=%lx startLoc=%lx endLoc=%lx, pc=%lx\n", cie, fde, startLoc, endLoc, pc);
if (cie == NULL || fde == NULL)
goto err;
@@ -856,5 +859,34 @@ done:
#undef FRAME_REG
}
+static int unwind(struct unwind_frame_info *frame, struct task_struct *tsk)
+{
+ struct _stp_module *m;
+ struct _stp_section *s = NULL;
+ unsigned long pc = UNW_PC(frame) - frame->call_frame;
+ int res;
+
+ dbug_unwind(1, "pc=%lx, %lx", pc, UNW_PC(frame));
+
+ if (UNW_PC(frame) == 0)
+ return -EINVAL;
+
+ m = _stp_mod_sec_lookup (pc, tsk, &s);
+ if (unlikely(m == NULL)) {
+ dbug_unwind(1, "No module found for pc=%lx", pc);
+ return -EINVAL;
+ }
+
+ dbug_unwind(1, "trying debug_frame\n");
+ res = unwind_frame (frame, m, s, m->debug_frame,
+ m->debug_frame_len, 0);
+ if (res != 0) {
+ dbug_unwind(1, "debug_frame failed: %d, trying eh_frame\n", res);
+ res = unwind_frame (frame, m, s, m->eh_frame,
+ m->eh_frame_len, 1);
+ }
+
+ return res;
+}
#endif /* STP_USE_DWARF_UNWINDER */