This is the mail archive of the gdb-patches@sourceware.org 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]
Other format: [Raw text]

Re: RFC: Longjmp vs LD_POINTER_GUARD revisited


On Monday 16 November 2009 14:36:13, Daniel Jacobowitz wrote:
> On Sun, Nov 15, 2009 at 03:05:33PM -0800, Paul Pluzhnikov wrote:
> > Still it's trivial to discover the canary without disassembling
> > anything (disassembling requires symbols, which may be stripped):
> > there are only 3 different algorithms I've seen (no canary, XOR,
> > XOR+shift-by-9). Hmm, looks like x86_64 has XOR+shift-by-17 now, but
> > ia64, SPARC and PPC all have just "plain XOR".
> 
> I don't know about "trivial" - could you explain how you would do this
> without disassembling?  I think that at the least we'd have to call
> setjmp in the inferior, which has risks with signals / multiple
> threads / etc.  I don't think we call functions in the inferior for
> discovery otherwise.

I've had this patch below laying here for ages, that does
what Paul is suggesting, while trying to avoid infcalls.
It's quite ugly...  I've been meaning to post this for ages,
but I'm a bit embarrassed by the hack :-)  It doesn't work in
every possible case.


There are two issues that made me try this route:

 - single-stepping all the way through longjmp turned out
   to be slow.  Did you not perceive that too?  Maybe we
   could limit the amount of single-stepping by not placing
   the longjmp breakpoint at 'longjmp', but at one of its callees
   that's closer to the real long jump.

 - I've tried a similar hack as yours and found that I had
   to add several more "functions still within longjmp" special
   cases.  I had tried it on netbsd and Windows too.  NetBSD
   was also bad at needing special casing.  I'll post the relevant
   bits of the patch in a bit, when I find it :-)

I guess we could make use-infcalls-for-discovery safer by forcing
scheduler-locking, and, by deferring asynchronous signals the
inferior catches while running the infcall.  I've got a heavily
modified gdbserver that defers signals similarly (places signals
on a queue until safe to deliver, and lets the inferior run a bit
more), which works quite well, but it's not a quick hack to
implement it, and gdb is quite different from gdbserver.

> (Also I'm not entirely comfortable having GDB call functions
> silently.  We do this for malloc, yes - IMO there ought to be an
> option to turn that off.  This is a nasty gotcha that folks using GDB
> for software forensics may not consider...)

Yeah, calling malloc in the inferior can also deadlock it (if done at
a time that e.g., the inferior was already holding a malloc mutex).


-- 
Pedro Alves

Index: src/gdb/i386-tdep.c
===================================================================
--- src.orig/gdb/i386-tdep.c	2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-tdep.c	2009-06-12 22:58:41.000000000 +0100
@@ -1605,7 +1605,7 @@ i386_dummy_id (struct gdbarch *gdbarch, 
    This address is copied into PC.  This routine returns non-zero on
    success.  */
 
-static int
+int
 i386_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
 {
   gdb_byte buf[4];
Index: src/gdb/i386-linux-nat.c
===================================================================
--- src.orig/gdb/i386-linux-nat.c	2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-linux-nat.c	2009-06-13 00:24:33.000000000 +0100
@@ -716,6 +716,31 @@ ps_get_thread_area (const struct ps_proc
   *(int *)base = desc[1];
   return PS_OK;
 }
+
+static int
+i386_linux_get_thread_area (struct target_ops *ops, CORE_ADDR *addr)
+{
+  int len = TYPE_LENGTH (builtin_type (target_gdbarch)->builtin_func_ptr);
+  struct regcache *regcache = get_thread_regcache (inferior_ptid);
+  void *tcbhead;
+  ULONGEST gs;
+  const int reg_thread_area = 3; /* bits to scale down register value.  */
+  int idx;
+  ps_err_e ps_err;
+  struct ps_prochandle prochandle;
+
+  regcache_cooked_read_unsigned (regcache, I386_GS_REGNUM, &gs);
+
+  idx = gs >> reg_thread_area;
+  prochandle.pid = ptid_get_pid (inferior_ptid);
+  ps_err = ps_get_thread_area (&prochandle, prochandle.pid, idx, &tcbhead);
+  if (ps_err != PS_OK)
+    return 0;
+
+  *addr = (CORE_ADDR) tcbhead;
+  return 1;
+}
+
 
 
 /* The instruction for a GNU/Linux system call is:
@@ -843,6 +868,8 @@ _initialize_i386_linux_nat (void)
   t->to_fetch_registers = i386_linux_fetch_inferior_registers;
   t->to_store_registers = i386_linux_store_inferior_registers;
 
+  t->to_get_thread_area = i386_linux_get_thread_area;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, i386_linux_new_thread);
Index: src/gdb/i386-linux-tdep.c
===================================================================
--- src.orig/gdb/i386-linux-tdep.c	2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-linux-tdep.c	2009-06-13 00:23:58.000000000 +0100
@@ -447,6 +447,138 @@ static int i386_linux_sc_reg_offset[] =
   0 * 4				/* %gs */
 };
 
+static char *setjmp_names[] =
+  {
+    "setjmp",
+    "setjmp@plt",
+    "_setjmp",
+    "_setjmp@plt",
+    "__sigsetjmp",
+    "__sigsetjmp@plt"
+  };
+
+static int
+setjmp_p (CORE_ADDR pc)
+{
+  struct minimal_symbol *msym;
+  int i;
+  const char *symname;
+
+  msym = lookup_minimal_symbol_by_pc (pc);
+  if (msym == NULL)
+    return 0;
+
+  symname = SYMBOL_LINKAGE_NAME (msym);
+  if (symname == NULL)
+    return 0;
+
+  for (i = 0; i < ARRAY_SIZE (setjmp_names); i++)
+    if (strcmp (symname, setjmp_names[i]) == 0)
+      return 1;
+
+  return 0;
+}
+
+static int
+is_setjmp_return_p (CORE_ADDR address)
+{
+  gdb_byte buf[8];
+  if (target_read_memory (address - 5, buf, 5) == 0)
+    {
+      /* callq <32-bit offset> */
+      if (buf[0] == 0xe8)
+	{
+	  /* eip-relative address */
+	  int32_t disp = extract_signed_integer (&buf[1], sizeof (int32_t));
+	  if (setjmp_p (address + disp))
+	    return 1;
+	}
+    }
+
+  return 0;
+}
+
+/* Rotate right X by N bits.  */
+#define ror32(x, n) (((x) >> ((int) (n))) | ((x) << (32 - (int) (n))))
+
+static int
+i386_linux_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
+{
+  CORE_ADDR tcbhead;
+  CORE_ADDR pointer_guard;
+  CORE_ADDR address;
+  gdb_byte buf[4];
+  int status;
+  int ptr_guard_offset;
+
+  /* glibc pointer mangles the PC in jmp_buf.  Unfortunately, there is
+     no blessed public API we can use to unmangle it, and, the
+     mangling algorithm itself has changed throughout glibc releases.
+     Some versions xor the address with a canary stored in the thread
+     control block, others apply an extra rotate on top for extra
+     mangling.
+
+     Users are supposed to be able to disable this mangling by
+     exporting LD_POINTER_GUARD=0, but, some versions got that wrong:
+     some got it backwards when set and require `LD_POINTER_GUARD=1';
+     others disable the xor, but still do the rotate.
+
+     What we do is, access glibc's private data, and try to reverse
+     the mangling using the different possible algorithms.  We know
+     we've succeeded when the longjmp's target PC points at an
+     instruction right after a call to setjmp -- that is, that
+     setjmp's return.  If all unmangling attempts fail, we assume that
+     the pristine PC extracted out of the jmp_buf is the best
+     choice.  */
+
+  if (!i386_get_longjmp_target (frame, pc))
+    return 0;
+
+  address = *pc;
+
+  /* Check if we already have a valid return address.  */
+  if (is_setjmp_return_p (address))
+    return 1;
+
+  /* The pointer guard is stored in tcbhead, at
+     offsetof (tcbhead_t, pointer_guard).  */
+
+  if (!target_get_thread_area (&tcbhead))
+    return 1; /* Nothing more we can do.  */
+
+  /* Wear sunglasses, please.  This is private data.  */
+
+  ptr_guard_offset = 0x18; /* offsetof (tcbhead_t, pointer_guard) */
+  status = target_read_memory (tcbhead + ptr_guard_offset, buf, 4);
+  if (status != 0)
+    /* Nothing more we can do.  */
+    return 1;
+
+  pointer_guard = extract_unsigned_integer (buf, 4);
+
+  address = ror32 (address, 9);
+
+  /* On some versions of glibc, exporting LD_POINTER_GUARD=0 disables
+     the xor, but still does the rol.  Check if we're there
+     already.  */
+  if (is_setjmp_return_p (address))
+    {
+      *pc = address;
+      return 1;
+    }
+
+  address ^= pointer_guard;
+
+  /* Check if we successfully unmangled the address.  */
+  if (is_setjmp_return_p (address))
+    {
+      *pc = address;
+      return 1;
+    }
+
+  return 1;
+}
+
 static void
 i386_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -468,6 +600,7 @@ i386_linux_init_abi (struct gdbarch_info
   tdep->sizeof_gregset = 17 * 4;
 
   tdep->jb_pc_offset = 20;	/* From <bits/setjmp.h>.  */
+  set_gdbarch_get_longjmp_target (gdbarch, i386_linux_get_longjmp_target);
 
   tdep->sigtramp_p = i386_linux_sigtramp_p;
   tdep->sigcontext_addr = i386_linux_sigcontext_addr;
Index: src/gdb/i386-tdep.h
===================================================================
--- src.orig/gdb/i386-tdep.h	2009-06-12 22:46:40.000000000 +0100
+++ src/gdb/i386-tdep.h	2009-06-12 22:59:23.000000000 +0100
@@ -221,6 +221,8 @@ extern void i386_elf_init_abi (struct gd
 /* Initialize a SVR4 architecture variant.  */
 extern void i386_svr4_init_abi (struct gdbarch_info, struct gdbarch *);
 
+extern int i386_get_longjmp_target (struct frame_info *, CORE_ADDR *);
+
 extern int i386_process_record (struct gdbarch *gdbarch,
                                 struct regcache *regcache, CORE_ADDR addr);
 
Index: src/gdb/target.h
===================================================================
--- src.orig/gdb/target.h	2009-06-12 22:46:47.000000000 +0100
+++ src/gdb/target.h	2009-06-13 00:23:44.000000000 +0100
@@ -445,6 +445,9 @@ struct target_ops
 					      CORE_ADDR load_module_addr,
 					      CORE_ADDR offset);
 
+    /* Returns true on success.  */
+    int (*to_get_thread_area) (struct target_ops *ops, CORE_ADDR *addr);
+
     /* Request that OPS transfer up to LEN 8-bit bytes of the target's
        OBJECT.  The OFFSET, for a seekable object, specifies the
        starting point.  The ANNEX can be used to provide additional
@@ -1090,6 +1093,8 @@ extern char *normal_pid_to_str (ptid_t p
 #define target_region_ok_for_hw_watchpoint(addr, len) \
     (*current_target.to_region_ok_for_hw_watchpoint) (addr, len)
 
+/* TCB.  */
+extern int target_get_thread_area (CORE_ADDR *addr);
 
 /* Set/clear a hardware watchpoint starting at ADDR, for LEN bytes.  TYPE is 0
    for write, 1 for read, and 2 for read/write accesses.  Returns 0 for
Index: src/gdb/target.c
===================================================================
--- src.orig/gdb/target.c	2009-06-12 22:46:47.000000000 +0100
+++ src/gdb/target.c	2009-06-13 00:23:27.000000000 +0100
@@ -628,6 +628,7 @@ update_current_target (void)
       INHERIT (to_find_memory_regions, t);
       INHERIT (to_make_corefile_notes, t);
       /* Do not inherit to_get_thread_local_address.  */
+      /* Do not inherit to_get_thread_area.  */
       INHERIT (to_can_execute_reverse, t);
       /* Do not inherit to_read_description.  */
       INHERIT (to_get_ada_task_ptid, t);
@@ -922,6 +923,20 @@ pop_all_targets (int quitting)
   pop_all_targets_above (dummy_stratum, quitting);
 }
 
+int
+target_get_thread_area (CORE_ADDR *addr)
+{
+  struct target_ops *target;
+
+  for (target = current_target.beneath;
+       target != NULL;
+       target = target->beneath)
+    if (target->to_get_thread_area != NULL)
+      return (*target->to_get_thread_area) (target, addr);
+
+  return 0;
+}
+
 /* Using the objfile specified in OBJFILE, find the address for the
    current thread's thread-local storage with offset OFFSET.  */
 CORE_ADDR
Index: src/gdb/amd64-linux-nat.c
===================================================================
--- src.orig/gdb/amd64-linux-nat.c	2009-06-12 22:46:35.000000000 +0100
+++ src/gdb/amd64-linux-nat.c	2009-06-13 00:24:17.000000000 +0100
@@ -391,6 +391,26 @@ ps_get_thread_area (const struct ps_proc
     }
   return PS_ERR;               /* ptrace failed.  */
 }
+
+static int
+amd64_linux_get_thread_area (struct target_ops *ops, CORE_ADDR *addr)
+{
+  int len = TYPE_LENGTH (builtin_type (target_gdbarch)->builtin_func_ptr);
+  void *tcbhead;
+  ps_err_e ps_err;
+  struct ps_prochandle prochandle;
+  int idx;
+
+  idx = FS;
+  prochandle.ptid = inferior_ptid;
+  ps_err = ps_get_thread_area (&prochandle, GET_LWP (prochandle.ptid),
+			       idx, &tcbhead);
+  if (ps_err != PS_OK)
+    return 0;
+
+  *addr = (CORE_ADDR) tcbhead;
+  return 1;
+}
 
 
 static void (*super_post_startup_inferior) (ptid_t ptid);
@@ -682,6 +702,8 @@ _initialize_amd64_linux_nat (void)
   t->to_fetch_registers = amd64_linux_fetch_inferior_registers;
   t->to_store_registers = amd64_linux_store_inferior_registers;
 
+  t->to_get_thread_area = amd64_linux_get_thread_area;
+
   /* Register the target.  */
   linux_nat_add_target (t);
   linux_nat_set_new_thread (t, amd64_linux_new_thread);
Index: src/gdb/amd64-linux-tdep.c
===================================================================
--- src.orig/gdb/amd64-linux-tdep.c	2009-06-12 22:46:02.000000000 +0100
+++ src/gdb/amd64-linux-tdep.c	2009-06-13 00:22:03.000000000 +0100
@@ -36,6 +36,9 @@
 #include "amd64-tdep.h"
 #include "solib-svr4.h"
 
+#include "target.h"
+#include "inferior.h"
+
 /* Mapping between the general-purpose registers in `struct user'
    format and GDB's register cache layout.  */
 
@@ -258,6 +261,139 @@ amd64_linux_write_pc (struct regcache *r
   regcache_cooked_write_unsigned (regcache, AMD64_LINUX_ORIG_RAX_REGNUM, -1);
 }
 
+static char *setjmp_names[] =
+  {
+    "setjmp",
+    "setjmp@plt",
+    "_setjmp",
+    "_setjmp@plt",
+    "__sigsetjmp",
+    "__sigsetjmp@plt"
+  };
+
+static int
+setjmp_p (CORE_ADDR pc)
+{
+  struct minimal_symbol *msym;
+  int i;
+  const char *symname;
+
+  msym = lookup_minimal_symbol_by_pc (pc);
+  if (msym == NULL)
+    return 0;
+
+  symname = SYMBOL_LINKAGE_NAME (msym);
+  if (symname == NULL)
+    return 0;
+
+  for (i = 0; i < ARRAY_SIZE (setjmp_names); i++)
+    if (strcmp (symname, setjmp_names[i]) == 0)
+      return 1;
+
+  return 0;
+}
+
+static int
+is_setjmp_return_p (CORE_ADDR address)
+{
+  gdb_byte buf[8];
+  if (target_read_memory (address - 5, buf, 5) == 0)
+    {
+      /* callq <32-bit offset> */
+      if (buf[0] == 0xe8)
+	{
+	  /* rip-relative address */
+	  int32_t disp = extract_signed_integer (&buf[1], sizeof (int32_t));
+	  if (setjmp_p (address + disp))
+	    return 1;
+	}
+    }
+
+  return 0;
+}
+
+/* Rotate right X by N bits.  */
+#define ror64(x, n) (((x) >> ((int) (n))) | ((x) << (64 - (int) (n))))
+
+static int
+amd64_linux_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
+{
+  CORE_ADDR tcbhead;
+  CORE_ADDR pointer_guard;
+  CORE_ADDR address;
+  gdb_byte buf[8];
+  int status;
+  int offset;
+  int ptr_guard_offset;
+
+  /* glibc pointer mangles the PC in jmp_buf.  Unfortunately, there is
+     no blessed public API we can use to unmangle it, and, the
+     mangling algorithm itself has changed throughout glibc releases.
+     Some versions xor the address with a canary stored in the thread
+     control block, others apply an extra rotate on top for extra
+     mangling.
+
+     Users are supposed to be able to disable this mangling by
+     exporting LD_POINTER_GUARD=0, but, some versions got that wrong:
+     some got it backwards when set and require `LD_POINTER_GUARD=1';
+     others disable the xor, but still do the rotate.
+
+     What we do is, access glibc's private data, and try to reverse
+     the mangling using the different possible algorithms.  We know
+     we've succeeded when the longjmp's target PC points at an
+     instruction right after a call to setjmp -- that is, that
+     setjmp's return.  If all unmangling attempts fail, we assume that
+     the pristine PC extracted out of the jmp_buf is the best
+     choice.  */
+
+  /* Extract the PC from the jmp_buf.  */
+  if (!amd64_get_longjmp_target (frame, pc))
+    return 0;
+
+  address = *pc;
+
+  /* Check if we already have a valid return address.  */
+  if (is_setjmp_return_p (address))
+    return 1;
+
+  /* The pointer guard is stored in tcbhead, at
+     offsetof (tcbhead_t, pointer_guard).  */
+
+  if (!target_get_thread_area (&tcbhead))
+    return 1; /* Nothing more we can do.  */
+
+  /* Wear sunglasses, please.  This is private data.  */
+  ptr_guard_offset = 0x30; /* offsetof (tcbhead_t, pointer_guard) */
+  status = target_read_memory (tcbhead + ptr_guard_offset, buf, 8);
+  if (status != 0)
+    /* Nothing more we can do.  */
+    return 1;
+
+  pointer_guard = extract_unsigned_integer (buf, 8);
+
+  address = ror64 (address, 17);
+
+  /* On some versions of glibc, exporting LD_POINTER_GUARD=0 disables
+     the xor, but still does the rol.  Check if we're there
+     already.  */
+  if (is_setjmp_return_p (address))
+    {
+      *pc = address;
+      return 1;
+    }
+
+  address ^= pointer_guard;
+
+  /* Check if we successfully unmangled the address.  */
+  if (is_setjmp_return_p (address))
+    {
+      *pc = address;
+      return 1;
+    }
+
+  return 1;
+}
+
 static void
 amd64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -274,6 +410,9 @@ amd64_linux_init_abi (struct gdbarch_inf
   tdep->sc_reg_offset = amd64_linux_sc_reg_offset;
   tdep->sc_num_regs = ARRAY_SIZE (amd64_linux_sc_reg_offset);
 
+  tdep->jb_pc_offset = 7 * 8;
+  set_gdbarch_get_longjmp_target (gdbarch, amd64_linux_get_longjmp_target);
+
   /* GNU/Linux uses SVR4-style shared libraries.  */
   set_solib_svr4_fetch_link_map_offsets
     (gdbarch, svr4_lp64_fetch_link_map_offsets);
Index: src/gdb/amd64-tdep.h
===================================================================
--- src.orig/gdb/amd64-tdep.h	2009-06-12 22:46:02.000000000 +0100
+++ src/gdb/amd64-tdep.h	2009-06-12 22:58:41.000000000 +0100
@@ -98,6 +98,8 @@ extern void amd64_supply_fxsave (struct 
 
 extern void amd64_collect_fxsave (const struct regcache *regcache, int regnum,
 				  void *fxsave);
+
+extern int amd64_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc);
 
 
 /* Variables exported from amd64nbsd-tdep.c.  */
Index: src/gdb/amd64-tdep.c
===================================================================
--- src.orig/gdb/amd64-tdep.c	2009-06-12 22:46:35.000000000 +0100
+++ src/gdb/amd64-tdep.c	2009-06-12 22:58:41.000000000 +0100
@@ -1950,7 +1950,7 @@ amd64_regset_from_core_section (struct g
    address is copied into PC.  This routine returns non-zero on
    success.  */
 
-static int
+int
 amd64_get_longjmp_target (struct frame_info *frame, CORE_ADDR *pc)
 {
   gdb_byte buf[8];
Index: src/gdb/testsuite/gdb.base/longjmp.c
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.c	2009-02-13 19:04:32.000000000 +0000
+++ src/gdb/testsuite/gdb.base/longjmp.c	2009-06-13 00:25:57.000000000 +0100
@@ -19,6 +19,7 @@
 #include <setjmp.h>
 
 jmp_buf env;
+jmp_buf env2;
 
 volatile int longjmps = 0;
 volatile int resumes = 0;
@@ -33,7 +34,7 @@ call_longjmp (jmp_buf *buf)
 void
 hidden_longjmp (void)
 {
-  if (setjmp (env) == 0) /* longjmp caught */
+  if (setjmp (env) == 0)
     {
       call_longjmp (&env);
     }
@@ -41,6 +42,51 @@ hidden_longjmp (void)
     resumes++;
 }
 
+void
+hidden_longjmp_2 (void)
+{
+  if (setjmp (env) == 0)
+    {
+      if (setjmp (env2) == 0)
+	{
+	  longjmps++;
+	  longjmp (env2, 1);
+	}
+      else
+	{
+	  resumes++;
+	  longjmps++;
+	  longjmp (env, 1);
+	}
+    }
+  else
+    {
+      resumes++;
+    }
+}
+
+void
+hidden_longjmp_3_1 (void)
+{
+  if (setjmp (env2) == 0)
+    {
+      longjmps++;
+      longjmp (env2, 1);
+    }
+  else
+    {
+      resumes++;
+      longjmps++;
+      longjmp (env, 1);
+    }
+}
+
+void
+hidden_longjmp_3 (void)
+{
+  hidden_longjmp_3_1 ();
+}
+
 int
 main ()
 {
@@ -72,10 +118,43 @@ main ()
 
   i = 2; /* miss_step_2 */
 
-  /* Pattern 3 - setjmp/longjmp inside stepped-over function.  */
-  hidden_longjmp (); /* patt3 */
+    /* This tests if GDB chooses the longjmp-resume over the
+     step-resume breakpoint.  If GDB chooses wrongly, a step over the
+     hidden_longjmp_3 function will resume the inferior and pass
+     straight the else clause without stopping to step.  */
+  if (setjmp (env) == 0) /* patt3 */
+    hidden_longjmp_3 ();
+  else
+    resumes++;
+
+  i = 3; /* miss_step_3 */
+
+
+  /* Quite similar, but in this case, we step into hidden_longjmp_3
+     before next'ing over the longjmp.  In this case, the step-resume
+     breakpoint will be set in an inner stack.  Check if GDB still
+     detects that the longjmp-resume address is prefered.  */
+  if (setjmp (env) == 0) /* patt4 */
+    hidden_longjmp_3 ();
+  else
+    resumes++;
+
+  i = 4; /* miss_step_4 */
+
+
+  /* Test that we don't miss-handle internal setjmp/longjmp
+     sequences.  */
+
+  /* setjmp/longjmp handled completelly inside the function.  A next
+     over this should not stop at the longjmp resume address.  */
+  hidden_longjmp (); /* patt5 */
+
+  i = 5; /* patt_end5.  */
+
+  /* Same, but with a nested setjmp/longjmp sequence.  */
+  hidden_longjmp_2 (); /* patt6 */
 
-  i = 3; /* patt_end3.  */
+  i = 6; /* patt_end6.  */
 
   return 0;
 }
Index: src/gdb/testsuite/gdb.base/longjmp.exp
===================================================================
--- src.orig/gdb/testsuite/gdb.base/longjmp.exp	2009-02-10 01:28:04.000000000 +0000
+++ src/gdb/testsuite/gdb.base/longjmp.exp	2009-06-14 20:31:19.000000000 +0100
@@ -49,10 +49,15 @@ if ![runto_main] then {
 
 set bp_miss_step_1 [gdb_get_line_number "miss_step_1"]
 set bp_miss_step_2 [gdb_get_line_number "miss_step_2"]
+set bp_miss_step_3 [gdb_get_line_number "miss_step_3"]
+set bp_miss_step_4 [gdb_get_line_number "miss_step_4"]
 
 set bp_start_test_1 [gdb_get_line_number "patt1"]
 set bp_start_test_2 [gdb_get_line_number "patt2"]
 set bp_start_test_3 [gdb_get_line_number "patt3"]
+set bp_start_test_4 [gdb_get_line_number "patt4"]
+set bp_start_test_5 [gdb_get_line_number "patt5"]
+set bp_start_test_6 [gdb_get_line_number "patt6"]
 
 #
 # Pattern 1 - simple longjmp.
@@ -117,7 +122,7 @@ gdb_test_multiple "next" $msg {
 }
 
 #
-# Pattern 3 - setjmp/longjmp inside stepped-over function.
+# Pattern 3 - prefer longjmp-resume
 #
 
 delete_breakpoints
@@ -125,6 +130,87 @@ delete_breakpoints
 gdb_test "break $bp_start_test_3" \
     "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_3.*" \
     "breakpoint at pattern 3 start"
-gdb_test "continue" "patt3.*" "continue to breakpoint at pattern 3 start"
+gdb_test "continue" "patt3.*" \
+    "continue to breakpoint at pattern 3 start"
+
+
+# set safe-net break
+gdb_test "break $bp_miss_step_3" \
+    "Breakpoint.*at.* file .*$srcfile, line.*$bp_miss_step_3.*" \
+    "breakpoint at miss_step_3"
+
+gdb_test "next" "hidden_longjmp_3 \\(\\).*" "next over setjmp (3)"
+
+set msg "next over hidden_longjmp_3 (3)"
+gdb_test_multiple "next" $msg {
+    -re "patt3.*$gdb_prompt $" {
+	pass $msg
+
+	gdb_test "next" "resumes\\+\\+.*" "next into else block (3)"
+	gdb_test "next" "miss_step_3.*" "next into safety net (3)"
+    }
+    -re "miss_step_3.*$gdb_prompt $" {
+	fail $msg
+    }
+}
+
+
+#
+# Pattern 4 - prefer longjmp-resume after step
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_4" \
+    "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_4.*" \
+    "breakpoint at pattern 4 start"
+gdb_test "continue" "patt4.*" "continue to breakpoint at pattern 4 start"
+
+# set safe-net break
+gdb_test "break $bp_miss_step_4" \
+    "Breakpoint.*at.* file .*$srcfile, line.*$bp_miss_step_4.*" \
+    "breakpoint at miss_step_4"
+
+gdb_test "next" "hidden_longjmp_3 \\(\\).*" "next over setjmp (4)"
+gdb_test "step" "hidden_longjmp_3_1 \\(\\).*" "step into hidden_longjmp_3 (4)"
+
+set msg "next over hidden_longjmp_3_1 (4)"
+gdb_test_multiple "next" $msg {
+    -re "patt4.*$gdb_prompt $" {
+	pass $msg
+
+	gdb_test "next" "resumes\\+\\+.*" "next into else block (4)"
+	gdb_test "next" "miss_step_4.*" "next safety net (4)"
+    }
+    -re "miss_step_4.*$gdb_prompt $" {
+	fail $msg
+    }
+}
+
+#
+# Pattern 5 - setjmp/longjmp handled inside stepped-over function.
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_5" \
+    "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_5.*" \
+    "breakpoint at pattern 5 start"
+gdb_test "continue" "patt5.*" "continue to breakpoint at pattern 5 start"
+
+gdb_test "next" "patt_end5.*" "next over patt5"
+
+#
+# Pattern 6 - nested setjmp/longjmp handled inside stepped-over
+# function.
+#
+
+delete_breakpoints
+
+gdb_test "break $bp_start_test_6" \
+    "Breakpoint.*at.* file .*$srcfile, line.*$bp_start_test_6.*" \
+    "breakpoint at pattern 6 start"
+gdb_test "continue" "patt6.*" "continue to breakpoint at pattern 6 start"
+
+gdb_test "next" "patt_end6.*" "next over patt6"
 
-gdb_test "next" "longjmp caught.*" "next over patt3"
Index: src/gdb/gdbthread.h
===================================================================
--- src.orig/gdb/gdbthread.h	2009-06-14 20:29:54.000000000 +0100
+++ src/gdb/gdbthread.h	2009-06-14 20:31:26.000000000 +0100
@@ -63,9 +63,12 @@ struct thread_info
 
   /* User/external stepping state.  */
 
-  /* Step-resume or longjmp-resume breakpoint.  */
+  /* Step-resume breakpoint.  */
   struct breakpoint *step_resume_breakpoint;
 
+  /* longjmp-resume breakpoint.  */
+  struct breakpoint *longjmp_resume_breakpoint;
+
   /* Range to single step within.
 
      If this is nonzero, respond to a single-step signal by continuing
@@ -212,6 +215,9 @@ extern void delete_thread_silent (ptid_t
 /* Delete a step_resume_breakpoint from the thread database. */
 extern void delete_step_resume_breakpoint (struct thread_info *);
 
+/* Delete a longjmp_resume_breakpoint from the thread database.  */
+extern void delete_longjmp_resume_breakpoint (struct thread_info *);
+
 /* Translate the integer thread id (GDB's homegrown id, not the system's)
    into a "pid" (which may be overloaded with extra thread information).  */
 extern ptid_t thread_id_to_pid (int);
Index: src/gdb/infrun.c
===================================================================
--- src.orig/gdb/infrun.c	2009-06-14 20:29:54.000000000 +0100
+++ src/gdb/infrun.c	2009-06-14 21:27:24.000000000 +0100
@@ -495,6 +495,9 @@ follow_exec (ptid_t pid, char *execd_pat
   th->step_range_start = 0;
   th->step_range_end = 0;
 
+  /* Neither can we follow a longjmp through an exec.  */
+  th->longjmp_resume_breakpoint = NULL;
+
   /* The target reports the exec event to the main thread, even if
      some other thread does the exec, and even if the main thread was
      already stopped --- if debugging in non-stop mode, it's possible
@@ -1873,21 +1876,22 @@ infrun_thread_thread_exit (struct thread
 /* Callback for iterate_over_threads.  */
 
 static int
-delete_step_resume_breakpoint_callback (struct thread_info *info, void *data)
+delete_momentary_breakpoints_callback (struct thread_info *info, void *data)
 {
   if (is_exited (info->ptid))
     return 0;
 
   delete_step_resume_breakpoint (info);
+  delete_longjmp_resume_breakpoint (info);
   return 0;
 }
 
-/* In all-stop, delete the step resume breakpoint of any thread that
-   had one.  In non-stop, delete the step resume breakpoint of the
+/* In all-stop, delete the step and longjmp resume breakpoints of any
+   thread that had any.  In non-stop, delete the breakpoint of the
    thread that just stopped.  */
 
 static void
-delete_step_thread_step_resume_breakpoint (void)
+delete_momentary_breakpoints (void)
 {
   if (!target_has_execution
       || ptid_equal (inferior_ptid, null_ptid))
@@ -1900,21 +1904,20 @@ delete_step_thread_step_resume_breakpoin
       /* If in non-stop mode, only delete the step-resume or
 	 longjmp-resume breakpoint of the thread that just stopped
 	 stepping.  */
-      struct thread_info *tp = inferior_thread ();
-      delete_step_resume_breakpoint (tp);
+      delete_momentary_breakpoints_callback (inferior_thread (), NULL);
     }
   else
     /* In all-stop mode, delete all step-resume and longjmp-resume
-       breakpoints of any thread that had them.  */
-    iterate_over_threads (delete_step_resume_breakpoint_callback, NULL);
+       breakpoints of all threads.  */
+    iterate_over_threads (delete_momentary_breakpoints_callback, NULL);
 }
 
 /* A cleanup wrapper. */
 
 static void
-delete_step_thread_step_resume_breakpoint_cleanup (void *arg)
+delete_momentary_breakpoints_cleanup (void *arg)
 {
-  delete_step_thread_step_resume_breakpoint ();
+  delete_momentary_breakpoints ();
 }
 
 /* Pretty print the results of target_wait, for debugging purposes.  */
@@ -1981,8 +1984,9 @@ wait_for_inferior (int treat_exec_as_sig
       (gdb_stdlog, "infrun: wait_for_inferior (treat_exec_as_sigtrap=%d)\n",
        treat_exec_as_sigtrap);
 
-  old_cleanups =
-    make_cleanup (delete_step_thread_step_resume_breakpoint_cleanup, NULL);
+  /* Be sure to delete step-resume and similar breakpoints if
+     something goes wrong.  */
+  old_cleanups = make_cleanup (delete_momentary_breakpoints_cleanup, NULL);
 
   ecs = &ecss;
   memset (ecs, 0, sizeof (*ecs));
@@ -2114,7 +2118,7 @@ fetch_inferior_event (void *client_data)
     {
       struct inferior *inf = find_inferior_pid (ptid_get_pid (ecs->ptid));
 
-      delete_step_thread_step_resume_breakpoint ();
+      delete_momentary_breakpoints (); /* step-resume, etc */
 
       /* We may not find an inferior if this was a process exit.  */
       if (inf == NULL || inf->stop_soon == NO_STOP_QUIETLY)
@@ -3363,10 +3367,6 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
 	    return;
 	  }
 
-	/* We're going to replace the current step-resume breakpoint
-	   with a longjmp-resume breakpoint.  */
-	delete_step_resume_breakpoint (ecs->event_thread);
-
 	/* Insert a breakpoint at resume address.  */
 	insert_longjmp_resume_breakpoint (jmp_buf_pc);
 
@@ -3374,17 +3374,67 @@ infrun: BPSTAT_WHAT_SET_LONGJMP_RESUME (
 	return;
 
       case BPSTAT_WHAT_CLEAR_LONGJMP_RESUME:
-        if (debug_infrun)
-	  fprintf_unfiltered (gdb_stdlog,
-			      "infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
+	{
+	  struct thread_info *tp = ecs->event_thread;
+	  struct frame_info *frame = get_current_frame ();
+	  struct gdbarch *gdbarch = get_frame_arch (frame);
 
-	gdb_assert (ecs->event_thread->step_resume_breakpoint != NULL);
-	delete_step_resume_breakpoint (ecs->event_thread);
+	  if (debug_infrun)
+	    fprintf_unfiltered (gdb_stdlog,
+				"infrun: BPSTAT_WHAT_CLEAR_LONGJMP_RESUME\n");
 
-	ecs->event_thread->stop_step = 1;
-	print_stop_reason (END_STEPPING_RANGE, 0);
-	stop_stepping (ecs);
-	return;
+	  gdb_assert (tp->longjmp_resume_breakpoint != NULL);
+	  delete_longjmp_resume_breakpoint (tp);
+
+	  /* Ignore long jumps that land somewhere inner to what we
+	     were nexting.  Otherwise, if the longjmp took us to an
+	     outer frame compared to the nexting frame, the
+	     step-resume breakpoint location will never be reached, so
+	     stop now.  */
+	  if (tp->step_resume_breakpoint)
+	    {
+	      /* If we can still see the frame where we placed the
+		 step-resume breakpoint in the frame chain, then we're
+		 now somewhere inner.  If not, then one of two things
+		 happened:
+
+		 - The longjmp took us somewhere outer to the nexting
+		 frame --- stop now.
+
+		 - For some reason we couldn't unwind all the way to
+		 the nexting frame (e.g., bad unwinding data for some
+		 frame in between).  Ideally, we never see this
+		 happen.  However, if it does happen, we get a
+		 spurious stop (that is what would happen if we always
+		 stopped at longjmp-resumes anyway), which is much
+		 better than having the process run away.  */
+	      if (!frame_id_eq (get_frame_id (frame),
+				tp->step_resume_breakpoint->frame_id)
+		  && frame_find_by_id (tp->step_resume_breakpoint->frame_id))
+		{
+		  if (debug_infrun)
+		    fprintf_unfiltered (gdb_stdlog, "\
+infrun: longjmp-resume inner than step-resume\n");
+
+		  keep_going (ecs);
+		  return;
+		}
+	      else
+		{
+		  if (debug_infrun)
+		    fprintf_unfiltered (gdb_stdlog, "\
+infrun: step-resume overran by longjmp\n");
+
+		  /* No longer need this.  */
+		  delete_step_resume_breakpoint (tp);
+		}
+	    }
+
+	  ecs->event_thread->stop_step = 1;
+	  print_stop_reason (END_STEPPING_RANGE, 0);
+	  stop_stepping (ecs);
+	  return;
+	}
 
       case BPSTAT_WHAT_SINGLE:
         if (debug_infrun)
@@ -3610,6 +3660,19 @@ infrun: not switching back to stepped th
       return;
     }
 
+  if (ecs->event_thread->longjmp_resume_breakpoint)
+    {
+      if (debug_infrun)
+	 fprintf_unfiltered (gdb_stdlog,
+			     "infrun: longjmp-resume breakpoint is inserted\n");
+
+      /* Having a longjmp-resume breakpoint overrides anything else
+         having to do with stepping commands until that breakpoint is
+         reached.  */
+      keep_going (ecs);
+      return;
+    }
+
   if (ecs->event_thread->step_range_end == 0)
     {
       if (debug_infrun)
@@ -3988,7 +4051,9 @@ infrun: not switching back to stepped th
 static int
 currently_stepping (struct thread_info *tp)
 {
-  return ((tp->step_range_end && tp->step_resume_breakpoint == NULL)
+  return ((tp->step_range_end
+	   && tp->step_resume_breakpoint == NULL
+	   && tp->longjmp_resume_breakpoint == NULL)
  	  || tp->trap_expected
  	  || tp->stepping_through_solib_after_catch
  	  || bpstat_should_step ());
@@ -4205,17 +4270,17 @@ insert_step_resume_breakpoint_at_caller 
 static void
 insert_longjmp_resume_breakpoint (CORE_ADDR pc)
 {
-  /* There should never be more than one step-resume or longjmp-resume
-     breakpoint per thread, so we should never be setting a new
+  /* There should never be more than one longjmp-resume breakpoint per
+     thread, so we should never be setting a new
      longjmp_resume_breakpoint when one is already active.  */
-  gdb_assert (inferior_thread ()->step_resume_breakpoint == NULL);
+  gdb_assert (inferior_thread ()->longjmp_resume_breakpoint == NULL);
 
   if (debug_infrun)
     fprintf_unfiltered (gdb_stdlog,
 			"infrun: inserting longjmp-resume breakpoint at 0x%s\n",
 			paddr_nz (pc));
 
-  inferior_thread ()->step_resume_breakpoint =
+  inferior_thread ()->longjmp_resume_breakpoint =
     set_momentary_breakpoint_at_pc (pc, bp_longjmp_resume);
 }
 
Index: src/gdb/thread.c
===================================================================
--- src.orig/gdb/thread.c	2009-06-14 20:29:54.000000000 +0100
+++ src/gdb/thread.c	2009-06-14 20:31:26.000000000 +0100
@@ -90,6 +90,16 @@ delete_step_resume_breakpoint (struct th
     }
 }
 
+void
+delete_longjmp_resume_breakpoint (struct thread_info *tp)
+{
+  if (tp && tp->longjmp_resume_breakpoint)
+    {
+      delete_breakpoint (tp->longjmp_resume_breakpoint);
+      tp->longjmp_resume_breakpoint = NULL;
+    }
+}
+
 static void
 clear_thread_inferior_resources (struct thread_info *tp)
 {


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