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
This commit is contained in:
Muhammad Omair Javaid 2021-07-12 07:06:34 +05:00
parent 6ebeb7f8ba
commit 5e6aabd48e
8 changed files with 198 additions and 0 deletions

View File

@ -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__)

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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<std::recursive_mutex> lock;
this->GetWatchpointList().GetListMutex(lock);
WatchpointSP matched_sp = m_watchpoint_list.FindByAddress(addr);

View File

@ -0,0 +1,5 @@
C_SOURCES := main.c
CFLAGS_EXTRAS := -march=armv8.3-a
include Makefile.rules

View File

@ -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'])

View File

@ -0,0 +1,29 @@
#include <stdint.h>
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;
}