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