[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