Skip to content

Commit 2e16e41

Browse files
committed
Add AArch64 MASK watchpoint support in debugserver
Add suport for MASK style watchpoints on AArch64 in debugserver on Darwin systems, for watching power-of-2 sized memory ranges. More work needed in lldb before this can be exposed to the user (because they will often try watching memory ranges that are not exactly power-of-2 in size/alignment) but this is the first part of adding that capability. Differential Revision: https://reviews.llvm.org/D149792 rdar://108233371
1 parent 4fac08f commit 2e16e41

File tree

6 files changed

+217
-39
lines changed

6 files changed

+217
-39
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""
2+
Watch larger-than-8-bytes regions of memory, confirm that
3+
writes to those regions are detected.
4+
"""
5+
6+
7+
8+
import lldb
9+
from lldbsuite.test.decorators import *
10+
from lldbsuite.test.lldbtest import *
11+
from lldbsuite.test import lldbutil
12+
13+
class UnalignedWatchpointTestCase(TestBase):
14+
15+
def continue_and_report_stop_reason(self, process, iter_str):
16+
process.Continue()
17+
self.assertIn(process.GetState(), [lldb.eStateStopped, lldb.eStateExited],
18+
iter_str)
19+
thread = process.GetSelectedThread()
20+
return thread.GetStopReason()
21+
22+
23+
NO_DEBUG_INFO_TESTCASE = True
24+
# debugserver on AArch64 has this feature.
25+
@skipIf(archs=no_match(['arm64', 'arm64e', 'aarch64']))
26+
@skipUnlessDarwin
27+
28+
# debugserver only gained the ability to watch larger regions
29+
# with this patch.
30+
@skipIfOutOfTreeDebugserver
31+
32+
def test_large_watchpoint(self):
33+
"""Test watchpoint that covers a large region of memory."""
34+
self.build()
35+
self.main_source_file = lldb.SBFileSpec("main.c")
36+
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
37+
"break here", self.main_source_file)
38+
39+
frame = thread.GetFrameAtIndex(0)
40+
41+
array_addr = frame.GetValueForVariablePath("array").GetValueAsUnsigned()
42+
43+
# watch 256 uint32_t elements in the middle of the array,
44+
# don't assume that the heap allocated array is aligned
45+
# to a 1024 byte boundary to begin with, force alignment.
46+
wa_256_addr = ((array_addr + 1024) & ~(1024-1))
47+
err = lldb.SBError()
48+
wp = target.WatchAddress(wa_256_addr, 1024, False, True, err)
49+
self.assertTrue(wp.IsValid())
50+
self.assertSuccess(err)
51+
52+
c_count = 0
53+
reason = self.continue_and_report_stop_reason(process, "continue #%d" % c_count)
54+
while reason == lldb.eStopReasonWatchpoint:
55+
c_count = c_count + 1
56+
reason = self.continue_and_report_stop_reason(process, "continue #%d" % c_count)
57+
self.assertLessEqual(c_count, 16)
58+
59+
self.assertEqual(c_count, 16)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <stdint.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include <stdlib.h>
5+
int main() {
6+
const int count = 65535;
7+
int *array = (int*) malloc(sizeof (int) * count);
8+
memset (array, 0, count * sizeof (int));
9+
10+
puts ("break here");
11+
12+
for (int i = 0; i < count - 16; i += 16)
13+
array[i] += 10;
14+
15+
puts ("done, exiting.");
16+
}

lldb/test/API/python_api/watchpoint/watchlocation/TestTargetWatchAddress.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,15 @@ def test_watch_address_with_invalid_watch_size(self):
129129
"pointee",
130130
value.GetValueAsUnsigned(0),
131131
value.GetType().GetPointeeType())
132-
# Watch for write to *g_char_ptr.
133-
error = lldb.SBError()
134-
watchpoint = target.WatchAddress(
135-
value.GetValueAsUnsigned(), 365, False, True, error)
136-
self.assertFalse(watchpoint)
137-
self.expect(error.GetCString(), exe=False,
138-
substrs=['watch size of %d is not supported' % 365])
132+
133+
# debugserver on Darwin AArch64 systems can watch large regions
134+
# of memory via https://reviews.llvm.org/D149792 , don't run this
135+
# test there.
136+
if self.getArchitecture() not in ['arm64', 'arm64e', 'arm64_32']:
137+
# Watch for write to *g_char_ptr.
138+
error = lldb.SBError()
139+
watchpoint = target.WatchAddress(
140+
value.GetValueAsUnsigned(), 365, False, True, error)
141+
self.assertFalse(watchpoint)
142+
self.expect(error.GetCString(), exe=False,
143+
substrs=['watch size of %d is not supported' % 365])

lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.cpp

Lines changed: 125 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@
4242
#define WCR_LOAD ((uint32_t)(1u << 3))
4343
#define WCR_STORE ((uint32_t)(1u << 4))
4444

45-
// Enable breakpoint, watchpoint, and vector catch debug exceptions.
46-
// (MDE bit in the MDSCR_EL1 register. Equivalent to the MDBGen bit in
47-
// DBGDSCRext in Aarch32)
48-
#define MDE_ENABLE ((uint32_t)(1u << 15))
49-
5045
// Single instruction step
5146
// (SS bit in the MDSCR_EL1 register)
5247
#define SS_ENABLE ((uint32_t)(1u))
@@ -807,8 +802,6 @@ uint32_t DNBArchMachARM64::EnableHardwareBreakpoint(nub_addr_t addr,
807802
(uint64_t)m_state.dbg.__bvr[i],
808803
(uint32_t)m_state.dbg.__bcr[i]);
809804

810-
// The kernel will set the MDE_ENABLE bit in the MDSCR_EL1 for us
811-
// automatically, don't need to do it here.
812805
kret = SetDBGState(also_set_on_task);
813806

814807
DNBLogThreadedIf(LOG_WATCHPOINTS,
@@ -848,9 +841,10 @@ DNBArchMachARM64::AlignRequestedWatchpoint(nub_addr_t requested_addr,
848841

849842
/// Round up \a requested_size to the next power-of-2 size, at least 8
850843
/// bytes
851-
/// requested_size == 3 -> aligned_size == 8
852-
/// requested_size == 13 -> aligned_size == 16
853-
/// requested_size == 16 -> aligned_size == 16
844+
/// requested_size == 8 -> aligned_size == 8
845+
/// requested_size == 9 -> aligned_size == 16
846+
/// requested_size == 15 -> aligned_size == 16
847+
/// requested_size == 192 -> aligned_size == 256
854848
/// Could be `std::bit_ceil(aligned_size)` when we build with C++20?
855849
aligned_size = 1ULL << (addr_bit_size - __builtin_clzll(aligned_size - 1));
856850

@@ -922,7 +916,7 @@ uint32_t DNBArchMachARM64::EnableHardwareWatchpoint(nub_addr_t addr,
922916
if (wps[0].aligned_size <= 8)
923917
return SetBASWatchpoint(wps[0], read, write, also_set_on_task);
924918
else
925-
return INVALID_NUB_HW_INDEX;
919+
return SetMASKWatchpoint(wps[0], read, write, also_set_on_task);
926920
}
927921

928922
// We have multiple WatchpointSpecs
@@ -1024,9 +1018,6 @@ uint32_t DNBArchMachARM64::SetBASWatchpoint(DNBArchMachARM64::WatchpointSpec wp,
10241018
(uint64_t)m_state.dbg.__wvr[i],
10251019
(uint32_t)m_state.dbg.__wcr[i]);
10261020

1027-
// The kernel will set the MDE_ENABLE bit in the MDSCR_EL1 for us
1028-
// automatically, don't need to do it here.
1029-
10301021
kret = SetDBGState(also_set_on_task);
10311022
// DumpDBGState(m_state.dbg);
10321023

@@ -1042,6 +1033,81 @@ uint32_t DNBArchMachARM64::SetBASWatchpoint(DNBArchMachARM64::WatchpointSpec wp,
10421033
return INVALID_NUB_HW_INDEX;
10431034
}
10441035

1036+
uint32_t
1037+
DNBArchMachARM64::SetMASKWatchpoint(DNBArchMachARM64::WatchpointSpec wp,
1038+
bool read, bool write,
1039+
bool also_set_on_task) {
1040+
const uint32_t num_hw_watchpoints = NumSupportedHardwareWatchpoints();
1041+
1042+
// Read the debug state
1043+
kern_return_t kret = GetDBGState(false);
1044+
if (kret != KERN_SUCCESS)
1045+
return INVALID_NUB_HW_INDEX;
1046+
1047+
// Check to make sure we have the needed hardware support
1048+
uint32_t i = 0;
1049+
1050+
for (i = 0; i < num_hw_watchpoints; ++i) {
1051+
if ((m_state.dbg.__wcr[i] & WCR_ENABLE) == 0)
1052+
break; // We found an available hw watchpoint slot
1053+
}
1054+
if (i == num_hw_watchpoints) {
1055+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1056+
"DNBArchMachARM64::"
1057+
"SetMASKWatchpoint(): All "
1058+
"hardware resources (%u) are in use.",
1059+
num_hw_watchpoints);
1060+
return INVALID_NUB_HW_INDEX;
1061+
}
1062+
1063+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1064+
"DNBArchMachARM64::"
1065+
"SetMASKWatchpoint() "
1066+
"set hardware register %d to MASK watchpoint "
1067+
"aligned start address 0x%llx, aligned size %zu",
1068+
i, wp.aligned_start, wp.aligned_size);
1069+
1070+
// Clear any previous LoHi joined-watchpoint that may have been in use
1071+
LoHi[i] = 0;
1072+
1073+
// MASK field is the number of low bits that are masked off
1074+
// when comparing the address with the DBGWVR<n>_EL1 values.
1075+
// If aligned size is 16, that means we ignore low 4 bits, 0b1111.
1076+
// popcount(16 - 1) give us the correct value of 4.
1077+
// 2GB is max watchable region, which is 31 bits (low bits 0x7fffffff
1078+
// masked off) -- a MASK value of 31.
1079+
const uint64_t mask = __builtin_popcountl(wp.aligned_size - 1) << 24;
1080+
// A '0b11111111' BAS value needed for mask watchpoints plus a
1081+
// nonzero mask value.
1082+
const uint64_t not_bas_wp = 0xff << 5;
1083+
1084+
m_state.dbg.__wvr[i] = wp.aligned_start;
1085+
m_state.dbg.__wcr[i] = mask | not_bas_wp | S_USER | // Stop only in user mode
1086+
(read ? WCR_LOAD : 0) | // Stop on read access?
1087+
(write ? WCR_STORE : 0) | // Stop on write access?
1088+
WCR_ENABLE; // Enable this watchpoint;
1089+
1090+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1091+
"DNBArchMachARM64::SetMASKWatchpoint() "
1092+
"adding watchpoint on address 0x%llx with control "
1093+
"register value 0x%llx",
1094+
(uint64_t)m_state.dbg.__wvr[i],
1095+
(uint64_t)m_state.dbg.__wcr[i]);
1096+
1097+
kret = SetDBGState(also_set_on_task);
1098+
1099+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1100+
"DNBArchMachARM64::"
1101+
"SetMASKWatchpoint() "
1102+
"SetDBGState() => 0x%8.8x.",
1103+
kret);
1104+
1105+
if (kret == KERN_SUCCESS)
1106+
return i;
1107+
1108+
return INVALID_NUB_HW_INDEX;
1109+
}
1110+
10451111
bool DNBArchMachARM64::ReenableHardwareWatchpoint(uint32_t hw_index) {
10461112
// If this logical watchpoint # is actually implemented using
10471113
// two hardware watchpoint registers, re-enable both of them.
@@ -1068,14 +1134,11 @@ bool DNBArchMachARM64::ReenableHardwareWatchpoint_helper(uint32_t hw_index) {
10681134

10691135
DNBLogThreadedIf(LOG_WATCHPOINTS,
10701136
"DNBArchMachARM64::"
1071-
"SetBASWatchpoint( %u ) - WVR%u = "
1137+
"ReenableHardwareWatchpoint_helper( %u ) - WVR%u = "
10721138
"0x%8.8llx WCR%u = 0x%8.8llx",
10731139
hw_index, hw_index, (uint64_t)m_state.dbg.__wvr[hw_index],
10741140
hw_index, (uint64_t)m_state.dbg.__wcr[hw_index]);
10751141

1076-
// The kernel will set the MDE_ENABLE bit in the MDSCR_EL1 for us
1077-
// automatically, don't need to do it here.
1078-
10791142
kret = SetDBGState(false);
10801143

10811144
return (kret == KERN_SUCCESS);
@@ -1177,30 +1240,61 @@ uint32_t DNBArchMachARM64::GetHardwareWatchpointHit(nub_addr_t &addr) {
11771240
uint32_t i, num = NumSupportedHardwareWatchpoints();
11781241
for (i = 0; i < num; ++i) {
11791242
nub_addr_t wp_addr = GetWatchAddress(debug_state, i);
1180-
uint32_t byte_mask = bits(debug_state.__wcr[i], 12, 5);
11811243

11821244
DNBLogThreadedIf(LOG_WATCHPOINTS,
11831245
"DNBArchImplARM64::"
11841246
"GetHardwareWatchpointHit() slot: %u "
1185-
"(addr = 0x%llx; byte_mask = 0x%x)",
1186-
i, static_cast<uint64_t>(wp_addr), byte_mask);
1247+
"(addr = 0x%llx, WCR = 0x%llx)",
1248+
i, wp_addr, debug_state.__wcr[i]);
11871249

11881250
if (!IsWatchpointEnabled(debug_state, i))
11891251
continue;
11901252

1191-
if (bits(wp_addr, 48, 3) != bits(addr, 48, 3))
1192-
continue;
1253+
// DBGWCR<n>EL1.BAS are the bits of the doubleword that are watched
1254+
// with a BAS watchpoint.
1255+
uint32_t bas_bits = bits(debug_state.__wcr[i], 12, 5);
1256+
// DBGWCR<n>EL1.MASK is the number of bits that are masked off the
1257+
// virtual address when comparing to DBGWVR<n>_EL1.
1258+
uint32_t mask = bits(debug_state.__wcr[i], 28, 24);
11931259

1194-
// Sanity check the byte_mask
1195-
uint32_t lsb = LowestBitSet(byte_mask);
1196-
if (lsb < 0)
1197-
continue;
1260+
const bool is_bas_watchpoint = mask == 0;
11981261

1199-
uint64_t byte_to_match = bits(addr, 2, 0);
1262+
DNBLogThreadedIf(
1263+
LOG_WATCHPOINTS,
1264+
"DNBArchImplARM64::"
1265+
"GetHardwareWatchpointHit() slot: %u %s",
1266+
i, is_bas_watchpoint ? "is BAS watchpoint" : "is MASK watchpoint");
12001267

1201-
if (byte_mask & (1 << byte_to_match)) {
1202-
addr = wp_addr + lsb;
1203-
return i;
1268+
if (is_bas_watchpoint) {
1269+
if (bits(wp_addr, 48, 3) != bits(addr, 48, 3))
1270+
continue;
1271+
} else {
1272+
if (bits(wp_addr, 48, mask) == bits(addr, 48, mask)) {
1273+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1274+
"DNBArchImplARM64::"
1275+
"GetHardwareWatchpointHit() slot: %u matched MASK "
1276+
"ignoring %u low bits",
1277+
i, mask);
1278+
return i;
1279+
}
1280+
}
1281+
1282+
if (is_bas_watchpoint) {
1283+
// Sanity check the bas_bits
1284+
uint32_t lsb = LowestBitSet(bas_bits);
1285+
if (lsb < 0)
1286+
continue;
1287+
1288+
uint64_t byte_to_match = bits(addr, 2, 0);
1289+
1290+
if (bas_bits & (1 << byte_to_match)) {
1291+
addr = wp_addr + lsb;
1292+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1293+
"DNBArchImplARM64::"
1294+
"GetHardwareWatchpointHit() slot: %u matched BAS",
1295+
i);
1296+
return i;
1297+
}
12041298
}
12051299
}
12061300
}

lldb/tools/debugserver/source/MacOSX/arm64/DNBArchImplARM64.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ class DNBArchMachARM64 : public DNBArchProtocol {
8686
bool write, bool also_set_on_task) override;
8787
uint32_t SetBASWatchpoint(WatchpointSpec wp, bool read, bool write,
8888
bool also_set_on_task);
89-
uint32_t SetMASKWatchpoint(WatchpointSpec wp);
89+
uint32_t SetMASKWatchpoint(WatchpointSpec wp, bool read, bool write,
90+
bool also_set_on_task);
9091
bool DisableHardwareWatchpoint(uint32_t hw_break_index,
9192
bool also_set_on_task) override;
9293
bool DisableHardwareWatchpoint_helper(uint32_t hw_break_index,

0 commit comments

Comments
 (0)