Skip to content

[lldb][Process] Introduce LoongArch64 hw break/watchpoint support #118770

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

Conversation

wangleiat
Copy link
Contributor

This patch adds support for setting/clearing hardware watchpoints and
breakpoints on LoongArch 64-bit hardware.

Refer to the following document for the hw break/watchpoint:
https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#control-and-status-registers-related-to-watchpoints

Fix Failed Tests:
lldb-shell :: Subprocess/clone-follow-child-wp.test
lldb-shell :: Subprocess/clone-follow-parent-wp.test
lldb-shell :: Subprocess/fork-follow-child-wp.test
lldb-shell :: Subprocess/fork-follow-parent-wp.test
lldb-shell :: Subprocess/vfork-follow-child-wp.test
lldb-shell :: Subprocess/vfork-follow-parent-wp.test
lldb-shell :: Watchpoint/ExpressionLanguage.test

Depends on: #118043

Created using spr 1.3.5-bogner

[skip ci]
Created using spr 1.3.5-bogner
@llvmbot
Copy link
Member

llvmbot commented Dec 5, 2024

@llvm/pr-subscribers-lldb

Author: wanglei (wangleiat)

Changes

This patch adds support for setting/clearing hardware watchpoints and
breakpoints on LoongArch 64-bit hardware.

Refer to the following document for the hw break/watchpoint:
https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#control-and-status-registers-related-to-watchpoints

Fix Failed Tests:
lldb-shell :: Subprocess/clone-follow-child-wp.test
lldb-shell :: Subprocess/clone-follow-parent-wp.test
lldb-shell :: Subprocess/fork-follow-child-wp.test
lldb-shell :: Subprocess/fork-follow-parent-wp.test
lldb-shell :: Subprocess/vfork-follow-child-wp.test
lldb-shell :: Subprocess/vfork-follow-parent-wp.test
lldb-shell :: Watchpoint/ExpressionLanguage.test

Depends on: #118043


Full diff: https://github.com/llvm/llvm-project/pull/118770.diff

7 Files Affected:

  • (modified) lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.cpp (+82)
  • (modified) lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.h (+8-1)
  • (modified) lldb/source/Plugins/Process/Utility/CMakeLists.txt (+1)
  • (added) lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.cpp (+170)
  • (added) lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.h (+29)
  • (modified) lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp (+2-1)
  • (modified) lldb/source/Target/Process.cpp (+2-1)
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.cpp b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.cpp
index f4d1bb297049da..12bd9e2104fae7 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.cpp
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.cpp
@@ -11,6 +11,7 @@
 #include "NativeRegisterContextLinux_loongarch64.h"
 
 #include "lldb/Host/HostInfo.h"
+#include "lldb/Host/linux/Ptrace.h"
 #include "lldb/Utility/DataBufferHeap.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/RegisterValue.h"
@@ -62,6 +63,19 @@ NativeRegisterContextLinux_loongarch64::NativeRegisterContextLinux_loongarch64(
   ::memset(&m_fpr, 0, sizeof(m_fpr));
   ::memset(&m_gpr, 0, sizeof(m_gpr));
 
+  ::memset(&m_hwp_regs, 0, sizeof(m_hwp_regs));
+  ::memset(&m_hbp_regs, 0, sizeof(m_hbp_regs));
+
+  // CTRL_PLV3_ENABLE, used to enable breakpoint/watchpoint
+  m_hw_dbg_enable_bit = 0x10;
+
+  // Refer to:
+  // https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#control-and-status-registers-related-to-watchpoints
+  // 14 is just a maximum value, query hardware for actual watchpoint count.
+  m_max_hwp_supported = 14;
+  m_max_hbp_supported = 14;
+  m_refresh_hwdebug_info = true;
+
   m_gpr_is_valid = false;
   m_fpu_is_valid = false;
 }
@@ -337,4 +351,72 @@ NativeRegisterContextLinux_loongarch64::GetExpeditedRegisters(
   return expedited_reg_nums;
 }
 
+Status NativeRegisterContextLinux_loongarch64::ReadHardwareDebugInfo() {
+  if (!m_refresh_hwdebug_info)
+    return Status();
+
+  ::pid_t tid = m_thread.GetID();
+
+  int regset = NT_LOONGARCH_HW_WATCH;
+  struct iovec ioVec;
+  struct user_watch_state dreg_state;
+  Status error;
+
+  ioVec.iov_base = &dreg_state;
+  ioVec.iov_len = sizeof(dreg_state);
+  error = NativeProcessLinux::PtraceWrapper(PTRACE_GETREGSET, tid, &regset,
+                                            &ioVec, ioVec.iov_len);
+  if (error.Fail())
+    return error;
+
+  m_max_hwp_supported = dreg_state.dbg_info & 0x3f;
+
+  regset = NT_LOONGARCH_HW_BREAK;
+  error = NativeProcessLinux::PtraceWrapper(PTRACE_GETREGSET, tid, &regset,
+                                            &ioVec, ioVec.iov_len);
+  if (error.Fail())
+    return error;
+
+  m_max_hbp_supported = dreg_state.dbg_info & 0x3f;
+
+  m_refresh_hwdebug_info = false;
+
+  return error;
+}
+
+Status NativeRegisterContextLinux_loongarch64::WriteHardwareDebugRegs(
+    DREGType hwbType) {
+  struct iovec ioVec;
+  struct user_watch_state dreg_state;
+  int regset;
+
+  memset(&dreg_state, 0, sizeof(dreg_state));
+  ioVec.iov_base = &dreg_state;
+
+  switch (hwbType) {
+  case eDREGTypeWATCH:
+    regset = NT_LOONGARCH_HW_WATCH;
+    ioVec.iov_len = sizeof(dreg_state.dbg_info) +
+                    (sizeof(dreg_state.dbg_regs[0]) * m_max_hwp_supported);
+
+    for (uint32_t i = 0; i < m_max_hwp_supported; i++) {
+      dreg_state.dbg_regs[i].addr = m_hwp_regs[i].address;
+      dreg_state.dbg_regs[i].ctrl = m_hwp_regs[i].control;
+    }
+    break;
+  case eDREGTypeBREAK:
+    regset = NT_LOONGARCH_HW_BREAK;
+    ioVec.iov_len = sizeof(dreg_state.dbg_info) +
+                    (sizeof(dreg_state.dbg_regs[0]) * m_max_hbp_supported);
+
+    for (uint32_t i = 0; i < m_max_hbp_supported; i++) {
+      dreg_state.dbg_regs[i].addr = m_hbp_regs[i].address;
+      dreg_state.dbg_regs[i].ctrl = m_hbp_regs[i].control;
+    }
+    break;
+  }
+
+  return NativeProcessLinux::PtraceWrapper(PTRACE_SETREGSET, m_thread.GetID(),
+                                           &regset, &ioVec, ioVec.iov_len);
+}
 #endif // defined(__loongarch__) && __loongarch_grlen == 64
diff --git a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.h b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.h
index 0a6084ff4206db..b00f039734bd6a 100644
--- a/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.h
+++ b/lldb/source/Plugins/Process/Linux/NativeRegisterContextLinux_loongarch64.h
@@ -12,6 +12,7 @@
 #define lldb_NativeRegisterContextLinux_loongarch64_h
 
 #include "Plugins/Process/Linux/NativeRegisterContextLinux.h"
+#include "Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.h"
 #include "Plugins/Process/Utility/RegisterInfoPOSIX_loongarch64.h"
 
 #include <asm/ptrace.h>
@@ -22,7 +23,8 @@ namespace process_linux {
 class NativeProcessLinux;
 
 class NativeRegisterContextLinux_loongarch64
-    : public NativeRegisterContextLinux {
+    : public NativeRegisterContextLinux,
+      public NativeRegisterContextDBReg_loongarch {
 public:
   NativeRegisterContextLinux_loongarch64(
       const ArchSpec &target_arch, NativeThreadProtocol &native_thread,
@@ -71,6 +73,7 @@ class NativeRegisterContextLinux_loongarch64
 private:
   bool m_gpr_is_valid;
   bool m_fpu_is_valid;
+  bool m_refresh_hwdebug_info;
 
   RegisterInfoPOSIX_loongarch64::GPR m_gpr;
 
@@ -83,6 +86,10 @@ class NativeRegisterContextLinux_loongarch64
   uint32_t CalculateFprOffset(const RegisterInfo *reg_info) const;
 
   const RegisterInfoPOSIX_loongarch64 &GetRegisterInfo() const;
+
+  Status ReadHardwareDebugInfo() override;
+
+  Status WriteHardwareDebugRegs(DREGType hwbType) override;
 };
 
 } // namespace process_linux
diff --git a/lldb/source/Plugins/Process/Utility/CMakeLists.txt b/lldb/source/Plugins/Process/Utility/CMakeLists.txt
index 0526c95503175e..0e1a5069d4409e 100644
--- a/lldb/source/Plugins/Process/Utility/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/Utility/CMakeLists.txt
@@ -11,6 +11,7 @@ add_lldb_library(lldbPluginProcessUtility
   NativeProcessSoftwareSingleStep.cpp
   NativeRegisterContextDBReg.cpp
   NativeRegisterContextDBReg_arm64.cpp
+  NativeRegisterContextDBReg_loongarch.cpp
   NativeRegisterContextDBReg_x86.cpp
   NativeRegisterContextRegisterInfo.cpp
   NetBSDSignals.cpp
diff --git a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.cpp b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.cpp
new file mode 100644
index 00000000000000..6438b02d50d1e8
--- /dev/null
+++ b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.cpp
@@ -0,0 +1,170 @@
+//===-- NativeRegisterContextDBReg_loongarch.cpp --------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "NativeRegisterContextDBReg_loongarch.h"
+
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/RegisterValue.h"
+
+using namespace lldb_private;
+
+uint32_t
+NativeRegisterContextDBReg_loongarch::SetHardwareBreakpoint(lldb::addr_t addr,
+                                                            size_t size) {
+  Log *log = GetLog(LLDBLog::Breakpoints);
+  LLDB_LOG(log, "addr: {0:x}, size: {1:x}", addr, size);
+
+  // Read hardware breakpoint and watchpoint information.
+  Status error = ReadHardwareDebugInfo();
+  if (error.Fail()) {
+    LLDB_LOG(log, "unable to set breakpoint: failed to read debug registers");
+    return LLDB_INVALID_INDEX32;
+  }
+
+  uint32_t bp_index = 0;
+
+  // Check if size has a valid hardware breakpoint length.
+  if (size != 4)
+    return LLDB_INVALID_INDEX32; // Invalid size for a LoongArch hardware
+                                 // breakpoint
+
+  // Check 4-byte alignment for hardware breakpoint target address.
+  if (addr & 0x03)
+    return LLDB_INVALID_INDEX32; // Invalid address, should be 4-byte aligned.
+
+  // Iterate over stored breakpoints and find a free bp_index
+  bp_index = LLDB_INVALID_INDEX32;
+  for (uint32_t i = 0; i < m_max_hbp_supported; i++) {
+    if (!BreakpointIsEnabled(i))
+      bp_index = i; // Mark last free slot
+    else if (m_hbp_regs[i].address == addr)
+      return LLDB_INVALID_INDEX32; // We do not support duplicate breakpoints.
+  }
+
+  if (bp_index == LLDB_INVALID_INDEX32)
+    return LLDB_INVALID_INDEX32;
+
+  // Update breakpoint in local cache
+  m_hbp_regs[bp_index].address = addr;
+  m_hbp_regs[bp_index].control = m_hw_dbg_enable_bit;
+
+  // PTRACE call to set corresponding hardware breakpoint register.
+  error = WriteHardwareDebugRegs(eDREGTypeBREAK);
+
+  if (error.Fail()) {
+    m_hbp_regs[bp_index].address = 0;
+    m_hbp_regs[bp_index].control &= ~m_hw_dbg_enable_bit;
+
+    LLDB_LOG(log, "unable to set breakpoint: failed to write debug registers");
+    return LLDB_INVALID_INDEX32;
+  }
+
+  return bp_index;
+}
+
+uint32_t
+NativeRegisterContextDBReg_loongarch::GetWatchpointSize(uint32_t wp_index) {
+  Log *log = GetLog(LLDBLog::Watchpoints);
+  LLDB_LOG(log, "wp_index: {0}", wp_index);
+
+  switch ((m_hwp_regs[wp_index].control >> 10) & 0x3) {
+  case 0x0:
+    return 8;
+  case 0x1:
+    return 4;
+  case 0x2:
+    return 2;
+  case 0x3:
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+uint32_t NativeRegisterContextDBReg_loongarch::SetHardwareWatchpoint(
+    lldb::addr_t addr, size_t size, uint32_t watch_flags) {
+  Log *log = GetLog(LLDBLog::Watchpoints);
+  LLDB_LOG(log, "addr: {0:x}, size: {1:x} watch_flags: {2:x}", addr, size,
+           watch_flags);
+
+  // Read hardware breakpoint and watchpoint information.
+  Status error = ReadHardwareDebugInfo();
+
+  if (error.Fail()) {
+    LLDB_LOG(log, "unable to set watchpoint: failed to read debug registers");
+    return LLDB_INVALID_INDEX32;
+  }
+
+  uint32_t control_value = 0, wp_index = 0;
+
+  // Check if we are setting watchpoint other than read/write/access Update
+  // watchpoint flag to match loongarch64 write-read bit configuration.
+  switch (watch_flags) {
+  case lldb::eWatchpointKindWrite:
+    watch_flags = 2;
+    break;
+  case lldb::eWatchpointKindRead:
+    watch_flags = 1;
+    break;
+  case (lldb::eWatchpointKindRead | lldb::eWatchpointKindWrite):
+    break;
+  default:
+    return LLDB_INVALID_INDEX32;
+  }
+
+  // Check if size has a valid hardware watchpoint length.
+  if (size != 1 && size != 2 && size != 4 && size != 8)
+    return LLDB_INVALID_INDEX32;
+
+  // Encode appropriate control register bits for the specified size
+  // size encoded:
+  // case 1 : 0b11
+  // case 2 : 0b10
+  // case 4 : 0b01
+  // case 8 : 0b00
+  auto EncodeSizeBits = [](int size) {
+    return (3 - llvm::Log2_32(size)) << 10;
+  };
+
+  // Setup control value
+  control_value = m_hw_dbg_enable_bit | EncodeSizeBits(size);
+  control_value |= watch_flags << 8;
+
+  // Iterate over stored watchpoints and find a free wp_index
+  wp_index = LLDB_INVALID_INDEX32;
+  for (uint32_t i = 0; i < m_max_hwp_supported; i++) {
+    if (!WatchpointIsEnabled(i)) {
+      wp_index = i; // Mark last free slot
+    } else if (m_hwp_regs[i].address == addr) {
+      return LLDB_INVALID_INDEX32; // We do not support duplicate watchpoints.
+    }
+  }
+
+  if (wp_index == LLDB_INVALID_INDEX32)
+    return LLDB_INVALID_INDEX32;
+
+  // Update watchpoint in local cache
+  // Note: `NativeRegisterContextDBReg::GetWatchpointAddress` return `real_addr`
+  m_hwp_regs[wp_index].real_addr = addr;
+  m_hwp_regs[wp_index].address = addr;
+  m_hwp_regs[wp_index].control = control_value;
+
+  // PTRACE call to set corresponding watchpoint register.
+  error = WriteHardwareDebugRegs(eDREGTypeWATCH);
+
+  if (error.Fail()) {
+    m_hwp_regs[wp_index].address = 0;
+    m_hwp_regs[wp_index].control &= ~m_hw_dbg_enable_bit;
+
+    LLDB_LOG(log, "unable to set watchpoint: failed to write debug registers");
+    return LLDB_INVALID_INDEX32;
+  }
+
+  return wp_index;
+}
diff --git a/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.h b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.h
new file mode 100644
index 00000000000000..dbf23343eb1a10
--- /dev/null
+++ b/lldb/source/Plugins/Process/Utility/NativeRegisterContextDBReg_loongarch.h
@@ -0,0 +1,29 @@
+//===-- NativeRegisterContextDBReg_loongarch.h ------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef lldb_NativeRegisterContextDBReg_loongarch_h
+#define lldb_NativeRegisterContextDBReg_loongarch_h
+
+#include "Plugins/Process/Utility/NativeRegisterContextDBReg.h"
+
+namespace lldb_private {
+
+class NativeRegisterContextDBReg_loongarch : public NativeRegisterContextDBReg {
+public:
+  uint32_t SetHardwareBreakpoint(lldb::addr_t addr, size_t size) override;
+
+  uint32_t SetHardwareWatchpoint(lldb::addr_t addr, size_t size,
+                                 uint32_t watch_flags) override;
+
+private:
+  uint32_t GetWatchpointSize(uint32_t wp_index) override;
+};
+
+} // namespace lldb_private
+
+#endif // #ifndef lldb_NativeRegisterContextDBReg_loongarch_h
diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
index 324db3db7eb4c7..c2fe05cad566ef 100644
--- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationServerCommon.cpp
@@ -231,7 +231,8 @@ GDBRemoteCommunicationServerCommon::Handle_qHostInfo(
       host_arch.GetMachine() == llvm::Triple::aarch64_32 ||
       host_arch.GetMachine() == llvm::Triple::aarch64_be ||
       host_arch.GetMachine() == llvm::Triple::arm ||
-      host_arch.GetMachine() == llvm::Triple::armeb || host_arch.IsMIPS())
+      host_arch.GetMachine() == llvm::Triple::armeb || host_arch.IsMIPS() ||
+      host_arch.GetTriple().isLoongArch())
     response.Printf("watchpoint_exceptions_received:before;");
   else
     response.Printf("watchpoint_exceptions_received:after;");
diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp
index db33525978a16a..68485a40a3fcce 100644
--- a/lldb/source/Target/Process.cpp
+++ b/lldb/source/Target/Process.cpp
@@ -2517,7 +2517,8 @@ bool Process::GetWatchpointReportedAfter() {
   llvm::Triple triple = arch.GetTriple();
 
   if (triple.isMIPS() || triple.isPPC64() || triple.isRISCV() ||
-      triple.isAArch64() || triple.isArmMClass() || triple.isARM())
+      triple.isAArch64() || triple.isArmMClass() || triple.isARM() ||
+      triple.isLoongArch())
     reported_after = false;
 
   return reported_after;

Created using spr 1.3.5-bogner

[skip ci]
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner

[skip ci]
Created using spr 1.3.5-bogner
Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small comments but otherwise fine. As usual, approval should come from the architecture expert, which I assume is @SixWeining.

Please do not land this right after #118043, wait at least a day to see if anything breaks in LLDB. Then you can land this.

Created using spr 1.3.5-bogner

[skip ci]
Created using spr 1.3.5-bogner
Created using spr 1.3.5-bogner

[skip ci]
Created using spr 1.3.5-bogner
@DavidSpickett
Copy link
Collaborator

Linux Arm and AArch64, Windows on Arm and x86 bots are fine with the refactoring PR before this. If you haven't had any complaints from the Apple bots (it's native code, so I'd hope not), then this can land.

Thanks for putting in the work on the refactoring!

@wangleiat wangleiat changed the base branch from users/wangleiat/spr/main.lldbprocess-introduce-loongarch64-hw-breakwatchpoint-support to main December 13, 2024 02:06
@wangleiat wangleiat merged commit 6c4e70f into main Dec 13, 2024
9 of 10 checks passed
@wangleiat wangleiat deleted the users/wangleiat/spr/lldbprocess-introduce-loongarch64-hw-breakwatchpoint-support branch December 13, 2024 02:06
@SixWeining SixWeining mentioned this pull request Nov 8, 2024
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants