From patchwork Sun Jun 22 01:18:07 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Thiago Jung Bauermann X-Patchwork-Id: 899062 Delivered-To: patch@linaro.org Received: by 2002:adf:e506:0:b0:3a6:d909:26ce with SMTP id j6csp367791wrm; Sat, 21 Jun 2025 20:54:39 -0700 (PDT) X-Forwarded-Encrypted: i=3; AJvYcCX8JV+pPuHkjGbI+U8cXz2icD7l8Frcegt4KpryU3QomjYC/N03yds6E4e+tRoMNaBamfuYow==@linaro.org X-Google-Smtp-Source: AGHT+IHbteN8SrKZa1Lj0fOBQsvc2NfceLZVDfdMcJIWgwlrh9eTAe8ShZMpfMY2CefwIZcaFr9a X-Received: by 2002:a05:622a:120b:b0:4a7:a8a:efd with SMTP id d75a77b69052e-4a77a27afbamr127794891cf.44.1750564478737; Sat, 21 Jun 2025 20:54:38 -0700 (PDT) ARC-Seal: i=2; a=rsa-sha256; t=1750564478; cv=pass; d=google.com; s=arc-20240605; b=Vz2V3Ao5byLx9To0qILOJyWCb1xv8wd+Y8+IC8I5r4OtDx/Sf6wXUcXOvrDGrVt3e3 BOcHFJ76qcS+wErrMIpymrqeElA7g5ohF6CJX3Lm9eSz4pbWrM7GE9A5S9wSYUrzUjgo bFARbzVpN6c1Zr4vICvHQPGG94/Oweou8qKszBwLA1h79+mAdT/1f5DuvYw3bX2ixGjI 5UhAvaBXpE1kxLMLSRciVlrw294QBM0ZYmalCdQgPlMMDRLj6mrA4VhRCl0zvo0ZEWky 7enFF81So4jAkgEjpCkFoLyS64AfM1u2g1Qg8KNgMkrqdqH1V4EfyC3SxcRJvULegPf5 M/Ng== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=errors-to:list-subscribe:list-help:list-post:list-archive :list-unsubscribe:list-id:precedence:content-transfer-encoding :mime-version:references:in-reply-to:message-id:date:subject:cc:to :from:dkim-signature:dkim-filter:arc-filter:dmarc-filter :delivered-to:dkim-filter; bh=dt4x1XRbSkfj6VM0jRQ/Z6KCzQ73SHBmjAiWZHxOm04=; fh=v0Swrqm/HGzmFfs2Lyn4KGEK39u4NVWMm4qGXeDZh/s=; b=KS10Vo2vHI4ny853FOxdmTLGJkkh78t/ijAonWxADnI7gZE7oaDd8oX8KGF5SWVeoB BzOlBIGuu3DcFhN4IN1wa2jmBfAerkom8DDFZ8ncGZjHAJupdCGSYvox392OxdjhRF6g 6q5NsRSmxYlBv1sfb6ZaYmiRtrV87ZkgfyLXZyE4diW+d7PKaeh1GPMLXcOEO8NvB7pZ pZ0RfWw4hHrwv/9eDmhafHKUt2Ef5kvWu/uNz9GaYSDcbH6n08CBpOTLwmhkalFJywBh XUDz+Sl6rd9SYCsL7TRGfDZLBAzctMlnMOYEopO0NlMV63lAn/sE3yQ+PRjHoYGGd8hT BZnQ==; dara=google.com ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b="BQP4k2/P"; arc=pass (i=1); spf=pass (google.com: domain of gdb-patches-bounces~patch=linaro.org@sourceware.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gdb-patches-bounces~patch=linaro.org@sourceware.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from server2.sourceware.org (server2.sourceware.org. [8.43.85.97]) by mx.google.com with ESMTPS id d75a77b69052e-4a779d4ea5dsi52260941cf.40.2025.06.21.20.54.38 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Jun 2025 20:54:38 -0700 (PDT) Received-SPF: pass (google.com: domain of gdb-patches-bounces~patch=linaro.org@sourceware.org designates 8.43.85.97 as permitted sender) client-ip=8.43.85.97; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b="BQP4k2/P"; arc=pass (i=1); spf=pass (google.com: domain of gdb-patches-bounces~patch=linaro.org@sourceware.org designates 8.43.85.97 as permitted sender) smtp.mailfrom="gdb-patches-bounces~patch=linaro.org@sourceware.org"; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id A891B3982035 for ; Sun, 22 Jun 2025 02:35:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org A891B3982035 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=BQP4k2/P X-Original-To: gdb-patches@sourceware.org Delivered-To: gdb-patches@sourceware.org Received: from mail-pj1-x1034.google.com (mail-pj1-x1034.google.com [IPv6:2607:f8b0:4864:20::1034]) by sourceware.org (Postfix) with ESMTPS id E974E3818ED4 for ; Sun, 22 Jun 2025 01:18:30 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org E974E3818ED4 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org E974E3818ED4 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::1034 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1750555111; cv=none; b=sVjr7Yc6AioIXPZUbuua4eoet5c/PBNPbmqW0GOGvyg4iCe8KT/pdPNgn5pzgiMqvM38sSKw0xGLW2RgzdJ6zCrvyudEXxTEdwkzLNS+ON9lVldGDqnL7Mb/XLJ5cxIQlBVzj280MFoLNtx/XZkMP0UR8lSkV1rSJ9MEcXu4/jQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1750555111; c=relaxed/simple; bh=kV7LCKwGOZIIkJOcrm36cNHoCtvFt5qWgiW28BHRdWQ=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=MxdiBU6jAHceSbTcfnBlB4KrnQyBL2vjNJXOmM7UWcK2Z+H6Sb+84RaGDF3EHlISVe4zX9jP86x633Of0kNthYWqxur4Rzke5eK8qkSlkMvN7GTPqU2nnfqOZzjzwGMjoMf88SpCkvdaaatGEGLL26iuCdHT4bD1HfsPlhY7pFI= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E974E3818ED4 Received: by mail-pj1-x1034.google.com with SMTP id 98e67ed59e1d1-3138d31e40aso2860095a91.1 for ; Sat, 21 Jun 2025 18:18:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1750555110; x=1751159910; darn=sourceware.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=dt4x1XRbSkfj6VM0jRQ/Z6KCzQ73SHBmjAiWZHxOm04=; b=BQP4k2/P7oUld6kpOlmSylwOZXBnsO4rDGLF1cdbdZ+jHHSZFP4i57wzb80dxYYOg+ e/e0T0WhEVEVoOqfJmKFJ8uY+wmucBvcmp8SRpQTsUoJEIMETUqRTufsZ1xDXgGPLmyu GwtxOBqmy7LP+oY+qxCoAfJhZbWoKJVIvCV2Kds5x83JGoaQ1oPpHJ3iK+vXXPwpAEQO ZcksCrxjjraLx37n5j+mBU/1AZgyIZaCpPjveycQXxJBxZMacY/Mv50QmT0H3FYDRsTE +Ztb0KyBOs90kCGWqYN8p5z/bxvae8T8QENOFfj189OkUe8onPH0KpyeXwcde/q3F4Bw Vnrw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1750555110; x=1751159910; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=dt4x1XRbSkfj6VM0jRQ/Z6KCzQ73SHBmjAiWZHxOm04=; b=Jqqtq04xUk30JCRwkk7SETLvS+s16q6AWx2Y8Km2GX3LjObvOI3hTU6XWSeuSTE0HG 3/96Fv2DKhwGZCNMzuX9YAuY2c4l0ycUT5g3JgrpLA7HgkRtH2mKyo/pFookiwihcq52 JMLDxTbSOrAK9ebMopKOVf4I7py8f4xWsAhGZoT3Ybn9V180ukvixw9QHcpWYRFf9fZ7 VO+2nirUtolgyYdhYA+NUrS7+h/9USCqpl8pYPXxrNZ3DtgeIB3873mQ2DBZ6cyU50/O kaJGcjchsCdafdwnJBCXfDtr0tC+ZMa3ZsWk6uGp65NS+e2g+nbPKI2oWyerwRmQ8kPs md3Q== X-Gm-Message-State: AOJu0YwYvCNYO+IUUEAZY3c0MpYDX7GvpWLUOHI3+Oj+PBvQi3+5PR/z vrY18AcLM699+ph1Yr1lH5lZSrO6mzTy0O+uVW47knyT/K6F6EJWcDWc+Sh2XT5D75uzOl7Lg7Y hJkWj X-Gm-Gg: ASbGncuHxktkDZLjmEU3hWSTwHZ2gajLCtncL23Eh+5FWPVhGOZ9CKAL2A1eBisnHMa n+c1TgRJsZRUmefOIsUfHiE6x0ltisKo3YA0M+YxunCbA3LTokKxMglQ5vY/g/rHvmBEJ6gvKzw g/Mfuw7py3UorxIyMKEpUz7Zp3utL48+Ds/i8K5kzWdi95G0g4ifY+6QGXRhMS+BorkJ+hGM76C RdzXsWpv1D9h8vjKD7RVTxRw2Yei3kEM/IJSTKowyjEenf2EOMGVmVBVCu0UrRwwYNCtH3wvl3Q 3BCkJOknFNspvjzYeTk9xjP616sUFn8MrDzXNcOHlfQDpJGf7oCm/Pam0cvHFXvx4/hqI3Za X-Received: by 2002:a17:90b:2248:b0:311:eb85:96ea with SMTP id 98e67ed59e1d1-3159d63c82amr13765356a91.9.1750555109375; Sat, 21 Jun 2025 18:18:29 -0700 (PDT) Received: from localhost ([2804:14d:7e39:88d6:79ae:7a30:10f3:dfed]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3158a318733sm7268383a91.38.2025.06.21.18.18.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 21 Jun 2025 18:18:28 -0700 (PDT) From: Thiago Jung Bauermann To: gdb-patches@sourceware.org Cc: Eli Zaretskii , "Schimpe, Christina" , Luis Machado Subject: [PATCH v3 5/9] GDB, gdbserver: aarch64-linux: Initial Guarded Control Stack support Date: Sat, 21 Jun 2025 22:18:07 -0300 Message-ID: <20250622011811.371949-6-thiago.bauermann@linaro.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250622011811.371949-1-thiago.bauermann@linaro.org> References: <20250622011811.371949-1-thiago.bauermann@linaro.org> MIME-Version: 1.0 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~patch=linaro.org@sourceware.org Add the org.gnu.gdb.aarch64.gcs feature with the GCSPR register, and the org.gnu.gdb.aarch64.gcs.linux feature with "registers" to represent the Linux kernel ptrace and prctl knobs that enable and lock specific GCS functionality. This code supports GCS only in Linux userspace applications, so the GCSPR that is exposed is the one at EL0. Also, support for calling inferior functions is enabled by adding an implementation for the shadow_stack_push gdbarch method. If for some reason a target description contains the org.gnu.gdb.aarch64.gcs feature but not the org.gnu.gdb.aarch64.gcs.linux feature then GCS support is disabled and GDB continues the debugging session. Features that need GCS support (for example, calling inferior functions) will not work and the inferior will get a segmentation fault signal instead. There's a testcase for this scenario but it only checks the native debugging case, even though in practice this problem would only occur in remote debugging with a broken stub or gdbserver. I tested manually with a gdbserver hacked to send a broken target description and it worked as described. Testcases gdb.arch/aarch64-gcs.exp, gdb.arch/aarch64-gcs-core.exp and gdb.arch/aarch64-gcs-wrong-tdesc.exp are included to cover the added functionality. The change in the core_find procedure allows aarch64-gcs-core.exp to recover the output from the crashed program. It's needed because the test program in this testcase prints to stdout the value of the GCSPR right before crashing, and the testcase needs it to check whether GDB got it right. --- Changes since v2: - Change aarch64_linux_init_abi to warn if tdep->has_gcs () but !tdep->has_gcs_linux () and disable GCS for the debugging session (suggested by Luis). - Add gdb.arch/aarch64-gcs-wrong-tdesc.exp to test the scenario above. Changes since v1: - Mention EL0 register in commit message (suggested by Christina). - Mention enabling support for calling inferior functions (suggested by Christina). - Don't use "struct" keyword when declaring variables (suggested by Christina). - Fix some whitespace issues (spotted by Christina). - Add aarch64_gdbarch_tdep::has_gcs_linux and use it in aarch64-linux tdep and native code since it implies tdep->has_gcs () to address Christina's review comment. - Add check in aarch64_linux_init_abi to make sure the target description has both the GCS and Linux GCS features to address Christina's review comment. - Add i18n for a couple of error strings (suggested by Christina). - In aarch64_gdbarch_init, return a bit earlier if the Linux GCS feature is present but GCS isn't (suggested by Christina). - Also dump tdep->gcs_linux_reg_base in aarch64_dump_tdep (suggested by Luis). - Moved documentation changes to its own patch. - Moved aarch64-gcs.exp and aarch64-gcs-core.exp to this patch. - Document change to core_find proc in lib/gdb.exp (suggested by Luis). gdb/aarch64-linux-nat.c | 79 ++++++++ gdb/aarch64-linux-tdep.c | 30 ++++ gdb/aarch64-tdep.c | 103 +++++++++++ gdb/aarch64-tdep.h | 21 +++ gdb/arch/aarch64-gcs-linux.h | 44 +++++ gdb/arch/aarch64.c | 8 + gdb/arch/aarch64.h | 10 +- gdb/features/Makefile | 2 + gdb/features/aarch64-gcs-linux.c | 21 +++ gdb/features/aarch64-gcs-linux.xml | 18 ++ gdb/features/aarch64-gcs.c | 14 ++ gdb/features/aarch64-gcs.xml | 11 ++ gdb/testsuite/gdb.arch/aarch64-gcs-core.c | 124 +++++++++++++ gdb/testsuite/gdb.arch/aarch64-gcs-core.exp | 105 +++++++++++ .../aarch64-gcs-tdesc-without-linux.xml | 65 +++++++ .../gdb.arch/aarch64-gcs-wrong-tdesc.c | 26 +++ .../gdb.arch/aarch64-gcs-wrong-tdesc.exp | 48 +++++ gdb/testsuite/gdb.arch/aarch64-gcs.c | 168 ++++++++++++++++++ gdb/testsuite/gdb.arch/aarch64-gcs.exp | 73 ++++++++ gdb/testsuite/lib/gdb.exp | 68 ++++++- gdbserver/linux-aarch64-low.cc | 46 +++++ 21 files changed, 1081 insertions(+), 3 deletions(-) create mode 100644 gdb/arch/aarch64-gcs-linux.h create mode 100644 gdb/features/aarch64-gcs-linux.c create mode 100644 gdb/features/aarch64-gcs-linux.xml create mode 100644 gdb/features/aarch64-gcs.c create mode 100644 gdb/features/aarch64-gcs.xml create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-core.c create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-core.exp create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.c create mode 100644 gdb/testsuite/gdb.arch/aarch64-gcs.exp diff --git a/gdb/aarch64-linux-nat.c b/gdb/aarch64-linux-nat.c index d7869f42e825..093206baff5c 100644 --- a/gdb/aarch64-linux-nat.c +++ b/gdb/aarch64-linux-nat.c @@ -51,6 +51,7 @@ #include "gdb_proc_service.h" #include "arch-utils.h" +#include "arch/aarch64-gcs-linux.h" #include "arch/aarch64-mte-linux.h" #include "nat/aarch64-mte-linux-ptrace.h" @@ -542,6 +543,67 @@ store_tlsregs_to_thread (struct regcache *regcache) perror_with_name (_("unable to store TLS register")); } +/* Fill GDB's register array with the GCS register values from + the current thread. */ + +static void +fetch_gcsregs_from_thread (regcache *regcache) +{ + aarch64_gdbarch_tdep *tdep + = gdbarch_tdep (regcache->arch ()); + + gdb_assert (tdep->gcs_reg_base != -1); + gdb_assert (tdep->gcs_linux_reg_base != -1); + + user_gcs user_gcs; + iovec iovec; + + iovec.iov_base = &user_gcs; + iovec.iov_len = sizeof (user_gcs); + + int tid = get_ptrace_pid (regcache->ptid ()); + if (ptrace (PTRACE_GETREGSET, tid, NT_ARM_GCS, &iovec) != 0) + perror_with_name (_("unable to fetch GCS registers")); + + regcache->raw_supply (tdep->gcs_reg_base, &user_gcs.gcspr_el0); + regcache->raw_supply (tdep->gcs_linux_reg_base, &user_gcs.features_enabled); + regcache->raw_supply (tdep->gcs_linux_reg_base + 1, + &user_gcs.features_locked); +} + +/* Store to the current thread the valid GCS register set in the GDB's + register array. */ + +static void +store_gcsregs_to_thread (regcache *regcache) +{ + aarch64_gdbarch_tdep *tdep + = gdbarch_tdep (regcache->arch ()); + + gdb_assert (tdep->gcs_reg_base != -1); + gdb_assert (tdep->gcs_linux_reg_base != -1); + + if (REG_VALID != regcache->get_register_status (tdep->gcs_reg_base) + || REG_VALID != regcache->get_register_status (tdep->gcs_linux_reg_base) + || REG_VALID + != regcache->get_register_status (tdep->gcs_linux_reg_base + 1)) + return; + + user_gcs user_gcs; + regcache->raw_collect (tdep->gcs_reg_base, &user_gcs.gcspr_el0); + regcache->raw_collect (tdep->gcs_linux_reg_base, &user_gcs.features_enabled); + regcache->raw_collect (tdep->gcs_linux_reg_base + 1, + &user_gcs.features_locked); + + iovec iovec; + iovec.iov_base = &user_gcs; + iovec.iov_len = sizeof (user_gcs); + + int tid = get_ptrace_pid (regcache->ptid ()); + if (ptrace (PTRACE_SETREGSET, tid, NT_ARM_GCS, &iovec) != 0) + perror_with_name (_("unable to store GCS registers")); +} + /* The AArch64 version of the "fetch_registers" target_ops method. Fetch REGNO from the target and place the result into REGCACHE. */ @@ -577,6 +639,9 @@ aarch64_fetch_registers (struct regcache *regcache, int regno) if (tdep->has_sme2 ()) fetch_zt_from_thread (regcache); + + if (tdep->has_gcs_linux ()) + fetch_gcsregs_from_thread (regcache); } /* General purpose register? */ else if (regno < AARCH64_V0_REGNUM) @@ -609,6 +674,11 @@ aarch64_fetch_registers (struct regcache *regcache, int regno) && regno >= tdep->tls_regnum_base && regno < tdep->tls_regnum_base + tdep->tls_register_count) fetch_tlsregs_from_thread (regcache); + /* GCS register? */ + else if (tdep->has_gcs_linux () + && (regno == tdep->gcs_reg_base || regno == tdep->gcs_linux_reg_base + || regno == tdep->gcs_linux_reg_base + 1)) + fetch_gcsregs_from_thread (regcache); } /* A version of the "fetch_registers" target_ops method used when running @@ -680,6 +750,9 @@ aarch64_store_registers (struct regcache *regcache, int regno) if (tdep->has_sme2 ()) store_zt_to_thread (regcache); + + if (tdep->has_gcs_linux ()) + store_gcsregs_to_thread (regcache); } /* General purpose register? */ else if (regno < AARCH64_V0_REGNUM) @@ -706,6 +779,11 @@ aarch64_store_registers (struct regcache *regcache, int regno) && regno >= tdep->tls_regnum_base && regno < tdep->tls_regnum_base + tdep->tls_register_count) store_tlsregs_to_thread (regcache); + /* GCS register? */ + else if (tdep->has_gcs_linux () + && (regno == tdep->gcs_reg_base || regno == tdep->gcs_linux_reg_base + || regno == tdep->gcs_linux_reg_base + 1)) + store_gcsregs_to_thread (regcache); /* PAuth registers are read-only. */ } @@ -881,6 +959,7 @@ aarch64_linux_nat_target::read_description () active or not. */ features.vq = aarch64_sve_get_vq (tid); features.pauth = hwcap & AARCH64_HWCAP_PACA; + features.gcs = features.gcs_linux = hwcap & HWCAP_GCS; features.mte = hwcap2 & HWCAP2_MTE; features.tls = aarch64_tls_register_count (tid); /* SME feature check. */ diff --git a/gdb/aarch64-linux-tdep.c b/gdb/aarch64-linux-tdep.c index a194ac809c23..fa5b265014a4 100644 --- a/gdb/aarch64-linux-tdep.c +++ b/gdb/aarch64-linux-tdep.c @@ -50,6 +50,7 @@ #include "record-full.h" #include "linux-record.h" +#include "arch/aarch64-gcs-linux.h" #include "arch/aarch64-mte.h" #include "arch/aarch64-mte-linux.h" #include "arch/aarch64-scalable-linux.h" @@ -1604,6 +1605,27 @@ aarch64_linux_iterate_over_regset_sections (struct gdbarch *gdbarch, cb (".reg-aarch-tls", sizeof_tls_regset, sizeof_tls_regset, &aarch64_linux_tls_regset, "TLS register", cb_data); } + + /* Handle GCS registers. */ + if (tdep->has_gcs_linux ()) + { + /* Create this on the fly in order to handle the variable regnums. */ + const regcache_map_entry gcs_regmap[] = + { + { 1, tdep->gcs_linux_reg_base, 8 }, /* features_enabled */ + { 1, tdep->gcs_linux_reg_base + 1, 8 }, /* features_locked */ + { 1, tdep->gcs_reg_base, 8 }, /* GCSPR */ + { 0 } + }; + + const regset aarch64_linux_gcs_regset = + { + gcs_regmap, regcache_supply_regset, regcache_collect_regset + }; + + cb (".reg-aarch-gcs", sizeof (user_gcs), sizeof (user_gcs), + &aarch64_linux_gcs_regset, "GCS registers", cb_data); + } } /* Implement the "core_read_description" gdbarch method. */ @@ -1628,6 +1650,7 @@ aarch64_linux_core_read_description (struct gdbarch *gdbarch, length. */ features.vq = aarch64_linux_core_read_vq_from_sections (gdbarch, abfd); features.pauth = hwcap & AARCH64_HWCAP_PACA; + features.gcs = features.gcs_linux = hwcap & HWCAP_GCS; features.mte = hwcap2 & HWCAP2_MTE; /* Handle the TLS section. */ @@ -2765,6 +2788,13 @@ aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch) NULL }; aarch64_gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + if (tdep->has_gcs () && !tdep->has_gcs_linux ()) + { + warning (_("Incomplete GCS support in the target: missing Linux part." + " GCS feature disabled.")); + tdep->gcs_reg_base = -1; + } + tdep->lowest_pc = 0x8000; linux_init_abi (info, gdbarch, 1); diff --git a/gdb/aarch64-tdep.c b/gdb/aarch64-tdep.c index 8d54e59f332a..0e9e6644dd0a 100644 --- a/gdb/aarch64-tdep.c +++ b/gdb/aarch64-tdep.c @@ -159,6 +159,18 @@ static const char *const aarch64_mte_register_names[] = "tag_ctl" }; +static const char *const aarch64_gcs_register_names[] = { + /* Guarded Control Stack Pointer Register. */ + "gcspr" +}; + +static const char *const aarch64_gcs_linux_register_names[] = { + /* Field in struct user_gcs. */ + "gcs_features_enabled", + /* Field in struct user_gcs. */ + "gcs_features_locked", +}; + static int aarch64_stack_frame_destroyed_p (struct gdbarch *, CORE_ADDR); /* AArch64 prologue cache structure. */ @@ -1875,6 +1887,39 @@ 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; + + enum 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); +} + +/* 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 @@ -4046,6 +4091,14 @@ aarch64_features_from_target_desc (const struct target_desc *tdesc) features.sme2 = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.sme2") != nullptr); + /* Check for the GCS feature. */ + features.gcs = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs") + != nullptr); + + /* Check for the GCS Linux feature. */ + features.gcs_linux = (tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux") + != nullptr); + return features; } @@ -4590,6 +4643,46 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) int first_w_regnum = num_pseudo_regs; num_pseudo_regs += 31; + const tdesc_feature *feature_gcs + = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs"); + int first_gcs_regnum = -1; + /* Add the GCS registers. */ + if (feature_gcs != nullptr) + { + first_gcs_regnum = num_regs; + /* Validate the descriptor provides the mandatory GCS registers and + allocate their numbers. */ + for (i = 0; i < ARRAY_SIZE (aarch64_gcs_register_names); i++) + valid_p &= tdesc_numbered_register (feature_gcs, tdesc_data.get (), + first_gcs_regnum + i, + aarch64_gcs_register_names[i]); + + num_regs += i; + } + + if (!valid_p) + return nullptr; + + const tdesc_feature *feature_gcs_linux + = tdesc_find_feature (tdesc, "org.gnu.gdb.aarch64.gcs.linux"); + int first_gcs_linux_regnum = -1; + /* Add the GCS Linux registers. */ + if (feature_gcs_linux != nullptr && feature_gcs == nullptr) + /* This feature depends on the GCS feature. */ + return nullptr; + else if (feature_gcs_linux != nullptr) + { + first_gcs_linux_regnum = num_regs; + /* Validate the descriptor provides the mandatory GCS Linux registers + and allocate their numbers. */ + for (i = 0; i < ARRAY_SIZE (aarch64_gcs_linux_register_names); i++) + valid_p &= tdesc_numbered_register (feature_gcs_linux, tdesc_data.get (), + first_gcs_linux_regnum + i, + aarch64_gcs_linux_register_names[i]); + + num_regs += i; + } + if (!valid_p) return nullptr; @@ -4611,6 +4704,8 @@ aarch64_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) tdep->mte_reg_base = first_mte_regnum; tdep->tls_regnum_base = first_tls_regnum; tdep->tls_register_count = tls_register_count; + tdep->gcs_reg_base = first_gcs_regnum; + tdep->gcs_linux_reg_base = first_gcs_linux_regnum; /* Set the SME register set details. The pseudo-registers will be adjusted later. */ @@ -4733,6 +4828,9 @@ 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 @@ -4905,6 +5003,11 @@ aarch64_dump_tdep (struct gdbarch *gdbarch, struct ui_file *file) pulongest (tdep->sme_tile_pseudo_base)); gdb_printf (file, _("aarch64_dump_tdep: sme_svq = %s\n"), pulongest (tdep->sme_svq)); + + gdb_printf (file, _("aarch64_dump_tdep: gcs_reg_base = %d\n"), + tdep->gcs_reg_base); + gdb_printf (file, _("aarch64_dump_tdep: gcs_linux_reg_base = %d\n"), + tdep->gcs_linux_reg_base); } #if GDB_SELF_TEST diff --git a/gdb/aarch64-tdep.h b/gdb/aarch64-tdep.h index 3b8dcc26545b..54ca641a35a3 100644 --- a/gdb/aarch64-tdep.h +++ b/gdb/aarch64-tdep.h @@ -182,6 +182,27 @@ struct aarch64_gdbarch_tdep : gdbarch_tdep_base { return sme2_zt0_regnum > 0; } + + /* First GCS register. This is -1 if no GCS registers are available. */ + int gcs_reg_base = -1; + + /* First GCS Linux-specific register. This is -1 if no GCS Linux feature is + available. */ + int gcs_linux_reg_base = -1; + + /* Returns true if the target supports GCS. */ + bool + has_gcs () const + { + return gcs_reg_base != -1; + } + + /* Returns true if the target supports the Linux GCS feature. */ + bool + has_gcs_linux () const + { + return gcs_linux_reg_base != -1; + } }; const target_desc *aarch64_read_description (const aarch64_features &features); diff --git a/gdb/arch/aarch64-gcs-linux.h b/gdb/arch/aarch64-gcs-linux.h new file mode 100644 index 000000000000..9366caa7289a --- /dev/null +++ b/gdb/arch/aarch64-gcs-linux.h @@ -0,0 +1,44 @@ +/* Common Linux target-dependent definitions for AArch64 GCS + + Copyright (C) 2025 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 ARCH_AARCH64_GCS_LINUX_H +#define ARCH_AARCH64_GCS_LINUX_H + +#include + +/* Feature check for Guarded Control Stack. */ +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +/* Make sure we only define these if the kernel header doesn't. */ +#ifndef GCS_MAGIC + +/* GCS state (NT_ARM_GCS). */ + +struct user_gcs +{ + uint64_t features_enabled; + uint64_t features_locked; + uint64_t gcspr_el0; +}; + +#endif /* GCS_MAGIC */ + +#endif /* ARCH_AARCH64_GCS_LINUX_H */ diff --git a/gdb/arch/aarch64.c b/gdb/arch/aarch64.c index 3e1ca0547340..dff2bc16003a 100644 --- a/gdb/arch/aarch64.c +++ b/gdb/arch/aarch64.c @@ -26,6 +26,8 @@ #include "../features/aarch64-sme.c" #include "../features/aarch64-sme2.c" #include "../features/aarch64-tls.c" +#include "../features/aarch64-gcs.c" +#include "../features/aarch64-gcs-linux.c" /* See arch/aarch64.h. */ @@ -65,6 +67,12 @@ aarch64_create_target_description (const aarch64_features &features) if (features.sme2) regnum = create_feature_aarch64_sme2 (tdesc.get (), regnum); + if (features.gcs) + regnum = create_feature_aarch64_gcs (tdesc.get (), regnum); + + if (features.gcs_linux) + regnum = create_feature_aarch64_gcs_linux (tdesc.get (), regnum); + return tdesc.release (); } diff --git a/gdb/arch/aarch64.h b/gdb/arch/aarch64.h index ee18b74b80f5..679d845df74e 100644 --- a/gdb/arch/aarch64.h +++ b/gdb/arch/aarch64.h @@ -51,6 +51,12 @@ struct aarch64_features /* Whether SME2 is supported. */ bool sme2 = false; + + /* Whether Guarded Control Stack is supported. */ + bool gcs = false; + + /* Whether Guarded Control Stack Linux features are supported. */ + bool gcs_linux = false; }; inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs) @@ -60,7 +66,9 @@ inline bool operator==(const aarch64_features &lhs, const aarch64_features &rhs) && lhs.mte == rhs.mte && lhs.tls == rhs.tls && lhs.svq == rhs.svq - && lhs.sme2 == rhs.sme2; + && lhs.sme2 == rhs.sme2 + && lhs.gcs == rhs.gcs + && lhs.gcs_linux == rhs.gcs_linux; } namespace std diff --git a/gdb/features/Makefile b/gdb/features/Makefile index 7a8c7999733a..92fd8085d478 100644 --- a/gdb/features/Makefile +++ b/gdb/features/Makefile @@ -203,6 +203,8 @@ FEATURE_XMLFILES = aarch64-core.xml \ aarch64-fpu.xml \ aarch64-pauth.xml \ aarch64-mte.xml \ + aarch64-gcs.xml \ + aarch64-gcs-linux.xml \ arc/v1-core.xml \ arc/v1-aux.xml \ arc/v2-core.xml \ diff --git a/gdb/features/aarch64-gcs-linux.c b/gdb/features/aarch64-gcs-linux.c new file mode 100644 index 000000000000..6b0d25b4518c --- /dev/null +++ b/gdb/features/aarch64-gcs-linux.c @@ -0,0 +1,21 @@ +/* THIS FILE IS GENERATED. -*- buffer-read-only: t -*- vi:set ro: + Original: aarch64-gcs-linux.xml */ + +#include "gdbsupport/tdesc.h" + +static int +create_feature_aarch64_gcs_linux (struct target_desc *result, long regnum) +{ + struct tdesc_feature *feature; + + feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs.linux"); + tdesc_type_with_fields *type_with_fields; + type_with_fields = tdesc_create_flags (feature, "features_flags", 8); + tdesc_add_flag (type_with_fields, 0, "PR_SHADOW_STACK_ENABLE"); + tdesc_add_flag (type_with_fields, 1, "PR_SHADOW_STACK_WRITE"); + tdesc_add_flag (type_with_fields, 2, "PR_SHADOW_STACK_PUSH"); + + tdesc_create_reg (feature, "gcs_features_enabled", regnum++, 1, "system", 64, "features_flags"); + tdesc_create_reg (feature, "gcs_features_locked", regnum++, 1, "system", 64, "features_flags"); + return regnum; +} diff --git a/gdb/features/aarch64-gcs-linux.xml b/gdb/features/aarch64-gcs-linux.xml new file mode 100644 index 000000000000..8d9d2ceb9260 --- /dev/null +++ b/gdb/features/aarch64-gcs-linux.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/gdb/features/aarch64-gcs.c b/gdb/features/aarch64-gcs.c new file mode 100644 index 000000000000..2b2caf29cc8c --- /dev/null +++ b/gdb/features/aarch64-gcs.c @@ -0,0 +1,14 @@ +/* THIS FILE IS GENERATED. -*- buffer-read-only: t -*- vi:set ro: + Original: aarch64-gcs.xml */ + +#include "gdbsupport/tdesc.h" + +static int +create_feature_aarch64_gcs (struct target_desc *result, long regnum) +{ + struct tdesc_feature *feature; + + feature = tdesc_create_feature (result, "org.gnu.gdb.aarch64.gcs"); + tdesc_create_reg (feature, "gcspr", regnum++, 1, "system", 64, "data_ptr"); + return regnum; +} diff --git a/gdb/features/aarch64-gcs.xml b/gdb/features/aarch64-gcs.xml new file mode 100644 index 000000000000..bbee5e001722 --- /dev/null +++ b/gdb/features/aarch64-gcs.xml @@ -0,0 +1,11 @@ + + + + + + + diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.c b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c new file mode 100644 index 000000000000..87ee7842f71b --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.c @@ -0,0 +1,124 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 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 +#include +#include +#include +#include + +/* Feature check for Guarded Control Stack. */ +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#define PR_SET_SHADOW_STACK_STATUS 75 +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#endif + +/* We need to use a macro to call prctl because after GCS is enabled, it's not + possible to return from the function which enabled it. This is because the + return address of the calling function isn't on the GCS. */ +#define my_syscall2(num, arg1, arg2) \ + ({ \ + register long _num __asm__("x8") = (num); \ + register long _arg1 __asm__("x0") = (long)(arg1); \ + register long _arg2 __asm__("x1") = (long)(arg2); \ + register long _arg3 __asm__("x2") = 0; \ + register long _arg4 __asm__("x3") = 0; \ + register long _arg5 __asm__("x4") = 0; \ + \ + __asm__ volatile ("svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc"); \ + _arg1; \ + }) + +#define get_gcspr(void) \ + ({ \ + unsigned long *gcspr; \ + \ + /* Get GCSPR_EL0. */ \ + asm volatile ("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ + \ + gcspr; \ + }) + +/* Corrupt the return address to see if GDB will report a SIGSEGV with the + expected + $_siginfo.si_code. */ +static void __attribute__ ((noinline)) +function (unsigned long *gcspr) +{ + /* x30 holds the return address. */ + register long x30 __asm__("x30") __attribute__ ((unused)); + + /* Print GCSPR to stdout so that the testcase can capture it. */ + printf ("%p\n", get_gcspr ()); + fflush (stdout); + + /* Cause a GCS exception. */ + x30 = 0xbadc0ffee; + __asm__ volatile ("ret\n"); +} + +int +main (void) +{ + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) + { + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); + return EXIT_FAILURE; + } + + /* Force shadow stacks on, our tests *should* be fine with or + without libc support and with or without this having ended + up tagged for GCS and enabled by the dynamic linker. We + can't use the libc prctl() function since we can't return + from enabling the stack. Also lock GCS if not already + locked so we can test behaviour when it's locked. */ + unsigned long gcs_mode; + int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to read GCS state: %d\n", ret); + return EXIT_FAILURE; + } + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) + { + gcs_mode = PR_SHADOW_STACK_ENABLE; + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to configure GCS: %d\n", ret); + return EXIT_FAILURE; + } + } + + unsigned long *gcspr = get_gcspr (); + + /* Pass gscpr to function just so it's used for something. */ + function (gcspr); /* Break here. */ + + /* Avoid returning, in case libc doesn't understand GCS. */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp new file mode 100644 index 000000000000..ef7e507407a4 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-core.exp @@ -0,0 +1,105 @@ +# Copyright 2025 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 reading and writing the core dump of a binary that uses a Guarded +# Control Stack. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" $testfile $srcfile] } { + return +} + +set linespec ${srcfile}:[gdb_get_line_number "Break here"] + +if ![runto $linespec] { + return +} + +# Continue until a crash. The line with the hex number is optional because +# it's printed by the test program, and doesn't appear in the Expect buffer +# when testing a remote target. +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "($hex\r\n)?" \ + "Program received signal SIGSEGV, Segmentation fault\\." \ + "function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ + {.*__asm__ volatile \("ret\\n"\);}] \ + "continue to SIGSEGV" + +set gcspr_in_gcore [get_valueof "/x" "\$gcspr" "*unknown*"] + +# Generate the gcore core file. +set gcore_filename [standard_output_file "${testfile}.gcore"] +set gcore_generated [gdb_gcore_cmd "$gcore_filename" "generate gcore file"] + +# Obtain an OS-generated core file. Save test program output to +# ${binfile}.out. +set core_filename [core_find $binfile {} {} "${binfile}.out"] +set core_generated [expr {$core_filename != ""}] +set os_core_name "${binfile}.core" +remote_exec build "mv $core_filename $os_core_name" +set core_filename $os_core_name + +# At this point we have a couple of core files, the gcore one generated by +# GDB and the one generated by the operating system. Make sure GDB can +# read both correctly. + +proc check_core_file {core_filename saved_gcspr} { + global decimal hex + + # Load the core file. + if [gdb_test "core $core_filename" \ + [multi_line \ + "Core was generated by .*\\." \ + "Program terminated with signal SIGSEGV, Segmentation fault\\." \ + "#0 function \\(gcspr=$hex\\) at .*aarch64-gcs-core.c:$decimal" \ + "$decimal.*__asm__ volatile \\(\"ret\\\\n\"\\);"] \ + "load core file"] { + return -1 + } + + # Check the value of GCSPR in the core file. + gdb_test "print/x \$gcspr" "\\$\[0-9\]+ = $saved_gcspr" \ + "gcspr contents from core file" +} + +if {$gcore_generated} { + clean_restart $binfile + + with_test_prefix "gcore corefile" { + check_core_file $gcore_filename $gcspr_in_gcore + } +} else { + fail "gcore corefile not generated" +} + +if {$core_generated} { + clean_restart $binfile + + with_test_prefix "OS corefile" { + # Read GCSPR value from saved output of the test program. + set out_id [open ${binfile}.out "r"] + set gcspr_in_core [gets $out_id] + + close $out_id + check_core_file $core_filename $gcspr_in_core + } +} else { + untested "OS corefile not generated" +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml b/gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml new file mode 100644 index 000000000000..056ab58b82a7 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-tdesc-without-linux.xml @@ -0,0 +1,65 @@ + + + + aarch64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c new file mode 100644 index 000000000000..10cf749f3e66 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.c @@ -0,0 +1,26 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 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 + +int +main (void) +{ + printf ("Hello, world!\n"); + + return 0; +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp new file mode 100644 index 000000000000..f0508cdc247a --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs-wrong-tdesc.exp @@ -0,0 +1,48 @@ +# Copyright 2025 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 that GDB complains when given a target description with the GCS feature +# but not the GCS Linux feature. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return +} + +set xml_path "${srcdir}/${subdir}/aarch64-gcs-tdesc-without-linux.xml" + +gdb_test "set tdesc filename ${xml_path}" \ + "warning: Incomplete GCS support in the target: missing Linux part. GCS feature disabled." \ + "warn about incomplete GCS support" + +# We can't test a debugging session on a remote target because with the +# wrong tdesc, GDB expects a g packet reply with the wrong size. +if {[gdb_protocol_is_remote]} { + return +} + +if ![runto_main] { + return +} + +gdb_test "print \$gcspr" " = " "GCSPR is unavailable" + +# Now check that we can continue the debugging session normally. +gdb_test "next" + +gdb_continue_to_end diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.c b/gdb/testsuite/gdb.arch/aarch64-gcs.c new file mode 100644 index 000000000000..1e00a010a9f1 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.c @@ -0,0 +1,168 @@ +/* This test program is part of GDB, the GNU debugger. + + Copyright 2025 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 +#include +#include +#include +#include +#include + +/* Feature check for Guarded Control Stack. */ +#ifndef HWCAP_GCS +#define HWCAP_GCS (1UL << 32) +#endif + +#ifndef PR_GET_SHADOW_STACK_STATUS +#define PR_GET_SHADOW_STACK_STATUS 74 +#define PR_SET_SHADOW_STACK_STATUS 75 +#define PR_SHADOW_STACK_ENABLE (1UL << 0) +#endif + +/* We need to use a macro to call prctl because after GCS is enabled, it's not + possible to return from the function which enabled it. This is because the + return address of the calling function isn't on the GCS. */ +#define my_syscall2(num, arg1, arg2) \ + ({ \ + register long _num __asm__("x8") = (num); \ + register long _arg1 __asm__("x0") = (long)(arg1); \ + register long _arg2 __asm__("x1") = (long)(arg2); \ + register long _arg3 __asm__("x2") = 0; \ + register long _arg4 __asm__("x3") = 0; \ + register long _arg5 __asm__("x4") = 0; \ + \ + __asm__ volatile ("svc #0\n" \ + : "=r"(_arg1) \ + : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), \ + "r"(_arg5), "r"(_num) \ + : "memory", "cc"); \ + _arg1; \ + }) + +#define get_gcspr(void) \ + ({ \ + unsigned long *gcspr; \ + \ + /* Get GCSPR_EL0. */ \ + asm volatile ("mrs %0, S3_3_C2_C5_1" : "=r"(gcspr) : : "cc"); \ + \ + gcspr; \ + }) + +static unsigned long *handler_gcspr = 0; + +static void +handler (int sig) +{ + handler_gcspr = get_gcspr (); +} + +static int __attribute__ ((unused)) +called_from_gdb (int val) +{ + return val + 1; +} + +/* Corrupt the return address to see if GDB will report a SIGSEGV with the expected + $_siginfo.si_code. */ +static void __attribute__ ((noinline)) +normal_function2 (void) +{ + /* x30 holds the return address. */ + register unsigned long x30 __asm__("x30") __attribute__ ((unused)); + + /* Cause a GCS exception. */ + x30 = 0xbadc0ffee; + __asm__ volatile ("ret\n"); +} + +static inline void __attribute__ ((__always_inline__)) +inline_function2 (void) +{ + normal_function2 (); +} + +/* Corrupt the return address to see if GDB will report a GCS error in this + function's frame . */ +static void __attribute__ ((noinline)) +normal_function1 (void) +{ + /* x30 holds the return address. */ + register unsigned long x30 __asm__ ("x30") __attribute__ ((unused)); + x30 = 0xbadc0ffee; + inline_function2 (); +} + +static inline void __attribute__ ((__always_inline__)) +inline_function1 (void) +{ + normal_function1 (); +} + +int +main (void) +{ + if (!(getauxval (AT_HWCAP) & HWCAP_GCS)) + { + fprintf (stderr, "GCS support not found in AT_HWCAP\n"); + return EXIT_FAILURE; + } + + /* Force shadow stacks on, our tests *should* be fine with or + without libc support and with or without this having ended + up tagged for GCS and enabled by the dynamic linker. We + can't use the libc prctl() function since we can't return + from enabling the stack. Also lock GCS if not already + locked so we can test behaviour when it's locked. */ + unsigned long gcs_mode; + int ret = my_syscall2 (__NR_prctl, PR_GET_SHADOW_STACK_STATUS, &gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to read GCS state: %d\n", ret); + return EXIT_FAILURE; + } + + if (!(gcs_mode & PR_SHADOW_STACK_ENABLE)) + { + gcs_mode = PR_SHADOW_STACK_ENABLE; + ret = my_syscall2 (__NR_prctl, PR_SET_SHADOW_STACK_STATUS, gcs_mode); + if (ret) + { + fprintf (stderr, "Failed to configure GCS: %d\n", ret); + return EXIT_FAILURE; + } + } + + /* This is used by GDB. */ + __attribute__((unused)) unsigned long *gcspr = get_gcspr (); + + struct sigaction act = { 0 }; + + act.sa_handler = &handler; /* Break here. */ + if (sigaction (SIGUSR1, &act, NULL) == -1) + { + perror ("sigaction"); + exit (EXIT_FAILURE); + } + + raise (SIGUSR1); + + inline_function1 (); + + /* Avoid returning, in case libc doesn't understand GCS. */ + exit (EXIT_SUCCESS); +} diff --git a/gdb/testsuite/gdb.arch/aarch64-gcs.exp b/gdb/testsuite/gdb.arch/aarch64-gcs.exp new file mode 100644 index 000000000000..ea70cc6ef2e9 --- /dev/null +++ b/gdb/testsuite/gdb.arch/aarch64-gcs.exp @@ -0,0 +1,73 @@ +# Copyright 2025 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 a binary that uses a Guarded Control Stack. + +require allow_aarch64_gcs_tests + +standard_testfile + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } { + return +} + +set linespec ${srcfile}:[gdb_get_line_number "Break here"] + +if ![runto ${linespec}] { + return +} + +gdb_test "print \$gcs_features_enabled" \ + [string_to_regexp { = [ PR_SHADOW_STACK_ENABLE ]}] \ + "GCS is enabled" + +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "GDB knows about gcspr" +gdb_test "print \$gcspr == gcspr" ". = 1" "GDB has the correct gcspr value" +gdb_test_no_output "set \$gcspr_in_main = \$gcspr" \ + "save gcspr value in main for later" + +# If the inferior function call fails, we don't want the tests following it +# to be affected. +gdb_test_no_output "set unwindonsignal on" +gdb_test "print called_from_gdb (41)" ". = 42" "call inferior function" + +gdb_test "break handler" "Breakpoint \[0-9\]+ .*aarch64-gcs.c, line \[0-9\]+\\." +gdb_test "handle SIGUSR1 nostop" \ + ".*\r\nSIGUSR1\\s+No\\s+Yes\\s+Yes\\s+User defined signal 1" \ + "let the inferior receive SIGUSR1 uninterrupted" +gdb_test "continue" \ + ".*\r\nBreakpoint \[0-9\]+, handler \\(sig=10\\) at .*aarch64-gcs.c.*handler_gcspr = get_gcspr \\(\\);" \ + "continue to signal handler" + +# Select the frame above the frame, which makes GDB +# unwind the gcspr from the signal frame GCS context. +gdb_test "frame 2" "#2 ($hex in )?\\S+ \\(.*\\) (at|from) \\S+.*" \ + "reached frame 2" +gdb_test "print \$gcspr" ". = \\(void \\*\\) $hex" "gcspr in frame level 2" + +gdb_test "continue" \ + [multi_line \ + "Continuing\\." \ + "" \ + "Program received signal SIGSEGV, Segmentation fault\\." \ + "normal_function2 \\(\\) at .*aarch64-gcs.c:$decimal" \ + "${decimal}\\s+__asm__ volatile \\(\"ret\\\\n\"\\);"] \ + "continue to SIGSEGV" + +gdb_test "print \$_siginfo.si_code" ". = 10" \ + "test value of si_code when GCS SIGSEGV happens" +# The GCS grows down, and there are two real frames until main. +gdb_test "print \$gcspr == \$gcspr_in_main - 16" ". = 1" \ + "test value of gcspr when GCS SIGSEGV happens" diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index ef46e8f58c0f..45cf7b48d015 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -5032,6 +5032,64 @@ gdb_caching_proc allow_aarch64_mops_tests {} { return $allow_mops_tests } +# Run a test on the target to see if it supports AArch64 GCS extensions. +# Return 0 if so, 1 if it does not. Note this causes a restart of GDB. + +gdb_caching_proc allow_aarch64_gcs_tests {} { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "allow_aarch64_gcs_tests" + + if { ![is_aarch64_target]} { + return 0 + } + + # Compile a program that tests the GCS feature. + set src { + #include + #include + + /* Feature check for Guarded Control Stack. */ + #ifndef HWCAP_GCS + #define HWCAP_GCS (1UL << 32) + #endif + + int main (void) { + bool gcs_supported = getauxval (AT_HWCAP) & HWCAP_GCS; + + /* Return success if GCS is supported. */ + return !gcs_supported; + } + } + + if {![gdb_simple_compile $me $src executable]} { + return 0 + } + + # Compilation succeeded so now run it via gdb. + clean_restart $obj + gdb_run_cmd + gdb_expect { + -re ".*$inferior_exited_re with code 01.*${gdb_prompt} $" { + verbose -log "\n$me gcs support not detected" + set allow_gcs_tests 0 + } + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "\n$me: gcs support detected" + set allow_gcs_tests 1 + } + default { + warning "\n$me: default case taken" + set allow_gcs_tests 0 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $allow_gcs_tests" 2 + return $allow_gcs_tests +} + # A helper that compiles a test case to see if __int128 is supported. proc gdb_int128_helper {lang} { return [gdb_can_simple_compile "i128-for-$lang" { @@ -9322,7 +9380,13 @@ proc remove_core {pid {test ""}} { } } -proc core_find {binfile {deletefiles {}} {arg ""}} { +# Runs ${binfile} expecting it to crash and generate a core file. +# If DELETEFILES is provided, remove these files after running the program. +# If ARG is provided, pass it as a command line argument to the program. +# If OUTPUT_FILE is provided, save the program output to it. +# Returns the name of the core dump, or empty string if not found. + +proc core_find {binfile {deletefiles {}} {arg ""} {output_file "/dev/null"}} { global objdir subdir set destcore "$binfile.core" @@ -9344,7 +9408,7 @@ proc core_find {binfile {deletefiles {}} {arg ""}} { set found 0 set coredir [standard_output_file coredir.[getpid]] file mkdir $coredir - catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >/dev/null 2>&1\"" + catch "system \"(cd ${coredir}; ulimit -c unlimited; ${binfile} ${arg}; true) >${output_file} 2>&1\"" # remote_exec host "${binfile}" set binfile_basename [file tail $binfile] foreach i [list \ diff --git a/gdbserver/linux-aarch64-low.cc b/gdbserver/linux-aarch64-low.cc index 2eb3af659ad1..e086a0a54b0b 100644 --- a/gdbserver/linux-aarch64-low.cc +++ b/gdbserver/linux-aarch64-low.cc @@ -39,6 +39,7 @@ #include "gdb_proc_service.h" #include "arch/aarch64.h" +#include "arch/aarch64-gcs-linux.h" #include "arch/aarch64-mte-linux.h" #include "arch/aarch64-scalable-linux.h" #include "linux-aarch32-tdesc.h" @@ -321,6 +322,42 @@ aarch64_store_tlsregset (struct regcache *regcache, const void *buf) supply_register (regcache, *regnum, tls_buf + sizeof (uint64_t)); } +/* Fill BUF with GCS register from the regcache. */ + +static void +aarch64_fill_gcsregset (regcache *regcache, void *buf) +{ + user_gcs *regset = (user_gcs *) buf; + int gcspr_regnum = find_regno (regcache->tdesc, "gcspr"); + int features_enabled_regnum = find_regno (regcache->tdesc, + "gcs_features_enabled"); + int features_locked_regnum = find_regno (regcache->tdesc, + "gcs_features_locked"); + + collect_register (regcache, gcspr_regnum, ®set->gcspr_el0); + collect_register (regcache, features_enabled_regnum, + ®set->features_enabled); + collect_register (regcache, features_locked_regnum, ®set->features_locked); +} + +/* Store GCS register to regcache. */ + +static void +aarch64_store_gcsregset (regcache *regcache, const void *buf) +{ + const user_gcs *regset = (const user_gcs *) buf; + int gcspr_regnum = find_regno (regcache->tdesc, "gcspr"); + int features_enabled_regnum = find_regno (regcache->tdesc, + "gcs_features_enabled"); + int features_locked_regnum = find_regno (regcache->tdesc, + "gcs_features_locked"); + + supply_register (regcache, gcspr_regnum, ®set->gcspr_el0); + supply_register (regcache, features_enabled_regnum, + ®set->features_enabled); + supply_register (regcache, features_locked_regnum, ®set->features_locked); +} + bool aarch64_target::low_supports_breakpoints () { @@ -846,6 +883,10 @@ static struct regset_info aarch64_regsets[] = { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_TLS, 0, OPTIONAL_REGS, aarch64_fill_tlsregset, aarch64_store_tlsregset }, + /* Guarded Control Stack registers. */ + { PTRACE_GETREGSET, PTRACE_SETREGSET, NT_ARM_GCS, + 0, OPTIONAL_REGS, + aarch64_fill_gcsregset, aarch64_store_gcsregset }, NULL_REGSET }; @@ -909,6 +950,10 @@ aarch64_adjust_register_sets (const struct aarch64_features &features) if (features.sme2) regset->size = AARCH64_SME2_ZT0_SIZE; break; + case NT_ARM_GCS: + if (features.gcs_linux) + regset->size = sizeof (user_gcs); + break; default: gdb_assert_not_reached ("Unknown register set found."); } @@ -940,6 +985,7 @@ aarch64_target::low_arch_setup () /* A-profile MTE is 64-bit only. */ features.mte = linux_get_hwcap2 (pid, 8) & HWCAP2_MTE; features.tls = aarch64_tls_register_count (tid); + features.gcs = features.gcs_linux = linux_get_hwcap (pid, 8) & HWCAP_GCS; /* Scalable Matrix Extension feature and size check. */ if (linux_get_hwcap2 (pid, 8) & HWCAP2_SME)