DavidSpickett created this revision. Herald added subscribers: mgrang, mgorny. DavidSpickett requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
This extends DumpDataExtractor to also print memory tags if called with an execution context on a target that has memory tagging. (lldb) memory read mte_buf mte_buf+32 -f "x" -s8 0x900fffff7ff8000: 0x0000000000000000 0x0000000000000000 (tag: 0x0) 0x900fffff7ff8010: 0x0000000000000000 0x0000000000000000 (tag: 0x1) Tags are printed on the end of each line, if that line has any tags associated with it. Meaning that untagged memory output is unchanged. Tags are printed based on the granule(s) of memory that a line covers. So you may have lines with 1 tag, with many tags, no tags or partially tagged lines. There are no special alignment requirements, simply use "memory read" as usual. All alignment is handled in DumpDataExtractor. In the case of partially tagged lines, untagged granules will show "<no tag>" so that the ordering is obvious. For example, a line that covers 2 granules where the first is not tagged: (lldb) memory read mte_buf-16 mte_buf+16 -l32 -f"x" 0x900fffff7ff7ff0: 0x00000000 <...> (tags: <no tag> 0x0) The tags are read from a new class MemoryTagMap which is a sparse map of tag read results. Using the new tag manager method MakeTaggedRanges you can get all the sub-ranges of a larger range, that have memory tagging. Then loop over these calling ReadMemoryTags, insert them into the map and request as needed when printing memory dumps. The tag map is populated once in DumpDataExtractor and re-used for each subsequently printed line (or recursive call of DumpDataExtractor, which some formats do). Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D107140 Files: lldb/include/lldb/Target/MemoryTagManager.h lldb/include/lldb/Target/Process.h lldb/include/lldb/Utility/MemoryTagMap.h lldb/source/Core/DumpDataExtractor.cpp lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h lldb/source/Utility/CMakeLists.txt lldb/source/Utility/MemoryTagMap.cpp lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp lldb/unittests/Utility/CMakeLists.txt lldb/unittests/Utility/MemoryTagMapTest.cpp
Index: lldb/unittests/Utility/MemoryTagMapTest.cpp =================================================================== --- /dev/null +++ lldb/unittests/Utility/MemoryTagMapTest.cpp @@ -0,0 +1,81 @@ +//===-- StatusTest.cpp ----------------------------------------------------===// +// +// 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 "lldb/Utility/MemoryTagMap.h" +#include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using namespace lldb_private; +using namespace lldb; + +// In these tests we use the AArch64 MTE tag manager because it is the only +// implementation of a memory tag manager. MemoryTagMap itself is generic. + +TEST(MemoryTagMapTest, EmptyTagMap) { + MemoryTagManagerAArch64MTE manager; + MemoryTagMap tag_map(&manager); + + tag_map.InsertTags(0, {}); + ASSERT_TRUE(tag_map.empty()); + tag_map.InsertTags(0, {0}); + ASSERT_FALSE(tag_map.empty()); +} + +TEST(MemoryTagMapTest, GetTags) { + using TagsVec = std::vector<llvm::Optional<lldb::addr_t>>; + + MemoryTagManagerAArch64MTE manager; + MemoryTagMap tag_map(&manager); + + // No tags for an address not in the map + ASSERT_TRUE(tag_map.GetTags(0, 16).empty()); + + tag_map.InsertTags(0, {0, 1}); + + // No tags if you read zero length + ASSERT_TRUE(tag_map.GetTags(0, 0).empty()); + + EXPECT_THAT(tag_map.GetTags(0, 16), ::testing::ContainerEq(TagsVec{0})); + + EXPECT_THAT(tag_map.GetTags(0, 32), ::testing::ContainerEq(TagsVec{0, 1})); + + // Last granule of the range is not tagged + EXPECT_THAT(tag_map.GetTags(0, 48), + ::testing::ContainerEq(TagsVec{0, 1, llvm::None})); + + EXPECT_THAT(tag_map.GetTags(16, 32), + ::testing::ContainerEq(TagsVec{1, llvm::None})); + + // Reading beyond that address gives you no tags at all + EXPECT_THAT(tag_map.GetTags(32, 16), ::testing::ContainerEq(TagsVec{})); + + // Address is granule aligned for you + // The length here is set such that alignment doesn't produce a 2 granule + // range. + EXPECT_THAT(tag_map.GetTags(8, 8), ::testing::ContainerEq(TagsVec{0})); + + EXPECT_THAT(tag_map.GetTags(30, 2), ::testing::ContainerEq(TagsVec{1})); + + // Here the length pushes the range into the next granule. When aligned + // this produces 2 granules. + EXPECT_THAT(tag_map.GetTags(30, 4), + ::testing::ContainerEq(TagsVec{1, llvm::None})); + + // A range can also have gaps at the beginning or in the middle. + // Add more tags, 1 granule away from the first range. + tag_map.InsertTags(48, {3, 4}); + + // Untagged first granule + EXPECT_THAT(tag_map.GetTags(32, 32), + ::testing::ContainerEq(TagsVec{llvm::None, 3})); + + // Untagged middle granule + EXPECT_THAT(tag_map.GetTags(16, 48), + ::testing::ContainerEq(TagsVec{1, llvm::None, 3})); +} Index: lldb/unittests/Utility/CMakeLists.txt =================================================================== --- lldb/unittests/Utility/CMakeLists.txt +++ lldb/unittests/Utility/CMakeLists.txt @@ -14,6 +14,7 @@ ListenerTest.cpp LogTest.cpp NameMatchesTest.cpp + MemoryTagMapTest.cpp PredicateTest.cpp ProcessInfoTest.cpp ProcessInstanceInfoTest.cpp @@ -46,6 +47,7 @@ XcodeSDKTest.cpp LINK_LIBS + lldbPluginProcessUtility lldbUtility lldbUtilityHelpers LLVMTestingSupport Index: lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp =================================================================== --- lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp +++ lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp @@ -246,6 +246,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<std::vector<MemoryTagManager::TagRange>> got = + manager.MakeTaggedRanges(0, 0x10, memory_regions); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_EQ(*got, std::vector<MemoryTagManager::TagRange>{}); + + // 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<MemoryTagManager::TagRange>{}); + + // 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>{ + 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>{ + 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>{ + 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<MemoryTagManager::TagRange> 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; Index: lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py =================================================================== --- lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py +++ lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py @@ -280,3 +280,124 @@ "\[0x[0-9A-Fa-f]+10, 0x[0-9A-Fa-f]+20\): 0x3 \(mismatch\)\n" "\[0x[0-9A-Fa-f]+20, 0x[0-9A-Fa-f]+30\): 0x3 \(mismatch\)\n" "\[0x[0-9A-Fa-f]+30, 0x[0-9A-Fa-f]+40\): 0x0$"]) + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + @skipUnlessAArch64MTELinuxCompiler + def test_mte_memory_read_tag_display(self): + self.setup_mte_test() + + # Reading from an untagged range should not be any different + self.expect("memory read non_mte_buf non_mte_buf+16", + substrs=["(tag"], matching=False) + + # Reading 16 bytes per line means 1 granule and so 1 tag per line + self.expect("memory read mte_buf mte_buf+32 -f \"x\" -l 1 -s 16", + patterns=[ + "0x[0-9A-fa-f]+00: 0x0+ \(tag: 0x0\)\n" + "0x[0-9A-fa-f]+10: 0x0+ \(tag: 0x1\)" + ]) + + # If bytes per line is > granule size then you get multiple tags + # per line. + self.expect("memory read mte_buf mte_buf+32 -f \"x\" -l 1 -s 32", + patterns=[ + "0x[0-9A-fa-f]+00: 0x0+ \(tags: 0x0 0x1\)\n" + ]) + + # Reading half a granule still shows you the tag for that granule + self.expect("memory read mte_buf mte_buf+8 -f \"x\" -l 1 -s 8", + patterns=[ + "0x[0-9A-fa-f]+00: 0x0+ \(tag: 0x0\)\n" + ]) + + # We can read a whole number of granules but split them over more lines + # than there are granules. Tags are shown repeated for each applicable line. + self.expect("memory read mte_buf+32 mte_buf+64 -f \"x\" -l 1 -s 8", + patterns=[ + "0x[0-9A-fa-f]+20: 0x0+ \(tag: 0x2\)\n" + "0x[0-9A-fa-f]+28: 0x0+ \(tag: 0x2\)\n" + "0x[0-9A-fa-f]+30: 0x0+ \(tag: 0x3\)\n" + "0x[0-9A-fa-f]+38: 0x0+ \(tag: 0x3\)" + ]) + + # Also works if we misalign the start address. Note the first tag is shown + # only once here and we have a new tag on the last line. + # (bytes per line == the misalignment here) + self.expect("memory read mte_buf+32+8 mte_buf+64+8 -f \"x\" -l 1 -s 8", + patterns=[ + "0x[0-9A-fa-f]+28: 0x0+ \(tag: 0x2\)\n" + "0x[0-9A-fa-f]+30: 0x0+ \(tag: 0x3\)\n" + "0x[0-9A-fa-f]+38: 0x0+ \(tag: 0x3\)\n" + "0x[0-9A-fa-f]+40: 0x0+ \(tag: 0x4\)" + ]) + + # We can do the same thing but where the misaligment isn't equal to + # bytes per line. This time, some lines cover multiple granules and + # so show multiple tags. + self.expect("memory read mte_buf+32+4 mte_buf+64+4 -f \"x\" -l 1 -s 8", + patterns=[ + "0x[0-9A-fa-f]+24: 0x0+ \(tag: 0x2\)\n" + "0x[0-9A-fa-f]+2c: 0x0+ \(tags: 0x2 0x3\)\n" + "0x[0-9A-fa-f]+34: 0x0+ \(tag: 0x3\)\n" + "0x[0-9A-fa-f]+3c: 0x0+ \(tags: 0x3 0x4\)" + ]) + + # If you read a range that includes non tagged areas those areas + # simply aren't annotated. + + # Initial part of range is untagged + self.expect("memory read mte_buf-16 mte_buf+32 -f \"x\" -l 1 -s 16", + patterns=[ + "0x[0-9A-fa-f]+f0: 0x0+\n" + "0x[0-9A-fa-f]+00: 0x0+ \(tag: 0x0\)\n" + "0x[0-9A-fa-f]+10: 0x0+ \(tag: 0x1\)" + ]) + + # End of range is untagged + self.expect("memory read mte_buf+page_size-16 mte_buf+page_size+16 -f \"x\" -l 1 -s 16", + patterns=[ + "0x[0-9A-fa-f]+f0: 0x0+ \(tag: 0xf\)\n" + "0x[0-9A-fa-f]+00: 0x0+" + ]) + + # The smallest MTE range we can get is a single page so we just check + # parts of this result. Where we read from before the tagged page to after it. + # Add --force here because we're reading just over 4k. + self.expect( + "memory read mte_read_only-16 mte_read_only+page_size+16 -f \"x\" -l 1 -s 16 --force", + patterns=[ + "0x[0-9A-fa-f]+f0: 0x0+\n" + "0x[0-9A-fa-f]+00: 0x0+ \(tag: 0x0\)\n", + "0x[0-9A-fa-f]+f0: 0x0+ \(tag: 0x0\)\n" + "0x[0-9A-fa-f]+00: 0x0+" + ]) + + # Some parts of a line might be tagged and others untagged. + # <no tag> is shown in where the tag would be, to keep the order intact. + self.expect("memory read mte_buf-16 mte_buf+32 -f \"x\" -l 1 -s 32", + patterns=["0x[0-9A-fa-f]+f0: 0x0+ \(tags: <no tag> 0x0\)"]) + self.expect( + "memory read mte_read_only+page_size-16 mte_read_only+page_size+16 -f \"x\" -l 1 -s 32", + patterns=["0x[0-9A-fa-f]+f0: 0x0+ \(tags: 0x0 <no tag>\)"]) + + # Here the start address is unaligned so we cover 3 granules instead of 2 + self.expect("memory read mte_buf-16+4 mte_buf+32+4 -f \"x\" -l 1 -s 32", + patterns=["0x[0-9A-fa-f]+f4: 0x0+ \(tags: <no tag> 0x0 0x1\)"]) + self.expect( + "memory read mte_read_only+page_size-16+4 mte_read_only+page_size+16+4 -f \"x\" -l 1 -s 32", + patterns=["0x[0-9A-fa-f]+f4: 0x0+ \(tags: 0x0 <no tag> <no tag>\)"]) + + # Some formats call DumpDataExtractor multiple times, + # check that those stil print tags only once per line. + self.expect("memory read mte_buf mte_buf+32 -f \"x\"", + patterns=["0x[0-9A-fa-f]+00: 0x0+ 0x0+ 0x0+ 0x0+ \(tag: 0x0\)\n", + "0x[0-9A-fa-f]+10: 0x0+ 0x0+ 0x0+ 0x0+ \(tag: 0x1\)"]) + + self.expect("memory read mte_buf mte_buf+32 -f \"bytes with ASCII\"", + patterns=["0x[0-9A-fa-f]+00: (00 ){16} \.{16} \(tag: 0x0\)\n", + "0x[0-9A-fa-f]+10: (00 ){16} \.{16} \(tag: 0x1\)"]) + + self.expect("memory read mte_buf mte_buf+32 -f \"uint8_t[]\" -s 16 -l 1", + patterns=["0x[0-9A-Fa-f]+00: \{(0x00 ){15}0x00\} \(tag: 0x0\)\n" + "0x[0-9A-Fa-f]+10: \{(0x00 ){15}0x00\} \(tag: 0x1\)"]) Index: lldb/source/Utility/MemoryTagMap.cpp =================================================================== --- /dev/null +++ lldb/source/Utility/MemoryTagMap.cpp @@ -0,0 +1,64 @@ +//===-- MemoryTagMap.cpp --------------------------------------------------===// +// +// 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 "lldb/Utility/MemoryTagMap.h" + +using namespace lldb_private; + +MemoryTagMap::MemoryTagMap(const MemoryTagManager *manager) + : m_manager(manager) { + assert(m_manager && "valid tag manager required to construct a MemoryTagMap"); +} + +void MemoryTagMap::InsertTags(lldb::addr_t addr, + const std::vector<lldb::addr_t> tags) { + // We're assuming that addr has no non address bits and is granule aligned. + size_t granule_size = m_manager->GetGranuleSize(); + for (auto tag : tags) { + m_addr_to_tag[addr] = tag; + addr += granule_size; + } +} + +bool MemoryTagMap::empty() const { return m_addr_to_tag.empty(); } + +std::vector<llvm::Optional<lldb::addr_t>> +MemoryTagMap::GetTags(lldb::addr_t addr, size_t len) const { + // Addr and len might be unaligned + addr = m_manager->RemoveNonAddressBits(addr); + MemoryTagManager::TagRange range(addr, len); + range = m_manager->ExpandToGranule(range); + + std::vector<llvm::Optional<lldb::addr_t>> tags; + lldb::addr_t end_addr = range.GetRangeEnd(); + addr = range.GetRangeBase(); + bool got_valid_tags = false; + size_t granule_size = m_manager->GetGranuleSize(); + + for (; addr < end_addr; addr += granule_size) { + llvm::Optional<lldb::addr_t> tag = GetTag(addr); + tags.push_back(tag); + if (tag) + got_valid_tags = true; + } + + // To save the caller checking if every item is llvm::None, + // we return an empty vector if we got no tags at all. + if (got_valid_tags) + return tags; + return {}; +} + +llvm::Optional<lldb::addr_t> MemoryTagMap::GetTag(lldb::addr_t addr) const { + // Here we assume that addr is granule aligned, just like when the tags + // were inserted. + auto found = m_addr_to_tag.find(addr); + if (found == m_addr_to_tag.end()) + return llvm::None; + return found->second; +} Index: lldb/source/Utility/CMakeLists.txt =================================================================== --- lldb/source/Utility/CMakeLists.txt +++ lldb/source/Utility/CMakeLists.txt @@ -45,6 +45,7 @@ Log.cpp Logging.cpp NameMatches.cpp + MemoryTagMap.cpp ProcessInfo.cpp RegisterValue.cpp RegularExpression.cpp Index: lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h =================================================================== --- lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h +++ 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<std::vector<TagRange>> MakeTaggedRanges( + lldb::addr_t addr, lldb::addr_t end_addr, + lldb_private::MemoryRegionInfos memory_regions) const override; + llvm::Expected<std::vector<lldb::addr_t>> UnpackTagsData(const std::vector<uint8_t> &tags, size_t granules = 0) const override; Index: lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp =================================================================== --- lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp +++ 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<MemoryTagManager::TagRange> 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<std::vector<MemoryTagManager::TagRange>> +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<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. + // 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<std::vector<lldb::addr_t>> MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector<uint8_t> &tags, size_t granules /*=0*/) const { Index: lldb/source/Core/DumpDataExtractor.cpp =================================================================== --- lldb/source/Core/DumpDataExtractor.cpp +++ lldb/source/Core/DumpDataExtractor.cpp @@ -17,11 +17,14 @@ #include "lldb/Target/ABI.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/ExecutionContextScope.h" +#include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/MemoryTagManager.h" #include "lldb/Target/Process.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Utility/DataExtractor.h" #include "lldb/Utility/Log.h" +#include "lldb/Utility/MemoryTagMap.h" #include "lldb/Utility/Stream.h" #include "llvm/ADT/APFloat.h" @@ -253,6 +256,85 @@ ss << f; } +static llvm::Optional<MemoryTagMap> +GetMemoryTags(lldb::addr_t addr, size_t length, + ExecutionContextScope *exe_scope) { + assert(addr != LLDB_INVALID_ADDRESS); + + if (!exe_scope) + return llvm::None; + + TargetSP target_sp = exe_scope->CalculateTarget(); + if (!target_sp) + return llvm::None; + + ProcessSP process_sp = target_sp->CalculateProcess(); + if (!process_sp) + return llvm::None; + + llvm::Expected<const MemoryTagManager *> tag_manager_or_err = + process_sp->GetMemoryTagManager(); + if (!tag_manager_or_err) { + llvm::consumeError(tag_manager_or_err.takeError()); + return llvm::None; + } + + MemoryRegionInfos memory_regions; + // Don't check return status, list will be just empty if an error happened. + process_sp->GetMemoryRegions(memory_regions); + + llvm::Expected<std::vector<MemoryTagManager::TagRange>> tagged_ranges_or_err = + (*tag_manager_or_err) + ->MakeTaggedRanges(addr, addr + length, memory_regions); + // Here we know that our range will not be inverted but we must still check + // for an error. + if (!tagged_ranges_or_err) { + llvm::consumeError(tagged_ranges_or_err.takeError()); + return llvm::None; + } + if (tagged_ranges_or_err->empty()) + return llvm::None; + + MemoryTagMap memory_tag_map(*tag_manager_or_err); + for (const MemoryTagManager::TagRange &range : *tagged_ranges_or_err) { + llvm::Expected<std::vector<lldb::addr_t>> tags_or_err = + process_sp->ReadMemoryTags(range.GetRangeBase(), range.GetByteSize()); + + if (tags_or_err) + memory_tag_map.InsertTags(range.GetRangeBase(), *tags_or_err); + else + llvm::consumeError(tags_or_err.takeError()); + } + + if (memory_tag_map.empty()) + return llvm::None; + + return memory_tag_map; +} + +static void +printMemoryTags(const DataExtractor &DE, Stream *s, lldb::addr_t addr, + size_t len, + const llvm::Optional<MemoryTagMap> &memory_tag_map) { + std::vector<llvm::Optional<lldb::addr_t>> tags = + memory_tag_map->GetTags(addr, len); + + // Only print if there is at least one tag for this line + if (tags.empty()) + return; + + s->Printf(" (tag%s:", tags.size() > 1 ? "s" : ""); + // Some granules may not be tagged but print something for them + // so that the ordering remains intact. + for (auto tag : tags) { + if (tag) + s->Printf(" 0x%" PRIx64, *tag); + else + s->PutCString(" <no tag>"); + } + s->PutCString(")"); +} + lldb::offset_t lldb_private::DumpDataExtractor( const DataExtractor &DE, Stream *s, offset_t start_offset, lldb::Format item_format, size_t item_byte_size, size_t item_count, @@ -272,6 +354,11 @@ offset_t offset = start_offset; + llvm::Optional<MemoryTagMap> memory_tag_map = llvm::None; + if (base_addr != LLDB_INVALID_ADDRESS) + memory_tag_map = + GetMemoryTags(base_addr, DE.GetByteSize() - offset, exe_scope); + if (item_format == eFormatInstruction) return DumpInstructions(DE, s, exe_scope, start_offset, base_addr, item_count); @@ -283,7 +370,10 @@ lldb::offset_t line_start_offset = start_offset; for (uint32_t count = 0; DE.ValidOffset(offset) && count < item_count; ++count) { + // If we are at the beginning or end of a line + // Note that the last line is handled outside this for loop. if ((count % num_per_line) == 0) { + // If we are at the end of a line if (count > 0) { if (item_format == eFormatBytesWithASCII && offset > line_start_offset) { @@ -295,6 +385,15 @@ offset - line_start_offset, SIZE_MAX, LLDB_INVALID_ADDRESS, 0, 0); } + + if (base_addr != LLDB_INVALID_ADDRESS && memory_tag_map) { + size_t line_len = offset - line_start_offset; + lldb::addr_t line_base = + base_addr + + (offset - start_offset - line_len) / DE.getTargetByteSize(); + printMemoryTags(DE, s, line_base, line_len, memory_tag_map); + } + s->EOL(); } if (base_addr != LLDB_INVALID_ADDRESS) @@ -796,14 +895,28 @@ } } - if (item_format == eFormatBytesWithASCII && offset > line_start_offset) { - s->Printf("%*s", static_cast<int>( - (num_per_line - (offset - line_start_offset)) * 3 + 2), - ""); - DumpDataExtractor(DE, s, line_start_offset, eFormatCharPrintable, 1, - offset - line_start_offset, SIZE_MAX, - LLDB_INVALID_ADDRESS, 0, 0); + // If anything was printed we want to catch the end of the last line. + // Since we will exit the for loop above before we get a chance to append to + // it normally. + if (offset > line_start_offset) { + if (item_format == eFormatBytesWithASCII) { + s->Printf("%*s", + static_cast<int>( + (num_per_line - (offset - line_start_offset)) * 3 + 2), + ""); + DumpDataExtractor(DE, s, line_start_offset, eFormatCharPrintable, 1, + offset - line_start_offset, SIZE_MAX, + LLDB_INVALID_ADDRESS, 0, 0); + } + + if (base_addr != LLDB_INVALID_ADDRESS && memory_tag_map) { + size_t line_len = offset - line_start_offset; + lldb::addr_t line_base = base_addr + (offset - start_offset - line_len) / + DE.getTargetByteSize(); + printMemoryTags(DE, s, line_base, line_len, memory_tag_map); + } } + return offset; // Return the offset at which we ended up } Index: lldb/include/lldb/Utility/MemoryTagMap.h =================================================================== --- /dev/null +++ lldb/include/lldb/Utility/MemoryTagMap.h @@ -0,0 +1,96 @@ +//===-- MemoryTagMap.h ------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_UTILITY_MEMORYTAGMAP_H +#define LLDB_UTILITY_MEMORYTAGMAP_H + +#include "lldb/Target/MemoryTagManager.h" +#include "lldb/lldb-private.h" +#include "llvm/ADT/Optional.h" +#include <map> + +namespace lldb_private { + +// MemoryTagMap provides a way to give a sparse read result +// when reading memory tags for a range. This is useful when +// you want to annotate some large memory dump that might include +// tagged memory but you don't know that it is all tagged. + +class MemoryTagMap { +public: + /// Init an empty tag map + /// + /// \param [in] manager + /// Non-null pointer to a memory tag manager. + MemoryTagMap(const MemoryTagManager *manager); + + /// Insert tags into the map starting from addr. + /// + /// \param [in] addr + /// Start address of the range to insert tags for. + /// This address should be granule aligned and have had + /// any non address bits removed. + /// (ideally you would use the base of the range you used + /// to read the tags in the first place) + /// + /// \param [in] tags + /// Vector of tags to insert. The first tag will be inserted + /// at addr, the next at addr+granule size and so on until + /// all tags have been inserted. + void InsertTags(lldb::addr_t addr, const std::vector<lldb::addr_t> tags); + + bool empty() const; + + /// Lookup memory tags for a range of memory from addr to addr+len. + /// + /// \param [in] addr + /// The start of the range. This may include non address bits and + /// does not have to be granule aligned. + /// + /// \param [in] len + /// The length in bytes of the range to read tags for. This does + /// not need to be aligned to a granule boundary. + /// + /// \return + /// A vector containing the tags found for the granules in the + /// range. (which is the result of granule aligning the given range) + /// + /// Each item in the vector is an optional tag. Meaning that if + /// it is valid then the granule had a tag and if not, it didn't. + /// + /// If the range had no tags at all, the vector will be empty. + /// If some of the range was tagged it will have items and some + /// of them may be llvm::None. + /// (this saves the caller checking whether all items are llvm::None) + std::vector<llvm::Optional<lldb::addr_t>> GetTags(lldb::addr_t addr, + size_t len) const; + +private: + /// Lookup the tag for address + /// + /// \param [in] address + /// The address to lookup a tag for. This should be aligned + /// to a granule boundary. + /// + /// \return + /// The tag for the granule that address refers to, or llvm::None + /// if it has no memory tag. + llvm::Optional<lldb::addr_t> GetTag(lldb::addr_t addr) const; + + // A map of granule aligned addresses to their memory tag + std::map<lldb::addr_t, lldb::addr_t> m_addr_to_tag; + + // Memory tag manager used to align addresses and get granule size + // This would be a const& but we need to put MemoryTagMap in an Optional + // elsewhere so that wouldn't work. + const MemoryTagManager *m_manager; +}; + +} // namespace lldb_private + +#endif // LLDB_UTILITY_MEMORYTAGMAP_H Index: lldb/include/lldb/Target/Process.h =================================================================== --- lldb/include/lldb/Target/Process.h +++ lldb/include/lldb/Target/Process.h @@ -43,6 +43,7 @@ #include "lldb/Utility/Broadcaster.h" #include "lldb/Utility/Event.h" #include "lldb/Utility/Listener.h" +#include "lldb/Utility/MemoryTagMap.h" #include "lldb/Utility/NameMatches.h" #include "lldb/Utility/ProcessInfo.h" #include "lldb/Utility/Status.h" Index: lldb/include/lldb/Target/MemoryTagManager.h =================================================================== --- lldb/include/lldb/Target/MemoryTagManager.h +++ 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<std::vector<TagRange>> + 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.
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits