This is the mail archive of the gdb-patches@sourceware.cygnus.com mailing list for the GDB project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Fix for Linux/i386 signal trampolines


Hi Jim,

I noticed that backtraces through signal handlers didn't work properly
on my Linux box.  It turned out that the handling of signal trampolines
was a bit broken.  Only worked for Linux 2.0.x with libc5.  I fixed it
such that it works with Linux 2.2.12.  I haven't tested other versions
but they should work fine too, since I believe that the method I use
to find the sigcontext structure on the stack is guaranteed to work as
long as the kernel maintains binary compatibility.

I also added support for systems that use glibc (libc6), but that only
works for glibc 2.1.2 an beyond.  The approach depends on some library
internals, but I have discussed this with Ulrich Drepper, the glibc
maintainer, and the things the code depends on (the __restore and
__restore_rt routines) aren't likely to change.  I didn't add support
for older versions of glibc, since they use signal trampolines that
are more complicated, and thus harder to recognize.  I don't think
this is a real problem.

Unfortunately stepping through signal trampolines (and therefore
stepping out of signal handlers) is broken.  I've localized the
problem, but I'm not sure how to fix it.  I'll bring it up on the gdb
mailing list to see if there are people with bright ideas about it...

Mark


2000-01-31  Mark Kettenis  <kettenis@gnu.org>

	Fix support for Linux/i386 signal trampolines.  The old approach
	didn't work for Linux 2.2 and beyond, and didn't work with recent
	versions of the GNU C library.
	* i386-tdep.c (LINUX_RT_SIGTRAMP_INSN0, LINUX_RT_SIGTRAMP_OFFSET0,
	LINUX_RT_SIGTRAMP_INSN1, LINUX_RT_SIGTRAMP_OFFSET1): New defines.
	(linux_rt_sigtramp_code): New variable.
	(LINUX_RT_SIGTRAMP_LEN): New define.
	(i386_linux_rt_sigtramp_start): New function.  Detect start of
	signal trampolines for RT signals.
	(i386_linux_sigtramp): Add support for RT signals.
	(i386_linux_sigcontext_addr): New function.
	(i386_linux_sigtramp_saved_pc, i386_linux_sigtramp_saved_sp):
	Reimplement in terms of i386_linux_sigcontext_addr.
	* config/i386/tm-linux.h (LINUX_SIGCONTEXT_SIZE): Removed.
	(IN_SIGTRAMP): Recognize the names of the signal tranmpolines used
	by recent versions of the GNU C library.


Index: gdb/i386-tdep.c
===================================================================
RCS file: /cvs/gdb/gdb/gdb/i386-tdep.c,v
retrieving revision 1.1.1.11
diff -u -r1.1.1.11 i386-tdep.c
--- gdb/i386-tdep.c	1999/10/19 02:46:36	1.1.1.11
+++ gdb/i386-tdep.c	2000/01/31 21:17:38
@@ -776,11 +776,19 @@
 
 #ifdef I386_LINUX_SIGTRAMP
 
-/* When the i386 Linux kernel calls a signal handler, the return
-   address points to a bit of code on the stack.  This function
-   returns whether the PC appears to be within this bit of code.
+/* Linux has two flavors of signals.  Normal signal handlers, and
+   "realtime" (RT) signals.  The RT signals can provide additional
+   information to the signal handler if the SA_SIGINFO flag is set
+   when establishing a signal handler using `sigaction'.  It is not
+   unlikely that future versions of Linux will support SA_SIGINFO for
+   normal signals too.  */
+
+/* When the i386 Linux kernel calls a signal handler and the
+   SA_RESTORER flag isn't set, the return address points to a bit of
+   code on the stack.  This function returns whether the PC appears to
+   be within this bit of code.
 
-   The instruction sequence is
+   The instruction sequence for normal signals is
        pop    %eax
        mov    $0x77,%eax
        int    $0x80
@@ -794,8 +802,16 @@
    order to identify a signal trampoline, but there doesn't seem to be
    any other way.  The IN_SIGTRAMP macro in tm-linux.h arranges to
    only call us if no function name could be identified, which should
-   be the case since the code is on the stack.  */
+   be the case since the code is on the stack.
 
+   Detection of signal trampolines for handlers that set the
+   SA_RESTORER flag is in general not possible.  Unfortunately this is
+   what the GNU C Library has been doing for quite some time now.
+   However, as of version 2.1.2, the GNU C Library uses signal
+   trampolines (named __restore and __restore_rt) that are identical
+   to the ones used by the kernel.  Therefore, these trampolines are
+   supported too.  */
+
 #define LINUX_SIGTRAMP_INSN0 (0x58)	/* pop %eax */
 #define LINUX_SIGTRAMP_OFFSET0 (0)
 #define LINUX_SIGTRAMP_INSN1 (0xb8)	/* mov $NNNN,%eax */
@@ -859,51 +875,145 @@
   return pc;
 }
 
+/* This function does the same for RT signals.  Here the instruction
+   sequence is
+       mov    $0xad,%eax
+       int    $0x80
+   or 0xb8 0xad 0x00 0x00 0x00 0xcd 0x80.
+
+   The effect is to call the system call rt_sigreturn.  */
+
+#define LINUX_RT_SIGTRAMP_INSN0 (0xb8)	/* mov $NNNN,%eax */
+#define LINUX_RT_SIGTRAMP_OFFSET0 (0)
+#define LINUX_RT_SIGTRAMP_INSN1 (0xcd)	/* int */
+#define LINUX_RT_SIGTRAMP_OFFSET1 (5)
+
+static const unsigned char linux_rt_sigtramp_code[] =
+{
+  LINUX_RT_SIGTRAMP_INSN0, 0xad, 0x00, 0x00, 0x00,	/* mov $0xad,%eax */
+  LINUX_RT_SIGTRAMP_INSN1, 0x80				/* int $0x80 */
+};
+
+#define LINUX_RT_SIGTRAMP_LEN (sizeof linux_rt_sigtramp_code)
+
+/* If PC is in a RT sigtramp routine, return the address of the start
+   of the routine.  Otherwise, return 0.  */
+
+static CORE_ADDR
+i386_linux_rt_sigtramp_start (pc)
+     CORE_ADDR pc;
+{
+  unsigned char buf[LINUX_RT_SIGTRAMP_LEN];
+
+  /* We only recognize a signal trampoline if PC is at the start of
+     one of the two instructions.  We optimize for finding the PC at
+     the start, as will be the case when the trampoline is not the
+     first frame on the stack.  We assume that in the case where the
+     PC is not at the start of the instruction sequence, there will be
+     a few trailing readable bytes on the stack.  */
+
+  if (read_memory_nobpt (pc, (char *) buf, LINUX_RT_SIGTRAMP_LEN) != 0)
+    return 0;
+
+  if (buf[0] != LINUX_RT_SIGTRAMP_INSN0)
+    {
+      if (buf[0] != LINUX_RT_SIGTRAMP_INSN1)
+	return 0;
+
+      pc -= LINUX_RT_SIGTRAMP_OFFSET1;
+
+      if (read_memory_nobpt (pc, (char *) buf, LINUX_RT_SIGTRAMP_LEN) != 0)
+	return 0;
+    }
+
+  if (memcmp (buf, linux_rt_sigtramp_code, LINUX_RT_SIGTRAMP_LEN) != 0)
+    return 0;
+
+  return pc;
+}
+
 /* Return whether PC is in a Linux sigtramp routine.  */
 
 int
 i386_linux_sigtramp (pc)
      CORE_ADDR pc;
 {
-  return i386_linux_sigtramp_start (pc) != 0;
+  return (i386_linux_sigtramp_start (pc) != 0
+	  || i386_linux_rt_sigtramp_start (pc) != 0);
 }
 
-/* Assuming FRAME is for a Linux sigtramp routine, return the saved
-   program counter.  The Linux kernel will set up a sigcontext
-   structure immediately before the sigtramp routine on the stack.  */
+/* Assuming FRAME is for a Linux sigtramp routine, return the address
+   of the associated sigcontext structure.  */
 
 CORE_ADDR
-i386_linux_sigtramp_saved_pc (frame)
+i386_linux_sigcontext_addr (frame)
      struct frame_info *frame;
 {
   CORE_ADDR pc;
 
   pc = i386_linux_sigtramp_start (frame->pc);
-  if (pc == 0)
-    error ("i386_linux_sigtramp_saved_pc called when no sigtramp");
-  return read_memory_integer ((pc
-			       - LINUX_SIGCONTEXT_SIZE
-			       + LINUX_SIGCONTEXT_PC_OFFSET),
-			      4);
+  if (pc)
+    {
+      CORE_ADDR sp;
+
+      if (frame->next)
+	/* If this isn't the top frame, it must be the frame for the
+	   signal handler itself.  The sigcontext structure lives on
+	   the stack, right after the signum argument.  */
+	return frame->next->frame + 12;
+
+      /* This is the top frame.  We'll have to find the address of the
+	 sigcontext structure by looking at the stack pointer.  Keep
+	 in mind that the first instruction of the sigtramp code is
+	 "pop %eax".  If the PC is at this instruction, adjust the
+	 returned value accordingly.  */
+      sp = read_register (SP_REGNUM);
+      if (pc == frame->pc)
+	return sp + 4;
+      return sp;
+    }
+
+  pc = i386_linux_rt_sigtramp_start (frame->pc);
+  if (pc)
+    {
+      if (frame->next)
+	/* If this isn't the top frame, it must be the frame for the
+	   signal handler itself.  The sigcontext structure is part of
+	   the user context.  A pointer to the user context is passed
+	   as the third argument to the signal handler.  */
+	return read_memory_integer (frame->next->frame + 16, 4) + 20;
+
+      /* This is the top frame.  Again, use the stack pointer to find
+	 the address of the sigcontext structure.  */
+      return read_memory_integer (read_register (SP_REGNUM) + 8, 4) + 20;
+    }
+
+  error ("Couldn't recognize signal trampoline.");
+  return 0;
 }
 
 /* Assuming FRAME is for a Linux sigtramp routine, return the saved
-   stack pointer.  The Linux kernel will set up a sigcontext structure
-   immediately before the sigtramp routine on the stack.  */
+   program counter.  */
 
 CORE_ADDR
-i386_linux_sigtramp_saved_sp (frame)
+i386_linux_sigtramp_saved_pc (frame)
      struct frame_info *frame;
 {
-  CORE_ADDR pc;
+  CORE_ADDR addr;
+  addr = i386_linux_sigcontext_addr (frame);
+  return read_memory_integer (addr + LINUX_SIGCONTEXT_PC_OFFSET, 4);
+}
 
-  pc = i386_linux_sigtramp_start (frame->pc);
-  if (pc == 0)
-    error ("i386_linux_sigtramp_saved_sp called when no sigtramp");
-  return read_memory_integer ((pc
-			       - LINUX_SIGCONTEXT_SIZE
-			       + LINUX_SIGCONTEXT_SP_OFFSET),
-			      4);
+/* Assuming FRAME is for a Linux sigtramp routine, return the saved
+   stack pointer.  */
+
+CORE_ADDR
+i386_linux_sigtramp_saved_sp (frame)
+     struct frame_info *frame;
+{
+  CORE_ADDR addr;
+  addr = i386_linux_sigcontext_addr (frame);
+  return read_memory_integer (addr + LINUX_SIGCONTEXT_SP_OFFSET, 4);
 }
 
 #endif /* I386_LINUX_SIGTRAMP */
Index: gdb/config/i386/tm-linux.h
===================================================================
RCS file: /cvs/gdb/gdb/gdb/config/i386/tm-linux.h,v
retrieving revision 1.1.1.8
diff -u -r1.1.1.8 tm-linux.h
--- gdb/config/i386/tm-linux.h	1999/12/07 03:56:10	1.1.1.8
+++ gdb/config/i386/tm-linux.h	2000/01/31 21:17:38
@@ -30,9 +30,6 @@
 #include "i386/tm-i386.h"
 #include "tm-linux.h"
 
-/* Size of sigcontext, from <asm/sigcontext.h>.  */
-#define LINUX_SIGCONTEXT_SIZE (88)
-
 /* Offset to saved PC in sigcontext, from <asm/sigcontext.h>.  */
 #define LINUX_SIGCONTEXT_PC_OFFSET (56)
 
@@ -108,7 +105,9 @@
    order to support backtracing through calls to signal handlers.  */
 
 #define I386_LINUX_SIGTRAMP
-#define IN_SIGTRAMP(pc, name) ((name) == NULL && i386_linux_sigtramp (pc))
+#define IN_SIGTRAMP(pc, name)                                           \
+  ((name) ? (STREQ ("__restore", name) || STREQ ("__restore_rt", name)) \
+          : i386_linux_sigtramp (pc))
 
 extern int i386_linux_sigtramp PARAMS ((CORE_ADDR));
 


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]