Skip to content

Commit 2bffa5b

Browse files
authored
[lldb][Windows] WoA HW Watchpoint support in LLDB (#108072)
This PR adds support for hardware watchpoints in LLDB for AArch64 Windows targets. Windows does not provide an API to query the number of available hardware watchpoints supported by underlying hardware platform. Therefore, current implementation supports only a single hardware watchpoint, which has been verified on Windows 11 using Microsoft SQ2 and Snapdragon Elite X hardware. LLDB test suite ninja check-lldb still fails watchpoint-related tests. However, tests that do not require more than a single watchpoint pass successfully when run individually.
1 parent 97b066f commit 2bffa5b

10 files changed

+102
-96
lines changed

lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,8 @@ NativeProcessWindows::GetAuxvData() const {
292292

293293
llvm::Expected<llvm::ArrayRef<uint8_t>>
294294
NativeProcessWindows::GetSoftwareBreakpointTrapOpcode(size_t size_hint) {
295-
static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x3e, 0xd4}; // brk #0xf000
295+
static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x3e,
296+
0xd4}; // brk #0xf000
296297
static const uint8_t g_thumb_opcode[] = {0xfe, 0xde}; // udf #0xfe
297298

298299
switch (GetArchitecture().GetMachine()) {
@@ -309,9 +310,9 @@ NativeProcessWindows::GetSoftwareBreakpointTrapOpcode(size_t size_hint) {
309310
}
310311

311312
size_t NativeProcessWindows::GetSoftwareBreakpointPCOffset() {
312-
// Windows always reports an incremented PC after a breakpoint is hit,
313-
// even on ARM.
314-
return cantFail(GetSoftwareBreakpointTrapOpcode(0)).size();
313+
// Windows always reports an incremented PC after a breakpoint is hit,
314+
// even on ARM.
315+
return cantFail(GetSoftwareBreakpointTrapOpcode(0)).size();
315316
}
316317

317318
bool NativeProcessWindows::FindSoftwareBreakpoint(lldb::addr_t addr) {
@@ -463,6 +464,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
463464
switch (record.GetExceptionCode()) {
464465
case DWORD(STATUS_SINGLE_STEP):
465466
case STATUS_WX86_SINGLE_STEP: {
467+
#ifndef __aarch64__
466468
uint32_t wp_id = LLDB_INVALID_INDEX32;
467469
if (NativeThreadWindows *thread = GetThreadByID(record.GetThreadID())) {
468470
NativeRegisterContextWindows &reg_ctx = thread->GetRegisterContext();
@@ -483,6 +485,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
483485
}
484486
}
485487
if (wp_id == LLDB_INVALID_INDEX32)
488+
#endif
486489
StopThread(record.GetThreadID(), StopReason::eStopReasonTrace);
487490

488491
SetState(eStateStopped, true);
@@ -492,23 +495,50 @@ NativeProcessWindows::OnDebugException(bool first_chance,
492495
}
493496
case DWORD(STATUS_BREAKPOINT):
494497
case STATUS_WX86_BREAKPOINT:
495-
if (FindSoftwareBreakpoint(record.GetExceptionAddress())) {
496-
LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
497-
record.GetExceptionAddress());
498498

499-
StopThread(record.GetThreadID(), StopReason::eStopReasonBreakpoint);
499+
if (NativeThreadWindows *stop_thread =
500+
GetThreadByID(record.GetThreadID())) {
501+
auto &reg_ctx = stop_thread->GetRegisterContext();
502+
const auto exception_addr = record.GetExceptionAddress();
503+
const auto thread_id = record.GetThreadID();
500504

501-
if (NativeThreadWindows *stop_thread =
502-
GetThreadByID(record.GetThreadID())) {
503-
auto &register_context = stop_thread->GetRegisterContext();
504-
uint32_t breakpoint_size = GetSoftwareBreakpointPCOffset();
505+
if (FindSoftwareBreakpoint(exception_addr)) {
506+
LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
507+
exception_addr);
505508
// The current PC is AFTER the BP opcode, on all architectures.
506-
uint64_t pc = register_context.GetPC() - breakpoint_size;
507-
register_context.SetPC(pc);
509+
reg_ctx.SetPC(reg_ctx.GetPC() - GetSoftwareBreakpointPCOffset());
510+
StopThread(thread_id, StopReason::eStopReasonBreakpoint);
511+
SetState(eStateStopped, true);
512+
return ExceptionResult::MaskException;
513+
} else {
514+
// This block of code will only be entered in case of a hardware
515+
// watchpoint or breakpoint hit on AArch64. However, we only handle
516+
// hardware watchpoints below as breakpoints are not yet supported.
517+
const std::vector<ULONG_PTR> &args = record.GetExceptionArguments();
518+
// Check that the ExceptionInformation array of EXCEPTION_RECORD
519+
// contains at least two elements: the first is a read-write flag
520+
// indicating the type of data access operation (read or write) while
521+
// the second contains the virtual address of the accessed data.
522+
if (args.size() >= 2) {
523+
uint32_t hw_id = LLDB_INVALID_INDEX32;
524+
Status error = reg_ctx.GetWatchpointHitIndex(hw_id, args[1]);
525+
if (error.Fail())
526+
LLDB_LOG(log,
527+
"received error while checking for watchpoint hits, pid = "
528+
"{0}, error = {1}",
529+
thread_id, error);
530+
531+
if (hw_id != LLDB_INVALID_INDEX32) {
532+
std::string desc =
533+
formatv("{0} {1} {2}", reg_ctx.GetWatchpointAddress(hw_id),
534+
hw_id, exception_addr)
535+
.str();
536+
StopThread(thread_id, StopReason::eStopReasonWatchpoint, desc);
537+
SetState(eStateStopped, true);
538+
return ExceptionResult::MaskException;
539+
}
540+
}
508541
}
509-
510-
SetState(eStateStopped, true);
511-
return ExceptionResult::MaskException;
512542
}
513543

514544
if (!initial_stop) {

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.cpp

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
using namespace lldb;
1919
using namespace lldb_private;
2020

21-
NativeRegisterContextWindows::NativeRegisterContextWindows(
22-
NativeThreadProtocol &thread, RegisterInfoInterface *reg_info_interface_p)
23-
: NativeRegisterContextRegisterInfo(thread, reg_info_interface_p) {}
24-
2521
lldb::thread_t NativeRegisterContextWindows::GetThreadHandle() const {
2622
auto wthread = static_cast<NativeThreadWindows *>(&m_thread);
2723
return wthread->GetHostThread().GetNativeThread().GetSystemHandle();

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ namespace lldb_private {
1717

1818
class NativeThreadWindows;
1919

20-
class NativeRegisterContextWindows : public NativeRegisterContextRegisterInfo {
20+
class NativeRegisterContextWindows
21+
: public virtual NativeRegisterContextRegisterInfo {
2122
public:
22-
NativeRegisterContextWindows(
23-
NativeThreadProtocol &native_thread,
24-
RegisterInfoInterface *reg_info_interface_p);
25-
2623
static std::unique_ptr<NativeRegisterContextWindows>
2724
CreateHostNativeRegisterContextWindows(const ArchSpec &target_arch,
2825
NativeThreadProtocol &native_thread);

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_WoW64.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ static Status SetWoW64ThreadContextHelper(lldb::thread_t thread_handle,
8888

8989
NativeRegisterContextWindows_WoW64::NativeRegisterContextWindows_WoW64(
9090
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
91-
: NativeRegisterContextWindows(native_thread,
92-
CreateRegisterInfoInterface(target_arch)) {}
91+
: NativeRegisterContextRegisterInfo(
92+
native_thread, CreateRegisterInfoInterface(target_arch)) {}
9393

9494
bool NativeRegisterContextWindows_WoW64::IsGPR(uint32_t reg_index) const {
9595
return (reg_index >= k_first_gpr_i386 && reg_index < k_first_alias_i386);

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,8 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
128128

129129
NativeRegisterContextWindows_arm::NativeRegisterContextWindows_arm(
130130
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
131-
: NativeRegisterContextWindows(native_thread,
132-
CreateRegisterInfoInterface(target_arch)) {}
131+
: NativeRegisterContextRegisterInfo(
132+
native_thread, CreateRegisterInfoInterface(target_arch)) {}
133133

134134
bool NativeRegisterContextWindows_arm::IsGPR(uint32_t reg_index) const {
135135
return (reg_index >= k_first_gpr_arm && reg_index <= k_last_gpr_arm);

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.cpp

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
#include "NativeRegisterContextWindows_arm64.h"
1212
#include "NativeThreadWindows.h"
13-
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
1413
#include "ProcessWindowsLog.h"
1514
#include "lldb/Host/HostInfo.h"
1615
#include "lldb/Host/HostThread.h"
@@ -143,8 +142,13 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
143142

144143
NativeRegisterContextWindows_arm64::NativeRegisterContextWindows_arm64(
145144
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
146-
: NativeRegisterContextWindows(native_thread,
147-
CreateRegisterInfoInterface(target_arch)) {}
145+
: NativeRegisterContextRegisterInfo(
146+
native_thread, CreateRegisterInfoInterface(target_arch)) {
147+
// Currently, there is no API to query the maximum supported hardware
148+
// breakpoints and watchpoints on Windows. The values set below are based
149+
// on tests conducted on Windows 11 with Snapdragon Elite X hardware.
150+
m_max_hwp_supported = 1;
151+
}
148152

149153
bool NativeRegisterContextWindows_arm64::IsGPR(uint32_t reg_index) const {
150154
return (reg_index >= k_first_gpr_arm64 && reg_index <= k_last_gpr_arm64);
@@ -709,48 +713,37 @@ Status NativeRegisterContextWindows_arm64::WriteAllRegisterValues(
709713
return SetThreadContextHelper(GetThreadHandle(), &tls_context);
710714
}
711715

712-
Status NativeRegisterContextWindows_arm64::IsWatchpointHit(uint32_t wp_index,
713-
bool &is_hit) {
714-
return Status::FromErrorString("unimplemented");
715-
}
716-
717-
Status NativeRegisterContextWindows_arm64::GetWatchpointHitIndex(
718-
uint32_t &wp_index, lldb::addr_t trap_addr) {
719-
return Status::FromErrorString("unimplemented");
720-
}
721-
722-
Status NativeRegisterContextWindows_arm64::IsWatchpointVacant(uint32_t wp_index,
723-
bool &is_vacant) {
724-
return Status::FromErrorString("unimplemented");
725-
}
726-
727-
Status NativeRegisterContextWindows_arm64::SetHardwareWatchpointWithIndex(
728-
lldb::addr_t addr, size_t size, uint32_t watch_flags, uint32_t wp_index) {
729-
return Status::FromErrorString("unimplemented");
730-
}
716+
llvm::Error NativeRegisterContextWindows_arm64::ReadHardwareDebugInfo() {
717+
::CONTEXT tls_context;
718+
Status error = GetThreadContextHelper(GetThreadHandle(), &tls_context,
719+
CONTEXT_DEBUG_REGISTERS);
720+
if (error.Fail())
721+
return error.ToError();
731722

732-
bool NativeRegisterContextWindows_arm64::ClearHardwareWatchpoint(
733-
uint32_t wp_index) {
734-
return false;
735-
}
723+
for (uint32_t i = 0; i < m_max_hwp_supported; i++) {
724+
m_hwp_regs[i].address = tls_context.Wvr[i];
725+
m_hwp_regs[i].control = tls_context.Wcr[i];
726+
}
736727

737-
Status NativeRegisterContextWindows_arm64::ClearAllHardwareWatchpoints() {
738-
return Status::FromErrorString("unimplemented");
728+
return llvm::Error::success();
739729
}
740730

741-
uint32_t NativeRegisterContextWindows_arm64::SetHardwareWatchpoint(
742-
lldb::addr_t addr, size_t size, uint32_t watch_flags) {
743-
return LLDB_INVALID_INDEX32;
744-
}
731+
llvm::Error
732+
NativeRegisterContextWindows_arm64::WriteHardwareDebugRegs(DREGType hwbType) {
733+
::CONTEXT tls_context;
734+
Status error = GetThreadContextHelper(GetThreadHandle(), &tls_context,
735+
CONTEXT_DEBUG_REGISTERS);
736+
if (error.Fail())
737+
return error.ToError();
745738

746-
lldb::addr_t
747-
NativeRegisterContextWindows_arm64::GetWatchpointAddress(uint32_t wp_index) {
748-
return LLDB_INVALID_ADDRESS;
749-
}
739+
if (hwbType == eDREGTypeWATCH) {
740+
for (uint32_t i = 0; i < m_max_hwp_supported; i++) {
741+
tls_context.Wvr[i] = m_hwp_regs[i].address;
742+
tls_context.Wcr[i] = m_hwp_regs[i].control;
743+
}
744+
}
750745

751-
uint32_t NativeRegisterContextWindows_arm64::NumSupportedHardwareWatchpoints() {
752-
// Not implemented
753-
return 0;
746+
return SetThreadContextHelper(GetThreadHandle(), &tls_context).ToError();
754747
}
755748

756749
#endif // defined(__aarch64__) || defined(_M_ARM64)

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.h

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#ifndef liblldb_NativeRegisterContextWindows_arm64_h_
1111
#define liblldb_NativeRegisterContextWindows_arm64_h_
1212

13+
#include "Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h"
14+
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
1315
#include "Plugins/Process/Utility/lldb-arm64-register-enums.h"
1416

1517
#include "NativeRegisterContextWindows.h"
@@ -18,7 +20,9 @@ namespace lldb_private {
1820

1921
class NativeThreadWindows;
2022

21-
class NativeRegisterContextWindows_arm64 : public NativeRegisterContextWindows {
23+
class NativeRegisterContextWindows_arm64
24+
: public NativeRegisterContextWindows,
25+
public NativeRegisterContextDBReg_arm64 {
2226
public:
2327
NativeRegisterContextWindows_arm64(const ArchSpec &target_arch,
2428
NativeThreadProtocol &native_thread);
@@ -37,28 +41,6 @@ class NativeRegisterContextWindows_arm64 : public NativeRegisterContextWindows {
3741

3842
Status WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override;
3943

40-
Status IsWatchpointHit(uint32_t wp_index, bool &is_hit) override;
41-
42-
Status GetWatchpointHitIndex(uint32_t &wp_index,
43-
lldb::addr_t trap_addr) override;
44-
45-
Status IsWatchpointVacant(uint32_t wp_index, bool &is_vacant) override;
46-
47-
bool ClearHardwareWatchpoint(uint32_t wp_index) override;
48-
49-
Status ClearAllHardwareWatchpoints() override;
50-
51-
Status SetHardwareWatchpointWithIndex(lldb::addr_t addr, size_t size,
52-
uint32_t watch_flags,
53-
uint32_t wp_index);
54-
55-
uint32_t SetHardwareWatchpoint(lldb::addr_t addr, size_t size,
56-
uint32_t watch_flags) override;
57-
58-
lldb::addr_t GetWatchpointAddress(uint32_t wp_index) override;
59-
60-
uint32_t NumSupportedHardwareWatchpoints() override;
61-
6244
protected:
6345
Status GPRRead(const uint32_t reg, RegisterValue &reg_value);
6446

@@ -72,6 +54,10 @@ class NativeRegisterContextWindows_arm64 : public NativeRegisterContextWindows {
7254
bool IsGPR(uint32_t reg_index) const;
7355

7456
bool IsFPR(uint32_t reg_index) const;
57+
58+
llvm::Error ReadHardwareDebugInfo() override;
59+
60+
llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override;
7561
};
7662

7763
} // namespace lldb_private

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_i386.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,8 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
9292

9393
NativeRegisterContextWindows_i386::NativeRegisterContextWindows_i386(
9494
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
95-
: NativeRegisterContextWindows(native_thread,
96-
CreateRegisterInfoInterface(target_arch)) {}
95+
: NativeRegisterContextRegisterInfo(
96+
native_thread, CreateRegisterInfoInterface(target_arch)) {}
9797

9898
bool NativeRegisterContextWindows_i386::IsGPR(uint32_t reg_index) const {
9999
return (reg_index < k_first_alias_i386);

lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_x86_64.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
110110

111111
NativeRegisterContextWindows_x86_64::NativeRegisterContextWindows_x86_64(
112112
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
113-
: NativeRegisterContextWindows(native_thread,
114-
CreateRegisterInfoInterface(target_arch)) {}
113+
: NativeRegisterContextRegisterInfo(
114+
native_thread, CreateRegisterInfoInterface(target_arch)) {}
115115

116116
bool NativeRegisterContextWindows_x86_64::IsGPR(uint32_t reg_index) const {
117117
return (reg_index >= k_first_gpr_x86_64 && reg_index < k_first_alias_x86_64);

llvm/docs/ReleaseNotes.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ Changes to LLDB
135135

136136
* When building LLDB with Python support, the minimum version of Python is now
137137
3.8.
138+
* LLDB now supports hardware watchpoints for AArch64 Windows targets. Windows
139+
does not provide API to query the number of supported hardware watchpoints.
140+
Therefore current implementation allows only 1 watchpoint, as tested with
141+
Windows 11 on the Microsoft SQ2 and Snapdragon Elite X platforms.
138142

139143
Changes to BOLT
140144
---------------------------------

0 commit comments

Comments
 (0)