From 883b0d5b7f873a7d6f8c8ee13c7f6174a2a79a50 Mon Sep 17 00:00:00 2001 From: David Spickett Date: Mon, 11 Jul 2022 13:26:36 +0100 Subject: [PATCH] [lldb][AArch64] Add UnpackTagsFromCoreFileSegment to MemoryTagManager This is the first part of support for reading MTE tags from Linux core files. The format is documented here: https://www.kernel.org/doc/html/latest/arm64/memory-tagging-extension.html#core-dump-support This patch adds a method to unpack from the format the core file uses, which is different to the one chosen for GDB packets. MemoryTagManagerAArch64MTE is not tied one OS so another OS might choose a different format in future. However, infrastructure to handle that would go untested until then so I've chosen not to attempt to handle that. Reviewed By: omjavaid Differential Revision: https://reviews.llvm.org/D129487 --- lldb/include/lldb/Target/MemoryTagManager.h | 15 +++++ .../Utility/MemoryTagManagerAArch64MTE.cpp | 64 ++++++++++++++++++ .../Utility/MemoryTagManagerAArch64MTE.h | 6 ++ .../MemoryTagManagerAArch64MTETest.cpp | 66 +++++++++++++++++++ 4 files changed, 151 insertions(+) diff --git a/lldb/include/lldb/Target/MemoryTagManager.h b/lldb/include/lldb/Target/MemoryTagManager.h index 28a8acc34632..b082224c38ed 100644 --- a/lldb/include/lldb/Target/MemoryTagManager.h +++ b/lldb/include/lldb/Target/MemoryTagManager.h @@ -113,6 +113,21 @@ public: UnpackTagsData(const std::vector &tags, size_t granules = 0) const = 0; + // Unpack tags from a corefile segment containing compressed tags + // (compression that may be different from the one used for GDB transport). + // + // This method asumes that: + // * addr and len have been granule aligned by a tag manager + // * addr >= tag_segment_virtual_address + // + // 'reader' will always be a wrapper around a CoreFile in real use + // but allows testing without having to mock a CoreFile. + typedef std::function CoreReaderFn; + std::vector virtual UnpackTagsFromCoreFileSegment( + CoreReaderFn reader, lldb::addr_t tag_segment_virtual_address, + lldb::addr_t tag_segment_data_address, lldb::addr_t addr, + size_t len) const = 0; + // Pack uncompressed tags into their storage format (e.g. for gdb QMemTags). // Checks that each tag is within the expected value range. // We do not check the number of tags or range they apply to because diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp index b71de4cadb18..e0126d840971 100644 --- a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp +++ b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp @@ -247,6 +247,70 @@ MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector &tags, return unpacked; } +std::vector +MemoryTagManagerAArch64MTE::UnpackTagsFromCoreFileSegment( + CoreReaderFn reader, lldb::addr_t tag_segment_virtual_address, + lldb::addr_t tag_segment_data_address, lldb::addr_t addr, + size_t len) const { + // We can assume by now that addr and len have been granule aligned by a tag + // manager. However because we have 2 tags per byte we need to round the range + // up again to align to 2 granule boundaries. + const size_t granule = GetGranuleSize(); + const size_t two_granules = granule * 2; + lldb::addr_t aligned_addr = addr; + size_t aligned_len = len; + + // First align the start address down. + if (aligned_addr % two_granules) { + assert(aligned_addr % two_granules == granule); + aligned_addr -= granule; + aligned_len += granule; + } + + // Then align the length up. + bool aligned_length_up = false; + if (aligned_len % two_granules) { + assert(aligned_len % two_granules == granule); + aligned_len += granule; + aligned_length_up = true; + } + + // ProcessElfCore should have validated this when it found the segment. + assert(aligned_addr >= tag_segment_virtual_address); + + // By now we know that aligned_addr is aligned to a 2 granule boundary. + const size_t offset_granules = + (aligned_addr - tag_segment_virtual_address) / granule; + // 2 tags per byte. + const size_t file_offset_in_bytes = offset_granules / 2; + + // By now we know that aligned_len is at least 2 granules. + const size_t tag_bytes_to_read = aligned_len / granule / 2; + std::vector tag_data(tag_bytes_to_read); + const size_t bytes_copied = + reader(tag_segment_data_address + file_offset_in_bytes, tag_bytes_to_read, + tag_data.data()); + assert(bytes_copied == tag_bytes_to_read); + + std::vector tags; + tags.reserve(2 * tag_data.size()); + // No need to check the range of the tag value here as each occupies only 4 + // bits. + for (auto tag_byte : tag_data) { + tags.push_back(tag_byte & 0xf); + tags.push_back(tag_byte >> 4); + } + + // If we aligned the address down, don't return the extra first tag. + if (addr != aligned_addr) + tags.erase(tags.begin()); + // If we aligned the length up, don't return the extra last tag. + if (aligned_length_up) + tags.pop_back(); + + return tags; +} + llvm::Expected> MemoryTagManagerAArch64MTE::PackTags( const std::vector &tags) const { std::vector packed; diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h index 7cda728b140f..365e176e5b1d 100644 --- a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h +++ b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h @@ -44,6 +44,12 @@ public: UnpackTagsData(const std::vector &tags, size_t granules = 0) const override; + std::vector + UnpackTagsFromCoreFileSegment(CoreReaderFn reader, + lldb::addr_t tag_segment_virtual_address, + lldb::addr_t tag_segment_data_address, + lldb::addr_t addr, size_t len) const override; + llvm::Expected> PackTags(const std::vector &tags) const override; diff --git a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp index 9c209ec2363a..6d8b699bbada 100644 --- a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp +++ b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp @@ -80,6 +80,72 @@ TEST(MemoryTagManagerAArch64MTETest, PackTags) { ASSERT_THAT(expected, testing::ContainerEq(*packed)); } +TEST(MemoryTagManagerAArch64MTETest, UnpackTagsFromCoreFileSegment) { + MemoryTagManagerAArch64MTE manager; + // This is our fake segment data where tags are compressed as 2 4 bit tags + // per byte. + std::vector tags_data; + MemoryTagManager::CoreReaderFn reader = + [&tags_data](lldb::offset_t offset, size_t length, void *dst) { + std::memcpy(dst, tags_data.data() + offset, length); + return length; + }; + + // Zero length is ok. + std::vector tags = + manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 0); + ASSERT_EQ(tags.size(), (size_t)0); + + // In the simplest case we read 2 tags which are in the same byte. + tags_data.push_back(0x21); + // The least significant bits are the first tag in memory. + std::vector expected{1, 2}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 32); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // If we read just one then it will have to trim off the second one. + expected = std::vector{1}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 16); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // If we read the second tag only then the first one must be trimmed. + expected = std::vector{2}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 16, 16); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // This trimming logic applies if you read a larger set of tags. + tags_data = std::vector{0x21, 0x43, 0x65, 0x87}; + + // Trailing tag should be trimmed. + expected = std::vector{1, 2, 3}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 0, 48); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // Leading tag should be trimmed. + expected = std::vector{2, 3, 4}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 16, 48); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // Leading and trailing trimmmed. + expected = std::vector{2, 3, 4, 5}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 0, 16, 64); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // The address given is an offset into the whole file so the address requested + // from the reader should be beyond that. + tags_data = std::vector{0xFF, 0xFF, 0x21, 0x43, 0x65, 0x87}; + expected = std::vector{1, 2}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 0, 2, 0, 32); + ASSERT_THAT(expected, testing::ContainerEq(tags)); + + // addr is a virtual address that we expect to be >= the tag segment's + // starting virtual address. So again an offset must be made from the + // difference. + expected = std::vector{3, 4}; + tags = manager.UnpackTagsFromCoreFileSegment(reader, 32, 2, 64, 32); + ASSERT_THAT(expected, testing::ContainerEq(tags)); +} + TEST(MemoryTagManagerAArch64MTETest, GetLogicalTag) { MemoryTagManagerAArch64MTE manager; -- 2.34.1