diff options
Diffstat (limited to 'runtime/unwind.c')
-rw-r--r-- | runtime/unwind.c | 208 |
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 ¬_fde; else if (fde[1] == 0xffffffff) return ¬_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 != ¬_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 */ |