diff options
author | Martin Hunt <hunt@redhat.com> | 2008-03-28 16:20:02 -0400 |
---|---|---|
committer | Martin Hunt <hunt@redhat.com> | 2008-03-28 16:20:02 -0400 |
commit | 614ead2b7dd8ac70cd89d018b09a397be7ade371 (patch) | |
tree | 11dfb318caa7c636cd2d933905810ca5a634115e /runtime | |
parent | fd2ef8221625866219d6fc8e99ac36520ac6017b (diff) | |
download | systemtap-steved-614ead2b7dd8ac70cd89d018b09a397be7ade371.tar.gz systemtap-steved-614ead2b7dd8ac70cd89d018b09a397be7ade371.tar.xz systemtap-steved-614ead2b7dd8ac70cd89d018b09a397be7ade371.zip |
kretprobe trampoline fixes
Recognize when a kretprobe trampoline was hit and continue with inexact stack dump.
Also some testsuite changes.
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/stack-i386.c | 22 | ||||
-rw-r--r-- | runtime/stack-x86_64.c | 17 | ||||
-rw-r--r-- | runtime/stack.c | 22 | ||||
-rw-r--r-- | runtime/sym.h | 1 | ||||
-rw-r--r-- | runtime/transport/symbols.c | 2 | ||||
-rw-r--r-- | runtime/unwind.c | 86 |
6 files changed, 111 insertions, 39 deletions
diff --git a/runtime/stack-i386.c b/runtime/stack-i386.c index a86f94be..d7c2c201 100644 --- a/runtime/stack-i386.c +++ b/runtime/stack-i386.c @@ -43,7 +43,10 @@ static void __stp_stack_print (struct pt_regs *regs, int verbose, int levels) #endif /* STAPCONF_X86_UNIREGS */ while (_stp_valid_stack_ptr(context, (unsigned long)ebp)) { - addr = *(unsigned long *)(ebp + 4); + if (unlikely(__stp_get_user(addr, (unsigned long *)(ebp + 4)))) { + /* cannot access stack. give up. */ + return; + } if (verbose) { _stp_print_char(' '); _stp_symbol_print (addr); @@ -55,18 +58,19 @@ static void __stp_stack_print (struct pt_regs *regs, int verbose, int levels) #else struct unwind_frame_info info; arch_unw_init_frame_info(&info, regs); + while (!arch_unw_user_mode(&info)) { int ret = unwind(&info); dbug_unwind(1, "ret=%d PC=%lx SP=%lx\n", ret, UNW_PC(&info), UNW_SP(&info)); - if (ret < 0) { - _stp_stack_print_fallback(context, UNW_SP(&info), verbose); - break; + if (ret == 0) { + _stp_func_print(UNW_PC(&info), verbose, 1); + continue; } - if (ret) - break; - _stp_func_print(UNW_PC(&info), verbose, 1); + /* If an error happened or we hit a kretprobe trampoline, use fallback backtrace */ + /* FIXME: is there a way to unwind across kretprobe trampolines? */ + if (ret < 0 || (ret > 0 && UNW_PC(&info) == _stp_kretprobe_trampoline)) + _stp_stack_print_fallback(UNW_SP(&info), verbose); + break; } -// _stp_printf("***********************\n"); -// _stp_stack_print_fallback(context, (unsigned long)stack, verbose); #endif /* CONFIG_FRAME_POINTER */ } diff --git a/runtime/stack-x86_64.c b/runtime/stack-x86_64.c index e2eb4aa2..846653be 100644 --- a/runtime/stack-x86_64.c +++ b/runtime/stack-x86_64.c @@ -8,13 +8,12 @@ * later version. */ -// todo: don't use unwinder for kernel if CONFIG_FRAME +// todo: don't use unwinder for kernel if CONFIG_FRAME /* DWARF unwinder failed. Just dump intereting addresses on kernel stack. */ static void _stp_stack_print_fallback(unsigned long stack, int verbose) { unsigned long addr; - while (stack & (THREAD_SIZE - 1)) { if (unlikely(__stp_get_user(addr, (unsigned long *)stack))) { /* cannot access stack. give up. */ @@ -33,12 +32,14 @@ static void __stp_stack_print(struct pt_regs *regs, int verbose, int levels) while (!arch_unw_user_mode(&info)) { int ret = unwind(&info); dbug_unwind(1, "ret=%d PC=%lx SP=%lx\n", ret, UNW_PC(&info), UNW_SP(&info)); - if (ret < 0) { - _stp_stack_print_fallback(UNW_SP(&info), verbose); - break; + if (ret == 0) { + _stp_func_print(UNW_PC(&info), verbose, 1); + continue; } - if (ret) - break; - _stp_func_print(UNW_PC(&info), verbose, 1); + /* If an error happened or we hit a kretprobe trampoline, use fallback backtrace */ + /* FIXME: is there a way to unwind across kretprobe trampolines? */ + if (ret < 0 || (ret > 0 && UNW_PC(&info) == _stp_kretprobe_trampoline)) + _stp_stack_print_fallback(UNW_SP(&info), verbose); + break; } } diff --git a/runtime/stack.c b/runtime/stack.c index e338f587..eefdf715 100644 --- a/runtime/stack.c +++ b/runtime/stack.c @@ -58,12 +58,15 @@ void _stp_stack_print(struct pt_regs *regs, int verbose, struct kretprobe_instan _stp_symbol_print((unsigned long)_stp_ret_addr_r(pi)); } else { _stp_print_char(' '); - _stp_symbol_print (REG_IP(regs)); + _stp_symbol_print(REG_IP(regs)); } _stp_print_char('\n'); - } else - _stp_printf ("%p ", (int64_t)REG_IP(regs)); - __stp_stack_print (regs, verbose, 0); + } else if (pi) + _stp_printf("%p %p ", (int64_t) _stp_ret_addr_r(pi), (int64_t) REG_IP(regs)); + else + _stp_printf("%p ", (int64_t) REG_IP(regs)); + + __stp_stack_print(regs, verbose, 0); } /** Writes stack backtrace to a string @@ -72,7 +75,7 @@ void _stp_stack_print(struct pt_regs *regs, int verbose, struct kretprobe_instan * @param regs A pointer to the struct pt_regs. * @returns void */ -void _stp_stack_snprint (char *str, int size, struct pt_regs *regs, int verbose, struct kretprobe_instance *pi) +void _stp_stack_snprint(char *str, int size, struct pt_regs *regs, int verbose, struct kretprobe_instance *pi) { /* To get a string, we use a simple trick. First flush the print buffer, */ /* then call _stp_stack_print, then copy the result into the output string */ @@ -84,19 +87,18 @@ void _stp_stack_snprint (char *str, int size, struct pt_regs *regs, int verbose, pb->len = 0; } - /** Prints the user stack backtrace * @param str string * @returns Same string as was input with trace info appended, * @note Currently limited to a depth of two. Works from jprobes and kprobes. */ #if 0 -void _stp_ustack_print (char *str) +void _stp_ustack_print(char *str) { - struct pt_regs *nregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) current->thread_info)) - 1; - _stp_printf ("%p : [user]\n", (int64_t)REG_IP(nregs)); + struct pt_regs *nregs = ((struct pt_regs *)(THREAD_SIZE + (unsigned long)current->thread_info)) - 1; + _stp_printf("%p : [user]\n", (int64_t) REG_IP(nregs)); if (REG_SP(nregs)) - _stp_printf ("%p : [user]\n", (int64_t)(*(unsigned long *)REG_SP(nregs))); + _stp_printf("%p : [user]\n", (int64_t) (*(unsigned long *)REG_SP(nregs))); } #endif /* 0 */ diff --git a/runtime/sym.h b/runtime/sym.h index 631a5bbf..0bb64c13 100644 --- a/runtime/sym.h +++ b/runtime/sym.h @@ -84,6 +84,7 @@ struct _stp_module *_stp_modules_by_addr[STP_MAX_MODULES]; /* the number of modules in the arrays */ int _stp_num_modules = 0; +static unsigned long _stp_kretprobe_trampoline = 0; unsigned long _stp_module_relocate (const char *module, const char *section, unsigned long offset); static struct _stp_module *_stp_get_unwind_info (unsigned long addr); diff --git a/runtime/transport/symbols.c b/runtime/transport/symbols.c index a81e594f..83a3a635 100644 --- a/runtime/transport/symbols.c +++ b/runtime/transport/symbols.c @@ -200,6 +200,8 @@ static int _stp_init_kernel_symbols(void) _stp_modules[0]->data = _stp_kallsyms_lookup_name("_etext"); _stp_modules[0]->text_size = _stp_modules[0]->data - _stp_modules[0]->text; _stp_modules_by_addr[0] = _stp_modules[0]; + + _stp_kretprobe_trampoline = _stp_kallsyms_lookup_name("kretprobe_trampoline"); return 0; } diff --git a/runtime/unwind.c b/runtime/unwind.c index 483c9345..e8cba72e 100644 --- a/runtime/unwind.c +++ b/runtime/unwind.c @@ -321,7 +321,7 @@ static unsigned long read_pointer(const u8 **pLoc, const void *end, signed ptrTy return 0; } if ((ptrType & DW_EH_PE_indirect) - && __stp_get_user(value, (unsigned long *)value)) + && __stp_get_user(value, (unsigned long *)value)) return 0; *pLoc = ptr.p8; @@ -621,15 +621,62 @@ static u32 *_stp_search_unwind_hdr(unsigned long pc, struct _stp_module *m) 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" +}; +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. */ + * number in case of an error. A positive return means unwinding is finished; + * don't try to fallback to dumping addresses on the stack. */ int unwind(struct unwind_frame_info *frame) { #define FRAME_REG(r, t) (((t *)frame)[reg_info[r].offs]) const u32 *fde, *cie = NULL; const u8 *ptr = NULL, *end = NULL; - unsigned long pc = UNW_PC(frame) - frame->call_frame; + unsigned long pc = UNW_PC(frame); unsigned long tableSize, startLoc = 0, endLoc = 0, cfa; unsigned i; signed ptrType = -1; @@ -637,6 +684,15 @@ int unwind(struct unwind_frame_info *frame) struct _stp_module *m; struct unwind_state state; + if (pc != _stp_kretprobe_trampoline) + pc -= frame->call_frame; + else { + unsigned long a1, a2, *addr = (unsigned long *)UNW_SP(frame); + __stp_get_user(a1, addr); + __stp_get_user(a2, addr+1); + dbug_unwind(1, "TRAMPOLINE: SP=%lx *SP=%lx *(SP+1)=%lx\n", UNW_SP(frame), a1, a2); + return -EINVAL; + } dbug_unwind(1, "pc=%lx, %lx", pc, UNW_PC(frame)); if (UNW_PC(frame) == 0) @@ -659,16 +715,16 @@ int unwind(struct unwind_frame_info *frame) /* found the fde, now set startLoc and endLoc */ if (fde != NULL) { cie = cie_for_fde(fde, m); - ptr = (const u8 *)(fde + 2); 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); + dbug_unwind(2, "startLoc=%lx, ptrType=%s", 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); if (pc > endLoc) { - dbug_unwind(1, "pc (%lx) > endLoc(%x)\n", pc, endLoc); - /* finished? */ + dbug_unwind(1, "pc (%lx) > endLoc(%lx)\n", pc, endLoc); goto done; } } else { @@ -676,6 +732,8 @@ int unwind(struct unwind_frame_info *frame) fde = NULL; } } + + /* 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)) { @@ -690,13 +748,13 @@ int unwind(struct unwind_frame_info *frame) ptr = (const u8 *)(fde + 2); startLoc = read_pointer(&ptr, (const u8 *)(fde + 1) + *fde, ptrType); - dbug_unwind(3, "startLoc=%p\n", (u64)startLoc); + dbug_unwind(2, "startLoc=%lx, ptrType=%s", startLoc, _stp_eh_enc_name(ptrType)); if (!startLoc) continue; 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); - dbug_unwind(3, "endLoc=%p\n", (u64)endLoc); + dbug_unwind(3, "endLoc=%lx\n", endLoc); if (pc >= startLoc && pc < endLoc) break; } @@ -729,6 +787,7 @@ int unwind(struct unwind_frame_info *frame) case 'R': continue; case 'S': + dbug_unwind(1, "This is a signal frame\n"); frame->call_frame = 0; continue; default: @@ -785,7 +844,6 @@ int unwind(struct unwind_frame_info *frame) goto err; /* update frame */ - dbug_unwind(1, "cie=%lx fde=%lx\n", cie, fde); #ifndef CONFIG_AS_CFI_SIGNAL_FRAME if (frame->call_frame && !UNW_DEFAULT_RA(state.regs[retAddrReg], state.dataAlign)) frame->call_frame = 0; @@ -793,11 +851,11 @@ int unwind(struct unwind_frame_info *frame) cfa = FRAME_REG(state.cfa.reg, unsigned long) + state.cfa.offs; startLoc = min((unsigned long)UNW_SP(frame), cfa); endLoc = max((unsigned long)UNW_SP(frame), cfa); - dbug_unwind(1, "cfa=%lx SP startLoc=%lx, endLoc=%lx\n", cfa, startLoc, endLoc); + dbug_unwind(1, "cfa=%lx startLoc=%lx, endLoc=%lx\n", cfa, startLoc, endLoc); if (STACK_LIMIT(startLoc) != STACK_LIMIT(endLoc)) { startLoc = min(STACK_LIMIT(cfa), cfa); endLoc = max(STACK_LIMIT(cfa), cfa); - dbug_unwind(1, "SP startLoc=%p, endLoc=%p\n", (u64)startLoc, (u64)endLoc); + dbug_unwind(1, "cfa startLoc=%p, endLoc=%p\n", (u64)startLoc, (u64)endLoc); } #ifndef CONFIG_64BIT # define CASES CASE(8); CASE(16); CASE(32) @@ -898,7 +956,11 @@ copy_failed: err: read_unlock(&m->lock); return -EIO; + done: + /* PC was in a range convered by a module but no unwind info */ + /* found for the specific PC. This seems to happen only for kretprobe */ + /* trampolines and at the end of interrupt backtraces. */ read_unlock(&m->lock); return 1; #undef CASES |