[Lldb-commits] [lldb] [lldb] Filter overridden functions from expression name lookup (PR #233545)

Longjun Luo via lldb-commits lldb-commits at lists.llvm.org
Tue Oct 20 02:56:33 PDT 2026


https://github.com/anatasluo updated https://github.com/llvm/llvm-project/pull/233545

>From f956ca473347b6f08cab0893886b1b65ade45598 Mon Sep 17 00:00:00 2001
From: Longjun Luo <luolongjuna at gmail.com>
Date: Mon, 19 Oct 2026 19:16:46 +0000
Subject: [PATCH] [lldb] Filter overridden functions from expression name
 lookup

An overridden weak function can retain its body and DWARF in an ELF
executable. Expression lookup may select that definition, causing a
call by name to execute the overridden body instead of the definition
selected by the linker.

Filter external DWARF function candidates in `LookupFunction` using
the executable's ELF symbol table before scope filtering and type
deduplication.

Attach a `FunctionCallLabel` to the selected declaration so JIT symbol
resolution preserves that choice (falling back to the ELF symbol's
address if the linked definition has no DWARF).

Fixes #189539

Assisted-by: OpenAI Codex
---
 lldb/include/lldb/Expression/Expression.h     |  14 ++
 lldb/include/lldb/Symbol/Function.h           |  13 +-
 lldb/include/lldb/Symbol/ObjectFile.h         |   6 +
 lldb/source/Expression/Expression.cpp         |  50 ++++++
 lldb/source/Expression/IRExecutionUnit.cpp    |  18 ++
 .../Clang/ClangExpressionDeclMap.cpp          |  72 +++++++-
 .../Clang/NameSearchContext.cpp               |   2 +
 .../Plugins/ObjectFile/ELF/ObjectFileELF.h    |   4 +
 .../SymbolFile/DWARF/SymbolFileDWARF.cpp      |   7 +-
 .../overridden-weak-symbols/Makefile          |  10 ++
 .../TestOverriddenWeakSymbols.py              | 155 ++++++++++++++++++
 .../overridden-weak-symbols/cxx-main.cpp      |   1 +
 .../overridden-weak-symbols/cxx-weak.cpp      |   1 +
 .../overridden-weak-symbols/localized-input.c |  10 ++
 .../overridden-weak-symbols/localized-main.c  |   5 +
 .../expression/overridden-weak-symbols/main.c |  34 ++++
 .../overridden-weak-symbols/no-debug-main.c   |   8 +
 .../overridden-weak-symbols/no-debug-strong.c |   1 +
 .../overridden-weak-symbols/shared-main.c     |  12 ++
 .../overridden-weak-symbols/shared-strong.c   |   1 +
 .../overridden-weak-symbols/shared-weak.c     |   2 +
 .../overridden-weak-symbols/symbol-only.c     |   8 +
 .../expression/overridden-weak-symbols/weak.c |  35 ++++
 lldb/unittests/Expression/ExpressionTest.cpp  |  40 +++++
 24 files changed, 505 insertions(+), 4 deletions(-)
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/Makefile
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/TestOverriddenWeakSymbols.py
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/cxx-main.cpp
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/cxx-weak.cpp
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/localized-input.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/localized-main.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/main.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-main.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-strong.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/shared-main.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/shared-strong.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/shared-weak.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/symbol-only.c
 create mode 100644 lldb/test/API/commands/expression/overridden-weak-symbols/weak.c

diff --git a/lldb/include/lldb/Expression/Expression.h b/lldb/include/lldb/Expression/Expression.h
index 847226167d584a..7c7f339490e182 100644
--- a/lldb/include/lldb/Expression/Expression.h
+++ b/lldb/include/lldb/Expression/Expression.h
@@ -13,6 +13,7 @@
 #include <string>
 #include <vector>
 
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/Support/FormatProviders.h"
 
 #include "lldb/Expression/ExpressionTypeSystemHelper.h"
@@ -21,6 +22,19 @@
 
 namespace lldb_private {
 
+/// Find all exact-name code symbols in the main ELF executable's symbol table.
+std::vector<Symbol *>
+FindELFFunctionSymbolsWithName(Target &target, const lldb::ModuleSP &module,
+                               ConstString name);
+
+/// Return the first symbol in name_matches at file_address, or nullptr.
+Symbol *FindSymbolAtFileAddress(llvm::ArrayRef<Symbol *> name_matches,
+                                lldb::addr_t file_address);
+
+/// Return an external or weak symbol when all such symbols with valid file
+/// addresses share one address; otherwise return nullptr.
+Symbol *FindUniqueExternalFunctionSymbol(llvm::ArrayRef<Symbol *> symbols);
+
 /// \class Expression Expression.h "lldb/Expression/Expression.h" Encapsulates
 /// a single expression for use in lldb
 ///
diff --git a/lldb/include/lldb/Symbol/Function.h b/lldb/include/lldb/Symbol/Function.h
index 04a8e9a89c90ec..e95c91631c7810 100644
--- a/lldb/include/lldb/Symbol/Function.h
+++ b/lldb/include/lldb/Symbol/Function.h
@@ -540,6 +540,16 @@ class Function : public UserID, public SymbolContextScope {
 
   const Mangled &GetMangled() const { return m_mangled; }
 
+  /// Whether debug information marks this definition as visible outside its CU.
+  bool IsExternal() const { return m_flags.Test(flagsExternal); }
+
+  void SetIsExternal(bool external) {
+    if (external)
+      m_flags.Set(flagsExternal);
+    else
+      m_flags.Clear(flagsExternal);
+  }
+
   /// Get the DeclContext for this function, if available.
   ///
   /// \return
@@ -634,7 +644,8 @@ class Function : public UserID, public SymbolContextScope {
 protected:
   enum {
     /// Whether we already tried to calculate the prologue size.
-    flagsCalculatedPrologueSize = (1 << 0)
+    flagsCalculatedPrologueSize = (1 << 0),
+    flagsExternal = (1 << 1)
   };
 
   /// The compile unit that owns this function.
diff --git a/lldb/include/lldb/Symbol/ObjectFile.h b/lldb/include/lldb/Symbol/ObjectFile.h
index 81ef424275271a..ca649743a64051 100644
--- a/lldb/include/lldb/Symbol/ObjectFile.h
+++ b/lldb/include/lldb/Symbol/ObjectFile.h
@@ -24,6 +24,7 @@
 #include "lldb/lldb-private.h"
 #include "llvm/Support/Threading.h"
 #include "llvm/Support/VersionTuple.h"
+#include "llvm/TargetParser/Triple.h"
 #include <optional>
 
 namespace lldb_private {
@@ -293,6 +294,11 @@ class ObjectFile : public std::enable_shared_from_this<ObjectFile>,
   ///     ArchSpec object is returned.
   virtual ArchSpec GetArchitecture() = 0;
 
+  /// Return the file format, or UnknownObjectFormat if not reported.
+  virtual llvm::Triple::ObjectFormatType GetFileFormat() const {
+    return llvm::Triple::UnknownObjectFormat;
+  }
+
   /// Gets the section list for the currently selected architecture (and
   /// object for archives).
   ///
diff --git a/lldb/source/Expression/Expression.cpp b/lldb/source/Expression/Expression.cpp
index 2fe0a8c743ef55..fd658ea3ae6c67 100644
--- a/lldb/source/Expression/Expression.cpp
+++ b/lldb/source/Expression/Expression.cpp
@@ -7,9 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "lldb/Expression/Expression.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Symbol/Symbol.h"
+#include "lldb/Symbol/SymbolContext.h"
 #include "lldb/Target/ExecutionContextScope.h"
 #include "lldb/Target/Target.h"
 
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
@@ -18,6 +22,52 @@
 
 using namespace lldb_private;
 
+std::vector<Symbol *> lldb_private::FindELFFunctionSymbolsWithName(
+    Target &target, const lldb::ModuleSP &module, ConstString name) {
+  if (!module || module != target.GetExecutableModule())
+    return {};
+  ObjectFile *object = module->GetObjectFile();
+  if (!object || object->GetFileFormat() != llvm::Triple::ELF)
+    return {};
+
+  // Query eSymbolTypeCode only; IFUNC resolvers (eSymbolTypeResolver) do not
+  // carry the final target address.
+  SymbolContextList candidates;
+  module->FindSymbolsWithNameAndType(name, lldb::eSymbolTypeCode, candidates);
+  std::vector<Symbol *> matches;
+  // Name lookup also matches C++ basenames; require an exact symbol name.
+  for (const SymbolContext &context : candidates) {
+    Symbol *symbol = context.symbol;
+    if (symbol->GetMangled().GetName(Mangled::ePreferMangled) == name)
+      matches.push_back(symbol);
+  }
+  return matches;
+}
+
+Symbol *
+lldb_private::FindSymbolAtFileAddress(llvm::ArrayRef<Symbol *> name_matches,
+                                      lldb::addr_t file_address) {
+  auto match = llvm::find_if(name_matches, [&](Symbol *symbol) {
+    return symbol->GetFileAddress() == file_address;
+  });
+  return match == name_matches.end() ? nullptr : *match;
+}
+
+Symbol *lldb_private::FindUniqueExternalFunctionSymbol(
+    llvm::ArrayRef<Symbol *> symbols) {
+  Symbol *selected = nullptr;
+  for (Symbol *symbol : symbols) {
+    // ELF weak symbols have IsWeak() set, rather than IsExternal().
+    if ((!symbol->IsExternal() && !symbol->IsWeak()) ||
+        symbol->GetFileAddress() == LLDB_INVALID_ADDRESS)
+      continue;
+    if (selected && selected->GetFileAddress() != symbol->GetFileAddress())
+      return nullptr;
+    selected = symbol;
+  }
+  return selected;
+}
+
 Expression::Expression(Target &target)
     : m_target_wp(target.shared_from_this()),
       m_jit_start_addr(LLDB_INVALID_ADDRESS),
diff --git a/lldb/source/Expression/IRExecutionUnit.cpp b/lldb/source/Expression/IRExecutionUnit.cpp
index 17e131512ef0f6..c3de65219ea960 100644
--- a/lldb/source/Expression/IRExecutionUnit.cpp
+++ b/lldb/source/Expression/IRExecutionUnit.cpp
@@ -26,6 +26,7 @@
 #include "lldb/Expression/ObjectFileJIT.h"
 #include "lldb/Host/HostInfo.h"
 #include "lldb/Symbol/CompileUnit.h"
+#include "lldb/Symbol/Function.h"
 #include "lldb/Symbol/SymbolContext.h"
 #include "lldb/Symbol/SymbolFile.h"
 #include "lldb/Symbol/SymbolVendor.h"
@@ -615,6 +616,23 @@ class LoadAddressResolver {
     m_symbol_was_missing_weak = true;
 
     for (auto candidate_sc : sc_list.SymbolContexts()) {
+      if (!candidate_sc.symbol && candidate_sc.function &&
+          candidate_sc.function->IsExternal()) {
+        Function *function = candidate_sc.function;
+        auto symbols = FindELFFunctionSymbolsWithName(
+            m_target, function->GetAddress().GetModule(),
+            function->GetMangled().GetName(Mangled::ePreferMangled));
+        // When a FunctionCallLabel resolves to a DWARF Function,
+        // candidate_sc.symbol is not set. Prefer a symbol at the function's
+        // own address; otherwise (e.g. an overridden weak DIE kept for its
+        // type when the strong definition has no DWARF), use the winning ELF
+        // symbol.
+        candidate_sc.symbol = FindSymbolAtFileAddress(
+            symbols, function->GetAddress().GetFileAddress());
+        if (!candidate_sc.symbol)
+          candidate_sc.symbol = FindUniqueExternalFunctionSymbol(symbols);
+      }
+
       // Only symbols can be weak undefined.
       if (!candidate_sc.symbol ||
           candidate_sc.symbol->GetType() != lldb::eSymbolTypeUndefined ||
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
index 351afdbbb2609d..517fda86808b6f 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
@@ -22,12 +22,14 @@
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleSpec.h"
 #include "lldb/Expression/DiagnosticManager.h"
+#include "lldb/Expression/Expression.h"
 #include "lldb/Expression/Materializer.h"
 #include "lldb/Symbol/CompileUnit.h"
 #include "lldb/Symbol/CompilerDecl.h"
 #include "lldb/Symbol/CompilerDeclContext.h"
 #include "lldb/Symbol/Function.h"
 #include "lldb/Symbol/ObjectFile.h"
+#include "lldb/Symbol/Symbol.h"
 #include "lldb/Symbol/SymbolContext.h"
 #include "lldb/Symbol/SymbolFile.h"
 #include "lldb/Symbol/SymbolVendor.h"
@@ -57,9 +59,12 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclarationName.h"
 #include "clang/AST/RecursiveASTVisitor.h"
+#include "llvm/ADT/DenseSet.h"
 
 #include "Plugins/LanguageRuntime/ObjC/ObjCLanguageRuntime.h"
 
+#include <tuple>
+
 using namespace lldb;
 using namespace lldb_private;
 using namespace clang;
@@ -67,6 +72,48 @@ using namespace clang;
 static const char *g_lldb_local_vars_namespace_cstr = "$__lldb_local_vars";
 
 namespace {
+SymbolContextList
+FilterOverriddenELFFunctions(Target &target,
+                             const SymbolContextList &functions) {
+  llvm::DenseSet<std::tuple<lldb_private::Module *, ConstString, addr_t>>
+      definitions;
+  for (const SymbolContext &context : functions) {
+    Function *function = context.function;
+    if (function && function->IsExternal()) {
+      definitions.insert(
+          {context.module_sp.get(),
+           function->GetMangled().GetName(Mangled::ePreferMangled),
+           function->GetAddress().GetFileAddress()});
+    }
+  }
+
+  SymbolContextList filtered;
+  for (const SymbolContext &context : functions) {
+    Function *function = context.function;
+    if (function && function->IsExternal()) {
+      ConstString name =
+          function->GetMangled().GetName(Mangled::ePreferMangled);
+      auto symbols = FindELFFunctionSymbolsWithName(
+          target, function->GetAddress().GetModule(), name);
+      Symbol *selected = FindUniqueExternalFunctionSymbol(symbols);
+      // Drop this DWARF candidate if:
+      // 1. The name resolves to a unique external/weak ELF symbol address,
+      // 2. This candidate has no symbol of its own at its address (neither the
+      //    selected symbol nor a localized symbol), and
+      // 3. Another DWARF candidate exists at the selected address (otherwise,
+      //    keep this candidate so its declaration still provides the type).
+      if (selected &&
+          !FindSymbolAtFileAddress(symbols,
+                                   function->GetAddress().GetFileAddress()) &&
+          definitions.contains(
+              {context.module_sp.get(), name, selected->GetFileAddress()}))
+        continue;
+    }
+    filtered.Append(context);
+  }
+  return filtered;
+}
+
 /// A lambda is represented by Clang as an artifical class whose
 /// members are the lambda captures. If we capture a 'this' pointer,
 /// the artifical class will contain a member variable named 'this'.
@@ -643,6 +690,10 @@ lldb::VariableSP ClangExpressionDeclMap::FindGlobalVariable(
 
   if (vars.GetSize() == 0)
     return VariableSP();
+
+  // FIXME: This blindly picks the first match without checking ELF symbol
+  // binding. For overridden weak variables, this currently works by accident
+  // only when the weak DIE's DW_AT_location resolves to the strong storage.
   return vars.GetVariableAtIndex(0);
 }
 
@@ -1281,6 +1332,11 @@ bool ClangExpressionDeclMap::LookupFunction(
         sc_list);
   }
 
+  // Select the linked definition before scope filtering or type deduplication
+  // can discard it in favor of a retained, overridden definition.
+  if (target)
+    sc_list = FilterOverriddenELFFunctions(*target, sc_list);
+
   // If we found more than one function, see if we can use the frame's decl
   // context to remove functions that are shadowed by other functions which
   // match in type but are nearer in scope.
@@ -1833,6 +1889,7 @@ void ClangExpressionDeclMap::AddOneFunction(NameSearchContext &context,
   CompilerType function_clang_type;
 
   bool is_indirect_function = false;
+  Target *target = m_parser_vars->m_exe_ctx.GetTargetPtr();
 
   if (function) {
     Type *function_type = function->GetType();
@@ -1926,6 +1983,19 @@ void ClangExpressionDeclMap::AddOneFunction(NameSearchContext &context,
 
         return;
       }
+
+      // Keep the selected declaration through JIT lookup. Do not pin calls
+      // into shared libraries, which may be overridden by another image.
+      ModuleSP module = function->GetCompileUnit()->GetModule();
+      if (module == target->GetExecutableModule() &&
+          module->GetObjectFile()->GetFileFormat() == llvm::Triple::ELF) {
+        ConstString name =
+            function->GetMangled().GetName(Mangled::ePreferMangled);
+        FunctionCallLabel label{
+            {}, module->GetID(), function->GetID(), name.GetStringRef()};
+        function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit(
+            *m_ast_context, label.toString()));
+      }
     } else {
       // We failed to copy the type we found
       LLDB_LOG(log,
@@ -1944,8 +2014,6 @@ void ClangExpressionDeclMap::AddOneFunction(NameSearchContext &context,
     return;
   }
 
-  Target *target = m_parser_vars->m_exe_ctx.GetTargetPtr();
-
   lldb::addr_t load_addr =
       fun_address.GetCallableLoadAddress(target, is_indirect_function);
 
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp b/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp
index 794b194ec47d5c..2e05c708febcea 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/NameSearchContext.cpp
@@ -42,6 +42,8 @@ clang::NamedDecl *NameSearchContext::AddFunDecl(const CompilerType &type,
   if (!type.IsValid())
     return nullptr;
 
+  // FIXME: Deduplicating by function type alone assumes all declarations with
+  // the same signature refer to the same function, hiding other definitions.
   if (m_function_types.count(type))
     return nullptr;
 
diff --git a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
index 8adb8af10fe169..8dc6e3d7266c5e 100644
--- a/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
+++ b/lldb/source/Plugins/ObjectFile/ELF/ObjectFileELF.h
@@ -120,6 +120,10 @@ class ObjectFileELF : public lldb_private::ObjectFile {
 
   lldb_private::ArchSpec GetArchitecture() override;
 
+  llvm::Triple::ObjectFormatType GetFileFormat() const override {
+    return llvm::Triple::ELF;
+  }
+
   lldb_private::UUID GetUUID() override;
 
   /// Return the contents of the .gnu_debuglink section, if the object file
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 41e3372584d4f4..f6640812cbb113 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -945,7 +945,12 @@ Function *SymbolFileDWARF::ParseFunction(CompileUnit &comp_unit,
   if (ranges.empty())
     return nullptr;
 
-  return dwarf_ast->ParseFunctionFromDWARF(comp_unit, die, std::move(ranges));
+  Function *function =
+      dwarf_ast->ParseFunctionFromDWARF(comp_unit, die, std::move(ranges));
+  if (function)
+    function->SetIsExternal(die.GetDIE()->GetAttributeValueAsUnsigned(
+        die.GetCU(), DW_AT_external, 0, /*check_elaborating_dies=*/true));
+  return function;
 }
 
 ConstString
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/Makefile b/lldb/test/API/commands/expression/overridden-weak-symbols/Makefile
new file mode 100644
index 00000000000000..ac3216ed92fd1b
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/Makefile
@@ -0,0 +1,10 @@
+C_SOURCES := main.c weak.c
+include Makefile.rules
+
+no-debug-strong.o: CFLAGS += -g0
+symbol-only.o: CFLAGS += -g0
+shared-strong.o: CFLAGS += -g0
+
+localized.o: localized-input.c
+	$(CC) $(CFLAGS) -c $< -o $@
+	$(OBJCOPY) --localize-symbol=action $@
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/TestOverriddenWeakSymbols.py b/lldb/test/API/commands/expression/overridden-weak-symbols/TestOverriddenWeakSymbols.py
new file mode 100644
index 00000000000000..d706ba925300c0
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/TestOverriddenWeakSymbols.py
@@ -0,0 +1,155 @@
+import lldb
+from lldbsuite.test import lldbutil
+from lldbsuite.test.decorators import (
+    requireExpressionEvaluation,
+    skipUnlessPlatform,
+)
+from lldbsuite.test.lldbtest import TestBase, line_number
+
+
+ at skipUnlessPlatform(["linux"])
+ at requireExpressionEvaluation
+class OverriddenWeakSymbolsTest(TestBase):
+    SHARED_BUILD_TESTCASE = False
+
+    def check_functions(self, cxx, reverse, pie, both_weak=False):
+        extension = "cpp" if cxx else "c"
+        prefix = "cxx-" if cxx else ""
+        sources = [f"{prefix}main.{extension}", f"{prefix}weak.{extension}"]
+        if reverse:
+            sources.reverse()
+        cflags = "-fPIE" if pie else "-fno-pie"
+        if both_weak:
+            cflags += " -DBOTH_WEAK"
+        self.build(
+            dictionary={
+                "C_SOURCES": "" if cxx else " ".join(sources),
+                "CXX_SOURCES": " ".join(sources) if cxx else "",
+                "CFLAGS_EXTRAS": cflags,
+                "LD_EXTRAS": "-pie" if pie else "-no-pie",
+            }
+        )
+        target, process, thread, _ = lldbutil.run_to_name_breakpoint(
+            self, "weak_context"
+        )
+        # Check both return value (11 vs 22) and return type (StrongResult vs
+        # WeakResult) to verify that the linked definition's DIE was selected.
+        self.expect_expr("action()", result_type="StrongResult", result_value="11")
+        self.expect_expr("other_action()", result_value="13")
+        self.expect_expr("weak_only()", result_value="7")
+        self.expect_expr("local_only()", result_value="77")
+        self.expect_expr("scoped_action()", result_value="77")
+        self.expect_expr("value", result_value="11")
+
+        pointer_breakpoint = target.BreakpointCreateByLocation(
+            "weak.c", line_number("weak.c", "lookup in pointer scope")
+        )
+        self.assertEqual(pointer_breakpoint.GetNumLocations(), 1)
+        process.Continue()
+        self.expect_expr("action()", result_value="77")
+        self.expect_expr("value", result_value="99")
+        target.BreakpointDelete(pointer_breakpoint.GetID())
+
+        breakpoint = target.BreakpointCreateByName("main_context")
+        self.assertEqual(breakpoint.GetNumLocations(), 1)
+        process.Continue()
+        self.expect_expr("observed_action", result_value="11")
+        self.expect_expr("observed_other", result_value="13")
+        self.expect_expr("action()", result_type="StrongResult", result_value="11")
+        self.expect_expr("other_action()", result_value="13")
+        self.expect_expr("&action == program_action", result_value="true")
+        self.expect_expr("&value == program_value", result_value="true")
+        self.expect_expr("scoped_action(0)", result_value="66")
+        if not cxx:
+            # The overridden body in main.c is still callable via its alias.
+            self.expect_expr("(int)retained_action()", result_value="22")
+        if cxx:
+            self.expect_expr("ns::selected()", result_value="33")
+            self.expect_expr("ns::selected(41)", result_value="42")
+            self.expect_expr("value_ref", result_value="11")
+            self.expect_expr("&value_ref == program_value", result_value="true")
+            self.expect_expr("value_ref = 17", result_value="17")
+            self.expect_expr("*program_value", result_value="17")
+
+        # The retained body remains available to an explicit address query.
+        matches = target.FindFunctions("action", lldb.eFunctionNameTypeBase)
+        for context in matches:
+            function = context.GetFunction()
+            if (
+                function.IsValid()
+                and context.GetCompileUnit().GetFileSpec().GetFilename()
+                == f"{prefix}main.{extension}"
+                and function.GetName() in ("action", "action()")
+            ):
+                address = function.GetStartAddress().GetLoadAddress(target)
+                self.expect_expr(f"((int (*)(void)){address})()", result_value="22")
+                break
+        else:
+            self.fail("Retained weak function is missing")
+
+    def test_c(self):
+        self.check_functions(cxx=False, reverse=False, pie=False)
+
+    def test_c_reverse_pie(self):
+        self.check_functions(cxx=False, reverse=True, pie=True)
+
+    def test_cxx(self):
+        self.check_functions(cxx=True, reverse=False, pie=False)
+
+    def test_cxx_reverse_pie(self):
+        self.check_functions(cxx=True, reverse=True, pie=True)
+
+    def test_two_weak_definitions(self):
+        # The selected ELF definition can itself be weak.
+        self.check_functions(cxx=False, reverse=True, pie=False, both_weak=True)
+
+    def test_selected_function_without_debug_info(self):
+        self.build(dictionary={"C_SOURCES": "no-debug-main.c no-debug-strong.c"})
+        lldbutil.run_to_name_breakpoint(self, "context")
+        # The visible declaration still supplies the type when the linked
+        # definition has no debug info.
+        self.expect_expr("action()", result_type="Result", result_value="11")
+        self.expect_expr("(int)action()", result_value="11")
+        self.expect_expr("program_action()", result_value="11")
+        self.expect_expr("&action == program_action", result_value="true")
+
+    def test_symbol_only_call(self):
+        self.build(dictionary={"C_SOURCES": "symbol-only.c"})
+        lldbutil.run_to_name_breakpoint(self, "context")
+        self.expect_expr("(int)action()", result_value="11")
+
+    def test_shared_library_override(self):
+        self.build(
+            dictionary={
+                "C_SOURCES": "shared-main.c shared-strong.c",
+                "DYLIB_C_SOURCES": "shared-weak.c",
+                "DYLIB_NAME": "weak",
+                "LD_EXTRAS": "-Wl,--export-dynamic",
+            }
+        )
+        lldbutil.run_to_name_breakpoint(
+            self, "main_context", extra_images=["libweak.so"]
+        )
+        # Only the shared library provides a function type, but the executable
+        # overrides that definition. Do not pin calls to the type's module.
+        self.expect_expr("action()", result_value="11")
+        self.expect_expr("observed_action", result_value="11")
+        self.expect_expr("program_action()", result_value="11")
+        self.expect_expr("&action == program_action", result_value="true")
+
+    def test_localized_function(self):
+        self.build(
+            dictionary={
+                "C_SOURCES": "localized-main.c",
+                "OBJECTS": "localized.o localized-main.o",
+            }
+        )
+        lldbutil.run_to_name_breakpoint(self, "local_context")
+        # `objcopy --localize-symbol` turns `action` into a local ELF symbol in
+        # localized.o while its DWARF DIE still has DW_AT_external. Evaluating
+        # `action()` in `local_context` must call the local definition (22), not
+        # the global one (11).
+        self.expect_expr("action()", result_value="22")
+        self.expect_expr("local_action()", result_value="22")
+        self.expect_expr("global_action()", result_value="11")
+        self.expect_expr("&action == local_action", result_value="true")
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/cxx-main.cpp b/lldb/test/API/commands/expression/overridden-weak-symbols/cxx-main.cpp
new file mode 100644
index 00000000000000..0f793e206f401e
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/cxx-main.cpp
@@ -0,0 +1 @@
+#include "main.c"
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/cxx-weak.cpp b/lldb/test/API/commands/expression/overridden-weak-symbols/cxx-weak.cpp
new file mode 100644
index 00000000000000..95317daa6b43ed
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/cxx-weak.cpp
@@ -0,0 +1 @@
+#include "weak.c"
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/localized-input.c b/lldb/test/API/commands/expression/overridden-weak-symbols/localized-input.c
new file mode 100644
index 00000000000000..0e23b10766909a
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/localized-input.c
@@ -0,0 +1,10 @@
+int action(void) { return 22; }
+int (*local_action)(void) = action;
+
+void local_context(void) {}
+
+int local_call(void) {
+  int result = action();
+  local_context();
+  return result;
+}
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/localized-main.c b/lldb/test/API/commands/expression/overridden-weak-symbols/localized-main.c
new file mode 100644
index 00000000000000..013f214d4d1b98
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/localized-main.c
@@ -0,0 +1,5 @@
+int action(void) { return 11; }
+int (*global_action)(void) = action;
+extern int local_call(void);
+
+int main(void) { return local_call() != 22 || action() != 11; }
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/main.c b/lldb/test/API/commands/expression/overridden-weak-symbols/main.c
new file mode 100644
index 00000000000000..925b43293a5954
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/main.c
@@ -0,0 +1,34 @@
+// Paired with weak.c: `action` is weak and `other_action` is strong.
+typedef int WeakResult;
+__attribute__((weak, noinline)) WeakResult action(void) { return 22; }
+#ifndef __cplusplus
+extern __typeof(action) retained_action __attribute__((alias("action")));
+#endif
+__attribute__((noinline)) int other_action(void) { return 13; }
+__attribute__((noinline)) int scoped_action(int n) { return 66 + n; }
+
+int value = 11;
+int (*program_action)(void) = action;
+int *program_value = &value;
+int observed_action, observed_other;
+
+void weak_context(void);
+int weak_only(void);
+__attribute__((noinline)) void main_context(void) {}
+
+#ifdef __cplusplus
+int &value_ref = value;
+
+namespace ns {
+  __attribute__((weak, noinline)) int selected(void) { return 32; }
+  __attribute__((noinline)) int selected(int n) { return n + 1; }
+} // namespace ns
+#endif
+
+int main(void) {
+  observed_action = action();
+  observed_other = other_action();
+  weak_context();
+  main_context();
+  return 0;
+}
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-main.c b/lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-main.c
new file mode 100644
index 00000000000000..a7e550266cdff2
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-main.c
@@ -0,0 +1,8 @@
+typedef int Result;
+__attribute__((weak, noinline)) Result action(void) { return 22; }
+int (*program_action)(void) = action;
+void context(void) {}
+int main(void) {
+  context();
+  return action() != 11;
+}
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-strong.c b/lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-strong.c
new file mode 100644
index 00000000000000..0da608e59e7e8b
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/no-debug-strong.c
@@ -0,0 +1 @@
+int action(void) { return 11; }
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/shared-main.c b/lldb/test/API/commands/expression/overridden-weak-symbols/shared-main.c
new file mode 100644
index 00000000000000..ac89c5a97983c9
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/shared-main.c
@@ -0,0 +1,12 @@
+extern int action(void);
+extern int library_call(void);
+int (*program_action)(void) = action;
+int observed_action;
+
+void main_context(void) {}
+
+int main(void) {
+  observed_action = library_call();
+  main_context();
+  return observed_action != 11;
+}
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/shared-strong.c b/lldb/test/API/commands/expression/overridden-weak-symbols/shared-strong.c
new file mode 100644
index 00000000000000..0da608e59e7e8b
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/shared-strong.c
@@ -0,0 +1 @@
+int action(void) { return 11; }
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/shared-weak.c b/lldb/test/API/commands/expression/overridden-weak-symbols/shared-weak.c
new file mode 100644
index 00000000000000..d9c086067cac5e
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/shared-weak.c
@@ -0,0 +1,2 @@
+__attribute__((weak, noinline)) int action(void) { return 22; }
+int library_call(void) { return action(); }
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/symbol-only.c b/lldb/test/API/commands/expression/overridden-weak-symbols/symbol-only.c
new file mode 100644
index 00000000000000..27139c81ed4580
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/symbol-only.c
@@ -0,0 +1,8 @@
+int action(void) { return 11; }
+
+__attribute__((noinline)) void context(void) {}
+
+int main(void) {
+  context();
+  return action() != 11;
+}
diff --git a/lldb/test/API/commands/expression/overridden-weak-symbols/weak.c b/lldb/test/API/commands/expression/overridden-weak-symbols/weak.c
new file mode 100644
index 00000000000000..7dd74ad9bf0d2a
--- /dev/null
+++ b/lldb/test/API/commands/expression/overridden-weak-symbols/weak.c
@@ -0,0 +1,35 @@
+// Paired with main.c to test weak overrides in both CU orders:
+// `action` is strong (or weak under BOTH_WEAK) and `other_action` is weak.
+typedef int StrongResult;
+#ifdef BOTH_WEAK
+__attribute__((weak))
+#endif
+__attribute__((noinline)) StrongResult action(void) {
+  return 11;
+}
+__attribute__((weak, noinline)) int other_action(void) { return 23; }
+__attribute__((weak, noinline)) int weak_only(void) { return 7; }
+__attribute__((weak)) int value = 22;
+
+static int local_only(void) { return 77; }
+static int scoped_action(void) { return 77; }
+
+__attribute__((noinline)) void pointer_context(void) {
+  int (*action)(void) = local_only;
+  int value = 99;
+  volatile int result = action() + value; // lookup in pointer scope
+  (void)result;
+}
+
+#ifdef __cplusplus
+namespace ns {
+  __attribute__((noinline)) int selected(void) { return 33; }
+} // namespace ns
+#endif
+
+void weak_context(void) {
+  int local_value = local_only();
+  local_value += scoped_action();
+  local_value += 1; // break here
+  pointer_context();
+}
diff --git a/lldb/unittests/Expression/ExpressionTest.cpp b/lldb/unittests/Expression/ExpressionTest.cpp
index 21ac8a31bd82df..5cdff2d57dafaa 100644
--- a/lldb/unittests/Expression/ExpressionTest.cpp
+++ b/lldb/unittests/Expression/ExpressionTest.cpp
@@ -11,11 +11,51 @@
 
 #include "TestingSupport/TestUtilities.h"
 #include "lldb/Expression/Expression.h"
+#include "lldb/Symbol/Symbol.h"
 #include "lldb/Target/Target.h"
 #include "llvm/Testing/Support/Error.h"
 
 using namespace lldb_private;
 
+TEST(ExpressionTests, UniqueExternalFunctionSymbol) {
+  auto section = std::make_shared<Section>(lldb::ModuleSP(), nullptr, 1,
+                                           ".text", lldb::eSectionTypeCode,
+                                           0x1000, 0x100, 0, 0x100, 0, 0);
+  auto make_symbol = [&](bool external, lldb::addr_t offset) {
+    return Symbol(0, "action", lldb::eSymbolTypeCode, external, false, false,
+                  false, section, offset, 1, true, false, 0);
+  };
+  Symbol local = make_symbol(false, 0);
+  Symbol global = make_symbol(true, 0x10);
+  Symbol duplicate = make_symbol(true, 0x10);
+  Symbol other = make_symbol(true, 0x20);
+  Symbol weak = make_symbol(false, 0x30);
+  weak.SetIsWeak(true);
+  Symbol invalid;
+  invalid.SetExternal(true);
+
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({}), nullptr);
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&local, &invalid}), nullptr);
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&global, &local}), &global);
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&local, &global}), &global);
+  Symbol *selected = FindUniqueExternalFunctionSymbol({&global, &duplicate});
+  ASSERT_NE(selected, nullptr);
+  EXPECT_EQ(selected->GetFileAddress(), global.GetFileAddress());
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&global, &other}), nullptr);
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&other, &global}), nullptr);
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&weak, &local}), &weak);
+  EXPECT_EQ(FindUniqueExternalFunctionSymbol({&weak, &global}), nullptr);
+
+  EXPECT_EQ(FindSymbolAtFileAddress({&global, &local}, 0x1000), &local);
+
+  Symbol *match = FindSymbolAtFileAddress({&global, &duplicate}, 0x1010);
+  ASSERT_NE(match, nullptr);
+  EXPECT_EQ(match->GetFileAddress(), 0x1010u);
+  match = FindSymbolAtFileAddress({&duplicate, &global}, 0x1010);
+  ASSERT_NE(match, nullptr);
+  EXPECT_EQ(match->GetFileAddress(), 0x1010u);
+}
+
 struct LabelTestCase {
   llvm::StringRef encoded;
   FunctionCallLabel label;



More information about the lldb-commits mailing list