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 @@ -64,7 +64,7 @@ // (which may include one or more memory regions) // // If so, return a modified range which will have been expanded - // to be granule aligned. + // to be granule aligned. Otherwise return an error. // // Tags in the input addresses are ignored and not present // in the returned range. @@ -72,6 +72,22 @@ 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. + // + // If any of memory_regions overlap each other, an error is returned. + // + // 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 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 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 MakeInvalidRangeErr(addr, end_addr); // Region addresses will not have memory tags. So when searching // we must use an untagged address. @@ -123,6 +128,98 @@ 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 MakeInvalidRangeErr(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. + // 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); + + // 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. + // However it is possible that this won't hold true for more niche + // targets, or embedded platforms with MPUs (memory protection units) + // where overlaps are allowed. + // + // There isn't a great way to handle this situation, so we'll just + // error. + // + // It is a shame to have to walk the regions up front but otherwise + // you have to handle a corner case like: + // < 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()); + }); + if (overlap != memory_regions.end()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Cannot calculate tagged ranges because " + "there are overlapping memory regions."); + + // While there are regions to check and the range has non zero length + for (const MemoryRegionInfo ®ion : 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> 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 @@ -165,6 +165,14 @@ llvm::FailedWithMessage( "End address (0x0) must be greater than the start address (0x1)")); + // The inversion check ignores tags in the addresses (MTE tags start at bit + // 56). + ASSERT_THAT_EXPECTED( + manager.MakeTaggedRange((lldb::addr_t)1 << 56, + ((lldb::addr_t)2 << 56) + 0x10, memory_regions), + llvm::FailedWithMessage( + "Address range 0x0:0x10 is not in a memory tagged region")); + // Adding a single region to cover the whole range memory_regions.push_back(MakeRegionInfo(0, 0x1000, true)); @@ -247,6 +255,111 @@ 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 + ASSERT_THAT_EXPECTED( + manager.MakeTaggedRanges(1, 0, memory_regions), + llvm::FailedWithMessage( + "End address (0x0) must be greater than the start address (0x1)")); + + // We remove tags before doing the inversion check, so this is not an error. + // Also no regions means no tagged regions returned. + // (bit 56 is where MTE tags begin) + llvm::Expected> got = + manager.MakeTaggedRanges((lldb::addr_t)2 << 56, + ((lldb::addr_t)1 << 56) + 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{}); + + // Overlapping regions is an error. + memory_regions.push_back(MakeRegionInfo(0x10, 0x20, false)); + ASSERT_THAT_EXPECTED( + manager.MakeTaggedRanges(0, 1, memory_regions), + llvm::FailedWithMessage("Cannot calculate tagged ranges because there " + "are overlapping memory regions.")); + memory_regions.pop_back(); + + // 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;