[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.
This commit is contained in:
Omair Javaid 2025-01-31 14:11:39 +05:00 committed by GitHub
parent 97b066f4e9
commit 2bffa5bf7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 104 additions and 98 deletions

View File

@ -292,7 +292,8 @@ NativeProcessWindows::GetAuxvData() const {
llvm::Expected<llvm::ArrayRef<uint8_t>>
NativeProcessWindows::GetSoftwareBreakpointTrapOpcode(size_t size_hint) {
static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x3e, 0xd4}; // brk #0xf000
static const uint8_t g_aarch64_opcode[] = {0x00, 0x00, 0x3e,
0xd4}; // brk #0xf000
static const uint8_t g_thumb_opcode[] = {0xfe, 0xde}; // udf #0xfe
switch (GetArchitecture().GetMachine()) {
@ -309,9 +310,9 @@ NativeProcessWindows::GetSoftwareBreakpointTrapOpcode(size_t size_hint) {
}
size_t NativeProcessWindows::GetSoftwareBreakpointPCOffset() {
// Windows always reports an incremented PC after a breakpoint is hit,
// even on ARM.
return cantFail(GetSoftwareBreakpointTrapOpcode(0)).size();
// Windows always reports an incremented PC after a breakpoint is hit,
// even on ARM.
return cantFail(GetSoftwareBreakpointTrapOpcode(0)).size();
}
bool NativeProcessWindows::FindSoftwareBreakpoint(lldb::addr_t addr) {
@ -463,6 +464,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
switch (record.GetExceptionCode()) {
case DWORD(STATUS_SINGLE_STEP):
case STATUS_WX86_SINGLE_STEP: {
#ifndef __aarch64__
uint32_t wp_id = LLDB_INVALID_INDEX32;
if (NativeThreadWindows *thread = GetThreadByID(record.GetThreadID())) {
NativeRegisterContextWindows &reg_ctx = thread->GetRegisterContext();
@ -483,6 +485,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
}
}
if (wp_id == LLDB_INVALID_INDEX32)
#endif
StopThread(record.GetThreadID(), StopReason::eStopReasonTrace);
SetState(eStateStopped, true);
@ -492,23 +495,50 @@ NativeProcessWindows::OnDebugException(bool first_chance,
}
case DWORD(STATUS_BREAKPOINT):
case STATUS_WX86_BREAKPOINT:
if (FindSoftwareBreakpoint(record.GetExceptionAddress())) {
LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
record.GetExceptionAddress());
StopThread(record.GetThreadID(), StopReason::eStopReasonBreakpoint);
if (NativeThreadWindows *stop_thread =
GetThreadByID(record.GetThreadID())) {
auto &reg_ctx = stop_thread->GetRegisterContext();
const auto exception_addr = record.GetExceptionAddress();
const auto thread_id = record.GetThreadID();
if (NativeThreadWindows *stop_thread =
GetThreadByID(record.GetThreadID())) {
auto &register_context = stop_thread->GetRegisterContext();
uint32_t breakpoint_size = GetSoftwareBreakpointPCOffset();
if (FindSoftwareBreakpoint(exception_addr)) {
LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
exception_addr);
// The current PC is AFTER the BP opcode, on all architectures.
uint64_t pc = register_context.GetPC() - breakpoint_size;
register_context.SetPC(pc);
}
reg_ctx.SetPC(reg_ctx.GetPC() - GetSoftwareBreakpointPCOffset());
StopThread(thread_id, StopReason::eStopReasonBreakpoint);
SetState(eStateStopped, true);
return ExceptionResult::MaskException;
} else {
// This block of code will only be entered in case of a hardware
// watchpoint or breakpoint hit on AArch64. However, we only handle
// hardware watchpoints below as breakpoints are not yet supported.
const std::vector<ULONG_PTR> &args = record.GetExceptionArguments();
// Check that the ExceptionInformation array of EXCEPTION_RECORD
// contains at least two elements: the first is a read-write flag
// indicating the type of data access operation (read or write) while
// the second contains the virtual address of the accessed data.
if (args.size() >= 2) {
uint32_t hw_id = LLDB_INVALID_INDEX32;
Status error = reg_ctx.GetWatchpointHitIndex(hw_id, args[1]);
if (error.Fail())
LLDB_LOG(log,
"received error while checking for watchpoint hits, pid = "
"{0}, error = {1}",
thread_id, error);
SetState(eStateStopped, true);
return ExceptionResult::MaskException;
if (hw_id != LLDB_INVALID_INDEX32) {
std::string desc =
formatv("{0} {1} {2}", reg_ctx.GetWatchpointAddress(hw_id),
hw_id, exception_addr)
.str();
StopThread(thread_id, StopReason::eStopReasonWatchpoint, desc);
SetState(eStateStopped, true);
return ExceptionResult::MaskException;
}
}
}
}
if (!initial_stop) {

View File

@ -18,10 +18,6 @@
using namespace lldb;
using namespace lldb_private;
NativeRegisterContextWindows::NativeRegisterContextWindows(
NativeThreadProtocol &thread, RegisterInfoInterface *reg_info_interface_p)
: NativeRegisterContextRegisterInfo(thread, reg_info_interface_p) {}
lldb::thread_t NativeRegisterContextWindows::GetThreadHandle() const {
auto wthread = static_cast<NativeThreadWindows *>(&m_thread);
return wthread->GetHostThread().GetNativeThread().GetSystemHandle();

View File

@ -17,12 +17,9 @@ namespace lldb_private {
class NativeThreadWindows;
class NativeRegisterContextWindows : public NativeRegisterContextRegisterInfo {
class NativeRegisterContextWindows
: public virtual NativeRegisterContextRegisterInfo {
public:
NativeRegisterContextWindows(
NativeThreadProtocol &native_thread,
RegisterInfoInterface *reg_info_interface_p);
static std::unique_ptr<NativeRegisterContextWindows>
CreateHostNativeRegisterContextWindows(const ArchSpec &target_arch,
NativeThreadProtocol &native_thread);

View File

@ -88,8 +88,8 @@ static Status SetWoW64ThreadContextHelper(lldb::thread_t thread_handle,
NativeRegisterContextWindows_WoW64::NativeRegisterContextWindows_WoW64(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
: NativeRegisterContextWindows(native_thread,
CreateRegisterInfoInterface(target_arch)) {}
: NativeRegisterContextRegisterInfo(
native_thread, CreateRegisterInfoInterface(target_arch)) {}
bool NativeRegisterContextWindows_WoW64::IsGPR(uint32_t reg_index) const {
return (reg_index >= k_first_gpr_i386 && reg_index < k_first_alias_i386);

View File

@ -128,8 +128,8 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
NativeRegisterContextWindows_arm::NativeRegisterContextWindows_arm(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
: NativeRegisterContextWindows(native_thread,
CreateRegisterInfoInterface(target_arch)) {}
: NativeRegisterContextRegisterInfo(
native_thread, CreateRegisterInfoInterface(target_arch)) {}
bool NativeRegisterContextWindows_arm::IsGPR(uint32_t reg_index) const {
return (reg_index >= k_first_gpr_arm && reg_index <= k_last_gpr_arm);

View File

@ -10,7 +10,6 @@
#include "NativeRegisterContextWindows_arm64.h"
#include "NativeThreadWindows.h"
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
#include "ProcessWindowsLog.h"
#include "lldb/Host/HostInfo.h"
#include "lldb/Host/HostThread.h"
@ -143,8 +142,13 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
NativeRegisterContextWindows_arm64::NativeRegisterContextWindows_arm64(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
: NativeRegisterContextWindows(native_thread,
CreateRegisterInfoInterface(target_arch)) {}
: NativeRegisterContextRegisterInfo(
native_thread, CreateRegisterInfoInterface(target_arch)) {
// Currently, there is no API to query the maximum supported hardware
// breakpoints and watchpoints on Windows. The values set below are based
// on tests conducted on Windows 11 with Snapdragon Elite X hardware.
m_max_hwp_supported = 1;
}
bool NativeRegisterContextWindows_arm64::IsGPR(uint32_t reg_index) const {
return (reg_index >= k_first_gpr_arm64 && reg_index <= k_last_gpr_arm64);
@ -709,48 +713,37 @@ Status NativeRegisterContextWindows_arm64::WriteAllRegisterValues(
return SetThreadContextHelper(GetThreadHandle(), &tls_context);
}
Status NativeRegisterContextWindows_arm64::IsWatchpointHit(uint32_t wp_index,
bool &is_hit) {
return Status::FromErrorString("unimplemented");
llvm::Error NativeRegisterContextWindows_arm64::ReadHardwareDebugInfo() {
::CONTEXT tls_context;
Status error = GetThreadContextHelper(GetThreadHandle(), &tls_context,
CONTEXT_DEBUG_REGISTERS);
if (error.Fail())
return error.ToError();
for (uint32_t i = 0; i < m_max_hwp_supported; i++) {
m_hwp_regs[i].address = tls_context.Wvr[i];
m_hwp_regs[i].control = tls_context.Wcr[i];
}
return llvm::Error::success();
}
Status NativeRegisterContextWindows_arm64::GetWatchpointHitIndex(
uint32_t &wp_index, lldb::addr_t trap_addr) {
return Status::FromErrorString("unimplemented");
}
llvm::Error
NativeRegisterContextWindows_arm64::WriteHardwareDebugRegs(DREGType hwbType) {
::CONTEXT tls_context;
Status error = GetThreadContextHelper(GetThreadHandle(), &tls_context,
CONTEXT_DEBUG_REGISTERS);
if (error.Fail())
return error.ToError();
Status NativeRegisterContextWindows_arm64::IsWatchpointVacant(uint32_t wp_index,
bool &is_vacant) {
return Status::FromErrorString("unimplemented");
}
if (hwbType == eDREGTypeWATCH) {
for (uint32_t i = 0; i < m_max_hwp_supported; i++) {
tls_context.Wvr[i] = m_hwp_regs[i].address;
tls_context.Wcr[i] = m_hwp_regs[i].control;
}
}
Status NativeRegisterContextWindows_arm64::SetHardwareWatchpointWithIndex(
lldb::addr_t addr, size_t size, uint32_t watch_flags, uint32_t wp_index) {
return Status::FromErrorString("unimplemented");
}
bool NativeRegisterContextWindows_arm64::ClearHardwareWatchpoint(
uint32_t wp_index) {
return false;
}
Status NativeRegisterContextWindows_arm64::ClearAllHardwareWatchpoints() {
return Status::FromErrorString("unimplemented");
}
uint32_t NativeRegisterContextWindows_arm64::SetHardwareWatchpoint(
lldb::addr_t addr, size_t size, uint32_t watch_flags) {
return LLDB_INVALID_INDEX32;
}
lldb::addr_t
NativeRegisterContextWindows_arm64::GetWatchpointAddress(uint32_t wp_index) {
return LLDB_INVALID_ADDRESS;
}
uint32_t NativeRegisterContextWindows_arm64::NumSupportedHardwareWatchpoints() {
// Not implemented
return 0;
return SetThreadContextHelper(GetThreadHandle(), &tls_context).ToError();
}
#endif // defined(__aarch64__) || defined(_M_ARM64)

View File

@ -10,6 +10,8 @@
#ifndef liblldb_NativeRegisterContextWindows_arm64_h_
#define liblldb_NativeRegisterContextWindows_arm64_h_
#include "Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h"
#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
#include "Plugins/Process/Utility/lldb-arm64-register-enums.h"
#include "NativeRegisterContextWindows.h"
@ -18,7 +20,9 @@ namespace lldb_private {
class NativeThreadWindows;
class NativeRegisterContextWindows_arm64 : public NativeRegisterContextWindows {
class NativeRegisterContextWindows_arm64
: public NativeRegisterContextWindows,
public NativeRegisterContextDBReg_arm64 {
public:
NativeRegisterContextWindows_arm64(const ArchSpec &target_arch,
NativeThreadProtocol &native_thread);
@ -37,28 +41,6 @@ public:
Status WriteAllRegisterValues(const lldb::DataBufferSP &data_sp) override;
Status IsWatchpointHit(uint32_t wp_index, bool &is_hit) override;
Status GetWatchpointHitIndex(uint32_t &wp_index,
lldb::addr_t trap_addr) override;
Status IsWatchpointVacant(uint32_t wp_index, bool &is_vacant) override;
bool ClearHardwareWatchpoint(uint32_t wp_index) override;
Status ClearAllHardwareWatchpoints() override;
Status SetHardwareWatchpointWithIndex(lldb::addr_t addr, size_t size,
uint32_t watch_flags,
uint32_t wp_index);
uint32_t SetHardwareWatchpoint(lldb::addr_t addr, size_t size,
uint32_t watch_flags) override;
lldb::addr_t GetWatchpointAddress(uint32_t wp_index) override;
uint32_t NumSupportedHardwareWatchpoints() override;
protected:
Status GPRRead(const uint32_t reg, RegisterValue &reg_value);
@ -72,6 +54,10 @@ private:
bool IsGPR(uint32_t reg_index) const;
bool IsFPR(uint32_t reg_index) const;
llvm::Error ReadHardwareDebugInfo() override;
llvm::Error WriteHardwareDebugRegs(DREGType hwbType) override;
};
} // namespace lldb_private

View File

@ -92,8 +92,8 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
NativeRegisterContextWindows_i386::NativeRegisterContextWindows_i386(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
: NativeRegisterContextWindows(native_thread,
CreateRegisterInfoInterface(target_arch)) {}
: NativeRegisterContextRegisterInfo(
native_thread, CreateRegisterInfoInterface(target_arch)) {}
bool NativeRegisterContextWindows_i386::IsGPR(uint32_t reg_index) const {
return (reg_index < k_first_alias_i386);

View File

@ -110,8 +110,8 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
NativeRegisterContextWindows_x86_64::NativeRegisterContextWindows_x86_64(
const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
: NativeRegisterContextWindows(native_thread,
CreateRegisterInfoInterface(target_arch)) {}
: NativeRegisterContextRegisterInfo(
native_thread, CreateRegisterInfoInterface(target_arch)) {}
bool NativeRegisterContextWindows_x86_64::IsGPR(uint32_t reg_index) const {
return (reg_index >= k_first_gpr_x86_64 && reg_index < k_first_alias_x86_64);

View File

@ -135,6 +135,10 @@ Changes to LLDB
* When building LLDB with Python support, the minimum version of Python is now
3.8.
* LLDB now supports hardware watchpoints for AArch64 Windows targets. Windows
does not provide API to query the number of supported hardware watchpoints.
Therefore current implementation allows only 1 watchpoint, as tested with
Windows 11 on the Microsoft SQ2 and Snapdragon Elite X platforms.
Changes to BOLT
---------------------------------