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 @@ -113,6 +113,21 @@ 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.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 @@ -44,6 +44,12 @@ 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/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 @@ -247,6 +247,70 @@ 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/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 @@ -80,6 +80,72 @@ 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;