aboutsummaryrefslogtreecommitdiff
path: root/contrib/llvm-project/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp
blob: b71de4cadb18545c84b08b66cdcf8dd7751b5f44 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//===-- MemoryTagManagerAArch64MTE.cpp --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "MemoryTagManagerAArch64MTE.h"
#include "llvm/Support/Error.h"
#include <assert.h>

using namespace lldb_private;

static const unsigned MTE_START_BIT = 56;
static const unsigned MTE_TAG_MAX = 0xf;
static const unsigned MTE_GRANULE_SIZE = 16;

lldb::addr_t
MemoryTagManagerAArch64MTE::GetLogicalTag(lldb::addr_t addr) const {
  return (addr >> MTE_START_BIT) & MTE_TAG_MAX;
}

lldb::addr_t
MemoryTagManagerAArch64MTE::RemoveTagBits(lldb::addr_t addr) const {
  // Here we're ignoring the whole top byte. If you've got MTE
  // you must also have TBI (top byte ignore).
  // The other 4 bits could contain other extension bits or
  // user metadata.
  return addr & ~((lldb::addr_t)0xFF << MTE_START_BIT);
}

ptrdiff_t MemoryTagManagerAArch64MTE::AddressDiff(lldb::addr_t addr1,
                                                  lldb::addr_t addr2) const {
  return RemoveTagBits(addr1) - RemoveTagBits(addr2);
}

lldb::addr_t MemoryTagManagerAArch64MTE::GetGranuleSize() const {
  return MTE_GRANULE_SIZE;
}

int32_t MemoryTagManagerAArch64MTE::GetAllocationTagType() const {
  return eMTE_allocation;
}

size_t MemoryTagManagerAArch64MTE::GetTagSizeInBytes() const { return 1; }

MemoryTagManagerAArch64MTE::TagRange
MemoryTagManagerAArch64MTE::ExpandToGranule(TagRange range) const {
  // Ignore reading a length of 0
  if (!range.IsValid())
    return range;

  const size_t granule = GetGranuleSize();

  // Align start down to granule start
  lldb::addr_t new_start = range.GetRangeBase();
  lldb::addr_t align_down_amount = new_start % granule;
  new_start -= align_down_amount;

  // Account for the distance we moved the start above
  size_t new_len = range.GetByteSize() + align_down_amount;
  // Then align up to the end of the granule
  size_t align_up_amount = granule - (new_len % granule);
  if (align_up_amount != granule)
    new_len += align_up_amount;

  return TagRange(new_start, new_len);
}

static llvm::Error MakeInvalidRangeErr(lldb::addr_t addr,
                                       lldb::addr_t end_addr) {
  return llvm::createStringError(
      llvm::inconvertibleErrorCode(),
      "End address (0x%" PRIx64
      ") must be greater than the start address (0x%" PRIx64 ")",
      end_addr, addr);
}

llvm::Expected<MemoryTagManager::TagRange>
MemoryTagManagerAArch64MTE::MakeTaggedRange(
    lldb::addr_t addr, lldb::addr_t end_addr,
    const lldb_private::MemoryRegionInfos &memory_regions) const {
  // First check that the range is not inverted.
  // We must remove tags here otherwise an address with a higher
  // tag value will always be > the other.
  ptrdiff_t len = AddressDiff(end_addr, addr);
  if (len <= 0)
    return MakeInvalidRangeErr(addr, end_addr);

  // Region addresses will not have memory tags. So when searching
  // we must use an untagged address.
  MemoryRegionInfo::RangeType tag_range(RemoveTagBits(addr), len);
  tag_range = ExpandToGranule(tag_range);

  // Make a copy so we can use the original for errors and the final return.
  MemoryRegionInfo::RangeType remaining_range(tag_range);

  // While there are parts of the range that don't have a matching tagged memory
  // region
  while (remaining_range.IsValid()) {
    // Search for a region that contains the start of the range
    MemoryRegionInfos::const_iterator region = std::find_if(
        memory_regions.cbegin(), memory_regions.cend(),
        [&remaining_range](const MemoryRegionInfo &region) {
          return region.GetRange().Contains(remaining_range.GetRangeBase());
        });

    if (region == memory_regions.cend() ||
        region->GetMemoryTagged() != MemoryRegionInfo::eYes) {
      // Some part of this range is untagged (or unmapped) so error
      return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                     "Address range 0x%" PRIx64 ":0x%" PRIx64
                                     " is not in a memory tagged region",
                                     tag_range.GetRangeBase(),
                                     tag_range.GetRangeEnd());
    }

    // We've found some part of the range so remove that part and continue
    // searching for the rest. Moving the base "slides" the range so we need to
    // save/restore the original end. If old_end is less than the new base, the
    // range will be set to have 0 size and we'll exit the while.
    lldb::addr_t old_end = remaining_range.GetRangeEnd();
    remaining_range.SetRangeBase(region->GetRange().GetRangeEnd());
    remaining_range.SetRangeEnd(old_end);
  }

  // Every part of the range is contained within a tagged memory region.
  return tag_range;
}

llvm::Expected<std::vector<MemoryTagManager::TagRange>>
MemoryTagManagerAArch64MTE::MakeTaggedRanges(
    lldb::addr_t addr, lldb::addr_t end_addr,
    const lldb_private::MemoryRegionInfos &memory_regions) const {
  // First check that the range is not inverted.
  // We must remove tags here otherwise an address with a higher
  // tag value will always be > the other.
  ptrdiff_t len = AddressDiff(end_addr, addr);
  if (len <= 0)
    return MakeInvalidRangeErr(addr, end_addr);

  std::vector<MemoryTagManager::TagRange> tagged_ranges;
  // No memory regions means no tagged memory at all
  if (memory_regions.empty())
    return tagged_ranges;

  // For the logic to work regions must be in ascending order
  // which is what you'd have if you used GetMemoryRegions.
  assert(std::is_sorted(
      memory_regions.begin(), memory_regions.end(),
      [](const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) {
        return lhs.GetRange().GetRangeBase() < rhs.GetRange().GetRangeBase();
      }));

  // If we're debugging userspace in an OS like Linux that uses an MMU,
  // the only reason we'd get overlapping regions is incorrect data.
  // It is possible that won't hold for embedded with memory protection
  // units (MPUs) that allow overlaps.
  //
  // For now we're going to assume the former, as there is no good way
  // to handle overlaps. For example:
  // < requested range >
  // [--  region 1   --]
  //           [-- region 2--]
  // Where the first region will reduce the requested range to nothing
  // and exit early before it sees the overlap.
  MemoryRegionInfos::const_iterator overlap = std::adjacent_find(
      memory_regions.begin(), memory_regions.end(),
      [](const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) {
        return rhs.GetRange().DoesIntersect(lhs.GetRange());
      });
  UNUSED_IF_ASSERT_DISABLED(overlap);
  assert(overlap == memory_regions.end());

  // Region addresses will not have memory tags so when searching
  // we must use an untagged address.
  MemoryRegionInfo::RangeType range(RemoveTagBits(addr), len);
  range = ExpandToGranule(range);

  // While there are regions to check and the range has non zero length
  for (const MemoryRegionInfo &region : memory_regions) {
    // If range we're checking has been reduced to zero length, exit early
    if (!range.IsValid())
      break;

    // If the region doesn't overlap the range at all, ignore it.
    if (!region.GetRange().DoesIntersect(range))
      continue;

    // If it's tagged record this sub-range.
    // (assuming that it's already granule aligned)
    if (region.GetMemoryTagged()) {
      // The region found may extend outside the requested range.
      // For example the first region might start before the range.
      // We must only add what covers the requested range.
      lldb::addr_t start =
          std::max(range.GetRangeBase(), region.GetRange().GetRangeBase());
      lldb::addr_t end =
          std::min(range.GetRangeEnd(), region.GetRange().GetRangeEnd());
      tagged_ranges.push_back(MemoryTagManager::TagRange(start, end - start));
    }

    // Move the range up to start at the end of the region.
    lldb::addr_t old_end = range.GetRangeEnd();
    // This "slides" the range so it moves the end as well.
    range.SetRangeBase(region.GetRange().GetRangeEnd());
    // So we set the end back to the original end address after sliding it up.
    range.SetRangeEnd(old_end);
    // (if the above were to try to set end < begin the range will just be set
    // to 0 size)
  }

  return tagged_ranges;
}

llvm::Expected<std::vector<lldb::addr_t>>
MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector<uint8_t> &tags,
                                           size_t granules /*=0*/) const {
  // 0 means don't check the number of tags before unpacking
  if (granules) {
    size_t num_tags = tags.size() / GetTagSizeInBytes();
    if (num_tags != granules) {
      return llvm::createStringError(
          llvm::inconvertibleErrorCode(),
          "Packed tag data size does not match expected number of tags. "
          "Expected %zu tag(s) for %zu granule(s), got %zu tag(s).",
          granules, granules, num_tags);
    }
  }

  // (if bytes per tag was not 1, we would reconstruct them here)

  std::vector<lldb::addr_t> unpacked;
  unpacked.reserve(tags.size());
  for (auto it = tags.begin(); it != tags.end(); ++it) {
    // Check all tags are in range
    if (*it > MTE_TAG_MAX) {
      return llvm::createStringError(
          llvm::inconvertibleErrorCode(),
          "Found tag 0x%x which is > max MTE tag value of 0x%x.", *it,
          MTE_TAG_MAX);
    }
    unpacked.push_back(*it);
  }

  return unpacked;
}

llvm::Expected<std::vector<uint8_t>> MemoryTagManagerAArch64MTE::PackTags(
    const std::vector<lldb::addr_t> &tags) const {
  std::vector<uint8_t> packed;
  packed.reserve(tags.size() * GetTagSizeInBytes());

  for (auto tag : tags) {
    if (tag > MTE_TAG_MAX) {
      return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                     "Found tag 0x%" PRIx64
                                     " which is > max MTE tag value of 0x%x.",
                                     tag, MTE_TAG_MAX);
    }
    packed.push_back(static_cast<uint8_t>(tag));
  }

  return packed;
}

llvm::Expected<std::vector<lldb::addr_t>>
MemoryTagManagerAArch64MTE::RepeatTagsForRange(
    const std::vector<lldb::addr_t> &tags, TagRange range) const {
  std::vector<lldb::addr_t> new_tags;

  // If the range is not empty
  if (range.IsValid()) {
    if (tags.empty()) {
      return llvm::createStringError(
          llvm::inconvertibleErrorCode(),
          "Expected some tags to cover given range, got zero.");
    }

    // We assume that this range has already been expanded/aligned to granules
    size_t granules = range.GetByteSize() / GetGranuleSize();
    new_tags.reserve(granules);
    for (size_t to_copy = 0; granules > 0; granules -= to_copy) {
      to_copy = granules > tags.size() ? tags.size() : granules;
      new_tags.insert(new_tags.end(), tags.begin(), tags.begin() + to_copy);
    }
  }

  return new_tags;
}