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(addr1) - RemoveTagBits(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       llvm::inconvertibleErrorCode(),
75       "End address (0x%" PRIx64
76       ") must be greater than the start address (0x%" PRIx64 ")",
77       end_addr, 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(end_addr, 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(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         memory_regions.cbegin(), memory_regions.cend(),
105         [&remaining_range](const MemoryRegionInfo &region) {
106           return region.GetRange().Contains(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(llvm::inconvertibleErrorCode(),
113                                      "Address range 0x%" PRIx64 ":0x%" PRIx64
114                                      " is not in a memory tagged region",
115                                      tag_range.GetRangeBase(),
116                                      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(end_addr, 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       memory_regions.begin(), memory_regions.end(),
170       [](const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) {
171         return rhs.GetRange().DoesIntersect(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(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(range.GetRangeBase(), region.GetRange().GetRangeBase());
199       lldb::addr_t end =
200           std::min(range.GetRangeEnd(), region.GetRange().GetRangeEnd());
201       tagged_ranges.push_back(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           llvm::inconvertibleErrorCode(),
226           "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           granules, granules, 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(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           llvm::inconvertibleErrorCode(),
241           "Found tag 0x%x which is > max MTE tag value of 0x%x.", *it,
242           MTE_TAG_MAX);
243     }
244     unpacked.push_back(*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(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(tag_byte & 0xf);
302     tags.push_back(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(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(tags.size() * GetTagSizeInBytes());
319 
320   for (auto tag : tags) {
321     if (tag > MTE_TAG_MAX) {
322       return llvm::createStringError(llvm::inconvertibleErrorCode(),
323                                      "Found tag 0x%" PRIx64
324                                      " which is > max MTE tag value of 0x%x.",
325                                      tag, MTE_TAG_MAX);
326     }
327     packed.push_back(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           llvm::inconvertibleErrorCode(),
343           "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(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(new_tags.end(), tags.begin(), tags.begin() + to_copy);
352     }
353   }
354 
355   return new_tags;
356 }
357