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), &not_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), &not_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

Reply via email to