Skip to content

Commit 396f261

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 (cherry picked from commit 2e16e41)
1 parent cc2431c commit 396f261

File tree

6 files changed

+217
-40
lines changed

6 files changed

+217
-40
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 & 32 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))
@@ -804,8 +799,6 @@ uint32_t DNBArchMachARM64::EnableHardwareBreakpoint(nub_addr_t addr,
804799
(uint64_t)m_state.dbg.__bvr[i],
805800
(uint32_t)m_state.dbg.__bcr[i]);
806801

807-
// The kernel will set the MDE_ENABLE bit in the MDSCR_EL1 for us
808-
// automatically, don't need to do it here.
809802
kret = SetDBGState(also_set_on_task);
810803

811804
DNBLogThreadedIf(LOG_WATCHPOINTS,
@@ -845,9 +838,10 @@ DNBArchMachARM64::AlignRequestedWatchpoint(nub_addr_t requested_addr,
845838

846839
/// Round up \a requested_size to the next power-of-2 size, at least 8
847840
/// bytes
848-
/// requested_size == 3 -> aligned_size == 8
849-
/// requested_size == 13 -> aligned_size == 16
850-
/// requested_size == 16 -> aligned_size == 16
841+
/// requested_size == 8 -> aligned_size == 8
842+
/// requested_size == 9 -> aligned_size == 16
843+
/// requested_size == 15 -> aligned_size == 16
844+
/// requested_size == 192 -> aligned_size == 256
851845
/// Could be `std::bit_ceil(aligned_size)` when we build with C++20?
852846
aligned_size = 1ULL << (addr_bit_size - __builtin_clzll(aligned_size - 1));
853847

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

925919
// We have multiple WatchpointSpecs
@@ -1021,9 +1015,6 @@ uint32_t DNBArchMachARM64::SetBASWatchpoint(DNBArchMachARM64::WatchpointSpec wp,
10211015
(uint64_t)m_state.dbg.__wvr[i],
10221016
(uint32_t)m_state.dbg.__wcr[i]);
10231017

1024-
// The kernel will set the MDE_ENABLE bit in the MDSCR_EL1 for us
1025-
// automatically, don't need to do it here.
1026-
10271018
kret = SetDBGState(also_set_on_task);
10281019
// DumpDBGState(m_state.dbg);
10291020

@@ -1039,6 +1030,81 @@ uint32_t DNBArchMachARM64::SetBASWatchpoint(DNBArchMachARM64::WatchpointSpec wp,
10391030
return INVALID_NUB_HW_INDEX;
10401031
}
10411032

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

10661132
DNBLogThreadedIf(LOG_WATCHPOINTS,
10671133
"DNBArchMachARM64::"
1068-
"SetBASWatchpoint( %u ) - WVR%u = "
1134+
"ReenableHardwareWatchpoint_helper( %u ) - WVR%u = "
10691135
"0x%8.8llx WCR%u = 0x%8.8llx",
10701136
hw_index, hw_index, (uint64_t)m_state.dbg.__wvr[hw_index],
10711137
hw_index, (uint64_t)m_state.dbg.__wcr[hw_index]);
10721138

1073-
// The kernel will set the MDE_ENABLE bit in the MDSCR_EL1 for us
1074-
// automatically, don't need to do it here.
1075-
10761139
kret = SetDBGState(false);
10771140

10781141
return (kret == KERN_SUCCESS);
@@ -1174,30 +1237,60 @@ uint32_t DNBArchMachARM64::GetHardwareWatchpointHit(nub_addr_t &addr) {
11741237
uint32_t i, num = NumSupportedHardwareWatchpoints();
11751238
for (i = 0; i < num; ++i) {
11761239
nub_addr_t wp_addr = GetWatchAddress(debug_state, i);
1177-
uint32_t byte_mask = bits(debug_state.__wcr[i], 12, 5);
11781240

11791241
DNBLogThreadedIf(LOG_WATCHPOINTS, "DNBArchImplX86_64::"
11801242
"GetHardwareWatchpointHit() slot: %u "
1181-
"(addr = 0x%llx; byte_mask = 0x%x)",
1182-
i, static_cast<uint64_t>(wp_addr),
1183-
byte_mask);
1243+
"(addr = 0x%llx, WCR = 0x%llx)",
1244+
i, wp_addr, debug_state.__wcr[i]);
11841245

11851246
if (!IsWatchpointEnabled(debug_state, i))
11861247
continue;
11871248

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

1191-
// Sanity check the byte_mask
1192-
uint32_t lsb = LowestBitSet(byte_mask);
1193-
if (lsb < 0)
1194-
continue;
1256+
const bool is_bas_watchpoint = mask == 0;
11951257

1196-
uint64_t byte_to_match = bits(addr, 2, 0);
1258+
DNBLogThreadedIf(
1259+
LOG_WATCHPOINTS,
1260+
"DNBArchImplARM64::"
1261+
"GetHardwareWatchpointHit() slot: %u %s",
1262+
i, is_bas_watchpoint ? "is BAS watchpoint" : "is MASK watchpoint");
11971263

1198-
if (byte_mask & (1 << byte_to_match)) {
1199-
addr = wp_addr + lsb;
1200-
return i;
1264+
if (is_bas_watchpoint) {
1265+
if (bits(wp_addr, 48, 3) != bits(addr, 48, 3))
1266+
continue;
1267+
} else {
1268+
if (bits(wp_addr, 48, mask) == bits(addr, 48, mask)) {
1269+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1270+
"DNBArchImplARM64::"
1271+
"GetHardwareWatchpointHit() slot: %u matched MASK "
1272+
"ignoring %u low bits",
1273+
i, mask);
1274+
return i;
1275+
}
1276+
}
1277+
1278+
if (is_bas_watchpoint) {
1279+
// Sanity check the bas_bits
1280+
uint32_t lsb = LowestBitSet(bas_bits);
1281+
if (lsb < 0)
1282+
continue;
1283+
1284+
uint64_t byte_to_match = bits(addr, 2, 0);
1285+
1286+
if (bas_bits & (1 << byte_to_match)) {
1287+
addr = wp_addr + lsb;
1288+
DNBLogThreadedIf(LOG_WATCHPOINTS,
1289+
"DNBArchImplARM64::"
1290+
"GetHardwareWatchpointHit() slot: %u matched BAS",
1291+
i);
1292+
return i;
1293+
}
12011294
}
12021295
}
12031296
}

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)