From patchwork Wed Jul 8 14:36:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138745 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 28C094BA2E33 for ; Wed, 8 Jul 2026 14:38:28 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 28C094BA2E33 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=iw/stjGg X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.15]) by sourceware.org (Postfix) with ESMTPS id 36C894BA2E11 for ; Wed, 8 Jul 2026 14:37:09 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 36C894BA2E11 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 36C894BA2E11 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.15 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521429; cv=none; b=fwCa7SPD3pa50EcpKdest12J1KhL0xDcMMUC7PPc6nmc7nI9u7csaJWKLSeIwSUhFUXlEiTTlaNWSbnxbMh845T1g8ER/i0IOZ7idCOhz88eWuv8+MAo/LN2//6SYJBjFYG99yfNQ/19pLOyCHvNGXCVsCMBqG+t6vXoElt2Tm8= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521429; c=relaxed/simple; bh=t0cKpxDUkSmYJMeEdcaMnj/scMuLq27HTe9JoBGcskA=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=FsF+K3A+Z1wNhoy5LGFm6henJC+jGOar9q0ij1WwFekiTIDAnJcCbbDe0zJoI7TJmjQgwLl1LfP6KG1zXVQ+9333XuMvWZkUCuNN3eFcWRxA0qKRDhiX957BmeISqhgbcDPtq/dxSULNuOfsJccYiXjO5vutNpav4wdeohn3Spc= 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=iw/stjGg DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 36C894BA2E11 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521429; x=1815057429; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=t0cKpxDUkSmYJMeEdcaMnj/scMuLq27HTe9JoBGcskA=; b=iw/stjGgZSQizTzKBvJouCkANOWmQNCYspjkZo+5jM/lSsvvjGl/4Jai FpUHMmmCaXPMWBZ0WjjEKsOWyvjXjyH2tFzgkroUN8i90bhgYFnz3S3LA pnM3tMbX0AbAsnAvp/HtZ/qYz8JqVeRB62CHQACG9Hlqumwe8EzinUP7f edZ+MT9Dz3ykSmpuC9eXAegZDmie3I5+Xx/J9bltDlLdv9aQqIHBP0sgW DEq9FAQBXOC22ClGuNAab9kQW5I3tmc8DdbPCtIj8nfeL6hbDd+jy3/Hs WTDKh2kIenzqynYmOSJ47h/071iwgz0LzCNmd5nYqlH85qTnJwZzKNnVG A==; X-CSE-ConnectionGUID: MwvY4aO4T5egEZnIS/WzcQ== X-CSE-MsgGUID: gA54H3vmTqSTRH7cdXrX6Q== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="87867484" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="87867484" Received: from orviesa006.jf.intel.com ([10.64.159.146]) by orvoesa107.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:09 -0700 X-CSE-ConnectionGUID: ZSq8xUSLR2aBFB+KHTAsaQ== X-CSE-MsgGUID: cjBv0jdVTwmm1Te9JfCpEA== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="252560298" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by orviesa006-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:08 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 01/13] gdb: Generalize handling of the shadow stack pointer. Date: Wed, 8 Jul 2026 14:36:27 +0000 Message-Id: <20260708143639.2214689-2-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org Until now, handling of the shadow stack pointer has been done in the target dependent implementations of the gdbarch hook 'gdbarch_shadow_stack_push'. Also amd64 and aarch64 linux specific unwinders for the shadow stack pointer are implemented. In a following patch a command line option "-shadow" will be added to the backtrace command to print the shadow stack backtrace. This requires more target-independent logic to handle the shadow stack pointer. To avoid that we duplicate the logic, add new source and header files "shadow-stack" for the implementation of shadow_stack_push and shadow stack pointer unwinding in a target-independent way. Since the conditions for an empty shadow stack differ betweens ARM's GCS and CET shadow stack this further requires a new gdbarch hook gdbarch_top_addr_empty_shadow_stack to unwind the previous shadow stack pointer. The ARM specific implementation for gdbarch_top_addr_empty_shadow_stack will added in the following commit: "aarch64: Implement gdbarch function top_addr_empty_shadow_stack.". Reviewed-By: Tom Tromey Reviewed-by: Thiago Jung Bauermann --- gdb/Makefile.in | 2 + gdb/aarch64-tdep.c | 50 +++--------- gdb/amd64-linux-tdep.c | 134 +++---------------------------- gdb/amd64-tdep.c | 22 +++++ gdb/gdbarch-gen.c | 126 +++++++++++++++++++++++------ gdb/gdbarch-gen.h | 63 +++++++++++---- gdb/gdbarch_components.py | 84 +++++++++++++++---- gdb/infcall.c | 4 +- gdb/linux-tdep.c | 9 ++- gdb/shadow-stack.c | 164 ++++++++++++++++++++++++++++++++++++++ gdb/shadow-stack.h | 42 ++++++++++ 11 files changed, 477 insertions(+), 223 deletions(-) create mode 100644 gdb/shadow-stack.c create mode 100644 gdb/shadow-stack.h diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 57f384170ab..4d1bbe5674f 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -1183,6 +1183,7 @@ COMMON_SFILES = \ sentinel-frame.c \ ser-event.c \ serial.c \ + shadow-stack.c \ skip.c \ solib.c \ solib-target.c \ @@ -1652,6 +1653,7 @@ HFILES_NO_SRCDIR = \ serial.h \ ser-tcp.h \ ser-unix.h \ + shadow-stack.h \ sh-tdep.h \ sim-regno.h \ skip.h \ diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index a84da1fd59e..848cee3043c 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -57,6 +57,9 @@ /* For inferior_ptid and current_inferior (). */ #include "inferior.h" + +#include "shadow-stack.h" + /* For std::sqrt and std::pow. */ #include @@ -1888,29 +1891,6 @@ pass_in_v_vfp_candidate (struct gdbarch *gdbarch, struct regcache *regcache, } } -/* Push LR_VALUE to the Guarded Control Stack. */ - -static void -aarch64_push_gcs_entry (regcache *regs, CORE_ADDR lr_value) -{ - gdbarch *arch = regs->arch (); - aarch64_gdbarch_tdep *tdep = gdbarch_tdep (arch); - CORE_ADDR gcs_addr; - - register_status status = regs->cooked_read (tdep->gcs_reg_base, &gcs_addr); - if (status != REG_VALID) - error (_("Can't read $gcspr.")); - - gcs_addr -= 8; - gdb_byte buf[8]; - store_integer (buf, gdbarch_byte_order (arch), lr_value); - if (target_write_memory (gcs_addr, buf, sizeof (buf)) != 0) - error (_("Can't write to Guarded Control Stack.")); - - /* Update GCSPR. */ - regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr); -} - /* Remove the newest entry from the Guarded Control Stack. */ static void @@ -1928,15 +1908,6 @@ aarch64_pop_gcs_entry (regcache *regs) regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8); } -/* Implement the "shadow_stack_push" gdbarch method. */ - -static void -aarch64_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr, - regcache *regcache) -{ - aarch64_push_gcs_entry (regcache, new_addr); -} - /* Implement the "push_dummy_call" gdbarch method. */ static CORE_ADDR @@ -3674,11 +3645,7 @@ aarch64_displaced_step_b (const int is_bl, const int32_t offset, regcache_cooked_write_unsigned (dsd->regs, AARCH64_LR_REGNUM, data->insn_addr + 4); dsd->dsc->linked_branch = true; - bool gcs_is_enabled; - gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs, - gcs_is_enabled); - if (gcs_is_enabled) - aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4); + shadow_stack_push (dsd->regs, data->insn_addr + 4); } } @@ -3842,7 +3809,7 @@ aarch64_displaced_step_others (const uint32_t insn, gdbarch_get_shadow_stack_pointer (dsd->regs->arch (), dsd->regs, gcs_is_enabled); if (gcs_is_enabled) - aarch64_push_gcs_entry (dsd->regs, data->insn_addr + 4); + shadow_stack_push (dsd->regs, data->insn_addr + 4); } else aarch64_emit_insn (dsd->insn_buf, insn); @@ -4816,6 +4783,10 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) /* Register a hook for converting a memory tag to a string. */ set_gdbarch_memtag_to_string (gdbarch, aarch64_memtag_to_string); + /* AArch64's shadow stack pointer is the GCSPR. */ + if (tdep->has_gcs ()) + set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base); + /* ABI */ set_gdbarch_short_bit (gdbarch, 16); set_gdbarch_int_bit (gdbarch, 32); @@ -4878,9 +4849,6 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) set_gdbarch_get_pc_address_flags (gdbarch, aarch64_get_pc_address_flags); - if (tdep->has_gcs ()) - set_gdbarch_shadow_stack_push (gdbarch, aarch64_shadow_stack_push); - tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data)); /* Fetch the updated number of registers after we're done adding all diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index 9b23db72bbe..42a62eaa975 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -48,8 +48,6 @@ #include "arch/amd64-linux-tdesc.h" #include "inferior.h" #include "x86-tdep.h" -#include "dwarf2/frame.h" -#include "frame-unwind.h" #include "cli/cli-style.h" /* The syscall's XML filename for i386. */ @@ -1921,18 +1919,6 @@ amd64_linux_get_tls_dtv_addr (struct gdbarch *gdbarch, ptid_t ptid, return dtv_addr; } -/* Return the number of bytes required to update the shadow stack pointer - by one element. For x32 the shadow stack elements are still 64-bit - aligned. Thus, gdbarch_addr_bit cannot be used to compute the new - stack pointer. */ - -static inline int -amd64_linux_shadow_stack_element_size_aligned (gdbarch *gdbarch) -{ - const bfd_arch_info *binfo = gdbarch_bfd_arch_info (gdbarch); - return (binfo->bits_per_word / binfo->bits_per_byte); -} - /* Read the shadow stack pointer register and return its value, if possible. */ @@ -1964,117 +1950,14 @@ amd64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache, return ssp; } -/* If shadow stack is enabled, push the address NEW_ADDR to the shadow - stack and increment the shadow stack pointer accordingly. */ +/* Return true if ADDR points to the top of an empty shadow stack, defined by + RANGE [start_address, end_address). */ -static void -amd64_linux_shadow_stack_push (gdbarch *gdbarch, CORE_ADDR new_addr, - regcache *regcache) -{ - bool shadow_stack_enabled; - std::optional ssp - = 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. */ - const int element_size - = amd64_linux_shadow_stack_element_size_aligned (gdbarch); - const CORE_ADDR new_ssp = *ssp - element_size; - - /* Using /proc/PID/smaps we can only check if NEW_SSP points to shadow - stack memory. If it doesn't, we assume the stack is full. */ - std::pair memrange; - if (!linux_address_in_shadow_stack_mem_range (new_ssp, &memrange)) - error (_("No space left on the shadow stack.")); - - /* On x86 there can be a shadow stack token at bit 63. For x32, the - address size is only 32 bit. Always write back the full 8 bytes to - include the shadow stack token. */ - const bfd_endian byte_order = gdbarch_byte_order (gdbarch); - write_memory_unsigned_integer (new_ssp, element_size, byte_order, - (ULONGEST) new_addr); - - i386_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); - gdb_assert (tdep->ssp_regnum > -1); - - regcache_raw_write_unsigned (regcache, tdep->ssp_regnum, new_ssp); -} - -/* Implement shadow stack pointer unwinding. For each new shadow stack - pointer check if its address is still in the shadow stack memory range. - If it's outside the range set the returned value to unavailable, - otherwise return a value containing the new shadow stack pointer. */ - -static value * -amd64_linux_dwarf2_prev_ssp (const frame_info_ptr &this_frame, - void **this_cache, int regnum) -{ - value *v = frame_unwind_got_register (this_frame, regnum, regnum); - gdb_assert (v != nullptr); - - gdbarch *gdbarch = get_frame_arch (this_frame); - - if (v->entirely_available () && !v->optimized_out ()) - { - int size = register_size (gdbarch, regnum); - bfd_endian byte_order = gdbarch_byte_order (gdbarch); - CORE_ADDR ssp = extract_unsigned_integer (v->contents_all ().data (), - size, byte_order); - - /* Using /proc/PID/smaps we can only check if the current shadow - stack pointer SSP points to shadow stack memory. Only if this is - the case a valid previous shadow stack pointer can be - calculated. */ - std::pair range; - if (linux_address_in_shadow_stack_mem_range (ssp, &range)) - { - /* The shadow stack grows downwards. To compute the previous - shadow stack pointer, we need to increment SSP. */ - CORE_ADDR new_ssp - = ssp + amd64_linux_shadow_stack_element_size_aligned (gdbarch); - - /* There can be scenarios where we have a shadow stack pointer - but the shadow stack is empty, as no call instruction has - been executed yet. If NEW_SSP points to the end of or before - (<=) the current shadow stack memory range we consider - NEW_SSP as valid (but empty). */ - if (new_ssp <= range.second) - return frame_unwind_got_address (this_frame, regnum, new_ssp); - } - } - - /* Return a value which is marked as unavailable in case we could not - calculate a valid previous shadow stack pointer. */ - value *retval - = value::allocate_register (get_next_frame_sentinel_okay (this_frame), - regnum, register_type (gdbarch, regnum)); - retval->mark_bytes_unavailable (0, retval->type ()->length ()); - return retval; -} - -/* Implement the "init_reg" dwarf2_frame_ops method. */ - -static void -amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg, - const frame_info_ptr &this_frame) +static bool +amd64_linux_top_addr_empty_shadow_stack + (const CORE_ADDR addr, const std::pair range) { - if (regnum == gdbarch_pc_regnum (gdbarch)) - reg->how = DWARF2_FRAME_REG_RA; - else if (regnum == gdbarch_sp_regnum (gdbarch)) - reg->how = DWARF2_FRAME_REG_CFA; - else if (regnum == AMD64_PL3_SSP_REGNUM) - { - reg->how = DWARF2_FRAME_REG_FN; - reg->loc.fn = amd64_linux_dwarf2_prev_ssp; - } + return addr == range.second; } static void @@ -2135,10 +2018,11 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_remove_non_address_bits_watchpoint (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); + + set_gdbarch_top_addr_empty_shadow_stack + (gdbarch, amd64_linux_top_addr_empty_shadow_stack); } static void diff --git a/gdb/amd64-tdep.c b/gdb/amd64-tdep.c index a982e610642..0972aaf93d5 100644 --- a/gdb/amd64-tdep.c +++ b/gdb/amd64-tdep.c @@ -51,6 +51,8 @@ #include "x86-tdep.h" #include "amd64-ravenscar-thread.h" #include "gdbsupport/selftest.h" +#include "shadow-stack.h" +#include "dwarf2/frame.h" /* Note that the AMD64 architecture was previously known as x86-64. The latter is (forever) engraved into the canonical system name as @@ -3488,6 +3490,21 @@ amd64_in_indirect_branch_thunk (struct gdbarch *gdbarch, CORE_ADDR pc) AMD64_RIP_REGNUM); } +static void +amd64_init_reg (gdbarch *gdbarch, int regnum, dwarf2_frame_state_reg *reg, + const frame_info_ptr &this_frame) +{ + if (regnum == gdbarch_pc_regnum (gdbarch)) + reg->how = DWARF2_FRAME_REG_RA; + else if (regnum == gdbarch_sp_regnum (gdbarch)) + reg->how = DWARF2_FRAME_REG_CFA; + else if (regnum == gdbarch_ssp_regnum (gdbarch)) + { + reg->how = DWARF2_FRAME_REG_FN; + reg->loc.fn = dwarf2_prev_ssp; + } +} + void amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch, const target_desc *default_tdesc) @@ -3647,6 +3664,11 @@ amd64_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_in_indirect_branch_thunk (gdbarch, amd64_in_indirect_branch_thunk); + if (tdep->ssp_regnum != -1) + set_gdbarch_ssp_regnum (gdbarch, tdep->ssp_regnum); + + dwarf2_frame_set_init_reg (gdbarch, amd64_init_reg); + register_amd64_ravenscar_ops (gdbarch); } diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index f424fa2a86e..022ad496026 100644 --- a/gdb/gdbarch-gen.c +++ b/gdb/gdbarch-gen.c @@ -85,6 +85,7 @@ struct gdbarch int pc_regnum = -1; int ps_regnum = -1; int fp0_regnum = -1; + int ssp_regnum = -1; gdbarch_dwarf2_reg_to_regnum_ftype *dwarf2_reg_to_regnum = no_op_reg_to_regnum; gdbarch_register_name_ftype *register_name = nullptr; gdbarch_register_type_ftype *register_type = nullptr; @@ -251,8 +252,10 @@ struct gdbarch gdbarch_read_core_file_mappings_ftype *read_core_file_mappings = default_read_core_file_mappings; gdbarch_use_target_description_from_corefile_notes_ftype *use_target_description_from_corefile_notes = default_use_target_description_from_corefile_notes; gdbarch_core_parse_exec_context_ftype *core_parse_exec_context = default_core_parse_exec_context; - gdbarch_shadow_stack_push_ftype *shadow_stack_push = nullptr; gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer = default_get_shadow_stack_pointer; + gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range = nullptr; + gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr; + int shadow_stack_element_size_aligned = 8; }; /* Create a new ``struct gdbarch'' based on information provided by @@ -334,6 +337,7 @@ verify_gdbarch (struct gdbarch *gdbarch) /* Skip verify of pc_regnum, invalid_p == 0. */ /* Skip verify of ps_regnum, invalid_p == 0. */ /* Skip verify of fp0_regnum, invalid_p == 0. */ + /* Skip verify of ssp_regnum, invalid_p == 0. */ /* Skip verify of dwarf2_reg_to_regnum, invalid_p == 0. */ if (gdbarch->register_name == nullptr) log.puts ("\n\tregister_name"); @@ -511,8 +515,10 @@ verify_gdbarch (struct gdbarch *gdbarch) /* Skip verify of read_core_file_mappings, invalid_p == 0. */ /* Skip verify of use_target_description_from_corefile_notes, invalid_p == 0. */ /* Skip verify of core_parse_exec_context, invalid_p == 0. */ - /* Skip verify of shadow_stack_push, has predicate. */ /* Skip verify of get_shadow_stack_pointer, invalid_p == 0. */ + /* Skip verify of address_in_shadow_stack_memory_range, has predicate. */ + /* Skip verify of top_addr_empty_shadow_stack, has predicate. */ + /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0. */ if (!log.empty ()) internal_error (_("verify_gdbarch: the following are invalid ...%s"), log.c_str ()); @@ -682,6 +688,9 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) gdb_printf (file, "gdbarch_dump: fp0_regnum = %s\n", plongest (gdbarch->fp0_regnum)); + gdb_printf (file, + "gdbarch_dump: ssp_regnum = %s\n", + plongest (gdbarch->ssp_regnum)); gdb_printf (file, "gdbarch_dump: dwarf2_reg_to_regnum = <%s>\n", host_address_to_string (gdbarch->dwarf2_reg_to_regnum)); @@ -1330,15 +1339,24 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) gdb_printf (file, "gdbarch_dump: core_parse_exec_context = <%s>\n", host_address_to_string (gdbarch->core_parse_exec_context)); - gdb_printf (file, - "gdbarch_dump: gdbarch_shadow_stack_push_p() = %d\n", - gdbarch_shadow_stack_push_p (gdbarch)); - gdb_printf (file, - "gdbarch_dump: shadow_stack_push = <%s>\n", - host_address_to_string (gdbarch->shadow_stack_push)); gdb_printf (file, "gdbarch_dump: get_shadow_stack_pointer = <%s>\n", host_address_to_string (gdbarch->get_shadow_stack_pointer)); + gdb_printf (file, + "gdbarch_dump: gdbarch_address_in_shadow_stack_memory_range_p() = %d\n", + gdbarch_address_in_shadow_stack_memory_range_p (gdbarch)); + gdb_printf (file, + "gdbarch_dump: address_in_shadow_stack_memory_range = <%s>\n", + host_address_to_string (gdbarch->address_in_shadow_stack_memory_range)); + gdb_printf (file, + "gdbarch_dump: gdbarch_top_addr_empty_shadow_stack_p() = %d\n", + gdbarch_top_addr_empty_shadow_stack_p (gdbarch)); + gdb_printf (file, + "gdbarch_dump: top_addr_empty_shadow_stack = <%s>\n", + host_address_to_string (gdbarch->top_addr_empty_shadow_stack)); + gdb_printf (file, + "gdbarch_dump: shadow_stack_element_size_aligned = %s\n", + plongest (gdbarch->shadow_stack_element_size_aligned)); if (gdbarch->dump_tdep != nullptr) gdbarch->dump_tdep (gdbarch, file); } @@ -2077,6 +2095,23 @@ set_gdbarch_fp0_regnum (struct gdbarch *gdbarch, gdbarch->fp0_regnum = fp0_regnum; } +int +gdbarch_ssp_regnum (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != nullptr); + /* Skip verify of ssp_regnum, invalid_p == 0. */ + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_ssp_regnum called\n"); + return gdbarch->ssp_regnum; +} + +void +set_gdbarch_ssp_regnum (struct gdbarch *gdbarch, + int ssp_regnum) +{ + gdbarch->ssp_regnum = ssp_regnum; +} + int gdbarch_dwarf2_reg_to_regnum (struct gdbarch *gdbarch, int dwarf2_regnr) { @@ -5246,43 +5281,84 @@ set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch->core_parse_exec_context = core_parse_exec_context; } +std::optional +gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled) +{ + gdb_assert (gdbarch != nullptr); + gdb_assert (gdbarch->get_shadow_stack_pointer != nullptr); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n"); + return gdbarch->get_shadow_stack_pointer (gdbarch, regcache, shadow_stack_enabled); +} + +void +set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, + gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer) +{ + gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer; +} + bool -gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch) +gdbarch_address_in_shadow_stack_memory_range_p (struct gdbarch *gdbarch) { gdb_assert (gdbarch != nullptr); - return gdbarch->shadow_stack_push != nullptr; + return gdbarch->address_in_shadow_stack_memory_range != nullptr; +} + +bool +gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, CORE_ADDR addr, std::pair *range) +{ + gdb_assert (gdbarch != nullptr); + gdb_assert (gdbarch->address_in_shadow_stack_memory_range != nullptr); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_address_in_shadow_stack_memory_range called\n"); + return gdbarch->address_in_shadow_stack_memory_range (addr, range); } void -gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache) +set_gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, + gdbarch_address_in_shadow_stack_memory_range_ftype address_in_shadow_stack_memory_range) +{ + gdbarch->address_in_shadow_stack_memory_range = address_in_shadow_stack_memory_range; +} + +bool +gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch *gdbarch) { gdb_assert (gdbarch != nullptr); - gdb_assert (gdbarch->shadow_stack_push != nullptr); + return gdbarch->top_addr_empty_shadow_stack != nullptr; +} + +bool +gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, const CORE_ADDR addr, const std::pair range) +{ + gdb_assert (gdbarch != nullptr); + gdb_assert (gdbarch->top_addr_empty_shadow_stack != nullptr); if (gdbarch_debug >= 2) - gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_push called\n"); - gdbarch->shadow_stack_push (gdbarch, new_addr, regcache); + gdb_printf (gdb_stdlog, "gdbarch_top_addr_empty_shadow_stack called\n"); + return gdbarch->top_addr_empty_shadow_stack (addr, range); } void -set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, - gdbarch_shadow_stack_push_ftype shadow_stack_push) +set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, + gdbarch_top_addr_empty_shadow_stack_ftype top_addr_empty_shadow_stack) { - gdbarch->shadow_stack_push = shadow_stack_push; + gdbarch->top_addr_empty_shadow_stack = top_addr_empty_shadow_stack; } -std::optional -gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled) +int +gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch) { gdb_assert (gdbarch != nullptr); - gdb_assert (gdbarch->get_shadow_stack_pointer != nullptr); + /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0. */ if (gdbarch_debug >= 2) - gdb_printf (gdb_stdlog, "gdbarch_get_shadow_stack_pointer called\n"); - return gdbarch->get_shadow_stack_pointer (gdbarch, regcache, shadow_stack_enabled); + gdb_printf (gdb_stdlog, "gdbarch_shadow_stack_element_size_aligned called\n"); + return gdbarch->shadow_stack_element_size_aligned; } void -set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, - gdbarch_get_shadow_stack_pointer_ftype get_shadow_stack_pointer) +set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, + int shadow_stack_element_size_aligned) { - gdbarch->get_shadow_stack_pointer = get_shadow_stack_pointer; + gdbarch->shadow_stack_element_size_aligned = shadow_stack_element_size_aligned; } diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index 678b308fba5..c05564fe05f 100644 --- a/gdb/gdbarch-gen.h +++ b/gdb/gdbarch-gen.h @@ -286,6 +286,12 @@ void set_gdbarch_ps_regnum (struct gdbarch *gdbarch, int ps_regnum); int gdbarch_fp0_regnum (struct gdbarch *gdbarch); void set_gdbarch_fp0_regnum (struct gdbarch *gdbarch, int fp0_regnum); +/* Register number for the shadow stack pointer. For inferior calls, the + gdbarch value ssp_regnum has to be provided. */ + +int gdbarch_ssp_regnum (struct gdbarch *gdbarch); +void set_gdbarch_ssp_regnum (struct gdbarch *gdbarch, int ssp_regnum); + /* Provide a default mapping from a DWARF2 register number to a gdb REGNUM. Return -1 for bad REGNUM. Note: Several targets get this wrong. */ @@ -1728,22 +1734,23 @@ void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_ /* Some targets support special hardware-assisted control-flow protection technologies. For example, the Intel Control-Flow Enforcement Technology (Intel CET) on x86 provides a shadow stack and indirect branch tracking. - To enable shadow stack support for inferior calls the shadow_stack_push - gdbarch hook has to be provided. The get_shadow_stack_pointer gdbarch - hook has to be provided to enable displaced stepping. - - Push NEW_ADDR to the shadow stack and update the shadow stack pointer. */ - -bool gdbarch_shadow_stack_push_p (struct gdbarch *gdbarch); - -using gdbarch_shadow_stack_push_ftype = void (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache); -void gdbarch_shadow_stack_push (struct gdbarch *gdbarch, CORE_ADDR new_addr, regcache *regcache); -void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stack_push_ftype *shadow_stack_push); - -/* If possible, return the shadow stack pointer. If the shadow stack + For GDB shadow stack support the following methods or values must be + provided: + - get_shadow_stack_pointer: required for displaced stepping and inferior + function calls + - address_in_shadow_stack_memory_range: required for shadow stack pointer + unwinding and inferior function calls + - top_addr_empty_shadow_stack: required for shadow stack pointer unwinding + - ssp_regnum: required for inferior function calls. + + If the shadow stack alignment is not the predefault of 8 bytes, configure + the gdbarch value shadow_stack_element_size_aligned. + + If possible, return the shadow stack pointer. If the shadow stack feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise set SHADOW_STACK_ENABLED to false. This hook has to be provided to enable - displaced stepping for shadow stack enabled programs. + displaced stepping and inferior function calls for shadow stack enabled + programs. On some architectures, the shadow stack pointer is available even if the feature is disabled. So dependent on the target, an implementation of this function may return a valid shadow stack pointer, but set @@ -1752,3 +1759,31 @@ void set_gdbarch_shadow_stack_push (struct gdbarch *gdbarch, gdbarch_shadow_stac using gdbarch_get_shadow_stack_pointer_ftype = std::optional (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled); std::optional gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, regcache *regcache, bool &shadow_stack_enabled); void set_gdbarch_get_shadow_stack_pointer (struct gdbarch *gdbarch, gdbarch_get_shadow_stack_pointer_ftype *get_shadow_stack_pointer); + +/* Returns true if ADDR belongs to a shadow stack memory range. If this is + the case and RANGE is non-null, assign the shadow stack memory range to + RANGE [start_address, end_address). This hook has to be provided for + shadow stack pointer unwinding and inferior function calls. */ + +bool gdbarch_address_in_shadow_stack_memory_range_p (struct gdbarch *gdbarch); + +using gdbarch_address_in_shadow_stack_memory_range_ftype = bool (CORE_ADDR addr, std::pair *range); +bool gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, CORE_ADDR addr, std::pair *range); +void set_gdbarch_address_in_shadow_stack_memory_range (struct gdbarch *gdbarch, gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range); + +/* Return true if ADDR points to the top of an empty shadow stack, defined by + RANGE [start_address, end_address). This hook has to be provided to enable + unwinding of the shadow stack pointer. */ + +bool gdbarch_top_addr_empty_shadow_stack_p (struct gdbarch *gdbarch); + +using gdbarch_top_addr_empty_shadow_stack_ftype = bool (const CORE_ADDR addr, const std::pair range); +bool gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, const CORE_ADDR addr, const std::pair range); +void set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack); + +/* The number of bytes required to update the shadow stack pointer by one + element. In case the alignment is not the predefault (8 bytes), configure + this value. */ + +int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch); +void set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, int shadow_stack_element_size_aligned); diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py index b9304d3036d..531d7e212d8 100644 --- a/gdb/gdbarch_components.py +++ b/gdb/gdbarch_components.py @@ -553,6 +553,17 @@ Value( invalid=False, ) +Value( + comment=""" +Register number for the shadow stack pointer. For inferior calls, the +gdbarch value ssp_regnum has to be provided. +""", + type="int", + name="ssp_regnum", + predefault="-1", + invalid=False, +) + Method( comment=""" Provide a default mapping from a DWARF2 register number to a gdb REGNUM. @@ -2752,24 +2763,23 @@ Method( Some targets support special hardware-assisted control-flow protection technologies. For example, the Intel Control-Flow Enforcement Technology (Intel CET) on x86 provides a shadow stack and indirect branch tracking. -To enable shadow stack support for inferior calls the shadow_stack_push -gdbarch hook has to be provided. The get_shadow_stack_pointer gdbarch -hook has to be provided to enable displaced stepping. +For GDB shadow stack support the following methods or values must be +provided: +- get_shadow_stack_pointer: required for displaced stepping and inferior + function calls +- address_in_shadow_stack_memory_range: required for shadow stack pointer + unwinding and inferior function calls +- top_addr_empty_shadow_stack: required for shadow stack pointer unwinding +- ssp_regnum: required for inferior function calls. + +If the shadow stack alignment is not the predefault of 8 bytes, configure +the gdbarch value shadow_stack_element_size_aligned. -Push NEW_ADDR to the shadow stack and update the shadow stack pointer. -""", - type="void", - name="shadow_stack_push", - params=[("CORE_ADDR", "new_addr"), ("regcache *", "regcache")], - predicate=True, -) - -Method( - comment=""" If possible, return the shadow stack pointer. If the shadow stack feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise set SHADOW_STACK_ENABLED to false. This hook has to be provided to enable -displaced stepping for shadow stack enabled programs. +displaced stepping and inferior function calls for shadow stack enabled +programs. On some architectures, the shadow stack pointer is available even if the feature is disabled. So dependent on the target, an implementation of this function may return a valid shadow stack pointer, but set @@ -2781,3 +2791,49 @@ SHADOW_STACK_ENABLED to false. predefault="default_get_shadow_stack_pointer", invalid=False, ) + +Function( + comment=""" +Returns true if ADDR belongs to a shadow stack memory range. If this is +the case and RANGE is non-null, assign the shadow stack memory range to +RANGE [start_address, end_address). This hook has to be provided for +shadow stack pointer unwinding and inferior function calls. +""", + type="bool", + name="address_in_shadow_stack_memory_range", + params=[("CORE_ADDR", "addr"), ("std::pair *", "range")], + predicate=True, +) + +Function( + comment=""" +Return true if ADDR points to the top of an empty shadow stack, defined by +RANGE [start_address, end_address). This hook has to be provided to enable +unwinding of the shadow stack pointer. +""", + type="bool", + name="top_addr_empty_shadow_stack", + params=[ + ("const CORE_ADDR", "addr"), + ("const std::pair", "range"), + ], + predicate=True, +) + +Value( + comment=""" +The number of bytes required to update the shadow stack pointer by one +element. In case the alignment is not the predefault (8 bytes), configure +this value. +""", + type="int", + name="shadow_stack_element_size_aligned", + predefault="8", + invalid=False, + # Currently unused but we wanted to keep this hook around, since + # x86 32-bit shadow stacks would require a 4-byte offset, for + # instance. But this is not supported yet by the Linux kernel + # as x86 shadow stack for userspace is only supported for amd64 + # linux starting with Linux kernel v6.6. + unused=True, +) diff --git a/gdb/infcall.c b/gdb/infcall.c index e6b24ff5310..c992b537efe 100644 --- a/gdb/infcall.c +++ b/gdb/infcall.c @@ -43,6 +43,7 @@ #include "thread-fsm.h" #include #include "gdbsupport/scope-exit.h" +#include "shadow-stack.h" #include #include "cli/cli-style.h" @@ -1485,8 +1486,7 @@ call_function_by_hand_dummy (struct value *function, /* Push the return address of the inferior (bp_addr) to the shadow stack and update the shadow stack pointer. As we don't execute a call instruction to call the function we need to handle this manually. */ - if (gdbarch_shadow_stack_push_p (gdbarch)) - gdbarch_shadow_stack_push (gdbarch, bp_addr, regcache); + shadow_stack_push (regcache, bp_addr); /* Set up a frame ID for the dummy frame so we can pass it to set_momentary_breakpoint. We need to give the breakpoint a frame diff --git a/gdb/linux-tdep.c b/gdb/linux-tdep.c index a7381677498..beffda90fdc 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -3149,8 +3149,11 @@ linux_address_in_shadow_stack_mem_range if (it != smaps.end ()) { - range->first = it->start_address; - range->second = it->end_address; + if (range != nullptr) + { + range->first = it->start_address; + range->second = it->end_address; + } return true; } @@ -3200,6 +3203,8 @@ linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_get_siginfo_type (gdbarch, linux_get_siginfo_type); set_gdbarch_core_parse_exec_context (gdbarch, linux_corefile_parse_exec_context); + set_gdbarch_address_in_shadow_stack_memory_range + (gdbarch, linux_address_in_shadow_stack_mem_range); } INIT_GDB_FILE (linux_tdep) diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c new file mode 100644 index 00000000000..160d30e0bfb --- /dev/null +++ b/gdb/shadow-stack.c @@ -0,0 +1,164 @@ +/* Manage a shadow stack pointer for GDB, the GNU debugger. + + Copyright (C) 2024-2026 Free Software Foundation, Inc. + This file is part of GDB. + + 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 . */ + +#include "arch-utils.h" +#include "gdbcore.h" +#include "extract-store-integer.h" +#include "frame.h" +#include "frame-unwind.h" +#include "shadow-stack.h" + +enum class ssp_update_direction +{ + /* Update ssp towards the oldest (outermost) element of the shadow + stack. */ + outer = 0, + + /* Update ssp towards the most recent (innermost) element of the + shadow stack. */ + inner, +}; + +/* Return a new shadow stack pointer which is incremented or decremented + by one element dependent on DIRECTION. */ + +static CORE_ADDR +update_shadow_stack_pointer (gdbarch *gdbarch, CORE_ADDR ssp, + const ssp_update_direction direction) +{ + bool increment = gdbarch_stack_grows_down (gdbarch) + ? direction == ssp_update_direction::outer + : direction == ssp_update_direction::inner; + + if (increment) + return ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch); + else + return ssp - gdbarch_shadow_stack_element_size_aligned (gdbarch); +} + +/* See shadow-stack.h. */ + +void +shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr) +{ + gdbarch *gdbarch = regcache->arch (); + if (!gdbarch_address_in_shadow_stack_memory_range_p (gdbarch) + || gdbarch_ssp_regnum (gdbarch) == -1) + return; + + bool shadow_stack_enabled; + std::optional ssp + = gdbarch_get_shadow_stack_pointer (gdbarch, regcache, + shadow_stack_enabled); + if (!ssp.has_value () || !shadow_stack_enabled) + return; + + const CORE_ADDR new_ssp + = update_shadow_stack_pointer (gdbarch, *ssp, + ssp_update_direction::inner); + + /* If NEW_SSP does not point to shadow stack memory, we assume the + stack is full. */ + if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, + new_ssp, + nullptr)) + error (_("No space left on the shadow stack.")); + + /* On x86 there can be a shadow stack token at bit 63. For x32, the + address size is only 32 bit. Always write back the full element + size to include the shadow stack token. */ + const int element_size + = gdbarch_shadow_stack_element_size_aligned (gdbarch); + + const bfd_endian byte_order = gdbarch_byte_order (gdbarch); + + write_memory_unsigned_integer (new_ssp, element_size, byte_order, + (unsigned long) new_addr); + + regcache_raw_write_unsigned (regcache, + gdbarch_ssp_regnum (gdbarch), + new_ssp); +} + +/* See shadow-stack.h. */ + +value * +dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache, + int regnum) +{ + value *v = frame_unwind_got_register (this_frame, regnum, regnum); + gdb_assert (v != nullptr); + + gdbarch *gdbarch = get_frame_arch (this_frame); + + if (gdbarch_address_in_shadow_stack_memory_range_p (gdbarch) + && v->entirely_available () && !v->optimized_out ()) + { + const int size = register_size (gdbarch, regnum); + bfd_endian byte_order = gdbarch_byte_order (gdbarch); + CORE_ADDR ssp = extract_unsigned_integer + (v->contents_all ().data (), size, byte_order); + + /* Only if the current shadow stack pointer SSP points to shadow + stack memory a valid previous shadow stack pointer can be + calculated. */ + std::pair range; + if (gdbarch_address_in_shadow_stack_memory_range (gdbarch, ssp, &range)) + { + /* 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 unwinder without support for shadow stack + switches for now. */ + const CORE_ADDR new_ssp + = update_shadow_stack_pointer (gdbarch, ssp, + ssp_update_direction::outer); + + /* On x86, if NEW_SSP points to the end outside of RANGE + (NEW_SSP == RANGE.SECOND), it indicates that NEW_SSP is + valid, but the shadow stack is empty. In contrast, for + ARM's Guarded Control Stack, if NEW_SSP points to the end + of RANGE, it means that the shadow stack feature is + disabled. */ + bool is_top_addr_empty_shadow_stack + = gdbarch_top_addr_empty_shadow_stack_p (gdbarch) + && gdbarch_top_addr_empty_shadow_stack (gdbarch, new_ssp, range); + + /* Validate NEW_SSP. This may depend on both + IS_TOP_ADDR_EMPTY_SHADOW_STACK and the gdbarch hook (e.g., x86), + or on the hook only (e.g., ARM). */ + if (is_top_addr_empty_shadow_stack + || gdbarch_address_in_shadow_stack_memory_range (gdbarch, + new_ssp, + &range)) + return frame_unwind_got_address (this_frame, regnum, new_ssp); + } + } + + /* Return a value which is marked as unavailable, in case we could not + calculate a valid previous shadow stack pointer. */ + value *retval + = value::allocate_register (get_next_frame_sentinel_okay (this_frame), + regnum, register_type (gdbarch, regnum)); + retval->mark_bytes_unavailable (0, retval->type ()->length ()); + return retval; +} diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h new file mode 100644 index 00000000000..5f540ff7d62 --- /dev/null +++ b/gdb/shadow-stack.h @@ -0,0 +1,42 @@ +/* Definitions to manage a shadow stack pointer for GDB, the GNU debugger. + + Copyright (C) 2024-2026 Free Software Foundation, Inc. + + This file is part of GDB. + + 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 . */ + +#ifndef GDB_SHADOW_STACK_H +#define GDB_SHADOW_STACK_H + +class regcache; +class frame_info_ptr; +struct value; + +/* If shadow stack is enabled, push the address NEW_ADDR on the shadow + stack and update the shadow stack pointer accordingly. */ + +void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr); + +/* Unwind the previous shadow stack pointer of THIS_FRAME's shadow stack + pointer. REGNUM is the register number of the shadow stack pointer. + Return a value that is unavailable in case we cannot unwind the + previous shadow stack pointer. Otherwise, return a value containing + the previous shadow stack pointer. */ + +value *dwarf2_prev_ssp (const frame_info_ptr &this_frame, + void **this_cache, int regnum); + +#endif /* GDB_SHADOW_STACK_H */ From patchwork Wed Jul 8 14:36:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138748 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 1D3654BA2E2F for ; Wed, 8 Jul 2026 14:39:16 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 1D3654BA2E2F 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=b+pXduPT X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.15]) by sourceware.org (Postfix) with ESMTPS id A40004BA2E1F for ; Wed, 8 Jul 2026 14:37:21 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A40004BA2E1F 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 A40004BA2E1F Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.15 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521441; cv=none; b=HBItZgv2edVsI27tzhS2yAuDCW11QALXzLVLBavxp1g+auBSJKhrbV1qHzABG3x7ulFVxZLjQG+GBMLymqcTB3WJZFw6mire12aYCA3DuStPyQQwBXHuNTPRga7VCO4blHf7i3F76VwYjKYXnHA2Sim3IGwydvpuZAMRBDpQPu0= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521441; c=relaxed/simple; bh=LT9UuvVVDkzg7XvoBEkkdrZ3Uj4M5nTCxLJ7N4i23aI=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=ZQs5r2NPq6RrN7A8ulHoKzXqTJIAU180uYXhjECJ3b185hCa8ZMt40etmmy2YUhGrHcH9w4oM33pbEvusnOqER6NIa/xSt+Y74xSJ1hMkKMsk9zlKdXzeFiLVWYSxfMPMsxGB9weSLGD8SSca2YHwzA4elZ+dJBPhdT4sYEXmnw= 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=b+pXduPT DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A40004BA2E1F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521442; x=1815057442; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=LT9UuvVVDkzg7XvoBEkkdrZ3Uj4M5nTCxLJ7N4i23aI=; b=b+pXduPTCLRFR7B0RZzVRSCKTblUc95mwqJ7yy3V9IFueFJA1Vnng/Gw rGok9s6FunhBnROhLahpIbCRVEwcY07V7E9PuoIcCjkuBY1uVlhGnLy3V eM7YZAGAxDWPkiBslMf/Vd7jdorkPdhAC8EwShqIirYlhI5KAuXEbyXSn w4/pAdAGAfaDAAkUI3Bfby79CePPKWxxm2caU2L6bjXi+P/sniB192klD vmrjHwFBdvsKs7S/uVN0teUa4KYpbJJr1kYLpd3tt8xJBbwZ4sCSJsTro J+d22xtuFFWM+bwuAFIkdH6ZlUi20cvnRJrRkz/zURodRCeYrlaCbgsPn Q==; X-CSE-ConnectionGUID: rlRJmkdQQzuvAFFhKMUtDg== X-CSE-MsgGUID: PjHyuzPYSmCyU2j+2d9jAA== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="87867527" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="87867527" Received: from orviesa006.jf.intel.com ([10.64.159.146]) by orvoesa107.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:21 -0700 X-CSE-ConnectionGUID: I1sVyX7rSbSUMzlVWdWKOg== X-CSE-MsgGUID: MMxf/DJaQLeQZvfcGsZF/Q== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="252560330" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by orviesa006-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:20 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 02/13] aarch64: Implement gdbarch function top_addr_empty_shadow_stack. Date: Wed, 8 Jul 2026 14:36:28 +0000 Message-Id: <20260708143639.2214689-3-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org With the implementation of that gdbarch function we can remove the arch specific function aarch64_linux_dwarf2_prev_gcspr and use the generalized function dwarf2_prev_ssp instead. Co-Authored-By: Christina Schimpe --- gdb/aarch64-linux-tdep.c | 51 ++-------------------------------------- gdb/aarch64-tdep.c | 28 ++++++++++++++++++++-- 2 files changed, 28 insertions(+), 51 deletions(-) diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index f11eccc1bc1..b5959057972 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -66,6 +66,7 @@ #include "elf/common.h" #include "elf/aarch64.h" #include "arch/aarch64-insn.h" +#include "shadow-stack.h" /* For std::pow */ #include @@ -2617,54 +2618,6 @@ aarch64_linux_get_shadow_stack_pointer (gdbarch *gdbarch, regcache *regcache, return gcspr; } -/* Implement Guarded Control Stack Pointer Register unwinding. For each - previous GCS pointer check if its address is still in the GCS memory - range. If it's outside the range set the returned value to unavailable, - otherwise return a value containing the new GCS pointer. */ - -static value * -aarch64_linux_dwarf2_prev_gcspr (const frame_info_ptr &this_frame, - void **this_cache, int regnum) -{ - value *v = frame_unwind_got_register (this_frame, regnum, regnum); - gdb_assert (v != nullptr); - - gdbarch *gdbarch = get_frame_arch (this_frame); - - if (v->entirely_available () && !v->optimized_out ()) - { - int size = register_size (gdbarch, regnum); - bfd_endian byte_order = gdbarch_byte_order (gdbarch); - CORE_ADDR gcspr = extract_unsigned_integer (v->contents_all ().data (), - size, byte_order); - - /* Starting with v6.13, the Linux kernel supports Guarded Control - Stack. Using /proc/PID/smaps we can only check if the current - GCSPR points to GCS memory. Only if this is the case a valid - previous GCS pointer can be calculated. */ - std::pair range; - if (linux_address_in_shadow_stack_mem_range (gcspr, &range)) - { - /* The GCS grows downwards. To compute the previous GCS pointer, - we need to increment the GCSPR. */ - CORE_ADDR new_gcspr = gcspr + 8; - - /* If NEW_GCSPR still points within the current GCS memory range - we consider it to be valid. */ - if (new_gcspr < range.second) - return frame_unwind_got_address (this_frame, regnum, new_gcspr); - } - } - - /* Return a value which is marked as unavailable in case we could not - calculate a valid previous GCS pointer. */ - value *retval - = value::allocate_register (get_next_frame_sentinel_okay (this_frame), - regnum, register_type (gdbarch, regnum)); - retval->mark_bytes_unavailable (0, retval->type ()->length ()); - return retval; -} - /* AArch64 Linux implementation of the report_signal_info gdbarch hook. Displays information about possible memory tag violations. */ @@ -3243,7 +3196,7 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) { set_gdbarch_get_shadow_stack_pointer (gdbarch, aarch64_linux_get_shadow_stack_pointer); - tdep->fn_prev_gcspr = aarch64_linux_dwarf2_prev_gcspr; + tdep->fn_prev_gcspr = dwarf2_prev_ssp; } } diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index 848cee3043c..d789b8569d2 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -1908,6 +1908,26 @@ aarch64_pop_gcs_entry (regcache *regs) regcache_cooked_write_unsigned (regs, tdep->gcs_reg_base, gcs_addr + 8); } +/* Implement the "top_addr_empty_shadow_stack" gdbarch method. */ + +static bool +aarch64_top_addr_empty_shadow_stack + (const CORE_ADDR addr, + const std::pair range) +{ + gdb_assert (addr >= range.first); + + /* For AArch64, addr must be strictly less than the upper address in the + range, but other architectures allow it to be equal to the upper + address when the stack is empty so GDB core works with those addresses + and can send them our way. */ + gdb_assert (addr <= range.second); + + /* The GCS grows down, and the oldest entry isn't an address. + Just the value '0'. */ + return addr >= range.second - 8; +} + /* Implement the "push_dummy_call" gdbarch method. */ static CORE_ADDR @@ -4783,9 +4803,13 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) /* Register a hook for converting a memory tag to a string. */ set_gdbarch_memtag_to_string (gdbarch, aarch64_memtag_to_string); - /* AArch64's shadow stack pointer is the GCSPR. */ if (tdep->has_gcs ()) - set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base); + { + /* AArch64's shadow stack pointer is the GCSPR. */ + set_gdbarch_ssp_regnum (gdbarch, tdep->gcs_reg_base); + set_gdbarch_top_addr_empty_shadow_stack + (gdbarch, aarch64_top_addr_empty_shadow_stack); + } /* ABI */ set_gdbarch_short_bit (gdbarch, 16); From patchwork Wed Jul 8 14:36:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138744 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 317AB4BA2E18 for ; Wed, 8 Jul 2026 14:38:16 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 317AB4BA2E18 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=fKvzleWU X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.15]) by sourceware.org (Postfix) with ESMTPS id 3175C4BA2E11 for ; Wed, 8 Jul 2026 14:37:27 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 3175C4BA2E11 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 3175C4BA2E11 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.15 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521447; cv=none; b=K4HeFMSwox4BNShAApeVr2lAUESXLzsKVycbj1TAZEpFo780qfzt7PeLfwRPpwgQv0mEhKjjOxkJg3h7FbKzfooP1cv81yIiSRAMZYzduC5pn5J3slDBwZ3T6DKYOjfsMu9G7s8faiwHBI+UPo8g+Q/4y1l8I+xUPvE7V/2PwrE= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521447; c=relaxed/simple; bh=KkKmmNvzVCoX9eTVr5e/fXybJv4UqwvIsG8fJjhEjK8=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=WsN/xDhyzShX8jp7YqVpmGog+8xAFsuMFLqShDZnOo6SRQHZwg9ulb/RQD/gL6syCwlvt0VBz/RS9j/KBe/m4lElcBZJk2lm9mChqn5KUGei9ma0FL7x766D3Btmw9HJBWrI46zT4mfEVk2szhK74Kvglr0dcitzMqtWd4458/4= 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=fKvzleWU DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3175C4BA2E11 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521447; x=1815057447; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=KkKmmNvzVCoX9eTVr5e/fXybJv4UqwvIsG8fJjhEjK8=; b=fKvzleWUrFJRvfWka+uAYtq9cbvNYGQ5sFQwSw6Envi814SVgPwVR7N2 aq+NEZgzHgnKnyfvwmjgarwy9YrG4+zi8q0fINjxmnoL+7SSUmqofZnSr M1uF9f19jhNrVwh9XivtPmsNytHCu5vVOjPuugDfDTRUre1na314JYo0p 363QBHqruxw1er4qO3bfwmk+bm/a0Jg7+WY3wLxKi+siqj3QzZKZH5C/D +zV/ylb956wcnug5hBtzSQ1nsf3N386hJhkoRKinH/8LG1Wk1eWrPxrJm +e0G5DpbenVUgkrUHSeUVB0bznaMcO3p9YlhiQShLCchzVERWjycQIOsg Q==; X-CSE-ConnectionGUID: 1Njslv8zTU+uduBvlAL7mQ== X-CSE-MsgGUID: xuTVP2ZXTQmT5/FeOBUJfQ== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="87867542" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="87867542" Received: from orviesa006.jf.intel.com ([10.64.159.146]) by orvoesa107.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:26 -0700 X-CSE-ConnectionGUID: VQrJsOGnQoye8unBerVFyQ== X-CSE-MsgGUID: xKLMET3HSpKCO8cmrsX5Bw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="252560358" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by orviesa006-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:25 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 03/13] gdb: Add get_main_func_start_pc to refactor frame.c:inside_main_func. Date: Wed, 8 Jul 2026 14:36:29 +0000 Message-Id: <20260708143639.2214689-4-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org Refactor frame.c:inside_main_func to use the new function get_main_func_start_pc, which will be called in a following commmit. Reviewed-by: Thiago Jung Bauermann --- gdb/frame.c | 37 +++++++++++++++++++++++++------------ gdb/frame.h | 6 ++++++ 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/gdb/frame.c b/gdb/frame.c index cefdde5ed1e..a9f2675ce3a 100644 --- a/gdb/frame.c +++ b/gdb/frame.c @@ -2641,27 +2641,26 @@ frame_debug_got_null_frame (const frame_info_ptr &this_frame, } } -/* Is this (non-sentinel) frame in the "main"() function? */ +/* See frame.h. */ -static bool -inside_main_func (const frame_info_ptr &this_frame) +std::optional +get_main_func_start_pc (gdbarch* gdbarch, const language lang) { if (current_program_space->symfile_object_file == nullptr) - return false; + return {}; - CORE_ADDR sym_addr = 0; const char *name = main_name (); bound_minimal_symbol msymbol = lookup_minimal_symbol (current_program_space, name, current_program_space->symfile_object_file); + CORE_ADDR sym_addr = 0; if (msymbol.minsym != nullptr) sym_addr = msymbol.value_address (); /* Favor a full symbol in Fortran, for the case where the Fortran main is also called "main". */ - if (msymbol.minsym == nullptr - || get_frame_language (this_frame) == language_fortran) + if (msymbol.minsym == nullptr || lang == language_fortran) { /* In some language (for example Fortran) there will be no minimal symbol with the name of the main function. In this case we should @@ -2677,16 +2676,30 @@ inside_main_func (const frame_info_ptr &this_frame) sym_addr = block->start (); } else if (msymbol.minsym == nullptr) - return false; + return {}; } /* Convert any function descriptor addresses into the actual function code address. */ - sym_addr = (gdbarch_convert_from_func_ptr_addr - (get_frame_arch (this_frame), sym_addr, - current_inferior ()->top_target ())); + return {gdbarch_convert_from_func_ptr_addr + (gdbarch, + sym_addr, + current_inferior ()->top_target ())}; +} + +/* Is this (non-sentinel) frame in the "main"() function? */ + +static bool +inside_main_func (const frame_info_ptr &this_frame) +{ + std::optional sym_addr + = get_main_func_start_pc (get_frame_arch (this_frame), + get_frame_language (this_frame)); + + if (!sym_addr.has_value ()) + return false; - return sym_addr == get_frame_func (this_frame); + return get_frame_func (this_frame) == *sym_addr; } /* Test whether THIS_FRAME is inside the process entry point function. */ diff --git a/gdb/frame.h b/gdb/frame.h index f6553fb7b6d..58732bceab4 100644 --- a/gdb/frame.h +++ b/gdb/frame.h @@ -887,6 +887,12 @@ extern struct symbol *get_frame_function (const frame_info_ptr &); extern CORE_ADDR get_pc_function_start (CORE_ADDR); +/* If possible, get the start pc of the main function of the current program + space for the language LANG. */ + +extern std::optional get_main_func_start_pc (gdbarch *gdbarch, + const language lang); + extern frame_info_ptr find_relative_frame (frame_info_ptr, int *); /* Wrapper over print_stack_frame modifying current_uiout with UIOUT for From patchwork Wed Jul 8 14:36:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138742 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 74CB14BA2E0B for ; Wed, 8 Jul 2026 14:38:02 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 74CB14BA2E0B 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=Ijr82ssp X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.15]) by sourceware.org (Postfix) with ESMTPS id 9CC474BA2E18 for ; Wed, 8 Jul 2026 14:37:31 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 9CC474BA2E18 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 9CC474BA2E18 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.15 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521451; cv=none; b=AfcG+QbB8qLz52fPB4Vu2jew8w54YtIk4FV90JykajxxzBasRvjYR0iIKWkEE+xuy31AJKO+OE/LkHxHCyhJLITGKuskHkWvqeW5NvguPRJKy97bBc51EXMEbFgRKK5+730izPtzUFN/Pvay2qPOaJQe8kvBLnWQoxxHcv/ML5A= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521451; c=relaxed/simple; bh=EpXHKr+UUXS7SLJj8jtrbMz2EW8ab9vVoe/4yHjt+jQ=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=EvkWz8ATvoXF5HJZBf2NluFkKBtp8YAB+mK32FM8TYWUKGIY+jXnls9XZ1borpKsn5RabaKL83yQF0AC/UDmEog8TRIpXVf59mWafSwhSbNin5ESSnsUevbAjSf265OH8uptUoI/AietZVHSUzUToLBxAolccZ1i0/z5meS4T3k= 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=Ijr82ssp DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 9CC474BA2E18 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521452; x=1815057452; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=EpXHKr+UUXS7SLJj8jtrbMz2EW8ab9vVoe/4yHjt+jQ=; b=Ijr82ssp7NPeNgGk7+lhRerpIv2hgMGgoK6yp+FMx6orkVDaqcxYyNpu Djv0gUciF5klw+7ceIaLfgPmykUWBpeOtMAy02y/+WC3xfAz2CaAJmlBK UfOP4zXayxFJLElpge7yIh1t/Dgz77hyDHclQUH5nWvoS7FCaN4hUnf23 YSW9VoAlf8EEX+r1vgQBr8xn6KsdkNMFA1+qWwYzMuhaG+kJbRMKIgkew dJbrnm72k8uxqaP5lG7vuWk9plxUpFcFwD9Bof2wh7AbjI1cooVkZuPyl H8sYni2+iZf2vHw5yvtYFcl36NFxIW4qlhUK8e/TqWpzUvhCkGJr125xw A==; X-CSE-ConnectionGUID: mMQNvDRoRfaj1gYGAoCwBQ== X-CSE-MsgGUID: iknAEjpuRZWBQkKvSzTndg== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="87867558" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="87867558" Received: from orviesa006.jf.intel.com ([10.64.159.146]) by orvoesa107.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:31 -0700 X-CSE-ConnectionGUID: zbHs0xWGSmG0UpoPDBWeHQ== X-CSE-MsgGUID: mgLVHDMATPyuN9qNmEB7pw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="252560368" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by orviesa006-auth.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:30 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 04/13] gdb: Refactor 'stack.c:print_frame'. Date: Wed, 8 Jul 2026 14:36:30 +0000 Message-Id: <20260708143639.2214689-5-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org Refactor the function 'stack.c:print_frame' by introducing several small functions. The functions will also be used in a later patch "gdb: Implement 'bt shadow' to print the shadow stack backtrace." Approved-By: Tom Tromey --- gdb/stack.c | 90 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/gdb/stack.c b/gdb/stack.c index e084976eabf..25527618a4d 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -1258,6 +1258,60 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang, return funname; } +/* Print the library LIB to UIOUT for the printing of frame + information. */ + +static void +print_lib (ui_out *uiout, const char *lib) +{ + annotate_frame_where (); + uiout->wrap_hint (2); + uiout->text (" from "); + uiout->field_string ("from", lib, file_name_style.style ()); +} + +/* Print the filenname of SAL to UIOUT for the printing of frame + information. */ + +static void +print_filename (ui_out *uiout, symtab_and_line sal) +{ + annotate_frame_source_begin (); + const char *filename_display; + + filename_display = symtab_to_filename_for_display (sal.symtab); + uiout->wrap_hint (3); + uiout->text (" at "); + annotate_frame_source_file (); + uiout->field_string ("file", filename_display, file_name_style.style ()); + + if (uiout->is_mi_like_p ()) + { + const char *fullname = symtab_to_fullname (sal.symtab); + uiout->field_string ("fullname", fullname); + } + + annotate_frame_source_file_end (); + uiout->text (":"); + annotate_frame_source_line (); + uiout->field_signed ("line", sal.line, line_number_style.style ()); + annotate_frame_source_end (); +} + +/* If available, print FUNNAME to UIOUT for the printing of frame + information. */ + +static void +print_funname (ui_out *uiout, + gdb::unique_xmalloc_ptr const &funname) +{ + annotate_frame_function_name (); + string_file stb; + gdb_puts (funname ? funname.get () : "??", &stb); + uiout->field_stream ("func", stb, function_name_style.style ()); + uiout->wrap_hint (3); +} + static void print_frame (struct ui_out *uiout, const frame_print_options &fp_opts, @@ -1303,12 +1357,8 @@ print_frame (struct ui_out *uiout, annotate_frame_address_end (); uiout->text (" in "); } - annotate_frame_function_name (); + print_funname (uiout, funname); - string_file stb; - gdb_puts (funname ? funname.get () : "??", &stb); - uiout->field_stream ("func", stb, function_name_style.style ()); - uiout->wrap_hint (3); annotate_frame_args (); uiout->text (" ("); @@ -1338,28 +1388,7 @@ print_frame (struct ui_out *uiout, } uiout->text (")"); if (print_what != SHORT_LOCATION && sal.symtab) - { - const char *filename_display; - - filename_display = symtab_to_filename_for_display (sal.symtab); - annotate_frame_source_begin (); - uiout->wrap_hint (3); - uiout->text (" at "); - annotate_frame_source_file (); - uiout->field_string ("file", filename_display, - file_name_style.style ()); - if (uiout->is_mi_like_p ()) - { - const char *fullname = symtab_to_fullname (sal.symtab); - - uiout->field_string ("fullname", fullname); - } - annotate_frame_source_file_end (); - uiout->text (":"); - annotate_frame_source_line (); - uiout->field_signed ("line", sal.line, line_number_style.style ()); - annotate_frame_source_end (); - } + print_filename (uiout, sal); if (print_what != SHORT_LOCATION && pc.has_value () && (funname == NULL || sal.symtab == NULL)) @@ -1369,12 +1398,7 @@ print_frame (struct ui_out *uiout, get_frame_address_in_block (frame)); if (lib) - { - annotate_frame_where (); - uiout->wrap_hint (2); - uiout->text (" from "); - uiout->field_string ("from", lib, file_name_style.style ()); - } + print_lib (uiout, lib); } if (uiout->is_mi_like_p ()) uiout->field_string ("arch", From patchwork Wed Jul 8 14:36:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138743 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 0305B4BA2E16 for ; Wed, 8 Jul 2026 14:38:13 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0305B4BA2E16 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=MUYCLrs9 X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id 6C3AD4BA2E19 for ; Wed, 8 Jul 2026 14:37:37 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 6C3AD4BA2E19 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 6C3AD4BA2E19 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521457; cv=none; b=HsBrp05vuFzDHHkHsBxHV4X3qXnfmxrbRmp4BXt0iKIs24Xr72sl59/siYVIFVyOqbz5FAg2m8F70nBTI67jeqZ5WneNCIWQP00J+7dItvL/pQruZaVkEcwvQAAlqGCvwjSnnsCW7yAe3qJgl3tLgFo/68Q4JwhrnHe1CfG822Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521457; c=relaxed/simple; bh=cURMsw/wc5SzKR8SpmbFEt5v/1n5/2QCLHecW+dZIw0=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=O6jDko9DeiIl8dJ+PsQGcef2anPW3FwH18WppQREkKdKoN7wCkUAKhq41/fAaxUTzxc/tEfNsExKRHD2cmXvaBcQBKfkF1ewH+ReC1hSjJh/+7g5PjCUX61iT9fRrRoeY0RXvMdnxDh1ovE9fhFoROEj5W8bq+tfe3H7wQz7Dto= 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=MUYCLrs9 DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6C3AD4BA2E19 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521458; x=1815057458; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=cURMsw/wc5SzKR8SpmbFEt5v/1n5/2QCLHecW+dZIw0=; b=MUYCLrs9D8BGDQ4mgl/45bhVccA7PADbP9sKOlsVQ1o44sek6Lgvl4Gp 5MngNuMyvtN85ZWHa6X4ww4oQngzRDx1npvE/GisuUbMs8752dcQnnlrN o8yvnyYomChDE0YxAfEVd1UElRT8xK4ko3Aki7TumtBhUTCjnCbrNs2bD n+AXqvG1XuRw2gv74KPKvy4FExKvewihYkzEUeJub6Fs4NS3f7S5DTZA3 zlwpvq7HHPh3jMNEgdTymDhxIpk4r+GVSYhoYYZyqP+ZJekk8ODjcBWhv KvsIWp8KWl1dBBb3RoiNaSN/nTa7T7NIwpN4X9XalPDt+v0akdgvBvv8E w==; X-CSE-ConnectionGUID: Nm008j2+QCO+rkMcPCsKRA== X-CSE-MsgGUID: WpAmM9FoR7OLc/LW+9ll4A== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375417" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375417" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:36 -0700 X-CSE-ConnectionGUID: O/pFUjnqQoOC62R9+vQFmA== X-CSE-MsgGUID: alpuTaflQde8s0GGFleh2g== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864249" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:35 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 05/13] gdb: Introduce 'stack.c:print_pc' function without frame argument. Date: Wed, 8 Jul 2026 14:36:31 +0000 Message-Id: <20260708143639.2214689-6-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 X-Spam-Status: No, score=-10.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org The function will be used in a later patch "gdb: Implement 'bt shadow' to print the shadow stack backtrace.". Approved-By: Tom Tromey --- gdb/stack.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gdb/stack.c b/gdb/stack.c index 25527618a4d..61cd73b8367 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -921,15 +921,14 @@ print_frame_info_to_print_what (const char *print_frame_info) print_frame_info); } -/* Print the PC from FRAME, plus any flags, to UIOUT. */ +/* Print the PC, plus any FLAGS (if non-empty), to UIOUT. */ static void -print_pc (struct ui_out *uiout, struct gdbarch *gdbarch, const frame_info_ptr &frame, +print_pc (ui_out *uiout, gdbarch *gdbarch, const std::string &flags, CORE_ADDR pc) { uiout->field_core_addr ("addr", gdbarch, pc); - std::string flags = gdbarch_get_pc_address_flags (gdbarch, frame, pc); if (!flags.empty ()) { uiout->text (" ["); @@ -938,6 +937,16 @@ print_pc (struct ui_out *uiout, struct gdbarch *gdbarch, const frame_info_ptr &f } } +/* Print the PC from FRAME, plus any flags, to UIOUT. */ + +static void +print_pc (ui_out *uiout, gdbarch *gdbarch, const frame_info_ptr &frame, + CORE_ADDR pc) +{ + std::string flags = gdbarch_get_pc_address_flags (gdbarch, frame, pc); + print_pc (uiout, gdbarch, flags, pc); +} + /* See stack.h. */ void From patchwork Wed Jul 8 14:36:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138749 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 09C344BA2E39 for ; Wed, 8 Jul 2026 14:39:19 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 09C344BA2E39 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=JrSZr8yj X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id 5C6CD4BA2E2A for ; Wed, 8 Jul 2026 14:37:41 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 5C6CD4BA2E2A 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 5C6CD4BA2E2A Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521461; cv=none; b=i1/sw52jGrXel9ZFHoQGkCfUogNmjXuKXfaYGvcfmUrMN7ZcZANao+7AN+40SQa9iZelL0ccpIXE0sshnUYSutpaF+/YlO1fjS5r3hM1J3IxcimiB/WFeRL4H1BuA1eGQj5p1rTSwB/7qml17Qvw5n4xAcG3BvQmNIRdnRX5Sgo= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521461; c=relaxed/simple; bh=aaCNwxsexwrUA6oYbkMOCmiOXsEytt9CDD34TF9hNrE=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=S6DmWSYEB5JSWV5KVWBOE8a63NnpfoRoHMXZ6StVw6Hs/CbDiKzNu1qv6zy41/MTv+/DecCbK+StT/nNMETc10M0QHMArgDvdcvzG7HbZnJjGDeopGCfOD5WWdTBlLkf+UP+n5vdH0vJC13GggLx0Blf+nvdAEFqweReTWQaIiw= 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=JrSZr8yj DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 5C6CD4BA2E2A DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521462; x=1815057462; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=aaCNwxsexwrUA6oYbkMOCmiOXsEytt9CDD34TF9hNrE=; b=JrSZr8yj2uBYY+pvccm+c4yts4NIebWjLwrdL299iWGgDYJfNJp499VJ P2dd3wb/ey0F5Sg424K0b8TDjZN+mwubPmlkocsEkDYZ1AwQ72G8yyS16 ehsCxI6PHcoSwx0XepxwzbODlFCPAx0nd8kdIwyg1eul20DythX9jBmmi 4a08Ty3MIZ3n6BdOgm+THs2GGtZ1RhQ3DMxUVO4vEs9Gvbe+pDjuDGuj1 3nIYCJsnuccPF99rdMILAQh3z8uKRxQdOrpAcSi/3cK8YfuwhyzcxfMxR zn7gRor0yEeLmUy8Vw44ZrXDcn5c95PWCfJ5Yi9kKQ1dROB+jy3fldKGl A==; X-CSE-ConnectionGUID: Io4kWPPAST+kvijF1pqJJw== X-CSE-MsgGUID: 7gYdoSRLQUiZvVb8kkI5Tg== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375434" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375434" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:41 -0700 X-CSE-ConnectionGUID: VJ4p3OBlSa+ZDpun127UBQ== X-CSE-MsgGUID: 3FPk/srUT+edGr2gbfZ1xg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864289" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:40 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 06/13] gdb: Refactor 'find_symbol_funname' and 'info_frame_command_core' in stack.c. Date: Wed, 8 Jul 2026 14:36:32 +0000 Message-Id: <20260708143639.2214689-7-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 X-Spam-Status: No, score=-10.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org To avoid code duplication, create a new function 'stack.c:find_symbol_funname', which will be used in 'stack.c:find_frame_funname'. The function will also be used in a following commit. Approved-By: Tom Tromey --- gdb/stack.c | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/gdb/stack.c b/gdb/stack.c index 61cd73b8367..f5196c384fd 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -1211,6 +1211,33 @@ get_last_displayed_sal () return sal; } +/* Find the function name for the symbol SYM. */ + +static gdb::unique_xmalloc_ptr +find_symbol_funname (const symbol *sym) +{ + gdb::unique_xmalloc_ptr funname; + const char *print_name = sym->print_name (); + + if (sym->language () == language_cplus) + { + /* It seems appropriate to use print_name () here, + to display the demangled name that we already have + stored in the symbol table, but we stored a version + with DMGL_PARAMS turned on, and here we don't want to + display parameters. So remove the parameters. */ + funname = cp_remove_params (print_name); + } + + if (funname == nullptr) + { + /* If we didn't hit the C++ case above, set *funname here. */ + funname = make_unique_xstrdup (print_name); + } + + return funname; +} + /* Attempt to obtain the name, FUNLANG and optionally FUNCP of the function corresponding to FRAME. */ @@ -1229,25 +1256,10 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang, func = get_frame_function (frame); if (func) { - const char *print_name = func->print_name (); - *funlang = func->language (); if (funcp) *funcp = func; - if (*funlang == language_cplus) - { - /* It seems appropriate to use print_name() here, - to display the demangled name that we already have - stored in the symbol table, but we stored a version - with DMGL_PARAMS turned on, and here we don't want to - display parameters. So remove the parameters. */ - funname = cp_remove_params (print_name); - } - - /* If we didn't hit the C++ case above, set *funname - here. */ - if (funname == NULL) - funname = make_unique_xstrdup (print_name); + funname = find_symbol_funname (func); } else { From patchwork Wed Jul 8 14:36:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138747 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A38BE4BA2E3A for ; Wed, 8 Jul 2026 14:39:15 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A38BE4BA2E3A 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=JpOHatox X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id 67F474BA2E24 for ; Wed, 8 Jul 2026 14:37:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 67F474BA2E24 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 67F474BA2E24 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521466; cv=none; b=xwu713J1B6P1BB6HYx4ieTXbaSJUyWLnQ4YBVEah5mLTwAulONmFo2Bq8g0cPwo6KpL231HXdQ9t8KDHEgKWYu+ApptfkDcO6VMANX9SCDCKMJ8ls6kkl7qNg25Z6tdXAOSHIYSLx7ktiSrGxx1Yxm+Xl/hWoulSrOkq07Ws23o= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521466; c=relaxed/simple; bh=r33CEqC2Cd+nmoL/h3p9nhbx1puvP5fCBs/dbFmDYhw=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=BmJcOrnaVN78zboGiNtmEWfAEnkt3dm1C1RLwvnChgUc/CsE24Nx98lYoHgTtMfBNFcR+HzmrA/hQBU6/Qbhc0pdlDTeLS9W2wfu7rPjx440VRXyg/u52k3wkz0UaiaD6h9JZYU430mXox1IPYna8uZDwoUFoSwN0EZ/rSAKEL0= 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=JpOHatox DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 67F474BA2E24 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521467; x=1815057467; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=r33CEqC2Cd+nmoL/h3p9nhbx1puvP5fCBs/dbFmDYhw=; b=JpOHatox6RaAwtcmE6fVmF66YSnhC6OlVrUPm3L3jFUxasyximNZd40L StezEQlhfCUNGTdvwqsGA3cRxbnQ4LuzlRD/eJHQEvl1BRBqOYTYnQ1MH 6K5bknqtj67v46f9mFplmdhjNrBfOIzhsR8UUDA/mXWABIiSYtzak/IDv R/mlifBamLexOrT7k0zN060uCB4t07K+i8rqafdGQuYmLD9g+w+OAyug8 UfYF3DDxMlGjb6k+GX2088ls4y7r/H3haxmUd5JKkuuIMdHporcgk0QzZ VF9IbMIACZIEi7naOBUQ/46tFY64MiMgsdsCOG2FgOQhUql/jMjnAs88O A==; X-CSE-ConnectionGUID: zIF4RCFWQXSg5W3t1/S+SA== X-CSE-MsgGUID: HpmxY3hfQUS0jODpZ8ZZhg== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375456" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375456" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:46 -0700 X-CSE-ConnectionGUID: yaSaImSkS5eDSfoeFXecrA== X-CSE-MsgGUID: VqRRHDCSRmmhle1h595L1w== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864316" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:45 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 07/13] gdb: Refactor 'stack.c:print_frame_info'. Date: Wed, 8 Jul 2026 14:36:33 +0000 Message-Id: <20260708143639.2214689-8-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 X-Spam-Status: No, score=-10.2 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org This patch restructures the 'stack.c:print_frame_info' function by introducing two new functions in stack.c. The new functions will also be called in a later patch "gdb: Implement 'bt shadow' subcommand to print the shadow stack backtrace." Approved-By: Tom Tromey --- gdb/stack.c | 159 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 65 deletions(-) diff --git a/gdb/stack.c b/gdb/stack.c index f5196c384fd..7df1d6e33da 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -957,6 +957,86 @@ get_user_print_what_frame_info (std::optional *what) (user_frame_print_options.print_frame_info); } +/* Return true if PRINT_WHAT is configured to print the location of a + frame. */ + +static bool +should_print_location (print_what print_what) +{ + return (print_what == LOCATION + || print_what == SRC_AND_LOC + || print_what == LOC_AND_ADDRESS + || print_what == SHORT_LOCATION); +} + +/* Print the source information for PC and SAL to UIOUT. Based on the + user-defined configuration disassemble-next-line, display disassembly + of the next source line, in addition to displaying the source line + itself. Print annotations describing source file and and line number + based on MID_STATEMENT information. If SHOW_ADDRESS is true, print the + program counter PC including, if non-empty, PC_ADDRESS_FLAGS. */ + +static void +print_source (ui_out *uiout, gdbarch *gdbarch, CORE_ADDR pc, + symtab_and_line sal, bool show_address, int mid_statement, + const std::string &pc_address_flags) +{ + if (sal.symtab == nullptr) + { + /* If disassemble-next-line is set to auto or on and doesn't have + the line debug messages for $pc, output the next instruction. */ + if (disassemble_next_line == AUTO_BOOLEAN_AUTO + || disassemble_next_line == AUTO_BOOLEAN_TRUE) + do_gdb_disassembly (gdbarch, 1, pc, pc + 1); + + /* If we do not have symtab information, we cannot print any source + line and must return here. */ + return; + } + + if (annotation_level > 0 + && annotate_source_line (sal.symtab, sal.line, mid_statement, pc)) + { + /* The call to ANNOTATE_SOURCE_LINE already printed the annotation + for this source line, so we avoid the two cases below and do not + print the actual source line. The documentation for annotations + makes it clear that the source line annotation is printed + __instead__ of printing the source line, not as well as. + + However, if we fail to print the source line, which usually means + either the source file is missing, or the requested line is out + of range of the file, then we don't print the source annotation, + and will pass through the "normal" print source line code below, + the expectation is that this code will print an appropriate + error. */ + } + else if (deprecated_print_frame_info_listing_hook) + deprecated_print_frame_info_listing_hook (sal.symtab, sal.line, + sal.line + 1, 0); + else + { + /* We used to do this earlier, but that is clearly wrong. This + function is used by many different parts of gdb, including + normal_stop in infrun.c, which uses this to print out the current + PC when we stepi/nexti into the middle of a source line. Only + the command line really wants this behavior. Other UIs probably + would like the ability to decide for themselves if it is + desired. */ + if (show_address) + { + print_pc (uiout, gdbarch, pc_address_flags, pc); + uiout->text ("\t"); + } + + print_source_lines (sal.symtab, sal.line, sal.line + 1, 0); + } + + /* If disassemble-next-line is set to on and there is line debug + messages, output assembly codes for next line. */ + if (disassemble_next_line == AUTO_BOOLEAN_TRUE) + do_gdb_disassembly (gdbarch, -1, sal.pc, sal.end); +} + /* Print information about frame FRAME. The output is format according to PRINT_LEVEL and PRINT_WHAT and PRINT_ARGS. For the meaning of PRINT_WHAT, see enum print_what comments in frame.h. @@ -973,8 +1053,6 @@ do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts, int set_current_sal) { struct gdbarch *gdbarch = get_frame_arch (frame); - int source_print; - int location_print; if (!current_uiout->is_mi_like_p () && fp_opts.print_frame_info != print_frame_info_auto) @@ -1045,75 +1123,26 @@ do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts, to get the line containing FRAME->pc. */ symtab_and_line sal = find_frame_sal (frame); - location_print = (print_what == LOCATION - || print_what == SRC_AND_LOC - || print_what == LOC_AND_ADDRESS - || print_what == SHORT_LOCATION); - if (location_print || !sal.symtab) - print_frame (uiout, fp_opts, frame, print_level, - print_what, print_args, sal); - - source_print = (print_what == SRC_LINE || print_what == SRC_AND_LOC); + if (should_print_location (print_what) || sal.symtab == nullptr) + print_frame (uiout, fp_opts, frame, print_level, print_what, + print_args, sal); - /* If disassemble-next-line is set to auto or on and doesn't have - the line debug messages for $pc, output the next instruction. */ - if ((disassemble_next_line == AUTO_BOOLEAN_AUTO - || disassemble_next_line == AUTO_BOOLEAN_TRUE) - && source_print && !sal.symtab) - do_gdb_disassembly (get_frame_arch (frame), 1, - get_frame_pc (frame), get_frame_pc (frame) + 1); - - if (source_print && sal.symtab) + if (print_what == SRC_LINE || print_what == SRC_AND_LOC) { int mid_statement = ((print_what == SRC_LINE) && frame_show_address (frame, sal)); - if (annotation_level > 0 - && annotate_source_line (sal.symtab, sal.line, mid_statement, - get_frame_pc (frame))) - { - /* The call to ANNOTATE_SOURCE_LINE already printed the - annotation for this source line, so we avoid the two cases - below and do not print the actual source line. The - documentation for annotations makes it clear that the source - line annotation is printed __instead__ of printing the source - line, not as well as. - - However, if we fail to print the source line, which usually - means either the source file is missing, or the requested - line is out of range of the file, then we don't print the - source annotation, and will pass through the "normal" print - source line code below, the expectation is that this code - will print an appropriate error. */ - } - else if (deprecated_print_frame_info_listing_hook) - deprecated_print_frame_info_listing_hook (sal.symtab, sal.line, - sal.line + 1, 0); - else - { - struct value_print_options opts; - - get_user_print_options (&opts); - /* We used to do this earlier, but that is clearly - wrong. This function is used by many different - parts of gdb, including normal_stop in infrun.c, - which uses this to print out the current PC - when we stepi/nexti into the middle of a source - line. Only the command line really wants this - behavior. Other UIs probably would like the - ability to decide for themselves if it is desired. */ - if (opts.addressprint && mid_statement) - { - print_pc (uiout, gdbarch, frame, get_frame_pc (frame)); - uiout->text ("\t"); - } - print_source_lines (sal.symtab, sal.line, sal.line + 1, 0); - } + value_print_options opts; + get_user_print_options (&opts); + + const bool print_address = opts.addressprint && mid_statement; + + std::string pc_address_flags + = gdbarch_get_pc_address_flags (gdbarch, frame, get_frame_pc (frame)); + + print_source (uiout, gdbarch, get_frame_pc (frame), sal, + print_address, mid_statement, pc_address_flags); - /* If disassemble-next-line is set to on and there is line debug - messages, output assembly codes for next line. */ - if (disassemble_next_line == AUTO_BOOLEAN_TRUE) - do_gdb_disassembly (get_frame_arch (frame), -1, sal.pc, sal.end); } if (set_current_sal) From patchwork Wed Jul 8 14:36:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138753 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A5FC54BA2E16 for ; Wed, 8 Jul 2026 14:40:17 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A5FC54BA2E16 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=mntER1g3 X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id A22D84BA2E17 for ; Wed, 8 Jul 2026 14:37:57 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org A22D84BA2E17 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 A22D84BA2E17 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521478; cv=none; b=sNbT1qAEZ7clHwBAdZs4mMv+c3n31kKn0Y8i9Oaf3UZnwg1CAQ7BRrFKAdZb28ZMBU3gvUM9mjryfDWmj2pWwe61KiMkSrWNmcGYEocoGtf5TjscVj/fh6WuVDQYBriJKSOxPW/FEIvlBBgGciXqVWFMspL5ZVNWcWHeNPyjNRQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521478; c=relaxed/simple; bh=qmcmsI9ZwipZkQ7KpD4RcWDIQbs6oWQHY+rRy9fxYps=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=aC4DZCo9RXal/TCU+H4lnmYP8w8lgpR8ikLc2wurpimrulh9uAEAd0tqPbhmw22FKWLAGPRQHE9zfDQHC3dHzFbXw0wM7lEwaNj18p/tpJvtQPKXqgHzqS3kK+EuADDOwuiQ8rrQ2ibK33006esqZ3bFX6mRoP/zAwav5juUMA4= 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=mntER1g3 DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A22D84BA2E17 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521478; x=1815057478; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=qmcmsI9ZwipZkQ7KpD4RcWDIQbs6oWQHY+rRy9fxYps=; b=mntER1g3vReKvUwWjQO/dwFjou1EkEVTTAP1A7FDyiIyfc+poTtI+Mq/ /M7TvTs5YtRs5ypYAidfdrDUTgAVFh50OpByFJPLpzJXoRA9N1vqWPZU6 jRCpczhImhVGVoJOoBv+5W/i8G4bJXt3n4RwDekuCOufkUdEY7G7YSDNe LO/SGv/9u0U1V9OQNItg7IC76WEeUe6erwg7MaOofvP6KVTUP/3tfopRT 9/4p0sHVoKJiBAetQidivNmOwLDCM20Hrk8YEsKlasCl1+bkE6uu4aJbS C+Dxh70+wwGlvKSz+YDS8QT6LREhX6Zh6iBgo+rTkq4/ZqVm+/CZktkBU g==; X-CSE-ConnectionGUID: bTZJU0TaSTGT29xSPg2CCA== X-CSE-MsgGUID: C0H5OQiORe+ogE98t51V9g== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375489" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375489" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:57 -0700 X-CSE-ConnectionGUID: ovbHxEr/RtWru1gx7XJprQ== X-CSE-MsgGUID: KhpwUNsqSvq1rsrBhmkCXw== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864347" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:37:55 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 08/13] gdb: Add command option 'bt -shadow' to print the shadow stack backtrace. Date: Wed, 8 Jul 2026 14:36:34 +0000 Message-Id: <20260708143639.2214689-9-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org Add command option '-shadow" to the backtrace command to print the shadow stack backtrace instead of the normal backtrace. This option may be combined with '-frame-info' and implies '-no-filters' and '-frame-arguments none'. Also the options @samp{-past-entry} or @samp{-hide} are not supported. This is an example for the output of 'bt -shadow' on amd64 linux: ~~ (gdb) bt -shadow /#0 0x000055555555514a in call1 at amd64-shadow-stack.c:27 /#1 0x000055555555515f in main at amd64-shadow-stack.c:38 ~~ Note that the normal backtrace includes one additional frame, since the shadow stack backtrace relies on return addresses of the shadow stack only. But except the missing frame arguments and frame #0 of the normal backtrace, the backtrace is the same. ~~ (gdb) bt /#0 call2 () at amd64-shadow-stack.c:21 /#1 0x000055555555514a in call1 () at amd64-shadow-stack.c:27 /#2 0x000055555555515f in main () at amd64-shadow-stack.c:38 ~~ This commit also adds a test for 'bt -shadow' on amd64. Although the test is OS independent we can only test this on linux, as GDB does not support shadow stack on other OS for now. Also we do not add a test for 32 bit, as support for shadow stack is limited to 64 bit by the linux kernel. Reviewed-by: Thiago Jung Bauermann --- gdb/NEWS | 3 + gdb/doc/gdb.texinfo | 25 + gdb/gdbarch-gen.h | 2 + gdb/gdbarch.h | 1 + gdb/gdbarch_components.py | 2 + gdb/shadow-stack.c | 475 ++++++++++++++++++ gdb/shadow-stack.h | 65 +++ gdb/stack.c | 49 +- gdb/stack.h | 43 ++ .../gdb.arch/amd64-shadow-stack-cmds.exp | 47 ++ gdb/testsuite/gdb.base/options.exp | 2 +- 11 files changed, 689 insertions(+), 25 deletions(-) diff --git a/gdb/NEWS b/gdb/NEWS index ec9b5a33787..fa06d21ddac 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -3,6 +3,9 @@ *** Changes since GDB 17 +* New "-shadow" command line option for the backtrace command to print + the shadow stack backtrace instead of the normal backtrace. + * Support for the Common Trace Format (CTF) has been removed. GDB now saves trace information exclusively in its own "tfile" format. diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index a698b2b8451..29ad2d24ca6 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -8788,6 +8788,29 @@ Related setting: @ref{set print raw-frame-arguments}. @item -frame-info @code{auto}|@code{source-line}|@code{location}|@code{source-and-location}|@code{location-and-address}|@code{short-location} Set printing of frame information. Related setting: @ref{set print frame-info}. + +@item -shadow +Print the shadow stack backtrace instead of the normal backtrace. +Similar to the normal backtrace @value{GDBN} prints one line per +element (so-called shadow stack frames) on the shadow stack. + +A shadow stack is supported, for instance, with the Intel Control-Flow +Enforcement Technology (@pxref{CET}) on x86 and the Guarded Control Stack +feature (@pxref{GCS}) on AArch64. + +This option may be combined with @code{-frame-info} and implies +@code{-no-filters} and @code{-frame-arguments none}. Also the options +@samp{-past-entry} or @samp{-hide} are not supported. + +This is how a shadow stack backtrace looks like on amd64: +@smallexample +@group +(gdb) bt -shadow +#0 0x000055555555514a in call1 at amd64-shadow-stack.c:27 +#1 0x000055555555515f in main at amd64-shadow-stack.c:38 +@end group +@end smallexample + @end table The optional @var{qualifier} is maintained for backward compatibility. @@ -27219,6 +27242,7 @@ information automatically from the core file, and will show one of the above messages depending on whether the synchronous or asynchronous mode is selected. @xref{Memory Tagging}. @xref{Memory}. +@anchor{GCS} @subsubsection AArch64 Guarded Control Stack @cindex Guarded Control Stack, AArch64 @cindex GCS, AArch64 @@ -27307,6 +27331,7 @@ registers @end itemize +@anchor{CET} @subsubsection Intel Control-Flow Enforcement Technology. @cindex Intel Control-Flow Enforcement Technology. diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index c05564fe05f..e4f9bc21b1d 100644 --- a/gdb/gdbarch-gen.h +++ b/gdb/gdbarch-gen.h @@ -1745,6 +1745,8 @@ void set_gdbarch_core_parse_exec_context (struct gdbarch *gdbarch, gdbarch_core_ If the shadow stack alignment is not the predefault of 8 bytes, configure the gdbarch value shadow_stack_element_size_aligned. + To support the command line option 'backtrace -shadow' each value and hook + listed above has to be provided. If possible, return the shadow stack pointer. If the shadow stack feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise diff --git a/gdb/gdbarch.h b/gdb/gdbarch.h index 7077f9262e9..b587a862e90 100644 --- a/gdb/gdbarch.h +++ b/gdb/gdbarch.h @@ -32,6 +32,7 @@ #include "registry.h" #include "solib.h" #include "find-memory-region.h" +#include "shadow-stack.h" struct floatformat; struct ui_file; diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py index 531d7e212d8..9d6fdc5f941 100644 --- a/gdb/gdbarch_components.py +++ b/gdb/gdbarch_components.py @@ -2774,6 +2774,8 @@ provided: If the shadow stack alignment is not the predefault of 8 bytes, configure the gdbarch value shadow_stack_element_size_aligned. +To support the command line option 'backtrace -shadow' each value and hook +listed above has to be provided. If possible, return the shadow stack pointer. If the shadow stack feature is enabled then set SHADOW_STACK_ENABLED to true, otherwise diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c index 160d30e0bfb..49b831617ab 100644 --- a/gdb/shadow-stack.c +++ b/gdb/shadow-stack.c @@ -22,6 +22,12 @@ #include "frame.h" #include "frame-unwind.h" #include "shadow-stack.h" +#include "stack.h" +#include "solib.h" +#include "event-top.h" +#include "cli/cli-style.h" +#include "buffered-streams.h" +#include "inferior.h" enum class ssp_update_direction { @@ -162,3 +168,472 @@ dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache, retval->mark_bytes_unavailable (0, retval->type ()->length ()); return retval; } + +/* Return true, if PC is in the middle of a statement. Note that in the + middle of a statement PC range includes sal.end (SAL.PC, SAL.END]. + Return false, if + - SAL.IS_STMT is false + - there is no location information associated with this SAL, which + could happen in case of inlined functions + - PC is not in the range (SAL.PC, SAL.END]. + This function is similar to stack.c:frame_show_address but is used + to determine if we are in the middle of a statement only, not to decide + if we should print a frame's address. */ + +static bool +pc_in_middle_of_statement (CORE_ADDR pc, symtab_and_line sal) +{ + if (sal.is_stmt == false) + return false; + + /* If there is a line number, but no PC, then there is no location + information associated with this sal. The only way that should + happen is for the call sites of inlined functions (SAL comes from + find_sal_for_pc). Otherwise, we would have some PC range if the SAL + came from a line table. However, as we don't have a frame for this + function we cannot assert (in contrast to frame_show_address). */ + if (sal.line != 0 && sal.pc == 0 && sal.end == 0) + return false; + + return pc > sal.pc && pc <= sal.end; +} + +/* Attempt to obtain the function name based on the symbol of PC. */ + +static gdb::unique_xmalloc_ptr +find_pc_funname (CORE_ADDR pc) +{ + symbol *func = find_symbol_for_pc (pc); + if (func != nullptr) + return find_symbol_funname (func); + + gdb::unique_xmalloc_ptr funname; + bound_minimal_symbol msymbol = lookup_minimal_symbol_by_pc (pc); + if (msymbol.minsym != nullptr) + funname = make_unique_xstrdup (msymbol.minsym->print_name ()); + + return funname; +} + +/* 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. */ + +static void +do_print_shadow_stack_frame_info + (ui_out *uiout, const frame_print_options &fp_opts, + const shadow_stack_frame_info &frame, print_what print_what) +{ + /* On x86 there can be a shadow stack token at bit 63. For x32, + the address size is only 32 bit. Thus, we still must use + gdbarch_shadow_stack_element_size_aligned (and not + gdbarch_addr_bit) to determine the width of the address to be + printed. */ + const int element_size + = gdbarch_shadow_stack_element_size_aligned (frame.arch); + + if (fp_opts.print_frame_info != print_frame_info_auto) + { + /* Use the specific frame information desired by the user. */ + print_what + = *print_frame_info_to_print_what (fp_opts.print_frame_info); + } + + /* At this point, since FRAME is a shadow stack frame which belongs to a + return address, we should always have a valid symtab_and_line object. */ + gdb_assert (frame.sal.has_value ()); + + const symtab_and_line &sal = frame.sal.value (); + if (should_print_location (print_what) || sal.symtab == nullptr) + { + gdb::unique_xmalloc_ptr funname = find_pc_funname (frame.value); + + ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame"); + uiout->text ("#"); + uiout->field_fmt_signed (2, ui_left, "level", frame.level); + + uiout->field_string + ("addr", hex_string_custom (frame.value, element_size * 2), + address_style.style ()); + + uiout->text (" in "); + print_funname (uiout, funname); + + if (print_what != SHORT_LOCATION && sal.symtab != nullptr) + print_filename (uiout, sal); + + if (print_what != SHORT_LOCATION + && (funname == nullptr || sal.symtab == nullptr) + && sal.pspace != nullptr) + { + const char *lib = solib_name_from_address (sal.pspace, + frame.value); + if (lib != nullptr) + print_lib (uiout, lib); + } + + uiout->text ("\n"); + } + + if (print_what == SRC_LINE || print_what == SRC_AND_LOC) + { + bool mid_statement = pc_in_middle_of_statement (frame.value, sal); + + /* While for the ordinary backtrace printing of pc is based on + MID_STATEMENT determined by stack.c:frame_show_address and the + print configuration, for shadow stack backtrace we always print + the pc/address on the shadow stack. */ + bool print_address = true; + print_source (uiout, frame.arch, frame.value, sal, print_address, + mid_statement, ""); + } + + gdb_flush (gdb_stdout); +} + +/* Redirect output to a temporary buffer for the duration of + do_print_shadow_stack_frame_info. */ + +static void +print_shadow_stack_frame_info + (const frame_print_options &fp_opts, + const shadow_stack_frame_info &frame, + print_what print_what) +{ + do_with_buffered_output + (do_print_shadow_stack_frame_info, current_uiout, + fp_opts, frame, print_what); +} + +/* Extract a char array which can be used for printing a reasonable + error message for REASON. REASON must not be NO_ERROR if passed + to this function. */ + +static const char * +ssp_unwind_stop_reason_to_err_string (ssp_unwind_stop_reason reason) +{ + switch (reason) + { + case ssp_unwind_stop_reason::memory_read_error: + return _("shadow stack memory read failure"); + } + + 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. */ + +static bool +read_shadow_stack_memory (gdbarch *gdbarch, CORE_ADDR ssp, + CORE_ADDR &return_value) +{ + /* On x86 there can be a shadow stack token at bit 63. For x32, the + address size is only 32 bit. Thus, we still must use + gdbarch_shadow_stack_element_size_aligned (and not gdbarch_addr_bit) + to read the full element for x32 as well. */ + const int element_size + = gdbarch_shadow_stack_element_size_aligned (gdbarch); + + const bfd_endian byte_order = gdbarch_byte_order (gdbarch); + return safe_read_memory_unsigned_integer (ssp, + element_size, + byte_order, + &return_value); +} + +/* If possible, return the starting shadow stack frame info needed to handle + COUNT outermost frames. This is the equivalent function to + stack.c:trailing_outermost_frame which is used to print the normal backtrace. + FRAME should point to the innermost (newest) element of the shadow stack. + RANGE is the shadow stack memory range [start_address, end_address) + corresponding to FRAME's shadow stack pointer. If COUNT is bigger than the + number of elements on the shadow stack, return FRAME. In case of failure, + assign an appropriate ssp_unwind_stop_reason in FRAME->UNWIND_STOP_REASON. */ + +static std::optional +get_trailing_outermost_shadow_stack_frame_info + (const std::pair range, + LONGEST count, shadow_stack_frame_info &frame) +{ + gdb_assert (count > 0); + + std::optional trailing + = std::optional (frame); + + std::optional current = trailing; + while (current.has_value () && count--) + { + QUIT; + current = current->unwind_prev_shadow_stack_frame_info (range); + } + + /* Will stop when CURRENT reaches the outermost (oldest) element of the + shadow stack. TRAILING will be COUNT below it. */ + while (current.has_value ()) + { + QUIT; + trailing = trailing->unwind_prev_shadow_stack_frame_info (range); + /* Since current has a value until this point we can assume that + trailing also has a value. */ + gdb_assert (trailing.has_value ()); + current = current->unwind_prev_shadow_stack_frame_info (range); + } + + 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. */ + +static std::optional +get_shadow_stack_frame_info + (gdbarch *fallback_arch, const CORE_ADDR ssp, unsigned long level) +{ + CORE_ADDR value; + if (!read_shadow_stack_memory (fallback_arch, ssp, value)) + return {}; + + /* At this point, we know that SSP points to VALUE which is a return + address. In contrast to find_frame_sal which is used for the normal + backtrace command, VALUE always points at the return instruction + (which is *after* the call instruction). Since we want to get the + line containing the call (because the call is where the user thinks + the program is), we pass 1 here as second argument. */ + symtab_and_line sal = find_sal_for_pc (value, 1); + struct gdbarch *sal_arch = get_sal_arch (sal); + + /* Fall back to the previous gdbarch in case we cannot extract it from + SAL. */ + if (sal_arch == nullptr) + sal_arch = fallback_arch; + + return std::optional + ({ssp, value, level, sal_arch, sal, + ssp_unwind_stop_reason::no_error}); +} + +std::optional +shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info + (std::pair range) +{ + if (!user_set_backtrace_options.backtrace_past_main + && this->inside_main_func ()) + { + /* Don't unwind past main(). */ + return {}; + } + + /* If the user's backtrace limit has been exceeded, stop. We must + add two to the current level; one of those accounts for + backtrace_limit being 1-based and the level being 0-based, and the + other accounts for the level of the new frame instead of the level + of the current frame. */ + if (this->level + 2 > user_set_backtrace_options.backtrace_limit) + return {}; + + CORE_ADDR new_ssp + = update_shadow_stack_pointer (this->arch, this->ssp, + ssp_update_direction::outer); + + if (gdbarch_top_addr_empty_shadow_stack_p (this->arch) + && gdbarch_top_addr_empty_shadow_stack (this->arch, new_ssp, range)) + return {}; + else if (gdbarch_stack_grows_down (this->arch)) + { + /* The shadow stack grows downwards. */ + if (new_ssp >= range.second) + { + /* We reached the outermost element of the shadow stack. */ + return {}; + } + /* We updated new_ssp towards the outermost element of the shadow + stack before, and new_ssp must be pointing to shadow stack + memory. */ + gdb_assert (new_ssp > range.first); + } + else + { + /* The shadow stack grows upwards. */ + if (new_ssp < range.first) + { + /* We reached the outermost element of the shadow stack. */ + return {}; + } + /* We updated new_ssp towards the outermost element of the shadow + stack before, and new_ssp must be pointing to shadow stack + memory. */ + gdb_assert (new_ssp <= range.second); + } + + /* Extract the previous shadow stack frame info (level + 1). */ + std::optional new_frame + = get_shadow_stack_frame_info (this->arch, new_ssp, this->level + 1); + + if (!new_frame.has_value ()) + this->unwind_stop_reason = ssp_unwind_stop_reason::memory_read_error; + + return new_frame; +} + +bool +shadow_stack_frame_info::inside_main_func () const +{ + language lang; + symbol *sym = find_symbol_for_pc (this->value); + if (sym != nullptr) + lang = sym->language (); + else + { + bound_minimal_symbol msymbol + = lookup_minimal_symbol_by_pc (this->value); + + if (msymbol.minsym != nullptr) + lang = msymbol.minsym->language (); + else + { + /* If we cannot extract the symbol, we cannot use the address to + find out if the function is inside main and we better print + this frame, too. */ + return false; + } + } + + std::optional sym_addr + = get_main_func_start_pc (this->arch, lang); + if (!sym_addr.has_value () || *sym_addr == 0) + return false; + + CORE_ADDR pc_func_start = get_pc_function_start (this->value); + if (*sym_addr == pc_func_start) + return true; + + return false; +} + +/* 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) +{ + if (!target_has_stack ()) + error (_("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.")); + + 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 is not enabled for the current thread.")); + + /* 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. */ + bool is_top_addr_empty_shadow_stack = false; + std::pair range; + if (!gdbarch_address_in_shadow_stack_memory_range (gdbarch, *start_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; + } + + if (!is_top_addr_empty_shadow_stack) + { + /* 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); + } + + if (is_top_addr_empty_shadow_stack) + { + gdb_printf (_("The shadow stack is empty.\n")); + return; + } + + /* 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); + + if (!current.has_value ()) + reason = ssp_unwind_stop_reason::memory_read_error; + + std::optional trailing = current; + + LONGEST count = -1; + if (current.has_value () && count_exp != nullptr) + { + count = parse_and_eval_long (count_exp); + /* If count is negative, update trailing with the shadow stack frame + info from which we should start printing. */ + if (count < 0) + { + trailing = get_trailing_outermost_shadow_stack_frame_info + (range, std::abs (count), *current); + + if (!trailing.has_value ()) + reason = current->unwind_stop_reason; + } + } + + if (!trailing.has_value ()) + { + gdb_assert (reason != ssp_unwind_stop_reason::no_error); + + error (_("Cannot print shadow stack backtrace: %s.\n"), + ssp_unwind_stop_reason_to_err_string (reason)); + } + + for (current = trailing; current.has_value () && count != 0; count--) + { + QUIT; + + print_shadow_stack_frame_info (fp_opts, *current, LOCATION); + + trailing = current; + current = current->unwind_prev_shadow_stack_frame_info (range); + } + + /* If we've stopped before the end, mention that. */ + if (current.has_value () && from_tty) + gdb_printf (_("(More shadow stack frames follow...)\n")); + + /* Due to the loop above, trailing always has a value at this point. */ + gdb_assert (trailing.has_value ()); + + /* If we've run out of shadow stack frames, and the reason appears to + be an error condition, print it. */ + if (!current.has_value () + && trailing->unwind_stop_reason > ssp_unwind_stop_reason::no_error) + gdb_printf (_("Shadow stack backtrace stopped at shadow stack " \ + "pointer %s due to: %s.\n"), + paddress (trailing->arch, trailing->ssp), + ssp_unwind_stop_reason_to_err_string + (trailing->unwind_stop_reason)); +} diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index 5f540ff7d62..5c89aee70bb 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -21,9 +21,17 @@ #ifndef GDB_SHADOW_STACK_H #define GDB_SHADOW_STACK_H +#include +#include +#include + +#include "symtab.h" + class regcache; class frame_info_ptr; struct value; +struct frame_print_options; +struct gdbarch; /* If shadow stack is enabled, push the address NEW_ADDR on the shadow stack and update the shadow stack pointer accordingly. */ @@ -39,4 +47,61 @@ void shadow_stack_push (regcache *regcache, const CORE_ADDR new_addr); value *dwarf2_prev_ssp (const frame_info_ptr &this_frame, void **this_cache, int regnum); +/* Implementation of "backtrace -shadow" command line option. */ + +void backtrace_shadow_command + (const frame_print_options &fp_opts, + const char *count_exp, int from_tty); + +enum class ssp_unwind_stop_reason +{ + /* No particular reason; either we haven't tried unwinding yet, or we + didn't fail. */ + no_error = 0, + + /* We could not read the memory of the shadow stack element. */ + memory_read_error, +}; + +/* Information of a shadow stack frame belonging to a shadow stack element + at shadow stack pointer SSP. */ + +class shadow_stack_frame_info +{ +public: + /* If possible, unwind the previous shadow stack frame info. RANGE is + the shadow stack memory range [start_address, end_address) belonging + to this frame's shadow stack pointer. If we cannot unwind the + previous frame info, set the unwind_stop_reason attribute. If we + reached the bottom of the shadow stack just don't return a value. */ + std::optional unwind_prev_shadow_stack_frame_info + (std::pair range); + + /* The shadow stack pointer. */ + CORE_ADDR ssp; + + /* The value of the shadow stack at SSP. */ + CORE_ADDR value; + + /* The level of the element on the shadow stack. */ + unsigned long level; + + /* Architecture of the current shadow stack frame. */ + struct gdbarch *arch; + + /* Optional SAL object of the current shadow stack frame. */ + std::optional sal; + + /* If unwinding of the previous frame info fails assign this value to a + matching condition ssp_unwind_stop_reason + > ssp_unwind_stop_reason::no_error. */ + ssp_unwind_stop_reason unwind_stop_reason + = ssp_unwind_stop_reason::no_error; + + /* Return true if this frame is inside the "main"() function. If the frame + is outside the "main"() function, does not have a SAL object or if the + symbol information cannot be extracted, return false. */ + bool inside_main_func () const; +}; + #endif /* GDB_SHADOW_STACK_H */ diff --git a/gdb/stack.c b/gdb/stack.c index 7df1d6e33da..e96fbe73947 100644 --- a/gdb/stack.c +++ b/gdb/stack.c @@ -51,6 +51,7 @@ #include "cli/cli-utils.h" #include "objfiles.h" #include "buffered-streams.h" +#include "shadow-stack.h" #include "symfile.h" #include "extension.h" @@ -191,6 +192,7 @@ struct backtrace_cmd_options bool full = false; bool no_filters = false; bool hide = false; + bool shadow = false; }; using bt_flag_option_def @@ -214,6 +216,14 @@ static const gdb::option::option_def backtrace_command_option_defs[] = { [] (backtrace_cmd_options *opt) { return &opt->hide; }, N_("Causes Python frame filter elided frames to not be printed."), }, + + bt_flag_option_def { + "shadow", + [] (backtrace_cmd_options *opt) { return &opt->shadow; }, + N_("Print shadow stack frames instead of normal frames.\n\ +This option may be combined with \"-frame-info\" and\n\ +implies '-no-filters' and '-frame-arguments none'."), + }, }; /* Prototypes for local functions. */ @@ -906,11 +916,9 @@ do_gdb_disassembly (struct gdbarch *gdbarch, } } -/* Converts the PRINT_FRAME_INFO choice to an optional enum print_what. - Value not present indicates to the caller to use default values - specific to the command being executed. */ +/* See stack.h. */ -static std::optional +std::optional print_frame_info_to_print_what (const char *print_frame_info) { for (int i = 0; print_frame_info_choices[i] != NULL; i++) @@ -960,7 +968,7 @@ get_user_print_what_frame_info (std::optional *what) /* Return true if PRINT_WHAT is configured to print the location of a frame. */ -static bool +bool should_print_location (print_what print_what) { return (print_what == LOCATION @@ -969,14 +977,9 @@ should_print_location (print_what print_what) || print_what == SHORT_LOCATION); } -/* Print the source information for PC and SAL to UIOUT. Based on the - user-defined configuration disassemble-next-line, display disassembly - of the next source line, in addition to displaying the source line - itself. Print annotations describing source file and and line number - based on MID_STATEMENT information. If SHOW_ADDRESS is true, print the - program counter PC including, if non-empty, PC_ADDRESS_FLAGS. */ +/* See stack.h. */ -static void +void print_source (ui_out *uiout, gdbarch *gdbarch, CORE_ADDR pc, symtab_and_line sal, bool show_address, int mid_statement, const std::string &pc_address_flags) @@ -1242,7 +1245,7 @@ get_last_displayed_sal () /* Find the function name for the symbol SYM. */ -static gdb::unique_xmalloc_ptr +gdb::unique_xmalloc_ptr find_symbol_funname (const symbol *sym) { gdb::unique_xmalloc_ptr funname; @@ -1308,10 +1311,9 @@ find_frame_funname (const frame_info_ptr &frame, enum language *funlang, return funname; } -/* Print the library LIB to UIOUT for the printing of frame - information. */ +/* See stack.h. */ -static void +void print_lib (ui_out *uiout, const char *lib) { annotate_frame_where (); @@ -1320,10 +1322,7 @@ print_lib (ui_out *uiout, const char *lib) uiout->field_string ("from", lib, file_name_style.style ()); } -/* Print the filenname of SAL to UIOUT for the printing of frame - information. */ - -static void +void print_filename (ui_out *uiout, symtab_and_line sal) { annotate_frame_source_begin (); @@ -1348,10 +1347,9 @@ print_filename (ui_out *uiout, symtab_and_line sal) annotate_frame_source_end (); } -/* If available, print FUNNAME to UIOUT for the printing of frame - information. */ +/* See stack.h. */ -static void +void print_funname (ui_out *uiout, gdb::unique_xmalloc_ptr const &funname) { @@ -2162,7 +2160,10 @@ backtrace_command (const char *arg, int from_tty) scoped_restore restore_set_backtrace_options = make_scoped_restore (&user_set_backtrace_options, set_bt_opts); - backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty); + if (!bt_cmd_opts.shadow) + backtrace_command_1 (fp_opts, bt_cmd_opts, arg, from_tty); + else + backtrace_shadow_command (fp_opts, arg, from_tty); } /* Completer for the "backtrace" command. */ diff --git a/gdb/stack.h b/gdb/stack.h index ad2700b59a7..3f4cf348841 100644 --- a/gdb/stack.h +++ b/gdb/stack.h @@ -84,4 +84,47 @@ void frame_apply_all_cmd_completer (struct cmd_list_element *ignore, completion_tracker &tracker, const char *text, const char */*word*/); +/* Print the filename of SAL to UIOUT for the printing of frame + information. */ + +void print_filename (ui_out *uiout, symtab_and_line sal); + +/* Print the library LIB to UIOUT for the printing of frame + information. */ + +void print_lib (ui_out *uiout, const char *lib); + +/* If available, print FUNNAME to UIOUT for the printing of frame + information. */ + +void print_funname (ui_out *uiout, + gdb::unique_xmalloc_ptr const &funname); + +/* Converts the PRINT_FRAME_INFO choice to an optional enum print_what. + Value not present indicates to the caller to use default values + specific to the command being executed. */ + +std::optional print_frame_info_to_print_what + (const char *print_frame_info); + +/* Return true if PRINT_WHAT is configured to print the location of a + frame. */ + +bool should_print_location (print_what print_what); + +/* Print the source information for PC and SAL to UIOUT. Based on the + user-defined configuration disassemble-next-line, display disassembly + of the next source line, in addition to displaying the source line + itself. Print annotations describing source file and and line number + based on MID_STATEMENT information. If SHOW_ADDRESS is true, print the + program counter PC including, if non-empty, PC_ADDRESS_FLAGS. */ + +void print_source (ui_out *uiout, gdbarch *gdbarch, CORE_ADDR pc, + symtab_and_line sal, bool show_address, + int mid_statement, const std::string &pc_address_flags); + +/* Find the function name for the symbol SYM. */ + +gdb::unique_xmalloc_ptr find_symbol_funname (const symbol *sym); + #endif /* GDB_STACK_H */ diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp index e67a0c2e4a1..0347313b647 100644 --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp @@ -109,6 +109,53 @@ save_vars { ::env(GLIBC_TUNABLES) } { gdb_test "print /x \$pl3_ssp" "= $ssp_call2" "check pl3_ssp of frame 0" } + set fill "\[^\r\n\]+" + # Build shadow stack frames to test the 'backtrace -shadow' command. + set sspval_main [get_valueof "/z" "*(long long int*)$ssp_main" ""] + set sspval_call1 [get_valueof "/z" "*(long long int*)$ssp_call1" ""] + set sspval_call2 [get_valueof "/z" "*(long long int*)$ssp_call2" ""] + set frame1 "#0\[ \t\]*$sspval_call2 in call1$fill" + set frame2 "#1\[ \t\]*$sspval_call1 in main$fill" + set frame3 "#2\[ \t\]*$sspval_main$fill" + + # Test bt -shadow, which prints until the main function. + gdb_test "bt -shadow" \ + [multi_line \ + "$frame1" \ + "$frame2" ] \ + "test shadow stack backtrace until main." + + # With -past-main set, we can still only test that we print the first + # 3 frames correctly, as the shadow stack enablement might depend on + # the underlying OS. + gdb_test "bt -shadow -past-main" \ + [multi_line \ + "$frame1" \ + "$frame2" \ + "$frame3" \ + ".*" ] \ + "test shadow stack backtrace until the bottom of the stack." + + gdb_test "bt -shadow 1" \ + [multi_line \ + "$frame1" \ + "\\(More shadow stack frames follow...\\)" ] \ + "test shadow stack backtrace with a positive value for count" + + # We can only test that we print a single frame, as the shadow stack + # enablement might depend on the underlying OS. + gdb_test "bt -shadow -1" "^#$decimal\[ \t\]*$hex$fill" \ + "test shadow stack backtrace with a negative value for count" + + # Test backtrace limit + gdb_test_no_output "set backtrace limit 2" + gdb_test "bt -shadow" \ + [multi_line \ + "$frame1" \ + "$frame2" ] \ + "test shadow stack backtrace with limit" + gdb_test_no_output "set backtrace limit unlimited" "restore backtrace limit default" + with_test_prefix "test return from current frame" { gdb_test "return (int) 1" "#0.*call1.*" \ "Test shadow stack return from current frame" \ diff --git a/gdb/testsuite/gdb.base/options.exp b/gdb/testsuite/gdb.base/options.exp index 35487ead6a7..8ca6e677aba 100644 --- a/gdb/testsuite/gdb.base/options.exp +++ b/gdb/testsuite/gdb.base/options.exp @@ -286,7 +286,6 @@ proc_with_prefix test-backtrace {} { clean_restart test_gdb_complete_unique "backtrace" "backtrace" - test_gdb_complete_none "backtrace " gdb_test "backtrace -" "Ambiguous option at: -" gdb_test "backtrace --" "No stack\\." @@ -302,6 +301,7 @@ proc_with_prefix test-backtrace {} { "-past-entry" "-past-main" "-raw-frame-arguments" + "-shadow" } # Test that we complete the qualifiers, if there's any. From patchwork Wed Jul 8 14:36:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138752 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 826574BA2E15 for ; Wed, 8 Jul 2026 14:40:04 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 826574BA2E15 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=AIF1iHWg X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id 7F1374BA2E2C for ; Wed, 8 Jul 2026 14:38:02 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 7F1374BA2E2C 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 7F1374BA2E2C Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521482; cv=none; b=MgLbF20RpSHWD0gp1C/9RQ3/+4+ppWqjAXp4nLT5gt8N7GcTqQwj9nt6nq7XJ9veak0D8qN7boI9rHyJ0mP5tQvrqKvC5LDKpA8yUJdSWbZMXxVJ8GZE92WiPKa6FHuMAmtwY3plBdTrNAEXhCkiMcBNODatcZFnrHnNOR8M0UM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521482; c=relaxed/simple; bh=82PO0t8Q78M+wm+Fse93VtlSfmg5rf5rgBO/z1/nfMI=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=BMbLGimx/eta0rYqZtZltDpYIrjtPr6aZPZqnY4MdzwIGaw8mPgLI4yZPKmEEVsvQJy+eDYwBGMdDdTaCspzX0imraweZtGlk29mops05hmJAX3VXF99I+pV/YXS86mdkLv1Jm89MWB5dlwcg/8aIYItNxbwm5n43QI6V2sLiIA= 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=AIF1iHWg DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 7F1374BA2E2C DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521483; x=1815057483; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=82PO0t8Q78M+wm+Fse93VtlSfmg5rf5rgBO/z1/nfMI=; b=AIF1iHWgfEm+VSSobPYmPmc+wttBCcNLw/hWgxmCPEhWY+2VSWfJD+AF A/ecESHlcZc6Y+Xyhdw59VZdRHsoRmcU437ZslQAbbu7JG1fqJ248pQvU Kd/JosY07xrqRwEK8ACrHUEGKcglee7r9PBH+S5Y6tKHC1zmqCEmWc0N8 VYOOsSsItreNTp6EYgjksMl8tLGcVoMKCZjHVspkUz/ZqkBqqEx8QWB6t 9MsFdHHsGi+/UwjNaYbAuT+m5R/HtooayLL/5tdFrRW6fIx9yNOO6BS6p uYMbEbxAigGB/Q6Y0a8YNb2obNnHj3RIEoUEoKrCMj1xUg31sjngp9SaL Q==; X-CSE-ConnectionGUID: g2fjyWTcSq2EU0tmdeLmPg== X-CSE-MsgGUID: u+WeJcwyQLu7aFR/vY9ZDQ== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375508" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375508" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:02 -0700 X-CSE-ConnectionGUID: K5fkA9ouSE2pta9B2G3FjA== X-CSE-MsgGUID: wDynAOlcQjOUC0om0iFYCg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864375" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:00 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 09/13] gdb: Provide gdbarch hook to distinguish shadow stack backtrace elements. Date: Wed, 8 Jul 2026 14:36:35 +0000 Message-Id: <20260708143639.2214689-10-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org On x86 with CET or on ARM with GCS, there can be elements on the shadow stack which are not return addresses. In this case, we don't want to print the shadow stack element, but a string instead which describes the frame similar to the normal backtrace command for dummy frames or signals. Provide a gdbarch hook to distinguish between return and non-return addresses and to configure a string which is printed instead of the shadow stack element. Reviewed-by: Thiago Jung Bauermann --- gdb/doc/gdb.texinfo | 19 +++++++++++++++++++ gdb/gdbarch-gen.c | 32 ++++++++++++++++++++++++++++++++ gdb/gdbarch-gen.h | 19 +++++++++++++++++++ gdb/gdbarch_components.py | 25 +++++++++++++++++++++++++ gdb/shadow-stack.c | 36 +++++++++++++++++++++++++++++++++++- gdb/shadow-stack.h | 26 ++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 1 deletion(-) diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo index 29ad2d24ca6..665576c4fc4 100644 --- a/gdb/doc/gdb.texinfo +++ b/gdb/doc/gdb.texinfo @@ -8811,6 +8811,25 @@ This is how a shadow stack backtrace looks like on amd64: @end group @end smallexample +There can be elements on the shadow stack which are not return addresses, +for example on x86 with the Intel Control-Flow Enforcement Technology +(@xref{CET}). In case of signals, the old shadow stack pointer is pushed +in a special format with bit 63 set. See @url{https://docs.kernel.org/arch/x86/shstk.html} +for more details. For such shadow stack elements, the shadow stack +frame just contains the level and a string describing the shadow stack +element: + +@smallexample +@group +(gdb) bt -shadow 4 +#0 0x00007ffff7c45330 in __restore_rt from /lib/x86_64-linux-gnu/libc.so.6 +#1 +#2 0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:26 +#3 0x000055555555519d in main at tmp/amd64-shadow-stack-signal.c:29 +(More shadow stack frames follow...) +@end group +@end smallexample + @end table The optional @var{qualifier} is maintained for backward compatibility. diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index 022ad496026..e93afff34b1 100644 --- a/gdb/gdbarch-gen.c +++ b/gdb/gdbarch-gen.c @@ -256,6 +256,7 @@ struct gdbarch gdbarch_address_in_shadow_stack_memory_range_ftype *address_in_shadow_stack_memory_range = nullptr; gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr; int shadow_stack_element_size_aligned = 8; + gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address = nullptr; }; /* Create a new ``struct gdbarch'' based on information provided by @@ -519,6 +520,7 @@ verify_gdbarch (struct gdbarch *gdbarch) /* Skip verify of address_in_shadow_stack_memory_range, has predicate. */ /* Skip verify of top_addr_empty_shadow_stack, has predicate. */ /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0. */ + /* Skip verify of is_no_return_shadow_stack_address, has predicate. */ if (!log.empty ()) internal_error (_("verify_gdbarch: the following are invalid ...%s"), log.c_str ()); @@ -1357,6 +1359,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) gdb_printf (file, "gdbarch_dump: shadow_stack_element_size_aligned = %s\n", plongest (gdbarch->shadow_stack_element_size_aligned)); + gdb_printf (file, + "gdbarch_dump: gdbarch_is_no_return_shadow_stack_address_p() = %d\n", + gdbarch_is_no_return_shadow_stack_address_p (gdbarch)); + gdb_printf (file, + "gdbarch_dump: is_no_return_shadow_stack_address = <%s>\n", + host_address_to_string (gdbarch->is_no_return_shadow_stack_address)); if (gdbarch->dump_tdep != nullptr) gdbarch->dump_tdep (gdbarch, file); } @@ -5362,3 +5370,27 @@ set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, { gdbarch->shadow_stack_element_size_aligned = shadow_stack_element_size_aligned; } + +bool +gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != nullptr); + return gdbarch->is_no_return_shadow_stack_address != nullptr; +} + +std::optional +gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level) +{ + gdb_assert (gdbarch != nullptr); + gdb_assert (gdbarch->is_no_return_shadow_stack_address != nullptr); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_is_no_return_shadow_stack_address called\n"); + return gdbarch->is_no_return_shadow_stack_address (gdbarch, ssp, value, level); +} + +void +set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, + gdbarch_is_no_return_shadow_stack_address_ftype is_no_return_shadow_stack_address) +{ + gdbarch->is_no_return_shadow_stack_address = is_no_return_shadow_stack_address; +} diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index e4f9bc21b1d..791324777dd 100644 --- a/gdb/gdbarch-gen.h +++ b/gdb/gdbarch-gen.h @@ -1789,3 +1789,22 @@ void set_gdbarch_top_addr_empty_shadow_stack (struct gdbarch *gdbarch, gdbarch_t int gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch); void set_gdbarch_shadow_stack_element_size_aligned (struct gdbarch *gdbarch, int shadow_stack_element_size_aligned); + +/* There can be elements on the shadow stack which are not return addresses. + This happens for example on x86 with CET in case of signals. + If an architecture implements the command option 'backtrace -shadow' and + the shadow stack can contain elements which are not return addresses, this + function has to be provided. + Return a shadow stack frame info with frame type + ssp_frame_type::non_return_frame, if the shadow stack pointer SSP belongs + to a valid shadow stack frame while the element on the shadow stack + VALUE does not refer to a return address. In that case, also the frame's + attribute non_return_description has to be set to a string which is + displayed instead of the element on the shadow stack in the shadow stack + backtrace. Otherwise, return an empty optional. */ + +bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch); + +using gdbarch_is_no_return_shadow_stack_address_ftype = std::optional (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level); +std::optional gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level); +void set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address); diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py index 9d6fdc5f941..0435b1b5eca 100644 --- a/gdb/gdbarch_components.py +++ b/gdb/gdbarch_components.py @@ -2839,3 +2839,28 @@ this value. # linux starting with Linux kernel v6.6. unused=True, ) + +Method( + comment=""" +There can be elements on the shadow stack which are not return addresses. +This happens for example on x86 with CET in case of signals. +If an architecture implements the command option 'backtrace -shadow' and +the shadow stack can contain elements which are not return addresses, this +function has to be provided. +Return a shadow stack frame info with frame type +ssp_frame_type::non_return_frame, if the shadow stack pointer SSP belongs +to a valid shadow stack frame while the element on the shadow stack +VALUE does not refer to a return address. In that case, also the frame's +attribute non_return_description has to be set to a string which is +displayed instead of the element on the shadow stack in the shadow stack +backtrace. Otherwise, return an empty optional. +""", + type="std::optional", + name="is_no_return_shadow_stack_address", + params=[ + ("const CORE_ADDR", "ssp"), + ("const CORE_ADDR", "value"), + ("const unsigned long", "level"), + ], + predicate=True, +) diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c index 49b831617ab..dbd8959753d 100644 --- a/gdb/shadow-stack.c +++ b/gdb/shadow-stack.c @@ -233,6 +233,29 @@ do_print_shadow_stack_frame_info const int element_size = gdbarch_shadow_stack_element_size_aligned (frame.arch); + if (frame.type != ssp_frame_type::normal_frame) + { + std::string str; + if (frame.type == ssp_frame_type::non_return_frame) + { + /* For non-return frames, the string must have a value. */ + gdb_assert (frame.non_return_description.has_value ()); + str = frame.non_return_description.value (); + } + else + gdb_assert_not_reached ("Invalid shadow stack frame type."); + + ui_out_emit_tuple tuple_emitter (uiout, "shadow-stack-frame"); + uiout->text ("#"); + uiout->field_fmt_signed (2, ui_left, "level", frame.level); + + uiout->field_string ("func", str, metadata_style.style ()); + + uiout->text ("\n"); + gdb_flush (gdb_stdout); + return; + } + if (fp_opts.print_frame_info != print_frame_info_auto) { /* Use the specific frame information desired by the user. */ @@ -398,6 +421,15 @@ get_shadow_stack_frame_info if (!read_shadow_stack_memory (fallback_arch, ssp, value)) return {}; + if (gdbarch_is_no_return_shadow_stack_address_p (fallback_arch)) + { + std::optional no_return_frame + = gdbarch_is_no_return_shadow_stack_address (fallback_arch, ssp, + value, level); + if (no_return_frame.has_value ()) + return *no_return_frame; + } + /* At this point, we know that SSP points to VALUE which is a return address. In contrast to find_frame_sal which is used for the normal backtrace command, VALUE always points at the return instruction @@ -414,6 +446,7 @@ get_shadow_stack_frame_info return std::optional ({ssp, value, level, sal_arch, sal, + ssp_frame_type::normal_frame, {}, ssp_unwind_stop_reason::no_error}); } @@ -421,7 +454,8 @@ std::optional shadow_stack_frame_info::unwind_prev_shadow_stack_frame_info (std::pair range) { - if (!user_set_backtrace_options.backtrace_past_main + if (this->type == ssp_frame_type::normal_frame + && !user_set_backtrace_options.backtrace_past_main && this->inside_main_func ()) { /* Don't unwind past main(). */ diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index 5c89aee70bb..4278620c70c 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -63,6 +63,21 @@ enum class ssp_unwind_stop_reason memory_read_error, }; +enum class ssp_frame_type +{ + /* A normal shadow stack frame which belongs to a return address of the + program execution flow. */ + normal_frame, + + /* A shadow stack frame which does not belong to a return address. + It is possible, on x86 for instance, that an element on the shadow + stack is not a return address. We don't want to print the address + in that case but only a string describing that specific frame type. + This frame type is configured in the target specific implementation + of is_no_return_shadow_stack_address. */ + non_return_frame, +}; + /* Information of a shadow stack frame belonging to a shadow stack element at shadow stack pointer SSP. */ @@ -92,6 +107,17 @@ class shadow_stack_frame_info /* Optional SAL object of the current shadow stack frame. */ std::optional sal; + /* The shadow stack frame type. */ + ssp_frame_type type; + + /* If this is not a normal shadow stack frame belonging to a return + address of the program execution flow then the frame will have the + type ssp_frame_type::non_return_frame. In this case, this string + is configured to describe this specific frame type and it will + be printed in the shadow stack backtrace instead of the element on + the shadow stack. */ + std::optional non_return_description; + /* If unwinding of the previous frame info fails assign this value to a matching condition ssp_unwind_stop_reason > ssp_unwind_stop_reason::no_error. */ From patchwork Wed Jul 8 14:36:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138746 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 805564BA2E05 for ; Wed, 8 Jul 2026 14:38:40 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 805564BA2E05 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=izNVHRlx X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id 3F6044BA2E15 for ; Wed, 8 Jul 2026 14:38:07 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 3F6044BA2E15 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 3F6044BA2E15 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521487; cv=none; b=uTlaee70xW13HqFxDH7or4yzCJxktj17VJz2OQE3MOol9SQNOYaQIF3DGNbVkFMVt9DWQbAiEzo2TkXcJbxt0byuuzodphJCllZMXvmwAFAZlQbtBED31wNxOHYmNhIZzJ0xS0+WWVH4VzvNshF08aOiiQ9a5bpL7EVaTrbVIRs= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521487; c=relaxed/simple; bh=Hus51Ni8Imnq+mUwOqoDOrD8fv2BXgZyDHBabg+Bb3k=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=cPJcyRJv13D3e3Hf9EFh6kjTNdjhIsGhwWzn5hxBnEwQoHFH/HkH3Yrns/CO4U6Qsm0odQwuUoD6SlV6fIK/45h6kwVre24VWTIRdMTklnqyTXMj5Yxv3EV9vWuVUEbGxRlXMYBL3bN4/181DpUoFE8c9yfE3SKpw3D6cZYU47w= 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=izNVHRlx DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 3F6044BA2E15 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521487; x=1815057487; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=Hus51Ni8Imnq+mUwOqoDOrD8fv2BXgZyDHBabg+Bb3k=; b=izNVHRlxaO6MD4tzscDCjG3mGURxw6t+9uE2PrtEr3zi8sFGrfIwIks2 5y0GvfyaBqp+LhStukEuS6khdeicWMgTVtuIsX/Y+wQu+LXNvm/q00rqO ZMbmmMSRASGHHfeRLPnRHx4DjDy/4ZqjitWBDxl9O+0suD1nYLE6e7wsj XHsnuyjjO70m/m8wIrhT0uHKaul/rOpjc192N5UzKR7nEa7hvxq1L/MTw uS4rJdCAxOvKI00ynD4OJ/oGbUxfaRyOfPmNYFUm3u2im3VJZzS2bg9B2 4HmCm9cAwP3VJBzfPHsRpcrAINlb+o/VYBFBW5pICJLmiVueDQrfSxIpR Q==; X-CSE-ConnectionGUID: JcQqYWYRSjyk4p1Cp/P3+g== X-CSE-MsgGUID: pZITeOF2TRi8NqAPu/6dYg== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375528" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375528" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:06 -0700 X-CSE-ConnectionGUID: 24lzdKkHRvqLRmpfIDXTEg== X-CSE-MsgGUID: yx/v6V6qT9iv8k3IcB/klQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864400" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:05 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 10/13] gdb: Implement the hook 'is_no_return_shadow_stack_address' for amd64 linux. Date: Wed, 8 Jul 2026 14:36:36 +0000 Message-Id: <20260708143639.2214689-11-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org There can be elements on the shadow stack which are not return addresses. This can happen, for instance, in case of signals on amd64 linux. The old shadow stack pointer is pushed in a special format with bit 63 set. |1...old SSP| - Pointer to old pre-signal ssp in sigframe token format (bit 63 set to 1) Linux kernel documentation: https://docs.kernel.org/arch/x86/shstk.html Implement the gdbarch hook is_no_return_shadow_stack_address to detect this scenario to print the shadow stack backtrace correctly. --- gdb/amd64-linux-tdep.c | 59 +++++++++++++++++++ .../amd64-shadow-stack-backtrace-signal.exp | 49 +++++++++++++++ .../gdb.arch/amd64-shadow-stack-signal.c | 31 ++++++++++ 3 files changed, 139 insertions(+) create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp create mode 100644 gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index 42a62eaa975..d98415b8989 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -1960,6 +1960,62 @@ amd64_linux_top_addr_empty_shadow_stack return addr == range.second; } +/* Return a shadow stack frame info, if the shadow stack pointer SSP + belongs to a valid shadow stack frame while the element on the shadow + stack VALUE does not refer to a return address. This can happen, for + instance, in case of signals. The old shadow stack pointer is pushed + in a special format with bit 63 set. In case this is true, a valid + shadow stack frame info is returned with its attributes frame_type and + non_return_description configured to ssp_frame_type::non_return_frame + and "", respectively. */ + +static std::optional +amd64_linux_is_no_return_shadow_stack_address + (gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, + const unsigned long level) +{ + /* SSP must belong to the shadow stack memory range. */ + std::pair range; + gdb_assert (gdbarch_address_in_shadow_stack_memory_range (gdbarch, + ssp, + &range)); + + /* In case bit 63 is not configured, the address on the shadow stack + should be a return address. */ + constexpr CORE_ADDR mask = (CORE_ADDR) 1 << 63; + if ((value & mask) == 0) + return {}; + + /* To compare the shadow stack pointer of the previous frame with the + value of FRAME, we must clear bit 63. */ + CORE_ADDR shadow_stack_val_cleared = (value & (~mask)); + + /* Compute the previous/old SSP. The shadow stack grows downwards. To + compute the previous shadow stack pointer, we need to increment + SSP. */ + CORE_ADDR prev_ssp + = ssp + gdbarch_shadow_stack_element_size_aligned (gdbarch); + + /* We incremented SSP by one element to compute PREV_SSP before. In + case SSP points to the first element of the shadow stack, PREV_SSP + must point to the bottom of the shadow stack (RANGE.SECOND), but not + beyond that address. */ + gdb_assert (prev_ssp > range.first && prev_ssp <= range.second); + + if (shadow_stack_val_cleared == prev_ssp) + { + /* Assign the current gdbarch to the new shadow stack frame. Since + the token is no PC value, do not assign a SAL object. */ + return std::optional + ({ssp, value, level, gdbarch, {}, + ssp_frame_type::non_return_frame, + {""}, + ssp_unwind_stop_reason::no_error}); + } + + return {}; +} + static void amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch, int num_disp_step_buffers) @@ -2023,6 +2079,9 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_top_addr_empty_shadow_stack (gdbarch, amd64_linux_top_addr_empty_shadow_stack); + + set_gdbarch_is_no_return_shadow_stack_address + (gdbarch, amd64_linux_is_no_return_shadow_stack_address); } static void diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp new file mode 100644 index 00000000000..21373dc07f3 --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp @@ -0,0 +1,49 @@ +# 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 shadow stack backtrace for signal handling on linux. + +require allow_ssp_tests {istarget "*-*-linux*"} + +standard_testfile amd64-shadow-stack-signal.c + +save_vars { ::env(GLIBC_TUNABLES) } { + + append_environment GLIBC_TUNABLES "glibc.cpu.hwcaps" "SHSTK" + + if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} \ + {debug additional_flags="-fcf-protection=return"}] } { + return + } + + if { ![runto_main] } { + return + } + + gdb_breakpoint "handler" + gdb_test "continue" \ + ".*Program received signal SIGUSR1, User defined signal 1.*" \ + "continue until signal" + gdb_continue_to_breakpoint "continue to breakpoint in handler" + + # Test shadow stack backtrace including . + gdb_test "bt -shadow" \ + [multi_line \ + "#0\[ \t\]*$hex in \[^\r\n\]+" \ + "#1\[ \t\]*" \ + "#2\[ \t\]*$hex in \[^\r\n\]+" \ + ".*" ] \ + "test shadow stack backtrace for signal handling." +} diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c b/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c new file mode 100644 index 00000000000..c726e05b224 --- /dev/null +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-signal.c @@ -0,0 +1,31 @@ +/* This testcase is part of GDB, the GNU debugger. + + 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 . */ + +#include + +void +handler (int signo) +{ +} + +int +main (void) +{ + signal (SIGUSR1, handler); + raise (SIGUSR1); + return 0; +} From patchwork Wed Jul 8 14:36:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138754 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id EF9C04BA2E09 for ; Wed, 8 Jul 2026 14:40:56 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org EF9C04BA2E09 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=dq49TkIw X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.16]) by sourceware.org (Postfix) with ESMTPS id BD26E4BA23D3 for ; Wed, 8 Jul 2026 14:38:12 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org BD26E4BA23D3 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 BD26E4BA23D3 Authentication-Results: sourceware.org; arc=none smtp.remote-ip=198.175.65.16 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521493; cv=none; b=rnK3euWvSCZCzxWyx2m2DLbNs4++KL5Jn0qJHF9CCgqBXIX6tWT4w/gY6We4Yg4/SaYbxpaxlXi2RiRnRL0SQbJ8zLhnjufn3m9Y/HM/kvndmVQ/OTw1SakSuZxZ58vN7ctO+6JKLVtSgzbP7kEt2iwpGtNK0Sbr7lVYaO01xAM= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521493; c=relaxed/simple; bh=RWwjWOiTK0sNrmROPF3Nf8KaHXqVo21vRaYowYKnl48=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=mIzS86mGYoE/R4FIU7t8DsJxz1eRrxMQv2yAOcrv1ZfEcT2t7Z3nSQ3q1wbLX6CVNXt4i57iv45M7yOoVnATjYZgeJys4A5wdcuxFN9+FGFRdt2yjb1zXNGKD+LRadzKzWVkrJg7JubbMTtAvn3Wz2AOyjUZ8iUhzyW09RB6Fyc= 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=dq49TkIw DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org BD26E4BA23D3 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521493; x=1815057493; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=RWwjWOiTK0sNrmROPF3Nf8KaHXqVo21vRaYowYKnl48=; b=dq49TkIwL+Tv2M+nDQxBDA9VdxUCuAwedCSmrwOhvQYQdlz81EF5QnvL pDFklPpYd01/o2+U1qdUqFcZ1Qjt9zCGGxFEOLw9T1Ee2uULdZyGdz064 pVpM+ZKz2+03wq76pj02hJInty1kVOYBliRfDAqkGysdc2VFMwkeUgmNv Zs3B04yHOiosn0RPwiU5MLqXn6UWDKWGnDQSUOnUIqYST2giaFLb3J99X DMqMmfEA0Fqaynmx9fkQ5wAeXURZzHu7GwjNuW6WYNdJoTp1sbLeezGQC xQenJpV32AjtFMmWL2n2FX9Wjyevj7LnhoRCAfAlIVFIwvae0eoH4ZKMK w==; X-CSE-ConnectionGUID: 5mDh/Z+PSU+ppb2LcakPFw== X-CSE-MsgGUID: WcLzKa/hRGGR2vwVbdonwQ== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84375547" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84375547" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by orvoesa108.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:12 -0700 X-CSE-ConnectionGUID: OBUctuQfTxCiwNFdRRkZoA== X-CSE-MsgGUID: K2vWCgQxTdu0ql7rFilWxg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="249864418" Received: from gkldtt-dev-004.igk.intel.com (HELO localhost) ([10.123.221.202]) by fmviesa006-auth.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 08 Jul 2026 07:38:11 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 11/13] gdb: Enable inferior calls in the shadow stack backtrace. Date: Wed, 8 Jul 2026 14:36:37 +0000 Message-Id: <20260708143639.2214689-12-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org Similar to the normal backtrace display in the shadow stack backtrace for inferior function calls. Example for a nested inferior call: ~~~ (gdb) bt -shadow \#0 \#1 \#2 0x000000000040045a in call1 at /tmp/amd64-shadow-stack.c:27 \#3 0x0000000000400466 in main at /tmp/amd64-shadow-stack.c:38 (gdb) bt \#0 call2 () at /tmp/amd64-shadow-stack.c:21 \#1 \#2 call2 () at /tmp/amd64-shadow-stack.c:21 \#3 \#4 call2 () at /tmp/amd64-shadow-stack.c:21 \#5 0x000000000040045a in call1 () at /tmp/amd64-shadow-stack.c:27 \#6 0x0000000000400466 in main () at /tmp/amd64-shadow-stack.c:38 ~~~ Reviewed-by: Thiago Jung Bauermann --- gdb/shadow-stack.c | 36 +++++++++++++++++++ gdb/shadow-stack.h | 4 +++ .../gdb.arch/amd64-shadow-stack-cmds.exp | 24 +++++++++++++ 3 files changed, 64 insertions(+) diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c index dbd8959753d..94062034bd3 100644 --- a/gdb/shadow-stack.c +++ b/gdb/shadow-stack.c @@ -215,6 +215,29 @@ find_pc_funname (CORE_ADDR pc) return funname; } +/* Check if the shadow stack frame's value VAL is an inferior call + return address. */ + +static bool +is_infcall_return_address (const CORE_ADDR val) +{ + const int inf_thread_num = inferior_thread ()->global_num; + + for (breakpoint &b : all_breakpoints ()) + { + if (b.type != bp_call_dummy + || b.thread != inf_thread_num + || b.frame_id.code_addr != val) + continue; + + for (bp_location &bl : b.locations ()) + if (bl.pspace == current_program_space) + return true; + } + + return false; +} + /* 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, @@ -242,6 +265,8 @@ do_print_shadow_stack_frame_info gdb_assert (frame.non_return_description.has_value ()); str = frame.non_return_description.value (); } + else if (frame.type == ssp_frame_type::dummy_frame) + str = ""; else gdb_assert_not_reached ("Invalid shadow stack frame type."); @@ -430,6 +455,17 @@ get_shadow_stack_frame_info return *no_return_frame; } + if (is_infcall_return_address (value)) + { + /* If VALUE belongs to a dummy call breakpoint get_sal_arch won't + return a valid gdbarch and we can assign the current gdbarch + here. We also don't show any SAL information, so we can set it + to an empty optional. */ + return std::optional + ({ssp, value, level, fallback_arch, {}, ssp_frame_type::dummy_frame, + {}, ssp_unwind_stop_reason::no_error}); + } + /* At this point, we know that SSP points to VALUE which is a return address. In contrast to find_frame_sal which is used for the normal backtrace command, VALUE always points at the return instruction diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index 4278620c70c..225fd3996cd 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -76,6 +76,10 @@ enum class ssp_frame_type This frame type is configured in the target specific implementation of is_no_return_shadow_stack_address. */ non_return_frame, + + /* A fake frame, created by GDB when performing an inferior function + call. */ + dummy_frame, }; /* Information of a shadow stack frame belonging to a shadow stack element diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp index 0347313b647..3259fd2f918 100644 --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-cmds.exp @@ -167,6 +167,30 @@ save_vars { ::env(GLIBC_TUNABLES) } { gdb_continue_to_end } + with_test_prefix "test inferior call shadow stack backtrace" { + restart_and_run_infcall_call2 + gdb_test "bt -shadow" \ + [multi_line \ + "#0\[ \t\]*" \ + "#1\[ \t\]*$hex in \[^\r\n\]+" \ + "#2\[ \t\]*$hex in \[^\r\n\]+" ] \ + "Test shadow stack backtrace for inferior calls." + + set inside_infcall_str \ + "The program being debugged stopped while in a function called from GDB" + gdb_test "call (int) call2()" \ + "Breakpoint \[0-9\]*, call2.*$inside_infcall_str.*" \ + "Execute an inferior call inside an inferior call." + + gdb_test "bt -shadow" \ + [multi_line \ + "#0\[ \t\]*" \ + "#1\[ \t\]*" \ + "#2\[ \t\]*$hex in \[^\r\n\]+" \ + "#3\[ \t\]*$hex in \[^\r\n\]+" ] \ + "Test shadow stack backtrace for nested inferior calls." + } + clean_restart ${::testfile} if { ![runto_main] } { return From patchwork Wed Jul 8 14:36:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138751 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 0FCB94BA2E10 for ; Wed, 8 Jul 2026 14:39:54 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0FCB94BA2E10 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=XaC1WmsR X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.19]) by sourceware.org (Postfix) with ESMTPS id 463B84BA2E2F for ; Wed, 8 Jul 2026 14:38:18 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 463B84BA2E2F 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 463B84BA2E2F 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=1783521498; cv=none; b=u2NDRHt7cSSUiFlELxBa20I57ROdwBshpUqs4eDSq5Bu9ViMLJ2/qiDZvTmIXeHtZqnvibsOOsQibyFvOA6wnpeYJO3Jr+tpoCJsCI3yj2H2YFVTwL/m1C//iK4LJssMZX/QBJF6nh3GkZkao4/DW67Q1br2hrnwDbk9cQxLK6o= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1783521498; c=relaxed/simple; bh=s0/M6P9LKmY47YZ/V5BtKBQ7DamXxw+G7rx46PkkVuY=; h=DKIM-Signature:From:To:Subject:Date:Message-Id:MIME-Version; b=nCOfQWsUjNGWGd4rVH/hIf949eUnJtmhXKHgmplNndE0B//g5DLPrcFHvUuRcA0TDkehRgHZ3pEwmq08xWYV81GXPFoLgfSEDzonpokdzO/qY6+5wvG+S/PHBvQFHej6U7IOhkMmrHNEyuxmUAcUcv4yeVkSbVZEqlm18J+m9eI= 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=XaC1WmsR DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 463B84BA2E2F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1783521498; x=1815057498; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=s0/M6P9LKmY47YZ/V5BtKBQ7DamXxw+G7rx46PkkVuY=; b=XaC1WmsRF5yM8ul3SaU485NbLZh+sI03P7F1+CdCvkSkRNbN3fIQIr2i KcftubWoK1YlP/hnyZke/jD5IwOukHJ3iah6gPrcLdGTESUYjV5qzmD0Z QZ7ASba/gW4KO3GdDzhjdOwl210sSc0fP0b/nQk+3ffcOqWpsBtoSk+iV 3tTWM0h//1Po5ZaTU+5nnnJo7hfMz0K81bCYN4Rga3KkaTFUiA4HExZQR CdpZM/Q/TeS1KdaBut/qb+ejPI+PQ3BbRWN9xh7YDD8bhggxMnSFvEOCa HNYaVTTtqt5uUD5VOcQ1TKNBUVq0/uAhUaXYmorq6PMphLUSRZXHTi1O7 w==; X-CSE-ConnectionGUID: C3AlyMwATL+PGH5dO/buPg== X-CSE-MsgGUID: th7F+WlSSSWOHQCuS2YksQ== X-IronPort-AV: E=McAfee;i="6800,10657,11841"; a="84158941" X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="84158941" 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:17 -0700 X-CSE-ConnectionGUID: SlW5cvNHSPuS0c8S061L7w== X-CSE-MsgGUID: /r7NrbwxTIO1buyRt8cQBg== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.25,153,1779174000"; d="scan'208";a="250945915" 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:16 -0700 From: Christina Schimpe To: gdb-patches@sourceware.org Cc: tom@tromey.com, thiago.bauermann@linaro.org Subject: [PATCH v4 12/13] gdb: Enable signal trampolines in the shadow stack backtrace. Date: Wed, 8 Jul 2026 14:36:38 +0000 Message-Id: <20260708143639.2214689-13-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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@sourceware.org Similar to the normal backtrace we now print "" in the shadow stack backtrace, too. ~~~ (gdb) bt -shadow /#0 /#1 /#2 0x00007ffff7c4527e in __GI_raise at ../sysdeps/posix/raise.c:26 /#3 0x0000555555555175 in main at /tmp/gdb.arch/amd64-shadow-stack-signal.c:29 (gdb) bt /#0 handler (signo=10) at /tmp/amd64-shadow-stack-signal.c:23 /#1 /#2 __pthread_kill_implementation (no_tid=0, signo=10, threadid=) at ./nptl/pthread_kill.c:44 /#3 __pthread_kill_internal (signo=10, threadid=) at ./nptl/pthread_kill.c:78 /#4 __GI___pthread_kill (threadid=, signo=signo@entry=10) at ./nptl/pthread_kill.c:89 /#5 0x00007ffff7c4527e in __GI_raise (sig=10) at ../sysdeps/posix/raise.c:26 /#6 0x0000555555555175 in main () at /tmp/amd64-shadow-stack-signal.c:29 ~~~ Reviewed-by: Thiago Jung Bauermann --- gdb/amd64-linux-tdep.c | 36 +++++++++++-------- gdb/gdbarch-gen.c | 32 +++++++++++++++++ gdb/gdbarch-gen.h | 11 ++++++ gdb/gdbarch_components.py | 13 +++++++ gdb/shadow-stack.c | 11 ++++++ gdb/shadow-stack.h | 5 +++ .../amd64-shadow-stack-backtrace-signal.exp | 2 +- 7 files changed, 95 insertions(+), 15 deletions(-) diff --git a/gdb/amd64-linux-tdep.c b/gdb/amd64-linux-tdep.c index d98415b8989..ab3b65ed4b8 100644 --- a/gdb/amd64-linux-tdep.c +++ b/gdb/amd64-linux-tdep.c @@ -147,12 +147,10 @@ static const gdb_byte amd64_x32_linux_sigtramp_code[] = the routine. Otherwise, return 0. */ static CORE_ADDR -amd64_linux_sigtramp_start (const frame_info_ptr &this_frame) +amd64_linux_sigtramp_start (gdbarch *gdbarch, CORE_ADDR pc) { - struct gdbarch *gdbarch; const gdb_byte *sigtramp_code; - CORE_ADDR pc = get_frame_pc (this_frame); - gdb_byte buf[LINUX_SIGTRAMP_LEN]; + std::array buf {}; /* We only recognize a signal trampoline if PC is at the start of one of the two instructions. We optimize for finding the PC at @@ -161,7 +159,7 @@ amd64_linux_sigtramp_start (const frame_info_ptr &this_frame) PC is not at the start of the instruction sequence, there will be a few trailing readable bytes on the stack. */ - if (!safe_frame_unwind_memory (this_frame, pc, buf)) + if (target_read_memory (pc, buf.data (), buf.size ()) != 0) return 0; if (buf[0] != LINUX_SIGTRAMP_INSN0) @@ -170,28 +168,25 @@ amd64_linux_sigtramp_start (const frame_info_ptr &this_frame) return 0; pc -= LINUX_SIGTRAMP_OFFSET1; - if (!safe_frame_unwind_memory (this_frame, pc, buf)) + if (target_read_memory (pc, buf.data (), buf.size ()) != 0) return 0; } - gdbarch = get_frame_arch (this_frame); if (gdbarch_ptr_bit (gdbarch) == 32) sigtramp_code = amd64_x32_linux_sigtramp_code; else sigtramp_code = amd64_linux_sigtramp_code; - if (memcmp (buf, sigtramp_code, LINUX_SIGTRAMP_LEN) != 0) + if (memcmp (buf.data (), sigtramp_code, LINUX_SIGTRAMP_LEN) != 0) return 0; return pc; } -/* Return whether THIS_FRAME corresponds to a GNU/Linux sigtramp - routine. */ +/* Return whether PC is pointing to a GNU/Linux sigtramp routine. */ static int -amd64_linux_sigtramp_p (const frame_info_ptr &this_frame) +amd64_linux_is_sigtramp_pc (gdbarch* gdbarch, const CORE_ADDR pc) { - CORE_ADDR pc = get_frame_pc (this_frame); const char *name; find_pc_partial_function (pc, &name, NULL, NULL); @@ -203,11 +198,22 @@ amd64_linux_sigtramp_p (const frame_info_ptr &this_frame) __sigaction, or __libc_sigaction (all aliases to the same function). */ if (name == NULL || strstr (name, "sigaction") != NULL) - return (amd64_linux_sigtramp_start (this_frame) != 0); + return (amd64_linux_sigtramp_start (gdbarch, pc) != 0); return (streq ("__restore_rt", name)); } +/* Return whether THIS_FRAME corresponds to a GNU/Linux sigtramp + routine. */ + +static int +amd64_linux_sigtramp_frame_p (const frame_info_ptr &this_frame) +{ + gdbarch *gdbarch = get_frame_arch (this_frame); + CORE_ADDR pc = get_frame_pc (this_frame); + return amd64_linux_is_sigtramp_pc (gdbarch, pc); +} + /* Offset to struct sigcontext in ucontext, from . */ #define AMD64_LINUX_UCONTEXT_SIGCONTEXT_OFFSET 40 @@ -2024,7 +2030,7 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch, linux_init_abi (info, gdbarch, num_disp_step_buffers); - tdep->sigtramp_p = amd64_linux_sigtramp_p; + tdep->sigtramp_p = amd64_linux_sigtramp_frame_p; tdep->sigcontext_addr = amd64_linux_sigcontext_addr; tdep->sc_reg_offset = amd64_linux_sc_reg_offset; tdep->sc_num_regs = ARRAY_SIZE (amd64_linux_sc_reg_offset); @@ -2082,6 +2088,8 @@ amd64_linux_init_abi_common (struct gdbarch_info info, struct gdbarch *gdbarch, set_gdbarch_is_no_return_shadow_stack_address (gdbarch, amd64_linux_is_no_return_shadow_stack_address); + + set_gdbarch_is_sigtramp_pc (gdbarch, amd64_linux_is_sigtramp_pc); } static void diff --git a/gdb/gdbarch-gen.c b/gdb/gdbarch-gen.c index e93afff34b1..c0b882563c1 100644 --- a/gdb/gdbarch-gen.c +++ b/gdb/gdbarch-gen.c @@ -257,6 +257,7 @@ struct gdbarch gdbarch_top_addr_empty_shadow_stack_ftype *top_addr_empty_shadow_stack = nullptr; int shadow_stack_element_size_aligned = 8; gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address = nullptr; + gdbarch_is_sigtramp_pc_ftype *is_sigtramp_pc = nullptr; }; /* Create a new ``struct gdbarch'' based on information provided by @@ -521,6 +522,7 @@ verify_gdbarch (struct gdbarch *gdbarch) /* Skip verify of top_addr_empty_shadow_stack, has predicate. */ /* Skip verify of shadow_stack_element_size_aligned, invalid_p == 0. */ /* Skip verify of is_no_return_shadow_stack_address, has predicate. */ + /* Skip verify of is_sigtramp_pc, has predicate. */ if (!log.empty ()) internal_error (_("verify_gdbarch: the following are invalid ...%s"), log.c_str ()); @@ -1365,6 +1367,12 @@ gdbarch_dump (struct gdbarch *gdbarch, struct ui_file *file) gdb_printf (file, "gdbarch_dump: is_no_return_shadow_stack_address = <%s>\n", host_address_to_string (gdbarch->is_no_return_shadow_stack_address)); + gdb_printf (file, + "gdbarch_dump: gdbarch_is_sigtramp_pc_p() = %d\n", + gdbarch_is_sigtramp_pc_p (gdbarch)); + gdb_printf (file, + "gdbarch_dump: is_sigtramp_pc = <%s>\n", + host_address_to_string (gdbarch->is_sigtramp_pc)); if (gdbarch->dump_tdep != nullptr) gdbarch->dump_tdep (gdbarch, file); } @@ -5394,3 +5402,27 @@ set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, { gdbarch->is_no_return_shadow_stack_address = is_no_return_shadow_stack_address; } + +bool +gdbarch_is_sigtramp_pc_p (struct gdbarch *gdbarch) +{ + gdb_assert (gdbarch != nullptr); + return gdbarch->is_sigtramp_pc != nullptr; +} + +int +gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, const CORE_ADDR pc) +{ + gdb_assert (gdbarch != nullptr); + gdb_assert (gdbarch->is_sigtramp_pc != nullptr); + if (gdbarch_debug >= 2) + gdb_printf (gdb_stdlog, "gdbarch_is_sigtramp_pc called\n"); + return gdbarch->is_sigtramp_pc (gdbarch, pc); +} + +void +set_gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, + gdbarch_is_sigtramp_pc_ftype is_sigtramp_pc) +{ + gdbarch->is_sigtramp_pc = is_sigtramp_pc; +} diff --git a/gdb/gdbarch-gen.h b/gdb/gdbarch-gen.h index 791324777dd..31500ee104d 100644 --- a/gdb/gdbarch-gen.h +++ b/gdb/gdbarch-gen.h @@ -1808,3 +1808,14 @@ bool gdbarch_is_no_return_shadow_stack_address_p (struct gdbarch *gdbarch); using gdbarch_is_no_return_shadow_stack_address_ftype = std::optional (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level); std::optional gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, const CORE_ADDR ssp, const CORE_ADDR value, const unsigned long level); void set_gdbarch_is_no_return_shadow_stack_address (struct gdbarch *gdbarch, gdbarch_is_no_return_shadow_stack_address_ftype *is_no_return_shadow_stack_address); + +/* Return whether PC is pointing to a sigtramp routine. This is needed in + addition to tdep->sigtramp_p, which requires a frame, because some callers + (e.g. the shadow stack backtrace command) only have a return address + available. */ + +bool gdbarch_is_sigtramp_pc_p (struct gdbarch *gdbarch); + +using gdbarch_is_sigtramp_pc_ftype = int (struct gdbarch *gdbarch, const CORE_ADDR pc); +int gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, const CORE_ADDR pc); +void set_gdbarch_is_sigtramp_pc (struct gdbarch *gdbarch, gdbarch_is_sigtramp_pc_ftype *is_sigtramp_pc); diff --git a/gdb/gdbarch_components.py b/gdb/gdbarch_components.py index 0435b1b5eca..fce705c33a1 100644 --- a/gdb/gdbarch_components.py +++ b/gdb/gdbarch_components.py @@ -2864,3 +2864,16 @@ backtrace. Otherwise, return an empty optional. ], predicate=True, ) + +Method( + comment=""" +Return whether PC is pointing to a sigtramp routine. This is needed in +addition to tdep->sigtramp_p, which requires a frame, because some callers +(e.g. the shadow stack backtrace command) only have a return address +available. +""", + type="int", + name="is_sigtramp_pc", + params=[("const CORE_ADDR", "pc")], + predicate=True, +) diff --git a/gdb/shadow-stack.c b/gdb/shadow-stack.c index 94062034bd3..6515d4b182d 100644 --- a/gdb/shadow-stack.c +++ b/gdb/shadow-stack.c @@ -267,6 +267,8 @@ do_print_shadow_stack_frame_info } else if (frame.type == ssp_frame_type::dummy_frame) str = ""; + else if (frame.type == ssp_frame_type::sigtramp_frame) + str = ""; else gdb_assert_not_reached ("Invalid shadow stack frame type."); @@ -480,6 +482,15 @@ get_shadow_stack_frame_info if (sal_arch == nullptr) sal_arch = fallback_arch; + if (gdbarch_is_sigtramp_pc_p (sal_arch) + && gdbarch_is_sigtramp_pc (sal_arch, value)) + { + return std::optional + ({ssp, value, level, sal_arch, sal, + ssp_frame_type::sigtramp_frame, {}, + ssp_unwind_stop_reason::no_error}); + } + return std::optional ({ssp, value, level, sal_arch, sal, ssp_frame_type::normal_frame, {}, diff --git a/gdb/shadow-stack.h b/gdb/shadow-stack.h index 225fd3996cd..d4b0d53acf3 100644 --- a/gdb/shadow-stack.h +++ b/gdb/shadow-stack.h @@ -80,6 +80,11 @@ enum class ssp_frame_type /* A fake frame, created by GDB when performing an inferior function call. */ dummy_frame, + + /* A shadow stack frame containing a return address which is in a signal + handler, similar to the SIGTRAMP_FRAME frame type in + frame.h:frame_type. */ + sigtramp_frame, }; /* Information of a shadow stack frame belonging to a shadow stack element diff --git a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp index 21373dc07f3..34559f0bd04 100644 --- a/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp +++ b/gdb/testsuite/gdb.arch/amd64-shadow-stack-backtrace-signal.exp @@ -41,7 +41,7 @@ save_vars { ::env(GLIBC_TUNABLES) } { # Test shadow stack backtrace including . gdb_test "bt -shadow" \ [multi_line \ - "#0\[ \t\]*$hex in \[^\r\n\]+" \ + "#0\[ \t\]*" \ "#1\[ \t\]*" \ "#2\[ \t\]*$hex in \[^\r\n\]+" \ ".*" ] \ From patchwork Wed Jul 8 14:36:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Schimpe, Christina" X-Patchwork-Id: 138750 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 2C53D4BA23CB for ; Wed, 8 Jul 2026 14:39:41 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 2C53D4BA23CB 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 X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org 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 X-Spam-Status: No, score=-10.3 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, SPF_HELO_NONE, TXREP, T_SPF_TEMPERROR shortcircuit=no autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org 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~patchwork=sourceware.org@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 +}