https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/212521
>From c9142af03257a4f0d278e29a25ed1a4fc0b9e0f6 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Tue, 28 Jul 2026 16:14:51 +0100 Subject: [PATCH 1/2] [lldb] Fix C++ expression evaluation with the MS C++ ABI IRForTarget mishandled the MS ABI: it could match a dynamic initializer function instead of the result variable, and __cxa_atexit stripping missed plain atexit calls, leaving dangling references to freed JIT memory. --- .../ExpressionParser/Clang/IRForTarget.cpp | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp index adfb5bd1eca94..114e44b34888c 100644 --- a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp +++ b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp @@ -180,6 +180,11 @@ bool IRForTarget::CreateResultVariable(llvm::Function &llvm_function) { // on Windows, so let's only check for Itanium guard variables. bool is_guard_var = isGuardVariableSymbol(result_name, /*MS ABI*/ false); + // Skip non-globals, e.g. the MS ABI dynamic initializer function that + // shares a mangled name with the result variable. + if (!isa<GlobalVariable>(value_symbol.second)) + continue; + if (result_name.contains("$__lldb_expr_result_ptr") && !is_guard_var) { found_result = true; m_result_is_pointer = true; @@ -1181,6 +1186,7 @@ bool IRForTarget::HandleObjCClass(Value *classlist_reference) { bool IRForTarget::RemoveCXAAtExit(BasicBlock &basic_block) { std::vector<CallInst *> calls_to_remove; + llvm::SmallVector<llvm::Function *, 2> dead_atexit_callbacks; for (Instruction &inst : basic_block) { CallInst *call = dyn_cast<CallInst>(&inst); @@ -1193,21 +1199,42 @@ bool IRForTarget::RemoveCXAAtExit(BasicBlock &basic_block) { llvm::Function *func = call->getCalledFunction(); - if (func && func->getName() == "__cxa_atexit") + // Itanium ABI uses __cxa_atexit; MS ABI uses plain atexit. + if (func && + (func->getName() == "__cxa_atexit" || func->getName() == "atexit")) remove = true; llvm::Value *val = call->getCalledOperand(); - if (val && val->getName() == "__cxa_atexit") + if (val && (val->getName() == "__cxa_atexit" || val->getName() == "atexit")) remove = true; - if (remove) + if (remove) { + // MS ABI callback thunks (mangled "??__F...") reference the static + // they destroy; track them to clear once the call is gone. + if (call->arg_size() > 0) + if (auto *cb = dyn_cast<llvm::Function>( + call->getArgOperand(0)->stripPointerCasts())) + if (cb->hasInternalLinkage() && cb->getName().starts_with("??__")) + dead_atexit_callbacks.push_back(cb); calls_to_remove.push_back(call); + } } for (CallInst *ci : calls_to_remove) ci->eraseFromParent(); + // Clear the body of any orphaned atexit-destructor thunk so it no longer + // references the statics it used to destroy. + for (llvm::Function *cb : dead_atexit_callbacks) { + if (!cb->use_empty()) + continue; + cb->deleteBody(); + llvm::BasicBlock *entry = + llvm::BasicBlock::Create(cb->getContext(), "", cb); + llvm::ReturnInst::Create(cb->getContext(), entry); + } + return true; } >From bde6793573b865c7f86a97a5417c0c5744326218 Mon Sep 17 00:00:00 2001 From: Charles Zablit <[email protected]> Date: Wed, 29 Jul 2026 15:19:04 +0100 Subject: [PATCH 2/2] add test --- .../result-with-destructor/Makefile | 3 ++ .../TestResultWithDestructor.py | 42 +++++++++++++++++++ .../result-with-destructor/main.cpp | 25 +++++++++++ 3 files changed, 70 insertions(+) create mode 100644 lldb/test/API/commands/expression/result-with-destructor/Makefile create mode 100644 lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py create mode 100644 lldb/test/API/commands/expression/result-with-destructor/main.cpp diff --git a/lldb/test/API/commands/expression/result-with-destructor/Makefile b/lldb/test/API/commands/expression/result-with-destructor/Makefile new file mode 100644 index 0000000000000..99998b20bcb05 --- /dev/null +++ b/lldb/test/API/commands/expression/result-with-destructor/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py b/lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py new file mode 100644 index 0000000000000..ec08be0cf8d95 --- /dev/null +++ b/lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py @@ -0,0 +1,42 @@ +""" +Test evaluating expressions whose result is an rvalue with a non-trivial +destructor. +""" + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class TestCase(TestBase): + @no_debug_info_test + def test(self): + self.build() + target, process, _, _ = lldbutil.run_to_source_breakpoint( + self, "// break here", lldb.SBFileSpec("main.cpp") + ) + + # An lvalue result is turned into a '$__lldb_expr_result_ptr' and needs + # no destructor. + self.expect_expr("f.x", result_type="int", result_value="42") + + # An rvalue result is turned into a static '$__lldb_expr_result' whose + # destructor gets registered with atexit/__cxa_atexit. + self.expect_expr( + "make_foo()", + result_type="Foo", + result_children=[ValueCheck(name="x", value="42")], + ) + self.expect_expr( + "make_widget()", + result_type="Widget", + result_children=[ValueCheck(name="x", value="47")], + ) + + # Make sure evaluating the expressions didn't leave a dangling + # destructor registered in the inferior. + target.DeleteAllBreakpoints() + process.Continue() + self.assertState(process.GetState(), lldb.eStateExited) + self.assertEqual(process.GetExitStatus(), 0) diff --git a/lldb/test/API/commands/expression/result-with-destructor/main.cpp b/lldb/test/API/commands/expression/result-with-destructor/main.cpp new file mode 100644 index 0000000000000..2252259d4e0f1 --- /dev/null +++ b/lldb/test/API/commands/expression/result-with-destructor/main.cpp @@ -0,0 +1,25 @@ +struct Foo { + Foo() : x(42) {} + ~Foo() {} + int x; +}; + +struct Widget { + Widget() : x(47) {} + ~Widget() {} + int x; +}; + +Foo make_foo() { return Foo(); } + +Widget make_widget() { return Widget(); } + +void bar() { + static Foo f; + // break here +} + +int main() { + bar(); + return 0; +} _______________________________________________ lldb-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
