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 @@ -246,6 +246,10 @@ // Handle a clone()-like event. bool MonitorClone(NativeThreadLinux &parent, lldb::pid_t child_pid, int event); + + // Remove any bits that are not virtual address bits. For example on AArch64 + // this includes the top byte, memory tags and pointer signature. + lldb::addr_t RemoveNonAddressBits(lldb::addr_t addr); }; } // namespace process_linux 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 @@ -1351,9 +1351,42 @@ return llvm::Error::success(); } +lldb::addr_t NativeProcessLinux::RemoveNonAddressBits(lldb::addr_t addr) { + NativeRegisterContextLinux &context = + GetCurrentThread()->GetRegisterContext(); + const RegisterInfo *reg_info = context.GetRegisterInfoByName("data_mask"); + const addr_t original = addr; + addr_t mask = 0; + + if (reg_info) { + // If this fails, inverse of 0 keeps all the bits intact. + mask = context.ReadRegisterAsUnsigned(reg_info, 0); + } + + const lldb_private::ArchSpec &arch = GetArchitecture(); + // AArch64 Linux always has top byte ignore enabled for userspace. + if (arch.IsValid() && arch.GetTriple().isAArch64()) + addr |= (addr_t)0xFF << 56; + + addr &= ~mask; + + Log *log = GetLog(POSIXLog::Memory); + LLDB_LOG(log, + "Removing non address bits from address {0:x} using mask {1:x}, new " + "address is {2:x}", + original, mask, addr); + + return addr; +} + Status NativeProcessLinux::ReadMemoryTags(int32_t type, lldb::addr_t addr, size_t len, std::vector &tags) { + Log *log = GetLog(POSIXLog::Memory); + LLDB_LOG(log, "type: {0} addr: {1:x} len: {2}", type, addr, len); + + addr = RemoveNonAddressBits(addr); + llvm::Expected details = GetCurrentThread()->GetRegisterContext().GetMemoryTaggingDetails(type); if (!details) @@ -1408,6 +1441,11 @@ Status NativeProcessLinux::WriteMemoryTags(int32_t type, lldb::addr_t addr, size_t len, const std::vector &tags) { + Log *log = GetLog(POSIXLog::Memory); + LLDB_LOG(log, "type: {0} addr: {1:x} len: {2}", type, addr, len); + + addr = RemoveNonAddressBits(addr); + llvm::Expected details = GetCurrentThread()->GetRegisterContext().GetMemoryTaggingDetails(type); if (!details) @@ -1522,6 +1560,11 @@ Status NativeProcessLinux::ReadMemory(lldb::addr_t addr, void *buf, size_t size, size_t &bytes_read) { + Log *log = GetLog(POSIXLog::Memory); + LLDB_LOG(log, "addr = {0:x}, buf = {1:x}, size = {2}", addr, buf, size); + + addr = RemoveNonAddressBits(addr); + if (ProcessVmReadvSupported()) { // The process_vm_readv path is about 50 times faster than ptrace api. We // want to use this syscall if it is supported. @@ -1553,9 +1596,6 @@ size_t remainder; long data; - Log *log = GetLog(POSIXLog::Memory); - LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size); - for (bytes_read = 0; bytes_read < size; bytes_read += remainder) { Status error = NativeProcessLinux::PtraceWrapper( PTRACE_PEEKDATA, GetID(), (void *)addr, nullptr, 0, &data); @@ -1582,7 +1622,9 @@ Status error; Log *log = GetLog(POSIXLog::Memory); - LLDB_LOG(log, "addr = {0}, buf = {1}, size = {2}", addr, buf, size); + LLDB_LOG(log, "addr = {0:x}, buf = {1:x}, size = {2}", addr, buf, size); + + addr = RemoveNonAddressBits(addr); for (bytes_written = 0; bytes_written < size; bytes_written += remainder) { remainder = size - bytes_written; diff --git a/lldb/test/API/tools/lldb-server/memory-non-address-bits/Makefile b/lldb/test/API/tools/lldb-server/memory-non-address-bits/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-non-address-bits/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-non-address-bits/TestGdbRemoteMemoryNonAddressBits.py b/lldb/test/API/tools/lldb-server/memory-non-address-bits/TestGdbRemoteMemoryNonAddressBits.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-non-address-bits/TestGdbRemoteMemoryNonAddressBits.py @@ -0,0 +1,86 @@ +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +""" +Check that lldb-server correctly removes the non-address bits +required to make ptrace calls on Linux. +""" + +class TestGdbRemoteMemoryNonAddressBits(gdbremote_testcase.GdbRemoteTestCaseBase): + + mydir = TestBase.compute_mydir(__file__) + + def prep_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. + # Pointer signing is done with a hint space instruction so the test + # can run on pre armv8.3-a CPUs. (though it will not be doing any useful + # checking in that case) + + # Run the process + self.test_sequence.add_log_lines( + [ + # Start running after initial stop + "read packet: $c#63", + # Match the plain and signed pointers + {"type": "output_match", + "regex": self.maybe_strict_output_regex(r"buf: (.+) buf_with_non_address: (.+)\r\n"), + "capture": {1: "buf", 2: "buf_with_non_address"}}, + # 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 = context.get("buf") + self.assertIsNotNone(buf) + buf_with_non_address = context.get("buf_with_non_address") + self.assertIsNotNone(buf_with_non_address) + + # nil means we couldn't set up a tagged page because the + # target doesn't support it. + if buf == "(nil)": + self.skipTest("Target must support MTE.") + + buf = int(buf, 16) + buf_with_non_address = int(buf_with_non_address, 16) + + return buf, buf_with_non_address + + def check_response(self, packet, expected): + self.test_sequence.add_log_lines(["read packet: ${}#00".format(packet), + "send packet: ${}#01".format(expected), + ], + True) + self.expect_gdbremote_sequence() + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + @skipUnlessAArch64MTELinuxCompiler + def test_non_address_bit_read_write(self): + """ Test that lldb-server removes non-address bits as needed for ptrace + operations where a virtual address is used.""" + buf, buf_with_non_address = self.prep_test() + + self.check_response("x{:x},1".format(buf), "\x00") + self.check_response("x{:x},1".format(buf_with_non_address), "\x00") + + self.check_response("M{:x},1:01".format(buf), "OK") + self.check_response("M{:x},1:01".format(buf_with_non_address), "OK") + + self.check_response("qMemTags:{:x},1:1".format(buf), "m00") + self.check_response("qMemTags:{:x},1:1".format(buf_with_non_address), "m00") + + self.check_response("QMemTags:{:x},1:1:00".format(buf), "OK") + self.check_response("QMemTags:{:x},1:1:00".format(buf_with_non_address), "OK") diff --git a/lldb/test/API/tools/lldb-server/memory-non-address-bits/main.c b/lldb/test/API/tools/lldb-server/memory-non-address-bits/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/tools/lldb-server/memory-non-address-bits/main.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +int print_pointers(char *buf, char *buf_with_non_address) { + // This output is picked up by the test. + printf("buf: %p buf_with_non_address: %p\n", buf, buf_with_non_address); + + // 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[]) { + // We need to allocate a memory tagged page to know whether + // our memory tag read/write packets succeeded. + if (prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC, 0, + 0, 0)) { + return print_pointers(NULL, 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_pointers(NULL, NULL); + + // Set top byte including where memory tag bits would go. + char *buf_with_non_address = (char *)((size_t)buf | (size_t)0xff << 56); + // Sign it using a hint space instruction in case the core doesn't have + // pointer authentication. + __asm__ __volatile__("pacdza %0" + : "=r"(buf_with_non_address) + : "r"(buf_with_non_address)); + // Address is now: + // <4 bit user tag><4 bit memory tag> + + return print_pointers(buf, buf_with_non_address); +}