kernel can potentially be tagged addresses. */
struct gdbarch *gdbarch = thread_architecture (inferior_ptid);
const CORE_ADDR addr_trap
- = address_significant (gdbarch, (CORE_ADDR) siginfo.si_addr);
+ = gdbarch_remove_non_address_bits (gdbarch, (CORE_ADDR) siginfo.si_addr);
/* Check if the address matches any watched address. */
state = aarch64_get_debug_reg_state (inferior_ptid.pid ());
CORE_ADDR addr = value_as_address (address);
/* Remove the top byte for the memory range check. */
- addr = address_significant (gdbarch, addr);
+ addr = gdbarch_remove_non_address_bits (gdbarch, addr);
/* Check if the page that contains ADDRESS is mapped with PROT_MTE. */
if (!linux_address_in_memtag_page (addr))
/* Fetch the allocation tag for ADDRESS. */
gdb::optional<CORE_ADDR> atag
- = aarch64_mte_get_atag (address_significant (gdbarch, addr));
+ = aarch64_mte_get_atag (gdbarch_remove_non_address_bits (gdbarch, addr));
if (!atag.has_value ())
return true;
else
{
/* Remove the top byte. */
- addr = address_significant (gdbarch, addr);
+ addr = gdbarch_remove_non_address_bits (gdbarch, addr);
/* Make sure we are dealing with a tagged address to begin with. */
if (!aarch64_linux_tagged_address_p (gdbarch, address))
return nullptr;
/* Remove the top byte. */
- addr = address_significant (gdbarch, addr);
+ addr = gdbarch_remove_non_address_bits (gdbarch, addr);
gdb::optional<CORE_ADDR> atag = aarch64_mte_get_atag (addr);
if (!atag.has_value ())
uiout->text ("\n");
gdb::optional<CORE_ADDR> atag
- = aarch64_mte_get_atag (address_significant (gdbarch, fault_addr));
+ = aarch64_mte_get_atag (gdbarch_remove_non_address_bits (gdbarch,
+ fault_addr));
gdb_byte ltag = aarch64_mte_get_ltag (fault_addr);
if (!atag.has_value ())
return tags;
}
+/* AArch64 implementation of the remove_non_address_bits gdbarch hook. Remove
+ non address bits from a pointer value. */
+
+static CORE_ADDR
+aarch64_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
+{
+ aarch64_gdbarch_tdep *tdep = gdbarch_tdep<aarch64_gdbarch_tdep> (gdbarch);
+
+ /* By default, we assume TBI and discard the top 8 bits plus the VA range
+ select bit (55). */
+ CORE_ADDR mask = AARCH64_TOP_BITS_MASK;
+
+ if (tdep->has_pauth ())
+ {
+ /* Fetch the PAC masks. These masks are per-process, so we can just
+ fetch data from whatever thread we have at the moment.
+
+ Also, we have both a code mask and a data mask. For now they are the
+ same, but this may change in the future. */
+ struct regcache *regs = get_current_regcache ();
+ CORE_ADDR cmask, dmask;
+
+ if (regs->cooked_read (tdep->pauth_reg_base, &dmask) != REG_VALID)
+ dmask = mask;
+
+ if (regs->cooked_read (tdep->pauth_reg_base + 1, &cmask) != REG_VALID)
+ cmask = mask;
+
+ mask |= aarch64_mask_from_pac_registers (cmask, dmask);
+ }
+
+ return aarch64_remove_top_bits (pointer, mask);
+}
+
static void
aarch64_linux_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
{
/* The top byte of a user space address known as the "tag",
is ignored by the kernel and can be regarded as additional
data associated with the address. */
- set_gdbarch_significant_addr_bit (gdbarch, 56);
+ set_gdbarch_remove_non_address_bits (gdbarch,
+ aarch64_remove_non_address_bits);
/* MTE-specific settings and hooks. */
if (tdep->has_mte ())
return LEGACY_SIM_REGNO_IGNORE;
}
+/* See arch-utils.h */
+
+CORE_ADDR
+default_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
+{
+ /* By default, just return the pointer value. */
+ return pointer;
+}
/* See arch-utils.h */
default_floatformat_for_type (struct gdbarch *gdbarch,
const char *name, int len);
+/* Default implementation of gdbarch_remove_non_address_bits. */
+CORE_ADDR default_remove_non_address_bits (struct gdbarch *gdbarch,
+ CORE_ADDR pointer);
+
/* Default implementation of gdbarch_memtag_to_string. */
extern std::string default_memtag_to_string (struct gdbarch *gdbarch,
struct value *tag);
return tdesc.release ();
}
+
+/* See arch/aarch64.h. */
+
+CORE_ADDR
+aarch64_remove_top_bits (CORE_ADDR pointer, CORE_ADDR mask)
+{
+ /* The VA range select bit is 55. This bit tells us if we have a
+ kernel-space address or a user-space address. */
+ bool kernel_address = (pointer & VA_RANGE_SELECT_BIT_MASK) != 0;
+
+ /* Remove the top non-address bits. */
+ pointer &= ~mask;
+
+ /* Sign-extend if we have a kernel-space address. */
+ if (kernel_address)
+ pointer |= mask;
+
+ return pointer;
+}
+
+/* See arch/aarch64.h. */
+
+CORE_ADDR
+aarch64_mask_from_pac_registers (const CORE_ADDR cmask, const CORE_ADDR dmask)
+{
+ /* If the masks differ, default to using the one with the most coverage. */
+ if (dmask != cmask)
+ return dmask > cmask ? dmask : cmask;
+
+ return cmask;
+}
target_desc *
aarch64_create_target_description (const aarch64_features &features);
+/* Given a pointer value POINTER and a MASK of non-address bits, remove the
+ non-address bits from the pointer and sign-extend the result if required.
+ The sign-extension is required so we can handle kernel addresses
+ correctly. */
+CORE_ADDR aarch64_remove_top_bits (CORE_ADDR pointer, CORE_ADDR mask);
+
+/* Given CMASK and DMASK the two PAC mask registers, return the correct PAC
+ mask to use for removing non-address bits from a pointer. */
+CORE_ADDR
+aarch64_mask_from_pac_registers (const CORE_ADDR cmask, const CORE_ADDR dmask);
+
/* Register numbers of various important registers.
Note that on SVE, the Z registers reuse the V register numbers and the V
registers become pseudo registers. */
#define AARCH64_TLS_REGISTER_SIZE 8
#define V_REGISTER_SIZE 16
+/* PAC-related constants. */
+/* Bit 55 is used to select between a kernel-space and user-space address. */
+#define VA_RANGE_SELECT_BIT_MASK 0x80000000000000ULL
+/* Mask with 1's in bits 55~63, used to remove the top byte of pointers
+ (Top Byte Ignore). */
+#define AARCH64_TOP_BITS_MASK 0xff80000000000000ULL
+
/* Pseudo register base numbers. */
#define AARCH64_Q0_REGNUM 0
#define AARCH64_D0_REGNUM (AARCH64_Q0_REGNUM + AARCH64_D_REGISTER_COUNT)
loc->gdbarch = value_type (v)->arch ();
loc->pspace = frame_pspace;
- loc->address = address_significant (loc->gdbarch, addr);
+ loc->address
+ = gdbarch_remove_non_address_bits (loc->gdbarch, addr);
if (bitsize != 0)
{
= gdbarch_adjust_breakpoint_address (gdbarch, bpaddr);
}
- adjusted_bpaddr = address_significant (gdbarch, adjusted_bpaddr);
+ adjusted_bpaddr
+ = gdbarch_remove_non_address_bits (gdbarch, adjusted_bpaddr);
/* An adjusted breakpoint address can significantly alter
a user's expectations. Print a warning if an adjustment
invalid=False,
)
-Value(
+Method(
comment="""
-On some machines, not all bits of an address word are significant.
-For example, on AArch64, the top bits of an address known as the "tag"
-are ignored by the kernel, the hardware, etc. and can be regarded as
-additional data associated with the address.
+On some architectures, not all bits of a pointer are significant.
+On AArch64, for example, the top bits of a pointer may carry a "tag", which
+can be ignored by the kernel and the hardware. The "tag" can be regarded as
+additional data associated with the pointer, but it is not part of the address.
+
+Given a pointer for the architecture, this hook removes all the
+non-significant bits and sign-extends things as needed. It gets used to remove
+non-address bits from data pointers (for example, removing the AArch64 MTE tag
+bits from a pointer) and from code pointers (removing the AArch64 PAC signature
+from a pointer containing the return address).
""",
- type="int",
- name="significant_addr_bit",
+ type="CORE_ADDR",
+ name="remove_non_address_bits",
+ params=[("CORE_ADDR", "pointer")],
+ predefault="default_remove_non_address_bits",
invalid=False,
)
extern CORE_ADDR gdbarch_addr_bits_remove (struct gdbarch *gdbarch, CORE_ADDR addr);
extern void set_gdbarch_addr_bits_remove (struct gdbarch *gdbarch, gdbarch_addr_bits_remove_ftype *addr_bits_remove);
-/* On some machines, not all bits of an address word are significant.
- For example, on AArch64, the top bits of an address known as the "tag"
- are ignored by the kernel, the hardware, etc. and can be regarded as
- additional data associated with the address. */
-
-extern int gdbarch_significant_addr_bit (struct gdbarch *gdbarch);
-extern void set_gdbarch_significant_addr_bit (struct gdbarch *gdbarch, int significant_addr_bit);
+/* On some architectures, not all bits of a pointer are significant.
+ On AArch64, for example, the top bits of a pointer may carry a "tag", which
+ can be ignored by the kernel and the hardware. The "tag" can be regarded as
+ additional data associated with the pointer, but it is not part of the address.
+
+ Given a pointer for the architecture, this hook removes all the
+ non-significant bits and sign-extends things as needed. It gets used to remove
+ non-address bits from data pointers (for example, removing the AArch64 MTE tag
+ bits from a pointer) and from code pointers (removing the AArch64 PAC signature
+ from a pointer containing the return address). */
+
+typedef CORE_ADDR (gdbarch_remove_non_address_bits_ftype) (struct gdbarch *gdbarch, CORE_ADDR pointer);
+extern CORE_ADDR gdbarch_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer);
+extern void set_gdbarch_remove_non_address_bits (struct gdbarch *gdbarch, gdbarch_remove_non_address_bits_ftype *remove_non_address_bits);
/* Return a string representation of the memory tag TAG. */
int frame_red_zone_size = 0;
gdbarch_convert_from_func_ptr_addr_ftype *convert_from_func_ptr_addr = convert_from_func_ptr_addr_identity;
gdbarch_addr_bits_remove_ftype *addr_bits_remove = core_addr_identity;
- int significant_addr_bit = 0;
+ gdbarch_remove_non_address_bits_ftype *remove_non_address_bits = default_remove_non_address_bits;
gdbarch_memtag_to_string_ftype *memtag_to_string = default_memtag_to_string;
gdbarch_tagged_address_p_ftype *tagged_address_p = default_tagged_address_p;
gdbarch_memtag_matches_p_ftype *memtag_matches_p = default_memtag_matches_p;
/* Skip verify of frame_red_zone_size, invalid_p == 0 */
/* Skip verify of convert_from_func_ptr_addr, invalid_p == 0 */
/* Skip verify of addr_bits_remove, invalid_p == 0 */
- /* Skip verify of significant_addr_bit, invalid_p == 0 */
+ /* Skip verify of remove_non_address_bits, invalid_p == 0 */
/* Skip verify of memtag_to_string, invalid_p == 0 */
/* Skip verify of tagged_address_p, invalid_p == 0 */
/* Skip verify of memtag_matches_p, invalid_p == 0 */
"gdbarch_dump: addr_bits_remove = <%s>\n",
host_address_to_string (gdbarch->addr_bits_remove));
gdb_printf (file,
- "gdbarch_dump: significant_addr_bit = %s\n",
- plongest (gdbarch->significant_addr_bit));
+ "gdbarch_dump: remove_non_address_bits = <%s>\n",
+ host_address_to_string (gdbarch->remove_non_address_bits));
gdb_printf (file,
"gdbarch_dump: memtag_to_string = <%s>\n",
host_address_to_string (gdbarch->memtag_to_string));
gdbarch->addr_bits_remove = addr_bits_remove;
}
-int
-gdbarch_significant_addr_bit (struct gdbarch *gdbarch)
+CORE_ADDR
+gdbarch_remove_non_address_bits (struct gdbarch *gdbarch, CORE_ADDR pointer)
{
gdb_assert (gdbarch != NULL);
- /* Skip verify of significant_addr_bit, invalid_p == 0 */
+ gdb_assert (gdbarch->remove_non_address_bits != NULL);
if (gdbarch_debug >= 2)
- gdb_printf (gdb_stdlog, "gdbarch_significant_addr_bit called\n");
- return gdbarch->significant_addr_bit;
+ gdb_printf (gdb_stdlog, "gdbarch_remove_non_address_bits called\n");
+ return gdbarch->remove_non_address_bits (gdbarch, pointer);
}
void
-set_gdbarch_significant_addr_bit (struct gdbarch *gdbarch,
- int significant_addr_bit)
+set_gdbarch_remove_non_address_bits (struct gdbarch *gdbarch,
+ gdbarch_remove_non_address_bits_ftype remove_non_address_bits)
{
- gdbarch->significant_addr_bit = significant_addr_bit;
+ gdbarch->remove_non_address_bits = remove_non_address_bits;
}
std::string
if (len == 0)
return TARGET_XFER_EOF;
- memaddr = address_significant (target_gdbarch (), memaddr);
+ memaddr = gdbarch_remove_non_address_bits (target_gdbarch (), memaddr);
/* Fill in READBUF with breakpoint shadows, or WRITEBUF with
breakpoint insns, thus hiding out from higher layers whether
--- /dev/null
+/* This file is part of GDB, the GNU debugger.
+
+ Copyright 2022 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 <http://www.gnu.org/licenses/>. */
+
+static long l = 0;
+static long *l_ptr = &l;
+
+int
+main (int argc, char **argv)
+{
+ return *l_ptr;
+}
--- /dev/null
+# Copyright 2022 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 <http://www.gnu.org/licenses/>.
+#
+# This file is part of the gdb testsuite.
+#
+# Test that GDB for AArch64/Linux can properly handle pointers with
+# the upper 16 bits (PAC) or 8 bits (Tag) set, as well as the
+# VA_RANGE_SELECT bit (55).
+
+if {![is_aarch64_target]} {
+ verbose "Skipping ${gdb_test_file_name}."
+ return
+}
+
+standard_testfile
+if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile}] } {
+ return -1
+}
+
+if ![runto_main] {
+ return -1
+}
+
+# We need to iterate over two distinct ranges, separated by a single bit.
+# This bit is 55 (VA_RANGE_SELECT) which tells us if we have a kernel-space
+# address or a user-space address.
+
+# The tag field has 8 bits.
+set tag_bits_count 8
+
+# The pac field has 7 bits.
+set pac_bits_count 7
+
+# A couple patterns that we reuse for the tests later. One is for a successful
+# memory read and the other is for a memory read failure.
+set memory_read_ok_pattern "$::hex\( <l>\)?:\[ \t\]+$::hex"
+set memory_read_fail_pattern "$::hex:\[ \t\]+Cannot access memory at address $::hex"
+
+set pac_enabled 0
+
+# Check if PAC is enabled.
+gdb_test_multiple "ptype \$pauth_cmask" "fetch PAC cmask" {
+ -re -wrap "type = long" {
+ set pac_enabled 1
+ }
+ -re -wrap "type = void" {
+ }
+ -re ".*$gdb_prompt $" {
+ fail $gdb_test_name
+ return 1
+ }
+}
+
+# Value of the cmask register.
+set cmask 0
+
+# If there are PAC registers, GDB uses those to unmask the PAC bits.
+if {$pac_enabled} {
+ set cmask [get_valueof "" "\$pauth_cmask >> 48" "0" "fetch PAC cmask"]
+}
+
+# Cycle through the tag and pac bit ranges and check how GDB
+# behaves when trying to access these addresses.
+foreach_with_prefix upper_bits {"0x0" "0x1" "0x2" "0x4" "0x8" "0x10" "0x20" "0x40" "0x80"} {
+ foreach_with_prefix lower_bits {"0x0" "0x1" "0x2" "0x4" "0x8" "0x10" "0x20" "0x40"} {
+
+ # A successful memory read pattern
+ set pattern $memory_read_ok_pattern
+
+ if {!$pac_enabled} {
+ # If PAC is not supported, memory reads will fail if
+ # lower_bits != 0x0
+ if {$lower_bits != "0x0"} {
+ set pattern $memory_read_fail_pattern
+ }
+ } else {
+ # Otherwise, figure out if the memory read will succeed or not by
+ # checking cmask.
+ gdb_test_multiple "p/x (~${cmask}ULL & (${lower_bits}ULL))" "" {
+ -re -wrap "= 0x0" {
+ # Either cmask is 0x7F or lower_bits is 0x0.
+ # Either way, the memory read should succeed.
+ }
+ -re -wrap "= $::hex" {
+ if {$lower_bits != "0x0"} {
+ # cmask doesn't mask off all the PAC bits, which
+ # results in a memory read failure, with the actual
+ # address being accessed differing from the one we
+ # passed.
+ set pattern $memory_read_fail_pattern
+ }
+ }
+ }
+ }
+
+ # Test without the VA_RANGE_SELECT bit set.
+ gdb_test "x/gx ((unsigned long) l_ptr | ((${upper_bits}ULL << 56) | (${lower_bits}ULL << 48)))" \
+ $pattern \
+ "user-space memory access"
+
+ # Now test with the VA_RANGE_SELECT bit set.
+ gdb_test "x/gx ((unsigned long) l_ptr | ((${upper_bits}ULL << 56) | (${lower_bits}ULL << 48) | (1ULL << 55))) " \
+ $memory_read_fail_pattern \
+ "kernel-space memory access"
+ }
+}
}
\f
-/* See utils.h. */
-
-CORE_ADDR
-address_significant (gdbarch *gdbarch, CORE_ADDR addr)
-{
- /* Clear insignificant bits of a target address and sign extend resulting
- address, avoiding shifts larger or equal than the width of a CORE_ADDR.
- The local variable ADDR_BIT stops the compiler reporting a shift overflow
- when it won't occur. Skip updating of target address if current target
- has not set gdbarch significant_addr_bit. */
- int addr_bit = gdbarch_significant_addr_bit (gdbarch);
-
- if (addr_bit && (addr_bit < (sizeof (CORE_ADDR) * HOST_CHAR_BIT)))
- {
- CORE_ADDR sign = (CORE_ADDR) 1 << (addr_bit - 1);
- addr &= ((CORE_ADDR) 1 << addr_bit) - 1;
- addr = (addr ^ sign) - sign;
- }
-
- return addr;
-}
-
const char *
paddress (struct gdbarch *gdbarch, CORE_ADDR addr)
{
extern void fputs_highlighted (const char *str, const compiled_regex &highlight,
struct ui_file *stream);
-/* Return the address only having significant bits. */
-extern CORE_ADDR address_significant (gdbarch *gdbarch, CORE_ADDR addr);
-
/* Convert CORE_ADDR to string in platform-specific manner.
This is usually formatted similar to 0x%lx. */
extern const char *paddress (struct gdbarch *gdbarch, CORE_ADDR addr);
return ret;
}
-/* Return the address only having significant bits. This is used to ignore
- the top byte (TBI). */
-
static CORE_ADDR
-address_significant (CORE_ADDR addr)
+aarch64_remove_non_address_bits (CORE_ADDR pointer)
{
- /* Clear insignificant bits of a target address and sign extend resulting
- address. */
- int addr_bit = 56;
+ /* By default, we assume TBI and discard the top 8 bits plus the
+ VA range select bit (55). */
+ CORE_ADDR mask = AARCH64_TOP_BITS_MASK;
+
+ /* Check if PAC is available for this target. */
+ if (tdesc_contains_feature (current_process ()->tdesc,
+ "org.gnu.gdb.aarch64.pauth"))
+ {
+ /* Fetch the PAC masks. These masks are per-process, so we can just
+ fetch data from whatever thread we have at the moment.
- CORE_ADDR sign = (CORE_ADDR) 1 << (addr_bit - 1);
- addr &= ((CORE_ADDR) 1 << addr_bit) - 1;
- addr = (addr ^ sign) - sign;
+ Also, we have both a code mask and a data mask. For now they are the
+ same, but this may change in the future. */
+
+ struct regcache *regs = get_thread_regcache (current_thread, 1);
+ CORE_ADDR dmask = regcache_raw_get_unsigned_by_name (regs, "pauth_dmask");
+ CORE_ADDR cmask = regcache_raw_get_unsigned_by_name (regs, "pauth_cmask");
+ mask |= aarch64_mask_from_pac_registers (cmask, dmask);
+ }
- return addr;
+ return aarch64_remove_top_bits (pointer, mask);
}
/* Implementation of linux target ops method "low_stopped_data_address". */
hardware watchpoint hit. The stopped data addresses coming from the
kernel can potentially be tagged addresses. */
const CORE_ADDR addr_trap
- = address_significant ((CORE_ADDR) siginfo.si_addr);
+ = aarch64_remove_non_address_bits ((CORE_ADDR) siginfo.si_addr);
/* Check if the address matches any watched address. */
state = aarch64_get_debug_reg_state (pid_of (current_thread));