diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -76,6 +76,8 @@ size_t GetFPRSize() override { return sizeof(m_fpr); } + lldb::addr_t FixWatchpointHitAddress(lldb::addr_t hit_addr) override; + private: bool m_gpr_is_valid; bool m_fpu_is_valid; diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -892,4 +892,19 @@ "Unknown AArch64 memory tag type %d", type); } +lldb::addr_t NativeRegisterContextLinux_arm64::FixWatchpointHitAddress( + lldb::addr_t hit_addr) { + // Linux configures user-space virtual addresses with top byte ignored. + // We set default value of mask such that top byte is masked out. + lldb::addr_t mask = ~((1ULL << 56) - 1); + + // Try to read pointer authentication data_mask register and calculate a + // consolidated data address mask after ignoring the top byte. + if (ReadPAuthMask().Success()) + mask |= m_pac_mask.data_mask; + + return hit_addr & ~mask; + ; +} + #endif // defined (__arm64__) || defined (__aarch64__) diff --git a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h --- a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h +++ b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h @@ -72,6 +72,9 @@ virtual llvm::Error ReadHardwareDebugInfo() = 0; virtual llvm::Error WriteHardwareDebugRegs(DREGType hwbType) = 0; + virtual lldb::addr_t FixWatchpointHitAddress(lldb::addr_t hit_addr) { + return hit_addr; + } }; } // namespace lldb_private diff --git a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp --- a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp +++ b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp @@ -421,6 +421,9 @@ if (error) return Status(std::move(error)); + // Mask off ignored bits from watchpoint trap address. + trap_addr = FixWatchpointHitAddress(trap_addr); + uint32_t watch_size; lldb::addr_t watch_addr; diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -41,6 +41,7 @@ #include "lldb/Symbol/Function.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Symbol/Symbol.h" +#include "lldb/Target/ABI.h" #include "lldb/Target/Language.h" #include "lldb/Target/LanguageRuntime.h" #include "lldb/Target/Process.h" @@ -823,6 +824,11 @@ // Grab the list mutex while doing operations. const bool notify = false; // Don't notify about all the state changes we do // on creating the watchpoint. + + // Mask off ignored bits from watchpoint address. + if (ABISP abi = m_process_sp->GetABI()) + addr = abi->FixDataAddress(addr); + std::unique_lock lock; this->GetWatchpointList().GetListMutex(lock); WatchpointSP matched_sp = m_watchpoint_list.FindByAddress(addr); diff --git a/lldb/test/API/commands/watchpoints/watch_tagged_addr/Makefile b/lldb/test/API/commands/watchpoints/watch_tagged_addr/Makefile new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/watchpoints/watch_tagged_addr/Makefile @@ -0,0 +1,5 @@ +C_SOURCES := main.c + +CFLAGS_EXTRAS := -march=armv8.3-a + +include Makefile.rules diff --git a/lldb/test/API/commands/watchpoints/watch_tagged_addr/TestWatchTaggedAddress.py b/lldb/test/API/commands/watchpoints/watch_tagged_addr/TestWatchTaggedAddress.py new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/watchpoints/watch_tagged_addr/TestWatchTaggedAddress.py @@ -0,0 +1,135 @@ +""" +Test LLDB can set and hit watchpoints on tagged addresses +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + +class TestWatchTaggedAddresses(TestBase): + + mydir = TestBase.compute_mydir(__file__) + NO_DEBUG_INFO_TESTCASE = True + + def setUp(self): + # Call super's setUp(). + TestBase.setUp(self) + + # Set source filename. + self.source = 'main.c' + + # Invoke the default build rule. + self.build() + + # Get the path of the executable + exe = self.getBuildArtifact("a.out") + + # Create a target by the debugger. + self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET) + + @skipIf(archs=no_match(["aarch64"])) + @skipIf(oslist=no_match(['linux'])) + def test_watch_hit_tagged_ptr_access(self): + """ + Test that LLDB hits watchpoint installed on an untagged address with + memory access by a tagged pointer. + """ + if not self.isAArch64PAuth(): + self.skipTest('Target must support pointer authentication.') + + # Add a breakpoint to set a watchpoint when stopped on the breakpoint. + lldbutil.run_break_set_by_symbol(self, 'main') + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # We should be stopped due to the breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + # Set the watchpoint variable declaration line number. + self.decl = line_number(self.source, + '// Watchpoint variable declaration.') + + # Now let's set a watchpoint on 'global_var'. + self.expect( + "watchpoint set variable global_var", + WATCHPOINT_CREATED, + substrs=[ + 'Watchpoint created', + 'size = 4', + 'type = w', + '%s:%d' % + (self.source, + self.decl)]) + + self.verify_watch_hits() + + @skipIf(archs=no_match(["aarch64"])) + @skipIf(oslist=no_match(['linux'])) + def test_watch_set_on_tagged_ptr(self): + """Test that LLDB can install and hit watchpoint on a tagged address""" + + if not self.isAArch64PAuth(): + self.skipTest('Target must support pointer authentication.') + + # Find the line number to break inside main(). + self.line = line_number(self.source, '// Set break point at this line.') + + # Add a breakpoint to set a watchpoint when stopped on the breakpoint. + lldbutil.run_break_set_by_file_and_line( + self, None, self.line, num_expected_locations=1) + + # Run the program. + self.runCmd("run", RUN_SUCCEEDED) + + # We should be stopped due to the breakpoint. + self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', + 'stop reason = breakpoint']) + + # Now let's set a expression watchpoint on 'tagged_ptr'. + self.expect( + "watchpoint set expression -s 4 -- tagged_ptr", + WATCHPOINT_CREATED, + substrs=[ + 'Watchpoint created', + 'size = 4', + 'type = w']) + + self.verify_watch_hits() + + def verify_watch_hits(self): + # Use the '-v' option to do verbose listing of the watchpoint. + # The hit count should be 0 initially. + self.expect("watchpoint list -v", + substrs=['Number of supported hardware watchpoints:', + 'hit_count = 0']) + + self.runCmd("process continue") + + # We should be stopped again due to the watchpoint (read_write type). + # The stop reason of the thread should be watchpoint. + self.expect("thread backtrace", STOPPED_DUE_TO_WATCHPOINT, + substrs=['stop reason = watchpoint']) + + self.runCmd("process continue") + + # We should be stopped again due to the watchpoint (read_write type). + # The stop reason of the thread should be watchpoint. + self.expect("thread backtrace", STOPPED_DUE_TO_WATCHPOINT, + substrs=['stop reason = watchpoint']) + + self.runCmd("process continue") + + # There should be no more watchpoint hit and the process status should + # be 'exited'. + self.expect("process status", + substrs=['exited']) + + # Use the '-v' option to do verbose listing of the watchpoint. + # The hit count should now be 2. + self.expect("watchpoint list -v", + substrs=['hit_count = 2']) diff --git a/lldb/test/API/commands/watchpoints/watch_tagged_addr/main.c b/lldb/test/API/commands/watchpoints/watch_tagged_addr/main.c new file mode 100644 --- /dev/null +++ b/lldb/test/API/commands/watchpoints/watch_tagged_addr/main.c @@ -0,0 +1,29 @@ +#include + +uint32_t global_var = 0; // Watchpoint variable declaration. + +int main(int argc, char **argv) { + int dummy = 0; + // Move address of global variable into tagged_ptr after tagging + // Simple tagging scheme where 62nd bit of tagged address is set + uint32_t *tagged_ptr = (uint32_t *)((uint64_t)&global_var | (1ULL << 62)); + + // pacdza computes and inserts a pointer authentication code for address + // stored in tagged_ptr using PAC key A. + __asm__ __volatile__("pacdza %0" : "=r"(tagged_ptr) : "r"(tagged_ptr)); + + ++dummy; // Set break point at this line. + + // Increment global_var + ++global_var; + + ++dummy; + + // autdza authenticates tagged_ptr using PAC key A. + __asm__ __volatile__("autdza %0" : "=r"(tagged_ptr) : "r"(tagged_ptr)); + + // Increment global_var using tagged_ptr + ++*tagged_ptr; + + return 0; +}