| /* Common Linux native ptrace code for AArch64 MTE. |
| |
| Copyright (C) 2021-2024 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 <http://www.gnu.org/licenses/>. */ |
| |
| #include "gdbsupport/byte-vector.h" |
| |
| #include "linux-ptrace.h" |
| |
| #include "arch/aarch64.h" |
| #include "arch/aarch64-mte.h" |
| #include "arch/aarch64-mte-linux.h" |
| #include "nat/aarch64-linux.h" |
| #include "nat/aarch64-mte-linux-ptrace.h" |
| |
| #include <sys/uio.h> |
| |
| /* Helper function to display various possible errors when reading |
| MTE tags. */ |
| |
| [[noreturn]] static void |
| aarch64_mte_linux_peek_error (int error) |
| { |
| switch (error) |
| { |
| case EIO: |
| perror_with_name (_("PEEKMTETAGS not supported")); |
| break; |
| case EFAULT: |
| perror_with_name (_("Couldn't fetch allocation tags")); |
| break; |
| case EOPNOTSUPP: |
| perror_with_name (_("PROT_MTE not enabled for requested address")); |
| default: |
| perror_with_name (_("Unknown MTE error")); |
| break; |
| } |
| } |
| |
| /* Helper function to display various possible errors when writing |
| MTE tags. */ |
| |
| [[noreturn]] static void |
| aarch64_mte_linux_poke_error (int error) |
| { |
| switch (error) |
| { |
| case EIO: |
| perror_with_name (_("POKEMTETAGS not supported")); |
| break; |
| case EFAULT: |
| perror_with_name (_("Couldn't store allocation tags")); |
| break; |
| case EOPNOTSUPP: |
| perror_with_name (_("PROT_MTE not enabled for requested address")); |
| default: |
| perror_with_name (_("Unknown MTE error")); |
| break; |
| } |
| } |
| |
| /* Helper to prepare a vector of tags to be passed on to the kernel. The |
| main purpose of this function is to optimize the number of calls to |
| ptrace if we're writing too many tags at once, like a pattern fill |
| request. |
| |
| Return a vector of tags of up to MAX_SIZE size, containing the tags that |
| must be passed on to the kernel, extracted from TAGS, starting at POS. |
| GRANULES is the number of tag granules to be modified. */ |
| |
| static gdb::byte_vector |
| prepare_tag_vector (size_t granules, const gdb::byte_vector &tags, size_t pos, |
| size_t max_size) |
| { |
| gdb::byte_vector t; |
| |
| if (granules == 0) |
| return t; |
| |
| gdb_assert (tags.size () > 0 && max_size > 0); |
| |
| if (granules > AARCH64_MTE_TAGS_MAX_SIZE) |
| t.resize (AARCH64_MTE_TAGS_MAX_SIZE); |
| else |
| t.resize (granules); |
| |
| size_t tag_count = tags.size (); |
| |
| for (size_t i = 0; i < t.size (); i++) |
| t[i] = tags[(pos + i) % tag_count]; |
| |
| return t; |
| } |
| |
| /* See nat/aarch64-mte-linux-ptrace.h */ |
| |
| bool |
| aarch64_mte_fetch_memtags (int tid, CORE_ADDR address, size_t len, |
| gdb::byte_vector &tags) |
| { |
| size_t ntags = aarch64_mte_get_tag_granules (address, len, |
| AARCH64_MTE_GRANULE_SIZE); |
| |
| /* If the memory range contains no tags, nothing left to do. */ |
| if (ntags == 0) |
| return true; |
| |
| gdb::byte_vector tagbuf (ntags); |
| |
| struct iovec iovec; |
| iovec.iov_base = tagbuf.data (); |
| iovec.iov_len = ntags; |
| |
| tags.clear (); |
| bool done_reading = false; |
| |
| /* The kernel may return less tags than we requested. Loop until we've read |
| all the requested tags or until we get an error. */ |
| while (!done_reading) |
| { |
| /* Attempt to read ntags allocation tags from the kernel. */ |
| if (ptrace (PTRACE_PEEKMTETAGS, tid, address, &iovec) < 0) |
| aarch64_mte_linux_peek_error (errno); |
| |
| /* Make sure the kernel returned at least one tag. */ |
| if (iovec.iov_len <= 0) |
| { |
| tags.clear (); |
| return false; |
| } |
| |
| /* Copy the tags the kernel returned. */ |
| for (size_t i = 0; i < iovec.iov_len; i++) |
| tags.push_back (tagbuf[i]); |
| |
| /* Are we done reading tags? */ |
| if (tags.size () == ntags) |
| done_reading = true; |
| else |
| { |
| address += iovec.iov_len * AARCH64_MTE_GRANULE_SIZE; |
| iovec.iov_len = ntags - iovec.iov_len; |
| } |
| } |
| return true; |
| } |
| |
| /* See nat/aarch64-mte-linux-ptrace.h */ |
| |
| bool |
| aarch64_mte_store_memtags (int tid, CORE_ADDR address, size_t len, |
| const gdb::byte_vector &tags) |
| { |
| if (tags.size () == 0) |
| return true; |
| |
| /* Get the number of tags we need to write. */ |
| size_t ntags = aarch64_mte_get_tag_granules (address, len, |
| AARCH64_MTE_GRANULE_SIZE); |
| |
| /* If the memory range contains no tags, nothing left to do. */ |
| if (ntags == 0) |
| return true; |
| |
| bool done_writing = false; |
| size_t tags_written = 0; |
| |
| /* Write all the tags, AARCH64_MTE_TAGS_MAX_SIZE blocks at a time. */ |
| while (!done_writing) |
| { |
| gdb::byte_vector t = prepare_tag_vector (ntags - tags_written, tags, |
| tags_written, |
| AARCH64_MTE_TAGS_MAX_SIZE); |
| |
| struct iovec iovec; |
| iovec.iov_base = t.data (); |
| iovec.iov_len = t.size (); |
| |
| /* Request the kernel to update the allocation tags. */ |
| if (ptrace (PTRACE_POKEMTETAGS, tid, address, &iovec) < 0) |
| aarch64_mte_linux_poke_error (errno); |
| |
| /* Make sure the kernel wrote at least one tag. */ |
| if (iovec.iov_len <= 0) |
| return false; |
| |
| tags_written += iovec.iov_len; |
| |
| /* Are we done writing tags? */ |
| if (tags_written == ntags) |
| done_writing = true; |
| else |
| address += iovec.iov_len * AARCH64_MTE_GRANULE_SIZE; |
| } |
| |
| return true; |
| } |