From 1822f7cf064bc795a31e8eeb736e48593abb5af2 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/5] 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-windows-tdep.c  |  28 +++++
 gdb/windows-tdep.c       | 237 +++++++++++++++++++++++++++++++++++++++
 gdb/windows-tdep.h       |  20 ++++
 4 files changed, 341 insertions(+), 1 deletion(-)

diff --git a/gdb/amd64-windows-tdep.c b/gdb/amd64-windows-tdep.c
index 3f9cbebc5d3..130d421be7e 100644
--- a/gdb/amd64-windows-tdep.c
+++ b/gdb/amd64-windows-tdep.c
@@ -1208,11 +1208,66 @@ amd64_windows_auto_wide_charset (void)
   return "UTF-16";
 }
 
-/* Common parts for gdbarch initialization for Windows and Cygwin on AMD64.  */
+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_common (gdbarch_info info, struct gdbarch *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 correctly handled
diff --git a/gdb/i386-windows-tdep.c b/gdb/i386-windows-tdep.c
index 4ffaa4562b5..26132f0a800 100644
--- a/gdb/i386-windows-tdep.c
+++ b/gdb/i386-windows-tdep.c
@@ -27,6 +27,7 @@
 #include "xml-support.h"
 #include "gdbcore.h"
 #include "inferior.h"
+#include "frame-unwind.h"
 
 /* Core file support.  */
 
@@ -199,6 +200,30 @@ i386_windows_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)
+  };
+
 /* Implement the "push_dummy_call" gdbarch method.  */
 
 static CORE_ADDR
@@ -236,6 +261,9 @@ i386_windows_init_abi_common (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);
+
   set_gdbarch_skip_trampoline_code (gdbarch, i386_windows_skip_trampoline_code);
 
   set_gdbarch_skip_main_prologue (gdbarch, i386_skip_main_prologue);
diff --git a/gdb/windows-tdep.c b/gdb/windows-tdep.c
index 019d4d18b64..a2c7a1efa97 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"
 #include "coff/internal.h"
 #include "libcoff.h"
@@ -1120,3 +1121,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 = (struct cygwin_sigwrapper_frame_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 00ae7a6258e..9c2efcc0516 100644
--- a/gdb/windows-tdep.h
+++ b/gdb/windows-tdep.h
@@ -48,4 +48,24 @@ extern void cygwin_init_abi (struct gdbarch_info info,
 
 extern bool is_linked_with_cygwin_dll (bfd *abfd);
 
+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.31.1.windows.1

