1 1.1 christos /* Common Linux native ptrace code for AArch64 MTE. 2 1.1 christos 3 1.1.1.2 christos Copyright (C) 2021-2024 Free Software Foundation, Inc. 4 1.1 christos 5 1.1 christos This file is part of GDB. 6 1.1 christos 7 1.1 christos This program is free software; you can redistribute it and/or modify 8 1.1 christos it under the terms of the GNU General Public License as published by 9 1.1 christos the Free Software Foundation; either version 3 of the License, or 10 1.1 christos (at your option) any later version. 11 1.1 christos 12 1.1 christos This program is distributed in the hope that it will be useful, 13 1.1 christos but WITHOUT ANY WARRANTY; without even the implied warranty of 14 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 1.1 christos GNU General Public License for more details. 16 1.1 christos 17 1.1 christos You should have received a copy of the GNU General Public License 18 1.1 christos along with this program. If not, see <http://www.gnu.org/licenses/>. */ 19 1.1 christos 20 1.1 christos #include "gdbsupport/byte-vector.h" 21 1.1 christos 22 1.1 christos #include "linux-ptrace.h" 23 1.1 christos 24 1.1 christos #include "arch/aarch64.h" 25 1.1 christos #include "arch/aarch64-mte-linux.h" 26 1.1 christos #include "nat/aarch64-linux.h" 27 1.1 christos #include "nat/aarch64-mte-linux-ptrace.h" 28 1.1 christos 29 1.1 christos #include <sys/uio.h> 30 1.1 christos 31 1.1 christos /* Helper function to display various possible errors when reading 32 1.1 christos MTE tags. */ 33 1.1 christos 34 1.1 christos static void ATTRIBUTE_NORETURN 35 1.1 christos aarch64_mte_linux_peek_error (int error) 36 1.1 christos { 37 1.1 christos switch (error) 38 1.1 christos { 39 1.1 christos case EIO: 40 1.1 christos perror_with_name (_("PEEKMTETAGS not supported")); 41 1.1 christos break; 42 1.1 christos case EFAULT: 43 1.1 christos perror_with_name (_("Couldn't fetch allocation tags")); 44 1.1 christos break; 45 1.1 christos case EOPNOTSUPP: 46 1.1 christos perror_with_name (_("PROT_MTE not enabled for requested address")); 47 1.1 christos default: 48 1.1 christos perror_with_name (_("Unknown MTE error")); 49 1.1 christos break; 50 1.1 christos } 51 1.1 christos } 52 1.1 christos 53 1.1 christos /* Helper function to display various possible errors when writing 54 1.1 christos MTE tags. */ 55 1.1 christos 56 1.1 christos static void ATTRIBUTE_NORETURN 57 1.1 christos aarch64_mte_linux_poke_error (int error) 58 1.1 christos { 59 1.1 christos switch (error) 60 1.1 christos { 61 1.1 christos case EIO: 62 1.1 christos perror_with_name (_("POKEMTETAGS not supported")); 63 1.1 christos break; 64 1.1 christos case EFAULT: 65 1.1 christos perror_with_name (_("Couldn't store allocation tags")); 66 1.1 christos break; 67 1.1 christos case EOPNOTSUPP: 68 1.1 christos perror_with_name (_("PROT_MTE not enabled for requested address")); 69 1.1 christos default: 70 1.1 christos perror_with_name (_("Unknown MTE error")); 71 1.1 christos break; 72 1.1 christos } 73 1.1 christos } 74 1.1 christos 75 1.1 christos /* Helper to prepare a vector of tags to be passed on to the kernel. The 76 1.1 christos main purpose of this function is to optimize the number of calls to 77 1.1 christos ptrace if we're writing too many tags at once, like a pattern fill 78 1.1 christos request. 79 1.1 christos 80 1.1 christos Return a vector of tags of up to MAX_SIZE size, containing the tags that 81 1.1 christos must be passed on to the kernel, extracted from TAGS, starting at POS. 82 1.1 christos GRANULES is the number of tag granules to be modified. */ 83 1.1 christos 84 1.1 christos static gdb::byte_vector 85 1.1 christos prepare_tag_vector (size_t granules, const gdb::byte_vector &tags, size_t pos, 86 1.1 christos size_t max_size) 87 1.1 christos { 88 1.1 christos gdb::byte_vector t; 89 1.1 christos 90 1.1 christos if (granules == 0) 91 1.1 christos return t; 92 1.1 christos 93 1.1 christos gdb_assert (tags.size () > 0 && max_size > 0); 94 1.1 christos 95 1.1 christos if (granules > AARCH64_MTE_TAGS_MAX_SIZE) 96 1.1 christos t.resize (AARCH64_MTE_TAGS_MAX_SIZE); 97 1.1 christos else 98 1.1 christos t.resize (granules); 99 1.1 christos 100 1.1 christos size_t tag_count = tags.size (); 101 1.1 christos 102 1.1 christos for (size_t i = 0; i < t.size (); i++) 103 1.1 christos t[i] = tags[(pos + i) % tag_count]; 104 1.1 christos 105 1.1 christos return t; 106 1.1 christos } 107 1.1 christos 108 1.1 christos /* See nat/aarch64-mte-linux-ptrace.h */ 109 1.1 christos 110 1.1 christos bool 111 1.1 christos aarch64_mte_fetch_memtags (int tid, CORE_ADDR address, size_t len, 112 1.1 christos gdb::byte_vector &tags) 113 1.1 christos { 114 1.1 christos size_t ntags = aarch64_mte_get_tag_granules (address, len, 115 1.1 christos AARCH64_MTE_GRANULE_SIZE); 116 1.1 christos 117 1.1 christos /* If the memory range contains no tags, nothing left to do. */ 118 1.1 christos if (ntags == 0) 119 1.1 christos return true; 120 1.1 christos 121 1.1 christos gdb_byte tagbuf[ntags]; 122 1.1 christos 123 1.1 christos struct iovec iovec; 124 1.1 christos iovec.iov_base = tagbuf; 125 1.1 christos iovec.iov_len = ntags; 126 1.1 christos 127 1.1 christos tags.clear (); 128 1.1 christos bool done_reading = false; 129 1.1 christos 130 1.1 christos /* The kernel may return less tags than we requested. Loop until we've read 131 1.1 christos all the requested tags or until we get an error. */ 132 1.1 christos while (!done_reading) 133 1.1 christos { 134 1.1 christos /* Attempt to read ntags allocation tags from the kernel. */ 135 1.1 christos if (ptrace (PTRACE_PEEKMTETAGS, tid, address, &iovec) < 0) 136 1.1 christos aarch64_mte_linux_peek_error (errno); 137 1.1 christos 138 1.1 christos /* Make sure the kernel returned at least one tag. */ 139 1.1 christos if (iovec.iov_len <= 0) 140 1.1 christos { 141 1.1 christos tags.clear (); 142 1.1 christos return false; 143 1.1 christos } 144 1.1 christos 145 1.1 christos /* Copy the tags the kernel returned. */ 146 1.1 christos for (size_t i = 0; i < iovec.iov_len; i++) 147 1.1 christos tags.push_back (tagbuf[i]); 148 1.1 christos 149 1.1 christos /* Are we done reading tags? */ 150 1.1 christos if (tags.size () == ntags) 151 1.1 christos done_reading = true; 152 1.1 christos else 153 1.1 christos { 154 1.1 christos address += iovec.iov_len * AARCH64_MTE_GRANULE_SIZE; 155 1.1 christos iovec.iov_len = ntags - iovec.iov_len; 156 1.1 christos } 157 1.1 christos } 158 1.1 christos return true; 159 1.1 christos } 160 1.1 christos 161 1.1 christos /* See nat/aarch64-mte-linux-ptrace.h */ 162 1.1 christos 163 1.1 christos bool 164 1.1 christos aarch64_mte_store_memtags (int tid, CORE_ADDR address, size_t len, 165 1.1 christos const gdb::byte_vector &tags) 166 1.1 christos { 167 1.1 christos if (tags.size () == 0) 168 1.1 christos return true; 169 1.1 christos 170 1.1 christos /* Get the number of tags we need to write. */ 171 1.1 christos size_t ntags = aarch64_mte_get_tag_granules (address, len, 172 1.1 christos AARCH64_MTE_GRANULE_SIZE); 173 1.1 christos 174 1.1 christos /* If the memory range contains no tags, nothing left to do. */ 175 1.1 christos if (ntags == 0) 176 1.1 christos return true; 177 1.1 christos 178 1.1 christos bool done_writing = false; 179 1.1 christos size_t tags_written = 0; 180 1.1 christos 181 1.1 christos /* Write all the tags, AARCH64_MTE_TAGS_MAX_SIZE blocks at a time. */ 182 1.1 christos while (!done_writing) 183 1.1 christos { 184 1.1 christos gdb::byte_vector t = prepare_tag_vector (ntags - tags_written, tags, 185 1.1 christos tags_written, 186 1.1 christos AARCH64_MTE_TAGS_MAX_SIZE); 187 1.1 christos 188 1.1 christos struct iovec iovec; 189 1.1 christos iovec.iov_base = t.data (); 190 1.1 christos iovec.iov_len = t.size (); 191 1.1 christos 192 1.1 christos /* Request the kernel to update the allocation tags. */ 193 1.1 christos if (ptrace (PTRACE_POKEMTETAGS, tid, address, &iovec) < 0) 194 1.1 christos aarch64_mte_linux_poke_error (errno); 195 1.1 christos 196 1.1 christos /* Make sure the kernel wrote at least one tag. */ 197 1.1 christos if (iovec.iov_len <= 0) 198 1.1 christos return false; 199 1.1 christos 200 1.1 christos tags_written += iovec.iov_len; 201 1.1 christos 202 1.1 christos /* Are we done writing tags? */ 203 1.1 christos if (tags_written == ntags) 204 1.1 christos done_writing = true; 205 1.1 christos else 206 1.1 christos address += iovec.iov_len * AARCH64_MTE_GRANULE_SIZE; 207 1.1 christos } 208 1.1 christos 209 1.1 christos return true; 210 1.1 christos } 211