[5/5] gdb/arm: Extend arm_m_addr_is_magic to support FNC_RETURN, add unwind-ns-to-s command

Message ID 20220114163552.4107885-5-christophe.lyon@foss.st.com
State Superseded
Headers show
Series
  • [1/5] gdb/arm: Fix prologue analysis to support vpush
Related show

Commit Message

Simon Marchi via Gdb-patches Jan. 14, 2022, 4:35 p.m.
This patch makes use of the support for several stack pointers
introduced by the previous patch to switch between them as needed
during unwinding.

It introduces a new 'unwind-ns-to-s' arm command to enable/disable
mode switching during unwinding. It is enabled by default.

It has been tested using an STM32L5 board (with cortex-m33) and the
sample applications shipped with the STM32Cube development
environment: GTZC_TZSC_MPCBB_TrustZone in
STM32CubeL5/Projects/NUCLEO-L552ZE-Q/Examples/GTZC.

The test consisted in setting breakpoints in various places and check
that the backtrace is correct: SecureFault_Callback (Non-secure mode),
__gnu_cmse_nonsecure_call (before and after the vpush instruction),
SecureFault_Handler (Secure mode).

This implies that we tested only some parts of this patch (only MSP*
were used), but remaining parts seem reasonable.
---
 gdb/NEWS            |   5 +
 gdb/arm-tdep.c      | 295 +++++++++++++++++++++++++++++++++++---------
 gdb/arm-tdep.h      |   1 +
 gdb/doc/gdb.texinfo |  11 ++
 4 files changed, 251 insertions(+), 61 deletions(-)

-- 
2.25.1

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index eeca1d39b10..0bfa2cfab78 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -4984,6 +4984,11 @@  show arm force-mode
   the current CPSR value for instructions without symbols; previous
   versions of GDB behaved as if "set arm fallback-mode arm".
 
+set arm unwind-ns-to-s
+  Enable unwinding from Non-secure to Secure mode on Cortex-M with
+  Security extension.
+  This can trigger security exception when unwinding exception stack.
+
 set disable-randomization
 show disable-randomization
   Standalone programs run with the virtual address space randomization enabled
diff --git a/gdb/arm-tdep.c b/gdb/arm-tdep.c
index 8115361d914..b35fb3ca08c 100644
--- a/gdb/arm-tdep.c
+++ b/gdb/arm-tdep.c
@@ -531,6 +531,7 @@  static CORE_ADDR arm_analyze_prologue
 /* See arm-tdep.h.  */
 
 bool arm_apcs_32 = true;
+bool arm_unwind_ns_to_s = true;
 
 /* Return the bit mask in ARM_PS_REGNUM that indicates Thumb mode.  */
 
@@ -747,28 +748,43 @@  arm_pc_is_thumb (struct gdbarch *gdbarch, CORE_ADDR memaddr)
    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:
-      case 0xffffffe9:
-      case 0xffffffed:
-      case 0xfffffff1:
-      case 0xfffffff9:
-      case 0xfffffffd:
-	/* Address is magic.  */
-	return 1;
+arm_m_addr_is_magic (struct gdbarch *gdbarch, CORE_ADDR addr)
+{
+  arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
+  if (tdep->have_sec_ext)
+    {
+      switch ((addr & 0xff000000))
+	{
+	case 0xff000000: /* EXC_RETURN pattern.  */
+	case 0xfe000000: /* FNC_RETURN pattern.  */
+	  return 1;
+	default:
+	  return 0;
+	}
+    }
+  else
+    {
+      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:
+	case 0xffffffe9:
+	case 0xffffffed:
+	case 0xfffffff1:
+	case 0xfffffff9:
+	case 0xfffffffd:
+	  /* Address is magic.  */
+	  return 1;
 
-      default:
-	/* Address is not magic.  */
-	return 0;
+	default:
+	  /* Address is not magic.  */
+	  return 0;
+	}
     }
 }
 
@@ -780,7 +796,7 @@  arm_addr_bits_remove (struct gdbarch *gdbarch, CORE_ADDR val)
 
   /* On M-profile devices, do not strip the low bit from EXC_RETURN
      (the magic exception return address).  */
-  if (tdep->is_m && arm_m_addr_is_magic (val))
+  if (tdep->is_m && arm_m_addr_is_magic (gdbarch, val))
     return val;
 
   if (arm_apcs_32)
@@ -2211,6 +2227,7 @@  arm_prologue_prev_register (struct frame_info *this_frame,
 {
   struct gdbarch *gdbarch = get_frame_arch (this_frame);
   struct arm_prologue_cache *cache;
+  gdb::optional<CORE_ADDR> sp_value;
 
   if (*this_cache == NULL)
     *this_cache = arm_make_prologue_cache (this_frame);
@@ -2237,6 +2254,12 @@  arm_prologue_prev_register (struct frame_info *this_frame,
     return frame_unwind_got_constant (this_frame, prev_regnum,
 				      arm_cache_get_prev_sp (cache));
 
+  /* The value might be one of the alternative SP, if so, use the
+   * value already constructed.  */
+  sp_value = arm_cache_get_sp_register (cache, prev_regnum);
+  if (sp_value.has_value ())
+    return frame_unwind_got_constant (this_frame, prev_regnum, *sp_value);
+
   /* The CPSR may have been changed by the call instruction and by the
      called function.  The only bit we can reconstruct is the T bit,
      by checking the low bit of LR as of the call.  This is a reliable
@@ -3217,15 +3240,20 @@  static struct arm_prologue_cache *
 arm_m_exception_cache (struct frame_info *this_frame)
 {
   struct gdbarch *gdbarch = get_frame_arch (this_frame);
+  arm_gdbarch_tdep *tdep = (arm_gdbarch_tdep *) gdbarch_tdep (gdbarch);
   enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
   struct arm_prologue_cache *cache;
   CORE_ADDR lr;
+  CORE_ADDR sp;
   CORE_ADDR unwound_sp;
+  uint32_t sp_r0_offset = 0;
   LONGEST xpsr;
   uint32_t exc_return;
-  uint32_t process_stack_used;
+  uint32_t fnc_return;
   uint32_t extended_frame_used;
-  uint32_t secure_stack_used;
+  uint32_t secure_stack_used = 0;
+  uint32_t default_callee_register_stacking = 0;
+  uint32_t exception_domain_is_secure = 0;
 
   cache = FRAME_OBSTACK_ZALLOC (struct arm_prologue_cache);
   arm_cache_init (cache, this_frame);
@@ -3235,35 +3263,124 @@  arm_m_exception_cache (struct frame_info *this_frame)
      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);
+  fnc_return = ((lr & 0xfffffffe) == 0xfefffffe);
+  if (tdep->have_sec_ext && fnc_return)
+    {
+      gdb_regnum actual_sp;
 
-  /* 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)
+      arm_cache_switch_prev_sp (cache, ARM_MSP_NS_REGNUM);
+      arm_cache_set_prev_sp (cache, sp);
+      if (lr & 1)
+	actual_sp = ARM_MSP_S_REGNUM;
+      else
+	actual_sp = ARM_MSP_NS_REGNUM;
+
+      arm_cache_switch_prev_sp (cache, actual_sp);
+      sp = get_frame_register_unsigned (this_frame, actual_sp);
+
+      cache->saved_regs[ARM_LR_REGNUM].set_addr (sp);
+
+      arm_cache_set_prev_sp (cache, sp + 8);
+
+      return cache;
+    }
+
+  /* Check EXC_RETURN indicator bits.  */
+  exc_return = (((lr >> 24) & 0xff) == 0xff);
+  if (exc_return)
     {
-      /* Thread (process) stack used, use PSP as SP.  */
-      unwound_sp = get_frame_register_unsigned (this_frame, ARM_PSP_REGNUM);
+      /* Check EXC_RETURN bit SPSEL if Main or Thread (process) stack used.  */
+      uint32_t process_stack_used = ((lr & (1 << 2)) != 0);
+
+      if (tdep->have_sec_ext)
+	{
+	  secure_stack_used = ((lr & (1 << 6)) != 0);
+	  default_callee_register_stacking = ((lr & (1 << 5)) != 0);
+	  exception_domain_is_secure = ((lr & (1 << 0)) == 0);
+
+	  /* Unwinding from non-secure to secure can trip security
+	   * measures.  In order to avoid the debugger to be
+	   * intrusive, rely on the user to configure the requested
+	   * mode.
+	   */
+	  if (secure_stack_used && !exception_domain_is_secure
+	      && !arm_unwind_ns_to_s)
+	    {
+	      warning (_("Non-secure to secure stack unwinding disabled."));
+
+	      /* Terminate any further stack unwinding by refer to self.  */
+	      arm_cache_set_prev_sp (cache, sp);
+	      return cache;
+	    }
+
+	  if (process_stack_used)
+	    {
+	      if (secure_stack_used)
+		  /* Secure thread (process) stack used, use PSP_S as SP.  */
+		  arm_cache_switch_prev_sp (cache, ARM_PSP_S_REGNUM);
+	      else
+		  /* Non-secure thread (process) stack used, use PSP_NS as SP.  */
+		  arm_cache_switch_prev_sp (cache, ARM_PSP_NS_REGNUM);
+	    }
+	  else
+	    {
+	      if (secure_stack_used)
+		/* Secure main stack used, use MSP_S as SP.  */
+		arm_cache_switch_prev_sp (cache, ARM_MSP_S_REGNUM);
+	      else
+		/* Non-secure main stack used, use MSP_NS as SP.  */
+		arm_cache_switch_prev_sp (cache, ARM_MSP_NS_REGNUM);
+	    }
+	}
+      else
+	{
+	  if (process_stack_used)
+	    /* Thread (process) stack used, use PSP as SP.  */
+	    arm_cache_switch_prev_sp (cache, ARM_PSP_REGNUM);
+	  else
+	    /* Main stack used, use MSP as SP.  */
+	    arm_cache_switch_prev_sp (cache, ARM_MSP_REGNUM);
+	}
     }
   else
     {
       /* Main stack used, use MSP as SP.  */
-      unwound_sp = get_frame_register_unsigned (this_frame, ARM_MSP_REGNUM);
+      arm_cache_switch_prev_sp (cache, ARM_MSP_REGNUM);
+    }
+
+  /* Fetch the SP to use for this frame.  */
+  unwound_sp = arm_cache_get_prev_sp (cache);
+
+  /* With the Security extension, the hardware saves R4..R11 too.  */
+  if (exc_return && tdep->have_sec_ext && secure_stack_used
+      && (!default_callee_register_stacking || exception_domain_is_secure))
+    {
+      /* Read R4..R11 from the integer callee registers.  */
+      cache->saved_regs[4].set_addr (unwound_sp + 0x08);
+      cache->saved_regs[5].set_addr (unwound_sp + 0x0C);
+      cache->saved_regs[6].set_addr (unwound_sp + 0x10);
+      cache->saved_regs[7].set_addr (unwound_sp + 0x14);
+      cache->saved_regs[8].set_addr (unwound_sp + 0x18);
+      cache->saved_regs[9].set_addr (unwound_sp + 0x1C);
+      cache->saved_regs[10].set_addr (unwound_sp + 0x20);
+      cache->saved_regs[11].set_addr (unwound_sp + 0x24);
+      sp_r0_offset = 0x28;
     }
 
   /* The hardware saves eight 32-bit words, comprising xPSR,
      ReturnAddress, LR (R14), R12, R3, R2, R1, R0.  See details in
      "B1.5.6 Exception entry behavior" in
      "ARMv7-M Architecture Reference Manual".  */
-  cache->saved_regs[0].set_addr (unwound_sp);
-  cache->saved_regs[1].set_addr (unwound_sp + 4);
-  cache->saved_regs[2].set_addr (unwound_sp + 8);
-  cache->saved_regs[3].set_addr (unwound_sp + 12);
-  cache->saved_regs[ARM_IP_REGNUM].set_addr (unwound_sp + 16);
-  cache->saved_regs[ARM_LR_REGNUM].set_addr (unwound_sp + 20);
-  cache->saved_regs[ARM_PC_REGNUM].set_addr (unwound_sp + 24);
-  cache->saved_regs[ARM_PS_REGNUM].set_addr (unwound_sp + 28);
+  cache->saved_regs[0].set_addr (unwound_sp + sp_r0_offset);
+  cache->saved_regs[1].set_addr (unwound_sp + sp_r0_offset + 4);
+  cache->saved_regs[2].set_addr (unwound_sp + sp_r0_offset + 8);
+  cache->saved_regs[3].set_addr (unwound_sp + sp_r0_offset + 12);
+  cache->saved_regs[ARM_IP_REGNUM].set_addr (unwound_sp + sp_r0_offset + 16);
+  cache->saved_regs[ARM_LR_REGNUM].set_addr (unwound_sp + sp_r0_offset + 20);
+  cache->saved_regs[ARM_PC_REGNUM].set_addr (unwound_sp + sp_r0_offset + 24);
+  cache->saved_regs[ARM_PS_REGNUM].set_addr (unwound_sp + sp_r0_offset + 28);
 
   /* Check EXC_RETURN bit FTYPE if extended stack frame (FPU regs stored)
      type used.  */
@@ -3282,41 +3399,43 @@  arm_m_exception_cache (struct frame_info *this_frame)
 	 This register is located at address 0xE000EF34.  */
 
       /* Extended stack frame type used.  */
-      fpu_regs_stack_offset = unwound_sp + 0x20;
+      fpu_regs_stack_offset = unwound_sp + sp_r0_offset + 0x20;
       for (i = 0; i < 16; i++)
 	{
 	  cache->saved_regs[ARM_D0_REGNUM + i].set_addr (fpu_regs_stack_offset);
 	  fpu_regs_stack_offset += 4;
 	}
-      cache->saved_regs[ARM_FPSCR_REGNUM].set_addr (unwound_sp + 0x60);
+      cache->saved_regs[ARM_FPSCR_REGNUM].set_addr (unwound_sp + sp_r0_offset + 0x60);
+      fpu_regs_stack_offset += 4;
+
+      if (tdep->have_sec_ext && !default_callee_register_stacking)
+	{
+	  /* Handle floating-point callee saved registers.  */
+	  fpu_regs_stack_offset = 0x90;
+	  for (i = 16; i < 32; i++)
+	    {
+	      cache->saved_regs[ARM_D0_REGNUM + i].set_addr (fpu_regs_stack_offset);
+	      fpu_regs_stack_offset += 4;
+	    }
 
-      /* Offset 0x64 is reserved.  */
-      arm_cache_set_prev_sp (cache, unwound_sp + 0x68);
+	  arm_cache_set_prev_sp (cache, unwound_sp + sp_r0_offset + 0xD0);
+	}
+      else
+	{
+	  /* Offset 0x64 is reserved.  */
+	  arm_cache_set_prev_sp (cache, unwound_sp + sp_r0_offset + 0x68);
+	}
     }
   else
     {
       /* Standard stack frame type used.  */
-      arm_cache_set_prev_sp (cache, 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.  */
+      arm_cache_set_prev_sp (cache, unwound_sp + sp_r0_offset + 0x20);
     }
 
   /* 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.  */
-  if (safe_read_memory_integer (unwound_sp + 28, 4, byte_order, &xpsr)
+  if (safe_read_memory_integer (unwound_sp + sp_r0_offset + 28, 4, byte_order, &xpsr)
       && (xpsr & (1 << 9)) != 0)
     arm_cache_set_prev_sp (cache, arm_cache_get_prev_sp (cache) + 4);
 
@@ -3351,6 +3470,7 @@  arm_m_exception_prev_register (struct frame_info *this_frame,
 			       int prev_regnum)
 {
   struct arm_prologue_cache *cache;
+  gdb::optional<CORE_ADDR> sp_value;
 
   if (*this_cache == NULL)
     *this_cache = arm_m_exception_cache (this_frame);
@@ -3361,6 +3481,21 @@  arm_m_exception_prev_register (struct frame_info *this_frame,
     return frame_unwind_got_constant (this_frame, prev_regnum,
 				      arm_cache_get_prev_sp (cache));
 
+  /* The value might be one of the alternative SP, if so, use the
+   * value already constructed.  */
+  sp_value = arm_cache_get_sp_register (cache, prev_regnum);
+  if (sp_value.has_value ())
+    return frame_unwind_got_constant (this_frame, prev_regnum, *sp_value);
+
+  if (prev_regnum == ARM_PC_REGNUM)
+    {
+      CORE_ADDR lr = frame_unwind_register_unsigned (this_frame, ARM_LR_REGNUM);
+      struct gdbarch *gdbarch = get_frame_arch (this_frame);
+
+      return frame_unwind_got_constant (this_frame, prev_regnum,
+					arm_addr_bits_remove (gdbarch, lr));
+    }
+
   return trad_frame_get_prev_register (this_frame, cache->saved_regs,
 				       prev_regnum);
 }
@@ -3373,13 +3508,14 @@  arm_m_exception_unwind_sniffer (const struct frame_unwind *self,
 				struct frame_info *this_frame,
 				void **this_prologue_cache)
 {
+  struct gdbarch *gdbarch = get_frame_arch (this_frame);
   CORE_ADDR this_pc = get_frame_pc (this_frame);
 
   /* No need to check is_m; this sniffer is only registered for
      M-profile architectures.  */
 
   /* Check if exception frame returns to a magic PC value.  */
-  return arm_m_addr_is_magic (this_pc);
+  return arm_m_addr_is_magic (gdbarch, this_pc);
 }
 
 /* Frame unwinder for M-profile exceptions.  */
@@ -8793,6 +8929,15 @@  arm_show_force_mode (struct ui_file *file, int from_tty,
 		    arm_force_mode_string);
 }
 
+static void
+arm_show_unwind_ns_to_s (struct ui_file *file, int from_tty,
+			 struct cmd_list_element *c, const char *value)
+{
+  fprintf_filtered (file,
+		    _("Usage of non-secure to secure exception stack unwinding is %s.\n"),
+		    arm_unwind_ns_to_s ? "on" : "off");
+}
+
 /* If the user changes the register disassembly style used for info
    register and other commands, we have to also switch the style used
    in opcodes for disassembly output.  This function is run in the "set
@@ -9266,6 +9411,7 @@  arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdesc_arch_data_up tdesc_data;
   int i;
   bool is_m = false;
+  bool have_sec_ext = false;
   int vfp_register_count = 0;
   bool have_s_pseudos = false, have_q_pseudos = false;
   bool have_wmmx_registers = false;
@@ -9633,6 +9779,23 @@  arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
 		have_q_pseudos = true;
 	    }
 	}
+      feature = tdesc_find_feature (tdesc,
+				    "org.gnu.gdb.arm.secext");
+      if (feature != NULL)
+	{
+	  valid_p &= tdesc_numbered_register (feature, tdesc_data.get (),
+					      ARM_MSP_S_REGNUM, "msp_s");
+	  valid_p &= tdesc_numbered_register (feature, tdesc_data.get (),
+					      ARM_MSP_NS_REGNUM, "msp_ns");
+	  valid_p &= tdesc_numbered_register (feature, tdesc_data.get (),
+					      ARM_PSP_S_REGNUM, "psp_s");
+	  valid_p &= tdesc_numbered_register (feature, tdesc_data.get (),
+					      ARM_PSP_NS_REGNUM, "psp_ns");
+	  if (!valid_p)
+	    return NULL;
+
+	  have_sec_ext = true;
+	}
     }
 
   /* If there is already a candidate, use it.  */
@@ -9673,6 +9836,7 @@  arm_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches)
   tdep->arm_abi = arm_abi;
   tdep->fp_model = fp_model;
   tdep->is_m = is_m;
+  tdep->have_sec_ext = have_sec_ext;
   tdep->have_fpa_registers = have_fpa_registers;
   tdep->have_wmmx_registers = have_wmmx_registers;
   gdb_assert (vfp_register_count == 0
@@ -10083,6 +10247,15 @@  vfp - VFP co-processor."),
 			NULL, NULL, arm_show_force_mode,
 			&setarmcmdlist, &showarmcmdlist);
 
+  /* Add a command to stop triggering security exception when
+   * unwinding exception stack.  */
+  add_setshow_boolean_cmd ("unwind-ns-to-s", no_class, &arm_unwind_ns_to_s,
+			   _("Set usage of non-secure to secure exception stack unwinding."),
+			   _("Show usage of non-secure to secure exception stack unwinding."),
+			   _("When on, the debugger can trigger memory access traps."),
+			   NULL, arm_show_unwind_ns_to_s,
+			   &setarmcmdlist, &showarmcmdlist);
+
   /* Debugging flag.  */
   add_setshow_boolean_cmd ("arm", class_maintenance, &arm_debug,
 			   _("Set ARM debugging."),
diff --git a/gdb/arm-tdep.h b/gdb/arm-tdep.h
index 9012b9da100..12c6ae1a83e 100644
--- a/gdb/arm-tdep.h
+++ b/gdb/arm-tdep.h
@@ -120,6 +120,7 @@  struct arm_gdbarch_tdep : gdbarch_tdep
   int mve_pseudo_count = 0;	/* Total number of MVE pseudo registers.  */
 
   bool is_m = false;		/* Does the target follow the "M" profile.  */
+  bool have_sec_ext = false;	/* Do we have secure extensions?  */
   CORE_ADDR lowest_pc = 0;	/* Lowest address at which instructions
 				   will appear.  */
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 9d507795993..66912684379 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -24894,6 +24894,17 @@  of @samp{set arm fallback-mode}.
 
 @item show arm force-mode
 Show the current forced instruction mode.
+Show the currently used ABI.
+
+@item set arm unwind-ns-to-s
+This command enables unwinding from Non-secure to Secure mode on
+Cortex-M with Security extension.
+This can trigger security exceptions when unwinding the exception
+stack.
+It is enabled by default.
+
+@item show arm unwind-ns-to-s
+Show whether unwind from Non-secure to Secure mode is enabled.
 
 @item set debug arm
 Toggle whether to display ARM-specific debugging messages from the ARM