diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -90,6 +90,9 @@ virtual Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, std::vector &tags); + virtual Status WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len, + const std::vector &tags); + /// Reads a null terminated string from memory. /// /// Reads up to \p max_size bytes of memory until it finds a '\0'. diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -168,7 +168,8 @@ eServerPacketType_jLLDBTraceGetState, eServerPacketType_jLLDBTraceGetBinaryData, - eServerPacketType_qMemTags, + eServerPacketType_qMemTags, // read memory tags + eServerPacketType_QMemTags, // write memory tags }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -58,6 +58,13 @@ return Status("not implemented"); } +lldb_private::Status +NativeProcessProtocol::WriteMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, + const std::vector &tags) { + return Status("not implemented"); +} + llvm::Optional NativeProcessProtocol::GetExitStatus() { if (m_state == lldb::eStateExited) return m_exit_status; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -83,6 +83,9 @@ Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, std::vector &tags) override; + Status WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len, + const std::vector &tags) override; + size_t UpdateThreads() override; const ArchSpec &GetArchitecture() const override { return m_arch; } diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1486,6 +1486,77 @@ return Status(); } +Status NativeProcessLinux::WriteMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, + const std::vector &tags) { + llvm::Expected 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> unpacked_tags_or_err = + details->manager->UnpackTagsData(tags); + if (!unpacked_tags_or_err) + return Status(unpacked_tags_or_err.takeError()); + + llvm::Expected> 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> 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(write_addr), static_cast(&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. diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -218,6 +218,8 @@ PacketResult Handle_qMemTags(StringExtractorGDBRemote &packet); + PacketResult Handle_QMemTags(StringExtractorGDBRemote &packet); + void SetCurrentThreadID(lldb::tid_t tid); lldb::tid_t GetCurrentThreadID() const; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -216,6 +216,10 @@ 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 @@ 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:,:: + + // 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::max(), /*base=*/16); + if (raw_type > std::numeric_limits::max()) + return SendIllFormedResponse(packet, invalid_type_err); + int32_t type = static_cast(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 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)); diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -143,6 +143,11 @@ 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; diff --git a/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py b/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py --- a/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py +++ b/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py @@ -3,27 +3,40 @@ 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 @@ 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")