Hello I wrote a test program (attached) to learn how to use libunwind to perform a backtrace from a sigsegv handler. To test the segfault handler the segfault was caused by calling a bad function pointer. Because of this RBP hasn't been updated as there was no function preamble to go to. This seems to screw up the back trace which ends up missing the function that called the bad function pointer as shown below in the example output. The attached patch does solve the problem for my test program but I'm not sure on what weird edge cases I could be creating. When running `make check` the test-ptrace test now fails on my machine. So I'm looking for advice about whether this could be feasible as a fix or not as I know it seems quite crazy.
The following is the output from attached test program. What the output looks like from master: Handler called ip = 55987c2c4324, sp = 7ffe51cad6d0 offset = 29 name = handle_sigsegv ip = 7f5963145e00, sp = 7ffe51cad700 offset = 40 name = killpg ip = 1, sp = 7ffe51cadcb8 offset = 40 name = ip = 55987c2c43bb, sp = 7ffe51cadcc8 offset = 78 name = main ip = 7f5963132223, sp = 7ffe51cadd90 offset = f3 name = __libc_start_main ip = 55987c2c410e, sp = 7ffe51cade50 offset = 2e name = _start What it "should" look like with the missing "crash" function, this is with the patch and what a gdb backtrace shows: Handler called ip = 55ceda4c5324, sp = 7ffc963ff810 offset = 29 name = handle_sigsegv ip = 7fc00c47be00, sp = 7ffc963ff840 offset = 40 name = killpg ip = 1, sp = 7ffc963ffdd8 offset = 40 name = ip = 55ceda4c5340, sp = 7ffc963ffde0 offset = 12 name = crash ip = 55ceda4c53bb, sp = 7ffc963ffdf0 offset = 78 name = main ip = 7fc00c468223, sp = 7ffc963ffeb0 offset = f3 name = __libc_start_main ip = 55ceda4c510e, sp = 7ffc963fff70 offset = 2e name = _start To build the test program I used the following command: gcc -o unwind unwind.c -g -lunwind -lunwind-x86_64 I have been testing on: Arch Linux with gcc version 8.2.1 20181127 (GCC) x86_64 Intel i7 (laptop) Ubuntu 18.04 with gcc 7.3.0 x86_64 Intel xeon (workstation) Thank you Regards Brock
From d9612429e7d5eeb1fdff34f79fde6088388cbb8b Mon Sep 17 00:00:00 2001 From: Brock York <twunknown@gmail.com> Date: Thu, 28 Feb 2019 18:39:26 +1100 Subject: [PATCH] Add a hack to fixup on a bad jump --- src/x86_64/Gstep.c | 104 +++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/src/x86_64/Gstep.c b/src/x86_64/Gstep.c index 10498170..d163f751 100644 --- a/src/x86_64/Gstep.c +++ b/src/x86_64/Gstep.c @@ -158,43 +158,75 @@ unw_step (unw_cursor_t *cursor) } else { - unw_word_t rbp1 = 0; - rbp_loc = DWARF_LOC(rbp, 0); - rsp_loc = DWARF_NULL_LOC; - rip_loc = DWARF_LOC (rbp + 8, 0); - ret = dwarf_get (&c->dwarf, rbp_loc, &rbp1); - Debug (1, "[RBP=0x%lx] = 0x%lx (cfa = 0x%lx) -> 0x%lx\n", - (unsigned long) DWARF_GET_LOC (c->dwarf.loc[RBP]), - rbp, c->dwarf.cfa, rbp1); - - /* Heuristic to determine incorrect guess. For RBP to be a - valid frame it needs to be above current CFA, but don't - let it go more than a little. Note that we can't deduce - anything about new RBP (rbp1) since it may not be a frame - pointer in the frame above. Just check we get the value. */ - if (ret < 0 - || rbp < c->dwarf.cfa - || (rbp - c->dwarf.cfa) > 0x4000) - { - rip_loc = DWARF_NULL_LOC; - rbp_loc = DWARF_NULL_LOC; - } - - c->frame_info.frame_type = UNW_X86_64_FRAME_GUESSED; - c->frame_info.cfa_reg_rsp = 0; - c->frame_info.cfa_reg_offset = 16; - c->frame_info.rbp_cfa_offset = -16; - c->dwarf.cfa += 16; - } + /* + * Check if previous RIP was invalid + * This could happen if a bad function pointer was + * followed and so the stack wasn't updated by the + * preamble + */ + unw_word_t not_used = 0; + if (dwarf_get(&c->dwarf, DWARF_MEM_LOC(c->dwarf, prev_ip), ¬_used) != 0) + { + Debug (2, "Previous RIP was invalid, attempting fixup\n"); - /* Mark all registers unsaved */ - for (i = 0; i < DWARF_NUM_PRESERVED_REGS; ++i) - c->dwarf.loc[i] = DWARF_NULL_LOC; - - c->dwarf.loc[RBP] = rbp_loc; - c->dwarf.loc[RSP] = rsp_loc; - c->dwarf.loc[RIP] = rip_loc; - c->dwarf.use_prev_instr = 1; + /*Test to see if what we think is the previous RIP is valid*/ + unw_word_t new_ip = 0; + if (dwarf_get(&c->dwarf, DWARF_MEM_LOC(c->dwarf, rbp - 8), &new_ip) == 0) { + if (dwarf_get(&c->dwarf, DWARF_MEM_LOC(c->dwarf, new_ip), ¬_used) == 0) { + Debug (2, "RBP - 8 looks valid\n"); + } + } + c->frame_info.cfa_reg_offset = 8; + c->frame_info.cfa_reg_rsp = -1; + c->frame_info.frame_type = UNW_X86_64_FRAME_STANDARD; + /* + * The call should have pushed RIP to the stack + * and since there was no preabmle rbp hasn't been + * touched so it should be just below it + */ + c->dwarf.loc[RIP] = DWARF_LOC (rbp - 8, 0); + c->dwarf.cfa += 8; + } + else + { + unw_word_t rbp1 = 0; + rbp_loc = DWARF_LOC(rbp, 0); + rsp_loc = DWARF_NULL_LOC; + rip_loc = DWARF_LOC (rbp + 8, 0); + ret = dwarf_get (&c->dwarf, rbp_loc, &rbp1); + Debug (1, "[RBP=0x%lx] = 0x%lx (cfa = 0x%lx) -> 0x%lx\n", + (unsigned long) DWARF_GET_LOC (c->dwarf.loc[RBP]), + rbp, c->dwarf.cfa, rbp1); + + /* Heuristic to determine incorrect guess. For RBP to be a + valid frame it needs to be above current CFA, but don't + let it go more than a little. Note that we can't deduce + anything about new RBP (rbp1) since it may not be a frame + pointer in the frame above. Just check we get the value. */ + if (ret < 0 + || rbp < c->dwarf.cfa + || (rbp - c->dwarf.cfa) > 0x4000) + { + rip_loc = DWARF_NULL_LOC; + rbp_loc = DWARF_NULL_LOC; + } + + c->frame_info.frame_type = UNW_X86_64_FRAME_GUESSED; + c->frame_info.cfa_reg_rsp = 0; + c->frame_info.cfa_reg_offset = 16; + c->frame_info.rbp_cfa_offset = -16; + c->dwarf.cfa += 16; + + /* Mark all registers unsaved */ + for (i = 0; i < DWARF_NUM_PRESERVED_REGS; ++i) + c->dwarf.loc[i] = DWARF_NULL_LOC; + + c->dwarf.loc[RBP] = rbp_loc; + c->dwarf.loc[RSP] = rsp_loc; + c->dwarf.loc[RIP] = rip_loc; + c->dwarf.use_prev_instr = 1; + } + } } if (DWARF_IS_NULL_LOC (c->dwarf.loc[RBP])) -- 2.20.1
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <string.h> #include <execinfo.h> #include <sys/types.h> #include <sys/ucontext.h> #include <unistd.h> #include <sys/ptrace.h> #define UNW_LOCAL_ONLY #include <libunwind.h> void local_backtrace() { unw_cursor_t cursor; unw_context_t uc; unw_word_t ip, sp, offset; unw_getcontext(&uc); unw_init_local(&cursor, &uc); char name[1000]; while (unw_step(&cursor) > 0) { unw_get_reg(&cursor, UNW_REG_IP, &ip); unw_get_reg(&cursor, UNW_REG_SP, &sp); memset(name, 0, sizeof(char) * 1000); unw_get_proc_name(&cursor, name, sizeof(char) * 1000, &offset); printf ("ip = %lx, sp = %lx offset = %lx name = %s\n", (long) ip, (long) sp, (long) offset, name); } } void handle_sigsegv(int signal, siginfo_t *info, void *ucontext) { printf("Handler called\n"); local_backtrace(); exit(-1); } void (*invalid_function)() = (void*)1; void crash() { invalid_function(); } int main(int argc, char *argv[]) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = handle_sigsegv; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); crash(); return 0; }
_______________________________________________ Libunwind-devel mailing list Libunwind-devel@nongnu.org https://lists.nongnu.org/mailman/listinfo/libunwind-devel