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 @@ -87,11 +87,45 @@ virtual size_t GetTagSizeInBytes() const = 0; // Unpack tags from their stored format (e.g. gdb qMemTags data) into seperate - // tags. Checks that each tag is within the expected value range and that the - // number of tags found matches the number of granules we originally asked - // for. + // tags. + // + // Checks that each tag is within the expected value range and if granules is + // set to non-zero, that the number of tags found matches the number of + // granules we expected to cover. + virtual llvm::Expected> + UnpackTagsData(const std::vector &tags, + size_t granules = 0) 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 + // it is up to the remote to repeat them as needed. + virtual llvm::Expected> + PackTags(const std::vector &tags) const = 0; + + // Take a set of tags and repeat them as much as needed to cover the given + // range. We assume that this range has been previously expanded/aligned to + // granules. (this method is used by lldb-server to implement QMemTags + // packet handling) + // + // If the range is empty, zero tags are returned. + // If the range is not empty and... + // * there are no tags, an error is returned. + // * there are fewer tags than granules, the tags are repeated to fill the + // range. + // * there are more tags than granules, only the tags required to cover + // the range are returned. + // + // When repeating tags it will not always return a multiple of the original + // list. For example if your range is 3 granules and your tags are 1 and 2. + // You will get tags 1, 2 and 1 returned. Rather than getting 1, 2, 1, 2, + // which would be one too many tags for the range. + // + // A single tag will just be repeated as you'd expected. Tag 1 over 3 granules + // would return 1, 1, 1. virtual llvm::Expected> - UnpackTagsData(const std::vector &tags, size_t granules) const = 0; + RepeatTagsForRange(const std::vector &tags, + TagRange range) const = 0; virtual ~MemoryTagManager() {} }; 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 @@ -38,7 +38,14 @@ llvm::Expected> UnpackTagsData(const std::vector &tags, - size_t granules) const override; + size_t granules = 0) const override; + + llvm::Expected> + PackTags(const std::vector &tags) const override; + + llvm::Expected> + RepeatTagsForRange(const std::vector &tags, + TagRange range) const override; }; } // namespace lldb_private 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 @@ -125,14 +125,17 @@ llvm::Expected> MemoryTagManagerAArch64MTE::UnpackTagsData(const std::vector &tags, - size_t granules) const { - size_t num_tags = tags.size() / GetTagSizeInBytes(); - if (num_tags != granules) { - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "Packed tag data size does not match expected number of tags. " - "Expected %zu tag(s) for %zu granules, got %zu tag(s).", - granules, granules, num_tags); + size_t granules /*=0*/) const { + // 0 means don't check the number of tags before unpacking + if (granules) { + size_t num_tags = tags.size() / GetTagSizeInBytes(); + if (num_tags != granules) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Packed tag data size does not match expected number of tags. " + "Expected %zu tag(s) for %zu granule(s), got %zu tag(s).", + granules, granules, num_tags); + } } // (if bytes per tag was not 1, we would reconstruct them here) @@ -152,3 +155,46 @@ return unpacked; } + +llvm::Expected> MemoryTagManagerAArch64MTE::PackTags( + const std::vector &tags) const { + std::vector packed; + packed.reserve(tags.size() * GetTagSizeInBytes()); + + for (auto tag : tags) { + if (tag > MTE_TAG_MAX) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Found tag 0x%" PRIx64 + " which is > max MTE tag value of 0x%x.", + tag, MTE_TAG_MAX); + } + packed.push_back(static_cast(tag)); + } + + return packed; +} + +llvm::Expected> +MemoryTagManagerAArch64MTE::RepeatTagsForRange( + const std::vector &tags, TagRange range) const { + std::vector new_tags; + + // If the range is not empty + if (range.IsValid()) { + if (tags.empty()) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Expected some tags to cover given range, got zero."); + } + + // We assume that this range has already been expanded/aligned to granules + size_t granules = range.GetByteSize() / GetGranuleSize(); + new_tags.reserve(granules); + for (size_t to_copy = 0; granules > 0; granules -= to_copy) { + to_copy = granules > tags.size() ? tags.size() : granules; + new_tags.insert(new_tags.end(), tags.begin(), tags.begin() + to_copy); + } + } + + return new_tags; +} 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 @@ -21,7 +21,7 @@ manager.UnpackTagsData(input, 2), llvm::FailedWithMessage( "Packed tag data size does not match expected number of tags. " - "Expected 2 tag(s) for 2 granules, got 0 tag(s).")); + "Expected 2 tag(s) for 2 granule(s), got 0 tag(s).")); // This is out of the valid tag range input.push_back(0x1f); @@ -41,6 +41,43 @@ manager.UnpackTagsData(input, 2); ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); ASSERT_THAT(expected, testing::ContainerEq(*got)); + + // Error for too much tag data + ASSERT_THAT_EXPECTED( + manager.UnpackTagsData(input, 1), + llvm::FailedWithMessage( + "Packed tag data size does not match expected number of tags. " + "Expected 1 tag(s) for 1 granule(s), got 2 tag(s).")); + + // By default, we don't check number of tags + llvm::Expected> got_zero = + manager.UnpackTagsData(input); + ASSERT_THAT_EXPECTED(got_zero, llvm::Succeeded()); + ASSERT_THAT(expected, testing::ContainerEq(*got)); + + // Which is the same as granules=0 + got_zero = manager.UnpackTagsData(input, 0); + ASSERT_THAT_EXPECTED(got_zero, llvm::Succeeded()); + ASSERT_THAT(expected, testing::ContainerEq(*got)); +} + +TEST(MemoryTagManagerAArch64MTETest, PackTags) { + MemoryTagManagerAArch64MTE manager; + + // Error for tag out of range + llvm::Expected> invalid_tag_err = + manager.PackTags({0x10}); + ASSERT_THAT_EXPECTED( + invalid_tag_err, + llvm::FailedWithMessage( + "Found tag 0x10 which is > max MTE tag value of 0xf.")); + + // 0xf here is the max tag value that we can pack + std::vector tags{0, 1, 0xf}; + std::vector expected{0, 1, 0xf}; + llvm::Expected> packed = manager.PackTags(tags); + ASSERT_THAT_EXPECTED(packed, llvm::Succeeded()); + ASSERT_THAT(expected, testing::ContainerEq(*packed)); } TEST(MemoryTagManagerAArch64MTETest, GetLogicalTag) { @@ -233,3 +270,53 @@ ASSERT_EQ(-32, manager.AddressDiff(0x5511222233334400, 0x4411222233334420)); ASSERT_EQ(65, manager.AddressDiff(0x9911222233334441, 0x6611222233334400)); } + +// Helper to check that repeating "tags" over "range" gives you +// "expected_tags". +static void +test_repeating_tags(const std::vector &tags, + MemoryTagManagerAArch64MTE::TagRange range, + const std::vector &expected_tags) { + MemoryTagManagerAArch64MTE manager; + llvm::Expected> tags_or_err = + manager.RepeatTagsForRange(tags, range); + ASSERT_THAT_EXPECTED(tags_or_err, llvm::Succeeded()); + ASSERT_THAT(expected_tags, testing::ContainerEq(*tags_or_err)); +} + +TEST(MemoryTagManagerAArch64MTETest, RepeatTagsForRange) { + MemoryTagManagerAArch64MTE manager; + + // Must have some tags if your range is not empty + llvm::Expected> no_tags_err = + manager.RepeatTagsForRange({}, + MemoryTagManagerAArch64MTE::TagRange{0, 16}); + ASSERT_THAT_EXPECTED( + no_tags_err, llvm::FailedWithMessage( + "Expected some tags to cover given range, got zero.")); + + // If the range is empty, you get no tags back + test_repeating_tags({1, 2, 3}, MemoryTagManagerAArch64MTE::TagRange{0, 0}, + {}); + // And you don't need tags for an empty range + test_repeating_tags({}, MemoryTagManagerAArch64MTE::TagRange{0, 0}, {}); + + // A single tag will just be multiplied as many times as needed + test_repeating_tags({5}, MemoryTagManagerAArch64MTE::TagRange{0, 16}, {5}); + test_repeating_tags({6}, MemoryTagManagerAArch64MTE::TagRange{0, 32}, {6, 6}); + + // If you've got as many tags as granules, it's a roundtrip + test_repeating_tags({7, 8}, MemoryTagManagerAArch64MTE::TagRange{0, 32}, + {7, 8}); + + // If you've got fewer tags than granules, they repeat. Exactly or partially + // as needed. + test_repeating_tags({7, 8}, MemoryTagManagerAArch64MTE::TagRange{0, 64}, + {7, 8, 7, 8}); + test_repeating_tags({7, 8}, MemoryTagManagerAArch64MTE::TagRange{0, 48}, + {7, 8, 7}); + + // If you've got more tags than granules you get back only those needed + test_repeating_tags({1, 2, 3, 4}, MemoryTagManagerAArch64MTE::TagRange{0, 32}, + {1, 2}); +}