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]

[rfc, arm] Backtrace out of restart handler in vector page


Hello,

when restarting an interrupted system call, the ARM kernel will in certain
circumstances install a special kernel stub to handle the final return to
user space.  The stub is located inside the vector page, and will pop the
PC from the stack.

When that system call is interrupted a second time, GDB is unable to
backtrace properly, since it does not know of this mini-stack frame.

The following patch fixes this problem by installing a special unwind
handler (in arm-linux-tdep.c instead of arm-tdep.c since this is Linux
specific).  This will have an effect only on recent kernels that allow
reading the vector page via ptrace.

Tested on armv7l-linux-gnueabi; fixes the following failures:
FAIL: gdb.server/ext-attach.exp: backtrace 2
FAIL: gdb.threads/attachstop-mt.exp: attach3 to stopped bt
FAIL: gdb.threads/attachstop-mt.exp: attach4, exit leaves process sleeping
FAIL: gdb.threads/attach-stopped.exp: threaded: attach2, exit leaves process sleeping
FAIL: gdb.threads/pthreads.exp: check backtrace from main thread
FAIL: gdb.threads/pthreads.exp: apply backtrace command to all three threads

Any comments?  I'm planning to commit within a couple of days.

Bye,
Ulrich


ChangeLog:

	* arm-linux-tdep.c: Include "frame-unwind.h".
	(struct arm_linux_stub_cache): New data structure.
	(arm_linux_make_stub_cache): New function.
	(arm_linux_stub_this_id): Likewise.
	(arm_linux_stub_prev_register): Likewise.
	(arm_linux_stub_unwind_sniffer): Likewise.
	(arm_linux_stub_unwind): New global variable.
	(arm_linux_init_abi): Install unwinder.
	* arm-tdep.c (arm_psr_thumb_bit): Make global.
	* arm-tdep.c (arm_psr_thumb_bit): Add prototype.

diff -urNp gdb-orig/gdb/arm-linux-tdep.c gdb-head/gdb/arm-linux-tdep.c
--- gdb-orig/gdb/arm-linux-tdep.c	2011-02-02 16:28:10.000000000 +0100
+++ gdb-head/gdb/arm-linux-tdep.c	2011-02-23 18:55:18.000000000 +0100
@@ -32,6 +32,7 @@
 #include "regset.h"
 #include "trad-frame.h"
 #include "tramp-frame.h"
+#include "frame-unwind.h"
 #include "breakpoint.h"
 
 #include "arm-tdep.h"
@@ -932,6 +933,119 @@ arm_linux_displaced_step_copy_insn (stru
   return dsc;
 }
 
+
+/* Unwinder to handle Linux kernel helpers.  These are special routines
+   provided by the kernel in the vector page starting at 0xffff0000.
+   Most of these routines do not set up a stack frame, so we leave them
+   to the generic stub unwinder.  However, there is one stub, used for
+   system call restart handling in certain situations, which *does*
+   have a stack frame (containing just a PC to restore).  The following
+   set of unwind handlers takes care of this case.  */
+
+struct arm_linux_stub_cache
+{
+  CORE_ADDR prev_sp;
+  CORE_ADDR prev_pc;
+  CORE_ADDR prev_ps;
+};
+
+static struct arm_linux_stub_cache *
+arm_linux_make_stub_cache (struct frame_info *this_frame)
+{
+  struct gdbarch *gdbarch = get_frame_arch (this_frame);
+  ULONGEST t_bit = arm_psr_thumb_bit (gdbarch);
+  struct arm_linux_stub_cache *cache;
+  CORE_ADDR sp, pc, ps;
+
+  /* Current PC contains a "pop { pc }" instruction.  Compute the
+     updated SP, PC, and PS values resulting from this instruction.  */
+
+  cache = FRAME_OBSTACK_ZALLOC (struct arm_linux_stub_cache);
+
+  sp = get_frame_register_unsigned (this_frame, ARM_SP_REGNUM);
+  cache->prev_sp = sp + 4;
+
+  pc = get_frame_memory_unsigned (this_frame, sp, 4);
+  cache->prev_pc = gdbarch_addr_bits_remove (gdbarch, pc);
+
+  ps = get_frame_register_unsigned (this_frame, ARM_PS_REGNUM);
+  if (pc & 1)
+    cache->prev_ps = ps | t_bit;
+  else
+    cache->prev_ps = ps & ~t_bit;
+
+  return cache;
+}
+
+static void
+arm_linux_stub_this_id (struct frame_info *this_frame,
+			void **this_cache,
+			struct frame_id *this_id)
+{
+  struct arm_linux_stub_cache *cache;
+
+  if (*this_cache == NULL)
+    *this_cache = arm_linux_make_stub_cache (this_frame);
+  cache = *this_cache;
+
+  *this_id = frame_id_build (cache->prev_sp, get_frame_pc (this_frame));
+}
+
+static struct value *
+arm_linux_stub_prev_register (struct frame_info *this_frame,
+			      void **this_cache,
+			      int prev_regnum)
+{
+  struct arm_linux_stub_cache *cache;
+
+  if (*this_cache == NULL)
+    *this_cache = arm_linux_make_stub_cache (this_frame);
+  cache = *this_cache;
+
+  /* We've computed the values for PC, SP, and PS previously.  */
+  if (prev_regnum == ARM_PC_REGNUM)
+    return frame_unwind_got_constant (this_frame, prev_regnum, cache->prev_pc);
+  if (prev_regnum == ARM_SP_REGNUM)
+    return frame_unwind_got_constant (this_frame, prev_regnum, cache->prev_sp);
+  if (prev_regnum == ARM_PS_REGNUM)
+    return frame_unwind_got_constant (this_frame, prev_regnum, cache->prev_ps);
+
+  /* All other registers are inherited from the previous frame.  */
+  return frame_unwind_got_register (this_frame, prev_regnum, prev_regnum);
+}
+
+static int
+arm_linux_stub_unwind_sniffer (const struct frame_unwind *self,
+			       struct frame_info *this_frame,
+			       void **this_prologue_cache)
+{
+  struct gdbarch *gdbarch = get_frame_arch (this_frame);
+  enum bfd_endian byte_order_for_code = gdbarch_byte_order_for_code (gdbarch);
+  CORE_ADDR pc = get_frame_pc (this_frame);
+  char buf[4];
+
+  /* This sniffer only hits if the PC points to the vector page, that page
+     is actually readable (it is not on older kernels), and we are within
+     the system call restart handler, as identifed by having a "pop { pc }"
+     instruction in ARM encoding at the current PC.  */
+  if (pc >= 0xffff0000
+      && target_read_memory (pc, buf, 4) == 0
+      && extract_unsigned_integer (buf, 4, byte_order_for_code)
+	 == 0xe49df004  /* pop { pc } */)
+    return 1;
+
+  return 0;
+}
+
+struct frame_unwind arm_linux_stub_unwind = {
+  NORMAL_FRAME,
+  arm_linux_stub_this_id,
+  arm_linux_stub_prev_register,
+  NULL,
+  arm_linux_stub_unwind_sniffer
+};
+
+
 static void
 arm_linux_init_abi (struct gdbarch_info info,
 		    struct gdbarch *gdbarch)
@@ -1023,6 +1137,8 @@ arm_linux_init_abi (struct gdbarch_info
 					   simple_displaced_step_free_closure);
   set_gdbarch_displaced_step_location (gdbarch, displaced_step_at_entry_point);
 
+  /* Unwinder for Linux kernel helper stubs.  */
+  frame_unwind_append_unwinder (gdbarch, &arm_linux_stub_unwind);
 
   tdep->syscall_next_pc = arm_linux_syscall_next_pc;
 }
diff -urNp gdb-orig/gdb/arm-tdep.c gdb-head/gdb/arm-tdep.c
--- gdb-orig/gdb/arm-tdep.c	2011-02-17 14:00:14.000000000 +0100
+++ gdb-head/gdb/arm-tdep.c	2011-02-23 17:23:16.000000000 +0100
@@ -262,7 +262,7 @@ int arm_apcs_32 = 1;
 
 /* Return the bit mask in ARM_PS_REGNUM that indicates Thumb mode.  */
 
-static int
+int
 arm_psr_thumb_bit (struct gdbarch *gdbarch)
 {
   if (gdbarch_tdep (gdbarch)->is_m)
diff -urNp gdb-orig/gdb/arm-tdep.h gdb-head/gdb/arm-tdep.h
--- gdb-orig/gdb/arm-tdep.h	2011-02-09 15:28:26.000000000 +0100
+++ gdb-head/gdb/arm-tdep.h	2011-02-23 17:24:01.000000000 +0100
@@ -310,6 +310,9 @@ extern void arm_displaced_step_fixup (st
 				      struct displaced_step_closure *,
 				      CORE_ADDR, CORE_ADDR, struct regcache *);
 
+/* Return the bit mask in ARM_PS_REGNUM that indicates Thumb mode.  */
+extern int arm_psr_thumb_bit (struct gdbarch *);
+
 /* Is the instruction at the given memory address a Thumb or ARM
    instruction?  */
 extern int arm_pc_is_thumb (struct gdbarch *, CORE_ADDR);
-- 
  Dr. Ulrich Weigand
  GNU Toolchain for Linux on System z and Cell BE
  Ulrich.Weigand@de.ibm.com


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