[Lldb-commits] [lldb] WoA HW Break and Watchpoint support in LLDB (PR #108072)

Omair Javaid via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 10 12:16:55 PDT 2024


https://github.com/omjavaid created https://github.com/llvm/llvm-project/pull/108072

This pull request adds support for hardware breakpoints and watchpoints in LLDB on Windows on ARM.

### Known Issues:

1. **Number of Supported Hardware Breakpoints/Watchpoints:** Windows does not provide the exact number of supported hardware breakpoints or watchpoints. The current implementation guesses this number based on testing how many can be successfully applied on the platform. winnt.h defines ARM64_MAX_WATCHPOINTS = 2 and ARM64_MAX_BREAKPOINTS = 8 however actual number differs.

2. **Initial Stop Behavior:** Hardware breakpoints or watchpoints set on the initial stop do not trigger, even though they are correctly written to the Windows context. They only trigger if set after the main program has started. This issue causes the test suite to fail when it attempts to set hardware breakpoints on the main function before running, as these do not get triggered.

### Testing:

1. Tested setting 1 hardware watchpoint and 6 hardware breakpoints on snapdragon elite x hardware.
2. The LLDB test suite currently fails related tests due to the initial stop behavior issue described above.

>From 1c29d30f0b9eb47da63c35f775ad69e1ef40c4de Mon Sep 17 00:00:00 2001
From: Muhammad Omair Javaid <omair.javaid at linaro.org>
Date: Tue, 10 Sep 2024 18:03:29 +0500
Subject: [PATCH] WoA hardware breakpoint/watchpoint support

---
 .../Windows/Common/NativeProcessWindows.cpp   | 52 ++++++++---
 .../Common/NativeRegisterContextWindows.cpp   |  3 -
 .../Common/NativeRegisterContextWindows.h     |  5 +-
 .../NativeRegisterContextWindows_WoW64.cpp    |  2 +-
 .../NativeRegisterContextWindows_arm.cpp      |  2 +-
 .../NativeRegisterContextWindows_arm64.cpp    | 86 ++++++++++---------
 .../NativeRegisterContextWindows_arm64.h      | 31 ++-----
 .../NativeRegisterContextWindows_i386.cpp     |  2 +-
 .../NativeRegisterContextWindows_x86_64.cpp   |  2 +-
 .../Windows/Common/NativeThreadWindows.cpp    | 32 +++++++
 10 files changed, 129 insertions(+), 88 deletions(-)

diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 24c9aa6b32659d..886df987dc84b6 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -491,24 +491,47 @@ NativeProcessWindows::OnDebugException(bool first_chance,
     return ExceptionResult::MaskException;
   }
   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 &register_context = stop_thread->GetRegisterContext();
+  case STATUS_WX86_BREAKPOINT: {
+    bool breakpoint_hit = false;
+    NativeThreadWindows *stop_thread = GetThreadByID(record.GetThreadID());
+
+    if (stop_thread) {
+      uint32_t hw_id = LLDB_INVALID_INDEX32;
+      auto &reg_ctx = stop_thread->GetRegisterContext();
+      reg_ctx.GetHardwareBreakHitIndex(hw_id, record.GetExceptionAddress());
+      if (hw_id != LLDB_INVALID_INDEX32) {
+        breakpoint_hit = true;
+        LLDB_LOG(log, "Hit hardware breakpoint at address {0:x}.",
+                 record.GetExceptionAddress());
+      } else if (FindSoftwareBreakpoint(record.GetExceptionAddress())) {
+        breakpoint_hit = true;
+        LLDB_LOG(log, "Hit non-loader breakpoint at address {0:x}.",
+                 record.GetExceptionAddress());
         uint32_t breakpoint_size = GetSoftwareBreakpointPCOffset();
         // The current PC is AFTER the BP opcode, on all architectures.
-        uint64_t pc = register_context.GetPC() - breakpoint_size;
-        register_context.SetPC(pc);
+        uint64_t pc = reg_ctx.GetPC() - breakpoint_size;
+        reg_ctx.SetPC(pc);
       }
 
-      SetState(eStateStopped, true);
-      return ExceptionResult::MaskException;
+      if (breakpoint_hit) {
+        StopThread(record.GetThreadID(), StopReason::eStopReasonBreakpoint);
+        SetState(eStateStopped, true);
+        return ExceptionResult::MaskException;
+      } else {
+        const std::vector<ULONG_PTR> &args = record.GetExceptionArguments();
+        if (args.size() >= 2) {
+          reg_ctx.GetWatchpointHitIndex(hw_id, args[1]);
+          if (hw_id != LLDB_INVALID_INDEX32) {
+            addr_t wp_pc = record.GetExceptionAddress();
+            std::string desc =
+                formatv("{0} {1} {2}", args[1], hw_id, wp_pc).str();
+            StopThread(record.GetThreadID(), StopReason::eStopReasonWatchpoint,
+                       desc);
+            SetState(eStateStopped, true);
+            return ExceptionResult::MaskException;
+          }
+        }
+      } 
     }
 
     if (!initial_stop) {
@@ -531,6 +554,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
       // Hit the initial stop. Continue the application.
       return ExceptionResult::BreakInDebugger;
     }
+  }
 
     [[fallthrough]];
   default:
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.cpp
index 9128363eaa577a..95be1183abb759 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.cpp
@@ -18,9 +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);
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.h b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.h
index 841b8547f3e904..66ced71d27d69a 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows.h
@@ -17,11 +17,8 @@ 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,
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_WoW64.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_WoW64.cpp
index a9642d1c5e48da..1ba89fbf3215bb 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_WoW64.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_WoW64.cpp
@@ -88,7 +88,7 @@ static Status SetWoW64ThreadContextHelper(lldb::thread_t thread_handle,
 
 NativeRegisterContextWindows_WoW64::NativeRegisterContextWindows_WoW64(
     const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
-    : NativeRegisterContextWindows(native_thread,
+    : NativeRegisterContextRegisterInfo(native_thread,
                                    CreateRegisterInfoInterface(target_arch)) {}
 
 bool NativeRegisterContextWindows_WoW64::IsGPR(uint32_t reg_index) const {
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm.cpp
index 4209fdf3c7109a..470da1afc8222e 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm.cpp
@@ -128,7 +128,7 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
 
 NativeRegisterContextWindows_arm::NativeRegisterContextWindows_arm(
     const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
-    : NativeRegisterContextWindows(native_thread,
+    : NativeRegisterContextRegisterInfo(native_thread,
                                    CreateRegisterInfoInterface(target_arch)) {}
 
 bool NativeRegisterContextWindows_arm::IsGPR(uint32_t reg_index) const {
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.cpp
index 080a9140e36a64..ae09ebffa5508c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.cpp
@@ -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,14 @@ 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;
+  m_max_hbp_supported = 6;
+}
 
 bool NativeRegisterContextWindows_arm64::IsGPR(uint32_t reg_index) const {
   return (reg_index >= k_first_gpr_arm64 && reg_index <= k_last_gpr_arm64);
@@ -709,48 +714,49 @@ 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");
-}
-
-bool NativeRegisterContextWindows_arm64::ClearHardwareWatchpoint(
-    uint32_t wp_index) {
-  return false;
-}
+llvm::Error NativeRegisterContextWindows_arm64::ReadHardwareDebugInfo() {
+  ::CONTEXT tls_context;
+  Status error =
+      GetThreadContextHelper(GetThreadHandle(), &tls_context, CONTEXT_DEBUG_REGISTERS);
+  if (error.Fail())
+    return error.ToError();
 
-Status NativeRegisterContextWindows_arm64::ClearAllHardwareWatchpoints() {
-  return Status::FromErrorString("unimplemented");
-}
+  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];
+  }
 
-uint32_t NativeRegisterContextWindows_arm64::SetHardwareWatchpoint(
-    lldb::addr_t addr, size_t size, uint32_t watch_flags) {
-  return LLDB_INVALID_INDEX32;
+  for (uint32_t i = 0; i < m_max_hbp_supported; i++) {
+    m_hbp_regs[i].address = tls_context.Bvr[i];
+    m_hbp_regs[i].control = tls_context.Bcr[i];
+  }
+  return llvm::Error::success();
 }
 
-lldb::addr_t
-NativeRegisterContextWindows_arm64::GetWatchpointAddress(uint32_t wp_index) {
-  return LLDB_INVALID_ADDRESS;
-}
+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();
+
+  switch (hwbType) {
+  case 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;
+    }
+    break;
+  case eDREGTypeBREAK:
+    for (uint32_t i = 0; i < m_max_hbp_supported; i++) {
+      tls_context.Bvr[i] = m_hbp_regs[i].address;
+      tls_context.Bcr[i] = m_hbp_regs[i].control;
+    }
+    break;
+  }
 
-uint32_t NativeRegisterContextWindows_arm64::NumSupportedHardwareWatchpoints() {
-  // Not implemented
-  return 0;
+  return SetThreadContextHelper(GetThreadHandle(), &tls_context).ToError();
 }
 
 #endif // defined(__aarch64__) || defined(_M_ARM64)
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.h b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.h
index 88afc1e7b18a2f..164d46143df950 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.h
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_arm64.h
@@ -11,6 +11,8 @@
 #define liblldb_NativeRegisterContextWindows_arm64_h_
 
 #include "Plugins/Process/Utility/lldb-arm64-register-enums.h"
+#include "Plugins/Process/Utility/NativeRegisterContextDBReg_arm64.h"
+#include "Plugins/Process/Utility/RegisterInfoPOSIX_arm64.h"
 
 #include "NativeRegisterContextWindows.h"
 
@@ -18,7 +20,8 @@ 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 +40,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);
 
@@ -72,6 +53,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
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_i386.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_i386.cpp
index 53df9866793946..71e3616d1af9c9 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_i386.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_i386.cpp
@@ -92,7 +92,7 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
 
 NativeRegisterContextWindows_i386::NativeRegisterContextWindows_i386(
     const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
-    : NativeRegisterContextWindows(native_thread,
+    : NativeRegisterContextRegisterInfo(native_thread,
                                    CreateRegisterInfoInterface(target_arch)) {}
 
 bool NativeRegisterContextWindows_i386::IsGPR(uint32_t reg_index) const {
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_x86_64.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_x86_64.cpp
index 4c59273b845aaa..3709390d953d19 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_x86_64.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeRegisterContextWindows_x86_64.cpp
@@ -110,7 +110,7 @@ NativeRegisterContextWindows::CreateHostNativeRegisterContextWindows(
 
 NativeRegisterContextWindows_x86_64::NativeRegisterContextWindows_x86_64(
     const ArchSpec &target_arch, NativeThreadProtocol &native_thread)
-    : NativeRegisterContextWindows(native_thread,
+    : NativeRegisterContextRegisterInfo(native_thread,
                                    CreateRegisterInfoInterface(target_arch)) {}
 
 bool NativeRegisterContextWindows_x86_64::IsGPR(uint32_t reg_index) const {
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
index 8ad59cd1ece887..cd5cd4541082fb 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeThreadWindows.cpp
@@ -178,9 +178,41 @@ Status NativeThreadWindows::RemoveWatchpoint(lldb::addr_t addr) {
 
 Status NativeThreadWindows::SetHardwareBreakpoint(lldb::addr_t addr,
                                                   size_t size) {
+#if defined(__aarch64__) || defined(_M_ARM64)
+  if (m_state == eStateLaunching)
+    return Status();
+
+  Status error = RemoveHardwareBreakpoint(addr);
+  if (error.Fail())
+    return error;
+
+  uint32_t bp_index = m_reg_context_up->SetHardwareBreakpoint(addr, size);
+
+  if (bp_index == LLDB_INVALID_INDEX32)
+    return Status::FromErrorString("Setting hardware breakpoint failed.");
+
+  m_hw_breakpoint_index_map.insert({addr, bp_index});
+  
+  return Status();
+#else
   return Status::FromErrorString("unimplemented.");
+#endif
 }
 
 Status NativeThreadWindows::RemoveHardwareBreakpoint(lldb::addr_t addr) {
+#if defined(__aarch64__) || defined(_M_ARM64)
+  auto bp = m_hw_breakpoint_index_map.find(addr);
+  if (bp == m_hw_breakpoint_index_map.end())
+    return Status();
+
+  uint32_t bp_index = bp->second;
+  if (m_reg_context_up->ClearHardwareBreakpoint(bp_index)) {
+    m_hw_breakpoint_index_map.erase(bp);
+    return Status();
+  }
+
+  return Status::FromErrorString("Clearing hardware breakpoint failed.");
+#else
   return Status::FromErrorString("unimplemented.");
+#endif
 }



More information about the lldb-commits mailing list