From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id 0dxtHChhTmqwcCwAWB0awg (envelope-from ) for ; Wed, 08 Jul 2026 10:39:36 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=ixtzjb8d; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 6F6291E098; Wed, 08 Jul 2026 10:39:36 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id B8D461E024 for ; Wed, 08 Jul 2026 10:39:34 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 4E40F4BA2E2B for ; Wed, 8 Jul 2026 14:39:34 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4E40F4BA2E2B Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=ixtzjb8d Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.19]) by sourceware.org (Postfix) with ESMTPS id 85BFD4BA2E11 for ; Wed, 8 Jul 2026 14:38:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 85BFD4BA2E11 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=intel.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 85BFD4BA2E11 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.19 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521502; cv=none; b=JlNcAGrTKfD7cdM4duHmxbMAhAXOWSBYgWUXqslm54gfQEAzVJXoE5NzjSJHwz3YuSqH7mDF37el/QDOECegRMwZmFcMT5OLlw2j1h26wdeQ0nceRNJc4zmGiru3vaQivkoHxcwepG40OUUYn0cgXDCxtIEkoOT/9R5nRf1qvu0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521502; c=relaxed/simple; bh=KaYMhIF/mX5fv7qQtoXorAuHV3fphTTcccJUz2w15D4=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=ZX4LdIreKDMw1DJMr0G4xLuzpvShpKWenwpDxT5v2uHkwuaWBOplezOkKaSq2cYMjteMyyr2HTnUY6FinPCLyu6g5l7eLg8Y1fyB6lNvm+Hjf7mIs2P8unKRJehflVldiCz5dCo2ivEugNnHq14EGEA2nwP+VZxNKumH2vZ7niw= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=ixtzjb8d DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 85BFD4BA2E11 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521503; x=1815057503; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=KaYMhIF/mX5fv7qQtoXorAuHV3fphTTcccJUz2w15D4=; b=ixtzjb8dBYsJjDbuc2xIqoyL7wAOsBU7YY+0KgZgtJ43oXyIEED9XB0F gHOSnNWYdK1dra2Ym1Y38creIutWZseLeVWNVxexajICdWQb0VJyHRj47 uOWjxIuXj+cPaw4JDs5trsRvVA454ohnX29qDpzfR1lqS8cG6WOtXH7WT sbjLzMjJ+F+EIMgNPodzQ86dBQq2QteKZF2HZ24cdwe6A7nzOWlnpunUm tj3Wsa7iOjZI333MTSCVZxJtOV/AFmzLKwqJgr8l5Klwq59FAGwr7FRfw mAo3OsVQbcHrlIDBNSmLHAGp87wAUdqL2J6r5EgWtwVFkK7YLASX4o0j2 w==; X-CSE-ConnectionGUID: 1uQbchj0T3KXrX50oTURgQ== X-CSE-MsgGUID: Tpx2YI0JSgKyjIMMapEG3g== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84158950" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84158950" Received: from fmviesa007.fm.intel.com ([10.60.135.147]) by orvoesa111.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:22 -0700 X-CSE-ConnectionGUID: 7pX3SUtlSbSHrdvlHKjEIw== X-CSE-MsgGUID: 3ec616GWQkGOHBnyYgJ6gw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="250945918" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa007-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:21 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 13/13] gdb, mi: Add -shadow-stack-list-frames command Date: Wed, 8 Jul 2026 14:36:39 +0000 Message-Id: <20260708143639.2214689-14-christina.schimpe@intel.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260708143639.2214689-1-christina.schimpe@intel.com> References: <20260708143639.2214689-1-christina.schimpe@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org Add the mi command for the command "backtrace -shadow". Similar to the mi interface for the ordinary 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 | 8 ++ gdb/doc/gdb.texinfo | 51 ++++++++ gdb/mi/mi-cmd-stack.c | 100 +++++++++++++++ gdb/mi/mi-cmds.c | 2 + gdb/mi/mi-cmds.h | 1 + gdb/shadow-stack.c | 121 ++++++++++++------ gdb/shadow-stack.h | 40 ++++++ .../gdb.mi/mi-shadow-stack-signal.exp | 69 ++++++++++ gdb/testsuite/gdb.mi/mi-shadow-stack.exp | 93 ++++++++++++++ 9 files changed, 449 insertions(+), 36 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 fa06d21ddac..8d45ec373f2 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -333,6 +333,14 @@ qExecAndArgs AIX version is now AIX 7.2 TL5. GDB will only support DWARF debugging in AIX, going forward. +* New MI commands + +-shadow-stack-list-frames + Added new MI command which is equivalent to the CLI command + 'backtrace -shadow' but supports 'low-frame' and 'high-frame' as + command line parameters. The parameters are used to print shadow + stack frames between 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 665576c4fc4..0f48709fc44 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -35532,6 +35532,57 @@ 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 @code{-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 @code{-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="0x00007ffff7c45330", + func="",arch="i386:x86-64"@}, +shadow-stack-frame=@{level="1",addr="0x80007ffff7bfffd8", + func="",arch="i386:x86-64"@}, +shadow-stack-frame=@{level="2",addr="0x00007ffff7c4527e", + func="__GI_raise",file="[...]/raise.c", + fullname="./[...]/posix/raise.c", + line="26",arch="i386:x86-64"@}] +(gdb) +@end smallexample + +Note that for frame 0 and 1, @value{GDBN} printed only @var{level}, +@var{addr}, @var{func} and @var{arch}. For frame 0, @var{func} is +@code{""} similar to the @code{-stack-list-frames} +command. For frame 1 @value{GDBN} prints @code{""} for +@var{func}. 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 280a08d121e..e6948838da4 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 "shadow-stack.h" +#include "gdbarch.h" +#include "arch-utils.h" enum what_to_list { locals, arguments, all }; @@ -764,3 +767,100 @@ mi_cmd_stack_info_frame (const char *command, const char *const *argv, print_frame_info (user_frame_print_options, get_selected_frame (), 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) +{ + const std::string mi_cmd_name = "-shadow-stack-list-frames"; + /* There should either be low - high range, or no arguments. */ + if ((argc != 0) && (argc != 2)) + error (_("%s: Usage: [FRAME_LOW FRAME_HIGH]"), mi_cmd_name.c_str ()); + + /* 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 = mi_cmd_name + ": 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); + + gdbarch *gdbarch = get_current_arch (); + const CORE_ADDR start_ssp + = get_validated_shadow_stack_pointer (gdbarch); + + ui_out_emit_list list_emitter (current_uiout, "shadow-stack"); + + std::optional> range + = get_non_empty_shadow_stack_range (gdbarch, start_ssp); + if (!range.has_value ()) + return; + + /* Extract the first shadow stack frame info (level 0). */ + std::optional curr + = get_shadow_stack_frame_info (gdbarch, start_ssp, 0); + + /* 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 (*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 (user_frame_print_options, *curr, + LOCATION); + curr = curr->unwind_prev_shadow_stack_frame_info (*range); + } +} diff --git a/gdb/mi/mi-cmds.c b/gdb/mi/mi-cmds.c index 48d85e8785c..51f173f66ee 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 6515d4b182d..12d44a497a6 100644 --- a/gdb/shadow-stack.c +++ b/gdb/shadow-stack.c @@ -276,8 +276,21 @@ do_print_shadow_stack_frame_info uiout->text ("#"); uiout->field_fmt_signed (2, ui_left, "level", frame.level); + if (uiout->is_mi_like_p ()) + { + uiout->field_string + ("addr", hex_string_custom (frame.value, element_size * 2), + address_style.style ()); + } + uiout->field_string ("func", str, metadata_style.style ()); + if (uiout->is_mi_like_p ()) + { + uiout->field_string + ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name); + } + uiout->text ("\n"); gdb_flush (gdb_stdout); return; @@ -323,6 +336,12 @@ do_print_shadow_stack_frame_info print_lib (uiout, lib); } + if (uiout->is_mi_like_p ()) + { + uiout->field_string + ("arch", gdbarch_bfd_arch_info (frame.arch)->printable_name); + } + uiout->text ("\n"); } @@ -342,10 +361,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 (const frame_print_options &fp_opts, const shadow_stack_frame_info &frame, @@ -372,11 +390,9 @@ ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason) gdb_assert_not_reached ("invalid unwind stop reason."); } -/* Read the memory at shadow stack pointer SSP and assign it to - RETURN_VALUE. In case of success, return true. Otherwise, - return false. */ +/* See shadow-stack.h. */ -static bool +bool read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp, CORE_ADDR &return_value) { @@ -435,12 +451,9 @@ get_trailing_outermost_shadow_stack_frame_info return trailing; } -/* If possible, get shadow stack frame info for the shadow stack pointer - SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used - as fallback gdbarch in case the gdbarch cannot be extracted from the - SAL. Usually this is the gdbarch of the previous frame. */ +/* See shadow-stack.h. */ -static std::optional +std::optional get_shadow_stack_frame_info (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level) { @@ -596,22 +609,30 @@ shadow_stack_frame_info::inside_main_func () const return false; } -/* Print all elements on the shadow stack or just the innermost COUNT_EXP - frames. */ +/* See shadow-stack.h. */ -void -backtrace_shadow_command (const frame_print_options &fp_opts, - const char *count_exp, int from_tty) +CORE_ADDR +get_validated_shadow_stack_pointer (gdbarch *gdbarch) { if (!target_has_stack ()) - error (_("No shadow stack.")); + { + if (!current_uiout->is_mi_like_p ()) + error (_("No shadow stack.")); + else + error (_("-shadow-stack-list-frames: No shadow stack.")); + } - gdbarch *gdbarch = get_current_arch (); if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch) || !gdbarch_top_addr_empty_shadow_stack_p (gdbarch) || gdbarch_ssp_regnum (gdbarch) == -1) - error (_("Printing of the shadow stack backtrace is not supported for" - " the current target.")); + { + std::string err_str = "Printing of the shadow stack backtrace"; + err_str += " is not supported for the current target."; + if (!current_uiout->is_mi_like_p ()) + error (_("%s"), err_str.c_str()); + else + error (_("-shadow-stack-list-frames: %s"), err_str.c_str()); + } regcache *regcache = get_thread_regcache (inferior_thread ()); bool shadow_stack_enabled = false; @@ -621,10 +642,25 @@ backtrace_shadow_command (const frame_print_options &fp_opts, shadow_stack_enabled); if (!start_ssp.has_value () || !shadow_stack_enabled) - error (_("Shadow stack is not enabled for the current thread.")); + { + std::string err_str + = "Shadow stack is not enabled for the current thread."; + if (!current_uiout->is_mi_like_p ()) + error (_("%s"), err_str.c_str()); + else + error (_("-shadow-stack-list-frames: %s"), err_str.c_str()); + } - /* Check if START_SSP points to a shadow stack memory range and use - the returned range to determine when to stop unwinding. + return *start_ssp; +} + +/* See shadow-stack.h. */ + +std::optional> +get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp) +{ + /* Check if 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. @@ -632,26 +668,39 @@ backtrace_shadow_command (const frame_print_options &fp_opts, 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. */ - bool is_top_addr_empty_shadow_stack = false; std::pair range; - if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_ssp, - &range)) + if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp, &range)) { /* For x86, if the current shadow stack pointer does not point to shadow stack memory but is valid, the shadow stack is empty. */ - is_top_addr_empty_shadow_stack = true; + return {}; } - if (!is_top_addr_empty_shadow_stack) + if (gdbarch_top_addr_empty_shadow_stack_p (gdbarch) + && gdbarch_top_addr_empty_shadow_stack (gdbarch, ssp, range)) { /* For ARM's Guarded Control Stack, the shadow stack can be empty even though START_SSP points to shadow stack memory range. */ - is_top_addr_empty_shadow_stack - = gdbarch_top_addr_empty_shadow_stack_p (gdbarch) - && gdbarch_top_addr_empty_shadow_stack (gdbarch, *start_ssp, range); + return {}; } - if (is_top_addr_empty_shadow_stack) + return {range}; +} + +/* Print all elements on the shadow stack or just the innermost COUNT_EXP + frames. */ + +void +backtrace_shadow_command (const frame_print_options &fp_opts, + const char *count_exp, int from_tty) +{ + gdbarch *gdbarch = get_current_arch (); + const CORE_ADDR start_ssp + = get_validated_shadow_stack_pointer (gdbarch); + + std::optional> range + = get_non_empty_shadow_stack_range (gdbarch, start_ssp); + if (!range.has_value ()) { gdb_printf (_("The shadow stack is empty.\n")); return; @@ -660,7 +709,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts, /* Extract the first shadow stack frame info (level 0). */ ssp_unwind_stop_reason reason = ssp_unwind_stop_reason::no_error; std::optional current - = get_shadow_stack_frame_info (gdbarch, *start_ssp, 0); + = get_shadow_stack_frame_info (gdbarch, start_ssp, 0); if (!current.has_value ()) reason = ssp_unwind_stop_reason::memory_read_error; @@ -676,7 +725,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts, if (count < 0) { trailing = get_trailing_outermost_shadow_stack_frame_info - (range, std::abs (count), *current); + (*range, std::abs (count), *current); if (!trailing.has_value ()) reason = current->unwind_stop_reason; @@ -698,7 +747,7 @@ backtrace_shadow_command (const frame_print_options &fp_opts, print_shadow_stack_frame_info (fp_opts, *current, LOCATION); trailing = current; - current = current->unwind_prev_shadow_stack_frame_info (range); + current = current->unwind_prev_shadow_stack_frame_info (*range); } /* If we've stopped before the end, mention that. */ diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index d4b0d53acf3..4eb8c53f995 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -139,4 +139,44 @@ class shadow_stack_frame_info bool inside_main_func () const; }; +/* Read the memory at shadow stack pointer SSP and assign it to + RETURN_VALUE. In case of success, return true. Otherwise, return + false. */ + +bool read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp, + CORE_ADDR &return_value); + +/* 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 + (const frame_print_options &fp_opts, + const shadow_stack_frame_info &frame, + print_what print_what); + +/* Check if shadow stacks are supported and enabled. If it is, return the + validated shadow stack pointer. If not, throw an appropriate error + message, either for the CLI command "bt -shadow" or for the MI equivalent + -shadow-stack-list-frames. */ + +CORE_ADDR get_validated_shadow_stack_pointer (gdbarch *gdbarch); + +/* If the shadow stack pointed to by SSP is empty, return an empty optional. + Otherwise return the shadow stack memory range. */ + +std::optional> + get_non_empty_shadow_stack_range (gdbarch *gdbarch, const CORE_ADDR ssp); + +/* If possible, get shadow stack frame info for the shadow stack pointer + SSP and its current frame LEVEL. Pass FALLBACK_ARCH which can be used + as fallback gdbarch in case the gdbarch cannot be extracted from the + SAL. Usually this is the gdbarch of the previous frame. */ + +std::optional get_shadow_stack_frame_info + (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level); + #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..cf302ae1a45 --- /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 triggered 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\",addr=\"$hex\",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..ac045d6f3a2 --- /dev/null +++ b/gdb/testsuite/gdb.mi/mi-shadow-stack.exp @@ -0,0 +1,93 @@ +# 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"}] } { + return + } + + 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\\\]" \ + "test shadow-stack-list-frames" + + mi_gdb_test "set backtrace past-main on" \ + ".*=cmd-param-changed,param=\"backtrace past-main\",value=\"on\".*\\^done" + mi_gdb_test "231-shadow-stack-list-frames" \ + "231\\^done,shadow-stack=\\\[$frame1.*$frame2.*$frame3.*\\\]" \ + "test shadow-stack-list-frames past main" + + # Test low-frame/high-frame + mi_gdb_test "231-shadow-stack-list-frames 0 1" \ + "231\\^done,shadow-stack=\\\[$frame1.*$frame2\\\]" \ + "test shadow-stack-list-frames low/high-frames" + + # Test inferior function calls. + set inside_infcall_str "The program being debugged stopped while in a function called from GDB" + mi_gdb_test "call (int) call2()" \ + ".*Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \ + "call (int) call2()" + + set dummy_frame1 "$frame_start\"0\",addr=\"$hex\",func=\"\",arch=\"$any\"\}" + set frame1 "$frame_start\"1\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}" + mi_gdb_test "231-shadow-stack-list-frames" \ + "231\\^done,shadow-stack=\\\[$dummy_frame1.*$frame1.*" \ + "test shadow-stack-list-frames inside inferior call" + + mi_gdb_test "call (int) call2()" \ + ".*Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \ + "Execute an inferior call inside an inferior call." + + set dummy_frame2 "$frame_start\"1\",addr=\"$hex\",func=\"\",arch=\"$any\"\}" + set frame2 "$frame_start\"2\",addr=\"$hex\",func=\"call1\",file=\"$any\",fullname=\"$any\",line=\"$decimal\",arch=\"$any\"\}" + mi_gdb_test "231-shadow-stack-list-frames" \ + "231\\^done,shadow-stack=\\\[$dummy_frame1.*$dummy_frame2.*$frame2.*" \ + "test shadow-stack-list-frames inside nested inferior call" + + mi_gdb_exit +} -- 2.34.1 Intel Deutschland GmbH Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany Tel: +49 89 991 430, www.intel.de Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell Chairperson of the Supervisory Board: Nicole Lau Registered Seat: Munich Commercial Register: Amtsgericht Muenchen HRB 186928