From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from simark.ca by simark.ca with LMTP id oYJ+DrvgtGqL3zsAWB0awg (envelope-from ) for ; Thu, 24 Sep 2026 04:35:07 -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=BtAHf2jK; dkim-atps=neutral Received: by simark.ca (Postfix, from userid 112) id 3025F1E04E; Thu, 24 Sep 2026 04:35:07 -0400 (EDT) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on simark.ca X-Spam-Level: X-Spam-Status: No, score=-3.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,RCVD_IN_VALIDITY_CERTIFIED_BLOCKED, RCVD_IN_VALIDITY_RPBL_BLOCKED,RCVD_IN_VALIDITY_SAFE_BLOCKED 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 B5F1B1E04E for ; Thu, 24 Sep 2026 04:35:02 -0400 (EDT) Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id BA8284BB3BC7 for ; Thu, 24 Sep 2026 08:35:01 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org BA8284BB3BC7 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=BtAHf2jK Received: from mgamail.intel.com (mgamail.intel.com [192.198.163.7]) by sourceware.org (Postfix) with ESMTPS id 957294BB3BCD for ; Thu, 24 Sep 2026 08:34:02 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 957294BB3BCD 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 957294BB3BCD Authentication-Results: sourceware.org; arc=none smtp.remote-ip=192.198.163.7 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790238843; cv=none; b=njeEe0Q0bZL1BRieo8UvGeaz99BI6DNyVRjNrJUtch4HtzDFFRmc2VW9LwTb7josIDBvZf7ifU1zCXno4QNhn6BpVYtFfdR0sMLDyG70/0ix9sLrraLols5oGTDlpxtw3umvGjpQ/IuHNrKkJwTk2fOYhLZE3/U3/aiK1l6D+aA= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1790238843; c=relaxed/simple; bh=3xqICGTiuqnmlvgxwXp8N1diDcvuJeeaS98la/wCD4g=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=LjGv78LOTg32Qkk2k7WX/KrwfnvXbZVre8FnCZK2cKmm/3QW+RCIYupEqx/Pp/kIP6C6e3WA/bVVP/Vqd6mtideCiik0ePxCw61Wip4sdEuHfNnykHZ6+1FUUD3a0c5HKxcWwBmW0kPQ4aKwlx+rDQenGL7istK3HTpPYXoNJgY= 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=BtAHf2jK DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 957294BB3BCD DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1790238843; x=1821774843; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=3xqICGTiuqnmlvgxwXp8N1diDcvuJeeaS98la/wCD4g=; b=BtAHf2jKZ2aoKuLm0/uL6SUpr1YsWkuRwhJmuYkFkmAmu29PsTCMosxL qn1Ffj4YnKqVYt8dCFTu2gEFZ3qr9753c642nJWjIBNE/CNUNa6oeCX8s pExbGMfJzz752F/YxAB2kbuRsFOfsKQ6vuxzLs8tLJzVUxSDVI2fi/bUL RPqX94Rs3l2pXmMf564Gysc4ia+BKhhsDc8XK6JrUJnXXK30OCxSeeMT8 2brEkP05sHuD8VAkWpHvndZD7t9cgGVkCE272Ys2Srxj/ECjWahfXSlXE 3G1dWvVnCdj84ud34ssXEoZlIyzHatAFKu0APxtU5LrN7zvjTMXOX17hQ Q==; X-CSE-ConnectionGUID: yNO7J0bbTjOlDpA0O6oOtQ== X-CSE-MsgGUID: hKVAnZGlSNSNh63cbggpnQ== X-IronPort-AV: E=McAfee;i="6800,10657,11914"; a="116524363" X-IronPort-AV: E=Sophos;i="6.27,120,1787036400"; d="scan'208";a="116524363" Received: from fmviesa006.fm.intel.com ([10.60.135.146]) by fmvoesa101.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Sep 2026 01:34:02 -0700 X-CSE-ConnectionGUID: PtBHfNm4SomSrVl/ePvhBQ== X-CSE-MsgGUID: 2yKDfF9bTKy5mqwsP3wg6g== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.27,120,1787036400"; d="scan'208";a="272558945" 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; 24 Sep 2026 01:34:00 -0700 From: Christina Joos To: gdb-patches@sourceware.org Cc: thiago.bauermann@linaro.org, tom@tromey.com, luis.machado.foss@gmail.com Subject: [PATCH v5 01/13] gdb: Generalize handling of the shadow stack pointer. Date: Thu, 24 Sep 2026 10:32:58 +0200 Message-ID: <20260924083311.1961530-2-christina.joos@intel.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260924083311.1961530-1-christina.joos@intel.com> References: <20260924083311.1961530-1-christina.joos@intel.com> MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit X-BeenThere: gdb-patches@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gdb-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: gdb-patches-bounces~public-inbox=simark.ca@sourceware.org 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 | 54 +++---------- 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(+), 227 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 d1574ec2d2c..60b33ce6838 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 950ad4f6aae..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,19 +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) -{ - bool gcs_is_enabled; - - (void) gdbarch_get_shadow_stack_pointer (gdbarch, regcache, gcs_is_enabled); - if (gcs_is_enabled) - aarch64_push_gcs_entry (regcache, new_addr); -} - /* Implement the "push_dummy_call" gdbarch method. */ static CORE_ADDR @@ -3678,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); } } @@ -3846,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); @@ -4820,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); @@ -4882,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 6008003466c..f5f85ba1f63 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 6eda8693d58..6611a3c3bc5 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. */ @@ -1734,22 +1740,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 @@ -1758,3 +1765,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 d8b2d114909..dc7080ae556 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. @@ -2760,24 +2771,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 @@ -2789,3 +2799,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 4c40f6cb06e..14b76557253 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" @@ -1493,8 +1494,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 1d525fd94c5..275581c5d05 100644 --- a/gdb/linux-tdep.c +++ b/gdb/linux-tdep.c @@ -3232,8 +3232,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; } @@ -3283,6 +3286,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.53.0 ________________________________________ Intel Deutschland GmbH Registered Address: Dornacher Strasse 1, 85622 Feldkirchen, Germany Tel: +49 (89) 99143-0 www.intel.de Managing Directors: Candice Moore, Jeffrey Schneiderman, Ramachandran Sitaraman Chairperson of the Supervisory Board: Sonja Pierer Registered Seat: Munich Commercial Register B: Amtsgericht Munich HRB 186928 This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). Any review or distribution by others is strictly prohibited. If you are not the intended recipient, please contact the sender and delete all copies.