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
13using namespace lldb_private;
14
15static const unsigned MTE_START_BIT = 56;
16static const unsigned MTE_TAG_MAX = 0xf;
17static const unsigned MTE_GRANULE_SIZE = 16;
18
19lldb::addr_t
20MemoryTagManagerAArch64MTE::GetLogicalTag(lldb::addr_t addr) const {
21 return (addr >> MTE_START_BIT) & MTE_TAG_MAX;
22}
23
24lldb::addr_t
25MemoryTagManagerAArch64MTE::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
33ptrdiff_t MemoryTagManagerAArch64MTE::AddressDiff(lldb::addr_t addr1,
34 lldb::addr_t addr2) const {
35 return RemoveTagBits(addr: addr1) - RemoveTagBits(addr: addr2);
36}
37
38lldb::addr_t MemoryTagManagerAArch64MTE::GetGranuleSize() const {
39 return MTE_GRANULE_SIZE;
40}
41
42int32_t MemoryTagManagerAArch64MTE::GetAllocationTagType() const {
43 return eMTE_allocation;
44}
45
46size_t MemoryTagManagerAArch64MTE::GetTagSizeInBytes() const { return 1; }
47
48MemoryTagManagerAArch64MTE::TagRange
49MemoryTagManagerAArch64MTE::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
71static 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
80llvm::Expected<MemoryTagManager::TagRange>
81MemoryTagManagerAArch64MTE::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 &region) {
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
132llvm::Expected<std::vector<MemoryTagManager::TagRange>>
133MemoryTagManagerAArch64MTE::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 &region : 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
217llvm::Expected<std::vector<lldb::addr_t>>
218MemoryTagManagerAArch64MTE::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
250std::vector<lldb::addr_t>
251MemoryTagManagerAArch64MTE::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
315llvm::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
333llvm::Expected<std::vector<lldb::addr_t>>
334MemoryTagManagerAArch64MTE::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

source code of lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp