[Lldb-commits] [lldb] [lldb][Windows] Append access-violation detail to lldb-server stop description (PR #203301)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Thu Jun 11 08:57:29 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/203301
>From 6296a9fbc9a652b3eaa097924bf29b08ee5f61b3 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 11 Jun 2026 16:08:24 +0100
Subject: [PATCH 1/2] [lldb][Windows] Append access-violation detail to
lldb-server stop description
---
.../Process/Windows/Common/CMakeLists.txt | 1 +
.../Windows/Common/ExceptionRecord.cpp | 113 ++++++++++++++++++
.../Process/Windows/Common/ExceptionRecord.h | 46 +++----
.../Windows/Common/NativeProcessWindows.cpp | 13 +-
.../Windows/Common/ProcessDebugger.cpp | 6 +-
.../Process/Windows/Common/ProcessWindows.cpp | 84 ++-----------
6 files changed, 150 insertions(+), 113 deletions(-)
create mode 100644 lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp
diff --git a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
index 9854b79fbb8d6..c6d8713a83159 100644
--- a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
@@ -1,6 +1,7 @@
add_lldb_library(lldbPluginProcessWindowsCommon PLUGIN
DebuggerThread.cpp
+ ExceptionRecord.cpp
LocalDebugDelegate.cpp
NativeProcessWindows.cpp
NativeRegisterContextWindows.cpp
diff --git a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp
new file mode 100644
index 0000000000000..56e35556896ad
--- /dev/null
+++ b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp
@@ -0,0 +1,113 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "ExceptionRecord.h"
+#include "lldb/Host/windows/windows.h"
+#include "lldb/lldb-forward.h"
+#include "lldb/lldb-types.h"
+
+#include <dbghelp.h>
+#include <memory>
+#include <vector>
+
+using namespace lldb_private;
+
+ExceptionRecord::ExceptionRecord(const EXCEPTION_RECORD &record,
+ lldb::tid_t thread_id) {
+ // Notes about the `record.ExceptionRecord` field:
+ // In the past, some code tried to parse the nested exception with it, but
+ // in practice, that code just causes Access Violation. I suspect
+ // `ExceptionRecord` here actually points to the address space of the
+ // debuggee process. However, I did not manage to find any official or
+ // unofficial reference that clarifies this point. If anyone would like to
+ // reimplement this, please also keep in mind to check how this behaves when
+ // debugging a WOW64 process. I suspect you may have to use the explicit
+ // `EXCEPTION_RECORD32` and `EXCEPTION_RECORD64` structs.
+ m_code = record.ExceptionCode;
+ m_continuable = (record.ExceptionFlags == 0);
+ m_exception_addr = reinterpret_cast<lldb::addr_t>(record.ExceptionAddress);
+ m_thread_id = thread_id;
+ m_arguments.assign(record.ExceptionInformation,
+ record.ExceptionInformation + record.NumberParameters);
+}
+
+lldb_private::ExceptionRecord::ExceptionRecord(const MINIDUMP_EXCEPTION &record,
+ lldb::tid_t thread_id)
+ : m_code(record.ExceptionCode), m_continuable(record.ExceptionFlags == 0),
+ m_exception_addr(static_cast<lldb::addr_t>(record.ExceptionAddress)),
+ m_thread_id(thread_id),
+ m_arguments(record.ExceptionInformation,
+ record.ExceptionInformation + record.NumberParameters) {}
+
+void lldb_private::ExceptionRecord::Dump(llvm::raw_ostream &stream) const {
+ // Decode additional exception information for specific exception types based
+ // on
+ // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_exception_record
+ // Mirrors ProcessWindows::DumpAdditionalExceptionInformation so that
+ // lldb-server stop descriptions match the in-process debugger.
+
+ const int addr_min_width = 2 + 8; // "0x" + 4 address bytes
+
+ const std::vector<ULONG_PTR> &args = GetExceptionArguments();
+ switch (GetExceptionValue()) {
+ case EXCEPTION_ACCESS_VIOLATION: {
+ if (args.size() < 2)
+ break;
+
+ stream << ": ";
+ const int access_violation_code = args[0];
+ const lldb::addr_t access_violation_address = args[1];
+ switch (access_violation_code) {
+ case 0:
+ stream << "Access violation reading";
+ break;
+ case 1:
+ stream << "Access violation writing";
+ break;
+ case 8:
+ stream << "User-mode data execution prevention (DEP) violation at";
+ break;
+ default:
+ stream << "Unknown access violation (code " << access_violation_code
+ << ") at";
+ break;
+ }
+ stream << " location "
+ << llvm::format_hex(access_violation_address, addr_min_width);
+ break;
+ }
+ case EXCEPTION_IN_PAGE_ERROR: {
+ if (args.size() < 3)
+ break;
+
+ stream << ": ";
+ const int page_load_error_code = args[0];
+ const lldb::addr_t page_load_error_address = args[1];
+ const DWORD underlying_code = args[2];
+ switch (page_load_error_code) {
+ case 0:
+ stream << "In page error reading";
+ break;
+ case 1:
+ stream << "In page error writing";
+ break;
+ case 8:
+ stream << "User-mode data execution prevention (DEP) violation at";
+ break;
+ default:
+ stream << "Unknown page loading error (code " << page_load_error_code
+ << ") at";
+ break;
+ }
+ stream << " location "
+ << llvm::format_hex(page_load_error_address, addr_min_width)
+ << " (status code " << llvm::format_hex(underlying_code, 8) << ")";
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h
index 6a55d0046796a..78777a0f1204d 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h
@@ -9,13 +9,17 @@
#ifndef liblldb_Plugins_Process_Windows_ExceptionRecord_H_
#define liblldb_Plugins_Process_Windows_ExceptionRecord_H_
-#include "lldb/Host/windows/windows.h"
#include "lldb/lldb-forward.h"
-#include <dbghelp.h>
#include <memory>
#include <vector>
+struct _EXCEPTION_RECORD;
+typedef struct _EXCEPTION_RECORD EXCEPTION_RECORD;
+
+struct _MINIDUMP_EXCEPTION;
+typedef struct _MINIDUMP_EXCEPTION MINIDUMP_EXCEPTION;
+
namespace lldb_private {
// ExceptionRecord
@@ -24,49 +28,31 @@ namespace lldb_private {
// notification of events that happen in a debugged process.
class ExceptionRecord {
public:
- ExceptionRecord(const EXCEPTION_RECORD &record, lldb::tid_t thread_id) {
- // Notes about the `record.ExceptionRecord` field:
- // In the past, some code tried to parse the nested exception with it, but
- // in practice, that code just causes Access Violation. I suspect
- // `ExceptionRecord` here actually points to the address space of the
- // debuggee process. However, I did not manage to find any official or
- // unofficial reference that clarifies this point. If anyone would like to
- // reimplement this, please also keep in mind to check how this behaves when
- // debugging a WOW64 process. I suspect you may have to use the explicit
- // `EXCEPTION_RECORD32` and `EXCEPTION_RECORD64` structs.
- m_code = record.ExceptionCode;
- m_continuable = (record.ExceptionFlags == 0);
- m_exception_addr = reinterpret_cast<lldb::addr_t>(record.ExceptionAddress);
- m_thread_id = thread_id;
- m_arguments.assign(record.ExceptionInformation,
- record.ExceptionInformation + record.NumberParameters);
- }
+ ExceptionRecord(const EXCEPTION_RECORD &record, lldb::tid_t thread_id);
// MINIDUMP_EXCEPTIONs are almost identical to EXCEPTION_RECORDs.
- ExceptionRecord(const MINIDUMP_EXCEPTION &record, lldb::tid_t thread_id)
- : m_code(record.ExceptionCode), m_continuable(record.ExceptionFlags == 0),
- m_exception_addr(static_cast<lldb::addr_t>(record.ExceptionAddress)),
- m_thread_id(thread_id),
- m_arguments(record.ExceptionInformation,
- record.ExceptionInformation + record.NumberParameters) {}
+ ExceptionRecord(const MINIDUMP_EXCEPTION &record, lldb::tid_t thread_id);
virtual ~ExceptionRecord() = default;
- DWORD
- GetExceptionCode() const { return m_code; }
+ unsigned long GetExceptionValue() const { return m_code; }
bool IsContinuable() const { return m_continuable; }
lldb::addr_t GetExceptionAddress() const { return m_exception_addr; }
lldb::tid_t GetThreadID() const { return m_thread_id; }
- const std::vector<ULONG_PTR>& GetExceptionArguments() const { return m_arguments; }
+ const std::vector<unsigned long long> &GetExceptionArguments() const {
+ return m_arguments;
+ }
+
+ void Dump(llvm::raw_ostream &stream) const;
private:
- DWORD m_code;
+ unsigned long m_code;
bool m_continuable;
lldb::addr_t m_exception_addr;
lldb::tid_t m_thread_id;
- std::vector<ULONG_PTR> m_arguments;
+ std::vector<unsigned long long> m_arguments;
};
}
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 80ecb321209ed..8599498cf2585 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "lldb/Host/windows/windows.h"
+#include <dbghelp.h>
+#include <excpt.h>
#include <psapi.h>
#include "NativeProcessWindows.h"
@@ -613,7 +615,7 @@ NativeProcessWindows::HandleBreakpointException(const ExceptionRecord &record) {
}
std::string desc = formatv("Exception {0:x8} encountered at address {1:x8}",
- record.GetExceptionCode(), exception_addr)
+ record.GetExceptionValue(), exception_addr)
.str();
StopThread(thread_id, StopReason::eStopReasonException, std::move(desc));
SetState(eStateStopped, true);
@@ -627,7 +629,7 @@ NativeProcessWindows::HandleGenericException(bool first_chance,
LLDB_LOG(log,
"Debugger thread reported exception {0:x} at address {1:x} "
"(first_chance={2})",
- record.GetExceptionCode(), record.GetExceptionAddress(),
+ record.GetExceptionValue(), record.GetExceptionAddress(),
first_chance);
if (first_chance)
@@ -635,11 +637,12 @@ NativeProcessWindows::HandleGenericException(bool first_chance,
std::string desc;
llvm::raw_string_ostream desc_stream(desc);
- desc_stream << "Exception " << llvm::format_hex(record.GetExceptionCode(), 8)
+ desc_stream << "Exception " << llvm::format_hex(record.GetExceptionValue(), 8)
<< " encountered at address "
<< llvm::format_hex(record.GetExceptionAddress(), 8);
+ record.Dump(desc_stream);
StopThread(record.GetThreadID(), StopReason::eStopReasonException,
- desc.c_str());
+ desc_stream.str());
SetState(eStateStopped, true);
return ExceptionResult::BreakInDebugger;
@@ -653,7 +656,7 @@ NativeProcessWindows::OnDebugException(bool first_chance,
// Let the debugger establish the internal status.
ProcessDebugger::OnDebugException(first_chance, record);
- switch (record.GetExceptionCode()) {
+ switch (record.GetExceptionValue()) {
case DWORD(STATUS_SINGLE_STEP):
case STATUS_WX86_SINGLE_STEP:
return HandleSingleStepException(record);
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
index ada2096df66f3..5a2c3bdc87d47 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessDebugger.cpp
@@ -511,13 +511,13 @@ ProcessDebugger::OnDebugException(bool first_chance,
LLDB_LOG(log,
"Debugger thread reported exception {0:x} at address {1:x}, but "
"there is no session.",
- record.GetExceptionCode(), record.GetExceptionAddress());
+ record.GetExceptionValue(), record.GetExceptionAddress());
return ExceptionResult::SendToApplication;
}
ExceptionResult result = ExceptionResult::SendToApplication;
- if ((record.GetExceptionCode() == EXCEPTION_BREAKPOINT ||
- record.GetExceptionCode() ==
+ if ((record.GetExceptionValue() == EXCEPTION_BREAKPOINT ||
+ record.GetExceptionValue() ==
0x4000001FL /*WOW64 STATUS_WX86_BREAKPOINT*/) &&
!m_session_data->m_initial_stop_received) {
// Handle breakpoints at the first chance.
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 30a1610ee5a17..cd91dd23cb298 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -10,6 +10,8 @@
// Windows includes
#include "lldb/Host/windows/windows.h"
+#include <dbghelp.h>
+#include <excpt.h>
#include <psapi.h>
#include "lldb/Breakpoint/Watchpoint.h"
@@ -312,74 +314,6 @@ void ProcessWindows::DidAttach(ArchSpec &arch_spec) {
RefreshStateAfterStop();
}
-static void
-DumpAdditionalExceptionInformation(llvm::raw_ostream &stream,
- const ExceptionRecordSP &exception) {
- // Decode additional exception information for specific exception types based
- // on
- // https://docs.microsoft.com/en-us/windows/desktop/api/winnt/ns-winnt-_exception_record
-
- const int addr_min_width = 2 + 8; // "0x" + 4 address bytes
-
- const std::vector<ULONG_PTR> &args = exception->GetExceptionArguments();
- switch (exception->GetExceptionCode()) {
- case EXCEPTION_ACCESS_VIOLATION: {
- if (args.size() < 2)
- break;
-
- stream << ": ";
- const int access_violation_code = args[0];
- const lldb::addr_t access_violation_address = args[1];
- switch (access_violation_code) {
- case 0:
- stream << "Access violation reading";
- break;
- case 1:
- stream << "Access violation writing";
- break;
- case 8:
- stream << "User-mode data execution prevention (DEP) violation at";
- break;
- default:
- stream << "Unknown access violation (code " << access_violation_code
- << ") at";
- break;
- }
- stream << " location "
- << llvm::format_hex(access_violation_address, addr_min_width);
- break;
- }
- case EXCEPTION_IN_PAGE_ERROR: {
- if (args.size() < 3)
- break;
-
- stream << ": ";
- const int page_load_error_code = args[0];
- const lldb::addr_t page_load_error_address = args[1];
- const DWORD underlying_code = args[2];
- switch (page_load_error_code) {
- case 0:
- stream << "In page error reading";
- break;
- case 1:
- stream << "In page error writing";
- break;
- case 8:
- stream << "User-mode data execution prevention (DEP) violation at";
- break;
- default:
- stream << "Unknown page loading error (code " << page_load_error_code
- << ") at";
- break;
- }
- stream << " location "
- << llvm::format_hex(page_load_error_address, addr_min_width)
- << " (status code " << llvm::format_hex(underlying_code, 8) << ")";
- break;
- }
- }
-}
-
void ProcessWindows::RefreshStateAfterStop() {
Log *log = GetLog(WindowsLog::Exception);
llvm::sys::ScopedLock lock(m_mutex);
@@ -417,7 +351,7 @@ void ProcessWindows::RefreshStateAfterStop() {
if (site && IsBreakpointSitePhysicallyEnabled(*site))
stop_thread->SetThreadStoppedAtUnexecutedBP(pc);
- switch (active_exception->GetExceptionCode()) {
+ switch (active_exception->GetExceptionValue()) {
case EXCEPTION_SINGLE_STEP: {
auto *reg_ctx = static_cast<RegisterContextWindows *>(
stop_thread->GetRegisterContext().get());
@@ -509,10 +443,10 @@ void ProcessWindows::RefreshStateAfterStop() {
std::string desc;
llvm::raw_string_ostream desc_stream(desc);
desc_stream << "Exception "
- << llvm::format_hex(active_exception->GetExceptionCode(), 8)
+ << llvm::format_hex(active_exception->GetExceptionValue(), 8)
<< " encountered at address "
<< llvm::format_hex(active_exception->GetExceptionAddress(), 8);
- DumpAdditionalExceptionInformation(desc_stream, active_exception);
+ active_exception->Dump(desc_stream);
stop_info =
StopInfo::CreateStopReasonWithException(*stop_thread, desc.c_str());
@@ -763,7 +697,7 @@ ProcessWindows::OnDebugException(bool first_chance,
LLDB_LOG(log,
"Debugger thread reported exception {0:x} at address {1:x}, "
"but there is no session.",
- record.GetExceptionCode(), record.GetExceptionAddress());
+ record.GetExceptionValue(), record.GetExceptionAddress());
return ExceptionResult::SendToApplication;
}
@@ -775,7 +709,7 @@ ProcessWindows::OnDebugException(bool first_chance,
}
ExceptionResult result = ExceptionResult::SendToApplication;
- switch (record.GetExceptionCode()) {
+ switch (record.GetExceptionValue()) {
case EXCEPTION_BREAKPOINT:
// Handle breakpoints at the first chance.
result = ExceptionResult::BreakInDebugger;
@@ -808,7 +742,7 @@ ProcessWindows::OnDebugException(bool first_chance,
LLDB_LOG(log,
"Debugger thread reported exception {0:x} at address {1:x} "
"(first_chance={2})",
- record.GetExceptionCode(), record.GetExceptionAddress(),
+ record.GetExceptionValue(), record.GetExceptionAddress(),
first_chance);
// For non-breakpoints, give the application a chance to handle the
// exception first.
@@ -991,7 +925,7 @@ std::optional<DWORD> ProcessWindows::GetActiveExceptionCode() const {
auto exc = m_session_data->m_debugger->GetActiveException().lock();
if (!exc)
return std::nullopt;
- return exc->GetExceptionCode();
+ return exc->GetExceptionValue();
}
Status ProcessWindows::EnableWatchpoint(WatchpointSP wp_sp, bool notify) {
>From d3ce0361249f301acfd242eff223a1824e0c9156 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 11 Jun 2026 16:57:00 +0100
Subject: [PATCH 2/2] fixup! [lldb][Windows] Append access-violation detail to
lldb-server stop description
---
.../source/Plugins/Process/Windows/Common/ExceptionRecord.cpp | 4 ++--
lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h | 3 ++-
.../Plugins/Process/Windows/Common/NativeProcessWindows.cpp | 2 +-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp
index 56e35556896ad..b24233c9320d8 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.cpp
@@ -53,7 +53,7 @@ void lldb_private::ExceptionRecord::Dump(llvm::raw_ostream &stream) const {
const int addr_min_width = 2 + 8; // "0x" + 4 address bytes
- const std::vector<ULONG_PTR> &args = GetExceptionArguments();
+ const llvm::ArrayRef<unsigned long long> args = GetExceptionArguments();
switch (GetExceptionValue()) {
case EXCEPTION_ACCESS_VIOLATION: {
if (args.size() < 2)
@@ -110,4 +110,4 @@ void lldb_private::ExceptionRecord::Dump(llvm::raw_ostream &stream) const {
break;
}
}
-}
\ No newline at end of file
+}
diff --git a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h
index 78777a0f1204d..c03d751f0a7c5 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ExceptionRecord.h
@@ -10,6 +10,7 @@
#define liblldb_Plugins_Process_Windows_ExceptionRecord_H_
#include "lldb/lldb-forward.h"
+#include "lldb/lldb-types.h"
#include <memory>
#include <vector>
@@ -41,7 +42,7 @@ class ExceptionRecord {
lldb::tid_t GetThreadID() const { return m_thread_id; }
- const std::vector<unsigned long long> &GetExceptionArguments() const {
+ llvm::ArrayRef<unsigned long long> GetExceptionArguments() const {
return m_arguments;
}
diff --git a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
index 8599498cf2585..73d2c480a7f92 100644
--- a/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/NativeProcessWindows.cpp
@@ -642,7 +642,7 @@ NativeProcessWindows::HandleGenericException(bool first_chance,
<< llvm::format_hex(record.GetExceptionAddress(), 8);
record.Dump(desc_stream);
StopThread(record.GetThreadID(), StopReason::eStopReasonException,
- desc_stream.str());
+ std::move(desc));
SetState(eStateStopped, true);
return ExceptionResult::BreakInDebugger;
More information about the lldb-commits
mailing list