]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb: Enable displaced stepping with shadow stack on amd64 linux.
authorChristina Schimpe <christina.schimpe@intel.com>
Fri, 18 Feb 2022 11:09:46 +0000 (03:09 -0800)
committerChristina Schimpe <christina.schimpe@intel.com>
Fri, 29 Aug 2025 17:02:10 +0000 (17:02 +0000)
Currently, if displaced stepping is active and the single stepped instruction
is a call instruction, the return address atop the stack is the address
following the copied instruction.  However, to allow normal program execution
it has to be the address following the original instruction.  Due to that
reason, the return address is corrected in amd64_displaced_step_fixup and
i386_displaced_step_fixup.

For programs that are shadow-stack enabled we see a control-protection
exception, as the address on the shadow stack does not match the address
atop the stack.

Fix this by correcting the shadow stack top address as well.

Approved-By: Andrew Burgess <aburgess@redhat.com>
Approved-By: Luis Machado <luis.machado@arm.com>
Reviewed-By: Eli Zaretskii <eliz@gnu.org>
gdb/NEWS
gdb/amd64-linux-tdep.c
gdb/amd64-tdep.c
gdb/doc/gdb.texinfo
gdb/i386-tdep.c
gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp [new file with mode: 0644]
gdb/testsuite/gdb.arch/amd64-shadow-stack.c

index 416e2209bbc92b0d423a1b8e5b1409e48f9a7dcd..afb84c88c7ff98e9251f793020c3335cb213c4fe 100644 (file)
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -3,6 +3,9 @@
 
 *** Changes since GDB 16
 
+* Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer
+  size (X32) Shadow Stacks are now supported.
+
 * Support for the shadow stack pointer register on x86-64 or x86-64 with
   32-bit pointer size (X32) GNU/Linux.
 
index 9fce1595a095a95cba3fe72eb89a738b75901006..a21f8a9a5ce4379c5b705d58bd8790576ebc9cf1 100644 (file)
@@ -1939,8 +1939,10 @@ amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch)
    possible.  */
 
 static std::optional<CORE_ADDR>
-amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
+amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache,
+                                     bool &shadow_stack_enabled)
 {
+  shadow_stack_enabled = false;
   const i386_gdbarch_tdep *tdep = gdbarch_tdep<i386_gdbarch_tdep> (gdbarch);
 
   if (tdep->ssp_regnum < 0)
@@ -1958,6 +1960,9 @@ amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache)
   if (ssp == 0x0)
     return {};
 
+  /* In case there is a shadow stack pointer available which is non-null,
+     the shadow stack feature is enabled.  */
+  shadow_stack_enabled = true;
   return ssp;
 }
 
@@ -1968,10 +1973,17 @@ static void
 amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr,
                               regcache *regcache)
 {
+  bool shadow_stack_enabled;
   std::optional<CORE_ADDR> ssp
-    = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache);
+    = amd64_linux_get_shadow_stack_pointer (gdbarch, regcache,
+                                           shadow_stack_enabled);
+
+  /* For amd64/Linux, if SSP has a value that means shadow stack is
+     enabled.  */
   if (!ssp.has_value ())
     return;
+  else
+    gdb_assert (shadow_stack_enabled);
 
   /* The shadow stack grows downwards.  To push addresses to the stack,
      we need to decrement SSP.  */
@@ -2126,6 +2138,8 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch,
     (gdbarch, amd64_linux_remove_non_address_bits_watchpoint);
 
   set_gdbarch_shadow_stack_push (gdbarch, amd64_linux_shadow_stack_push);
+  set_gdbarch_get_shadow_stack_pointer (gdbarch,
+                                       amd64_linux_get_shadow_stack_pointer);
   dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg);
 }
 
index e9049d31e5d50b2423afda5a64b7f23a40e9610d..916083457b67058dc5b3d9ffd3f340adff8f64cf 100644 (file)
@@ -1934,6 +1934,22 @@ amd64_displaced_step_fixup (struct gdbarch *gdbarch,
       displaced_debug_printf ("relocated return addr at %s to %s",
                              paddress (gdbarch, rsp),
                              paddress (gdbarch, retaddr));
+
+      /* If shadow stack is enabled, we need to correct the return address
+        on the shadow stack too.  */
+      bool shadow_stack_enabled;
+      std::optional<CORE_ADDR> ssp
+       = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
+                                           shadow_stack_enabled);
+      if (shadow_stack_enabled)
+       {
+         gdb_assert (ssp.has_value ());
+         write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
+                                        retaddr);
+         displaced_debug_printf ("relocated shadow stack return addr at %s "
+                                 "to %s", paddress (gdbarch, *ssp),
+                                 paddress (gdbarch, retaddr));
+       }
     }
 }
 
index a01406ffab277c017a14cbda07ae0d1ddbbbca73..f0501d2fed29841dea0f79de95954d2ed2fd5a34 100644 (file)
@@ -27062,12 +27062,20 @@ control protection exception.  After executing a @code{ENDBR} instruction
 the state machine returns to the IDLE state.
 @end itemize
 
-Impact on Call/Print:
+Impact on @value{GDBN} commands:
+@itemize @bullet
+@item Call/Print:
 Inferior calls in @value{GDBN} reset the current PC to the beginning of the
 function that is called.  No call instruction is executed, but the @code{RET}
 instruction actually is.  To avoid a control protection exception due to the
 missing return address on the shadow stack, @value{GDBN} pushes the new return
 address to the shadow stack and updates the shadow stack pointer.
+@item Step:
+With displaced stepping, @value{GDBN} may run an out of line copy of a call
+instruction.  In this case, the wrong return address is pushed to the shadow
+stack.  @value{GDBN} corrects this value to avoid a control protection
+exception.  For more details on displaced stepping, see @ref{displaced-stepping}.
+@end itemize
 
 @node Alpha
 @subsection Alpha
@@ -41762,6 +41770,7 @@ GLOBAL              Disassembler_2      (Matches current architecture)
 @cindex out-of-line single-stepping
 @item set displaced-stepping
 @itemx show displaced-stepping
+@anchor{displaced-stepping}
 Control whether or not @value{GDBN} will do @dfn{displaced stepping}
 if the target supports it.  Displaced stepping is a way to single-step
 over breakpoints without removing them from the inferior, by executing
index 8a053315ea8873a3f20ebe8ed61e37041bc3e467..f75745a3ecf69e62a0aee6b750434345429373c3 100644 (file)
@@ -897,6 +897,22 @@ i386_displaced_step_fixup (struct gdbarch *gdbarch,
       displaced_debug_printf ("relocated return addr at %s to %s",
                              paddress (gdbarch, esp),
                              paddress (gdbarch, retaddr));
+
+      /* If shadow stack is enabled, we need to correct the return address
+        on the shadow stack too.  */
+      bool shadow_stack_enabled;
+      std::optional<CORE_ADDR> ssp
+       = gdbarch_get_shadow_stack_pointer (gdbarch, regs,
+                                           shadow_stack_enabled);
+      if (shadow_stack_enabled)
+       {
+         gdb_assert (ssp.has_value ());
+         write_memory_unsigned_integer (*ssp, retaddr_len, byte_order,
+                                        retaddr);
+         displaced_debug_printf ("relocated shadow stack return addr at %s "
+                                 "to %s", paddress (gdbarch, *ssp),
+                                 paddress (gdbarch, retaddr));
+       }
     }
 }
 
diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-disp-step.exp
new file mode 100644 (file)
index 0000000..e4efa00
--- /dev/null
@@ -0,0 +1,84 @@
+# Copyright 2025 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test continue from call instructions with shadow stack and displaced
+# stepping being enabled.
+
+require allow_ssp_tests support_displaced_stepping
+
+standard_testfile amd64-shadow-stack.c
+
+save_vars { ::env(GLIBC_TUNABLES) } {
+
+    append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK"
+
+    if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \
+         additional_flags="-fcf-protection=return"] } {
+       return
+    }
+
+    # Enable displaced stepping.
+    gdb_test_no_output "set displaced-stepping on"
+    gdb_test "show displaced-stepping" ".* displaced stepping .* is on.*"
+
+    if { ![runto_main] } {
+       return
+    }
+
+    # Get the address of the call to the call1 function.
+    set call1_addr -1
+    gdb_test_multiple "disassemble main" "" {
+       -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call1>.*" {
+           set call1_addr $expect_out(1,string)
+           pass $gdb_test_name
+       }
+    }
+
+    if { $call1_addr == -1 } {
+       return
+    }
+
+    # Get the address of the call to the call2 function.
+    set call2_addr -1
+    gdb_test_multiple "disassemble call1" "" {
+       -re -wrap "($hex) <\\+($decimal)>:\\s*call\\s*0x.*<call2>.*" {
+           set call2_addr $expect_out(1,string)
+           pass $gdb_test_name
+       }
+    }
+
+    if { $call2_addr == -1 } {
+       return
+    }
+
+    gdb_test "break *$call1_addr" \
+       "Breakpoint $decimal at $hex.*" \
+       "break at the address of the call1 instruction"
+
+    gdb_test "break *$call2_addr" \
+       "Breakpoint $decimal at $hex.*" \
+       "break at the address of the call2 instruction"
+
+    gdb_test "continue" \
+           "Breakpoint $decimal, $call1_addr in main ().*" \
+           "continue until call1 instruction"
+
+    # Test continue from breakpoint at call1 and call2 instructions.
+    gdb_test "continue" \
+       "Breakpoint $decimal, $call2_addr in call1 ().*" \
+       "continue from call1 instruction"
+
+    gdb_continue_to_end "continue from call2 instruction"
+}
index bf90463ed204823ed92d8af94db2fc4610cc25fb..4a1ca1e05899e5e5257b2f1ef997baac15e82a62 100644 (file)
@@ -30,6 +30,11 @@ call1 ()
 int
 main ()
 {
+  /* Depending on instruction generation we might end up in the call
+     instruction of call1 function after "runto_main".  Avoid this by
+     adding a nop instruction, to simplify the testing in
+     amd64-shadow-stack-disp-step.exp.  */
+  asm ("nop");
   call1 (); /* break main.  */
   return 0;
 }