From da2e614f56b196b37175e2babfac8ed7b43ab624 Mon Sep 17 00:00:00 2001 From: David Spickett Date: Fri, 19 Feb 2021 15:57:59 +0000 Subject: [PATCH] [lldb][AArch64] Add memory tag reading to lldb-server This adds memory tag reading using the new "qMemTags" packet and ptrace on AArch64 Linux. This new packet is following the one used by GDB. (https://sourceware.org/gdb/current/onlinedocs/gdb/General-Query-Packets.html) On AArch64 Linux we use ptrace's PEEKMTETAGS to read tags and we assume that lldb has already checked that the memory region actually has tagging enabled. We do not assume that lldb has expanded the requested range to granules and expand it again to be sure. (although lldb will be sending aligned ranges because it happens to need them client side anyway) Also we don't assume untagged addresses. So for AArch64 we'll remove the top byte before using them. (the top byte includes MTE and other non address data) To do the ptrace read NativeProcessLinux will ask the native register context for a memory tag manager based on the type in the packet. This also gives you the ptrace numbers you need. (it's called a register context but it also has non register data, so it saves adding another per platform sub class) The only supported platform for this is AArch64 Linux and the only supported tag type is MTE allocation tags. Anything else will error. Ptrace can return a partial result but for lldb-server we will be treating that as an error. To succeed we need to get all the tags we expect. (Note that the protocol leaves room for logical tags to be read via qMemTags but this is not going to be implemented for lldb at this time.) Reviewed By: omjavaid Differential Revision: https://reviews.llvm.org/D95601 --- .../lldb/Host/common/NativeProcessProtocol.h | 3 + lldb/include/lldb/Host/linux/Ptrace.h | 6 + .../lldb/Utility/StringExtractorGDBRemote.h | 2 + .../Host/common/NativeProcessProtocol.cpp | 6 + .../Process/Linux/NativeProcessLinux.cpp | 55 +++++++++ .../Process/Linux/NativeProcessLinux.h | 3 + .../Linux/NativeRegisterContextLinux.h | 18 +++ .../NativeRegisterContextLinux_arm64.cpp | 13 ++ .../Linux/NativeRegisterContextLinux_arm64.h | 3 + .../GDBRemoteCommunicationServerLLGS.cpp | 70 +++++++++++ .../GDBRemoteCommunicationServerLLGS.h | 2 + .../Utility/StringExtractorGDBRemote.cpp | 2 + .../tools/lldb-server/memory-tagging/Makefile | 4 + .../TestGdbRemoteMemoryTagging.py | 116 ++++++++++++++++++ .../tools/lldb-server/memory-tagging/main.c | 55 +++++++++ 15 files changed, 358 insertions(+) create mode 100644 lldb/test/API/tools/lldb-server/memory-tagging/Makefile create mode 100644 lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py create mode 100644 lldb/test/API/tools/lldb-server/memory-tagging/main.c diff --git a/lldb/include/lldb/Host/common/NativeProcessProtocol.h b/lldb/include/lldb/Host/common/NativeProcessProtocol.h index fc5435cf4074..0650b7f7cd25 100644 --- a/lldb/include/lldb/Host/common/NativeProcessProtocol.h +++ b/lldb/include/lldb/Host/common/NativeProcessProtocol.h @@ -87,6 +87,9 @@ public: Status ReadMemoryWithoutTrap(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read); + virtual Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, + 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/Host/linux/Ptrace.h b/lldb/include/lldb/Host/linux/Ptrace.h index c54b75f07112..29489d1ae486 100644 --- a/lldb/include/lldb/Host/linux/Ptrace.h +++ b/lldb/include/lldb/Host/linux/Ptrace.h @@ -50,6 +50,12 @@ typedef int __ptrace_request; #define ARCH_GET_FS 0x1003 #define ARCH_GET_GS 0x1004 #endif +#ifndef PTRACE_PEEKMTETAGS +#define PTRACE_PEEKMTETAGS 33 +#endif +#ifndef PTRACE_POKEMTETAGS +#define PTRACE_POKEMTETAGS 34 +#endif #define LLDB_PTRACE_NT_ARM_TLS 0x401 // ARM TLS register diff --git a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h index d78bd3b139b9..6ec9e93e8238 100644 --- a/lldb/include/lldb/Utility/StringExtractorGDBRemote.h +++ b/lldb/include/lldb/Utility/StringExtractorGDBRemote.h @@ -167,6 +167,8 @@ public: eServerPacketType_jLLDBTraceStop, eServerPacketType_jLLDBTraceGetState, eServerPacketType_jLLDBTraceGetBinaryData, + + eServerPacketType_qMemTags, }; ServerPacketType GetServerPacketType() const; diff --git a/lldb/source/Host/common/NativeProcessProtocol.cpp b/lldb/source/Host/common/NativeProcessProtocol.cpp index d15bb21e8e6d..2beedd392c10 100644 --- a/lldb/source/Host/common/NativeProcessProtocol.cpp +++ b/lldb/source/Host/common/NativeProcessProtocol.cpp @@ -52,6 +52,12 @@ NativeProcessProtocol::GetMemoryRegionInfo(lldb::addr_t load_addr, return Status("not implemented"); } +lldb_private::Status +NativeProcessProtocol::ReadMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, 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.cpp b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp index 2d53972b69b7..b816d945181c 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.cpp @@ -1431,6 +1431,61 @@ llvm::Error NativeProcessLinux::DeallocateMemory(lldb::addr_t addr) { return llvm::Error::success(); } +Status NativeProcessLinux::ReadMemoryTags(int32_t type, lldb::addr_t addr, + size_t len, + std::vector &tags) { + llvm::Expected details = + GetCurrentThread()->GetRegisterContext().GetMemoryTaggingDetails(type); + if (!details) + return Status(details.takeError()); + + // Ignore 0 length read + 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); + + // Allocate enough space for all tags to be read + size_t num_tags = range.GetByteSize() / details->manager->GetGranuleSize(); + tags.resize(num_tags * details->manager->GetTagSizeInBytes()); + + struct iovec tags_iovec; + uint8_t *dest = tags.data(); + lldb::addr_t read_addr = range.GetRangeBase(); + + // This call can return partial data so loop until we error or + // get all tags back. + while (num_tags) { + tags_iovec.iov_base = dest; + tags_iovec.iov_len = num_tags; + + Status error = NativeProcessLinux::PtraceWrapper( + details->ptrace_read_req, GetID(), reinterpret_cast(read_addr), + static_cast(&tags_iovec), 0, nullptr); + + if (error.Fail()) { + // Discard partial reads + tags.resize(0); + return error; + } + + size_t tags_read = tags_iovec.iov_len; + assert(tags_read && (tags_read <= num_tags)); + + dest += tags_read * details->manager->GetTagSizeInBytes(); + read_addr += details->manager->GetGranuleSize() * tags_read; + num_tags -= tags_read; + } + + 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 64414e692058..c30d286e3e1f 100644 --- a/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeProcessLinux.h @@ -80,6 +80,9 @@ public: llvm::Error DeallocateMemory(lldb::addr_t addr) override; + Status ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, + std::vector &tags) override; + size_t UpdateThreads() override; const ArchSpec &GetArchitecture() const override { return m_arch; } diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h index 7d5ea575269d..b66f6907d8ae 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux.h @@ -11,6 +11,8 @@ #include "Plugins/Process/Utility/NativeRegisterContextRegisterInfo.h" #include "lldb/Host/common/NativeThreadProtocol.h" +#include "lldb/Target/MemoryTagManager.h" +#include "llvm/Support/Error.h" namespace lldb_private { namespace process_linux { @@ -57,6 +59,22 @@ public: /// they are supported. virtual llvm::Optional GetMmapData() { return llvm::None; } + struct MemoryTaggingDetails { + /// Object with tag handling utilities. If the function below returns + /// a valid structure, you can assume that this pointer is valid. + std::unique_ptr manager; + int ptrace_read_req; /// ptrace operation number for memory tag read + int ptrace_write_req; /// ptrace operation number for memory tag write + }; + /// Return architecture specific data needed to use memory tags, + /// if they are supported. + virtual llvm::Expected + GetMemoryTaggingDetails(int32_t type) { + return llvm::createStringError( + llvm::inconvertibleErrorCode(), + "Architecture does not support memory tagging"); + } + protected: // NB: This constructor is here only because gcc<=6.5 requires a virtual base // class initializer on abstract class (even though it is never used). It can diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp index 1a85ea0f697f..f78c0d2bb32f 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -13,6 +13,7 @@ #include "lldb/Host/common/NativeProcessProtocol.h" +#include "lldb/Host/linux/Ptrace.h" #include "lldb/Utility/DataBufferHeap.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/RegisterValue.h" @@ -21,6 +22,7 @@ #include "Plugins/Process/Linux/NativeProcessLinux.h" #include "Plugins/Process/Linux/Procfs.h" #include "Plugins/Process/POSIX/ProcessPOSIXLog.h" +#include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h" #include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h" // System includes - They have to be included after framework includes because @@ -877,4 +879,15 @@ std::vector NativeRegisterContextLinux_arm64::GetExpeditedRegisters( return expedited_reg_nums; } +llvm::Expected +NativeRegisterContextLinux_arm64::GetMemoryTaggingDetails(int32_t type) { + if (type == MemoryTagManagerAArch64MTE::eMTE_allocation) { + return MemoryTaggingDetails{std::make_unique(), + PTRACE_PEEKMTETAGS, PTRACE_POKEMTETAGS}; + } + + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "Unknown AArch64 memory tag type %d", type); +} + #endif // defined (__arm64__) || defined (__aarch64__) diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h index 12c6c0165305..f19b047380ce 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -53,6 +53,9 @@ public: bool RegisterOffsetIsDynamic() const override { return true; } + llvm::Expected + GetMemoryTaggingDetails(int32_t type) override; + protected: Status ReadGPR() override; diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp index 71c64672e4fc..000d0249cea9 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include "GDBRemoteCommunicationServerLLGS.h" @@ -211,6 +212,10 @@ void GDBRemoteCommunicationServerLLGS::RegisterPacketHandlers() { RegisterMemberFunctionHandler(StringExtractorGDBRemote::eServerPacketType_g, &GDBRemoteCommunicationServerLLGS::Handle_g); + RegisterMemberFunctionHandler( + StringExtractorGDBRemote::eServerPacketType_qMemTags, + &GDBRemoteCommunicationServerLLGS::Handle_qMemTags); + RegisterPacketHandler(StringExtractorGDBRemote::eServerPacketType_k, [this](StringExtractorGDBRemote packet, Status &error, bool &interrupt, bool &quit) { @@ -3414,6 +3419,71 @@ GDBRemoteCommunicationServerLLGS::Handle_QPassSignals( return SendOKResponse(); } +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); + + int32_t type = + packet.GetS32(std::numeric_limits::max(), /*base=*/16); + if (type == std::numeric_limits::max() || + // To catch inputs like "123aardvark" that will parse but clearly aren't + // valid in this case. + packet.GetBytesLeft()) { + return SendIllFormedResponse(packet, invalid_type_err); + } + + StreamGDBRemote response; + std::vector tags; + Status error = m_current_process->ReadMemoryTags(type, addr, length, tags); + if (error.Fail()) + return SendErrorResponse(1); + + // This m is here in case we want to support multi part replies in the future. + // In the same manner as qfThreadInfo/qsThreadInfo. + response.PutChar('m'); + response.PutBytesAsRawHex8(tags.data(), tags.size()); + return SendPacketNoLock(response.GetString()); +} + 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 bfc4f7939893..fbbe863e176a 100644 --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerLLGS.h @@ -216,6 +216,8 @@ protected: PacketResult Handle_g(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 ea4b9ef95a38..bcc40f46d5e5 100644 --- a/lldb/source/Utility/StringExtractorGDBRemote.cpp +++ b/lldb/source/Utility/StringExtractorGDBRemote.cpp @@ -223,6 +223,8 @@ StringExtractorGDBRemote::GetServerPacketType() const { return eServerPacketType_qMemoryRegionInfoSupported; if (PACKET_STARTS_WITH("qModuleInfo:")) return eServerPacketType_qModuleInfo; + if (PACKET_STARTS_WITH("qMemTags:")) + return eServerPacketType_qMemTags; break; case 'P': diff --git a/lldb/test/API/tools/lldb-server/memory-tagging/Makefile b/lldb/test/API/tools/lldb-server/memory-tagging/Makefile new file mode 100644 index 000000000000..01745f1889ea --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-tagging/Makefile @@ -0,0 +1,4 @@ +C_SOURCES := main.c +CFLAGS_EXTRAS := -march=armv8.5-a+memtag + +include Makefile.rules diff --git a/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py b/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py new file mode 100644 index 000000000000..fe80de5efbe2 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-tagging/TestGdbRemoteMemoryTagging.py @@ -0,0 +1,116 @@ +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +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), + "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. """ + + self.build() + self.set_inferior_startup_launch() + procs = self.prep_debug_monitor_and_inferior() + + # Run the process + self.test_sequence.add_log_lines( + [ + # Start running after initial stop + "read packet: $c#63", + # Match the address of the MTE page + {"type": "output_match", + "regex": self.maybe_strict_output_regex(r"buffer: (.+) page_size: (.+)\r\n"), + "capture": {1: "buffer", 2: "page_size"}}, + # Now stop the inferior + "read packet: {}".format(chr(3)), + # And wait for the stop notification + {"direction": "send", "regex": r"^\$T[0-9a-fA-F]{2}thread:[0-9a-fA-F]+;"}], + True) + + # Run the packet stream + context = self.expect_gdbremote_sequence() + self.assertIsNotNone(context) + + buf_address = context.get("buffer") + self.assertIsNotNone(buf_address) + page_size = context.get("page_size") + self.assertIsNotNone(page_size) + + # nil means we couldn't set up a tagged page because the + # target doesn't support it. + if buf_address == "(nil)": + self.skipTest("Target must support MTE.") + + 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. + + # Sanity check that address is correct + self.check_qmemtags_response("{:x},20:1".format(buf_address), "m0001") + + # Invalid packets + + # No content + self.check_qmemtags_response("", "E03") + # Only address + self.check_qmemtags_response("{:x}".format(buf_address), "E03") + # Only address and length + self.check_qmemtags_response("{:x},20".format(buf_address), "E03") + # Empty address + self.check_qmemtags_response(",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") + # Empty length + self.check_qmemtags_response("{: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") + # Empty type + self.check_qmemtags_response("{:x},10:".format(buf_address), "E03") + # Types we don't support + self.check_qmemtags_response("{: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") + # 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") + + # Valid packets + + # Reading nothing is allowed + self.check_qmemtags_response("{:x},0:1".format(buf_address), "m") + # A range that's already aligned + self.check_qmemtags_response("{: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") + # 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") + # 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. diff --git a/lldb/test/API/tools/lldb-server/memory-tagging/main.c b/lldb/test/API/tools/lldb-server/memory-tagging/main.c new file mode 100644 index 000000000000..4b1a8720dd46 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-tagging/main.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int print_result(char *ptr) { + // Page size allows the test to try reading off of the end of the page + printf("buffer: %p page_size: 0x%x\n", ptr, sysconf(_SC_PAGESIZE)); + + // Exit after some time, so we don't leave a zombie process + // if the test framework lost track of us. + sleep(60); + return 0; +} + +int main(int argc, char const *argv[]) { + if (prctl(PR_SET_TAGGED_ADDR_CTRL, + PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | + // Allow all tags to be generated by the addg + // instruction __arm_mte_increment_tag produces. + (0xffff << PR_MTE_TAG_SHIFT), + 0, 0, 0)) { + return print_result(NULL); + } + + size_t page_size = sysconf(_SC_PAGESIZE); + char *buf = mmap(0, page_size, PROT_READ | PROT_WRITE | PROT_MTE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) + return print_result(NULL); + + // Set incrementing tags until end of the page + char *tagged_ptr = buf; + // This intrinsic treats the addresses as if they were untagged + while (__arm_mte_ptrdiff(tagged_ptr, buf) < page_size) { + // This sets the allocation tag + __arm_mte_set_tag(tagged_ptr); + // Set the tag of the next granule (hence +16) to the next + // tag value. Returns a new pointer with the new logical tag. + // Tag values wrap at 0xF so it'll cycle. + tagged_ptr = __arm_mte_increment_tag(tagged_ptr + 16, 1); + } + + // lldb-server should be removing the top byte from addresses passed + // to ptrace. So put some random bits in there. + // ptrace expects you to remove them but it can still succeed if you + // don't. So this isn't proof that we're removing them, it's just a + // smoke test in case something didn't account for them. + buf = (char *)((size_t)buf | ((size_t)0xAA << 56)); + return print_result(buf); +}