Add the mi command for the command "backtrace -shadow". Similar to the mi interface for the normal backtrace command, support low-frame and high-frame as command line parameters. Example print of a full shadow stack backtrace: ~~~ (gdb) -shadow-stack-list-frames ^done,shadow-stack=[ shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70", func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h", fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h", line="58",arch="i386:x86-64"}, shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20", func="__libc_start_main_impl",file="../csu/libc-start.c", fullname="/usr/[...]/csu/libc-start.c", line="128",arch="i386:x86-64"}, shadow-stack-frame={level="2",addr="0x0000000000401075", func="_start",arch="i386:x86-64"}] ~~~ Example print of a shadow stack backtrace using low- and high-frame: ~~~ (gdb) -shadow-stack-list-frames 0 1 ^done,shadow-stack=[ shadow-stack-frame={level="0",addr="0x00007ffff7c3fe70", func="__libc_start_call_main",file="../sysdeps/nptl/libc_start_call_main.h", fullname="/usr/[...]/sysdeps/nptl/libc_start_call_main.h", line="58",arch="i386:x86-64"}, shadow-stack-frame={level="1",addr="0x00007ffff7c3ff20", func="__libc_start_main_impl",file="../csu/libc-start.c", fullname="/usr/[...]/csu/libc-start.c", line="128",arch="i386:x86-64"}] ~~~ --- gdb/NEWS | 10 ++ gdb/doc/gdb.texinfo | 47 ++++++ gdb/mi/mi-cmd-stack.c | 142 ++++++++++++++++++ gdb/mi/mi-cmds.c | 2 + gdb/mi/mi-cmds.h | 1 + gdb/shadow-stack.c | 18 ++- gdb/shadow-stack.h | 11 ++ .../gdb.mi/mi-shadow-stack-signal.exp | 69 +++++++++ gdb/testsuite/gdb.mi/mi-shadow-stack.exp | 65 ++++++++ 9 files changed, 362 insertions(+), 3 deletions(-) create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp create mode 100644 gdb/testsuite/gdb.mi/mi-shadow-stack.exp diff --git a/gdb/NEWS b/gdb/NEWS index 37b3add11ed..d3991a495f4 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -177,6 +177,16 @@ qExecAndArgs deprecated since GDB 10. Users who need to control the size of a memory port's internal buffer can use the 'setvbuf' procedure. +* New MI commands + +-shadow-stack-list-frames + + Added new MI command '-shadow-stack-list-frames' which is equivalent to + the CLI subcommand 'backtrace shadow' but supports 'low-frame' and + 'high-frame' as command line parameters. The parameters are used to + print shadow stack frames between certain levels on the shadow stack + only. + *** Changes in GDB 17 * Debugging Linux programs that use x86-64 or x86-64 with 32-bit pointer diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index f7dc7c4397d..829b58a62f0 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -35433,6 +35433,53 @@ Show a single frame: (gdb) @end smallexample +@anchor{-shadow-stack-list-frames} +@findex -shadow-stack-list-frames +@subheading The @code{-shadow-stack-list-frames} Command + +@subsubheading Synopsis + +@smallexample + -shadow-stack-list-frames [ @var{low-frame} @var{high-frame} ] +@end smallexample + +List the shadow stack frames currently on the shadow stack. In case the +element on the shadow stack is a return address, @value{GDBN} prints the +same fields as in -stack-list-frames with the return address on the shadow +stack as @var{addr}. +If the element on the shadow stack is not a return address, @value{GDBN} +only prints @var{level}, @var{addr} and @var{arch}. + +If invoked without arguments, this command prints a backtrace for the +whole shadow stack. Like the -stack-list-frames command, if given two +integer arguments, it shows the frames whose levels are between +the two arguments (inclusive). + +@subsubheading @value{GDBN} Command + +The corresponding @value{GDBN} command is @samp{backtrace shadow}. + +@subsubheading Example + +Show shadow stack frames between @var{low-frame} and @var{high-frame}: + +@smallexample +(gdb) +-shadow-stack-list-frames 0 2 +^done,shadow-stack=[ +shadow-stack-frame=@{level="0",addr="0x00007ffff7c54d90", + func="__restore_rt",from="/lib64/libc.so.6",arch="i386:x86-64"@}, +shadow-stack-frame=@{level="1",addr="0x80007ffff79fffd8",arch="i386:x86-64"@}, +shadow-stack-frame=@{level="2",addr="0x00007ffff7c54ce6", + func="__GI_raise",file="../sysdeps/posix/raise.c", + fullname="/usr/src/debug/glibc-2.34-90.bkc.el9.x86_64/sysdeps/posix/raise.c", + line="27",arch="i386:x86-64"@}] +(gdb) +@end smallexample + +For frame 1 we can see that we only print @var{level}, @var{addr} and +@var{arch}. This is due to an element on the shadow stack which is not a +return address. @findex -stack-list-locals @anchor{-stack-list-locals} diff --git a/gdb/mi/mi-cmd-stack.c b/gdb/mi/mi-cmd-stack.c index f2a4b3044b0..a7a50da9f48 100644 --- a/gdb/mi/mi-cmd-stack.c +++ b/gdb/mi/mi-cmd-stack.c @@ -32,6 +32,9 @@ #include "mi-parse.h" #include #include "inferior.h" +#include "gdbarch.h" +#include "gdbcore.h" +#include "arch-utils.h" enum what_to_list { locals, arguments, all }; @@ -773,3 +776,142 @@ mi_cmd_stack_info_frame (const char *command, const char *const *argv, print_frame_info (user_frame_print_options, get_selected_frame (NULL), 1, LOC_AND_ADDRESS, 0, 1); } + +/* Parse arguments of -shadow-stack-list-frames command and set FRAME_LOW + and FRAME_HIGH accordingly. Throw an error in case the arguments are + invalid. */ +static void +mi_cmd_shadow_stack_list_frames_parse_args (const char *const *argv, + int argc, int &frame_low, + int &frame_high) +{ + /* There should either be low - high range, or no arguments. */ + if ((argc != 0) && (argc != 2)) + error (_("-shadow-stack-list-frames: Usage: [FRAME_LOW FRAME_HIGH]")); + + /* If there is a range, set it. */ + if (argc == 2) + { + frame_low = atoi (argv[0]); + frame_high = atoi (argv[1]); + std::string err_str; + if (frame_low < 0) + { + err_str = "``" + std::to_string (frame_low) + "''"; + if (frame_high < 0) + err_str += " and ``" + std::to_string (frame_high) + "''"; + } + else if (frame_high < 0) + err_str = "``" + std::to_string (frame_high) + "''"; + + if (!err_str.empty ()) + { + err_str = "-shadow-stack-list-frames: Invalid option " + err_str; + error ("%s.", err_str.c_str ()); + } + } + else + { + /* No arguments, print the whole shadow stack backtrace. */ + frame_low = -1; + frame_high = -1; + } +} + +/* Print a list of the shadow stack frames. Args can be none, in which + case we want to print the whole shadow stack backtrace, or a pair of + numbers specifying the frame numbers at which to start and stop the + display. If the two numbers are equal, a single frame will be + displayed. */ + +void +mi_cmd_shadow_stack_list_frames (const char *command, + const char *const *argv, + int argc) +{ + int frame_low; + int frame_high; + + mi_cmd_shadow_stack_list_frames_parse_args (argv, argc, frame_low, + frame_high); + + if (!target_has_stack ()) + error (_("-shadow-stack-list-frames: No shadow stack.")); + + gdbarch *gdbarch = get_current_arch (); + if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)) + error (_("-shadow-stack-list-frames: Printing of shadow stack " + "backtrace is not supported for the current target.")); + + + regcache *regcache = get_thread_regcache (inferior_thread ()); + bool shadow_stack_enabled = false; + + std::optional start_ssp + = gdbarch_get_shadow_stack_pointer (gdbarch, regcache, + shadow_stack_enabled); + if (!start_ssp.has_value () || !shadow_stack_enabled) + error (_("-shadow-stack-list-frames: Shadow stack is not enabled " + " for the current thread.")); + + ui_out_emit_list list_emitter (current_uiout, "shadow-stack"); + + /* Check if START_SSP points to a shadow stack memory range and use + the returned range to determine when to stop unwinding. + Note that a shadow stack memory range can change, due to shadow stack + switches for instance on x86 for an inter-privilege far call or when + calling an interrupt/exception handler at a higher privilege level. + Shadow stack for userspace is supported for amd64 linux starting with + Linux kernel v6.6. However, shadow stack switches are not supported + due to missing kernel space support. We therefore implement this + command without support for shadow stack switches for now. */ + std::pair range; + if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp, + &range)) + { + /* If START_SSP points off the shadow stack memory range, we cannot + print the shadow stack backtrace. This is possible, for + instance, on x86 if NEW_SSP points to the end of RANGE which + means that the shadow stack is empty. */ + return; + } + + /* For ARM's Guarded Control Stack, if START_SSP points one element + before the end of RANGE, it means that the shadow stack pointer + is valid but the shadow stack is empty. */ + if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch) + && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range)) + return; + + std::optional curr; + CORE_ADDR new_value; + const int addr_size_byte = gdbarch_addr_bit (gdbarch) / 8; + const bfd_endian byte_order = gdbarch_byte_order (gdbarch); + if (!safe_read_memory_unsigned_integer (*start_ssp, addr_size_byte, + byte_order, &new_value)) + error (_("-shadow-stack-list-frames: Cannot read shadow stack memory.")); + + curr = {*start_ssp, new_value, 0, ssp_unwind_stop_reason::no_error}; + + /* Let's position curr on the shadow stack frame at which to start the + display. This could be the innermost frame if the whole shadow stack + needs displaying, or if frame_low is 0. */ + int frame_num = 0; + for (; curr.has_value () && frame_num < frame_low; frame_num++) + curr = curr->unwind_prev_shadow_stack_frame_info (gdbarch, range); + + if (!curr.has_value ()) + error (_("-shadow-stack-list-frames: Not enough frames on the shadow " + "stack.")); + + /* Now let's print the shadow stack frames up to frame_high, or until + the bottom of the shadow stack. */ + for (; curr.has_value () && (frame_num <= frame_high || frame_high == -1); + frame_num++) + { + QUIT; + print_shadow_stack_frame_info (gdbarch, user_frame_print_options, *curr, + LOCATION); + curr = curr->unwind_prev_shadow_stack_frame_info (gdbarch, range); + } +} diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index 552eafbbcbc..f823ff3b238 100644 --- a/gdb/mi/mi-cmds.c +++ b/gdb/mi/mi-cmds.c @@ -303,6 +303,8 @@ add_builtin_mi_commands () add_mi_cmd_mi ("stack-info-frame", mi_cmd_stack_info_frame); add_mi_cmd_mi ("stack-list-arguments", mi_cmd_stack_list_args); add_mi_cmd_mi ("stack-list-frames", mi_cmd_stack_list_frames); + add_mi_cmd_mi ("shadow-stack-list-frames", + mi_cmd_shadow_stack_list_frames); add_mi_cmd_mi ("stack-list-locals", mi_cmd_stack_list_locals); add_mi_cmd_mi ("stack-list-variables", mi_cmd_stack_list_variables); add_mi_cmd_mi ("stack-select-frame", mi_cmd_stack_select_frame, diff --git a/gdb/mi/mi-cmds.h b/gdb/mi/mi-cmds.h index 81c4f0b4e37..2a37bd8861a 100644 --- a/gdb/mi/mi-cmds.h +++ b/gdb/mi/mi-cmds.h @@ -100,6 +100,7 @@ extern mi_cmd_argv_ftype mi_cmd_stack_list_frames; extern mi_cmd_argv_ftype mi_cmd_stack_list_locals; extern mi_cmd_argv_ftype mi_cmd_stack_list_variables; extern mi_cmd_argv_ftype mi_cmd_stack_select_frame; +extern mi_cmd_argv_ftype mi_cmd_shadow_stack_list_frames; extern mi_cmd_argv_ftype mi_cmd_symbol_list_lines; extern mi_cmd_argv_ftype mi_cmd_symbol_info_functions; extern mi_cmd_argv_ftype mi_cmd_symbol_info_module_functions; diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c index e930187604e..6a440bc5379 100644 --- a/gdb/shadow-stack.c +++ b/gdb/shadow-stack.c @@ -249,6 +249,13 @@ do_print_shadow_stack_frame_info uiout->field_string ("func", frame_type, metadata_style.style ()); + + if (uiout->is_mi_like_p ()) + { + uiout->field_string + ("arch", (gdbarch_bfd_arch_info (gdbarch))->printable_name); + } + uiout->text ("\n"); gdb_flush (gdb_stdout); return; @@ -312,6 +319,12 @@ do_print_shadow_stack_frame_info if (lib != nullptr) print_lib (uiout, lib, true); } + + if (uiout->is_mi_like_p ()) + { + uiout->field_string + ("arch", gdbarch_bfd_arch_info (gdbarch)->printable_name); + } } /* Extra scope to print frame tuple. */ uiout->text ("\n"); @@ -334,10 +347,9 @@ do_print_shadow_stack_frame_info gdb_flush (gdb_stdout); } -/* Redirect output to a temporary buffer for the duration of - do_print_shadow_stack_frame_info. */ +/* See shadow-stack.h. */ -static void +void print_shadow_stack_frame_info (gdbarch *gdbarch, const frame_print_options &fp_opts, const shadow_stack_frame_info &frame, print_what print_what) diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index 24a6d48efa0..ae5adc6fb01 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -81,4 +81,15 @@ class shadow_stack_frame_info = ssp_unwind_stop_reason::no_error; }; +/* Print information of shadow stack frame info FRAME. The output is + formatted according to PRINT_WHAT. For the meaning of PRINT_WHAT, see + enum print_what comments in frame.h. Note that PRINT_WHAT is + overridden, if PRINT_OPTIONS.print_frame_info != print_frame_info_auto. + Redirect output to a temporary buffer for the duration of + do_print_shadow_stack_frame_info. */ + +void print_shadow_stack_frame_info + (gdbarch *gdbarch, const frame_print_options &fp_opts, + const shadow_stack_frame_info &frame, print_what print_what); + #endif /* GDB_SHADOW_STACK_H */ diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp new file mode 100644 index 00000000000..c84f96ce37b --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack-signal.exp @@ -0,0 +1,69 @@ +# Copyright 2024-2026 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 . + +# Test the mi command -shadow-stack-list-frames for signal handling on linux. + +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +require allow_ssp_tests {istarget "*-*-linux*"} + +save_vars { ::env(GLIBC_TUNABLES) } { + append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK" + + set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack-signal.c" + set testfile mi-shadow-stack + + # Test shadow-stack-list-frames for shadow stack element which is no + # return address. + if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug additional_flags="-fcf-protection=return"}] } { + return + } + + if { [mi_clean_restart $testfile] } { + return + } + + mi_runto_main + mi_send_resuming_command "exec-continue" "continue till signal" + + set r_signal "reason=\"signal-received\",signal-name=\"SIGUSR1\",signal-meaning=\"User defined signal 1\"" + gdb_expect { + -re ".*stopped,${r_signal}.*$mi_gdb_prompt" { + pass "Wait for user interrupt" + } + timeout { + fail "Wait for user interrupt (timeout)" + return + } + } + + mi_gdb_test "break handler" \ + {(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done} + + mi_execute_to "exec-continue" "breakpoint-hit" "handler" ".*" ".*" ".*" \ + {"" "disp=\"keep\""} "continue to handler" + + # We only test the frame belonging to the shadow stack element which + # is not a return address. This frame is trigged by the signal + # exception. + set any "\[^\"\]+" + mi_gdb_test "231-shadow-stack-list-frames 1 1" \ + "231\\^done,shadow-stack=\\\[shadow-stack-frame=\{level=\"1\",func=\"\",arch=\"$any\"\}\\\]" \ + "test shadow-stack-list-frames" + + mi_gdb_exit +} diff --git a/gdb/testsuite/gdb.mi/mi-shadow-stack.exp b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp new file mode 100644 index 00000000000..ad92f21cf3e --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp @@ -0,0 +1,65 @@ +# Copyright 2024-2026 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 . + +# Test the mi command -shadow-stack-list-frames. + +load_lib mi-support.exp +set MIFLAGS "-i=mi" + +require allow_ssp_tests + +save_vars { ::env(GLIBC_TUNABLES) } { + append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK" + + set srcfile "${srcdir}/gdb.arch/amd64-shadow-stack.c" + set testfile mi-shadow-stack + + if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug additional_flags="-fcf-protection=return"}] } { + restart_and_run_infcall_call2 + } + + if { [mi_clean_restart $testfile] } { + return + } + + mi_runto_main + + mi_gdb_test "break call2" \ + {(&.*)*.*~"Breakpoint 2 at.*\\n".*=breakpoint-created,bkpt=\{number="2",type="breakpoint".*\}.*\n\^done} + + mi_execute_to "exec-continue" "breakpoint-hit" "call2" ".*" ".*" ".*" \ + {"" "disp=\"keep\""} "continue to call2" + + set any "\[^\"\]+" + set any_remaining_frame_attr "\[^\r\n]+" + + # It's enough to test the first 3 frames. For frame 3 we just test that it + # exists as other attributes might depend on the environment. + set frame_start "shadow-stack-frame=\{level=" + set frame1 "$frame_start\"0\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}" + set frame2 "$frame_start\"1\",addr=\"$hex\",func=\"main\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}" + set frame3 "$frame_start\"2\",addr=\"$hex\"$any_remaining_frame_attr\}" + mi_gdb_test "231-shadow-stack-list-frames" \ + "231\\^done,shadow-stack=\\\[$frame1.*$frame2.*$frame3.*" \ + "test shadow-stack-list-frames" + + # Test low-frame/high-frame + mi_gdb_test "231-shadow-stack-list-frames 1 2" \ + "231\\^done,shadow-stack=\\\[$frame2.*$frame3\\\]" \ + "test shadow-stack-list-frames low/high-frames" + + mi_gdb_exit +} -- 2.34.1