[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
Thu Mar 12 07:17:30 PDT 2026


https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/185473

>From 5669ed9626dcd3044c596b0f2a51d37e0e90ee70 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/6] [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 7259bc2aa2382..992fc1e46ad06 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 {
@@ -491,6 +536,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 f3521a23447eb88e9912643cbc1345f91feb7429 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/6] 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 992fc1e46ad06..b845744aed8fa 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 {
@@ -348,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 &&
@@ -536,11 +494,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()`.
@@ -911,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/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 74ea1bba9000da498523f20579200701af8383fa 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/6] 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 9151da4f93d383d130ae95539471fe76c905e1d5 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/6] 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.

>From e383f33640b0def1170e29f460dbaaaffd8a0605 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 12 Mar 2026 14:12:23 +0000
Subject: [PATCH 5/6] switch to an api test

---
 .../Python/lldbsuite/test/decorators.py       | 15 ++++++
 .../Windows/Common/MSVCRTCFrameRecognizer.cpp |  1 -
 .../Windows/Common/MSVCRTCFrameRecognizer.h   |  4 +-
 .../windows/msvcrt/TestMSVCRTCException.py    | 53 +++++++++++++++++++
 lldb/test/API/windows/msvcrt/main.c           |  6 +++
 5 files changed, 76 insertions(+), 3 deletions(-)
 create mode 100644 lldb/test/API/windows/msvcrt/TestMSVCRTCException.py
 create mode 100644 lldb/test/API/windows/msvcrt/main.c

diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py
index 6a516e2e4addc..e409c56ba4ec6 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -1060,6 +1060,21 @@ 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/MSVCRTCFrameRecognizer.cpp b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
index 334493d53bb5a..f5bf7cc50d2d5 100644
--- a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.cpp
@@ -8,7 +8,6 @@
 
 #include "MSVCRTCFrameRecognizer.h"
 
-#include "ProcessWindows.h"
 #include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/StackFrameRecognizer.h"
diff --git a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
index 56e3e18d33f92..51a451cb94f90 100644
--- a/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
+++ b/lldb/source/Plugins/Process/Windows/Common/MSVCRTCFrameRecognizer.h
@@ -9,13 +9,13 @@
 #ifndef LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
 #define LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
 
-#include "lldb/Target/Process.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(Process &process);
+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.
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..81b775e41b8af
--- /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);
+}
\ No newline at end of file

>From fb633673da4feebe53037a0dbcf5ec5a1a561c17 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Thu, 12 Mar 2026 14:17:13 +0000
Subject: [PATCH 6/6] fixup! switch to an api test

---
 lldb/packages/Python/lldbsuite/test/decorators.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py
index e409c56ba4ec6..2c62ba03f39bc 100644
--- a/lldb/packages/Python/lldbsuite/test/decorators.py
+++ b/lldb/packages/Python/lldbsuite/test/decorators.py
@@ -1060,6 +1060,7 @@ def is_compiler_clang():
 
     return skipTestIfFn(is_compiler_clang)(func)
 
+
 def skipUnlessMSVC(func):
     """Decorate the item to skip test unless msvc is available."""
 



More information about the lldb-commits mailing list