diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.h @@ -102,6 +102,11 @@ void SetStopped(); + /// Extend m_stop_description with logical and allocation tag values. + /// If there is an error along the way just add the information we were able + /// to get. + void AnnotateSyncTagCheckFault(const siginfo_t *info); + // Member Variables lldb::StateType m_state; ThreadStopInfo m_stop_info; diff --git a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp --- a/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeThreadLinux.cpp @@ -26,6 +26,7 @@ #include "llvm/ADT/SmallString.h" #include "Plugins/Process/POSIX/CrashReason.h" +#include "Plugins/Process/Utility/MemoryTagManagerAArch64MTE.h" #include // Try to define a macro to encapsulate the tgkill syscall @@ -299,11 +300,69 @@ ? CrashReason::eInvalidAddress : GetCrashReason(*info); m_stop_description = GetCrashReasonString(reason, *info); + + if (reason == CrashReason::eSyncTagCheckFault) { + AnnotateSyncTagCheckFault(info); + } + break; } } } +void NativeThreadLinux::AnnotateSyncTagCheckFault(const siginfo_t *info) { + int32_t allocation_tag_type = 0; + switch (GetProcess().GetArchitecture().GetMachine()) { + // aarch64_32 deliberately not here because there's no 32 bit MTE + case llvm::Triple::aarch64: + case llvm::Triple::aarch64_be: + allocation_tag_type = MemoryTagManagerAArch64MTE::eMTE_allocation; + break; + default: + return; + } + + auto details = + GetRegisterContext().GetMemoryTaggingDetails(allocation_tag_type); + if (!details) { + llvm::consumeError(details.takeError()); + return; + } + + // We assume that the stop description is currently: + // signal SIGSEGV: sync tag check fault (fault address: ) + // Remove the closing ) + m_stop_description.pop_back(); + + std::stringstream ss; + lldb::addr_t fault_addr = reinterpret_cast(info->si_addr); + std::unique_ptr manager(std::move(details->manager)); + + ss << " logical tag: 0x" << std::hex << manager->GetLogicalTag(fault_addr); + + std::vector allocation_tag_data; + // The fault address may not be granule aligned. ReadMemoryTags will granule + // align any range you give it, potentially making it larger. + // To prevent this set len to 1. This always results in a range that is at + // most 1 granule in size and includes fault_addr. + Status status = GetProcess().ReadMemoryTags(allocation_tag_type, fault_addr, + 1, allocation_tag_data); + + if (status.Success()) { + llvm::Expected> allocation_tag = + manager->UnpackTagsData(allocation_tag_data, 1); + if (allocation_tag) { + ss << " allocation tag: 0x" << std::hex << allocation_tag->front() << ")"; + } else { + llvm::consumeError(allocation_tag.takeError()); + ss << ")"; + } + } else + ss << ")"; + + m_stop_description += ss.str(); +} + bool NativeThreadLinux::IsStopped(int *signo) { if (!StateIsStoppedState(m_state, false)) return false; diff --git a/lldb/test/API/linux/aarch64/mte_tag_faults/Makefile b/lldb/test/API/linux/aarch64/mte_tag_faults/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_faults/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/linux/aarch64/mte_tag_faults/TestAArch64LinuxMTEMemoryTagFaults.py b/lldb/test/API/linux/aarch64/mte_tag_faults/TestAArch64LinuxMTEMemoryTagFaults.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_faults/TestAArch64LinuxMTEMemoryTagFaults.py @@ -0,0 +1,62 @@ +""" +Test reporting of MTE tag access faults. +""" + + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class AArch64LinuxMTEMemoryTagFaultsTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + NO_DEBUG_INFO_TESTCASE = True + + def setup_mte_test(self, fault_type): + if not self.isAArch64MTE(): + self.skipTest('Target must support MTE.') + + self.build() + self.runCmd("file " + self.getBuildArtifact("a.out"), CURRENT_EXECUTABLE_SET) + + lldbutil.run_break_set_by_file_and_line(self, "main.c", + line_number('main.c', '// Breakpoint here'), + num_expected_locations=1) + + self.runCmd("run {}".format(fault_type), RUN_SUCCEEDED) + + if self.process().GetState() == lldb.eStateExited: + self.fail("Test program failed to run.") + + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + @skipUnlessAArch64MTELinuxCompiler + def test_mte_tag_fault_sync(self): + self.setup_mte_test("sync") + # The logical tag should be included in the fault address + # and we know what the bottom byte should be. + # It will be 0x10 (to be in the 2nd granule), +1 to be 0x11. + # Which tests that lldb-server handles fault addresses that + # are not granule aligned. + self.expect("continue", + patterns=[ + "\* thread #1, name = 'a.out', stop reason = signal SIGSEGV: " + "sync tag check fault \(fault address: 0x9[0-9A-Fa-f]+11\ " + "logical tag: 0x9 allocation tag: 0xa\)"]) + + @skipUnlessArch("aarch64") + @skipUnlessPlatform(["linux"]) + @skipUnlessAArch64MTELinuxCompiler + def test_mte_tag_fault_async(self): + self.setup_mte_test("async") + self.expect("continue", + substrs=[ + "* thread #1, name = 'a.out', stop reason = " + "signal SIGSEGV: async tag check fault"]) diff --git a/lldb/test/API/linux/aarch64/mte_tag_faults/main.c b/lldb/test/API/linux/aarch64/mte_tag_faults/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/linux/aarch64/mte_tag_faults/main.c @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Set bits 59-56 to tag, removing any existing tag +static char *set_tag(char *ptr, size_t tag) { + return (char *)(((size_t)ptr & ~((size_t)0xf << 56)) | (tag << 56)); +} + +int main(int argc, char const *argv[]) { + // We assume that the test runner has checked we're on an MTE system + + // Only expect to get the fault type + if (argc != 2) + return 1; + + unsigned long prctl_arg2 = 0; + if (!strcmp(argv[1], "sync")) + prctl_arg2 = PR_MTE_TCF_SYNC; + else if (!strcmp(argv[1], "async")) + prctl_arg2 = PR_MTE_TCF_ASYNC; + else + return 1; + + // Set fault type + if (prctl(PR_SET_TAGGED_ADDR_CTRL, prctl_arg2, 0, 0, 0)) + return 1; + + // Allocate some memory with tagging enabled that we + // can read/write if we use correct tags. + char *buf = mmap(0, sysconf(_SC_PAGESIZE), PROT_MTE | PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (buf == MAP_FAILED) + return 1; + + // Our pointer will have tag 9 + char *tagged_buf = set_tag(buf, 9); + // Set allocation tags for the first 2 granules + __arm_mte_set_tag(set_tag(tagged_buf, 9)); + __arm_mte_set_tag(set_tag(tagged_buf + 16, 10)); + + // Confirm that we can write when tags match + *tagged_buf = ' '; + + // Breakpoint here + // Faults because tag 9 in the ptr != allocation tag of 10. + // + 16 puts us in the second granule and +1 makes the fault address + // misaligned relative to the granule size. This misalignment must + // be accounted for by lldb-server. + *(tagged_buf + 16 + 1) = '?'; + + return 0; +}