[lldb][AArch64] Add memory tag writing to lldb-server

This is implemented using the QMemTags packet, as specified
by GDB in:
https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html#General-Query-Packets

(recall that qMemTags was previously added to read tags)

On receipt of a valid packet lldb-server will:
* align the given address and length to granules
  (most of the time lldb will have already done this
  but the specification doesn't guarantee it)
* Repeat the supplied tags as many times as needed to cover
  the range. (if tags > range we just use as many as needed)
* Call ptrace POKEMTETAGS to write the tags.

The ptrace step will loop just like the tag read does,
until all tags are written or we get an error.
Meaning that if ptrace succeeds it could be a partial write.
So we call it again and if we then get an error, return an error to
lldb.

We are not going to attempt to restore tags after a partial
write followed by an error. This matches the behaviour of the
existing memory writes.

The lldb-server tests have been extended to include read and
write in the same test file. With some updated function names
since "qMemTags" vs "QMemTags" isn't very clear when they're
next to each other.

Reviewed By: omjavaid

Differential Revision: https://reviews.llvm.org/D105180
This commit is contained in:
David Spickett 2021-03-31 14:02:34 +01:00
parent d7dd12aee3
commit 7d27230de3
9 changed files with 326 additions and 37 deletions

View File

@ -90,6 +90,9 @@ public:
virtual Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
std::vector<uint8_t> &tags);
virtual Status WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
const std::vector<uint8_t> &tags);
/// Reads a null terminated string from memory.
///
/// Reads up to \p max_size bytes of memory until it finds a '\0'.

View File

@ -168,7 +168,8 @@ public:
eServerPacketType_jLLDBTraceGetState,
eServerPacketType_jLLDBTraceGetBinaryData,
eServerPacketType_qMemTags,
eServerPacketType_qMemTags, // read memory tags
eServerPacketType_QMemTags, // write memory tags
};
ServerPacketType GetServerPacketType() const;

View File

@ -58,6 +58,13 @@ NativeProcessProtocol::ReadMemoryTags(int32_t type, lldb::addr_t addr,
return Status("not implemented");
}
lldb_private::Status
NativeProcessProtocol::WriteMemoryTags(int32_t type, lldb::addr_t addr,
size_t len,
const std::vector<uint8_t> &tags) {
return Status("not implemented");
}
llvm::Optional<WaitStatus> NativeProcessProtocol::GetExitStatus() {
if (m_state == lldb::eStateExited)
return m_exit_status;

View File

@ -1486,6 +1486,77 @@ Status NativeProcessLinux::ReadMemoryTags(int32_t type, lldb::addr_t addr,
return Status();
}
Status NativeProcessLinux::WriteMemoryTags(int32_t type, lldb::addr_t addr,
size_t len,
const std::vector<uint8_t> &tags) {
llvm::Expected<NativeRegisterContextLinux::MemoryTaggingDetails> details =
GetCurrentThread()->GetRegisterContext().GetMemoryTaggingDetails(type);
if (!details)
return Status(details.takeError());
// Ignore 0 length write
if (!len)
return Status();
// lldb will align the range it requests but it is not required to by
// the protocol so we'll do it again just in case.
// Remove non address bits too. Ptrace calls may work regardless but that
// is not a guarantee.
MemoryTagManager::TagRange range(details->manager->RemoveNonAddressBits(addr),
len);
range = details->manager->ExpandToGranule(range);
// Not checking number of tags here, we may repeat them below
llvm::Expected<std::vector<lldb::addr_t>> unpacked_tags_or_err =
details->manager->UnpackTagsData(tags);
if (!unpacked_tags_or_err)
return Status(unpacked_tags_or_err.takeError());
llvm::Expected<std::vector<lldb::addr_t>> repeated_tags_or_err =
details->manager->RepeatTagsForRange(*unpacked_tags_or_err, range);
if (!repeated_tags_or_err)
return Status(repeated_tags_or_err.takeError());
// Repack them for ptrace to use
llvm::Expected<std::vector<uint8_t>> final_tag_data =
details->manager->PackTags(*repeated_tags_or_err);
if (!final_tag_data)
return Status(final_tag_data.takeError());
struct iovec tags_vec;
uint8_t *src = final_tag_data->data();
lldb::addr_t write_addr = range.GetRangeBase();
// unpacked tags size because the number of bytes per tag might not be 1
size_t num_tags = repeated_tags_or_err->size();
// This call can partially write tags, so we loop until we
// error or all tags have been written.
while (num_tags > 0) {
tags_vec.iov_base = src;
tags_vec.iov_len = num_tags;
Status error = NativeProcessLinux::PtraceWrapper(
details->ptrace_write_req, GetID(),
reinterpret_cast<void *>(write_addr), static_cast<void *>(&tags_vec), 0,
nullptr);
if (error.Fail()) {
// Don't attempt to restore the original values in the case of a partial
// write
return error;
}
size_t tags_written = tags_vec.iov_len;
assert(tags_written && (tags_written <= num_tags));
src += tags_written * details->manager->GetTagSizeInBytes();
write_addr += details->manager->GetGranuleSize() * tags_written;
num_tags -= tags_written;
}
return Status();
}
size_t NativeProcessLinux::UpdateThreads() {
// The NativeProcessLinux monitoring threads are always up to date with
// respect to thread state and they keep the thread list populated properly.

View File

@ -83,6 +83,9 @@ public:
Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
std::vector<uint8_t> &tags) override;
Status WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len,
const std::vector<uint8_t> &tags) override;
size_t UpdateThreads() override;
const ArchSpec &GetArchitecture() const override { return m_arch; }

View File

@ -216,6 +216,10 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() {
StringExtractorGDBRemote::eServerPacketType_qMemTags,
&GDBRemoteCommunicationServerLLGS::Handle_qMemTags);
RegisterMemberFunctionHandler(
StringExtractorGDBRemote::eServerPacketType_QMemTags,
&GDBRemoteCommunicationServerLLGS::Handle_QMemTags);
RegisterPacketHandler(StringExtractorGDBRemote::eServerPacketType_k,
[this](StringExtractorGDBRemote packet, Status &error,
bool &interrupt, bool &quit) {
@ -3492,6 +3496,94 @@ GDBRemoteCommunicationServerLLGS::Handle_qMemTags(
return SendPacketNoLock(response.GetString());
}
GDBRemoteCommunication::PacketResult
GDBRemoteCommunicationServerLLGS::Handle_QMemTags(
StringExtractorGDBRemote &packet) {
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));
// Ensure we have a process.
if (!m_current_process ||
(m_current_process->GetID() == LLDB_INVALID_PROCESS_ID)) {
LLDB_LOGF(
log,
"GDBRemoteCommunicationServerLLGS::%s failed, no process available",
__FUNCTION__);
return SendErrorResponse(1);
}
// We are expecting
// QMemTags:<hex address>,<hex length>:<hex type>:<tags as hex bytes>
// Address
packet.SetFilePos(strlen("QMemTags:"));
const char *current_char = packet.Peek();
if (!current_char || *current_char == ',')
return SendIllFormedResponse(packet, "Missing address in QMemTags packet");
const lldb::addr_t addr = packet.GetHexMaxU64(/*little_endian=*/false, 0);
// Length
char previous_char = packet.GetChar();
current_char = packet.Peek();
// If we don't have a separator or the length field is empty
if (previous_char != ',' || (current_char && *current_char == ':'))
return SendIllFormedResponse(packet,
"Invalid addr,length pair in QMemTags packet");
if (packet.GetBytesLeft() < 1)
return SendIllFormedResponse(
packet, "Too short QMemtags: packet (looking for length)");
const size_t length = packet.GetHexMaxU64(/*little_endian=*/false, 0);
// Type
const char *invalid_type_err = "Invalid type field in QMemTags: packet";
if (packet.GetBytesLeft() < 1 || packet.GetChar() != ':')
return SendIllFormedResponse(packet, invalid_type_err);
// Our GetU64 uses strtoull which allows leading +/-, we don't want that.
const char *first_type_char = packet.Peek();
if (first_type_char && (*first_type_char == '+' || *first_type_char == '-'))
return SendIllFormedResponse(packet, invalid_type_err);
// The type is a signed integer but is in the packet as its raw bytes.
// So parse first as unsigned then cast to signed later.
// We extract to 64 bit, even though we only expect 32, so that we've
// got some invalid value we can check for.
uint64_t raw_type =
packet.GetU64(std::numeric_limits<uint64_t>::max(), /*base=*/16);
if (raw_type > std::numeric_limits<uint32_t>::max())
return SendIllFormedResponse(packet, invalid_type_err);
int32_t type = static_cast<int32_t>(raw_type);
// Tag data
if (packet.GetBytesLeft() < 1 || packet.GetChar() != ':')
return SendIllFormedResponse(packet,
"Missing tag data in QMemTags: packet");
// Must be 2 chars per byte
const char *invalid_data_err = "Invalid tag data in QMemTags: packet";
if (packet.GetBytesLeft() % 2)
return SendIllFormedResponse(packet, invalid_data_err);
// This is bytes here and is unpacked into target specific tags later
// We cannot assume that number of bytes == length here because the server
// can repeat tags to fill a given range.
std::vector<uint8_t> tag_data;
// Zero length writes will not have any tag data
// (but we pass them on because it will still check that tagging is enabled)
if (packet.GetBytesLeft()) {
size_t byte_count = packet.GetBytesLeft() / 2;
tag_data.resize(byte_count);
size_t converted_bytes = packet.GetHexBytes(tag_data, 0);
if (converted_bytes != byte_count) {
return SendIllFormedResponse(packet, invalid_data_err);
}
}
Status status =
m_current_process->WriteMemoryTags(type, addr, length, tag_data);
return status.Success() ? SendOKResponse() : SendErrorResponse(1);
}
void GDBRemoteCommunicationServerLLGS::MaybeCloseInferiorTerminalConnection() {
Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS));

View File

@ -218,6 +218,8 @@ protected:
PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet);
PacketResult Handle_QMemTags(StringExtractorGDBRemote &packet);
void SetCurrentThreadID(lldb::tid_t tid);
lldb::tid_t GetCurrentThreadID() const;

View File

@ -143,6 +143,11 @@ StringExtractorGDBRemote::GetServerPacketType() const {
return eServerPacketType_QListThreadsInStopReply;
break;
case 'M':
if (PACKET_STARTS_WITH("QMemTags"))
return eServerPacketType_QMemTags;
break;
case 'R':
if (PACKET_STARTS_WITH("QRestoreRegisterState:"))
return eServerPacketType_QRestoreRegisterState;

View File

@ -3,27 +3,40 @@ from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
"""
Check that lldb-server correctly processes qMemTags and QMemTags packets.
In the tests below E03 means the packet wasn't formed correctly
and E01 means it was but we had some other error acting upon it.
We do not test reading or writing over a page boundary
within the same mapping. That logic is handled in the kernel
so it's just a single ptrace call for lldb-server.
"""
class TestGdbRemoteMemoryTagging(gdbremote_testcase.GdbRemoteTestCaseBase):
mydir = TestBase.compute_mydir(__file__)
def check_qmemtags_response(self, body, expected):
self.test_sequence.add_log_lines(["read packet: $qMemTags:{}#00".format(body),
def check_memtags_response(self, packet_name, body, expected):
self.test_sequence.add_log_lines(["read packet: ${}:{}#00".format(packet_name, body),
"send packet: ${}#00".format(expected),
],
True)
self.expect_gdbremote_sequence()
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
@skipUnlessAArch64MTELinuxCompiler
def test_qmemtags_packets(self):
""" Test that qMemTags packets are parsed correctly and/or rejected. """
def check_tag_read(self, body, expected):
self.check_memtags_response("qMemTags", body, expected)
def prep_memtags_test(self):
self.build()
self.set_inferior_startup_launch()
procs = self.prep_debug_monitor_and_inferior()
# We don't use isAArch64MTE here because we cannot do runCmd in an
# lldb-server test. Instead we run the example and skip if it fails
# to allocate an MTE buffer.
# Run the process
self.test_sequence.add_log_lines(
[
@ -56,61 +69,153 @@ class TestGdbRemoteMemoryTagging(gdbremote_testcase.GdbRemoteTestCaseBase):
buf_address = int(buf_address, 16)
page_size = int(page_size, 16)
# In the tests below E03 means the packet wasn't formed correctly
# and E01 means it was but we had some other error acting upon it.
return buf_address, page_size
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
@skipUnlessAArch64MTELinuxCompiler
def test_qMemTags_packets(self):
""" Test that qMemTags packets are parsed correctly and/or rejected. """
buf_address, page_size = self.prep_memtags_test()
# Sanity check that address is correct
self.check_qmemtags_response("{:x},20:1".format(buf_address), "m0001")
self.check_tag_read("{:x},20:1".format(buf_address), "m0001")
# Invalid packets
# No content
self.check_qmemtags_response("", "E03")
self.check_tag_read("", "E03")
# Only address
self.check_qmemtags_response("{:x}".format(buf_address), "E03")
self.check_tag_read("{:x}".format(buf_address), "E03")
# Only address and length
self.check_qmemtags_response("{:x},20".format(buf_address), "E03")
self.check_tag_read("{:x},20".format(buf_address), "E03")
# Empty address
self.check_qmemtags_response(",20:1", "E03")
self.check_tag_read(",20:1", "E03")
# Invalid addresses
self.check_qmemtags_response("aardvark,20:1", "E03")
self.check_qmemtags_response("-100,20:1", "E03")
self.check_qmemtags_response("cafe?,20:1", "E03")
self.check_tag_read("aardvark,20:1", "E03")
self.check_tag_read("-100,20:1", "E03")
self.check_tag_read("cafe?,20:1", "E03")
# Empty length
self.check_qmemtags_response("{:x},:1".format(buf_address), "E03")
self.check_tag_read("{:x},:1".format(buf_address), "E03")
# Invalid lengths
self.check_qmemtags_response("{:x},food:1".format(buf_address), "E03")
self.check_qmemtags_response("{:x},-1:1".format(buf_address), "E03")
self.check_qmemtags_response("{:x},12??:1".format(buf_address), "E03")
self.check_tag_read("{:x},food:1".format(buf_address), "E03")
self.check_tag_read("{:x},-1:1".format(buf_address), "E03")
self.check_tag_read("{:x},12??:1".format(buf_address), "E03")
# Empty type
self.check_qmemtags_response("{:x},10:".format(buf_address), "E03")
self.check_tag_read("{:x},10:".format(buf_address), "E03")
# Types we don't support
self.check_qmemtags_response("{:x},10:FF".format(buf_address), "E01")
self.check_tag_read("{:x},10:FF".format(buf_address), "E01")
# (even if the length of the read is zero)
self.check_qmemtags_response("{:x},0:FF".format(buf_address), "E01")
self.check_qmemtags_response("{:x},10:-1".format(buf_address), "E01")
self.check_qmemtags_response("{:x},10:+20".format(buf_address), "E01")
self.check_tag_read("{:x},0:FF".format(buf_address), "E01")
self.check_tag_read("{:x},10:-1".format(buf_address), "E01")
self.check_tag_read("{:x},10:+20".format(buf_address), "E01")
# Invalid type format
self.check_qmemtags_response("{:x},10:cat".format(buf_address), "E03")
self.check_qmemtags_response("{:x},10:?11".format(buf_address), "E03")
self.check_tag_read("{:x},10:cat".format(buf_address), "E03")
self.check_tag_read("{:x},10:?11".format(buf_address), "E03")
# Valid packets
# Reading nothing is allowed
self.check_qmemtags_response("{:x},0:1".format(buf_address), "m")
self.check_tag_read("{:x},0:1".format(buf_address), "m")
# A range that's already aligned
self.check_qmemtags_response("{:x},20:1".format(buf_address), "m0001")
self.check_tag_read("{:x},20:1".format(buf_address), "m0001")
# lldb-server should re-align the range
# Here we read from 1/2 way through a granule, into the next. Expands to 2 granules
self.check_qmemtags_response("{:x},10:1".format(buf_address+64-8), "m0304")
self.check_tag_read("{:x},10:1".format(buf_address+64-8), "m0304")
# Read up to the end of an MTE page.
# We know that the last tag should be 0xF since page size will always be a
# multiple of 256 bytes, which is 16 granules and we have 16 tags to use.
self.check_qmemtags_response("{:x},10:1".format(buf_address+page_size-16), "m0f")
self.check_tag_read("{:x},10:1".format(buf_address+page_size-16), "m0f")
# Here we read off of the end of the MTE range so ptrace gives us one tag,
# then fails on the second call. To lldb-server this means the response
# should just be an error, not a partial read.
self.check_qmemtags_response("{:x},20:1".format(buf_address+page_size-16), "E01")
# Note that we do not test reading over a page boundary within the same
# mapping. That logic is handled in the kernel itself so it's just a single
# ptrace call for lldb-server.
self.check_tag_read("{:x},20:1".format(buf_address+page_size-16), "E01")
def check_tag_write(self, body, expected):
self.check_memtags_response("QMemTags", body, expected)
@skipUnlessArch("aarch64")
@skipUnlessPlatform(["linux"])
@skipUnlessAArch64MTELinuxCompiler
def test_QMemTags_packets(self):
""" Test that QMemTags packets are parsed correctly and/or rejected. """
buf_address, page_size = self.prep_memtags_test()
# Sanity check that address is correct
self.check_tag_write("{:x},10:1:0e".format(buf_address), "OK")
self.check_tag_read("{:x},10:1".format(buf_address), "m0e")
# No content
self.check_tag_write("", "E03")
# Only address
self.check_tag_write("{:x}".format(buf_address), "E03")
# Only address and length
self.check_tag_write("{:x},20".format(buf_address), "E03")
# Missing data
self.check_tag_write("{:x},20:1".format(buf_address), "E03")
# Zero length write must still include seperator after type
self.check_tag_write("{:x},0:1".format(buf_address), "E03")
# Empty address
self.check_tag_write(",10:1:01", "E03")
# Invalid addresses
self.check_tag_write("aardvark,10:1:01", "E03")
self.check_tag_write("-100,10:1:01", "E03")
self.check_tag_write("cafe?,10:1:01", "E03")
# Empty length
self.check_tag_write("{:x},:1:01".format(buf_address), "E03")
# Invalid lengths
self.check_tag_write("{:x},food:1:01".format(buf_address), "E03")
self.check_tag_write("{:x},-1:1:01".format(buf_address), "E03")
self.check_tag_write("{:x},12??:1:01".format(buf_address), "E03")
# Empty type
self.check_tag_write("{:x},10::01".format(buf_address), "E03")
# Types we don't support
self.check_tag_write("{:x},10:FF:01".format(buf_address), "E01")
# (even if the length of the write is zero)
self.check_tag_write("{:x},0:FF:".format(buf_address), "E01")
# Invalid type format
self.check_tag_write("{:x},0:cat:".format(buf_address), "E03")
self.check_tag_write("{:x},0:?11:".format(buf_address), "E03")
# Leading +/- not allowed
self.check_tag_write("{:x},10:-1:".format(buf_address), "E03")
self.check_tag_write("{:x},10:+20:".format(buf_address), "E03")
# We use a uint64_t when parsing, but dont expect anything > 32 bits
self.check_tag_write("{:x},10:123412341:".format(buf_address), "E03")
# Invalid tag data
self.check_tag_write("{:x},10:1:??".format(buf_address), "E03")
self.check_tag_write("{:x},10:1:45?".format(buf_address), "E03")
# (must be 2 chars per byte)
self.check_tag_write("{:x},10:1:123".format(buf_address), "E03")
# Tag out of range
self.check_tag_write("{:x},10:1:23".format(buf_address), "E01")
# Non-zero length write must include some tag data
self.check_tag_write("{:x},10:1:".format(buf_address), "E01")
# Valid packets
# Zero length write doesn't need any tag data (but should include the :)
self.check_tag_write("{:x},0:1:".format(buf_address), "OK")
# Zero length unaligned is the same
self.check_tag_write("{:x},0:1:".format(buf_address+8), "OK")
# Ranges can be aligned already
self.check_tag_write("{:x},20:1:0405".format(buf_address), "OK")
self.check_tag_read("{:x},20:1".format(buf_address), "m0405")
# Unaliged ranges will be aligned by the server
self.check_tag_write("{:x},10:1:0607".format(buf_address+8), "OK")
self.check_tag_read("{:x},20:1".format(buf_address), "m0607")
# Tags will be repeated as needed to cover the range
self.check_tag_write("{:x},30:1:09".format(buf_address), "OK")
self.check_tag_read("{:x},30:1".format(buf_address), "m090909")
# One more repeating tags for good measure, part repetition this time
# (for full tests see the MemoryTagManagerAArch64MTE unittests)
self.check_tag_write("{:x},30:1:0a0b".format(buf_address), "OK")
self.check_tag_read("{:x},30:1".format(buf_address), "m0a0b0a")
# We can write up to the end of the MTE page
last_granule = buf_address + page_size - 16;
self.check_tag_write("{:x},10:1:0c".format(last_granule), "OK")
self.check_tag_read("{:x},10:1".format(last_granule), "m0c")
# Writing over the end of the page is an error
self.check_tag_write("{:x},20:1:0d".format(last_granule), "E01")
# The last tag in the page was written thought, and we do not
# attempt to restore its original value.
self.check_tag_read("{:x},10:1".format(last_granule), "m0d")