Fix signal trampoline detection/unwinding on recent FreeBSD/i386 and FreeBSD/amd64

Message ID 54DA9572.1010304@redhat.com
State New, archived
Headers

Commit Message

Pedro Alves Feb. 10, 2015, 11:34 p.m. UTC
  Thanks, updated patch looks good.  Feel free to push.

On 02/10/2015 07:14 PM, John Baldwin wrote:
> On Tuesday, February 10, 2015 05:08:14 PM Pedro Alves wrote:
>> On 02/10/2015 02:50 PM, John Baldwin wrote:
>>>> +     sysctl that returns the location of the signal trampoline.
>>>> +     Note that this fetches the address for the current (gdb) process.
>>>> +     This will be correct for other 64-bit processes, but the signal
>>>> +     trampoline location is not properly set for 32-bit processes. */
>>
>> I'm not sure I understand what does "but the signal trampoline
>> location is not properly set for 32-bit processes" means.  You mean
>> it's not properly set because GDB is 64-bit; or it's not properly set
>> in the kernel; or something else?
> 
> The sysctl is designed to be used against the target process, but I did not
> see an easy way to hook into each run and ptrace attach to invoke the sysctl
> against the inferior directly.  

You'd do something like the patch below, on top of yours.  Completely
untested.  Just for illustration.

However, unless this info is recorded in core dumps, this is all of course
broken for core file debugging ...

Do we _really_ need to know the sigtramp location?  What does the sigtramp
disassembly look like?  How about just detecting the sigtramp
like other platforms do, by recognizing the instructions?  On Linux, this
is just:

  mov $__NR_rt_sigreturn, %rax
  syscall

And is parsed in amd64_linux_sigtramp_p -> amd64_linux_sigtramp_start.

Looking at:

https://github.com/freebsd/freebsd/blob/master/sys/amd64/amd64/sigtramp.S

It looks pretty much the same.  That should make it always work correctly
for (cross) core and remote debugging.

-------------
From 3193c7b56d459d72d76031c299b939fee1afe265 Mon Sep 17 00:00:00 2001
From: Pedro Alves <palves@redhat.com>
Date: Tue, 10 Feb 2015 22:11:42 +0000
Subject: [PATCH] AMD64 FreeBSD: get sigtramp of the current inferior

instead of gdb's.
---
 gdb/amd64fbsd-nat.c    | 103 ++++++++++++++++++++++++++++---------------------
 gdb/amd64fbsd-tdep.c   |  27 ++++++++++---
 gdb/target-debug.h     |   8 ++++
 gdb/target-delegates.c |  33 ++++++++++++++++
 gdb/target.h           |  12 ++++++
 5 files changed, 132 insertions(+), 51 deletions(-)
  

Comments

Mark Kettenis Feb. 11, 2015, midnight UTC | #1
> Date: Tue, 10 Feb 2015 23:34:10 +0000
> From: Pedro Alves <palves@redhat.com>
> 
> Thanks, updated patch looks good.  Feel free to push.

Same here.  Although you guys should really make randomize the
location signal trampoline page.  In that case you should look at
amd64obsd-tdep.c:amd64obsd_sigtramp_p().

> On 02/10/2015 07:14 PM, John Baldwin wrote:
> > On Tuesday, February 10, 2015 05:08:14 PM Pedro Alves wrote:
> >> On 02/10/2015 02:50 PM, John Baldwin wrote:
> >>>> +     sysctl that returns the location of the signal trampoline.
> >>>> +     Note that this fetches the address for the current (gdb) process.
> >>>> +     This will be correct for other 64-bit processes, but the signal
> >>>> +     trampoline location is not properly set for 32-bit processes. */
> >>
> >> I'm not sure I understand what does "but the signal trampoline
> >> location is not properly set for 32-bit processes" means.  You mean
> >> it's not properly set because GDB is 64-bit; or it's not properly set
> >> in the kernel; or something else?
> > 
> > The sysctl is designed to be used against the target process, but I did not
> > see an easy way to hook into each run and ptrace attach to invoke the sysctl
> > against the inferior directly.  
> 
> You'd do something like the patch below, on top of yours.  Completely
> untested.  Just for illustration.
> 
> However, unless this info is recorded in core dumps, this is all of course
> broken for core file debugging ...
> 
> Do we _really_ need to know the sigtramp location?  What does the sigtramp
> disassembly look like?  How about just detecting the sigtramp
> like other platforms do, by recognizing the instructions?  On Linux, this
> is just:
> 
>   mov $__NR_rt_sigreturn, %rax
>   syscall
> 
> And is parsed in amd64_linux_sigtramp_p -> amd64_linux_sigtramp_start.
> 
> Looking at:
> 
> https://github.com/freebsd/freebsd/blob/master/sys/amd64/amd64/sigtramp.S
> 
> It looks pretty much the same.  That should make it always work correctly
> for (cross) core and remote debugging.
> 
> -------------
  
John Baldwin Feb. 11, 2015, 3:32 p.m. UTC | #2
On Tuesday, February 10, 2015 11:34:10 PM Pedro Alves wrote:
> Thanks, updated patch looks good.  Feel free to push.

Note that I do not have read/write access to git, so I believe I need someone 
to push this for me?

> On 02/10/2015 07:14 PM, John Baldwin wrote:
> > On Tuesday, February 10, 2015 05:08:14 PM Pedro Alves wrote:
> >> On 02/10/2015 02:50 PM, John Baldwin wrote:
> >>>> +     sysctl that returns the location of the signal trampoline.
> >>>> +     Note that this fetches the address for the current (gdb) process.
> >>>> +     This will be correct for other 64-bit processes, but the signal
> >>>> +     trampoline location is not properly set for 32-bit processes. */
> >> 
> >> I'm not sure I understand what does "but the signal trampoline
> >> location is not properly set for 32-bit processes" means.  You mean
> >> it's not properly set because GDB is 64-bit; or it's not properly set
> >> in the kernel; or something else?
> > 
> > The sysctl is designed to be used against the target process, but I did
> > not
> > see an easy way to hook into each run and ptrace attach to invoke the
> > sysctl against the inferior directly.
> 
> You'd do something like the patch below, on top of yours.  Completely
> untested.  Just for illustration.
> 
> However, unless this info is recorded in core dumps, this is all of course
> broken for core file debugging ...

Yes, it occurred to me that to make this really reliable I'd have to annotate 
it in core dumps instead.

> Do we _really_ need to know the sigtramp location?  What does the sigtramp
> disassembly look like?  How about just detecting the sigtramp
> like other platforms do, by recognizing the instructions?  On Linux, this
> is just:
> 
>   mov $__NR_rt_sigreturn, %rax
>   syscall
> 
> And is parsed in amd64_linux_sigtramp_p -> amd64_linux_sigtramp_start.

Actually, this does sound far simpler.  I was simply updating the sigtramp 
code that was already present.  I can certainly work on changing both i386
and amd64 to do this instead if that is the preferred method (and it seems to 
be from looking at other targets).
  
Pedro Alves Feb. 11, 2015, 4:40 p.m. UTC | #3
On 02/11/2015 03:32 PM, John Baldwin wrote:

> Actually, this does sound far simpler.  I was simply updating the sigtramp 
> code that was already present.  I can certainly work on changing both i386
> and amd64 to do this instead if that is the preferred method (and it seems to 
> be from looking at other targets).

Yep, that's the preferred method.  That'd be great.

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/amd64fbsd-nat.c b/gdb/amd64fbsd-nat.c
index 762b13f..ca962e1 100644
--- a/gdb/amd64fbsd-nat.c
+++ b/gdb/amd64fbsd-nat.c
@@ -140,6 +140,62 @@  amd64fbsd_supply_pcb (struct regcache *regcache, struct pcb *pcb)
 }
 
 
+static int
+amd64fbsd_get_signal_trampoline (struct target_ops *ops,
+				 struct mem_range *range)
+{
+#ifdef KERN_PROC_SIGTRAMP
+  /* FreeBSD 9.2 and later provide a kern.proc.sigtramp.<pid>
+     sysctl that returns the location of the signal trampoline.  */
+  {
+    int mib[4];
+    struct kinfo_sigtramp kst;
+    size_t len;
+
+    mib[0] = CTL_KERN;
+    mib[1] = KERN_PROC;
+    mib[2] = KERN_PROC_SIGTRAMP;
+    mib[3] = ptid_get_pid (inferior_ptid);
+    len = sizeof (kst);
+    if (sysctl (mib, 4, &kst, &len, NULL, 0) == 0)
+      {
+	CORE_ADDR start_addr = (uintptr_t) kst.ksigtramp_start;
+	CORE_ADDR end_addr = (uintptr_t) kst.ksigtramp_end;
+
+	range->start = start_addr;
+	range->length = end_addr - start_addr;
+	return 1;
+      }
+  }
+#else
+  /* FreeBSD provides a kern.ps_strings sysctl that we can use to
+     locate the sigtramp.  That way we can still recognize a sigtramp
+     if its location is changed in a new kernel.  Of course this is
+     still based on the assumption that the sigtramp is placed
+     directly under the location where the program arguments and
+     environment can be found.  */
+  {
+    int mib[2];
+    long ps_strings;
+    size_t len;
+
+    mib[0] = CTL_KERN;
+    mib[1] = KERN_PS_STRINGS;
+    len = sizeof (ps_strings);
+    if (sysctl (mib, 2, &ps_strings, &len, NULL, 0) == 0)
+      {
+	CORE_ADDR start_addr = ps_strings - 32;
+	CORE_ADDR end_addr = ps_strings;
+
+	range->start = start_addr;
+	range->length = end_addr - start_addr;
+	return 1;
+      }
+  }
+#endif
+  return 0;
+}
+
 static void (*super_mourn_inferior) (struct target_ops *ops);
 
 static void
@@ -166,6 +222,8 @@  _initialize_amd64fbsd_nat (void)
   /* Add some extra features to the common *BSD/i386 target.  */
   t = amd64bsd_target ();
 
+  t->to_get_signal_trampoline = amd64fbsd_get_signal_trampoline;
+
 #ifdef HAVE_PT_GETDBREGS
 
   x86_use_watchpoints (t);
@@ -244,49 +302,4 @@  Please report this to <bug-gdb@gnu.org>."),
     }
 
   SC_RBP_OFFSET = offset;
-
-#ifdef KERN_PROC_SIGTRAMP
-  /* FreeBSD 9.2 and later provide a kern.proc.sigtramp.<pid>
-     sysctl that returns the location of the signal trampoline.
-     Note that this fetches the address for the current (gdb) process.
-     This will be correct for other 64-bit processes, but the signal
-     trampoline location is not properly set for 32-bit processes.  */
-  {
-    int mib[4];
-    struct kinfo_sigtramp kst;
-    size_t len;
-
-    mib[0] = CTL_KERN;
-    mib[1] = KERN_PROC;
-    mib[2] = KERN_PROC_SIGTRAMP;
-    mib[3] = getpid ();
-    len = sizeof (kst);
-    if (sysctl (mib, 4, &kst, &len, NULL, 0) == 0)
-      {
-	amd64fbsd_sigtramp_start_addr = (uintptr_t) kst.ksigtramp_start;
-	amd64fbsd_sigtramp_end_addr = (uintptr_t) kst.ksigtramp_end;
-      }
-  }
-#else
-  /* FreeBSD provides a kern.ps_strings sysctl that we can use to
-     locate the sigtramp.  That way we can still recognize a sigtramp
-     if its location is changed in a new kernel.  Of course this is
-     still based on the assumption that the sigtramp is placed
-     directly under the location where the program arguments and
-     environment can be found.  */
-  {
-    int mib[2];
-    long ps_strings;
-    size_t len;
-
-    mib[0] = CTL_KERN;
-    mib[1] = KERN_PS_STRINGS;
-    len = sizeof (ps_strings);
-    if (sysctl (mib, 2, &ps_strings, &len, NULL, 0) == 0)
-      {
-	amd64fbsd_sigtramp_start_addr = ps_strings - 32;
-	amd64fbsd_sigtramp_end_addr = ps_strings;
-      }
-  }
-#endif
 }
diff --git a/gdb/amd64fbsd-tdep.c b/gdb/amd64fbsd-tdep.c
index abb0cab..69cd186 100644
--- a/gdb/amd64fbsd-tdep.c
+++ b/gdb/amd64fbsd-tdep.c
@@ -87,10 +87,6 @@  static int amd64fbsd_r_reg_offset[] =
   -1				/* %gs */
 };
 
-/* Location of the signal trampoline.  */
-CORE_ADDR amd64fbsd_sigtramp_start_addr = 0x7fffffffffc0ULL;
-CORE_ADDR amd64fbsd_sigtramp_end_addr = 0x7fffffffffe0ULL;
-
 /* From <machine/signal.h>.  */
 int amd64fbsd_sc_reg_offset[] =
 {
@@ -182,6 +178,26 @@  amd64fbsd_collect_uthread (const struct regcache *regcache,
     }
 }
 
+/* Return whether THIS_FRAME corresponds to a NetBSD sigtramp
+   routine.  */
+
+static int
+amd64fbsd_sigtramp_p (struct frame_info *this_frame)
+{
+  struct mem_range range;
+  CORE_ADDR pc;
+
+  if (!target_get_signal_trampoline (&range))
+    {
+      /* Fallback to hardcoded location...  */
+      range.start = 0x7fffffffffc0ULL;
+      range.length = 0x7fffffffffe0ULL - range.start;
+    }
+
+  pc = get_frame_pc (this_frame);
+  return address_in_mem_range (pc, &range);
+}
+
 static void
 amd64fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 {
@@ -199,8 +215,7 @@  amd64fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
 
   amd64_init_abi (info, gdbarch);
 
-  tdep->sigtramp_start = amd64fbsd_sigtramp_start_addr;
-  tdep->sigtramp_end = amd64fbsd_sigtramp_end_addr;
+  tdep->sigtramp_p = amd64fbsd_sigtramp_p;
   tdep->sigcontext_addr = amd64fbsd_sigcontext_addr;
   tdep->sc_reg_offset = amd64fbsd_sc_reg_offset;
   tdep->sc_num_regs = ARRAY_SIZE (amd64fbsd_sc_reg_offset);
diff --git a/gdb/target-debug.h b/gdb/target-debug.h
index 63ce12c..f114a07 100644
--- a/gdb/target-debug.h
+++ b/gdb/target-debug.h
@@ -193,4 +193,12 @@  target_debug_print_signals (unsigned char *sigs)
   fputs_unfiltered (" }", gdb_stdlog);
 }
 
+static void
+target_debug_print_struct_mem_range_p (struct mem_range *range)
+{
+  fprintf_unfiltered (gdb_stdlog, "[%s:%x]",
+		      paddress (target_gdbarch (), range->start),
+		      range->length);
+}
+
 #endif /* TARGET_DEBUG_H */
diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c
index e026179..4f9cb72 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -3764,6 +3764,35 @@  debug_done_generating_core (struct target_ops *self)
   fputs_unfiltered (")\n", gdb_stdlog);
 }
 
+static int
+delegate_get_signal_trampoline (struct target_ops *self, struct mem_range *arg1)
+{
+  self = self->beneath;
+  return self->to_get_signal_trampoline (self, arg1);
+}
+
+static int
+tdefault_get_signal_trampoline (struct target_ops *self, struct mem_range *arg1)
+{
+  return 0;
+}
+
+static int
+debug_get_signal_trampoline (struct target_ops *self, struct mem_range *arg1)
+{
+  int result;
+  fprintf_unfiltered (gdb_stdlog, "-> %s->to_get_signal_trampoline (...)\n", debug_target.to_shortname);
+  result = debug_target.to_get_signal_trampoline (&debug_target, arg1);
+  fprintf_unfiltered (gdb_stdlog, "<- %s->to_get_signal_trampoline (", debug_target.to_shortname);
+  target_debug_print_struct_target_ops_p (&debug_target);
+  fputs_unfiltered (", ", gdb_stdlog);
+  target_debug_print_struct_mem_range_p (arg1);
+  fputs_unfiltered (") = ", gdb_stdlog);
+  target_debug_print_int (result);
+  fputs_unfiltered ("\n", gdb_stdlog);
+  return result;
+}
+
 static void
 install_delegators (struct target_ops *ops)
 {
@@ -4045,6 +4074,8 @@  install_delegators (struct target_ops *ops)
     ops->to_prepare_to_generate_core = delegate_prepare_to_generate_core;
   if (ops->to_done_generating_core == NULL)
     ops->to_done_generating_core = delegate_done_generating_core;
+  if (ops->to_get_signal_trampoline == NULL)
+    ops->to_get_signal_trampoline = delegate_get_signal_trampoline;
 }
 
 static void
@@ -4189,6 +4220,7 @@  install_dummy_methods (struct target_ops *ops)
   ops->to_decr_pc_after_break = default_target_decr_pc_after_break;
   ops->to_prepare_to_generate_core = tdefault_prepare_to_generate_core;
   ops->to_done_generating_core = tdefault_done_generating_core;
+  ops->to_get_signal_trampoline = tdefault_get_signal_trampoline;
 }
 
 static void
@@ -4333,4 +4365,5 @@  init_debug_target (struct target_ops *ops)
   ops->to_decr_pc_after_break = debug_decr_pc_after_break;
   ops->to_prepare_to_generate_core = debug_prepare_to_generate_core;
   ops->to_done_generating_core = debug_done_generating_core;
+  ops->to_get_signal_trampoline = debug_get_signal_trampoline;
 }
diff --git a/gdb/target.h b/gdb/target.h
index fb60123..e509e5e 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -1141,6 +1141,15 @@  struct target_ops
     void (*to_done_generating_core) (struct target_ops *)
       TARGET_DEFAULT_IGNORE ();
 
+    /* Return the current target's signal trampoline address range.
+       Return true on success, false if the information isn't
+       available.  This is implemented on targets that need to query
+       the kernel for this information.  Most ports we get at this
+       information through gdbarch/osabi instead.  */
+    int (*to_get_signal_trampoline) (struct target_ops *ops,
+				     struct mem_range *range)
+      TARGET_DEFAULT_RETURN (0);
+
     int to_magic;
     /* Need sub-structure for target machine related rather than comm related?
      */
@@ -2055,6 +2064,9 @@  extern int simple_verify_memory (struct target_ops* ops,
 int target_verify_memory (const gdb_byte *data,
 			  CORE_ADDR memaddr, ULONGEST size);
 
+#define target_get_signal_trampoline(range)				\
+  (*current_target.to_get_signal_trampoline) (&current_target, range)
+
 /* Routines for maintenance of the target structures...
 
    complete_target_initialization: Finalize a target_ops by filling in