Skip to content

Commit 681f1a5

Browse files
[lldb][windows] print stop reason if MSVC's runtime check fails (llvm#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 llvm#184990. rdar://172103284
1 parent da03148 commit 681f1a5

8 files changed

Lines changed: 214 additions & 0 deletions

File tree

lldb/packages/Python/lldbsuite/test/decorators.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,6 +1061,22 @@ def is_compiler_clang():
10611061
return skipTestIfFn(is_compiler_clang)(func)
10621062

10631063

1064+
def skipUnlessMSVC(func):
1065+
"""Decorate the item to skip test unless msvc is available."""
1066+
1067+
def is_msvc_in_path():
1068+
result = subprocess.run(
1069+
["cl.exe"],
1070+
capture_output=True,
1071+
text=True,
1072+
)
1073+
if result.returncode != 0:
1074+
return f"Test requires MSVC to be in the Path."
1075+
return None
1076+
1077+
return skipTestIfFn(is_msvc_in_path)(func)
1078+
1079+
10641080
def skipUnlessThreadSanitizer(func):
10651081
"""Decorate the item to skip test unless Clang -fsanitize=thread is supported."""
10661082

lldb/source/Plugins/Process/Windows/Common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
add_lldb_library(lldbPluginProcessWindowsCommon PLUGIN
33
DebuggerThread.cpp
44
LocalDebugDelegate.cpp
5+
MSVCRTCFrameRecognizer.cpp
56
NativeProcessWindows.cpp
67
NativeRegisterContextWindows.cpp
78
NativeRegisterContextWindows_arm.cpp
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "MSVCRTCFrameRecognizer.h"
10+
11+
#include "lldb/Symbol/VariableList.h"
12+
#include "lldb/Target/Process.h"
13+
#include "lldb/Target/StackFrameRecognizer.h"
14+
#include "lldb/Target/Target.h"
15+
#include "lldb/Target/Thread.h"
16+
#include "lldb/Utility/ConstString.h"
17+
#include "lldb/ValueObject/ValueObject.h"
18+
19+
using namespace lldb;
20+
using namespace lldb_private;
21+
22+
namespace lldb_private {
23+
24+
void RegisterMSVCRTCFrameRecognizer(ProcessWindows &process) {
25+
process.GetTarget().GetFrameRecognizerManager().AddRecognizer(
26+
std::make_shared<MSVCRTCFrameRecognizer>(), ConstString(),
27+
{ConstString("failwithmessage")}, Mangled::ePreferDemangled,
28+
/*first_instruction_only=*/false);
29+
}
30+
31+
lldb::RecognizedStackFrameSP
32+
MSVCRTCFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) {
33+
// failwithmessage calls __debugbreak() which lands at frame 0.
34+
if (frame_sp->GetFrameIndex() != 0)
35+
return RecognizedStackFrameSP();
36+
// Only fire on EXCEPTION_BREAKPOINT (0x80000003), not on other exceptions
37+
// that might incidentally have failwithmessage somewhere in the call stack.
38+
auto *pw =
39+
static_cast<ProcessWindows *>(frame_sp->GetThread()->GetProcess().get());
40+
auto exc_code = pw->GetActiveExceptionCode();
41+
if (!exc_code || *exc_code != EXCEPTION_BREAKPOINT)
42+
return RecognizedStackFrameSP();
43+
44+
const char *fn_name = frame_sp->GetFunctionName();
45+
if (!fn_name)
46+
return RecognizedStackFrameSP();
47+
if (!llvm::StringRef(fn_name).contains("failwithmessage"))
48+
return RecognizedStackFrameSP();
49+
50+
VariableListSP vars = frame_sp->GetInScopeVariableList(false);
51+
if (!vars)
52+
return RecognizedStackFrameSP();
53+
54+
for (size_t i = 0; i < vars->GetSize(); ++i) {
55+
VariableSP var = vars->GetVariableAtIndex(i);
56+
if (!var || var->GetName() != ConstString("msg"))
57+
continue;
58+
59+
ValueObjectSP val =
60+
frame_sp->GetValueObjectForFrameVariable(var, eNoDynamicValues);
61+
if (!val)
62+
break;
63+
64+
uint64_t ptr = val->GetValueAsUnsigned(0);
65+
if (!ptr)
66+
break;
67+
68+
std::string msg;
69+
Status err;
70+
frame_sp->GetThread()->GetProcess()->ReadCStringFromMemory(ptr, msg, err);
71+
if (err.Success() && !msg.empty())
72+
return lldb::RecognizedStackFrameSP(
73+
new MSVCRTCRecognizedFrame("Run-time check failure: " + msg));
74+
break;
75+
}
76+
77+
return RecognizedStackFrameSP();
78+
}
79+
80+
} // namespace lldb_private
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
10+
#define LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H
11+
12+
#include "ProcessWindows.h"
13+
#include "lldb/Target/StackFrameRecognizer.h"
14+
15+
namespace lldb_private {
16+
17+
/// Registers the MSVC run-time check failure frame recognizer with the target.
18+
void RegisterMSVCRTCFrameRecognizer(ProcessWindows &process);
19+
20+
/// Recognized stack frame for an MSVC _RTC failure. Carries the human-readable
21+
/// stop description extracted from failwithmessage's \c msg parameter.
22+
class MSVCRTCRecognizedFrame : public RecognizedStackFrame {
23+
public:
24+
MSVCRTCRecognizedFrame(std::string desc) { m_stop_desc = std::move(desc); }
25+
};
26+
27+
/// Recognizes the MSVC CRT's \c failwithmessage frame, extracts the
28+
/// run-time check failure message from the \c msg parameter, and returns it
29+
/// as the thread stop description.
30+
class MSVCRTCFrameRecognizer : public StackFrameRecognizer {
31+
public:
32+
std::string GetName() override {
33+
return "MSVC Run-Time Check Failure Recognizer";
34+
}
35+
lldb::RecognizedStackFrameSP
36+
RecognizeFrame(lldb::StackFrameSP frame_sp) override;
37+
};
38+
39+
} // namespace lldb_private
40+
41+
#endif // LLDB_PLUGINS_PROCESS_WINDOWS_MSVCRTCFRAMERECOGNIZER_H

lldb/source/Plugins/Process/Windows/Common/ProcessWindows.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "ExceptionRecord.h"
4545
#include "ForwardDecl.h"
4646
#include "LocalDebugDelegate.h"
47+
#include "MSVCRTCFrameRecognizer.h"
4748
#include "ProcessWindowsLog.h"
4849
#include "TargetThreadWindows.h"
4950

@@ -303,6 +304,8 @@ void ProcessWindows::DidLaunch() {
303304
void ProcessWindows::DidAttach(ArchSpec &arch_spec) {
304305
llvm::sys::ScopedLock lock(m_mutex);
305306

307+
RegisterMSVCRTCFrameRecognizer(*this);
308+
306309
// The initial stop won't broadcast the state change event, so account for
307310
// that here.
308311
if (m_session_data && GetPrivateState() == eStateStopped &&
@@ -861,6 +864,15 @@ std::optional<uint32_t> ProcessWindows::GetWatchpointSlotCount() {
861864
return RegisterContextWindows::GetNumHardwareBreakpointSlots();
862865
}
863866

867+
std::optional<DWORD> ProcessWindows::GetActiveExceptionCode() const {
868+
if (!m_session_data || !m_session_data->m_debugger)
869+
return std::nullopt;
870+
auto exc = m_session_data->m_debugger->GetActiveException().lock();
871+
if (!exc)
872+
return std::nullopt;
873+
return exc->GetExceptionCode();
874+
}
875+
864876
Status ProcessWindows::EnableWatchpoint(WatchpointSP wp_sp, bool notify) {
865877
Status error;
866878

lldb/source/Plugins/Process/Windows/Common/ProcessWindows.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ class ProcessWindows : public Process, public ProcessDebugger {
9393
void OnDebuggerError(const Status &error, uint32_t type) override;
9494

9595
std::optional<uint32_t> GetWatchpointSlotCount() override;
96+
97+
/// Returns the exception code of the active (current) debug exception,
98+
/// or std::nullopt if there is no active exception.
99+
std::optional<DWORD> GetActiveExceptionCode() const;
100+
96101
Status EnableWatchpoint(lldb::WatchpointSP wp_sp,
97102
bool notify = true) override;
98103
Status DisableWatchpoint(lldb::WatchpointSP wp_sp,
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import lldb
2+
import subprocess
3+
import os
4+
from lldbsuite.test.decorators import *
5+
from lldbsuite.test.lldbtest import *
6+
from lldbsuite.test import lldbutil
7+
8+
9+
class TestMSVCRTCException(TestBase):
10+
NO_DEBUG_INFO_TESTCASE = True
11+
12+
@skipUnlessPlatform(["windows"])
13+
@skipUnlessMSVC
14+
def test_msvc_runtime_checks(self):
15+
"""Test that lldb prints MSVC's runtime checks exceptions as stop reasons."""
16+
17+
src = os.path.join(self.getSourceDir(), "main.c")
18+
exe = os.path.join(self.getBuildDir(), "a.exe")
19+
20+
result = subprocess.run(
21+
["cl.exe", "/nologo", "/Od", "/Zi", "/MDd", "/RTC1", "/Fe" + exe, src],
22+
cwd=self.getBuildDir(),
23+
capture_output=True,
24+
text=True,
25+
)
26+
self.assertEqual(
27+
result.returncode,
28+
0,
29+
"Compilation failed:\n" + result.stdout + result.stderr,
30+
)
31+
32+
target = self.dbg.CreateTarget(exe)
33+
self.assertTrue(target.IsValid(), "Could not create target")
34+
35+
process = target.LaunchSimple(None, None, self.get_process_working_directory())
36+
self.assertTrue(process.IsValid(), "Could not launch process")
37+
38+
self.assertEqual(process.GetState(), lldb.eStateStopped)
39+
40+
thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonException)
41+
self.assertIsNotNone(thread, "No thread stopped with exception stop reason")
42+
43+
stop_description = thread.GetStopDescription(256)
44+
self.assertIn(
45+
"Run-time check failure",
46+
stop_description,
47+
"Stop reason does not mention run-time check failure",
48+
)
49+
self.assertIn(
50+
"variable 'x' is being used without being initialized",
51+
stop_description,
52+
"Stop reason does not mention uninitialized variable 'x'",
53+
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <stdio.h>
2+
3+
int main() {
4+
int x;
5+
printf("%d\n", x);
6+
}

0 commit comments

Comments
 (0)