diff options
Diffstat (limited to '0001-Teach-gdb-how-to-unwind-cygwin-_sigbe-and-sigdelayed.patch')
-rw-r--r-- | 0001-Teach-gdb-how-to-unwind-cygwin-_sigbe-and-sigdelayed.patch | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/0001-Teach-gdb-how-to-unwind-cygwin-_sigbe-and-sigdelayed.patch b/0001-Teach-gdb-how-to-unwind-cygwin-_sigbe-and-sigdelayed.patch new file mode 100644 index 0000000..9cee767 --- /dev/null +++ b/0001-Teach-gdb-how-to-unwind-cygwin-_sigbe-and-sigdelayed.patch @@ -0,0 +1,457 @@ +From 3b165f5d903053172dfb2182eea98595b73fd8d1 Mon Sep 17 00:00:00 2001 +From: Jon Turney <jon.turney@dronecode.org.uk> +Date: Tue, 12 Jan 2016 22:49:09 +0000 +Subject: [PATCH 1/3] Teach gdb how to unwind cygwin _sigbe and sigdelayed + frames + +The majority of functions in the cygwin DLL are wrapped by routines which use an +an alternate stack to return via a signal handler if a signal occured while +inside the function. (See [1],[2]) + +At present, these frames cannot be correctly unwound by gdb. There doesn't seem +to currently be a way to correctly describe these frames using DWARF CFI. + +So instead, write a custom unwinder for _sigbe and sigdelayed frames, which gets +the return address from the alternate stack. + +The offset of tls::stackptr from TIB.stacktop is determined by analyzing the +code in _sigbe or sigdelayed. + +This can backtrace from _sigbe and from a sighandler through sigdelayed. + +Implemented for amd64 and i386 + +Issues: + +1. We should detect if we are in the wrapper after the return address has been +popped off the alternate stack, and if so, fetch the return address from the +register it's been popped into. + +2. If there are multiple _sigbe or sigdelayed stack frames to be unwound, this +only unwinds the first one correctly, because we don't unwind the value of the +alternate stack pointer itself. + +This is no worse than currently, when we can't even unwind one of these frame +correctly, but isn't quite correct. + +I guess this could be handled by defining a pseduo-register to track it's value +as we unwind the stack. + +[1] https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=winsup/cygwin/gendef +[2] https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob;f=winsup/cygwin/how-signals-work.txt +--- + gdb/amd64-windows-tdep.c | 57 ++++++++++++ + gdb/i386-cygwin-tdep.c | 28 ++++++ + gdb/windows-tdep.c | 237 +++++++++++++++++++++++++++++++++++++++++++++++ + gdb/windows-tdep.h | 21 +++++ + 4 files changed, 343 insertions(+) + +diff --git a/gdb/amd64-windows-tdep.c b/gdb/amd64-windows-tdep.c +index a092298..c9b9d20 100644 +--- a/gdb/amd64-windows-tdep.c ++++ b/gdb/amd64-windows-tdep.c +@@ -1218,11 +1218,68 @@ amd64_windows_auto_wide_charset (void) + return "UTF-16"; + } + ++static const struct insn_pattern amd64_sigbe_bytes[] = { ++ /* movq $-8,%r11 */ ++ { 0x49, 0xff }, ++ { 0xc7, 0xff }, ++ { 0xc3, 0xff }, ++ { 0xf8, 0xff }, ++ { 0xff, 0xff }, ++ { 0xff, 0xff }, ++ { 0xff, 0xff }, ++ /* xaddq %r11,$tls::stackptr(%r10) */ ++ { 0x4d, 0xff }, ++ { 0x0f, 0xff }, ++ { 0xc1, 0xff }, ++ { 0x9a, 0xff }, ++ { 0x00, 0x00 }, /* 4 bytes for tls::stackptr */ ++ { 0x00, 0x00 }, ++ { 0x00, 0x00 }, ++ { 0x00, 0x00 } ++}; ++ ++static const struct insn_pattern amd64_sigdelayed_bytes[] = { ++ /* movq $-8,%r11 */ ++ { 0x49, 0xff }, ++ { 0xc7, 0xff }, ++ { 0xc3, 0xff }, ++ { 0xf8, 0xff }, ++ { 0xff, 0xff }, ++ { 0xff, 0xff }, ++ { 0xff, 0xff }, ++ /* xaddq %r11,$tls::stackptr(%r12) */ ++ { 0x4d, 0xff }, ++ { 0x0f, 0xff }, ++ { 0xc1, 0xff }, ++ { 0x9c, 0xff }, ++ { 0x24, 0xff }, ++ { 0x00, 0x00 }, /* 4 bytes for tls::stackptr */ ++ { 0x00, 0x00 }, ++ { 0x00, 0x00 }, ++ { 0x00, 0x00 } ++}; ++ ++#define COUNT(x) (sizeof(x)/sizeof(x[0])) ++ ++static const struct insn_pattern_sequence amd64_sigbe = ++ { ++ amd64_sigbe_bytes, COUNT(amd64_sigbe_bytes) ++ }; ++ ++static const struct insn_pattern_sequence amd64_sigdelayed = ++ { ++ amd64_sigdelayed_bytes, COUNT(amd64_sigdelayed_bytes) ++ }; ++ + static void + amd64_windows_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) + { + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + ++ cygwin_sigwrapper_frame_unwind_set_sigbe_pattern (&amd64_sigbe); ++ cygwin_sigwrapper_frame_unwind_set_sigdelayed_pattern (&amd64_sigdelayed); ++ frame_unwind_append_unwinder (gdbarch, &cygwin_sigwrapper_frame_unwind); ++ + /* The dwarf2 unwinder (appended very early by i386_gdbarch_init) is + preferred over the SEH one. The reasons are: + - binaries without SEH but with dwarf2 debug info are correcly handled +diff --git a/gdb/i386-cygwin-tdep.c b/gdb/i386-cygwin-tdep.c +index 79ff478..4fc4686 100644 +--- a/gdb/i386-cygwin-tdep.c ++++ b/gdb/i386-cygwin-tdep.c +@@ -26,6 +26,7 @@ + #include "xml-support.h" + #include "gdbcore.h" + #include "inferior.h" ++#include "frame-unwind.h" + + /* Core file support. */ + +@@ -204,11 +205,38 @@ i386_cygwin_auto_wide_charset (void) + return "UTF-16"; + } + ++static const struct insn_pattern i386_sigbe_bytes[] = { ++ /* movl $-4,%eax */ ++ { 0xb8, 0xff }, ++ { 0xfc, 0xff }, ++ { 0xff, 0xff }, ++ { 0xff, 0xff }, ++ { 0xff, 0xff }, ++ /* xadd %eax,$tls::stackptr(%ebx) */ ++ { 0x0f, 0xff }, ++ { 0xc1, 0xff }, ++ { 0x83, 0xff }, ++ { 0x00, 0x00 }, /* 4 bytes for tls::stackptr */ ++ { 0x00, 0x00 }, ++ { 0x00, 0x00 }, ++ { 0x00, 0x00 } ++}; ++ ++#define COUNT(x) (sizeof(x)/sizeof(x[0])) ++ ++static const struct insn_pattern_sequence i386_sigbe = ++ { ++ i386_sigbe_bytes, COUNT(i386_sigbe_bytes) ++ }; ++ + static void + i386_cygwin_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) + { + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + ++ cygwin_sigwrapper_frame_unwind_set_sigbe_pattern (&i386_sigbe); ++ frame_unwind_append_unwinder (gdbarch, &cygwin_sigwrapper_frame_unwind); ++ + windows_init_abi (info, gdbarch); + + set_gdbarch_skip_trampoline_code (gdbarch, i386_cygwin_skip_trampoline_code); +diff --git a/gdb/windows-tdep.c b/gdb/windows-tdep.c +index dc4e2e4..352df90 100644 +--- a/gdb/windows-tdep.c ++++ b/gdb/windows-tdep.c +@@ -33,6 +33,7 @@ + #include "complaints.h" + #include "solib.h" + #include "solib-target.h" ++#include "frame-unwind.h" + #include "gdbcore.h" + + struct cmd_list_element *info_w32_cmdlist; +@@ -540,3 +541,239 @@ even if their meaning is unknown."), + isn't another convenience variable of the same name. */ + create_internalvar_type_lazy ("_tlb", &tlb_funcs, NULL); + } ++ ++struct cygwin_sigwrapper_frame_cache ++{ ++ CORE_ADDR sp; ++ CORE_ADDR pc; ++ CORE_ADDR prev_pc; ++ int tlsoffset; ++}; ++ ++/* Return non-zero if the instructions at PC match the series ++ described in PATTERN, or zero otherwise. PATTERN is an array of ++ 'struct insn_pattern' objects, terminated by an entry whose mask is ++ zero. ++ ++ When the match is successful, fill INSN[i] with what PATTERN[i] ++ matched. */ ++static int ++insns_match_pattern (struct gdbarch *gdbarch, CORE_ADDR pc, ++ const struct insn_pattern *pattern, int length, ++ gdb_byte *insn) ++{ ++ int i; ++ CORE_ADDR npc = pc; ++ ++ for (i = 0; i < length; i++) ++ { ++ gdb_byte buf; ++ target_read_memory (npc, &buf, 1); ++ insn[i] = buf; ++ if ((insn[i] & pattern[i].mask) == pattern[i].data) ++ npc += 1; ++ else ++ return 0; ++ } ++ return 1; ++} ++ ++const struct insn_pattern_sequence *sigbe_pattern; ++const struct insn_pattern_sequence *sigdelayed_pattern; ++ ++void cygwin_sigwrapper_frame_unwind_set_sigbe_pattern ( ++ const struct insn_pattern_sequence *pattern) ++{ ++ sigbe_pattern = pattern; ++} ++ ++void cygwin_sigwrapper_frame_unwind_set_sigdelayed_pattern ( ++ const struct insn_pattern_sequence *pattern) ++{ ++ sigdelayed_pattern = pattern; ++} ++ ++static void ++cygwin_sigwrapper_frame_analyze (struct gdbarch *gdbarch, CORE_ADDR start, ++ CORE_ADDR end, int *tlsoffset) ++{ ++ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); ++ CORE_ADDR addr; ++ ++ *tlsoffset = 0; ++ ++ for (addr = start; addr < end; addr++) ++ { ++ if (sigbe_pattern) ++ { ++ int len = sigbe_pattern->length; ++ gdb_byte insn[len]; ++ ++ if (insns_match_pattern (gdbarch, addr, sigbe_pattern->pattern, ++ len, insn)) ++ { ++ *tlsoffset = extract_signed_integer (&(insn[len - 4]), 4, ++ byte_order); ++ break; ++ } ++ } ++ ++ if (sigdelayed_pattern) ++ { ++ int len = sigdelayed_pattern->length; ++ gdb_byte insn[len]; ++ ++ if (insns_match_pattern (gdbarch, addr, sigdelayed_pattern->pattern, ++ len, insn)) ++ { ++ *tlsoffset = extract_signed_integer (&(insn[len - 4]), 4, ++ byte_order); ++ break; ++ } ++ } ++ } ++ ++ /* ++ XXX: Perhaps we should also note the address of the xaddq instruction which ++ pops the RA from the sigstack. If PC is after that, we should look in the ++ appropriate register to get the RA, not on the sigstack. ++ */ ++} ++ ++/* Fill THIS_CACHE using the cygwin sigwrapper unwinding data ++ for THIS_FRAME. */ ++ ++static struct cygwin_sigwrapper_frame_cache * ++cygwin_sigwrapper_frame_cache (struct frame_info *this_frame, void **this_cache) ++{ ++ struct gdbarch *gdbarch = get_frame_arch (this_frame); ++ enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); ++ struct cygwin_sigwrapper_frame_cache *cache = *this_cache; ++ ptid_t ptid; ++ CORE_ADDR thread_local_base; ++ CORE_ADDR stacktop; ++ CORE_ADDR signalstackptr; ++ CORE_ADDR ra; ++ const int len = gdbarch_addr_bit (gdbarch)/8; ++ gdb_byte buf[len]; ++ ++ /* Get current PC and SP. */ ++ cache->pc = get_frame_pc (this_frame); ++ get_frame_register (this_frame, gdbarch_sp_regnum(gdbarch), buf); ++ cache->sp = extract_unsigned_integer (buf, len, byte_order); ++ ++ /* Get address of top of stack from thread information block */ ++ ptid = inferior_ptid; ++ target_get_tib_address (ptid, &thread_local_base); ++ ++ read_memory(thread_local_base + len, buf, len); ++ stacktop = extract_unsigned_integer(buf, len, byte_order); ++ ++ if (frame_debug) ++ fprintf_unfiltered (gdb_stdlog, ++ "cygwin_sigwrapper_frame_prev_register TEB.stacktop=%s\n", ++ paddress (gdbarch, stacktop )); ++ ++ /* Find cygtls, relative to stacktop, and read signalstackptr from cygtls */ ++ read_memory(stacktop + cache->tlsoffset, buf, len); ++ signalstackptr = extract_unsigned_integer(buf, len, byte_order); ++ ++ if (frame_debug) ++ fprintf_unfiltered (gdb_stdlog, ++ "cygwin_sigwrapper_frame_prev_register sigsp=%s\n", ++ paddress (gdbarch, signalstackptr)); ++ ++ /* Read return address from signal stack */ ++ read_memory (signalstackptr - len, buf, len); ++ cache->prev_pc = extract_unsigned_integer (buf, len, byte_order); ++ ++ if (frame_debug) ++ fprintf_unfiltered (gdb_stdlog, ++ "cygwin_sigwrapper_frame_prev_register ra=%s\n", ++ paddress (gdbarch, cache->prev_pc)); ++ ++ return cache; ++} ++ ++static struct value * ++cygwin_sigwrapper_frame_prev_register (struct frame_info *this_frame, void **this_cache, ++ int regnum) ++{ ++ struct gdbarch *gdbarch = get_frame_arch (this_frame); ++ struct cygwin_sigwrapper_frame_cache *cache = ++ cygwin_sigwrapper_frame_cache (this_frame, this_cache); ++ ++ if (frame_debug) ++ fprintf_unfiltered (gdb_stdlog, ++ "cygwin_sigwrapper_frame_prev_register %s for pc=%s\n", ++ gdbarch_register_name (gdbarch, regnum), ++ paddress (gdbarch, cache->prev_pc)); ++ ++ if (regnum == gdbarch_pc_regnum (gdbarch)) ++ return frame_unwind_got_address (this_frame, regnum, cache->prev_pc); ++ ++ return frame_unwind_got_register (this_frame, regnum, regnum); ++} ++ ++static void ++cygwin_sigwrapper_frame_this_id (struct frame_info *this_frame, void **this_cache, ++ struct frame_id *this_id) ++{ ++ *this_id = frame_id_build_unavailable_stack (get_frame_func (this_frame)); ++} ++ ++static int ++cygwin_sigwrapper_frame_sniffer (const struct frame_unwind *self, ++ struct frame_info *this_frame, ++ void **this_cache) ++{ ++ struct gdbarch *gdbarch = get_frame_arch (this_frame); ++ struct cygwin_sigwrapper_frame_cache* cache; ++ const char *name; ++ int tlsoffset; ++ CORE_ADDR start, end; ++ ++ CORE_ADDR pc = get_frame_pc (this_frame); ++ find_pc_partial_function (pc, &name, &start, &end); ++ ++ if (!name) ++ return 0; ++ ++ if ((strcmp(name, "_sigbe") != 0) && (strcmp(name, "__sigbe") != 0) && ++ (strcmp(name, "sigdelayed") != 0) && (strcmp(name, "_sigdelayed") != 0)) ++ return 0; ++ ++ if (frame_debug) ++ fprintf_unfiltered (gdb_stdlog, ++ "cygwin_sigwrapper_frame_sniffer name=%s, start=%s, end=%s\n", ++ name, paddress (gdbarch, start), paddress (gdbarch, end)); ++ ++ cygwin_sigwrapper_frame_analyze(gdbarch, start, end, &tlsoffset); ++ if (tlsoffset == 0) ++ return 0; ++ ++ if (frame_debug) ++ fprintf_unfiltered (gdb_stdlog, ++ "cygwin_sigwrapper_frame_sniffer sigstackptroffset=%x\n", ++ tlsoffset); ++ ++ cache = FRAME_OBSTACK_ZALLOC (struct cygwin_sigwrapper_frame_cache); ++ cache->tlsoffset = tlsoffset; ++ ++ *this_cache = cache; ++ cygwin_sigwrapper_frame_cache (this_frame, this_cache); ++ ++ return 1; ++} ++ ++/* Cygwin sigwapper unwinder. */ ++ ++const struct frame_unwind cygwin_sigwrapper_frame_unwind = ++{ ++ NORMAL_FRAME, ++ default_frame_unwind_stop_reason, ++ &cygwin_sigwrapper_frame_this_id, ++ &cygwin_sigwrapper_frame_prev_register, ++ NULL, ++ &cygwin_sigwrapper_frame_sniffer ++}; +diff --git a/gdb/windows-tdep.h b/gdb/windows-tdep.h +index e1c34c2..b16a64a 100644 +--- a/gdb/windows-tdep.h ++++ b/gdb/windows-tdep.h +@@ -32,4 +32,25 @@ extern void windows_xfer_shared_library (const char* so_name, + + extern void windows_init_abi (struct gdbarch_info info, + struct gdbarch *gdbarch); ++ ++extern const struct frame_unwind cygwin_sigwrapper_frame_unwind; ++ ++/* An instruction to match. */ ++struct insn_pattern ++{ ++ gdb_byte data; /* See if it matches this.... */ ++ gdb_byte mask; /* ... with this mask. */ ++}; ++ ++struct insn_pattern_sequence ++{ ++ const struct insn_pattern *pattern; ++ int length; ++}; ++ ++extern void cygwin_sigwrapper_frame_unwind_set_sigbe_pattern( ++ const struct insn_pattern_sequence *pattern); ++extern void cygwin_sigwrapper_frame_unwind_set_sigdelayed_pattern( ++ const struct insn_pattern_sequence *pattern); ++ + #endif +-- +2.6.2 + |