[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:58:36 PDT 2026
https://github.com/karim-alweheshy updated https://github.com/llvm/llvm-project/pull/232694
>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 1/2] [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 ®ex, 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 ®ex,
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 ®ex,
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());
+}
>From fd29549179b2985f65c0ec77b10552bf11827aca Mon Sep 17 00:00:00 2001
From: Karim Alweheshy <karim.alweheshy at reddit.com>
Date: Fri, 16 Oct 2026 13:56:51 +0200
Subject: [PATCH 2/2] [lldb] Register the Bounds runtime fixture shared library
Supply the harness library search environment when launching, so Linux can load Unrelated before reaching main.
Assisted-by: OpenAI Codex
---
.../runtime_detection/TestBoundsSafetyRuntimeDetection.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py b/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py
index b5ebaf98460d6d..45a343659fe58d 100644
--- a/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py
+++ b/lldb/test/API/lang/BoundsSafety/runtime_detection/TestBoundsSafetyRuntimeDetection.py
@@ -69,8 +69,9 @@ def start(
if late
else None
)
+ environment = self.registerSharedLibrariesWithTarget(target, ["Unrelated"])
process = target.LaunchSimple(
- arguments, None, self.get_process_working_directory()
+ arguments, environment, self.get_process_working_directory()
)
self.assertState(process.GetState(), lldb.eStateStopped)
if runtime_location is not None:
More information about the lldb-commits
mailing list