Skip to content

[lldb][Windows] WoA HW Watchpoint support in LLDB #108072

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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();
Expand All @@ -483,6 +485,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
}
}
if (wp_id == LLDB_INVALID_INDEX32)
#endif
StopThread(record.GetThreadID(), StopReason::eStopReasonTrace);

SetState(eStateStopped, true);
Expand All @@ -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);

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

SetState(eStateStopped, true);
return ExceptionResult::MaskException;
}

if (!initial_stop) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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");
}

Status NativeRegisterContextWindows_arm64::GetWatchpointHitIndex(
uint32_t &wp_index, lldb::addr_t trap_addr) {
return Status::FromErrorString("unimplemented");
}

Status NativeRegisterContextWindows_arm64::IsWatchpointVacant(uint32_t wp_index,
bool &is_vacant) {
return Status::FromErrorString("unimplemented");
}

Status NativeRegisterContextWindows_arm64::SetHardwareWatchpointWithIndex(
lldb::addr_t addr, size_t size, uint32_t watch_flags, uint32_t wp_index) {
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();

bool NativeRegisterContextWindows_arm64::ClearHardwareWatchpoint(
uint32_t wp_index) {
return false;
}
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];
}

Status NativeRegisterContextWindows_arm64::ClearAllHardwareWatchpoints() {
return Status::FromErrorString("unimplemented");
return llvm::Error::success();
}

uint32_t NativeRegisterContextWindows_arm64::SetHardwareWatchpoint(
lldb::addr_t addr, size_t size, uint32_t watch_flags) {
return LLDB_INVALID_INDEX32;
}
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();

lldb::addr_t
NativeRegisterContextWindows_arm64::GetWatchpointAddress(uint32_t wp_index) {
return LLDB_INVALID_ADDRESS;
}
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;
}
}

uint32_t NativeRegisterContextWindows_arm64::NumSupportedHardwareWatchpoints() {
// Not implemented
return 0;
return SetThreadContextHelper(GetThreadHandle(), &tls_context).ToError();
}

#endif // defined(__aarch64__) || defined(_M_ARM64)
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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);
Expand All @@ -37,28 +41,6 @@ class NativeRegisterContextWindows_arm64 : public NativeRegisterContextWindows {

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

Expand All @@ -72,6 +54,10 @@ class NativeRegisterContextWindows_arm64 : public NativeRegisterContextWindows {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 4 additions & 0 deletions llvm/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------------------
Expand Down