1 | //===-- MemoryTagManagerAArch64MTE.cpp --------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "MemoryTagManagerAArch64MTE.h" |
10 | #include "llvm/Support/Error.h" |
11 | #include <assert.h> |
12 | |
13 | using namespace lldb_private; |
14 | |
15 | static const unsigned MTE_START_BIT = 56; |
16 | static const unsigned MTE_TAG_MAX = 0xf; |
17 | static const unsigned MTE_GRANULE_SIZE = 16; |
18 | |
19 | lldb::addr_t |
20 | MemoryTagManagerAArch64MTE::GetLogicalTag(lldb::addr_t addr) const { |
21 | return (addr >> MTE_START_BIT) & MTE_TAG_MAX; |
22 | } |
23 | |
24 | lldb::addr_t |
25 | MemoryTagManagerAArch64MTE::RemoveTagBits(lldb::addr_t addr) const { |
26 | // Here we're ignoring the whole top byte. If you've got MTE |
27 | // you must also have TBI (top byte ignore). |
28 | // The other 4 bits could contain other extension bits or |
29 | // user metadata. |
30 | return addr & ~((lldb::addr_t)0xFF << MTE_START_BIT); |
31 | } |
32 | |
33 | ptrdiff_t MemoryTagManagerAArch64MTE::AddressDiff(lldb::addr_t addr1, |
34 | lldb::addr_t addr2) const { |
35 | return RemoveTagBits(addr: addr1) - RemoveTagBits(addr: addr2); |
36 | } |
37 | |
38 | lldb::addr_t MemoryTagManagerAArch64MTE::GetGranuleSize() const { |
39 | return MTE_GRANULE_SIZE; |
40 | } |
41 | |
42 | int32_t MemoryTagManagerAArch64MTE::GetAllocationTagType() const { |
43 | return eMTE_allocation; |
44 | } |
45 | |
46 | size_t MemoryTagManagerAArch64MTE::GetTagSizeInBytes() const { return 1; } |
47 | |
48 | MemoryTagManagerAArch64MTE::TagRange |
49 | MemoryTagManagerAArch64MTE::ExpandToGranule(TagRange range) const { |
50 | // Ignore reading a length of 0 |
51 | if (!range.IsValid()) |
52 | return range; |
53 | |
54 | const size_t granule = GetGranuleSize(); |
55 | |
56 | // Align start down to granule start |
57 | lldb::addr_t new_start = range.GetRangeBase(); |
58 | lldb::addr_t align_down_amount = new_start % granule; |
59 | new_start -= align_down_amount; |
60 | |
61 | // Account for the distance we moved the start above |
62 | size_t new_len = range.GetByteSize() + align_down_amount; |
63 | // Then align up to the end of the granule |
64 | size_t align_up_amount = granule - (new_len % granule); |
65 | if (align_up_amount != granule) |
66 | new_len += align_up_amount; |
67 | |
68 | return TagRange(new_start, new_len); |
69 | } |
70 | |
71 | static llvm::Error MakeInvalidRangeErr(lldb::addr_t addr, |
72 | lldb::addr_t end_addr) { |
73 | return llvm::createStringError( |
74 | EC: llvm::inconvertibleErrorCode(), |
75 | Fmt: "End address (0x%" PRIx64 |
76 | ") must be greater than the start address (0x%" PRIx64 ")" , |
77 | Vals: end_addr, Vals: addr); |
78 | } |
79 | |
80 | llvm::Expected<MemoryTagManager::TagRange> |
81 | MemoryTagManagerAArch64MTE::MakeTaggedRange( |
82 | lldb::addr_t addr, lldb::addr_t end_addr, |
83 | const lldb_private::MemoryRegionInfos &memory_regions) const { |
84 | // First check that the range is not inverted. |
85 | // We must remove tags here otherwise an address with a higher |
86 | // tag value will always be > the other. |
87 | ptrdiff_t len = AddressDiff(addr1: end_addr, addr2: addr); |
88 | if (len <= 0) |
89 | return MakeInvalidRangeErr(addr, end_addr); |
90 | |
91 | // Region addresses will not have memory tags. So when searching |
92 | // we must use an untagged address. |
93 | MemoryRegionInfo::RangeType tag_range(RemoveTagBits(addr), len); |
94 | tag_range = ExpandToGranule(range: tag_range); |
95 | |
96 | // Make a copy so we can use the original for errors and the final return. |
97 | MemoryRegionInfo::RangeType remaining_range(tag_range); |
98 | |
99 | // While there are parts of the range that don't have a matching tagged memory |
100 | // region |
101 | while (remaining_range.IsValid()) { |
102 | // Search for a region that contains the start of the range |
103 | MemoryRegionInfos::const_iterator region = std::find_if( |
104 | first: memory_regions.cbegin(), last: memory_regions.cend(), |
105 | pred: [&remaining_range](const MemoryRegionInfo ®ion) { |
106 | return region.GetRange().Contains(r: remaining_range.GetRangeBase()); |
107 | }); |
108 | |
109 | if (region == memory_regions.cend() || |
110 | region->GetMemoryTagged() != MemoryRegionInfo::eYes) { |
111 | // Some part of this range is untagged (or unmapped) so error |
112 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
113 | Fmt: "Address range 0x%" PRIx64 ":0x%" PRIx64 |
114 | " is not in a memory tagged region" , |
115 | Vals: tag_range.GetRangeBase(), |
116 | Vals: tag_range.GetRangeEnd()); |
117 | } |
118 | |
119 | // We've found some part of the range so remove that part and continue |
120 | // searching for the rest. Moving the base "slides" the range so we need to |
121 | // save/restore the original end. If old_end is less than the new base, the |
122 | // range will be set to have 0 size and we'll exit the while. |
123 | lldb::addr_t old_end = remaining_range.GetRangeEnd(); |
124 | remaining_range.SetRangeBase(region->GetRange().GetRangeEnd()); |
125 | remaining_range.SetRangeEnd(old_end); |
126 | } |
127 | |
128 | // Every part of the range is contained within a tagged memory region. |
129 | return tag_range; |
130 | } |
131 | |
132 | llvm::Expected<std::vector<MemoryTagManager::TagRange>> |
133 | MemoryTagManagerAArch64MTE::MakeTaggedRanges( |
134 | lldb::addr_t addr, lldb::addr_t end_addr, |
135 | const lldb_private::MemoryRegionInfos &memory_regions) const { |
136 | // First check that the range is not inverted. |
137 | // We must remove tags here otherwise an address with a higher |
138 | // tag value will always be > the other. |
139 | ptrdiff_t len = AddressDiff(addr1: end_addr, addr2: addr); |
140 | if (len <= 0) |
141 | return MakeInvalidRangeErr(addr, end_addr); |
142 | |
143 | std::vector<MemoryTagManager::TagRange> tagged_ranges; |
144 | // No memory regions means no tagged memory at all |
145 | if (memory_regions.empty()) |
146 | return tagged_ranges; |
147 | |
148 | // For the logic to work regions must be in ascending order |
149 | // which is what you'd have if you used GetMemoryRegions. |
150 | assert(std::is_sorted( |
151 | memory_regions.begin(), memory_regions.end(), |
152 | [](const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) { |
153 | return lhs.GetRange().GetRangeBase() < rhs.GetRange().GetRangeBase(); |
154 | })); |
155 | |
156 | // If we're debugging userspace in an OS like Linux that uses an MMU, |
157 | // the only reason we'd get overlapping regions is incorrect data. |
158 | // It is possible that won't hold for embedded with memory protection |
159 | // units (MPUs) that allow overlaps. |
160 | // |
161 | // For now we're going to assume the former, as there is no good way |
162 | // to handle overlaps. For example: |
163 | // < requested range > |
164 | // [-- region 1 --] |
165 | // [-- region 2--] |
166 | // Where the first region will reduce the requested range to nothing |
167 | // and exit early before it sees the overlap. |
168 | MemoryRegionInfos::const_iterator overlap = std::adjacent_find( |
169 | first: memory_regions.begin(), last: memory_regions.end(), |
170 | binary_pred: [](const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) { |
171 | return rhs.GetRange().DoesIntersect(rhs: lhs.GetRange()); |
172 | }); |
173 | UNUSED_IF_ASSERT_DISABLED(overlap); |
174 | assert(overlap == memory_regions.end()); |
175 | |
176 | // Region addresses will not have memory tags so when searching |
177 | // we must use an untagged address. |
178 | MemoryRegionInfo::RangeType range(RemoveTagBits(addr), len); |
179 | range = ExpandToGranule(range); |
180 | |
181 | // While there are regions to check and the range has non zero length |
182 | for (const MemoryRegionInfo ®ion : memory_regions) { |
183 | // If range we're checking has been reduced to zero length, exit early |
184 | if (!range.IsValid()) |
185 | break; |
186 | |
187 | // If the region doesn't overlap the range at all, ignore it. |
188 | if (!region.GetRange().DoesIntersect(rhs: range)) |
189 | continue; |
190 | |
191 | // If it's tagged record this sub-range. |
192 | // (assuming that it's already granule aligned) |
193 | if (region.GetMemoryTagged()) { |
194 | // The region found may extend outside the requested range. |
195 | // For example the first region might start before the range. |
196 | // We must only add what covers the requested range. |
197 | lldb::addr_t start = |
198 | std::max(a: range.GetRangeBase(), b: region.GetRange().GetRangeBase()); |
199 | lldb::addr_t end = |
200 | std::min(a: range.GetRangeEnd(), b: region.GetRange().GetRangeEnd()); |
201 | tagged_ranges.push_back(x: MemoryTagManager::TagRange(start, end - start)); |
202 | } |
203 | |
204 | // Move the range up to start at the end of the region. |
205 | lldb::addr_t old_end = range.GetRangeEnd(); |
206 | // This "slides" the range so it moves the end as well. |
207 | range.SetRangeBase(region.GetRange().GetRangeEnd()); |
208 | // So we set the end back to the original end address after sliding it up. |
209 | range.SetRangeEnd(old_end); |
210 | // (if the above were to try to set end < begin the range will just be set |
211 | // to 0 size) |
212 | } |
213 | |
214 | return tagged_ranges; |
215 | } |
216 | |
217 | llvm::Expected<std::vector<lldb::addr_t>> |
218 | MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector<uint8_t> &tags, |
219 | size_t granules /*=0*/) const { |
220 | // 0 means don't check the number of tags before unpacking |
221 | if (granules) { |
222 | size_t num_tags = tags.size() / GetTagSizeInBytes(); |
223 | if (num_tags != granules) { |
224 | return llvm::createStringError( |
225 | EC: llvm::inconvertibleErrorCode(), |
226 | Fmt: "Packed tag data size does not match expected number of tags. " |
227 | "Expected %zu tag(s) for %zu granule(s), got %zu tag(s)." , |
228 | Vals: granules, Vals: granules, Vals: num_tags); |
229 | } |
230 | } |
231 | |
232 | // (if bytes per tag was not 1, we would reconstruct them here) |
233 | |
234 | std::vector<lldb::addr_t> unpacked; |
235 | unpacked.reserve(n: tags.size()); |
236 | for (auto it = tags.begin(); it != tags.end(); ++it) { |
237 | // Check all tags are in range |
238 | if (*it > MTE_TAG_MAX) { |
239 | return llvm::createStringError( |
240 | EC: llvm::inconvertibleErrorCode(), |
241 | Fmt: "Found tag 0x%x which is > max MTE tag value of 0x%x." , Vals: *it, |
242 | Vals: MTE_TAG_MAX); |
243 | } |
244 | unpacked.push_back(x: *it); |
245 | } |
246 | |
247 | return unpacked; |
248 | } |
249 | |
250 | std::vector<lldb::addr_t> |
251 | MemoryTagManagerAArch64MTE::UnpackTagsFromCoreFileSegment( |
252 | CoreReaderFn reader, lldb::addr_t tag_segment_virtual_address, |
253 | lldb::addr_t tag_segment_data_address, lldb::addr_t addr, |
254 | size_t len) const { |
255 | // We can assume by now that addr and len have been granule aligned by a tag |
256 | // manager. However because we have 2 tags per byte we need to round the range |
257 | // up again to align to 2 granule boundaries. |
258 | const size_t granule = GetGranuleSize(); |
259 | const size_t two_granules = granule * 2; |
260 | lldb::addr_t aligned_addr = addr; |
261 | size_t aligned_len = len; |
262 | |
263 | // First align the start address down. |
264 | if (aligned_addr % two_granules) { |
265 | assert(aligned_addr % two_granules == granule); |
266 | aligned_addr -= granule; |
267 | aligned_len += granule; |
268 | } |
269 | |
270 | // Then align the length up. |
271 | bool aligned_length_up = false; |
272 | if (aligned_len % two_granules) { |
273 | assert(aligned_len % two_granules == granule); |
274 | aligned_len += granule; |
275 | aligned_length_up = true; |
276 | } |
277 | |
278 | // ProcessElfCore should have validated this when it found the segment. |
279 | assert(aligned_addr >= tag_segment_virtual_address); |
280 | |
281 | // By now we know that aligned_addr is aligned to a 2 granule boundary. |
282 | const size_t offset_granules = |
283 | (aligned_addr - tag_segment_virtual_address) / granule; |
284 | // 2 tags per byte. |
285 | const size_t file_offset_in_bytes = offset_granules / 2; |
286 | |
287 | // By now we know that aligned_len is at least 2 granules. |
288 | const size_t tag_bytes_to_read = aligned_len / granule / 2; |
289 | std::vector<uint8_t> tag_data(tag_bytes_to_read); |
290 | const size_t bytes_copied = |
291 | reader(tag_segment_data_address + file_offset_in_bytes, tag_bytes_to_read, |
292 | tag_data.data()); |
293 | UNUSED_IF_ASSERT_DISABLED(bytes_copied); |
294 | assert(bytes_copied == tag_bytes_to_read); |
295 | |
296 | std::vector<lldb::addr_t> tags; |
297 | tags.reserve(n: 2 * tag_data.size()); |
298 | // No need to check the range of the tag value here as each occupies only 4 |
299 | // bits. |
300 | for (auto tag_byte : tag_data) { |
301 | tags.push_back(x: tag_byte & 0xf); |
302 | tags.push_back(x: tag_byte >> 4); |
303 | } |
304 | |
305 | // If we aligned the address down, don't return the extra first tag. |
306 | if (addr != aligned_addr) |
307 | tags.erase(position: tags.begin()); |
308 | // If we aligned the length up, don't return the extra last tag. |
309 | if (aligned_length_up) |
310 | tags.pop_back(); |
311 | |
312 | return tags; |
313 | } |
314 | |
315 | llvm::Expected<std::vector<uint8_t>> MemoryTagManagerAArch64MTE::PackTags( |
316 | const std::vector<lldb::addr_t> &tags) const { |
317 | std::vector<uint8_t> packed; |
318 | packed.reserve(n: tags.size() * GetTagSizeInBytes()); |
319 | |
320 | for (auto tag : tags) { |
321 | if (tag > MTE_TAG_MAX) { |
322 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
323 | Fmt: "Found tag 0x%" PRIx64 |
324 | " which is > max MTE tag value of 0x%x." , |
325 | Vals: tag, Vals: MTE_TAG_MAX); |
326 | } |
327 | packed.push_back(x: static_cast<uint8_t>(tag)); |
328 | } |
329 | |
330 | return packed; |
331 | } |
332 | |
333 | llvm::Expected<std::vector<lldb::addr_t>> |
334 | MemoryTagManagerAArch64MTE::RepeatTagsForRange( |
335 | const std::vector<lldb::addr_t> &tags, TagRange range) const { |
336 | std::vector<lldb::addr_t> new_tags; |
337 | |
338 | // If the range is not empty |
339 | if (range.IsValid()) { |
340 | if (tags.empty()) { |
341 | return llvm::createStringError( |
342 | EC: llvm::inconvertibleErrorCode(), |
343 | Msg: "Expected some tags to cover given range, got zero." ); |
344 | } |
345 | |
346 | // We assume that this range has already been expanded/aligned to granules |
347 | size_t granules = range.GetByteSize() / GetGranuleSize(); |
348 | new_tags.reserve(n: granules); |
349 | for (size_t to_copy = 0; granules > 0; granules -= to_copy) { |
350 | to_copy = granules > tags.size() ? tags.size() : granules; |
351 | new_tags.insert(position: new_tags.end(), first: tags.begin(), last: tags.begin() + to_copy); |
352 | } |
353 | } |
354 | |
355 | return new_tags; |
356 | } |
357 | |