diff --git a/lldb/include/lldb/Target/MemoryTagManager.h b/lldb/include/lldb/Target/MemoryTagManager.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/MemoryTagManager.h @@ -0,0 +1,86 @@ +//===-- MemoryTagManager.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_TARGET_MEMORYTAGMANAGER_H +#define LLDB_TARGET_MEMORYTAGMANAGER_H + +#include "lldb/Utility/RangeMap.h" +#include "lldb/lldb-private.h" +#include "llvm/Support/Error.h" + +namespace lldb_private { + +// This interface allows high level commands to handle memory tags +// in a generic way. +// +// Definitions: +// logical tag - the tag stored in a pointer +// allocation tag - the tag stored in hardware +// (e.g. special memory, cache line bits) +// granule - number of bytes of memory a single tag applies to + +class MemoryTagManager { +public: + typedef Range TagRange; + + // Extract the logical tag from a pointer + // The tag is returned as a plain value, with any shifts removed. + // For example if your tags are stored in bits 56-60 then the logical tag + // you get will have been shifted down 56 before being returned. + virtual lldb::addr_t GetLogicalTag(lldb::addr_t addr) const = 0; + + // Remove non address bits from a pointer + virtual lldb::addr_t RemoveNonAddressBits(lldb::addr_t addr) const = 0; + + // Return the difference between two addresses, ignoring any logical tags they + // have. If your tags are just part of a larger set of ignored bits, this + // should ignore all those bits. + virtual ptrdiff_t AddressDiff(lldb::addr_t addr1, + lldb::addr_t addr2) const = 0; + + // Return the number of bytes a single tag covers + virtual lldb::addr_t GetGranuleSize() const = 0; + + // Align an address range to granule boundaries. + // So that reading memory tags for the new range returns + // tags that will cover the original range. + // + // Say your granules are 16 bytes and you want + // tags for 16 bytes of memory starting from address 8. + // 1 granule isn't enough because it only covers addresses + // 0-16, we want addresses 8-24. So the range must be + // expanded to 2 granules. + virtual TagRange ExpandToGranule(TagRange range) 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. + // + // This value is unique within a given architecture. Meaning that different + // tagging schemes within the same architecture should use unique values, + // but other architectures can overlap those values. + virtual int32_t GetAllocationTagType() const = 0; + + // Return the number of bytes a single tag will be packed into during + // transport. For example an MTE tag is 4 bits but occupies 1 byte during + // transport. + 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. + virtual llvm::Expected> + UnpackTagsData(const std::vector &tags, size_t granules) const = 0; + + virtual ~MemoryTagManager() {} +}; + +} // namespace lldb_private + +#endif // LLDB_TARGET_MEMORYTAGMANAGER_H diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt --- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt +++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt @@ -8,6 +8,7 @@ InferiorCallPOSIX.cpp LinuxProcMaps.cpp LinuxSignals.cpp + MemoryTagManagerAArch64MTE.cpp MipsLinuxSignals.cpp NativeProcessSoftwareSingleStep.cpp NativeRegisterContextDBReg_arm64.cpp diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h @@ -0,0 +1,42 @@ +//===-- MemoryTagManagerAArch64MTE.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_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGMANAGERAARCH64MTE_H +#define LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGMANAGERAARCH64MTE_H + +#include "lldb/Target/MemoryTagManager.h" + +namespace lldb_private { + +class MemoryTagManagerAArch64MTE : public MemoryTagManager { +public: + // This enum is supposed to be shared for all of AArch64 but until + // there are more tag types than MTE, it will live here. + enum MTETagTypes { + eMTE_logical = 0, + eMTE_allocation = 1, + }; + + lldb::addr_t GetGranuleSize() const override; + int32_t GetAllocationTagType() const override; + size_t GetTagSizeInBytes() const override; + + lldb::addr_t GetLogicalTag(lldb::addr_t addr) const override; + lldb::addr_t RemoveNonAddressBits(lldb::addr_t addr) const override; + ptrdiff_t AddressDiff(lldb::addr_t addr1, lldb::addr_t addr2) const override; + + TagRange ExpandToGranule(TagRange range) const override; + + llvm::Expected> + UnpackTagsData(const std::vector &tags, + size_t granules) const override; +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_PROCESS_UTILITY_MEMORYTAGMANAGERAARCH64MTE_H diff --git a/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Plugins/Process/Utility/MemoryTagManagerAArch64MTE.cpp @@ -0,0 +1,98 @@ +//===-- MemoryTagManagerAArch64MTE.cpp --------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "MemoryTagManagerAArch64MTE.h" + +using namespace lldb_private; + +static const unsigned MTE_START_BIT = 56; +static const unsigned MTE_TAG_MAX = 0xf; +static const unsigned MTE_GRANULE_SIZE = 16; + +lldb::addr_t +MemoryTagManagerAArch64MTE::GetLogicalTag(lldb::addr_t addr) const { + return (addr >> MTE_START_BIT) & MTE_TAG_MAX; +} + +lldb::addr_t +MemoryTagManagerAArch64MTE::RemoveNonAddressBits(lldb::addr_t addr) const { + // Here we're ignoring the whole top byte. If you've got MTE + // you must also have TBI (top byte ignore). + // The other 4 bits could contain other extension bits or + // user metadata. + return addr & ~((lldb::addr_t)0xFF << MTE_START_BIT); +} + +ptrdiff_t MemoryTagManagerAArch64MTE::AddressDiff(lldb::addr_t addr1, + lldb::addr_t addr2) const { + return RemoveNonAddressBits(addr1) - RemoveNonAddressBits(addr2); +} + +lldb::addr_t MemoryTagManagerAArch64MTE::GetGranuleSize() const { + return MTE_GRANULE_SIZE; +} + +int32_t MemoryTagManagerAArch64MTE::GetAllocationTagType() const { + return eMTE_allocation; +} + +size_t MemoryTagManagerAArch64MTE::GetTagSizeInBytes() const { return 1; } + +MemoryTagManagerAArch64MTE::TagRange +MemoryTagManagerAArch64MTE::ExpandToGranule(TagRange range) const { + // Ignore reading a length of 0 + if (!range.IsValid()) + return range; + + const size_t granule = GetGranuleSize(); + + // Align start down to granule start + lldb::addr_t new_start = range.GetRangeBase(); + lldb::addr_t align_down_amount = new_start % granule; + new_start -= align_down_amount; + + // Account for the distance we moved the start above + size_t new_len = range.GetByteSize() + align_down_amount; + // Then align up to the end of the granule + size_t align_up_amount = granule - (new_len % granule); + if (align_up_amount != granule) + new_len += align_up_amount; + + return TagRange(new_start, new_len); +} + +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 %" PRIu64 " tag(s) for %" PRIu64 " granules, got %" PRIu64 + " tag(s).", + granules, granules, num_tags); + } + + // (if bytes per tag was not 1, we would reconstruct them here) + + std::vector unpacked; + unpacked.reserve(tags.size()); + for (auto it = tags.begin(); it != tags.end(); ++it) { + // Check all tags are in range + if (*it > MTE_TAG_MAX) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Found tag 0x%x which is > max MTE tag value of 0x%x.", *it, + MTE_TAG_MAX); + } + unpacked.push_back(*it); + } + + return unpacked; +} diff --git a/lldb/unittests/Process/Utility/CMakeLists.txt b/lldb/unittests/Process/Utility/CMakeLists.txt --- a/lldb/unittests/Process/Utility/CMakeLists.txt +++ b/lldb/unittests/Process/Utility/CMakeLists.txt @@ -17,7 +17,9 @@ add_lldb_unittest(ProcessUtilityTests RegisterContextTest.cpp LinuxProcMapsTest.cpp + MemoryTagManagerAArch64MTETest.cpp ${PLATFORM_SOURCES} LINK_LIBS - lldbPluginProcessUtility) + lldbPluginProcessUtility + LLVMTestingSupport) diff --git a/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp new file mode 100644 --- /dev/null +++ b/lldb/unittests/Process/Utility/MemoryTagManagerAArch64MTETest.cpp @@ -0,0 +1,120 @@ +//===-- MemoryTagManagerAArch64MTETest.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 "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace lldb_private; + +TEST(MemoryTagManagerAArch64MTETest, UnpackTagsData) { + MemoryTagManagerAArch64MTE manager; + + // Error for insufficient tag data + std::vector input; + ASSERT_THAT_EXPECTED( + 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).")); + + // This is out of the valid tag range + input.push_back(0x1f); + ASSERT_THAT_EXPECTED( + manager.UnpackTagsData(input, 1), + llvm::FailedWithMessage( + "Found tag 0x1f which is > max MTE tag value of 0xf.")); + + // MTE tags are 1 per byte + input.pop_back(); + input.push_back(0xe); + input.push_back(0xf); + + std::vector expected{0xe, 0xf}; + + llvm::Expected> got = + manager.UnpackTagsData(input, 2); + ASSERT_THAT_EXPECTED(got, llvm::Succeeded()); + ASSERT_THAT(expected, testing::ContainerEq(*got)); +} + +TEST(MemoryTagManagerAArch64MTETest, GetLogicalTag) { + MemoryTagManagerAArch64MTE manager; + + // Set surrounding bits to check shift is correct + ASSERT_EQ((lldb::addr_t)0, manager.GetLogicalTag(0xe0e00000ffffffff)); + // Max tag value + ASSERT_EQ((lldb::addr_t)0xf, manager.GetLogicalTag(0x0f000000ffffffff)); + ASSERT_EQ((lldb::addr_t)2, manager.GetLogicalTag(0x02000000ffffffff)); +} + +TEST(MemoryTagManagerAArch64MTETest, ExpandToGranule) { + MemoryTagManagerAArch64MTE manager; + // Reading nothing, no alignment needed + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(0, 0), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0, 0))); + + // Ranges with 0 size are unchanged even if address is non 0 + // (normally 0x1234 would be aligned to 0x1230) + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(0x1234, 0), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0x1234, 0))); + + // Ranges already aligned don't change + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(0x100, 64), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0x100, 64))); + + // Any read of less than 1 granule is rounded up to reading 1 granule + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(0, 16), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(0, 1))); + + // Start address is aligned down, and length modified accordingly + // Here bytes 8 through 24 straddle 2 granules. So the resulting range starts + // at 0 and covers 32 bytes. + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(0, 32), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(8, 16))); + + // Here only the size of the range needs aligning + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(16, 32), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(16, 24))); + + // Start and size need aligning here but we only need 1 granule to cover it + ASSERT_EQ( + MemoryTagManagerAArch64MTE::TagRange(16, 16), + manager.ExpandToGranule(MemoryTagManagerAArch64MTE::TagRange(18, 4))); +} + +TEST(MemoryTagManagerAArch64MTETest, RemoveNonAddressBits) { + MemoryTagManagerAArch64MTE manager; + + ASSERT_EQ(0, 0); + ASSERT_EQ((lldb::addr_t)0x00ffeedd11223344, + manager.RemoveNonAddressBits(0x00ffeedd11223344)); + ASSERT_EQ((lldb::addr_t)0x0000000000000000, + manager.RemoveNonAddressBits(0xFF00000000000000)); + ASSERT_EQ((lldb::addr_t)0x0055555566666666, + manager.RemoveNonAddressBits(0xee55555566666666)); +} + +TEST(MemoryTagManagerAArch64MTETest, AddressDiff) { + MemoryTagManagerAArch64MTE manager; + + ASSERT_EQ(0, manager.AddressDiff(0, 0)); + // Result is signed + ASSERT_EQ(10, manager.AddressDiff(10, 0)); + ASSERT_EQ(-10, manager.AddressDiff(0, 10)); + // Anything in the top byte is ignored + ASSERT_EQ(0, manager.AddressDiff(0x2211222233334444, 0x3311222233334444)); + ASSERT_EQ(-32, manager.AddressDiff(0x5511222233334400, 0x4411222233334420)); + ASSERT_EQ(65, manager.AddressDiff(0x9911222233334441, 0x6611222233334400)); +}