mirror of
https://github.com/intel/llvm.git
synced 2026-01-26 12:26:52 +08:00
[lldb][AArch64] Add MTE memory tag reading to lldb
This adds GDB client support for the qMemTags packet which reads memory tags. Following the design which was recently committed to GDB. https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#General-Query-Packets (look for qMemTags) lldb commands will use the new Process methods GetMemoryTagManager and ReadMemoryTags. The former takes a range and checks that: * The current process architecture has an architecture plugin * That plugin provides a MemoryTagManager * That the range of memory requested lies in a tagged range (it will expand it to granules for you) If all that was true you get a MemoryTagManager you can give to ReadMemoryTags. This two step process is done to allow commands to get the tag manager without having to read tags as well. For example you might just want to remove a logical tag, or error early if a range with tagged addresses is inverted. Note that getting a MemoryTagManager doesn't mean that the process or a specific memory range is tagged. Those are seperate checks. Having a tag manager just means this architecture *could* have a tagging feature enabled. An architecture plugin has been added for AArch64 which will return a MemoryTagManagerAArch64MTE, which was added in a previous patch. Reviewed By: omjavaid Differential Revision: https://reviews.llvm.org/D95602
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
#define LLDB_CORE_ARCHITECTURE_H
|
||||
|
||||
#include "lldb/Core/PluginInterface.h"
|
||||
#include "lldb/Target/MemoryTagManager.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
@@ -97,6 +98,17 @@ public:
|
||||
Target &target) const {
|
||||
return addr;
|
||||
}
|
||||
|
||||
// Returns a pointer to an object that can manage memory tags for this
|
||||
// Architecture E.g. masking out tags, unpacking tag streams etc. Returns
|
||||
// nullptr if the architecture does not have a memory tagging extension.
|
||||
//
|
||||
// The return pointer being valid does not mean that the current process has
|
||||
// memory tagging enabled, just that a tagging technology exists for this
|
||||
// architecture.
|
||||
virtual const MemoryTagManager *GetMemoryTagManager() const {
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "lldb/Target/ExecutionContextScope.h"
|
||||
#include "lldb/Target/InstrumentationRuntime.h"
|
||||
#include "lldb/Target/Memory.h"
|
||||
#include "lldb/Target/MemoryTagManager.h"
|
||||
#include "lldb/Target/QueueList.h"
|
||||
#include "lldb/Target/ThreadList.h"
|
||||
#include "lldb/Target/ThreadPlanStack.h"
|
||||
@@ -1709,6 +1710,44 @@ public:
|
||||
lldb::addr_t CallocateMemory(size_t size, uint32_t permissions,
|
||||
Status &error);
|
||||
|
||||
/// If the address range given is in a memory tagged range and this
|
||||
/// architecture and process supports memory tagging, return a tag
|
||||
/// manager that can be used to maniupulate those memory tags.
|
||||
/// Tags present in the addresses given are ignored.
|
||||
///
|
||||
/// \param[in] addr
|
||||
/// Start of memory range.
|
||||
///
|
||||
/// \param[in] end_addr
|
||||
/// End of the memory range. Where end is one beyond the last byte to be
|
||||
/// included.
|
||||
///
|
||||
/// \return
|
||||
/// Either a valid pointer to a tag manager or an error describing why one
|
||||
/// could not be provided.
|
||||
llvm::Expected<const MemoryTagManager *>
|
||||
GetMemoryTagManager(lldb::addr_t addr, lldb::addr_t end_addr);
|
||||
|
||||
/// Expands the range addr to addr+len to align with granule boundaries and
|
||||
/// then calls DoReadMemoryTags to do the target specific operations.
|
||||
/// Tags are returned unpacked so can be used without conversion.
|
||||
///
|
||||
/// \param[in] tag_manager
|
||||
/// The tag manager to get memory tagging information from.
|
||||
///
|
||||
/// \param[in] addr
|
||||
/// Start of memory range to read tags for.
|
||||
///
|
||||
/// \param[in] len
|
||||
/// Length of memory range to read tags for (in bytes).
|
||||
///
|
||||
/// \return
|
||||
/// Either the unpacked tags or an error describing a failure to read
|
||||
/// or unpack them.
|
||||
llvm::Expected<std::vector<lldb::addr_t>>
|
||||
ReadMemoryTags(const MemoryTagManager *tag_manager, lldb::addr_t addr,
|
||||
size_t len);
|
||||
|
||||
/// Resolve dynamically loaded indirect functions.
|
||||
///
|
||||
/// \param[in] address
|
||||
@@ -2728,6 +2767,29 @@ protected:
|
||||
/// false otherwise.
|
||||
virtual bool SupportsMemoryTagging() { return false; }
|
||||
|
||||
/// Does the final operation to read memory tags. E.g. sending a GDB packet.
|
||||
/// It assumes that ReadMemoryTags has checked that memory tagging is enabled
|
||||
/// and has expanded the memory range as needed.
|
||||
///
|
||||
/// \param[in] addr
|
||||
/// Start of address range to read memory tags for.
|
||||
///
|
||||
/// \param[in] len
|
||||
/// Length of the memory range to read tags for (in bytes).
|
||||
///
|
||||
/// \param[in] type
|
||||
/// Type of tags to read (get this from a MemoryTagManager)
|
||||
///
|
||||
/// \return
|
||||
/// The packed tag data received from the remote or an error
|
||||
/// if the read failed.
|
||||
virtual llvm::Expected<std::vector<uint8_t>>
|
||||
DoReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"%s does not support reading memory tags",
|
||||
GetPluginName().GetCString());
|
||||
}
|
||||
|
||||
// Type definitions
|
||||
typedef std::map<lldb::LanguageType, lldb::LanguageRuntimeSP>
|
||||
LanguageRuntimeCollection;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
//===-- ArchitectureAArch64.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/Architecture/AArch64/ArchitectureAArch64.h"
|
||||
#include "lldb/Core/PluginManager.h"
|
||||
#include "lldb/Utility/ArchSpec.h"
|
||||
|
||||
using namespace lldb_private;
|
||||
using namespace lldb;
|
||||
|
||||
LLDB_PLUGIN_DEFINE(ArchitectureAArch64)
|
||||
|
||||
ConstString ArchitectureAArch64::GetPluginNameStatic() {
|
||||
return ConstString("aarch64");
|
||||
}
|
||||
|
||||
void ArchitectureAArch64::Initialize() {
|
||||
PluginManager::RegisterPlugin(GetPluginNameStatic(),
|
||||
"AArch64-specific algorithms",
|
||||
&ArchitectureAArch64::Create);
|
||||
}
|
||||
|
||||
void ArchitectureAArch64::Terminate() {
|
||||
PluginManager::UnregisterPlugin(&ArchitectureAArch64::Create);
|
||||
}
|
||||
|
||||
std::unique_ptr<Architecture>
|
||||
ArchitectureAArch64::Create(const ArchSpec &arch) {
|
||||
auto machine = arch.GetMachine();
|
||||
if (machine != llvm::Triple::aarch64 && machine != llvm::Triple::aarch64_be &&
|
||||
machine != llvm::Triple::aarch64_32) {
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<Architecture>(new ArchitectureAArch64());
|
||||
}
|
||||
|
||||
ConstString ArchitectureAArch64::GetPluginName() {
|
||||
return GetPluginNameStatic();
|
||||
}
|
||||
uint32_t ArchitectureAArch64::GetPluginVersion() { return 1; }
|
||||
@@ -0,0 +1,40 @@
|
||||
//===-- ArchitectureAArch64.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_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H
|
||||
#define LLDB_SOURCE_PLUGINS_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H
|
||||
|
||||
#include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h"
|
||||
#include "lldb/Core/Architecture.h"
|
||||
|
||||
namespace lldb_private {
|
||||
|
||||
class ArchitectureAArch64 : public Architecture {
|
||||
public:
|
||||
static ConstString GetPluginNameStatic();
|
||||
static void Initialize();
|
||||
static void Terminate();
|
||||
|
||||
ConstString GetPluginName() override;
|
||||
uint32_t GetPluginVersion() override;
|
||||
|
||||
void OverrideStopInfo(Thread &thread) const override{};
|
||||
|
||||
const MemoryTagManager *GetMemoryTagManager() const override {
|
||||
return &m_memory_tag_manager;
|
||||
}
|
||||
|
||||
private:
|
||||
static std::unique_ptr<Architecture> Create(const ArchSpec &arch);
|
||||
ArchitectureAArch64() = default;
|
||||
MemoryTagManagerAArch64MTE m_memory_tag_manager;
|
||||
};
|
||||
|
||||
} // namespace lldb_private
|
||||
|
||||
#endif // LLDB_SOURCE_PLUGINS_ARCHITECTURE_AARCH64_ARCHITECTUREAARCH64_H
|
||||
11
lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt
Normal file
11
lldb/source/Plugins/Architecture/AArch64/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
add_lldb_library(lldbPluginArchitectureAArch64 PLUGIN
|
||||
ArchitectureAArch64.cpp
|
||||
|
||||
LINK_LIBS
|
||||
lldbPluginProcessUtility
|
||||
lldbCore
|
||||
lldbTarget
|
||||
lldbUtility
|
||||
LINK_COMPONENTS
|
||||
Support
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
add_subdirectory(Arm)
|
||||
add_subdirectory(Mips)
|
||||
add_subdirectory(PPC64)
|
||||
add_subdirectory(AArch64)
|
||||
|
||||
@@ -586,6 +586,50 @@ bool GDBRemoteCommunicationClient::GetMemoryTaggingSupported() {
|
||||
return m_supports_memory_tagging == eLazyBoolYes;
|
||||
}
|
||||
|
||||
DataBufferSP GDBRemoteCommunicationClient::ReadMemoryTags(lldb::addr_t addr,
|
||||
size_t len,
|
||||
int32_t type) {
|
||||
StreamString packet;
|
||||
packet.Printf("qMemTags:%lx,%lx:%x", addr, len, type);
|
||||
StringExtractorGDBRemote response;
|
||||
|
||||
Log *log = ProcessGDBRemoteLog::GetLogIfAnyCategoryIsSet(GDBR_LOG_MEMORY);
|
||||
|
||||
if (SendPacketAndWaitForResponse(packet.GetString(), response, false) !=
|
||||
PacketResult::Success ||
|
||||
!response.IsNormalResponse()) {
|
||||
LLDB_LOGF(log, "GDBRemoteCommunicationClient::%s: qMemTags packet failed",
|
||||
__FUNCTION__);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We are expecting
|
||||
// m<hex encoded bytes>
|
||||
|
||||
if (response.GetChar() != 'm') {
|
||||
LLDB_LOGF(log,
|
||||
"GDBRemoteCommunicationClient::%s: qMemTags response did not "
|
||||
"begin with \"m\"",
|
||||
__FUNCTION__);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t expected_bytes = response.GetBytesLeft() / 2;
|
||||
DataBufferSP buffer_sp(new DataBufferHeap(expected_bytes, 0));
|
||||
size_t got_bytes = response.GetHexBytesAvail(buffer_sp->GetData());
|
||||
// Check both because in some situations chars are consumed even
|
||||
// if the decoding fails.
|
||||
if (response.GetBytesLeft() || (expected_bytes != got_bytes)) {
|
||||
LLDB_LOGF(
|
||||
log,
|
||||
"GDBRemoteCommunicationClient::%s: Invalid data in qMemTags response",
|
||||
__FUNCTION__);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return buffer_sp;
|
||||
}
|
||||
|
||||
bool GDBRemoteCommunicationClient::GetxPacketSupported() {
|
||||
if (m_supports_x == eLazyBoolCalculate) {
|
||||
StringExtractorGDBRemote response;
|
||||
|
||||
@@ -453,6 +453,9 @@ public:
|
||||
|
||||
bool GetMemoryTaggingSupported();
|
||||
|
||||
lldb::DataBufferSP ReadMemoryTags(lldb::addr_t addr, size_t len,
|
||||
int32_t type);
|
||||
|
||||
/// Use qOffsets to query the offset used when relocating the target
|
||||
/// executable. If successful, the returned structure will contain at least
|
||||
/// one value in the offsets field.
|
||||
|
||||
@@ -2771,6 +2771,25 @@ bool ProcessGDBRemote::SupportsMemoryTagging() {
|
||||
return m_gdb_comm.GetMemoryTaggingSupported();
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<uint8_t>>
|
||||
ProcessGDBRemote::DoReadMemoryTags(lldb::addr_t addr, size_t len,
|
||||
int32_t type) {
|
||||
// By this point ReadMemoryTags has validated that tagging is enabled
|
||||
// for this target/process/address.
|
||||
DataBufferSP buffer_sp = m_gdb_comm.ReadMemoryTags(addr, len, type);
|
||||
if (!buffer_sp) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"Error reading memory tags from remote");
|
||||
}
|
||||
|
||||
// Return the raw tag data
|
||||
llvm::ArrayRef<uint8_t> tag_data = buffer_sp->GetData();
|
||||
std::vector<uint8_t> got;
|
||||
got.reserve(tag_data.size());
|
||||
std::copy(tag_data.begin(), tag_data.end(), std::back_inserter(got));
|
||||
return got;
|
||||
}
|
||||
|
||||
Status ProcessGDBRemote::WriteObjectFile(
|
||||
std::vector<ObjectFile::LoadableData> entries) {
|
||||
Status error;
|
||||
|
||||
@@ -408,6 +408,9 @@ protected:
|
||||
|
||||
bool HasErased(FlashRange range);
|
||||
|
||||
llvm::Expected<std::vector<uint8_t>>
|
||||
DoReadMemoryTags(lldb::addr_t addr, size_t len, int32_t type) override;
|
||||
|
||||
private:
|
||||
// For ProcessGDBRemote only
|
||||
std::string m_partial_profile_data;
|
||||
|
||||
@@ -6065,3 +6065,84 @@ bool Process::CallVoidArgVoidPtrReturn(const Address *address,
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
llvm::Expected<const MemoryTagManager *>
|
||||
Process::GetMemoryTagManager(lldb::addr_t addr, lldb::addr_t end_addr) {
|
||||
Architecture *arch = GetTarget().GetArchitecturePlugin();
|
||||
const MemoryTagManager *tag_manager =
|
||||
arch ? arch->GetMemoryTagManager() : nullptr;
|
||||
if (!arch || !tag_manager) {
|
||||
return llvm::createStringError(
|
||||
llvm::inconvertibleErrorCode(),
|
||||
"This architecture does not support memory tagging",
|
||||
GetPluginName().GetCString());
|
||||
}
|
||||
|
||||
if (!SupportsMemoryTagging()) {
|
||||
return llvm::createStringError(llvm::inconvertibleErrorCode(),
|
||||
"Process does not support memory tagging");
|
||||
}
|
||||
|
||||
ptrdiff_t len = tag_manager->AddressDiff(end_addr, addr);
|
||||
if (len <= 0) {
|
||||
return llvm::createStringError(
|
||||
llvm::inconvertibleErrorCode(),
|
||||
"End address (0x%" PRIx64
|
||||
") must be greater than the start address (0x%" PRIx64 ")",
|
||||
end_addr, addr);
|
||||
}
|
||||
|
||||
// Region lookup is not address size aware so mask the address
|
||||
MemoryRegionInfo::RangeType tag_range(tag_manager->RemoveNonAddressBits(addr),
|
||||
len);
|
||||
tag_range = tag_manager->ExpandToGranule(tag_range);
|
||||
|
||||
// Make a copy so we can use the original range in errors
|
||||
MemoryRegionInfo::RangeType remaining_range(tag_range);
|
||||
|
||||
// While we haven't found a matching memory region for some of the range
|
||||
while (remaining_range.IsValid()) {
|
||||
MemoryRegionInfo region;
|
||||
Status status = GetMemoryRegionInfo(remaining_range.GetRangeBase(), region);
|
||||
|
||||
if (status.Fail() || region.GetMemoryTagged() != MemoryRegionInfo::eYes) {
|
||||
return llvm::createStringError(
|
||||
llvm::inconvertibleErrorCode(),
|
||||
"Address range 0x%lx:0x%lx is not in a memory tagged region",
|
||||
tag_range.GetRangeBase(), tag_range.GetRangeEnd());
|
||||
}
|
||||
|
||||
if (region.GetRange().GetRangeEnd() >= remaining_range.GetRangeEnd()) {
|
||||
// We've found a region for the whole range or the last piece of a range
|
||||
remaining_range.SetByteSize(0);
|
||||
} else {
|
||||
// We've found some part of the range, look for the rest
|
||||
remaining_range.SetRangeBase(region.GetRange().GetRangeEnd());
|
||||
}
|
||||
}
|
||||
|
||||
return tag_manager;
|
||||
}
|
||||
|
||||
llvm::Expected<std::vector<lldb::addr_t>>
|
||||
Process::ReadMemoryTags(const MemoryTagManager *tag_manager, lldb::addr_t addr,
|
||||
size_t len) {
|
||||
if (!tag_manager) {
|
||||
return llvm::createStringError(
|
||||
llvm::inconvertibleErrorCode(),
|
||||
"A memory tag manager is required for reading memory tags.");
|
||||
}
|
||||
|
||||
MemoryTagManager::TagRange range(tag_manager->RemoveNonAddressBits(addr),
|
||||
len);
|
||||
range = tag_manager->ExpandToGranule(range);
|
||||
|
||||
llvm::Expected<std::vector<uint8_t>> tag_data =
|
||||
DoReadMemoryTags(range.GetRangeBase(), range.GetByteSize(),
|
||||
tag_manager->GetAllocationTagType());
|
||||
if (!tag_data)
|
||||
return tag_data.takeError();
|
||||
|
||||
return tag_manager->UnpackTagsData(
|
||||
*tag_data, range.GetByteSize() / tag_manager->GetGranuleSize());
|
||||
}
|
||||
|
||||
@@ -465,3 +465,68 @@ TEST_F(GDBRemoteCommunicationClientTest, GetQOffsets) {
|
||||
EXPECT_EQ(llvm::None, GetQOffsets("TextSeg=0x1234"));
|
||||
EXPECT_EQ(llvm::None, GetQOffsets("TextSeg=12345678123456789"));
|
||||
}
|
||||
|
||||
static void
|
||||
check_qmemtags(TestClient &client, MockServer &server, size_t read_len,
|
||||
const char *packet, llvm::StringRef response,
|
||||
llvm::Optional<std::vector<uint8_t>> expected_tag_data) {
|
||||
const auto &ReadMemoryTags = [&](size_t len, const char *packet,
|
||||
llvm::StringRef response) {
|
||||
std::future<DataBufferSP> result = std::async(std::launch::async, [&] {
|
||||
return client.ReadMemoryTags(0xDEF0, read_len, 1);
|
||||
});
|
||||
|
||||
HandlePacket(server, packet, response);
|
||||
return result.get();
|
||||
};
|
||||
|
||||
auto result = ReadMemoryTags(0, packet, response);
|
||||
if (expected_tag_data) {
|
||||
ASSERT_TRUE(result);
|
||||
llvm::ArrayRef<uint8_t> expected(*expected_tag_data);
|
||||
llvm::ArrayRef<uint8_t> got = result->GetData();
|
||||
ASSERT_THAT(expected, testing::ContainerEq(got));
|
||||
} else {
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(GDBRemoteCommunicationClientTest, ReadMemoryTags) {
|
||||
// Zero length reads are valid
|
||||
check_qmemtags(client, server, 0, "qMemTags:def0,0:1", "m",
|
||||
std::vector<uint8_t>{});
|
||||
|
||||
// The client layer does not check the length of the received data.
|
||||
// All we need is the "m" and for the decode to use all of the chars
|
||||
check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m09",
|
||||
std::vector<uint8_t>{0x9});
|
||||
|
||||
// Zero length response is fine as long as the "m" is present
|
||||
check_qmemtags(client, server, 0, "qMemTags:def0,0:1", "m",
|
||||
std::vector<uint8_t>{});
|
||||
|
||||
// Normal responses
|
||||
check_qmemtags(client, server, 16, "qMemTags:def0,10:1", "m66",
|
||||
std::vector<uint8_t>{0x66});
|
||||
check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m0102",
|
||||
std::vector<uint8_t>{0x1, 0x2});
|
||||
|
||||
// Empty response is an error
|
||||
check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "", llvm::None);
|
||||
// Usual error response
|
||||
check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "E01", llvm::None);
|
||||
// Leading m missing
|
||||
check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "01", llvm::None);
|
||||
// Anything other than m is an error
|
||||
check_qmemtags(client, server, 17, "qMemTags:def0,11:1", "z01", llvm::None);
|
||||
// Decoding tag data doesn't use all the chars in the packet
|
||||
check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m09zz", llvm::None);
|
||||
// Data that is not hex bytes
|
||||
check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "mhello",
|
||||
llvm::None);
|
||||
// Data is not a complete hex char
|
||||
check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m9", llvm::None);
|
||||
// Data has a trailing hex char
|
||||
check_qmemtags(client, server, 32, "qMemTags:def0,20:1", "m01020",
|
||||
llvm::None);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user