diff --git a/lldb/include/lldb/Target/MemoryTagManager.h b/lldb/include/lldb/Target/MemoryTagManager.h --- a/lldb/include/lldb/Target/MemoryTagManager.h +++ b/lldb/include/lldb/Target/MemoryTagManager.h @@ -72,6 +72,20 @@ lldb::addr_t addr, lldb::addr_t end_addr, const lldb_private::MemoryRegionInfos &memory_regions) const = 0; + // Given a range addr to end_addr, check that end_addr >= addr. + // If it is not, return an error saying so. + // Otherwise, granule align it and return a set of ranges representing + // subsections of the aligned range that have memory tagging enabled. + // + // Basically a sparse version of MakeTaggedRange. Use this when you + // want to know which parts of a larger range have memory tagging. + // + // Tags in the input addresses are ignored and not present + // in the returned range. + virtual llvm::Expected> + MakeTaggedRanges(lldb::addr_t addr, lldb::addr_t end_addr, + lldb_private::MemoryRegionInfos memory_regions) const = 0; + // Return the type value to use in GDB protocol qMemTags packets to read // allocation tags. This is named "Allocation" specifically because the spec // allows for logical tags to be read the same way, though we do not use that. diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h --- a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h +++ b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h @@ -36,6 +36,10 @@ lldb::addr_t addr, lldb::addr_t end_addr, const lldb_private::MemoryRegionInfos &memory_regions) const override; + llvm::Expected> MakeTaggedRanges( + lldb::addr_t addr, lldb::addr_t end_addr, + lldb_private::MemoryRegionInfos memory_regions) const override; + llvm::Expected> UnpackTagsData(const std::vector &tags, size_t granules = 0) const override; diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp --- a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp +++ b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "MemoryTagManagerAArch64MTE.h" +#include "llvm/Support/Error.h" using namespace lldb_private; @@ -66,6 +67,15 @@ return TagRange(new_start, new_len); } +static llvm::Error make_invalid_range_err(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 MemoryTagManagerAArch64MTE::MakeTaggedRange( lldb::addr_t addr, lldb::addr_t end_addr, @@ -74,13 +84,8 @@ // 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 llvm::createStringError( - llvm::inconvertibleErrorCode(), - "End address (0x%" PRIx64 - ") must be greater than the start address (0x%" PRIx64 ")", - end_addr, addr); - } + if (len <= 0) + return make_invalid_range_err(addr, end_addr); // Region addresses will not have memory tags. So when searching // we must use an untagged address. @@ -123,6 +128,73 @@ return tag_range; } +llvm::Expected> +MemoryTagManagerAArch64MTE::MakeTaggedRanges( + lldb::addr_t addr, lldb::addr_t end_addr, + 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 make_invalid_range_err(addr, end_addr); + + std::vector 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. + // We're going to assume that there are no overlapping regions + // and that each region is granule aligned already. + // Regions are most likely multiples of page size and granules + // are some smaller division of that. + std::sort(memory_regions.begin(), memory_regions.end(), + [](const MemoryRegionInfo &lhs, const MemoryRegionInfo &rhs) { + return lhs.GetRange().GetRangeBase() < + rhs.GetRange().GetRangeBase(); + }); + + // Region addresses will not have memory tags so when searching + // we must use an untagged address. + MemoryRegionInfo::RangeType range(RemoveNonAddressBits(addr), len); + range = ExpandToGranule(range); + + MemoryRegionInfos::const_iterator region = memory_regions.begin(); + MemoryRegionInfos::const_iterator end_region = memory_regions.end(); + + // While there are regions to check and the range has non zero length + for (; region != end_region && range.IsValid(); ++region) { + // 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> MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector &tags, size_t granules /*=0*/) const { diff --git a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp --- a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp +++ b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp @@ -247,6 +247,101 @@ ASSERT_EQ(*got, expected_range); } +TEST(MemoryTagManagerAArch64MTETest, MakeTaggedRanges) { + MemoryTagManagerAArch64MTE manager; + MemoryRegionInfos memory_regions; + + // Note that MakeTaggedRanges takes start/end address. + // Whereas TagRanges and regions take start address and size. + + // Range must not be inverted + // (this is the only error situation) + ASSERT_THAT_EXPECTED( + manager.MakeTaggedRange(1, 0, memory_regions), + llvm::FailedWithMessage( + "End address (0x0) must be greater than the start address (0x1)")); + + // No regions means no tagged regions, no ranges returned. + llvm::Expected> got = + manager.MakeTaggedRanges(0, 0x10, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, std::vector{}); + + // Cover whole range, untagged. No ranges returned. + memory_regions.push_back(MakeRegionInfo(0, 0x20, false)); + got = manager.MakeTaggedRanges(0, 0x20, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, std::vector{}); + + // Make the region tagged and it'll be the one range returned. + memory_regions.back().SetMemoryTagged(MemoryRegionInfo::eYes); + got = manager.MakeTaggedRanges(0, 0x20, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, std::vector{ + MemoryTagManager::TagRange(0, 0x20)}); + + // This region will be trimmed if it's larger than the whole range. + memory_regions.clear(); + memory_regions.push_back(MakeRegionInfo(0, 0x40, true)); + got = manager.MakeTaggedRanges(0x10, 0x30, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, std::vector{ + MemoryTagManager::TagRange(0x10, 0x20)}); + + memory_regions.clear(); + + // Only start of range is tagged, only that is returned. + // Start the region just before the requested range to check + // we limit the result to the requested range. + memory_regions.push_back(MakeRegionInfo(0, 0x20, true)); + got = manager.MakeTaggedRanges(0x10, 0x100, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, std::vector{ + MemoryTagManager::TagRange(0x10, 0x10)}); + + // Add a tagged region at the end, now we get both + // and the middle is untagged. + // The range added here is deliberately over the end of the + // requested range to show that we trim the end. + memory_regions.push_back(MakeRegionInfo(0xE0, 0x40, true)); + got = manager.MakeTaggedRanges(0x10, 0x110, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + + std::vector expected{ + MemoryTagManager::TagRange(0x10, 0x10), + MemoryTagManager::TagRange(0xE0, 0x30)}; + ASSERT_EQ(*got, expected); + + // Now add a middle tagged region. + memory_regions.push_back(MakeRegionInfo(0x90, 0x20, true)); + // MakeTaggedRanges will sort the regions it is given, so the output + // is always in ascending address order. So this goes in the middle + // of expected. + expected.insert(std::next(expected.begin()), + MemoryTagManager::TagRange(0x90, 0x20)); + got = manager.MakeTaggedRanges(0x10, 0x110, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, expected); + + // Then if we add untagged regions in between the tagged, + // the output should stay the same. + memory_regions.push_back(MakeRegionInfo(0x20, 0x30, false)); + memory_regions.push_back(MakeRegionInfo(0xC0, 0x10, false)); + got = manager.MakeTaggedRanges(0x10, 0x110, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, expected); + + // Finally check that we handle only having the end of the range. + memory_regions.clear(); + expected.clear(); + + memory_regions.push_back(MakeRegionInfo(0x100, 0x10, true)); + expected.push_back(MemoryTagManager::TagRange(0x100, 0x10)); + got = manager.MakeTaggedRanges(0x10, 0x110, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, expected); +} + TEST(MemoryTagManagerAArch64MTETest, RemoveNonAddressBits) { MemoryTagManagerAArch64MTE manager;