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/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h --- a/lldb/include/lldb/Target/Process.h +++ b/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" diff --git a/lldb/include/lldb/Utility/MemoryTagMap.h b/lldb/include/lldb/Utility/MemoryTagMap.h new file mode 100644 --- /dev/null +++ b/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 + +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 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> 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 GetTag(lldb::addr_t addr) const; + + // A map of granule aligned addresses to their memory tag + std::map 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 diff --git a/lldb/source/Core/DumpDataExtractor.cpp b/lldb/source/Core/DumpDataExtractor.cpp --- a/lldb/source/Core/DumpDataExtractor.cpp +++ b/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 +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 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> 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> 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 &memory_tag_map) { + std::vector> 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(" "); + } + 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 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( - (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( + (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 } 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/source/Utility/CMakeLists.txt b/lldb/source/Utility/CMakeLists.txt --- a/lldb/source/Utility/CMakeLists.txt +++ b/lldb/source/Utility/CMakeLists.txt @@ -45,6 +45,7 @@ Log.cpp Logging.cpp NameMatches.cpp + MemoryTagMap.cpp ProcessInfo.cpp RegisterValue.cpp RegularExpression.cpp diff --git a/lldb/source/Utility/MemoryTagMap.cpp b/lldb/source/Utility/MemoryTagMap.cpp new file mode 100644 --- /dev/null +++ b/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 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> +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> 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 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 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; +} diff --git a/lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py b/lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py --- a/lldb/test/API/linux/aarch64/mte_tag_access/TestAArch64LinuxMTEMemoryTagAccess.py +++ b/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. + # 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: 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 \)"]) + + # 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: 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 \)"]) + + # 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\)"]) 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 @@ -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> 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; diff --git a/lldb/unittests/Utility/CMakeLists.txt b/lldb/unittests/Utility/CMakeLists.txt --- a/lldb/unittests/Utility/CMakeLists.txt +++ b/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 diff --git a/lldb/unittests/Utility/MemoryTagMapTest.cpp b/lldb/unittests/Utility/MemoryTagMapTest.cpp new file mode 100644 --- /dev/null +++ b/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>; + + 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})); +}