[Lldb-commits] [lldb] [lldb][windows] print stop reason if MSVC's runtime check fails (PR #185473)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Wed Mar 11 10:42:56 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/185473
>From ac5a4fa7a2544233d13a31c95aaf9e1a38371892 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Mon, 9 Mar 2026 17:34:49 +0000
Subject: [PATCH 1/4] [lldb][windows] print stop reason if MSVC's runtime check
fails
---
.../Process/Windows/Common/ProcessWindows.cpp | 50 +++++++++++++++++++
lldb/test/Shell/Process/MSVCRTCException.cpp | 16 ++++++
2 files changed, 66 insertions(+)
create mode 100644 lldb/test/Shell/Process/MSVCRTCException.cpp
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index c61a41cd00444..16bbb636fad0c 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -27,6 +27,7 @@
#include "lldb/Host/windows/ConnectionConPTYWindows.h"
#include "lldb/Host/windows/HostThreadWindows.h"
#include "lldb/Symbol/ObjectFile.h"
+#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/StopInfo.h"
@@ -34,6 +35,7 @@
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
+#include "lldb/ValueObject/ValueObject.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Format.h"
@@ -79,6 +81,49 @@ std::string GetProcessExecutableName(DWORD pid) {
}
return file_name;
}
+
+std::optional<std::string> GetMSVCRTCFailureDescription(Thread &thread) {
+ const uint32_t kMaxFrames = 8;
+
+ for (uint32_t i = 0; i < kMaxFrames; ++i) {
+ StackFrameSP frame = thread.GetStackFrameAtIndex(i);
+ if (!frame)
+ break;
+
+ SymbolContext sc = frame->GetSymbolContext(eSymbolContextSymbol);
+ if (!sc.symbol)
+ continue;
+
+ const char *fn_name = frame->GetFunctionName();
+ if (!fn_name)
+ continue;
+ llvm::StringRef name(fn_name);
+
+ if (!name.contains("failwithmessage"))
+ continue;
+ VariableListSP vars = frame->GetInScopeVariableList(false);
+ if (!vars)
+ continue;
+ for (size_t j = 0; j < vars->GetSize(); ++j) {
+ VariableSP var = vars->GetVariableAtIndex(j);
+ if (!var || var->GetName() != ConstString("msg"))
+ continue;
+ ValueObjectSP val =
+ frame->GetValueObjectForFrameVariable(var, eNoDynamicValues);
+ if (!val)
+ break;
+ uint64_t ptr = val->GetValueAsUnsigned(0);
+ if (!ptr)
+ break;
+ std::string msg;
+ Status err;
+ thread.GetProcess()->ReadCStringFromMemory(ptr, msg, err);
+ if (err.Success() && !msg.empty())
+ return "Run-time check failure: " + msg;
+ }
+ }
+ return std::nullopt;
+}
} // anonymous namespace
namespace lldb_private {
@@ -488,6 +533,11 @@ void ProcessWindows::RefreshStateAfterStop() {
}
stop_thread->SetStopInfo(stop_info);
return;
+ } else if (auto rtc_desc = GetMSVCRTCFailureDescription(*stop_thread)) {
+ stop_info = StopInfo::CreateStopReasonWithException(*stop_thread,
+ rtc_desc->c_str());
+ stop_thread->SetStopInfo(stop_info);
+ return;
} else {
// The thread hit a hard-coded breakpoint like an `int 3` or
// `__debugbreak()`.
diff --git a/lldb/test/Shell/Process/MSVCRTCException.cpp b/lldb/test/Shell/Process/MSVCRTCException.cpp
new file mode 100644
index 0000000000000..3238aaa24c8d2
--- /dev/null
+++ b/lldb/test/Shell/Process/MSVCRTCException.cpp
@@ -0,0 +1,16 @@
+// clang-format off
+
+// Test that lldb prints MSVC's runtime checks exceptions as stop reasons.
+
+// REQUIRES: msvc
+
+// RUN: %msvc_cl /nologo /Od /Zi /MDd /RTC1 -o %t.exe %s
+// RUN: %lldb -f %t.exe -b -o 'r' 2>&1 | FileCheck %s
+// CHECK: thread #1, stop reason = Run-time check failure: The variable 'x' is being used without being initialized.
+
+#include <iostream>
+
+int main() {
+ int x;
+ printf("%d\n", x);
+}
\ No newline at end of file
>From 0114f035b9cdfed219d9c9813d3edcecd45a1e3f Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 11 Mar 2026 12:20:50 +0000
Subject: [PATCH 2/4] fixup! [lldb][windows] print stop reason if MSVC's
runtime check fails
---
.../Process/Windows/Common/CMakeLists.txt | 1 +
.../Windows/Common/MSVCRTCFrameRecognizer.cpp | 81 +++++++++++++++++++
.../Windows/Common/MSVCRTCFrameRecognizer.h | 41 ++++++++++
.../Process/Windows/Common/ProcessWindows.cpp | 62 +++-----------
.../Process/Windows/Common/ProcessWindows.h | 5 ++
lldb/test/Shell/Process/MSVCRTCException.cpp | 6 +-
6 files changed, 143 insertions(+), 53 deletions(-)
create mode 100644 lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
create mode 100644 lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
diff --git a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
index 9854b79fbb8d6..edb0532ea01c7 100644
--- a/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
@@ -2,6 +2,7 @@
add_lldb_library(lldbPluginProcessWindowsCommon PLUGIN
DebuggerThread.cpp
LocalDebugDelegate.cpp
+ MSVCRTCFrameRecognizer.cpp
NativeProcessWindows.cpp
NativeRegisterContextWindows.cpp
NativeRegisterContextWindows_arm.cpp
diff --git a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
new file mode 100644
index 0000000000000..674c5d7cdd9a4
--- /dev/null
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
@@ -0,0 +1,81 @@
+//===-- MSVCRTCRecognizer.cpp ---------------------------------------------===//
+//
+// 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 "MSVCRTCFrameRecognizer.h"
+
+#include "ProcessWindows.h"
+#include "lldb/Symbol/VariableList.h"
+#include "lldb/Target/Process.h"
+#include "lldb/Target/StackFrameRecognizer.h"
+#include "lldb/Target/Target.h"
+#include "lldb/Target/Thread.h"
+#include "lldb/Utility/ConstString.h"
+#include "lldb/ValueObject/ValueObject.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+namespace lldb_private {
+
+void RegisterMSVCRTCFrameRecognizer(Process &process) {
+ process.GetTarget().GetFrameRecognizerManager().AddRecognizer(
+ std::make_shared<MSVCRTCFrameRecognizer>(), ConstString(),
+ {ConstString("failwithmessage")}, Mangled::ePreferDemangled,
+ /*first_instruction_only=*/false);
+}
+
+lldb::RecognizedStackFrameSP
+MSVCRTCFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) {
+ // failwithmessage calls __debugbreak() which lands at frame 0.
+ if (frame_sp->GetFrameIndex() != 0)
+ return RecognizedStackFrameSP();
+ // Only fire on EXCEPTION_BREAKPOINT (0x80000003), not on other exceptions
+ // that might incidentally have failwithmessage somewhere in the call stack.
+ auto *pw =
+ static_cast<ProcessWindows *>(frame_sp->GetThread()->GetProcess().get());
+ auto exc_code = pw->GetActiveExceptionCode();
+ if (!exc_code || *exc_code != EXCEPTION_BREAKPOINT)
+ return RecognizedStackFrameSP();
+
+ const char *fn_name = frame_sp->GetFunctionName();
+ if (!fn_name)
+ return RecognizedStackFrameSP();
+ if (!llvm::StringRef(fn_name).contains("failwithmessage"))
+ return RecognizedStackFrameSP();
+
+ VariableListSP vars = frame_sp->GetInScopeVariableList(false);
+ if (!vars)
+ return RecognizedStackFrameSP();
+
+ for (size_t i = 0; i < vars->GetSize(); ++i) {
+ VariableSP var = vars->GetVariableAtIndex(i);
+ if (!var || var->GetName() != ConstString("msg"))
+ continue;
+
+ ValueObjectSP val =
+ frame_sp->GetValueObjectForFrameVariable(var, eNoDynamicValues);
+ if (!val)
+ break;
+
+ uint64_t ptr = val->GetValueAsUnsigned(0);
+ if (!ptr)
+ break;
+
+ std::string msg;
+ Status err;
+ frame_sp->GetThread()->GetProcess()->ReadCStringFromMemory(ptr, msg, err);
+ if (err.Success() && !msg.empty())
+ return lldb::RecognizedStackFrameSP(
+ new MSVCRTCRecognizedFrame("Run-time check failure: " + msg));
+ break;
+ }
+
+ return RecognizedStackFrameSP();
+}
+
+} // namespace lldb_private
diff --git a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
new file mode 100644
index 0000000000000..653871e8ad82f
--- /dev/null
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
@@ -0,0 +1,41 @@
+//===-- MSVCRTCRecognizer.h -----------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
+#define LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
+
+#include "lldb/Target/Process.h"
+#include "lldb/Target/StackFrameRecognizer.h"
+
+namespace lldb_private {
+
+/// Registers the MSVC run-time check failure frame recognizer with the target.
+void RegisterMSVCRTCFrameRecognizer(Process &process);
+
+/// Recognized stack frame for an MSVC _RTC failure. Carries the human-readable
+/// stop description extracted from failwithmessage's \c msg parameter.
+class MSVCRTCRecognizedFrame : public RecognizedStackFrame {
+public:
+ MSVCRTCRecognizedFrame(std::string desc) { m_stop_desc = std::move(desc); }
+};
+
+/// Recognizes the MSVC CRT's \c failwithmessage frame, extracts the
+/// run-time check failure message from the \c msg parameter, and returns it
+/// as the thread stop description.
+class MSVCRTCFrameRecognizer : public StackFrameRecognizer {
+public:
+ std::string GetName() override {
+ return "MSVC Run-Time Check Failure Recognizer";
+ }
+ lldb::RecognizedStackFrameSP
+ RecognizeFrame(lldb::StackFrameSP frame_sp) override;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
index 16bbb636fad0c..f99dd3636a4ae 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -27,7 +27,6 @@
#include "lldb/Host/windows/ConnectionConPTYWindows.h"
#include "lldb/Host/windows/HostThreadWindows.h"
#include "lldb/Symbol/ObjectFile.h"
-#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/MemoryRegionInfo.h"
#include "lldb/Target/StopInfo.h"
@@ -35,7 +34,6 @@
#include "lldb/Utility/LLDBLog.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/State.h"
-#include "lldb/ValueObject/ValueObject.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Format.h"
@@ -46,6 +44,7 @@
#include "ExceptionRecord.h"
#include "ForwardDecl.h"
#include "LocalDebugDelegate.h"
+#include "MSVCRTCFrameRecognizer.h"
#include "ProcessWindowsLog.h"
#include "TargetThreadWindows.h"
@@ -81,49 +80,6 @@ std::string GetProcessExecutableName(DWORD pid) {
}
return file_name;
}
-
-std::optional<std::string> GetMSVCRTCFailureDescription(Thread &thread) {
- const uint32_t kMaxFrames = 8;
-
- for (uint32_t i = 0; i < kMaxFrames; ++i) {
- StackFrameSP frame = thread.GetStackFrameAtIndex(i);
- if (!frame)
- break;
-
- SymbolContext sc = frame->GetSymbolContext(eSymbolContextSymbol);
- if (!sc.symbol)
- continue;
-
- const char *fn_name = frame->GetFunctionName();
- if (!fn_name)
- continue;
- llvm::StringRef name(fn_name);
-
- if (!name.contains("failwithmessage"))
- continue;
- VariableListSP vars = frame->GetInScopeVariableList(false);
- if (!vars)
- continue;
- for (size_t j = 0; j < vars->GetSize(); ++j) {
- VariableSP var = vars->GetVariableAtIndex(j);
- if (!var || var->GetName() != ConstString("msg"))
- continue;
- ValueObjectSP val =
- frame->GetValueObjectForFrameVariable(var, eNoDynamicValues);
- if (!val)
- break;
- uint64_t ptr = val->GetValueAsUnsigned(0);
- if (!ptr)
- break;
- std::string msg;
- Status err;
- thread.GetProcess()->ReadCStringFromMemory(ptr, msg, err);
- if (err.Success() && !msg.empty())
- return "Run-time check failure: " + msg;
- }
- }
- return std::nullopt;
-}
} // anonymous namespace
namespace lldb_private {
@@ -345,6 +301,8 @@ void ProcessWindows::DidLaunch() {
void ProcessWindows::DidAttach(ArchSpec &arch_spec) {
llvm::sys::ScopedLock lock(m_mutex);
+ RegisterMSVCRTCFrameRecognizer(*this);
+
// The initial stop won't broadcast the state change event, so account for
// that here.
if (m_session_data && GetPrivateState() == eStateStopped &&
@@ -533,11 +491,6 @@ void ProcessWindows::RefreshStateAfterStop() {
}
stop_thread->SetStopInfo(stop_info);
return;
- } else if (auto rtc_desc = GetMSVCRTCFailureDescription(*stop_thread)) {
- stop_info = StopInfo::CreateStopReasonWithException(*stop_thread,
- rtc_desc->c_str());
- stop_thread->SetStopInfo(stop_info);
- return;
} else {
// The thread hit a hard-coded breakpoint like an `int 3` or
// `__debugbreak()`.
@@ -908,6 +861,15 @@ std::optional<uint32_t> ProcessWindows::GetWatchpointSlotCount() {
return RegisterContextWindows::GetNumHardwareBreakpointSlots();
}
+std::optional<DWORD> ProcessWindows::GetActiveExceptionCode() const {
+ if (!m_session_data || !m_session_data->m_debugger)
+ return std::nullopt;
+ auto exc = m_session_data->m_debugger->GetActiveException().lock();
+ if (!exc)
+ return std::nullopt;
+ return exc->GetExceptionCode();
+}
+
Status ProcessWindows::EnableWatchpoint(WatchpointSP wp_sp, bool notify) {
Status error;
diff --git a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
index d3e3f9a5ed0ce..c1478dd30c4d2 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
@@ -93,6 +93,11 @@ class ProcessWindows : public Process, public ProcessDebugger {
void OnDebuggerError(const Status &error, uint32_t type) override;
std::optional<uint32_t> GetWatchpointSlotCount() override;
+
+ /// Returns the exception code of the active (current) debug exception,
+ /// or std::nullopt if there is no active exception.
+ std::optional<DWORD> GetActiveExceptionCode() const;
+
Status EnableWatchpoint(lldb::WatchpointSP wp_sp,
bool notify = true) override;
Status DisableWatchpoint(lldb::WatchpointSP wp_sp,
diff --git a/lldb/test/Shell/Process/MSVCRTCException.cpp b/lldb/test/Shell/Process/MSVCRTCException.cpp
index 3238aaa24c8d2..942288e882598 100644
--- a/lldb/test/Shell/Process/MSVCRTCException.cpp
+++ b/lldb/test/Shell/Process/MSVCRTCException.cpp
@@ -11,6 +11,6 @@
#include <iostream>
int main() {
- int x;
- printf("%d\n", x);
-}
\ No newline at end of file
+ int x;
+ printf("%d\n", x);
+}
>From 11f3be10d4394ab5438477de0f9293beed4744db Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 11 Mar 2026 17:42:14 +0000
Subject: [PATCH 3/4] fixup! [lldb][windows] print stop reason if MSVC's
runtime check fails
---
.../Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp | 2 +-
lldb/test/Shell/Process/{ => Windows}/MSVCRTCException.cpp | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename lldb/test/Shell/Process/{ => Windows}/MSVCRTCException.cpp (100%)
diff --git a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
index 674c5d7cdd9a4..7909967db9c59 100644
--- a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
@@ -22,7 +22,7 @@ using namespace lldb_private;
namespace lldb_private {
-void RegisterMSVCRTCFrameRecognizer(Process &process) {
+void RegisterMSVCRTCFrameRecognizer(ProcessWindows &process) {
process.GetTarget().GetFrameRecognizerManager().AddRecognizer(
std::make_shared<MSVCRTCFrameRecognizer>(), ConstString(),
{ConstString("failwithmessage")}, Mangled::ePreferDemangled,
diff --git a/lldb/test/Shell/Process/MSVCRTCException.cpp b/lldb/test/Shell/Process/Windows/MSVCRTCException.cpp
similarity index 100%
rename from lldb/test/Shell/Process/MSVCRTCException.cpp
rename to lldb/test/Shell/Process/Windows/MSVCRTCException.cpp
>From 691541a3e2a92846863465a17890a2fe94a6eb63 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 11 Mar 2026 17:42:42 +0000
Subject: [PATCH 4/4] fixup! [lldb][windows] print stop reason if MSVC's
runtime check fails
---
.../Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp | 2 +-
.../Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
index 7909967db9c59..334493d53bb5a 100644
--- a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
@@ -1,4 +1,4 @@
-//===-- MSVCRTCRecognizer.cpp ---------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
diff --git a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
index 653871e8ad82f..56e3e18d33f92 100644
--- a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
@@ -1,4 +1,4 @@
-//===-- MSVCRTCRecognizer.h -----------------------------------------------===//
+//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
More information about the lldb-commits
mailing list