From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id UeijCpbhtGqL3zsAWB0awg (envelope-from ) for ; Thu, 24 Sep 2026 04:38:46 -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=KwI/4Qlh; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 2682E1E06B; Thu, 24 Sep 2026 04:38:46 -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 [IPv6:2620:52:6:3111::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 63F551E033 for ; Thu, 24 Sep 2026 04:38:44 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 4C3544BB3BCA for ; Thu, 24 Sep 2026 08:38:42 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 4C3544BB3BCA 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=KwI/4Qlh Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.11]) by sourceware.org (Postfix) with ESMTPS id 345724BB589A for ; Thu, 24 Sep 2026 08:35:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 345724BB589A 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 345724BB589A Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.11 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790238909; cv=none; b=gmxUVDAl2Tssc9mGPQa4exCqW2w8wqNvgi1FWqLzusetJ3qrfxXCpFHn7MTcdC3iaxvRuTfa6Dy5Jo1BkA52YxupsYFDSkLA4WutLdqzG9Jj9mKPLguMXC9utPa8TOk6wGfcjMJB9M33rHVJE/WzQdNgAoNa+myfMfMzKgfDExU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790238909; c=relaxed/simple; bh=T6lfp1CRTY3q4QgYFRFgKm/DiqzIBIUhBTppsnILIc4=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=wICmP2nRQd6kPL1OTMYcM5mIvgy/exAeMITQQBOvl7uYMOSmyJKIhuqL+CpgXqycBnCcIwxaFIxxLQSWCOzfsl+9HISZQalyFxyF3VKhr/H3esxAK5CcGLJRA0PdCs78EIZhSExJ6GMsbBv0LYjXWGtecQnHkn9llCsVhiDwMjc= 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=KwI/4Qlh DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 345724BB589A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1790238909; x=1821774909; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=T6lfp1CRTY3q4QgYFRFgKm/DiqzIBIUhBTppsnILIc4=; b=KwI/4QlhvYBslCU6otMc8W61NymQl7/f8n2I3EhOAarv04ysFICCwyyW MCbGB4CJ8ZRLe5qF7cinAEl88DrOQ8uFp3AKUWjI7L7hVqckOkbmjiCjt v6SoN2ORcqcYLFvHbl8HuD0jcAM34Yelz62TYJ5BVF+nL8+gvh/Z1MSJx Xyb4LVTJYwtG7/ATX5BL36wUY6ftv7EU9vB1Cu68aU6SceCeuXG3Qaude MBNiZyWgcrbdSwclALvzVYXJNV1kqE9E5QiEDzi3IdDV+sKKFiZgOsBDM HWQLCcufPFyaQrhQLtrOlTAgIAi6FNENkmMZJjPZrgta+jZjzwKzf+BFh g==; X-CSE-ConnectionGUID: mxKzpn9AQq6IZBVTYEDOhw== X-CSE-MsgGUID: Q5GFxiXRSUmwzX9xkzMHCQ== X-IronPort-AV: E=McAfee;i="6800,10657,11914"; a="100339140" X-IronPort-AV: E=Sophos;i="6.27,120,1787036400"; d="scan'208";a="100339140" Received: from fmviesa004.fm.intel.com ([10.60.135.144]) by orvoesa103.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Sep 2026 01:35:07 -0700 X-CSE-ConnectionGUID: 4dB1CqoHQoabwGP2nPngrQ== X-CSE-MsgGUID: RLY0P2DVSPGjQ8GKAW7CfA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.27,120,1787036400"; d="scan'208";a="278680276" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa004-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Sep 2026 01:35:06 -0700 From: Christina Joos To: gdb-patches@sourceware.org Cc: thiago.bauermann@linaro.org, tom@tromey.com, luis.machado.foss@gmail.com Subject: [PATCH v5 13/13] gdb, mi: Add -shadow-stack-list-frames command Date: Thu, 24 Sep 2026 10:33:10 +0200 Message-ID: <20260924083311.1961530-14-christina.joos@intel.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260924083311.1961530-1-christina.joos@intel.com> References: <20260924083311.1961530-1-christina.joos@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 | 95 +++++++++++++ gdb/mi/mi-cmds.c | 2 + gdb/mi/mi-cmds.h | 1 + gdb/shadow-stack.c | 127 +++++++++++++----- gdb/shadow-stack.h | 43 ++++++ .../gdb.mi/mi-shadow-stack-signal.exp | 69 ++++++++++ gdb/testsuite/gdb.mi/mi-shadow-stack.exp | 93 +++++++++++++ 9 files changed, 453 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 707f3b5327e..52edd6de239 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -391,6 +391,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 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 c2f9474b4de..4558a4a523c 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -35585,6 +35585,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}, @var{func} 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 a3b577032ce..c127fc9d239 100644 --- a/gdb/mi/mi-cmd-stack.c +++ b/gdb/mi/mi-cmd-stack.c @@ -34,6 +34,9 @@ #include "inferior.h" #include "source.h" #include "gdbsupport/unordered_map.h" +#include "shadow-stack.h" +#include "gdbarch.h" +#include "arch-utils.h" enum what_to_list { locals, arguments, all }; @@ -830,3 +833,95 @@ 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) +{ + /* 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]); + if (frame_low < 0 && frame_high < 0) + error (_("-shadow-stack-list-frames: Invalid option " \ + "``%d'' and ``%d''."), + frame_low, frame_high); + else if (frame_low < 0) + error (_("-shadow-stack-list-frames: Invalid option ``%d''."), + frame_low); + else if (frame_high < 0) + error (_("-shadow-stack-list-frames: Invalid option ``%d''."), + frame_high); + } + 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 108ff645107..9e5d18dbeb4 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 86633cbcb49..a9cbde4ad78 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,35 @@ 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.")); + { + if (!current_uiout->is_mi_like_p ()) + { + error (_("Printing of the shadow stack backtrace is not " \ + "supported for the current target.")); + } + else + { + error (_("-shadow-stack-list-frames: " \ + "Printing of the shadow stack backtrace is not " \ + "supported for the current target.")); + } + } regcache *regcache = get_thread_regcache (inferior_thread ()); bool shadow_stack_enabled = false; @@ -621,10 +647,26 @@ 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.")); + { + if (!current_uiout->is_mi_like_p ()) + error (_("Shadow stack is not enabled for the current thread.")); + else + { + error (_("-shadow-stack-list-frames: " \ + "Shadow stack is not enabled for the current thread.")); + } + } + + return *start_ssp; +} - /* Check if START_SSP points to a shadow stack memory range and use - the returned range to determine when to stop unwinding. +/* 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 +674,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 +715,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 +731,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 +753,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..8cdc389846a 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -139,4 +139,47 @@ 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. + This helper is specific to printing the shadow stack backtrace and must + only be called from the CLI command "bt -shadow" or its MI equivalent + "-shadow-stack-list-frames" since the error messages are specific to + these commands. */ + +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..c9e9c80a76b --- /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-signal + + # 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.53.0 ________________________________________ Intel Deutschland GmbH Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany Tel: +49 (89) 99143-0 www.intel.de Managing Directors: Candice Moore, Jeffrey Schneiderman, Ramachandran Sitaraman Chairperson of the Supervisory Board: Sonja Pierer Registered Seat: Munich Commercial Register B: Amtsgericht Munich HRB 186928 This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). Any review or distribution by others is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies.