From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id WR9kC+BgTmqwcCwAWB0awg (envelope-from ) for ; Wed, 08 Jul 2026 10:38:24 -0400 Authentication-Results: simark.ca; dkim=pass (2048-bit key; unprotected) header.d=intel.com header.i=@intel.com header.a=rsa-sha256 header.s=Intel header.b=iw/stjGg; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 283351E024; Wed, 08 Jul 2026 10:38:24 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-6.4 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_00, DKIMWL_WL_HIGH,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=ham autolearn_force=no version=4.0.1 Received: from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by simark.ca (Postfix) with ESMTPS id D47111E024 for ; Wed, 08 Jul 2026 10:38:21 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 034344BA2E16 for ; Wed, 8 Jul 2026 14:38:21 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 034344BA2E16 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 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 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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 */ -- 2.34.1 Intel Deutschland GmbH Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany Tel: +49 89 991 430, www.intel.de Managing Directors: Harry Demas, Jeffrey Schneiderman, Yin Chong Sorrell Chairperson of the Supervisory Board: Nicole Lau Registered Seat: Munich Commercial Register: Amtsgericht Muenchen HRB 186928