Fix exception stack unwinding for ARM Cortex-M
authorFredrik Hederstierna <fredrik.hederstierna@verisure.com>
Mon, 14 Sep 2020 13:56:34 +0000 (14:56 +0100)
committerAlan Hayward <alan.hayward@arm.com>
Mon, 14 Sep 2020 14:26:12 +0000 (15:26 +0100)
For Cortex-M targets using floating-point, eg the Cortex-M4F, its not possible
to get any call-stack backtrace if setting a breakpoint in ISR.

The exception stack unwinder for Cortex-M does not consider if floating-point
registers was stacked or not, further the Cortex-M has two stack pointers: MSP
(Main Stack Pointer) and PSP (Process Stack Pointer).
This is not handled when GDB tries to backtrace in the exception stack
unwinder.

This patch fixes this, and gives a correct call-stack backtrace from
breakpoints set in a handler or ISR.

gdb/ChangeLog:

        * arm-tdep.c (arm_m_exception_cache): Try use correct stack
        pointer and stack frame offset when unwinding.

gdb/ChangeLog
gdb/arm-tdep.c

index dd0305648ea70b1ad451345d9633c58c329d9587..dd31d707c8c1f0cb9491c6eee75e19bbca936613 100644 (file)
@@ -1,3 +1,9 @@
+2020-09-14  Fredrik Hederstierna  <fredrik.hederstierna@verisure.com>
+            Adam Renquinha <arenquinha@cimeq.qc.ca>
+
+        * arm-tdep.c (arm_m_exception_cache): Try use correct stack
+        pointer and stack frame offset when unwinding.
+
 2020-09-13  Pedro Alves  <pedro@palves.net>
 
        * NEWS: Document "-break-insert --qualified".
index 074eedb48005829ba366802bdeda27571b922e49..1a5017495adf9cf2c5e7d2438a455dadefd24710 100644 (file)
@@ -469,7 +469,7 @@ arm_pc_is_thumb (struct gdbarch *gdbarch, CORE_ADDR memaddr)
 }
 
 /* Determine if the address specified equals any of these magic return
-   values, called EXC_RETURN, defined by the ARM v6-M and v7-M
+   values, called EXC_RETURN, defined by the ARM v6-M, v7-M and v8-M
    architectures.
 
    From ARMv6-M Reference Manual B1.5.8
@@ -500,13 +500,25 @@ arm_pc_is_thumb (struct gdbarch *gdbarch, CORE_ADDR memaddr)
    0xFFFFFFFD    Thread mode      Process         Basic
 
    For more details see "B1.5.8 Exception return behavior"
-   in both ARMv6-M and ARMv7-M Architecture Reference Manuals.  */
+   in both ARMv6-M and ARMv7-M Architecture Reference Manuals.
+
+   In the ARMv8-M Architecture Technical Reference also adds
+   for implementations without the Security Extension:
+
+   EXC_RETURN    Condition
+   0xFFFFFFB0    Return to Handler mode.
+   0xFFFFFFB8    Return to Thread mode using the main stack.
+   0xFFFFFFBC    Return to Thread mode using the process stack.  */
 
 static int
 arm_m_addr_is_magic (CORE_ADDR addr)
 {
   switch (addr)
     {
+      /* Values from ARMv8-M Architecture Technical Reference.  */
+      case 0xffffffb0:
+      case 0xffffffb8:
+      case 0xffffffbc:
       /* Values from Tables in B1.5.8 the EXC_RETURN definitions of
         the exception return behavior.  */
       case 0xffffffe1:
@@ -2923,14 +2935,64 @@ arm_m_exception_cache (struct frame_info *this_frame)
   struct gdbarch *gdbarch = get_frame_arch (this_frame);
   enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
   struct arm_prologue_cache *cache;
+  CORE_ADDR lr;
+  CORE_ADDR sp;
   CORE_ADDR unwound_sp;
   LONGEST xpsr;
+  uint32_t exc_return;
+  uint32_t process_stack_used;
+  uint32_t extended_frame_used;
+  uint32_t secure_stack_used;
 
   cache = FRAME_OBSTACK_ZALLOC (struct arm_prologue_cache);
   cache->saved_regs = trad_frame_alloc_saved_regs (this_frame);
 
-  unwound_sp = get_frame_register_unsigned (this_frame,
-                                           ARM_SP_REGNUM);
+  /* ARMv7-M Architecture Reference "B1.5.6 Exception entry behavior"
+     describes which bits in LR that define which stack was used prior
+     to the exception and if FPU is used (causing extended stack frame).  */
+
+  lr = get_frame_register_unsigned (this_frame, ARM_LR_REGNUM);
+  sp = get_frame_register_unsigned (this_frame, ARM_SP_REGNUM);
+
+  /* Check EXC_RETURN indicator bits.  */
+  exc_return = (((lr >> 28) & 0xf) == 0xf);
+
+  /* Check EXC_RETURN bit SPSEL if Main or Thread (process) stack used.  */
+  process_stack_used = ((lr & (1 << 2)) != 0);
+  if (exc_return && process_stack_used)
+    {
+      /* Thread (process) stack used.
+         Potentially this could be other register defined by target, but PSP
+         can be considered a standard name for the "Process Stack Pointer".
+         To be fully aware of system registers like MSP and PSP, these could
+         be added to a separate XML arm-m-system-profile that is valid for
+         ARMv6-M and ARMv7-M architectures. Also to be able to debug eg a
+         corefile off-line, then these registers must be defined by GDB,
+         and also be included in the corefile regsets.  */
+
+      int psp_regnum = user_reg_map_name_to_regnum (gdbarch, "psp", -1);
+      if (psp_regnum == -1)
+        {
+          /* Thread (process) stack could not be fetched,
+             give warning and exit.  */
+
+          warning (_("no PSP thread stack unwinding supported."));
+
+          /* Terminate any further stack unwinding by refer to self.  */
+          cache->prev_sp = sp;
+          return cache;
+        }
+      else
+        {
+          /* Thread (process) stack used, use PSP as SP.  */
+          unwound_sp = get_frame_register_unsigned (this_frame, psp_regnum);
+        }
+    }
+  else
+    {
+      /* Main stack used, use MSP as SP.  */
+      unwound_sp = sp;
+    }
 
   /* The hardware saves eight 32-bit words, comprising xPSR,
      ReturnAddress, LR (R14), R12, R3, R2, R1, R0.  See details in
@@ -2940,15 +3002,62 @@ arm_m_exception_cache (struct frame_info *this_frame)
   cache->saved_regs[1].addr = unwound_sp + 4;
   cache->saved_regs[2].addr = unwound_sp + 8;
   cache->saved_regs[3].addr = unwound_sp + 12;
-  cache->saved_regs[12].addr = unwound_sp + 16;
-  cache->saved_regs[14].addr = unwound_sp + 20;
-  cache->saved_regs[15].addr = unwound_sp + 24;
+  cache->saved_regs[ARM_IP_REGNUM].addr = unwound_sp + 16;
+  cache->saved_regs[ARM_LR_REGNUM].addr = unwound_sp + 20;
+  cache->saved_regs[ARM_PC_REGNUM].addr = unwound_sp + 24;
   cache->saved_regs[ARM_PS_REGNUM].addr = unwound_sp + 28;
 
+  /* Check EXC_RETURN bit FTYPE if extended stack frame (FPU regs stored)
+     type used.  */
+  extended_frame_used = ((lr & (1 << 4)) == 0);
+  if (exc_return && extended_frame_used)
+    {
+      int i;
+      int fpu_regs_stack_offset;
+
+      /* This code does not take into account the lazy stacking, see "Lazy
+         context save of FP state", in B1.5.7, also ARM AN298, supported
+         by Cortex-M4F architecture.
+         To fully handle this the FPCCR register (Floating-point Context
+         Control Register) needs to be read out and the bits ASPEN and LSPEN
+         could be checked to setup correct lazy stacked FP registers.
+         This register is located at address 0xE000EF34.  */
+
+      /* Extended stack frame type used.  */
+      fpu_regs_stack_offset = unwound_sp + 0x20;
+      for (i = 0; i < 16; i++)
+        {
+          cache->saved_regs[ARM_D0_REGNUM + i].addr = fpu_regs_stack_offset;
+          fpu_regs_stack_offset += 4;
+        }
+      cache->saved_regs[ARM_FPSCR_REGNUM].addr = unwound_sp + 0x60;
+
+      /* Offset 0x64 is reserved.  */
+      cache->prev_sp = unwound_sp + 0x68;
+    }
+  else
+    {
+      /* Standard stack frame type used.  */
+      cache->prev_sp = unwound_sp + 0x20;
+    }
+
+  /* Check EXC_RETURN bit S if Secure or Non-secure stack used.  */
+  secure_stack_used = ((lr & (1 << 6)) != 0);
+  if (exc_return && secure_stack_used)
+    {
+      /* ARMv8-M Exception and interrupt handling is not considered here.
+        In the ARMv8-M architecture also EXC_RETURN bit S is controlling if
+        the Secure or Non-secure stack was used. To separate Secure and
+        Non-secure stacks, processors that are based on the ARMv8-M
+        architecture support 4 stack pointers: MSP_S, PSP_S, MSP_NS, PSP_NS.
+        In addition, a stack limit feature is provided using stack limit
+        registers (accessible using MSR and MRS instructions) in Privileged
+        level.  */
+    }
+
   /* If bit 9 of the saved xPSR is set, then there is a four-byte
      aligner between the top of the 32-byte stack frame and the
      previous context's stack pointer.  */
-  cache->prev_sp = unwound_sp + 32;
   if (safe_read_memory_integer (unwound_sp + 28, 4, byte_order, &xpsr)
       && (xpsr & (1 << 9)) != 0)
     cache->prev_sp += 4;