https://github.com/charles-zablit created https://github.com/llvm/llvm-project/pull/203301
`lldb-server.exe` reports access violations with only the bare exception code (e.g. `Exception 0xc0000005 encountered at address 0x...`), while the in-process Windows debugger produces the much more useful `Access violation reading location 0x...` / `writing` / `DEP at` form. This patch lifts the in process helper into a method of the `ExceptionRecord` class to allow both Windows plugins to use it. This fixes `exception_access_violation.cpp` with `LLDB_USE_LLDB_SERVER=1`. >From 6296a9fbc9a652b3eaa097924bf29b08ee5f61b3 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Thu, 11 Jun 2026 16:08:24 +0100 Subject: [PATCH] [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) { _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
