Author: Charles Zablit Date: 2026-07-30T11:58:56+01:00 New Revision: 0af5e8419f4bb6c830f3e2ec4b640830ea63a8b0
URL: https://github.com/llvm/llvm-project/commit/0af5e8419f4bb6c830f3e2ec4b640830ea63a8b0 DIFF: https://github.com/llvm/llvm-project/commit/0af5e8419f4bb6c830f3e2ec4b640830ea63a8b0.diff LOG: [lldb] Fix C++ expression evaluation with the MS C++ ABI (#212521) IRForTarget didn't correctly handle the Microsoft C++ ABI when preparing JIT'd expressions: - `CreateResultVariable` could match a compiler generated dynamic initializer function instead of the result variable, since both share the same mangled substring under the MS ABI. - `RemoveCXAAtExit` only stripped `__cxa_atexit` calls (Itanium ABI). On the MS ABI, static destructors are registered via plain `atexit`. Example: ```cpp struct Foo { Foo() : x(42) {} ~Foo() {} int x; }; void bar() { static Foo f; // <breakpoint here> } ``` Evaluating `f.x` at the breakpoint on Windows previously could resolve against the wrong symbol or crash, because the MS-ABI-specific dynamic initializer function and atexit-registered destructor thunk weren't accounted for. Changes - Restrict result variable lookup to GlobalVariables, so the dynamic initializer function is skipped. - Strip `atexit` registrations in addition to `__cxa_atexit`. - Clear the body of any atexit destructor thunk. This is needed for: - https://github.com/swiftlang/llvm-project/pull/13517 Added: lldb/test/API/commands/expression/result-with-destructor/Makefile lldb/test/API/commands/expression/result-with-destructor/TestResultWithDestructor.py lldb/test/API/commands/expression/result-with-destructor/main.cpp Modified: lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp Removed: ################################################################################ diff --git a/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp b/lldb/source/Plugins/ExpressionParser/Clang/IRForTarget.cpp index adfb5bd1eca94..497c7f86d39c4 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 destructor 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("??__F")) + 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; } 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
