Skip to content

Commit 5e7b435

Browse files
charles-zablittru
authored andcommitted
[lldb][Windows] ignore loader breakpoints in system modules (llvm#208233)
Currently, when debugging a program with `lldb-dap` on Windows and using 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` skip the first `int3` after launch when it originates from a system module (the loader's debugger notification). Only that first loader breakpoint is skipped. Any later int3, including `__debugbreak()`, `__builtin_debugtrap()` in the debuggee's own code, still stops the debugger. Fixes llvm#198763 (cherry picked from commit 83530ce)
1 parent e39f33c commit 5e7b435

10 files changed

Lines changed: 283 additions & 47 deletions

File tree

lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -50,44 +50,6 @@ using namespace llvm;
5050

5151
namespace lldb_private {
5252

53-
namespace {
54-
55-
void NormalizeWindowsPath(std::string &s) {
56-
for (char &c : s) {
57-
if (c == '/')
58-
c = '\\';
59-
else
60-
c = std::tolower(static_cast<unsigned char>(c));
61-
}
62-
}
63-
64-
bool IsSystemDLL(const FileSpec &spec) {
65-
if (!spec)
66-
return false;
67-
68-
static const std::string windows_prefix = []() {
69-
std::string prefix;
70-
wchar_t buf[MAX_PATH];
71-
UINT len = ::GetWindowsDirectoryW(buf, MAX_PATH);
72-
if (len == 0 || len >= MAX_PATH)
73-
return prefix;
74-
llvm::convertWideToUTF8(std::wstring_view(buf, len), prefix);
75-
NormalizeWindowsPath(prefix);
76-
if (!prefix.empty() && prefix.back() != '\\')
77-
prefix += '\\';
78-
return prefix;
79-
}();
80-
81-
if (windows_prefix.empty())
82-
return false;
83-
84-
std::string path = spec.GetPath();
85-
NormalizeWindowsPath(path);
86-
return llvm::StringRef(path).starts_with(windows_prefix);
87-
}
88-
89-
} // namespace
90-
9153
NativeProcessWindows::NativeProcessWindows(ProcessLaunchInfo &launch_info,
9254
NativeDelegate &delegate,
9355
llvm::Error &E)
@@ -122,6 +84,8 @@ NativeProcessWindows::NativeProcessWindows(lldb::pid_t pid, int terminal_fd,
12284
if (E)
12385
return;
12486

87+
m_expecting_loader_int3 = true;
88+
12589
SetID(GetDebuggedProcessId());
12690

12791
ProcessInstanceInfo info;
@@ -635,9 +599,8 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
635599
return ExceptionResult::BreakInDebugger;
636600
}
637601

638-
// Any remaining STATUS_BREAKPOINT is a breakpoint instruction in the
639-
// program's own code (e.g. `__debugbreak()` or `__builtin_debugtrap()`).
640-
// Stop the debugger and let the user decide what to do.
602+
// Our own DebugBreakProcess() injection, used to implement
603+
// Halt()/Interrupt().
641604
if (m_pending_halt) {
642605
LLDB_LOG(log,
643606
"DebugBreakProcess injection treated as Halt SIGSTOP for tid "
@@ -664,6 +627,15 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
664627
return ExceptionResult::BreakInDebugger;
665628
}
666629

630+
if (m_expecting_loader_int3 && IsSystemModuleAddress(exception_addr)) {
631+
m_expecting_loader_int3 = false;
632+
LLDB_LOG(log,
633+
"Skipping expected loader breakpoint at address {0:x} in a "
634+
"system module.",
635+
exception_addr);
636+
return ExceptionResult::MaskException;
637+
}
638+
667639
std::string desc = formatv("Exception {0:x8} encountered at address {1:x8}",
668640
record.GetExceptionValue(), exception_addr)
669641
.str();
@@ -776,7 +748,7 @@ DllEventAction NativeProcessWindows::OnLoadDll(const ModuleSpec &module_spec,
776748
return DllEventAction::ContinueDebugLoop;
777749

778750
// Can't resolve a breakpoint in a system DLL.
779-
if (!resolved || IsSystemDLL(resolved))
751+
if (!resolved || ProcessDebugger::IsSystemDLL(resolved.GetPath()))
780752
return DllEventAction::ContinueDebugLoop;
781753

782754
NativeThreadWindows *loader_thread = GetThreadByID(thread_id);
@@ -819,7 +791,7 @@ DllEventAction NativeProcessWindows::OnUnloadDll(lldb::addr_t module_addr,
819791
if (!m_initial_stop_seen || !m_client_supports_libraries_read)
820792
return DllEventAction::ContinueDebugLoop;
821793

822-
if (!unloaded_spec || IsSystemDLL(unloaded_spec))
794+
if (!unloaded_spec || ProcessDebugger::IsSystemDLL(unloaded_spec.GetPath()))
823795
return DllEventAction::ContinueDebugLoop;
824796

825797
NativeThreadWindows *unloader_thread = GetThreadByID(thread_id);

lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ class NativeProcessWindows : public NativeProcessProtocol,
182182
/// launch / attach.
183183
bool m_initial_stop_seen = false;
184184

185+
bool m_expecting_loader_int3 = false;
186+
185187
/// Set when Halt() / Interrupt() schedules a DebugBreakProcess injection.
186188
bool m_pending_halt = false;
187189

lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,81 @@
1919
#include "lldb/Host/ProcessLaunchInfo.h"
2020
#include "lldb/Target/MemoryRegionInfo.h"
2121
#include "lldb/Target/Process.h"
22+
#include "lldb/Utility/FileSpec.h"
2223
#include "llvm/Support/ConvertUTF.h"
2324
#include "llvm/Support/Error.h"
2425

2526
#include "DebuggerThread.h"
2627
#include "ExceptionRecord.h"
2728
#include "ProcessWindowsLog.h"
2829

30+
#include <string>
31+
#include <string_view>
32+
2933
using namespace lldb;
3034
using namespace lldb_private;
3135

36+
static void NormalizeWindowsPathSeparators(std::string &s) {
37+
for (char &c : s)
38+
if (c == '/')
39+
c = '\\';
40+
}
41+
42+
bool ProcessDebugger::IsSystemDLL(llvm::StringRef path) {
43+
if (path.empty())
44+
return false;
45+
46+
static const std::string windows_prefix = []() {
47+
std::string prefix;
48+
wchar_t buf[MAX_PATH];
49+
UINT len = ::GetWindowsDirectoryW(buf, MAX_PATH);
50+
if (len == 0 || len >= MAX_PATH)
51+
return prefix;
52+
llvm::convertWideToUTF8(std::wstring_view(buf, len), prefix);
53+
NormalizeWindowsPathSeparators(prefix);
54+
if (!prefix.empty() && prefix.back() != '\\')
55+
prefix += '\\';
56+
return prefix;
57+
}();
58+
59+
if (windows_prefix.empty())
60+
return false;
61+
62+
std::string normalized = path.str();
63+
NormalizeWindowsPathSeparators(normalized);
64+
return llvm::StringRef(normalized).starts_with_insensitive(windows_prefix);
65+
}
66+
67+
bool ProcessDebugger::IsSystemModuleAddress(lldb::addr_t addr) {
68+
if (!m_session_data || !m_session_data->m_debugger)
69+
return false;
70+
lldb::process_t handle = m_session_data->m_debugger->GetProcess()
71+
.GetNativeProcess()
72+
.GetSystemHandle();
73+
if (handle == nullptr || handle == LLDB_INVALID_PROCESS)
74+
return false;
75+
76+
MEMORY_BASIC_INFORMATION mbi = {};
77+
if (::VirtualQueryEx(handle, reinterpret_cast<LPCVOID>(addr), &mbi,
78+
sizeof(mbi)) != sizeof(mbi))
79+
return false;
80+
if (mbi.AllocationBase == nullptr)
81+
return false;
82+
83+
// A truncated path still carries the leading directory, which is all
84+
// IsSystemDLL() inspects. MAX_PATH is enough.
85+
wchar_t module_path[MAX_PATH];
86+
DWORD len = ::GetModuleFileNameExW(
87+
handle, reinterpret_cast<HMODULE>(mbi.AllocationBase), module_path,
88+
MAX_PATH);
89+
if (len == 0)
90+
return false;
91+
92+
std::string path_utf8;
93+
llvm::convertWideToUTF8(std::wstring_view(module_path, len), path_utf8);
94+
return IsSystemDLL(path_utf8);
95+
}
96+
3297
static DWORD ConvertLldbToWinApiProtect(uint32_t protect) {
3398
// We also can process a read / write permissions here, but if the debugger
3499
// will make later a write into the allocated memory, it will fail. To get

lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "lldb/lldb-forward.h"
1616
#include "lldb/lldb-types.h"
1717
#include "llvm/ADT/SmallVector.h"
18+
#include "llvm/ADT/StringRef.h"
1819
#include "llvm/Support/Error.h"
1920
#include "llvm/Support/ErrorExtras.h"
2021
#include "llvm/Support/Mutex.h"
@@ -68,6 +69,10 @@ class ProcessDebugger {
6869
uint16_t length_lower_word);
6970
virtual void OnDebuggerError(const Status &error, uint32_t type);
7071

72+
static bool IsSystemDLL(llvm::StringRef path);
73+
74+
bool IsSystemModuleAddress(lldb::addr_t addr);
75+
7176
protected:
7277
Status DetachProcess();
7378

lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <psapi.h>
1616

1717
#include "lldb/Breakpoint/Watchpoint.h"
18+
#include "lldb/Core/Address.h"
1819
#include "lldb/Core/IOHandler.h"
1920
#include "lldb/Core/Module.h"
2021
#include "lldb/Core/ModuleSpec.h"
@@ -231,6 +232,9 @@ ProcessWindows::DoAttachToProcessWithID(lldb::pid_t pid,
231232
Status error = AttachProcess(pid, attach_info, delegate);
232233
if (error.Success())
233234
SetID(GetDebuggedProcessId());
235+
236+
m_expecting_loader_int3 = true;
237+
234238
return error;
235239
}
236240

@@ -297,8 +301,12 @@ Status ProcessWindows::DoDestroy() {
297301

298302
Status ProcessWindows::DoHalt(bool &caused_stop) {
299303
StateType state = GetPrivateState();
300-
if (state != eStateStopped)
301-
return HaltProcess(caused_stop);
304+
if (state != eStateStopped) {
305+
m_pending_halt = true;
306+
Status error = HaltProcess(caused_stop);
307+
if (error.Fail() || !caused_stop)
308+
m_pending_halt = false;
309+
}
302310
caused_stop = false;
303311
return Status();
304312
}
@@ -714,7 +722,22 @@ ProcessWindows::OnDebugException(bool first_chance,
714722

715723
ExceptionResult result = ExceptionResult::SendToApplication;
716724
switch (record.GetExceptionValue()) {
717-
case EXCEPTION_BREAKPOINT:
725+
case EXCEPTION_BREAKPOINT: {
726+
const lldb::addr_t bp_addr = record.GetExceptionAddress();
727+
if (m_pending_halt) {
728+
m_pending_halt = false;
729+
} else if (m_expecting_loader_int3 && first_chance &&
730+
m_session_data->m_initial_stop_received &&
731+
!GetBreakpointSiteList().FindByAddress(bp_addr) &&
732+
IsSystemModuleAddress(bp_addr)) {
733+
m_expecting_loader_int3 = false;
734+
LLDB_LOG(log,
735+
"Skipping expected loader breakpoint at address {0:x} in a "
736+
"system module.",
737+
bp_addr);
738+
return ExceptionResult::MaskException;
739+
}
740+
718741
// Handle breakpoints at the first chance.
719742
result = ExceptionResult::BreakInDebugger;
720743

@@ -737,6 +760,7 @@ ProcessWindows::OnDebugException(bool first_chance,
737760
DrainProcessStdout();
738761
SetPrivateState(eStateStopped);
739762
break;
763+
}
740764
case EXCEPTION_SINGLE_STEP:
741765
result = ExceptionResult::BreakInDebugger;
742766
DrainProcessStdout();

lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ class ProcessWindows : public Process, public ProcessDebugger {
133133
std::map<lldb::break_id_t, WatchpointInfo> m_watchpoints;
134134
std::vector<lldb::break_id_t> m_watchpoint_ids;
135135
std::shared_ptr<PTY> m_pty;
136+
bool m_pending_halt = false;
137+
bool m_expecting_loader_int3 = false;
136138
};
137139
} // namespace lldb_private
138140

lldb/test/API/attach/Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C_SOURCES := main.c
2+
3+
include Makefile.rules

0 commit comments

Comments
 (0)