Add support for backtracing through Renesas RX exception frames.
authorKevin Buettner <kevinb@redhat.com>
Thu, 2 Jul 2015 23:46:31 +0000 (16:46 -0700)
committerKevin Buettner <kevinb@redhat.com>
Fri, 3 Jul 2015 00:21:18 +0000 (17:21 -0700)
This change adds support for backtracing through Renesas RX exception
frames.

Determination about the type of frame is made by scanning the
remainder of the function for a return instruction and then looking at
which, if any, return instruction is found.  A normal RTS instruction
indicates that the frame is a normal frame.  An RTFI instruction
indicates that it's a fast interrupt, and an RTE instruction indicates
that the frame is a (normal) exception frame.  If no return instruction
is found within the scanned region - which can happen when the end of
the function cannot be found - it is assumed to be a normal frame.

I was able to test that normal prologue scanning still works by
disabling the dwarf2 sniffer.  I've tested this code for normal
interrupts.  The fast interrupt case has not been tested.

gdb/ChangeLog:

* rx-tdep.c (RX_USP_REGNUM, RX_BPC_REGNUM): New constants.
(enum rx_frame_type): New.
(struct rx_prologue): Add new field `frame_type'.
(rx_analyze_prologue): Add `frame_type' parameter. Cache this
parameter in the prologue struct.  Add code for recording
locations of PC and PSW for fast interrupt and exception frames.
(rx_skip_prologue): Adjust call to rx_analyze_prologue.
(rx_analyze_frame_prologue): Add `frame_type' parameter.
(rx_frame_type): New function.
(rx_frame_base): Fetch frame type and pass it to rx_analyze_prologue.
(rx_frame_this_id): Rename parameter `this_prologue_cache' to
`this_cache'.
(rx_frame_prev_register): Rename parameter `this_prologue_cache' to
`this_cache'.  Add cases for RX_FRAME_TYPE_EXCEPTION and
RX_FRAME_TYPE_FAST_INTERRUPT.
(normal_frame_p, exception_frame_p, rx_frame_sniffer_common)
(rx_frame_sniffer, rx_exception_sniffer): New functions.
(rx_frame_unwind): Use rx_frame_sniffer instead of
default_frame_sniffer.
(rx_frame_unwind): New unwinder.
(rx_gdbarch_init): Register new unwinder.

gdb/ChangeLog
gdb/rx-tdep.c

index 1dbae2065e59d68389b0be7e804ff14fb0f4cb25..201af51ac8a69c84aeb25cd68db1e18798c84c48 100644 (file)
@@ -1,3 +1,27 @@
+2015-07-02  Kevin Buettner  <kevinb@redhat.com>
+
+       * rx-tdep.c (RX_USP_REGNUM, RX_BPC_REGNUM): New constants.
+       (enum rx_frame_type): New.
+       (struct rx_prologue): Add new field `frame_type'.
+       (rx_analyze_prologue): Add `frame_type' parameter. Cache this
+       parameter in the prologue struct.  Add code for recording
+       locations of PC and PSW for fast interrupt and exception frames.
+       (rx_skip_prologue): Adjust call to rx_analyze_prologue.
+       (rx_analyze_frame_prologue): Add `frame_type' parameter.
+       (rx_frame_type): New function.
+       (rx_frame_base): Fetch frame type and pass it to rx_analyze_prologue.
+       (rx_frame_this_id): Rename parameter `this_prologue_cache' to
+       `this_cache'.
+       (rx_frame_prev_register): Rename parameter `this_prologue_cache' to
+       `this_cache'.  Add cases for RX_FRAME_TYPE_EXCEPTION and 
+       RX_FRAME_TYPE_FAST_INTERRUPT.
+       (normal_frame_p, exception_frame_p, rx_frame_sniffer_common)
+       (rx_frame_sniffer, rx_exception_sniffer): New functions.
+       (rx_frame_unwind): Use rx_frame_sniffer instead of
+       default_frame_sniffer.
+       (rx_frame_unwind): New unwinder.
+       (rx_gdbarch_init): Register new unwinder.
+
 2015-07-02  Kevin Buettner  <kevinb@redhat.com>
 
        * rx-tdep.c (RX_BPSW_REGNUM, RX_FPSW_REGNUM): New constants.
index 15c4cdedadded88138e9e46f402cbc2b736328af..8442c765f89ea5a938c8a0354bf3a7b97fa3e001 100644 (file)
@@ -45,14 +45,23 @@ enum
   RX_R4_REGNUM = 4,
   RX_FP_REGNUM = 6,
   RX_R15_REGNUM = 15,
+  RX_USP_REGNUM = 16,
   RX_PSW_REGNUM = 18,
   RX_PC_REGNUM = 19,
   RX_BPSW_REGNUM = 21,
+  RX_BPC_REGNUM = 22,
   RX_FPSW_REGNUM = 24,
   RX_ACC_REGNUM = 25,
   RX_NUM_REGS = 26
 };
 
+/* RX frame types.  */
+enum rx_frame_type {
+  RX_FRAME_TYPE_NORMAL,
+  RX_FRAME_TYPE_EXCEPTION,
+  RX_FRAME_TYPE_FAST_INTERRUPT
+};
+
 /* Architecture specific data.  */
 struct gdbarch_tdep
 {
@@ -69,6 +78,10 @@ struct gdbarch_tdep
 /* This structure holds the results of a prologue analysis.  */
 struct rx_prologue
 {
+  /* Frame type, either a normal frame or one of two types of exception
+     frames.  */
+  enum rx_frame_type frame_type;
+
   /* The offset from the frame base to the stack pointer --- always
      zero or negative.
 
@@ -203,9 +216,11 @@ rx_get_opcode_byte (void *handle)
 
 /* Analyze a prologue starting at START_PC, going no further than
    LIMIT_PC.  Fill in RESULT as appropriate.  */
+
 static void
-rx_analyze_prologue (CORE_ADDR start_pc,
-                    CORE_ADDR limit_pc, struct rx_prologue *result)
+rx_analyze_prologue (CORE_ADDR start_pc, CORE_ADDR limit_pc,
+                     enum rx_frame_type frame_type,
+                    struct rx_prologue *result)
 {
   CORE_ADDR pc, next_pc;
   int rn;
@@ -216,6 +231,8 @@ rx_analyze_prologue (CORE_ADDR start_pc,
 
   memset (result, 0, sizeof (*result));
 
+  result->frame_type = frame_type;
+
   for (rn = 0; rn < RX_NUM_REGS; rn++)
     {
       reg[rn] = pv_register (rn, 0);
@@ -225,9 +242,30 @@ rx_analyze_prologue (CORE_ADDR start_pc,
   stack = make_pv_area (RX_SP_REGNUM, gdbarch_addr_bit (target_gdbarch ()));
   back_to = make_cleanup_free_pv_area (stack);
 
-  /* The call instruction has saved the return address on the stack.  */
-  reg[RX_SP_REGNUM] = pv_add_constant (reg[RX_SP_REGNUM], -4);
-  pv_area_store (stack, reg[RX_SP_REGNUM], 4, reg[RX_PC_REGNUM]);
+  if (frame_type == RX_FRAME_TYPE_FAST_INTERRUPT)
+    {
+      /* This code won't do anything useful at present, but this is
+         what happens for fast interrupts.  */
+      reg[RX_BPSW_REGNUM] = reg[RX_PSW_REGNUM];
+      reg[RX_BPC_REGNUM] = reg[RX_PC_REGNUM];
+    }
+  else
+    {
+      /* When an exception occurs, the PSW is saved to the interrupt stack
+         first.  */
+      if (frame_type == RX_FRAME_TYPE_EXCEPTION)
+       {
+         reg[RX_SP_REGNUM] = pv_add_constant (reg[RX_SP_REGNUM], -4);
+         pv_area_store (stack, reg[RX_SP_REGNUM], 4, reg[RX_PSW_REGNUM]);
+       }
+
+      /* The call instruction (or an exception/interrupt) has saved the return
+          address on the stack.  */
+      reg[RX_SP_REGNUM] = pv_add_constant (reg[RX_SP_REGNUM], -4);
+      pv_area_store (stack, reg[RX_SP_REGNUM], 4, reg[RX_PC_REGNUM]);
+
+    }
+
 
   pc = start_pc;
   while (pc < limit_pc)
@@ -376,7 +414,9 @@ rx_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
   if (!find_pc_partial_function (pc, &name, &func_addr, &func_end))
     return pc;
 
-  rx_analyze_prologue (pc, func_end, &p);
+  /* The frame type doesn't matter here, since we only care about
+     where the prologue ends.  We'll use RX_FRAME_TYPE_NORMAL.  */
+  rx_analyze_prologue (pc, func_end, RX_FRAME_TYPE_NORMAL, &p);
   return p.prologue_end;
 }
 
@@ -384,8 +424,10 @@ rx_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc)
    associated function if there is not cache entry as specified by
    THIS_PROLOGUE_CACHE.  Save the decoded prologue in the cache and
    return that struct as the value of this function.  */
+
 static struct rx_prologue *
 rx_analyze_frame_prologue (struct frame_info *this_frame,
+                          enum rx_frame_type frame_type,
                           void **this_prologue_cache)
 {
   if (!*this_prologue_cache)
@@ -402,19 +444,76 @@ rx_analyze_frame_prologue (struct frame_info *this_frame,
       if (!func_start)
        stop_addr = func_start;
 
-      rx_analyze_prologue (func_start, stop_addr, *this_prologue_cache);
+      rx_analyze_prologue (func_start, stop_addr, frame_type,
+                           *this_prologue_cache);
     }
 
   return *this_prologue_cache;
 }
 
+/* Determine type of frame by scanning the function for a return
+   instruction.  */
+
+static enum rx_frame_type
+rx_frame_type (struct frame_info *this_frame, void **this_cache)
+{
+  const char *name;
+  CORE_ADDR pc, start_pc, lim_pc;
+  int bytes_read;
+  struct rx_get_opcode_byte_handle opcode_handle;
+  RX_Opcode_Decoded opc;
+
+  gdb_assert (this_cache != NULL);
+
+  /* If we have a cached value, return it.  */
+
+  if (*this_cache != NULL)
+    {
+      struct rx_prologue *p = *this_cache;
+
+      return p->frame_type;
+    }
+
+  /* No cached value; scan the function.  The frame type is cached in
+     rx_analyze_prologue / rx_analyze_frame_prologue.  */
+  
+  pc = get_frame_pc (this_frame);
+  
+  /* Attempt to find the last address in the function.  If it cannot
+     be determined, set the limit to be a short ways past the frame's
+     pc.  */
+  if (!find_pc_partial_function (pc, &name, &start_pc, &lim_pc))
+    lim_pc = pc + 20;
+
+  while (pc < lim_pc)
+    {
+      opcode_handle.pc = pc;
+      bytes_read = rx_decode_opcode (pc, &opc, rx_get_opcode_byte,
+                                    &opcode_handle);
+
+      if (bytes_read <= 0 || opc.id == RXO_rts)
+       return RX_FRAME_TYPE_NORMAL;
+      else if (opc.id == RXO_rtfi)
+       return RX_FRAME_TYPE_FAST_INTERRUPT;
+      else if (opc.id == RXO_rte)
+        return RX_FRAME_TYPE_EXCEPTION;
+
+      pc += bytes_read;
+    }
+
+  return RX_FRAME_TYPE_NORMAL;
+}
+
+
 /* Given the next frame and a prologue cache, return this frame's
    base.  */
+
 static CORE_ADDR
-rx_frame_base (struct frame_info *this_frame, void **this_prologue_cache)
+rx_frame_base (struct frame_info *this_frame, void **this_cache)
 {
+  enum rx_frame_type frame_type = rx_frame_type (this_frame, this_cache);
   struct rx_prologue *p
-    = rx_analyze_frame_prologue (this_frame, this_prologue_cache);
+    = rx_analyze_frame_prologue (this_frame, frame_type, this_cache);
 
   /* In functions that use alloca, the distance between the stack
      pointer and the frame base varies dynamically, so we can't use
@@ -435,46 +534,166 @@ rx_frame_base (struct frame_info *this_frame, void **this_prologue_cache)
 }
 
 /* Implement the "frame_this_id" method for unwinding frames.  */
+
 static void
-rx_frame_this_id (struct frame_info *this_frame,
-                 void **this_prologue_cache, struct frame_id *this_id)
+rx_frame_this_id (struct frame_info *this_frame, void **this_cache,
+                  struct frame_id *this_id)
 {
-  *this_id = frame_id_build (rx_frame_base (this_frame, this_prologue_cache),
+  *this_id = frame_id_build (rx_frame_base (this_frame, this_cache),
                             get_frame_func (this_frame));
 }
 
 /* Implement the "frame_prev_register" method for unwinding frames.  */
+
 static struct value *
-rx_frame_prev_register (struct frame_info *this_frame,
-                       void **this_prologue_cache, int regnum)
+rx_frame_prev_register (struct frame_info *this_frame, void **this_cache,
+                        int regnum)
 {
+  enum rx_frame_type frame_type = rx_frame_type (this_frame, this_cache);
   struct rx_prologue *p
-    = rx_analyze_frame_prologue (this_frame, this_prologue_cache);
-  CORE_ADDR frame_base = rx_frame_base (this_frame, this_prologue_cache);
-  int reg_size = register_size (get_frame_arch (this_frame), regnum);
+    = rx_analyze_frame_prologue (this_frame, frame_type, this_cache);
+  CORE_ADDR frame_base = rx_frame_base (this_frame, this_cache);
 
   if (regnum == RX_SP_REGNUM)
-    return frame_unwind_got_constant (this_frame, regnum, frame_base);
+    {
+      if (frame_type == RX_FRAME_TYPE_EXCEPTION)
+        {
+         struct value *psw_val;
+         CORE_ADDR psw;
+
+         psw_val = rx_frame_prev_register (this_frame, this_cache,
+                                           RX_PSW_REGNUM);
+         psw = extract_unsigned_integer (value_contents_all (psw_val), 4, 
+                                         gdbarch_byte_order (
+                                           get_frame_arch (this_frame)));
+
+         if ((psw & 0x20000 /* U bit */) != 0)
+           return rx_frame_prev_register (this_frame, this_cache,
+                                          RX_USP_REGNUM);
+
+          /* Fall through for the case where U bit is zero.  */
+       }
+
+      return frame_unwind_got_constant (this_frame, regnum, frame_base);
+    }
+
+  if (frame_type == RX_FRAME_TYPE_FAST_INTERRUPT)
+    {
+      if (regnum == RX_PC_REGNUM)
+        return rx_frame_prev_register (this_frame, this_cache,
+                                      RX_BPC_REGNUM);
+      if (regnum == RX_PSW_REGNUM)
+        return rx_frame_prev_register (this_frame, this_cache,
+                                      RX_BPSW_REGNUM);
+    }
 
   /* If prologue analysis says we saved this register somewhere,
      return a description of the stack slot holding it.  */
-  else if (p->reg_offset[regnum] != 1)
+  if (p->reg_offset[regnum] != 1)
     return frame_unwind_got_memory (this_frame, regnum,
                                    frame_base + p->reg_offset[regnum]);
 
   /* Otherwise, presume we haven't changed the value of this
      register, and get it from the next frame.  */
+  return frame_unwind_got_register (this_frame, regnum, regnum);
+}
+
+/* Return TRUE if the frame indicated by FRAME_TYPE is a normal frame.  */
+
+static int
+normal_frame_p (enum rx_frame_type frame_type)
+{
+  return (frame_type == RX_FRAME_TYPE_NORMAL);
+}
+
+/* Return TRUE if the frame indicated by FRAME_TYPE is an exception
+   frame.  */
+
+static int
+exception_frame_p (enum rx_frame_type frame_type)
+{
+  return (frame_type == RX_FRAME_TYPE_EXCEPTION
+          || frame_type == RX_FRAME_TYPE_FAST_INTERRUPT);
+}
+
+/* Common code used by both normal and exception frame sniffers.  */
+
+static int
+rx_frame_sniffer_common (const struct frame_unwind *self,
+                         struct frame_info *this_frame,
+                        void **this_cache,
+                        int (*sniff_p)(enum rx_frame_type) )
+{
+  gdb_assert (this_cache != NULL);
+
+  if (*this_cache == NULL)
+    {
+      enum rx_frame_type frame_type = rx_frame_type (this_frame, this_cache);
+
+      if (sniff_p (frame_type))
+        {
+         /* The call below will fill in the cache, including the frame
+            type.  */
+         (void) rx_analyze_frame_prologue (this_frame, frame_type, this_cache);
+
+         return 1;
+        }
+      else
+        return 0;
+    }
   else
-    return frame_unwind_got_register (this_frame, regnum, regnum);
+    {
+      struct rx_prologue *p = *this_cache;
+
+      return sniff_p (p->frame_type);
+    }
+}
+
+/* Frame sniffer for normal (non-exception) frames.  */
+
+static int
+rx_frame_sniffer (const struct frame_unwind *self,
+                  struct frame_info *this_frame,
+                 void **this_cache)
+{
+  return rx_frame_sniffer_common (self, this_frame, this_cache,
+                                  normal_frame_p);
+}
+
+/* Frame sniffer for exception frames.  */
+
+static int
+rx_exception_sniffer (const struct frame_unwind *self,
+                             struct frame_info *this_frame,
+                            void **this_cache)
+{
+  return rx_frame_sniffer_common (self, this_frame, this_cache,
+                                  exception_frame_p);
 }
 
+/* Data structure for normal code using instruction-based prologue
+   analyzer.  */
+
 static const struct frame_unwind rx_frame_unwind = {
   NORMAL_FRAME,
   default_frame_unwind_stop_reason,
   rx_frame_this_id,
   rx_frame_prev_register,
   NULL,
-  default_frame_sniffer
+  rx_frame_sniffer
+};
+
+/* Data structure for exception code using instruction-based prologue
+   analyzer.  */
+
+static const struct frame_unwind rx_exception_unwind = {
+  /* SIGTRAMP_FRAME could be used here, but backtraces are less informative.  */
+  NORMAL_FRAME,
+  default_frame_unwind_stop_reason,
+  rx_frame_this_id,
+  rx_frame_prev_register,
+  NULL,
+  rx_exception_sniffer
 };
 
 /* Implement the "unwind_pc" gdbarch method.  */
@@ -913,6 +1132,7 @@ rx_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   set_gdbarch_dwarf2_reg_to_regnum (gdbarch, rx_dwarf_reg_to_regnum);
 
   /* Frame unwinding.  */
+  frame_unwind_append_unwinder (gdbarch, &rx_exception_unwind);
   dwarf2_append_unwinders (gdbarch);
   frame_unwind_append_unwinder (gdbarch, &rx_frame_unwind);