From 5e6aabd48e351cf2632c25cb8bdfd0598a5019a6 Mon Sep 17 00:00:00 2001 From: Muhammad Omair Javaid Date: Mon, 12 Jul 2021 07:06:34 +0500 Subject: [PATCH] Support AArch64/Linux watchpoint on tagged addresses AArch64 architecture support virtual addresses with some of the top bits ignored. These ignored bits can host memory tags or bit masks that can serve to check for authentication of address integrity. We need to clear away the top ignored bits from watchpoint address to reliably hit and set watchpoints on addresses containing tags or masks in their top bits. This patch adds support to watch tagged addresses on AArch64/Linux. Reviewed By: DavidSpickett Differential Revision: https://reviews.llvm.org/D101361 --- .../Linux/NativeRegisterContextLinux_arm64.cpp | 15 +++ .../Linux/NativeRegisterContextLinux_arm64.h | 2 + .../Utility/NativeRegisterContextDBReg_arm64.cpp | 3 + .../Utility/NativeRegisterContextDBReg_arm64.h | 3 + lldb/source/Target/Target.cpp | 6 + .../watchpoints/watch_tagged_addr/Makefile | 5 + .../watch_tagged_addr/TestWatchTaggedAddress.py | 135 +++++++++++++++++++++ .../commands/watchpoints/watch_tagged_addr/main.c | 29 +++++ 8 files changed, 198 insertions(+) create mode 100644 lldb/test/API/commands/watchpoints/watch_tagged_addr/Makefile create mode 100644 lldb/test/API/commands/watchpoints/watch_tagged_addr/TestWatchTaggedAddress.py create mode 100644 lldb/test/API/commands/watchpoints/watch_tagged_addr/main.c diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp index a0672a6..34a520e 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.cpp @@ -892,4 +892,19 @@ NativeRegisterContextLinux_arm64::GetMemoryTaggingDetails(int32_t type) { "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/Linux/NativeRegisterContextLinux_arm64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h index 6b56660..4dfc78b 100644 --- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h +++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_arm64.h @@ -76,6 +76,8 @@ protected: 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/Utility/NativeRegisterContextDBReg_arm64.cpp b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp index 5c05baf..feee857 100644 --- a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp +++ b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.cpp @@ -421,6 +421,9 @@ Status NativeRegisterContextDBReg_arm64::GetWatchpointHitIndex( 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/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h index 12ef557..3da0b04 100644 --- a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h +++ b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h @@ -72,6 +72,9 @@ protected: 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/Target/Target.cpp b/lldb/source/Target/Target.cpp index 839eb58..6cb7a99 100644 --- 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 @@ WatchpointSP Target::CreateWatchpoint(lldb::addr_t addr, size_t size, // 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 index 0000000..90555c6 --- /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 index 0000000..c7aab63 --- /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 index 0000000..b005c2a --- /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; +} -- 2.7.4