[Lldb-commits] [lldb] 681f1a5 - [lldb][windows] print stop reason if MSVC's runtime check fails (#185473)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Mar 13 08:36:57 PDT 2026
Author: Charles Zablit
Date: 2026-03-13T15:36:51Z
New Revision: 681f1a5ee987bfeac95696f8cdec001b0e59ae96
URL: https://github.com/llvm/llvm-project/commit/681f1a5ee987bfeac95696f8cdec001b0e59ae96
DIFF: https://github.com/llvm/llvm-project/commit/681f1a5ee987bfeac95696f8cdec001b0e59ae96.diff
LOG: [lldb][windows] print stop reason if MSVC's runtime check fails (#185473)
This patch extracts the `msg` value of the `failwithmessage` error and
uses it as the stop reason if the MSVC Runtime fails while debugging.
# Before
```
lldb.exe C:\Users\charleszablit\Developer\testing\uninit.exe -b -o 'r'
(lldb) target create "C:\\Users\\charleszablit\\Developer\\testing\\uninit.exe"
Current executable set to 'C:\Users\charleszablit\Developer\testing\uninit.exe' (x86_64).
(lldb) r
Process 9400 launched: 'C:\Users\charleszablit\Developer\testing\uninit.exe' (x86_64)
Process 9400 stopped
* thread #1, stop reason = Exception 0x80000003 encountered at address 0x7ff96516c96a
frame #0: 0x00007ff77efe20ba uninit.exe`failwithmessage(retaddr=0x00007ff77efe150f, crttype=1, errnum=3, msg="The variable 'x' is being used without being initialized.") at error.cpp:210
```
# After
```
lldb.exe C:\Users\charleszablit\Developer\testing\uninit.exe -b -o 'r'
(lldb) target create "C:\\Users\\charleszablit\\Developer\\testing\\uninit.exe"
Current executable set to 'C:\Users\charleszablit\Developer\testing\uninit.exe' (x86_64).
(lldb) r
Process 9400 launched: 'C:\Users\charleszablit\Developer\testing\uninit.exe' (x86_64)
Process 9400 stopped
* thread #1, stop reason = Run-time check failure: The variable 'x' is being used without being initialized.
frame #0: 0x00007ff77efe20ba uninit.exe`failwithmessage(retaddr=0x00007ff77efe150f, crttype=1, errnum=3, msg="The variable 'x' is being used without being initialized.") at error.cpp:210
```
fix https://github.com/llvm/llvm-project/issues/184990.
rdar://172103284
Added:
lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
lldb/test/API/windows/msvcrt/TestMSVCRTCException.py
lldb/test/API/windows/msvcrt/main.c
Modified:
lldb/packages/Python/lldbsuite/test/decorators.py
lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt
lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h
Removed:
################################################################################
diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py
index 6a516e2e4addc..2c62ba03f39bc 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -1061,6 +1061,22 @@ def is_compiler_clang():
return skipTestIfFn(is_compiler_clang)(func)
+def skipUnlessMSVC(func):
+ """Decorate the item to skip test unless msvc is available."""
+
+ def is_msvc_in_path():
+ result = subprocess.run(
+ ["cl.exe"],
+ capture_output=True,
+ text=True,
+ )
+ if result.returncode != 0:
+ return f"Test requires MSVC to be in the Path."
+ return None
+
+ return skipTestIfFn(is_msvc_in_path)(func)
+
+
def skipUnlessThreadSanitizer(func):
"""Decorate the item to skip test unless Clang -fsanitize=thread is supported."""
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..f5bf7cc50d2d5
--- /dev/null
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "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(ProcessWindows &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..51a451cb94f90
--- /dev/null
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "ProcessWindows.h"
+#include "lldb/Target/StackFrameRecognizer.h"
+
+namespace lldb_private {
+
+/// Registers the MSVC run-time check failure frame recognizer with the target.
+void RegisterMSVCRTCFrameRecognizer(ProcessWindows &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 7259bc2aa2382..b845744aed8fa 100644
--- a/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp
@@ -44,6 +44,7 @@
#include "ExceptionRecord.h"
#include "ForwardDecl.h"
#include "LocalDebugDelegate.h"
+#include "MSVCRTCFrameRecognizer.h"
#include "ProcessWindowsLog.h"
#include "TargetThreadWindows.h"
@@ -303,6 +304,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 &&
@@ -861,6 +864,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/API/windows/msvcrt/TestMSVCRTCException.py b/lldb/test/API/windows/msvcrt/TestMSVCRTCException.py
new file mode 100644
index 0000000000000..63727c37b1ae3
--- /dev/null
+++ b/lldb/test/API/windows/msvcrt/TestMSVCRTCException.py
@@ -0,0 +1,53 @@
+import lldb
+import subprocess
+import os
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class TestMSVCRTCException(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ @skipUnlessPlatform(["windows"])
+ @skipUnlessMSVC
+ def test_msvc_runtime_checks(self):
+ """Test that lldb prints MSVC's runtime checks exceptions as stop reasons."""
+
+ src = os.path.join(self.getSourceDir(), "main.c")
+ exe = os.path.join(self.getBuildDir(), "a.exe")
+
+ result = subprocess.run(
+ ["cl.exe", "/nologo", "/Od", "/Zi", "/MDd", "/RTC1", "/Fe" + exe, src],
+ cwd=self.getBuildDir(),
+ capture_output=True,
+ text=True,
+ )
+ self.assertEqual(
+ result.returncode,
+ 0,
+ "Compilation failed:\n" + result.stdout + result.stderr,
+ )
+
+ target = self.dbg.CreateTarget(exe)
+ self.assertTrue(target.IsValid(), "Could not create target")
+
+ process = target.LaunchSimple(None, None, self.get_process_working_directory())
+ self.assertTrue(process.IsValid(), "Could not launch process")
+
+ self.assertEqual(process.GetState(), lldb.eStateStopped)
+
+ thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonException)
+ self.assertIsNotNone(thread, "No thread stopped with exception stop reason")
+
+ stop_description = thread.GetStopDescription(256)
+ self.assertIn(
+ "Run-time check failure",
+ stop_description,
+ "Stop reason does not mention run-time check failure",
+ )
+ self.assertIn(
+ "variable 'x' is being used without being initialized",
+ stop_description,
+ "Stop reason does not mention uninitialized variable 'x'",
+ )
diff --git a/lldb/test/API/windows/msvcrt/main.c b/lldb/test/API/windows/msvcrt/main.c
new file mode 100644
index 0000000000000..1c5205f5df333
--- /dev/null
+++ b/lldb/test/API/windows/msvcrt/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main() {
+ int x;
+ printf("%d\n", x);
+}
More information about the lldb-commits
mailing list