[Lldb-commits] [lldb] [lldb] Match runtime linkage names without indexing unrelated symbols (PR #232694)

Karim Alweheshy via lldb-commits lldb-commits at lists.llvm.org
Fri Oct 16 04:41:23 PDT 2026


https://github.com/karim-alweheshy created https://github.com/llvm/llvm-project/pull/232694

Looking for a few known runtime entry points currently builds demangled-name indexes for all symbols in each candidate module. Add an opt-in linear match against exact linkage spellings, including linker annotation aliases, and use it for BoundsSafety detection and breakpoint activation.

The default lookup stays unchanged. The new path still materializes symbols through the normal providers; it avoids name indexing rather than introducing an object-file metadata reader. Symbol order and relevant symbol kinds are preserved. DWARF function lookup and language pruning retain their existing behavior. Resolver copies and saved breakpoints retain the selected matching mode.

History checked: retain Symtab locking from #192753 and the source-name/DWARF distinctions discussed in #205701. Closed #110543 concerned a language-specific workaround, rather than a rejection of this opt-in API. Bounds activation also preserves the configured target language inherited by the previous Target helper.

This is the prerequisite for separate dispatch qSymbol and primary JIT consumers; those behavior changes are excluded here.

Validation: Built current-main LLDB and both relevant native test binaries. All 16 selected native tests and 19 Darwin integration variants pass. Native tests assert that known linkage queries and BoundsSafety probes avoid unrelated name indexing. API tests cover detection/activation, non-runtime libraries, hidden/static functions, stripped symbols with dSYM information, and late-loaded images. Unsupported debug formats and the intentionally excluded raw-DWARF stripped-symbol variant are reported separately.

Assisted-by: OpenAI Codex


<!-- av pr metadata
This information is embedded by the av CLI when creating PRs to track the status of stacks when using Aviator. Please do not delete or edit this section of the PR.
```
{"parent":"main","parentHead":"","trunk":"main"}
```
-->


>From 2888d6d184552bee1b635bbc575968b12360e79a Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at reddit.com>
Date: Fri, 16 Oct 2026 11:15:06 +0200
Subject: [PATCH] [lldb] Match runtime linkage names without indexing unrelated
 symbols

Add an opt-in exact linkage-name path that preserves symbol order, types and linker annotations while avoiding demangled-name indexes. Use it for BoundsSafety detection and breakpoint activation, retaining existing DWARF lookup and default name matching.

Assisted-by: OpenAI Codex
---
 .../lldb/Breakpoint/BreakpointResolverName.h  |   9 +
 lldb/include/lldb/Core/Module.h               |   8 +-
 lldb/include/lldb/Core/ModuleList.h           |   3 +-
 lldb/include/lldb/Symbol/Symtab.h             |   8 +-
 .../Breakpoint/BreakpointResolverName.cpp     |  24 +-
 lldb/source/Core/Module.cpp                   |  13 +-
 lldb/source/Core/ModuleList.cpp               |   6 +-
 .../InstrumentationRuntimeBoundsSafety.cpp    |  60 +++--
 lldb/source/Symbol/Symtab.cpp                 |  50 ++++-
 .../BoundsSafety/runtime_detection/Makefile   |  16 ++
 .../TestBoundsSafetyRuntimeDetection.py       | 160 +++++++++++++
 .../runtime_detection/cxx_runtime.cpp         |   2 +
 .../runtime_detection/late_runtime.cpp        |   2 +
 .../runtime_detection/library_runtime.c       |   2 +
 .../BoundsSafety/runtime_detection/main.c     |  30 +++
 .../BoundsSafety/runtime_detection/runtime.c  |  25 +++
 .../runtime_detection/unrelated.cpp           |   5 +
 lldb/unittests/Symbol/SymtabTest.cpp          | 211 ++++++++++++++++++
 lldb/unittests/Target/CMakeLists.txt          |   2 +
 .../Target/InstrumentationRuntimeTest.cpp     | 145 ++++++++++++
 20 files changed, 748 insertions(+), 33 deletions(-)
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/Makefile
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/cxx_runtime.cpp
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/late_runtime.cpp
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/library_runtime.c
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/main.c
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/runtime.c
 create mode 100644 lldb/test/API/lang/BoundsSafety/runtime_detection/unrelated.cpp
 create mode 100644 lldb/unittests/Target/InstrumentationRuntimeTest.cpp

diff --git a/lldb/include/lldb/Breakpoint/BreakpointResolverName.h b/lldb/include/lldb/Breakpoint/BreakpointResolverName.h
index 33b5db966c9742..e4da5c6609c1b2 100644
--- a/lldb/include/lldb/Breakpoint/BreakpointResolverName.h
+++ b/lldb/include/lldb/Breakpoint/BreakpointResolverName.h
@@ -58,6 +58,14 @@ class BreakpointResolverName : public BreakpointResolver {
 
   ~BreakpointResolverName() override = default;
 
+  /// Match exact symbol-table linkage names without building demangled-name
+  /// indexes. DWARF lookup and pruning retain their source-name behavior.
+  /// This option applies only to exact-name resolvers.
+  void SetMatchLinkageNames(bool match) {
+    assert(!match || m_match_type == Breakpoint::Exact);
+    m_match_linkage_names = match;
+  }
+
   Searcher::CallbackReturn SearchCallback(SearchFilter &filter,
                                           SymbolContext &context,
                                           Address *addr) override;
@@ -85,6 +93,7 @@ class BreakpointResolverName : public BreakpointResolver {
   Breakpoint::MatchType m_match_type;
   lldb::LanguageType m_language;
   bool m_skip_prologue;
+  bool m_match_linkage_names = false;
 
   void AddNameLookup(llvm::StringRef name,
                      lldb::FunctionNameType name_type_mask);
diff --git a/lldb/include/lldb/Core/Module.h b/lldb/include/lldb/Core/Module.h
index e71a29e3e413b9..468198572a18ac 100644
--- a/lldb/include/lldb/Core/Module.h
+++ b/lldb/include/lldb/Core/Module.h
@@ -69,6 +69,9 @@ struct ModuleFunctionSearchOptions {
   bool include_symbols = false;
   /// Include inlined functions.
   bool include_inlines = false;
+  /// Match exact symbol-table linkage spellings without demangling unrelated
+  /// symbols. DWARF function lookup retains its source-name behavior.
+  bool match_linkage_names = false;
 };
 
 /// \class Module Module.h "lldb/Core/Module.h"
@@ -262,9 +265,12 @@ class Module : public std::enable_shared_from_this<Module>,
   const Symbol *FindFirstSymbolWithNameAndType(
       ConstString name, lldb::SymbolType symbol_type = lldb::eSymbolTypeAny);
 
+  /// When match_linkage_names is true, match exact linkage spellings and linker
+  /// annotation aliases without building the demangled name indexes.
   void FindSymbolsWithNameAndType(ConstString name,
                                   lldb::SymbolType symbol_type,
-                                  SymbolContextList &sc_list);
+                                  SymbolContextList &sc_list,
+                                  bool match_linkage_names = false);
 
   void FindSymbolsMatchingRegExAndType(
       const RegularExpression &regex, lldb::SymbolType symbol_type,
diff --git a/lldb/include/lldb/Core/ModuleList.h b/lldb/include/lldb/Core/ModuleList.h
index 61259b54d7e39b..4cfe57dda6c6a9 100644
--- a/lldb/include/lldb/Core/ModuleList.h
+++ b/lldb/include/lldb/Core/ModuleList.h
@@ -389,7 +389,8 @@ class ModuleList {
 
   void FindSymbolsWithNameAndType(ConstString name,
                                   lldb::SymbolType symbol_type,
-                                  SymbolContextList &sc_list) const;
+                                  SymbolContextList &sc_list,
+                                  bool match_linkage_names = false) const;
 
   void FindSymbolsMatchingRegExAndType(const RegularExpression &regex,
                                        lldb::SymbolType symbol_type,
diff --git a/lldb/include/lldb/Symbol/Symtab.h b/lldb/include/lldb/Symbol/Symtab.h
index 224de9e775859f..fa5a86f64e2054 100644
--- a/lldb/include/lldb/Symbol/Symtab.h
+++ b/lldb/include/lldb/Symbol/Symtab.h
@@ -101,7 +101,8 @@ class Symtab {
           Mangled::NamePreference::ePreferDemangled);
   void FindAllSymbolsWithNameAndType(ConstString name,
                                      lldb::SymbolType symbol_type,
-                                     std::vector<uint32_t> &symbol_indexes);
+                                     std::vector<uint32_t> &symbol_indexes,
+                                     bool match_linkage_names = false);
   void FindAllSymbolsWithNameAndType(ConstString name,
                                      lldb::SymbolType symbol_type,
                                      Debug symbol_debug_type,
@@ -120,8 +121,11 @@ class Symtab {
   Symbol *FindSymbolContainingFileAddress(lldb::addr_t file_addr);
   void ForEachSymbolContainingFileAddress(
       lldb::addr_t file_addr, std::function<bool(Symbol *)> const &callback);
+  /// When match_linkage_names is true, match only the exact linkage spelling,
+  /// including linker annotation aliases, without building demangled indexes.
   void FindFunctionSymbols(ConstString name, uint32_t name_type_mask,
-                           SymbolContextList &sc_list);
+                           SymbolContextList &sc_list,
+                           bool match_linkage_names = false);
 
   void SortSymbolIndexesByValue(std::vector<uint32_t> &indexes,
                                 bool remove_duplicates) const;
diff --git a/lldb/source/Breakpoint/BreakpointResolverName.cpp b/lldb/source/Breakpoint/BreakpointResolverName.cpp
index 981b6fba708d6a..b1a03ffd7aff55 100644
--- a/lldb/source/Breakpoint/BreakpointResolverName.cpp
+++ b/lldb/source/Breakpoint/BreakpointResolverName.cpp
@@ -24,6 +24,8 @@
 using namespace lldb;
 using namespace lldb_private;
 
+static constexpr llvm::StringLiteral MatchLinkageNames("MatchLinkageNames");
+
 BreakpointResolverName::BreakpointResolverName(
     const BreakpointSP &bkpt, const char *name_cstr,
     FunctionNameType name_type_mask, LanguageType language,
@@ -84,7 +86,8 @@ BreakpointResolverName::BreakpointResolverName(
                          rhs.GetOffset(), rhs.GetOffsetIsInsnCount()),
       m_lookups(rhs.m_lookups), m_regex(rhs.m_regex),
       m_match_type(rhs.m_match_type), m_language(rhs.m_language),
-      m_skip_prologue(rhs.m_skip_prologue) {}
+      m_skip_prologue(rhs.m_skip_prologue),
+      m_match_linkage_names(rhs.m_match_linkage_names) {}
 
 BreakpointResolverSP BreakpointResolverName::CreateFromStructuredData(
     const StructuredData::Dictionary &options_dict, Status &error) {
@@ -117,10 +120,24 @@ BreakpointResolverSP BreakpointResolverName::CreateFromStructuredData(
     return nullptr;
   }
 
+  bool match_linkage_names = false;
+  if (options_dict.HasKey(MatchLinkageNames) &&
+      !options_dict.GetValueForKeyAsBoolean(MatchLinkageNames,
+                                            match_linkage_names)) {
+    error = Status::FromErrorString(
+        "BRN::CFSD: MatchLinkageNames entry is not a boolean.");
+    return nullptr;
+  }
+
   llvm::StringRef regex_text;
   success = options_dict.GetValueForKeyAsString(
       GetKey(OptionNames::RegexString), regex_text);
   if (success) {
+    if (match_linkage_names) {
+      error = Status::FromErrorString(
+          "BRN::CFSD: Linkage names require an exact-name resolver.");
+      return nullptr;
+    }
     return std::make_shared<BreakpointResolverName>(
         nullptr, RegularExpression(regex_text), language, offset,
         skip_prologue);
@@ -182,6 +199,7 @@ BreakpointResolverSP BreakpointResolverName::CreateFromStructuredData(
     for (size_t i = 1; i < num_elem; i++) {
       resolver_sp->AddNameLookup(names[i], name_masks[i]);
     }
+    resolver_sp->SetMatchLinkageNames(match_linkage_names);
     return resolver_sp;
 }
 
@@ -211,6 +229,9 @@ StructuredData::ObjectSP BreakpointResolverName::SerializeToStructuredData() {
   options_dict_sp->AddBooleanItem(GetKey(OptionNames::SkipPrologue),
                                   m_skip_prologue);
 
+  if (m_match_linkage_names)
+    options_dict_sp->AddBooleanItem(MatchLinkageNames, true);
+
   return WrapOptionsDict(options_dict_sp);
 }
 
@@ -264,6 +285,7 @@ BreakpointResolverName::SearchCallback(SearchFilter &filter,
   ModuleFunctionSearchOptions function_options;
   function_options.include_symbols = !filter_by_cu;
   function_options.include_inlines = true;
+  function_options.match_linkage_names = m_match_linkage_names;
 
   switch (m_match_type) {
   case Breakpoint::Exact:
diff --git a/lldb/source/Core/Module.cpp b/lldb/source/Core/Module.cpp
index 7eab74e57766e9..00b518ddcb9794 100644
--- a/lldb/source/Core/Module.cpp
+++ b/lldb/source/Core/Module.cpp
@@ -817,8 +817,11 @@ void Module::FindFunctions(llvm::ArrayRef<Module::LookupInfo> lookup_infos,
                            options.include_inlines, sc_list);
     if (options.include_symbols)
       if (Symtab *symtab = symbols->GetSymtab())
-        symtab->FindFunctionSymbols(lookup_info.GetLookupName(),
-                                    lookup_info.GetNameTypeMask(), sc_list);
+        symtab->FindFunctionSymbols(options.match_linkage_names
+                                        ? lookup_info.GetName()
+                                        : lookup_info.GetLookupName(),
+                                    lookup_info.GetNameTypeMask(), sc_list,
+                                    options.match_linkage_names);
   }
 }
 
@@ -1306,12 +1309,14 @@ void Module::FindFunctionSymbols(ConstString name, uint32_t name_type_mask,
 
 void Module::FindSymbolsWithNameAndType(ConstString name,
                                         SymbolType symbol_type,
-                                        SymbolContextList &sc_list) {
+                                        SymbolContextList &sc_list,
+                                        bool match_linkage_names) {
   // No need to protect this call using m_mutex all other method calls are
   // already thread safe.
   if (Symtab *symtab = GetSymtab()) {
     std::vector<uint32_t> symbol_indexes;
-    symtab->FindAllSymbolsWithNameAndType(name, symbol_type, symbol_indexes);
+    symtab->FindAllSymbolsWithNameAndType(name, symbol_type, symbol_indexes,
+                                          match_linkage_names);
     SymbolIndicesToSymbolContextList(symtab, symbol_indexes, sc_list);
   }
 }
diff --git a/lldb/source/Core/ModuleList.cpp b/lldb/source/Core/ModuleList.cpp
index 9f5614538a9874..7e76e17eabf8a3 100644
--- a/lldb/source/Core/ModuleList.cpp
+++ b/lldb/source/Core/ModuleList.cpp
@@ -560,10 +560,12 @@ void ModuleList::FindGlobalVariables(const RegularExpression &regex,
 
 void ModuleList::FindSymbolsWithNameAndType(ConstString name,
                                             SymbolType symbol_type,
-                                            SymbolContextList &sc_list) const {
+                                            SymbolContextList &sc_list,
+                                            bool match_linkage_names) const {
   std::lock_guard<std::recursive_mutex> guard(m_modules_mutex);
   for (const ModuleSP &module_sp : m_modules)
-    module_sp->FindSymbolsWithNameAndType(name, symbol_type, sc_list);
+    module_sp->FindSymbolsWithNameAndType(name, symbol_type, sc_list,
+                                          match_linkage_names);
 }
 
 void ModuleList::FindSymbolsMatchingRegExAndType(
diff --git a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp
index 506785ad15e1b4..e7bfc4c08cb428 100644
--- a/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp
+++ b/lldb/source/Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.cpp
@@ -9,13 +9,17 @@
 #include "InstrumentationRuntimeBoundsSafety.h"
 
 #include "Plugins/Process/Utility/HistoryThread.h"
+#include "lldb/Breakpoint/BreakpointResolverName.h"
 #include "lldb/Breakpoint/StoppointCallbackContext.h"
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/Core/SearchFilter.h"
 #include "lldb/Symbol/Block.h"
+#include "lldb/Symbol/ObjectFile.h"
 #include "lldb/Symbol/Symbol.h"
 #include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Symbol/Symtab.h"
 #include "lldb/Symbol/Variable.h"
 #include "lldb/Symbol/VariableList.h"
 #include "lldb/Target/InstrumentationRuntimeStopInfo.h"
@@ -29,6 +33,7 @@
 #include "clang/CodeGen/ModuleBuilder.h"
 
 #include <memory>
+#include <mutex>
 #include <type_traits>
 
 using namespace lldb;
@@ -386,15 +391,31 @@ InstrumentationRuntimeBoundsSafety::GetPatternForRuntimeLibrary() {
 bool InstrumentationRuntimeBoundsSafety::CheckIfRuntimeIsValid(
     const lldb::ModuleSP module_sp) {
   Log *log_category = GetLog(LLDBLog::InstrumentationRuntime);
-  for (const auto &SoftTrapFunc : getBoundsSafetySoftTrapRuntimeFuncs()) {
-    ConstString test_sym(SoftTrapFunc);
-
-    if (module_sp->FindFirstSymbolWithNameAndType(test_sym,
-                                                  lldb::eSymbolTypeAny)) {
-      LLDB_LOG(log_category, "found \"{0}\" in {1}",
-               test_sym.AsCString("<unknown symbol>"),
-               module_sp->GetFileSpec().GetPath());
-      return true;
+  if (Symtab *symtab = module_sp->GetSymtab()) {
+    const ConstString minimal(BoundsSafetySoftTrapMinimal);
+    const ConstString with_str(BoundsSafetySoftTrapStr);
+    std::lock_guard<std::recursive_mutex> guard(symtab->GetMutex());
+    const size_t num_symbols = symtab->GetNumSymbols();
+    for (size_t i = 0; i < num_symbols; ++i) {
+      Symbol *symbol = symtab->SymbolAtIndex(i);
+      if (symbol->IsTrampoline())
+        continue;
+      // These runtime entry points have C linkage. Their presence does not
+      // require indexing or demangling every other symbol in the module.
+      ConstString name = symbol->GetMangled().GetName(Mangled::ePreferMangled);
+      bool matches = name == minimal || name == with_str;
+      if (!matches && symbol->ContainsLinkerAnnotations()) {
+        llvm::StringRef stripped =
+            symtab->GetObjectFile()->StripLinkerSymbolAnnotations(
+                name.GetStringRef());
+        matches = stripped == BoundsSafetySoftTrapMinimal ||
+                  stripped == BoundsSafetySoftTrapStr;
+      }
+      if (matches && !symbol->IsSyntheticWithAutoGeneratedName()) {
+        LLDB_LOG(log_category, "found \"{0}\" in {1}", name,
+                 module_sp->GetFileSpec().GetPath());
+        return true;
+      }
     }
   }
   LLDB_LOG(log_category,
@@ -451,14 +472,19 @@ void InstrumentationRuntimeBoundsSafety::Activate() {
   for (auto &breakpoint_func : getBoundsSafetySoftTrapRuntimeFuncs())
     breakpoints.emplace_back(breakpoint_func);
 
-  BreakpointSP breakpoint = process_sp->GetTarget().CreateBreakpoint(
-      /*containingModules=*/nullptr,
-      /*containingSourceFiles=*/nullptr, breakpoints, eFunctionNameTypeFull,
-      eLanguageTypeUnknown,
-      /*m_offset=*/0,
-      /*skip_prologue*/ eLazyBoolNo,
-      /*internal=*/true,
-      /*request_hardware*/ false);
+  Target &target = process_sp->GetTarget();
+  SearchFilterSP filter =
+      std::make_shared<SearchFilterForUnconstrainedSearches>(
+          target.shared_from_this());
+  auto resolver = std::make_shared<BreakpointResolverName>(
+      nullptr, breakpoints, eFunctionNameTypeFull,
+      target.GetLanguage().AsLanguageType(),
+      /*offset=*/0, /*skip_prologue=*/false);
+  resolver->SetMatchLinkageNames(true);
+  BreakpointResolverSP resolver_sp = resolver;
+  BreakpointSP breakpoint = target.CreateBreakpoint(
+      filter, resolver_sp, /*internal=*/true, /*hardware=*/false,
+      /*resolve_indirect_symbols=*/true);
 
   if (!breakpoint)
     return LogBeforeReturn<void>("failed to create breakpoint");
diff --git a/lldb/source/Symbol/Symtab.cpp b/lldb/source/Symbol/Symtab.cpp
index efea0339d2ef66..8b42f9b24617af 100644
--- a/lldb/source/Symbol/Symtab.cpp
+++ b/lldb/source/Symbol/Symtab.cpp
@@ -810,12 +810,30 @@ Symbol *Symtab::FindSymbolWithType(SymbolType symbol_type,
   return nullptr;
 }
 
-void
-Symtab::FindAllSymbolsWithNameAndType(ConstString name,
-                                      SymbolType symbol_type,
-                                      std::vector<uint32_t> &symbol_indexes) {
+void Symtab::FindAllSymbolsWithNameAndType(
+    ConstString name, SymbolType symbol_type,
+    std::vector<uint32_t> &symbol_indexes, bool match_linkage_names) {
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
 
+  if (match_linkage_names) {
+    if (!name)
+      return;
+    for (uint32_t i = 0; i < m_symbols.size(); ++i) {
+      Symbol &symbol = m_symbols[i];
+      if (symbol_type != eSymbolTypeAny && symbol.GetType() != symbol_type)
+        continue;
+      ConstString linkage_name =
+          symbol.GetMangled().GetName(Mangled::ePreferMangled);
+      bool matches = linkage_name == name;
+      if (!matches && symbol.ContainsLinkerAnnotations())
+        matches = m_objfile->StripLinkerSymbolAnnotations(
+                      linkage_name.GetStringRef()) == name.GetStringRef();
+      if (matches && !symbol.IsSyntheticWithAutoGeneratedName())
+        symbol_indexes.push_back(i);
+    }
+    return;
+  }
+
   // Initialize all of the lookup by name indexes before converting NAME to a
   // uniqued string NAME_STR below.
   InitNameIndexes();
@@ -1085,9 +1103,31 @@ void Symtab::SymbolIndicesToSymbolContextList(
 }
 
 void Symtab::FindFunctionSymbols(ConstString name, uint32_t name_type_mask,
-                                 SymbolContextList &sc_list) {
+                                 SymbolContextList &sc_list,
+                                 bool match_linkage_names) {
   std::vector<uint32_t> symbol_indexes;
 
+  if (match_linkage_names) {
+    std::lock_guard<std::recursive_mutex> guard(m_mutex);
+    std::vector<uint32_t> matching_indexes;
+    FindAllSymbolsWithNameAndType(name, eSymbolTypeAny, matching_indexes,
+                                  /*match_linkage_names=*/true);
+    for (uint32_t i : matching_indexes) {
+      switch (m_symbols[i].GetType()) {
+      case eSymbolTypeCode:
+      case eSymbolTypeResolver:
+      case eSymbolTypeReExported:
+      case eSymbolTypeAbsolute:
+        symbol_indexes.push_back(i);
+        break;
+      default:
+        break;
+      }
+    }
+    SymbolIndicesToSymbolContextList(symbol_indexes, sc_list);
+    return;
+  }
+
   // eFunctionNameTypeAuto should be pre-resolved by a call to
   // Module::LookupInfo::LookupInfo()
   assert((name_type_mask & eFunctionNameTypeAuto) == 0);
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/Makefile b/lldb/test/API/lang/BoundsSafety/runtime_detection/Makefile
new file mode 100644
index 00000000000000..3e28c7d677ad3a
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/Makefile
@@ -0,0 +1,16 @@
+C_SOURCES := main.c
+DYLIB_CXX_SOURCES := unrelated.cpp
+DYLIB_NAME := Unrelated
+
+ifeq ($(RUNTIME_LOCATION),main)
+C_SOURCES += runtime.c
+else ifeq ($(RUNTIME_LOCATION),dylib)
+DYLIB_C_SOURCES := library_runtime.c
+else ifeq ($(RUNTIME_LOCATION),cxx)
+DYLIB_CXX_SOURCES += cxx_runtime.cpp
+else ifeq ($(RUNTIME_LOCATION),both)
+C_SOURCES += runtime.c
+DYLIB_CXX_SOURCES += cxx_runtime.cpp
+endif
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py b/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py
new file mode 100644
index 00000000000000..b5ebaf98460d6d
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py
@@ -0,0 +1,160 @@
+import lldb
+from lldbsuite.test.decorators import skipIf, skipIfWindows, skipUnlessDarwin
+from lldbsuite.test.lldbtest import TestBase
+
+
+ at skipIfWindows
+class TestBoundsSafetyRuntimeDetection(TestBase):
+    SHARED_BUILD_TESTCASE = False
+
+    def start(
+        self,
+        runtime_location=None,
+        with_string=False,
+        static=False,
+        without_debug_info=False,
+        late=False,
+        strip_runtime_symbols=False,
+        unrelated_dsym=False,
+    ):
+        flags = []
+        if with_string:
+            flags.append("-DWITH_STRING_TRAP")
+        if static:
+            flags.append("-DSTATIC_RUNTIME")
+        if runtime_location in ("main", "both"):
+            flags.append("-DCALL_MAIN_RUNTIME")
+        if runtime_location in ("dylib", "cxx", "both"):
+            flags.append("-DCALL_LIBRARY_RUNTIME")
+        if late:
+            self.build(
+                dictionary={
+                    "DYLIB_ONLY": "YES",
+                    "DYLIB_NAME": "Late",
+                    "DYLIB_CXX_SOURCES": "late_runtime.cpp",
+                }
+            )
+            flags.append("-DLATE_LOAD")
+        dictionary = {
+            "RUNTIME_LOCATION": runtime_location or "none",
+            "CFLAGS_EXTRAS": " ".join(flags),
+        }
+        if without_debug_info:
+            dictionary["DEBUG_INFO_FLAG"] = "-g0"
+        self.build(dictionary=dictionary)
+        if unrelated_dsym and self.platformIsDarwin():
+            library = self.getBuildArtifact(
+                self.platformContext.getFullLibName("Unrelated")
+            )
+            self.runBuildCommand(
+                ["xcrun", "dsymutil", "-o", library + ".dSYM", library]
+            )
+        if strip_runtime_symbols:
+            library = self.getBuildArtifact(
+                self.platformContext.getFullLibName("Unrelated")
+            )
+            self.runBuildCommand(["xcrun", "strip", "-x", library])
+            self.runBuildCommand(["codesign", "-f", "-s", "-", library])
+        self.runCmd("settings set target.preload-symbols false")
+        self.runCmd("settings set target.load-script-from-symbol-file false")
+        self.log = self.getBuildArtifact("instrumentation.log")
+        self.runCmd(f'log enable lldb instrumentation-runtime -f "{self.log}"')
+        target = self.createTestTarget(load_dependent_modules=False)
+        breakpoint = target.BreakpointCreateByName(
+            "main", target.GetExecutable().GetFilename()
+        )
+        self.assertEqual(breakpoint.GetNumLocations(), 1)
+        arguments = (
+            [self.getBuildArtifact(self.platformContext.getFullLibName("Late"))]
+            if late
+            else None
+        )
+        process = target.LaunchSimple(
+            arguments, None, self.get_process_working_directory()
+        )
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        if runtime_location is not None:
+            self.assertTrue(
+                process.IsInstrumentationRuntimePresent(
+                    lldb.eInstrumentationRuntimeTypeBoundsSafety
+                )
+            )
+        return target
+
+    def check_trap(self, target, module_name, with_string=False, raw_symbol=True):
+        process = target.GetProcess()
+        self.assertSuccess(process.Continue())
+        self.assertState(process.GetState(), lldb.eStateStopped)
+        thread = process.GetSelectedThread()
+        self.assertEqual(thread.GetStopReason(), lldb.eStopReasonInstrumentation)
+        frame = thread.GetFrameAtIndex(0)
+        expected_name = (
+            "__bounds_safety_soft_trap_s"
+            if with_string
+            else "__bounds_safety_soft_trap"
+        )
+        if raw_symbol:
+            self.assertEqual(frame.GetSymbol().GetName(), expected_name)
+        else:
+            self.assertIn(
+                frame.GetFunctionName(),
+                (expected_name, expected_name + "()", "::" + expected_name + "()"),
+            )
+        self.assertIn(module_name, frame.GetModule().GetFileSpec().GetFilename())
+
+    def test_non_runtime_library_is_rejected(self):
+        target = self.start(unrelated_dsym=True)
+        self.runCmd("log disable lldb instrumentation-runtime")
+        library = self.platformContext.getFullLibName("Unrelated")
+        with open(self.log) as log:
+            self.assertTrue(
+                any(
+                    "did not find BoundsSafety soft trap functions in module" in line
+                    and library in line
+                    for line in log
+                )
+            )
+        process = target.GetProcess()
+        self.assertSuccess(process.Continue())
+        self.assertState(process.GetState(), lldb.eStateExited)
+        self.assertEqual(process.GetExitStatus(), 42)
+
+    def test_hidden_minimal_runtime_in_executable(self):
+        target = self.start("main")
+        self.check_trap(target, target.GetExecutable().GetFilename())
+
+    def test_hidden_string_runtime_in_executable(self):
+        target = self.start("main", with_string=True)
+        self.check_trap(target, target.GetExecutable().GetFilename(), with_string=True)
+
+    def test_hidden_minimal_runtime_in_unrelated_library(self):
+        target = self.start("dylib")
+        self.check_trap(target, "Unrelated")
+
+    def test_hidden_string_runtime_in_unrelated_library(self):
+        target = self.start("dylib", with_string=True)
+        self.check_trap(target, "Unrelated", with_string=True)
+
+    def test_extern_c_runtime_in_cxx_library(self):
+        target = self.start("cxx")
+        self.check_trap(target, "Unrelated")
+
+    def test_static_runtime_copies_in_multiple_images(self):
+        target = self.start("both", static=True)
+        self.check_trap(target, target.GetExecutable().GetFilename())
+        self.check_trap(target, "Unrelated")
+
+    def test_runtime_without_dwarf(self):
+        target = self.start("dylib", without_debug_info=True)
+        self.check_trap(target, "Unrelated")
+
+    def test_late_loaded_runtime_copy(self):
+        target = self.start("main", late=True)
+        self.check_trap(target, target.GetExecutable().GetFilename())
+        self.check_trap(target, "Late")
+
+    @skipUnlessDarwin
+    @skipIf(debug_info="dwarf")
+    def test_extern_c_runtime_with_dwarf_only(self):
+        target = self.start("cxx", strip_runtime_symbols=True)
+        self.check_trap(target, "Unrelated", raw_symbol=False)
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/cxx_runtime.cpp b/lldb/test/API/lang/BoundsSafety/runtime_detection/cxx_runtime.cpp
new file mode 100644
index 00000000000000..213108252bfae9
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/cxx_runtime.cpp
@@ -0,0 +1,2 @@
+#define CALL_RUNTIME_NAME call_library_trap
+#include "runtime.c"
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/late_runtime.cpp b/lldb/test/API/lang/BoundsSafety/runtime_detection/late_runtime.cpp
new file mode 100644
index 00000000000000..17e80adeb1b70c
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/late_runtime.cpp
@@ -0,0 +1,2 @@
+#define CALL_RUNTIME_NAME call_late_trap
+#include "runtime.c"
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/library_runtime.c b/lldb/test/API/lang/BoundsSafety/runtime_detection/library_runtime.c
new file mode 100644
index 00000000000000..213108252bfae9
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/library_runtime.c
@@ -0,0 +1,2 @@
+#define CALL_RUNTIME_NAME call_library_trap
+#include "runtime.c"
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/main.c b/lldb/test/API/lang/BoundsSafety/runtime_detection/main.c
new file mode 100644
index 00000000000000..2e74e156b2767b
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/main.c
@@ -0,0 +1,30 @@
+extern int unrelated(void);
+#ifdef CALL_MAIN_RUNTIME
+extern void call_main_trap(void);
+#endif
+#ifdef CALL_LIBRARY_RUNTIME
+extern void call_library_trap(void);
+#endif
+#ifdef LATE_LOAD
+#include <assert.h>
+#include <dlfcn.h>
+#endif
+
+int main(int argc, char **argv) {
+  int result = unrelated();
+#ifdef CALL_MAIN_RUNTIME
+  call_main_trap();
+#endif
+#ifdef CALL_LIBRARY_RUNTIME
+  call_library_trap();
+#endif
+#ifdef LATE_LOAD
+  void *library = dlopen(argv[1], RTLD_NOW);
+  assert(library);
+  void (*call_late_trap)(void) =
+      (void (*)(void))dlsym(library, "call_late_trap");
+  assert(call_late_trap);
+  call_late_trap();
+#endif
+  return result;
+}
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/runtime.c b/lldb/test/API/lang/BoundsSafety/runtime_detection/runtime.c
new file mode 100644
index 00000000000000..ca09d8dc5f4289
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/runtime.c
@@ -0,0 +1,25 @@
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef STATIC_RUNTIME
+#define TRAP_LINKAGE static __attribute__((noinline, used))
+#else
+#define TRAP_LINKAGE __attribute__((visibility("hidden"), noinline, used))
+#endif
+
+#ifndef CALL_RUNTIME_NAME
+#define CALL_RUNTIME_NAME call_main_trap
+#endif
+
+#ifdef WITH_STRING_TRAP
+TRAP_LINKAGE void __bounds_safety_soft_trap_s(const char *reason) {}
+void CALL_RUNTIME_NAME(void) { __bounds_safety_soft_trap_s("fixture"); }
+#else
+TRAP_LINKAGE void __bounds_safety_soft_trap(void) {}
+void CALL_RUNTIME_NAME(void) { __bounds_safety_soft_trap(); }
+#endif
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/unrelated.cpp b/lldb/test/API/lang/BoundsSafety/runtime_detection/unrelated.cpp
new file mode 100644
index 00000000000000..9c19c4e77d1167
--- /dev/null
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/unrelated.cpp
@@ -0,0 +1,5 @@
+namespace Unrelated {
+int value() { return 42; }
+} // namespace Unrelated
+
+extern "C" int unrelated(void) { return Unrelated::value(); }
diff --git a/lldb/unittests/Symbol/SymtabTest.cpp b/lldb/unittests/Symbol/SymtabTest.cpp
index 0236b6caa97068..c023d4409ca4fc 100644
--- a/lldb/unittests/Symbol/SymtabTest.cpp
+++ b/lldb/unittests/Symbol/SymtabTest.cpp
@@ -16,9 +16,11 @@
 
 #include "lldb/Core/DataFileCache.h"
 #include "lldb/Core/Module.h"
+#include "lldb/Core/ModuleList.h"
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Host/HostInfo.h"
 #include "lldb/Symbol/Symbol.h"
+#include "lldb/Symbol/SymbolContext.h"
 #include "lldb/Symbol/Symtab.h"
 #include "lldb/Utility/DataEncoder.h"
 #include "lldb/Utility/DataExtractor.h"
@@ -844,3 +846,212 @@ TEST_F(SymtabTest, TestSymbolTableCreatedOnDemand) {
   Symtab *cached_symtab = symbol_file->GetSymtab(/*can_create=*/false);
   ASSERT_EQ(symtab, cached_symtab);
 }
+
+TEST_F(SymtabTest, LinkageFunctionLookupDoesNotIndexOtherNames) {
+  auto image = TestFile::fromYaml(R"(
+--- !ELF
+FileHeader:
+  Class: ELFCLASS64
+  Data: ELFDATA2LSB
+  Type: ET_DYN
+  Machine: EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+    Flags: [SHF_ALLOC, SHF_EXECINSTR]
+    Address: 0x1000
+    Content: C3
+)");
+  ASSERT_THAT_EXPECTED(image, llvm::Succeeded());
+  auto module = std::make_shared<Module>(image->moduleSpec());
+  ObjectFile *object = module->GetObjectFile();
+  ASSERT_NE(object, nullptr);
+  Symtab *symtab = object->GetSymtab();
+  uint32_t id = 0;
+  auto add_symbol = [&](llvm::StringRef name, SymbolType type, bool trampoline,
+                        bool annotations) {
+    const uint32_t symbol_id = id++;
+    symtab->AddSymbol(Symbol(symbol_id, name, type, /*external=*/false,
+                             /*is_debug=*/false, trampoline,
+                             /*is_artificial=*/false, SectionSP(), id, 0,
+                             /*size_is_valid=*/false, annotations, 0));
+  };
+  add_symbol("runtime", eSymbolTypeCode, false, false);
+  add_symbol("runtime at VERSION", eSymbolTypeResolver, false, true);
+  add_symbol("runtime", eSymbolTypeReExported, false, false);
+  add_symbol("runtime", eSymbolTypeAbsolute, false, false);
+  add_symbol("runtime", eSymbolTypeUndefined, false, false);
+  add_symbol("runtime", eSymbolTypeData, false, false);
+  // IsTrampoline uses the symbol type; the constructor's legacy bool is unused.
+  add_symbol("runtime", eSymbolTypeTrampoline, true, false);
+  add_symbol("_Z7runtimev", eSymbolTypeCode, false, false);
+  add_symbol("$s4Test7runtimeyyF", eSymbolTypeCode, false, false);
+  for (unsigned i = 0; i < 1024; ++i)
+    add_symbol("_Z9unrelatedILi" + std::to_string(i) + "EEvv", eSymbolTypeCode,
+               false, false);
+
+  SymbolContextList matches;
+  symtab->FindFunctionSymbols(ConstString("runtime"), eFunctionNameTypeFull,
+                              matches, /*match_linkage_names=*/true);
+  ASSERT_EQ(matches.GetSize(), 4u);
+  for (unsigned i = 0; i < matches.GetSize(); ++i) {
+    SymbolContext context;
+    ASSERT_TRUE(matches.GetContextAtIndex(i, context));
+    ASSERT_NE(context.symbol, nullptr);
+    EXPECT_EQ(context.symbol->GetID(), i);
+  }
+  EXPECT_EQ(module->GetSymtabIndexTime().get().count(), 0.0);
+
+  SymbolContextList absent;
+  symtab->FindFunctionSymbols(ConstString("absent"), eFunctionNameTypeFull,
+                              absent, /*match_linkage_names=*/true);
+  EXPECT_EQ(absent.GetSize(), 0u);
+  EXPECT_EQ(module->GetSymtabIndexTime().get().count(), 0.0);
+
+  // The default still builds the general indexes and finds the C++ basename.
+  SymbolContextList normal;
+  symtab->FindFunctionSymbols(ConstString("runtime"), eFunctionNameTypeBase,
+                              normal);
+  EXPECT_GT(normal.GetSize(), matches.GetSize());
+  EXPECT_GT(module->GetSymtabIndexTime().get().count(), 0.0);
+}
+
+TEST_F(SymtabTest, LinkageSymbolLookupPreservesTypesOrderAndAnnotations) {
+  auto image = TestFile::fromYaml(R"(
+--- !ELF
+FileHeader:
+  Class: ELFCLASS64
+  Data: ELFDATA2LSB
+  Type: ET_DYN
+  Machine: EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+    Flags: [SHF_ALLOC, SHF_EXECINSTR]
+    Address: 0x1000
+    Content: C3
+)");
+  ASSERT_THAT_EXPECTED(image, llvm::Succeeded());
+  auto module = std::make_shared<Module>(image->moduleSpec());
+  Symtab *symtab = module->GetObjectFile()->GetSymtab();
+  ASSERT_NE(symtab, nullptr);
+  auto add = [&](user_id_t id, llvm::StringRef name, SymbolType type,
+                 bool external, bool trampoline, bool annotations) {
+    symtab->AddSymbol(Symbol(id, name, type, external, false, trampoline, false,
+                             SectionSP(), id, 0, false, annotations, 0));
+  };
+  add(0, "dispatch_queue_offsets", eSymbolTypeData, false, false, false);
+  add(1, "dispatch_queue_offsets at VERSION", eSymbolTypeData, true, false, true);
+  add(2, "dispatch_queue_offsets", eSymbolTypeReExported, true, false, false);
+  add(3, "dispatch_queue_offsets", eSymbolTypeUndefined, true, false, false);
+  add(4, "dispatch_queue_offsets", eSymbolTypeTrampoline, true, true, false);
+  add(5, "_Z22dispatch_queue_offsetsv", eSymbolTypeCode, true, false, false);
+  add(6, "_Z18other_wire_requestv", eSymbolTypeCode, true, false, false);
+  Symbol named_synthetic(7, "dispatch_queue_offsets", eSymbolTypeData, false,
+                         false, false, false, SectionSP(), 7, 0, false, false,
+                         0);
+  named_synthetic.SetIsSynthetic(true);
+  symtab->AddSymbol(named_synthetic);
+  ConstString synthetic_name("_Z30qsymbol_synthetic_unrelated_06v");
+  Symbol unrelated_synthetic(8, synthetic_name.GetStringRef(), eSymbolTypeCode,
+                             false, false, false, false, SectionSP(), 8, 0,
+                             false, false, 0);
+  unrelated_synthetic.SetIsSynthetic(true);
+  symtab->AddSymbol(unrelated_synthetic);
+  ConstString counterpart;
+  ASSERT_FALSE(synthetic_name.GetMangledCounterpart(counterpart));
+
+  std::vector<uint32_t> matches;
+  symtab->FindAllSymbolsWithNameAndType(ConstString("dispatch_queue_offsets"),
+                                        eSymbolTypeAny, matches,
+                                        /*match_linkage_names=*/true);
+  ASSERT_EQ(matches.size(), 6u);
+  const user_id_t all_ids[] = {0, 1, 2, 3, 4, 7};
+  for (auto [index, id] : llvm::zip(matches, all_ids))
+    EXPECT_EQ(symtab->SymbolAtIndex(index)->GetID(), id);
+  // Checking autogenerated-name status on every named synthetic symbol would
+  // demangle this unrelated name even though no index is built.
+  EXPECT_FALSE(synthetic_name.GetMangledCounterpart(counterpart));
+
+  matches.clear();
+  symtab->FindAllSymbolsWithNameAndType(ConstString("dispatch_queue_offsets"),
+                                        eSymbolTypeData, matches,
+                                        /*match_linkage_names=*/true);
+  ASSERT_EQ(matches.size(), 3u);
+  const user_id_t data_ids[] = {0, 1, 7};
+  for (auto [index, id] : llvm::zip(matches, data_ids))
+    EXPECT_EQ(symtab->SymbolAtIndex(index)->GetID(), id);
+  EXPECT_EQ(module->GetSymtabIndexTime().get().count(), 0.0);
+
+  matches.clear();
+  symtab->FindAllSymbolsWithNameAndType(ConstString("dispatch_queue_offsets"),
+                                        eSymbolTypeTrampoline, matches,
+                                        /*match_linkage_names=*/true);
+  ASSERT_EQ(matches.size(), 1u);
+  EXPECT_EQ(symtab->SymbolAtIndex(matches.front())->GetID(), 4u);
+  EXPECT_EQ(module->GetSymtabIndexTime().get().count(), 0.0);
+
+  matches.clear();
+  symtab->FindAllSymbolsWithNameAndType(ConstString(), eSymbolTypeAny, matches,
+                                        /*match_linkage_names=*/true);
+  EXPECT_TRUE(matches.empty());
+  EXPECT_EQ(module->GetSymtabIndexTime().get().count(), 0.0);
+
+  // Unrelated qSymbol requests still use the established name index, including
+  // a C++ function's unqualified basename.
+  symtab->FindAllSymbolsWithNameAndType(ConstString("other_wire_request"),
+                                        eSymbolTypeAny, matches);
+  ASSERT_EQ(matches.size(), 1u);
+  EXPECT_EQ(symtab->SymbolAtIndex(matches.front())->GetID(), 6u);
+  EXPECT_GT(module->GetSymtabIndexTime().get().count(), 0.0);
+}
+
+TEST_F(SymtabTest, LinkageModuleListLookupPreservesModuleOrderAndTrampolines) {
+  auto image = TestFile::fromYaml(R"(
+--- !ELF
+FileHeader:
+  Class: ELFCLASS64
+  Data: ELFDATA2LSB
+  Type: ET_DYN
+  Machine: EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+    Flags: [SHF_ALLOC, SHF_EXECINSTR]
+    Address: 0x1000
+    Content: C3
+)");
+  ASSERT_THAT_EXPECTED(image, llvm::Succeeded());
+  auto first = std::make_shared<Module>(image->moduleSpec());
+  auto second = std::make_shared<Module>(image->moduleSpec());
+  for (const auto &module : {first, second}) {
+    Symtab *symtab = module->GetObjectFile()->GetSymtab();
+    ASSERT_NE(symtab, nullptr);
+    // Keep the symtab provider available independently of the candidate type.
+    symtab->AddSymbol(Symbol(0, "unrelated", eSymbolTypeCode, false, false,
+                             false, false, SectionSP(), 0, 0, false, false, 0));
+    symtab->AddSymbol(Symbol(
+        1, "runtime", module == first ? eSymbolTypeTrampoline : eSymbolTypeData,
+        false, false, false, false, SectionSP(), 0, 0, false, false, 0));
+  }
+  ModuleList modules;
+  modules.Append(first);
+  modules.Append(second);
+  SymbolContextList matches;
+  modules.FindSymbolsWithNameAndType(ConstString("runtime"), eSymbolTypeAny,
+                                     matches, /*match_linkage_names=*/true);
+  ASSERT_EQ(matches.GetSize(), 2u);
+  EXPECT_EQ(matches[0].module_sp, first);
+  EXPECT_EQ(matches[0].symbol->GetType(), eSymbolTypeTrampoline);
+  EXPECT_EQ(matches[1].module_sp, second);
+  EXPECT_EQ(matches[1].symbol->GetType(), eSymbolTypeData);
+  EXPECT_EQ(first->GetSymtabIndexTime().get().count(), 0.0);
+  EXPECT_EQ(second->GetSymtabIndexTime().get().count(), 0.0);
+
+  // The default name indexes keep their existing trampoline exclusion.
+  matches.Clear();
+  modules.FindSymbolsWithNameAndType(ConstString("runtime"), eSymbolTypeAny,
+                                     matches);
+  ASSERT_EQ(matches.GetSize(), 1u);
+  EXPECT_EQ(matches[0].module_sp, second);
+}
diff --git a/lldb/unittests/Target/CMakeLists.txt b/lldb/unittests/Target/CMakeLists.txt
index 7cf36f12cdede4..8f661198ac2572 100644
--- a/lldb/unittests/Target/CMakeLists.txt
+++ b/lldb/unittests/Target/CMakeLists.txt
@@ -3,6 +3,7 @@ add_lldb_unittest(TargetTests
   DynamicRegisterInfoTest.cpp
   ExecutableModuleTest.cpp
   ExecutionContextTest.cpp
+  InstrumentationRuntimeTest.cpp
   LanguageTest.cpp
   LocateModuleCallbackTest.cpp
   MemoryRegionInfoTest.cpp
@@ -27,6 +28,7 @@ add_lldb_unittest(TargetTests
   LINK_LIBS
       lldbCore
       lldbHost
+      lldbPluginInstrumentationRuntimeBoundsSafety
       lldbPluginObjectFileBreakpad
       lldbPluginObjectFileELF
       lldbPluginObjectFileMachO
diff --git a/lldb/unittests/Target/InstrumentationRuntimeTest.cpp b/lldb/unittests/Target/InstrumentationRuntimeTest.cpp
new file mode 100644
index 00000000000000..72dc09a7409a94
--- /dev/null
+++ b/lldb/unittests/Target/InstrumentationRuntimeTest.cpp
@@ -0,0 +1,145 @@
+//===-- InstrumentationRuntimeTest.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Plugins/InstrumentationRuntime/BoundsSafety/InstrumentationRuntimeBoundsSafety.h"
+#include "Plugins/ObjectFile/ELF/ObjectFileELF.h"
+#include "Plugins/SymbolFile/Symtab/SymbolFileSymtab.h"
+#include "TestingSupport/SubsystemRAII.h"
+#include "TestingSupport/TestUtilities.h"
+#include "lldb/Breakpoint/BreakpointResolverName.h"
+#include "lldb/Core/Module.h"
+#include "lldb/Core/ModuleList.h"
+#include "lldb/Host/FileSystem.h"
+#include "lldb/Host/HostInfo.h"
+#include "lldb/Symbol/ObjectFile.h"
+#include "lldb/Symbol/Symbol.h"
+#include "lldb/Symbol/Symtab.h"
+#include "gtest/gtest.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+namespace {
+class InstrumentationRuntimeTest : public testing::Test {
+  SubsystemRAII<FileSystem, HostInfo, ObjectFileELF, SymbolFileSymtab>
+      subsystem;
+};
+
+constexpr llvm::StringLiteral EmptyImage = R"(
+--- !ELF
+FileHeader:
+  Class: ELFCLASS64
+  Data: ELFDATA2LSB
+  Type: ET_DYN
+  Machine: EM_X86_64
+Sections:
+  - Name: .text
+    Type: SHT_PROGBITS
+    Flags: [SHF_ALLOC, SHF_EXECINSTR]
+    Address: 0x1000
+    Content: C3
+)";
+
+} // namespace
+
+TEST_F(InstrumentationRuntimeTest, BoundsSafetyProbeUsesExactCNames) {
+  auto image = TestFile::fromYaml(EmptyImage);
+  ASSERT_THAT_EXPECTED(image, llvm::Succeeded());
+
+  auto probe = [&](llvm::StringRef name, SymbolType type, bool trampoline,
+                   bool synthetic, bool expected, bool annotations = false) {
+    SCOPED_TRACE(name.str());
+    auto module = std::make_shared<Module>(image->moduleSpec());
+    auto sentinel = std::make_shared<Module>(image->moduleSpec());
+    ASSERT_NE(module->GetObjectFile(), nullptr);
+    ASSERT_NE(sentinel->GetObjectFile(), nullptr);
+    module->SetFileSpecAndObjectName(FileSpec("unrelated.so"), ConstString());
+    sentinel->SetFileSpecAndObjectName(FileSpec("sentinel.so"), ConstString());
+    Symtab *symtab = module->GetObjectFile()->GetSymtab();
+    // Give the symtab provider a function even when the candidate is an
+    // undefined symbol, so Module::GetSymtab retains the provider.
+    symtab->AddSymbol(Symbol(1, "unrelated", eSymbolTypeCode, false, false,
+                             false, false, SectionSP(), 0, 0, false, false, 0));
+    Symbol symbol(0, name, type, /*external=*/false, /*is_debug=*/false,
+                  trampoline, /*is_artificial=*/false, SectionSP(), 0, 0,
+                  /*size_is_valid=*/false, annotations, 0);
+    // Trampoline classification uses the type, not the legacy constructor flag.
+    ASSERT_EQ(symbol.IsTrampoline(), trampoline);
+    symbol.SetIsSynthetic(synthetic);
+    symtab->AddSymbol(symbol);
+
+    ModuleList modules;
+    modules.Append(module);
+    modules.Append(sentinel);
+    auto runtime = InstrumentationRuntimeBoundsSafety::CreateInstance(nullptr);
+    runtime->ModulesDidLoad(modules);
+
+    // A match stops the module walk even though activation has no process.
+    // A negative probe reaches the sentinel and creates its raw symbol table.
+    EXPECT_EQ(sentinel->GetObjectFile()->GetSymtab(/*can_create=*/false) ==
+                  nullptr,
+              expected);
+    EXPECT_EQ(module->GetSymtabIndexTime().get().count(), 0.0);
+  };
+
+  for (auto name : {"__bounds_safety_soft_trap", "__bounds_safety_soft_trap_s"})
+    for (auto type :
+         {eSymbolTypeCode, eSymbolTypeResolver, eSymbolTypeReExported,
+          eSymbolTypeAbsolute, eSymbolTypeUndefined})
+      probe(name, type, false, false, true);
+
+  probe("__bounds_safety_soft_trap at VERSION", eSymbolTypeCode, false, false,
+        true, true);
+  probe("__bounds_safety_soft_trap", eSymbolTypeTrampoline, true, false, false);
+  probe("", eSymbolTypeCode, false, true, false);
+  probe("_Z25__bounds_safety_soft_trapv", eSymbolTypeCode, false, false, false);
+  probe("$s4Test25__bounds_safety_soft_trapyyF", eSymbolTypeCode, false, false,
+        false);
+}
+
+TEST_F(InstrumentationRuntimeTest, LinkageResolverSerialization) {
+  auto resolver = std::make_shared<BreakpointResolverName>(
+      nullptr, "runtime", eFunctionNameTypeFull, eLanguageTypeUnknown,
+      Breakpoint::Exact, 0, false, false);
+  auto serialized = resolver->SerializeToStructuredData();
+  auto *dictionary = serialized->GetAsDictionary();
+  ASSERT_NE(dictionary, nullptr);
+  StructuredData::Dictionary *options = nullptr;
+  ASSERT_TRUE(dictionary->GetValueForKeyAsDictionary("Options", options));
+  ASSERT_NE(options, nullptr);
+  EXPECT_FALSE(options->HasKey("MatchLinkageNames"));
+
+  Status error;
+  EXPECT_NE(BreakpointResolver::CreateFromStructuredData(*dictionary, error),
+            nullptr);
+  EXPECT_TRUE(error.Success());
+
+  resolver->SetMatchLinkageNames(true);
+  serialized = resolver->SerializeToStructuredData();
+  dictionary = serialized->GetAsDictionary();
+  auto restored =
+      BreakpointResolver::CreateFromStructuredData(*dictionary, error);
+  ASSERT_NE(restored, nullptr);
+  auto reserialized = restored->SerializeToStructuredData();
+  ASSERT_TRUE(reserialized->GetAsDictionary()->GetValueForKeyAsDictionary(
+      "Options", options));
+  bool match_linkage_names = false;
+  EXPECT_TRUE(options->GetValueForKeyAsBoolean("MatchLinkageNames",
+                                               match_linkage_names));
+  EXPECT_TRUE(match_linkage_names);
+
+  options->AddStringItem("MatchLinkageNames", "invalid");
+  EXPECT_EQ(BreakpointResolverName::CreateFromStructuredData(*options, error),
+            nullptr);
+  EXPECT_TRUE(error.Fail());
+  options->AddBooleanItem("MatchLinkageNames", true);
+  options->AddStringItem("Regex", "runtime");
+  EXPECT_EQ(BreakpointResolverName::CreateFromStructuredData(*options, error),
+            nullptr);
+  EXPECT_TRUE(error.Fail());
+}



More information about the lldb-commits mailing list