https://github.com/charles-zablit created 
https://github.com/llvm/llvm-project/pull/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.

>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] [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;
 }
 

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to