diff options
author | David Spickett <david.spickett@linaro.org> | 2021-03-31 14:02:34 +0100 |
---|---|---|
committer | David Spickett <david.spickett@linaro.org> | 2021-07-27 12:02:17 +0100 |
commit | 7d27230de3336b8c79bfdc90f59858f6dad28fa5 (patch) | |
tree | df523622b95d310a6e102d2e9aeb9e9322719d03 /lldb/source | |
parent | d7dd12aee399a19e890143604b6993f02232ca24 (diff) | |
download | llvm-7d27230de3336b8c79bfdc90f59858f6dad28fa5.zip llvm-7d27230de3336b8c79bfdc90f59858f6dad28fa5.tar.gz llvm-7d27230de3336b8c79bfdc90f59858f6dad28fa5.tar.bz2 |
[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
Diffstat (limited to 'lldb/source')
6 files changed, 180 insertions, 0 deletions
diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index 2beedd3..ea80a05 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -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; diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index b816d94..8c45796 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -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. diff --git a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h index c30d286..902afb6 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -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; } diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index f6c3ba4..5e69b57 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -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)); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h index fbbe863..04d0605 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -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; diff --git a/lldb/source/Utility/StringExtractorGDBRemote.cpp b/lldb/source/Utility/StringExtractorGDBRemote.cpp index bcc40f4..29cf585 100644 --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -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; |