[Lldb-commits] [lldb] [lldb][Windows] ignore loader breakpoints in system modules (PR #208233)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Jul 8 07:50:00 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Charles Zablit (charles-zablit)
<details>
<summary>Changes</summary>
Currently, when debugging a program with `lldb-dap` on Windows and the `integratedTerminal` option, lldb-dap immediatly stops with an `0x80000003` Exception. This is because `ntdll` executes an `int3` breakpoint during process initialization when a debugger is attached.
This patch makes lldb and lldb-server ignore `int3` breakpoints are in system modules.
Fixes https://github.com/llvm/llvm-project/issues/198763
---
Full diff: https://github.com/llvm/llvm-project/pull/208233.diff
5 Files Affected:
- (modified) lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp (+12-43)
- (modified) lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp (+69)
- (modified) lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h (+5)
- (modified) lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp (+15-1)
- (modified) lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py (-1)
``````````diff
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index ada92894efc47..f1de697c34b67 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -50,44 +50,6 @@ using namespace llvm;
namespace lldb_private {
-namespace {
-
-void NormalizeWindowsPath(std::string &s) {
- for (char &c : s) {
- if (c == '/')
- c = '\\';
- else
- c = std::tolower(static_cast<unsigned char>(c));
- }
-}
-
-bool IsSystemDLL(const FileSpec &spec) {
- if (!spec)
- return false;
-
- static const std::string windows_prefix = []() {
- std::string prefix;
- wchar_t buf[MAX_PATH];
- UINT len = ::GetWindowsDirectoryW(buf, MAX_PATH);
- if (len == 0 || len >= MAX_PATH)
- return prefix;
- llvm::convertWideToUTF8(std::wstring_view(buf, len), prefix);
- NormalizeWindowsPath(prefix);
- if (!prefix.empty() && prefix.back() != '\\')
- prefix += '\\';
- return prefix;
- }();
-
- if (windows_prefix.empty())
- return false;
-
- std::string path = spec.GetPath();
- NormalizeWindowsPath(path);
- return llvm::StringRef(path).starts_with(windows_prefix);
-}
-
-} // namespace
-
NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info,
NativeDelegate &delegate,
llvm::Error &E)
@@ -635,9 +597,8 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
return ExceptionResult::BreakInDebugger;
}
- // Any remaining STATUS_BREAKPOINT is a breakpoint instruction in the
- // program's own code (e.g. `__debugbreak()` or `__builtin_debugtrap()`).
- // Stop the debugger and let the user decide what to do.
+ // Our own DebugBreakProcess() injection, used to implement
+ // Halt()/Interrupt().
if (m_pending_halt) {
LLDB_LOG(log,
"DebugBreakProcess injection treated as Halt SIGSTOP for tid "
@@ -664,6 +625,14 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
return ExceptionResult::BreakInDebugger;
}
+ if (IsSystemModuleAddress(exception_addr)) {
+ LLDB_LOG(log,
+ "Ignoring loader/OS breakpoint at address {0:x} in a system "
+ "module.",
+ exception_addr);
+ return ExceptionResult::MaskException;
+ }
+
std::string desc = formatv("Exception {0:x8} encountered at address {1:x8}",
record.GetExceptionValue(), exception_addr)
.str();
@@ -776,7 +745,7 @@ DllEventAction NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
return DllEventAction::ContinueDebugLoop;
// Can't resolve a breakpoint in a system DLL.
- if (!resolved || IsSystemDLL(resolved))
+ if (!resolved || ProcessDebugger::IsSystemDLL(resolved))
return DllEventAction::ContinueDebugLoop;
NativeThreadWindows *loader_thread = GetThreadByID(thread_id);
@@ -819,7 +788,7 @@ DllEventAction NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr,
if (!m_initial_stop_seen || !m_client_supports_libraries_read)
return DllEventAction::ContinueDebugLoop;
- if (!unloaded_spec || IsSystemDLL(unloaded_spec))
+ if (!unloaded_spec || ProcessDebugger::IsSystemDLL(unloaded_spec))
return DllEventAction::ContinueDebugLoop;
NativeThreadWindows *unloader_thread = GetThreadByID(thread_id);
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
index 63fc20f36b07b..a4322c3d3f536 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
@@ -19,6 +19,7 @@
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/Process.h"
+#include "lldb/Utility/FileSpec.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Error.h"
@@ -26,9 +27,77 @@
#include "ExceptionRecord.h"
#include "ProcessWindowsLog.h"
+#include <cctype>
+#include <string>
+#include <string_view>
+
using namespace lldb;
using namespace lldb_private;
+static void NormalizeWindowsPath(std::string &s) {
+ for (char &c : s) {
+ if (c == '/')
+ c = '\\';
+ else
+ c = std::tolower(static_cast<unsigned char>(c));
+ }
+}
+
+bool ProcessDebugger::IsSystemDLL(const FileSpec &spec) {
+ if (!spec)
+ return false;
+
+ static const std::string windows_prefix = []() {
+ std::string prefix;
+ wchar_t buf[MAX_PATH];
+ UINT len = ::GetWindowsDirectoryW(buf, MAX_PATH);
+ if (len == 0 || len >= MAX_PATH)
+ return prefix;
+ llvm::convertWideToUTF8(std::wstring_view(buf, len), prefix);
+ NormalizeWindowsPath(prefix);
+ if (!prefix.empty() && prefix.back() != '\\')
+ prefix += '\\';
+ return prefix;
+ }();
+
+ if (windows_prefix.empty())
+ return false;
+
+ std::string path = spec.GetPath();
+ NormalizeWindowsPath(path);
+ return llvm::StringRef(path).starts_with(windows_prefix);
+}
+
+bool ProcessDebugger::IsSystemModuleAddress(lldb::addr_t addr) {
+ if (!m_session_data || !m_session_data->m_debugger)
+ return false;
+ lldb::process_t handle = m_session_data->m_debugger->GetProcess()
+ .GetNativeProcess()
+ .GetSystemHandle();
+ if (handle == nullptr || handle == LLDB_INVALID_PROCESS)
+ return false;
+
+ MEMORY_BASIC_INFORMATION mbi = {};
+ if (::VirtualQueryEx(handle, reinterpret_cast<LPCVOID>(addr), &mbi,
+ sizeof(mbi)) != sizeof(mbi))
+ return false;
+ if (mbi.AllocationBase == nullptr)
+ return false;
+
+ // A truncated path still carries the leading directory, which is all
+ // IsSystemDLL() inspects. MAX_PATH is enough.
+ wchar_t module_path[MAX_PATH];
+ DWORD len = ::GetModuleFileNameExW(
+ handle, reinterpret_cast<HMODULE>(mbi.AllocationBase), module_path,
+ MAX_PATH);
+ if (len == 0)
+ return false;
+
+ std::string path_utf8;
+ llvm::convertWideToUTF8(std::wstring_view(module_path, len), path_utf8);
+ return IsSystemDLL(FileSpec(path_utf8));
+}
+
static DWORD ConvertLldbToWinApiProtect(uint32_t protect) {
// We also can process a read / write permissions here, but if the debugger
// will make later a write into the allocated memory, it will fail. To get
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
index 77b7dfff14268..b06f022d0fe80 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h
@@ -29,6 +29,7 @@ class HostProcess;
class HostThread;
class ProcessLaunchInfo;
class ProcessAttachInfo;
+class FileSpec;
class ProcessWindowsData {
public:
@@ -68,6 +69,10 @@ class ProcessDebugger {
uint16_t length_lower_word);
virtual void OnDebuggerError(const Status &error, uint32_t type);
+ static bool IsSystemDLL(const FileSpec &spec);
+
+ bool IsSystemModuleAddress(lldb::addr_t addr);
+
protected:
Status DetachProcess();
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 18631a1545bdc..b5793bf283c80 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -15,6 +15,7 @@
#include <psapi.h>
#include "lldb/Breakpoint/Watchpoint.h"
+#include "lldb/Core/Address.h"
#include "lldb/Core/IOHandler.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ModuleSpec.h"
@@ -97,6 +98,7 @@ ProcessSP ProcessWindows::CreateInstance(lldb::TargetSP target_sp,
}
static bool ShouldUseLLDBServer() {
+ return true;
llvm::StringRef use_lldb_server = ::getenv("LLDB_USE_LLDB_SERVER");
return use_lldb_server.equals_insensitive("on") ||
use_lldb_server.equals_insensitive("yes") ||
@@ -710,7 +712,18 @@ ProcessWindows::OnDebugException(bool first_chance,
ExceptionResult result = ExceptionResult::SendToApplication;
switch (record.GetExceptionValue()) {
- case EXCEPTION_BREAKPOINT:
+ case EXCEPTION_BREAKPOINT: {
+ const lldb::addr_t bp_addr = record.GetExceptionAddress();
+ if (first_chance && m_session_data->m_initial_stop_received &&
+ !GetBreakpointSiteList().FindByAddress(bp_addr) &&
+ IsSystemModuleAddress(bp_addr)) {
+ LLDB_LOG(
+ log,
+ "Ignoring loader/OS breakpoint at address {0:x} in a system module.",
+ bp_addr);
+ return ExceptionResult::MaskException;
+ }
+
// Handle breakpoints at the first chance.
result = ExceptionResult::BreakInDebugger;
@@ -733,6 +746,7 @@ ProcessWindows::OnDebugException(bool first_chance,
DrainProcessStdout();
SetPrivateState(eStateStopped);
break;
+ }
case EXCEPTION_SINGLE_STEP:
result = ExceptionResult::BreakInDebugger;
DrainProcessStdout();
diff --git a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
index 429051c9cf9f9..4e09efadfdcdc 100644
--- a/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
+++ b/lldb/test/API/tools/lldb-dap/runInTerminal/TestDAP_runInTerminal.py
@@ -66,7 +66,6 @@ def read_pipe_message(pipe):
@skipIfBuildType(["debug"])
- at skipIfWindows # https://github.com/llvm/llvm-project/issues/198763
class TestDAP_runInTerminal(lldbdap_testcase.DAPTestCaseBase):
SHARED_BUILD_TESTCASE = False
``````````
</details>
https://github.com/llvm/llvm-project/pull/208233
More information about the lldb-commits
mailing list