diff --git a/lldb/include/lldb/Core/DumpDataExtractor.h b/lldb/include/lldb/Core/DumpDataExtractor.h --- a/lldb/include/lldb/Core/DumpDataExtractor.h +++ b/lldb/include/lldb/Core/DumpDataExtractor.h @@ -76,6 +76,15 @@ /// same integer value. If the items being displayed are not /// bitfields, this value should be zero. /// +/// \param[in] exe_scope +/// If provided, this will be used to lookup language specific +/// information, address information and memory tags. +/// (if they are requested by the other options) +/// +/// \param[in] show_memory_tags +/// If exe_scope and base_addr are valid, include memory tags +/// in the output. This does not apply to certain formats. +/// /// \return /// The offset at which dumping ended. lldb::offset_t @@ -83,7 +92,8 @@ lldb::Format item_format, size_t item_byte_size, size_t item_count, size_t num_per_line, uint64_t base_addr, uint32_t item_bit_size, uint32_t item_bit_offset, - ExecutionContextScope *exe_scope = nullptr); + ExecutionContextScope *exe_scope = nullptr, + bool show_memory_tags = false); void DumpHexBytes(Stream *s, const void *src, size_t src_len, uint32_t bytes_per_line, lldb::addr_t base_addr); diff --git a/lldb/source/Commands/CommandObjectMemory.cpp b/lldb/source/Commands/CommandObjectMemory.cpp --- a/lldb/source/Commands/CommandObjectMemory.cpp +++ b/lldb/source/Commands/CommandObjectMemory.cpp @@ -90,6 +90,10 @@ error = m_offset.SetValueFromString(option_value); break; + case '\x01': + m_show_tags = true; + break; + default: llvm_unreachable("Unimplemented option"); } @@ -103,6 +107,7 @@ m_force = false; m_offset.Clear(); m_language_for_type.Clear(); + m_show_tags = false; } Status FinalizeSettings(Target *target, OptionGroupFormat &format_options) { @@ -276,6 +281,7 @@ bool m_force; OptionValueUInt64 m_offset; OptionValueLanguage m_language_for_type; + bool m_show_tags = false; }; // Read memory from the inferior process @@ -854,7 +860,7 @@ size_t bytes_dumped = DumpDataExtractor( data, output_stream_p, 0, format, item_byte_size, item_count, num_per_line / target->GetArchitecture().GetDataByteSize(), addr, 0, 0, - exe_scope); + exe_scope, m_memory_options.m_show_tags); m_next_addr = addr + bytes_dumped; output_stream_p->EOL(); return true; diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -489,6 +489,8 @@ "display data.">; def memory_read_force : Option<"force", "r">, Groups<[1,2,3]>, Desc<"Necessary if reading over target.max-memory-read-size bytes.">; + def memory_read_show_tags : Option<"show-tags", "\\x01">, Group<1>, + Desc<"Include memory tags in output (does not apply to binary output).">; } let Command = "memory find" in { 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,6 +17,9 @@ #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/MemoryTagMap.h" #include "lldb/Target/Process.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.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, @@ -261,7 +343,7 @@ // non-zero, the value is a bitfield uint32_t item_bit_offset, // If "item_bit_size" is non-zero, this is the // shift amount to apply to a bitfield - ExecutionContextScope *exe_scope) { + ExecutionContextScope *exe_scope, bool show_memory_tags) { if (s == nullptr) return start_offset; @@ -272,6 +354,11 @@ offset_t offset = start_offset; + llvm::Optional memory_tag_map = llvm::None; + if (show_memory_tags && 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/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,128 @@ "\[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) + + # show-tags option is required + self.expect("memory read mte_buf mte_buf+32 -f \"x\" -l 1 -s 16", + patterns=["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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + 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 --show-tags", + patterns=["0x[0-9A-fa-f]+f4: 0x0+ \(tags: 0x0 \)"]) + + # Some formats call DumpDataExtractor multiple times, + # check that those print tags only once per line. + self.expect("memory read mte_buf mte_buf+32 -f \"x\" --show-tags", + 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\" --show-tags", + 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 --show-tags", + patterns=["0x[0-9A-Fa-f]+00: \{(0x00 ){15}0x00\} \(tag: 0x0\)\n" + "0x[0-9A-Fa-f]+10: \{(0x00 ){15}0x00\} \(tag: 0x1\)"])