[Lldb-commits] [lldb] [lldb] Opportunistically use `__clang_vtable` for robust dynamic type resolution (PR #210584)
via lldb-commits
lldb-commits at lists.llvm.org
Sun Jul 19 02:39:03 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: David Mentler (mentlerd)
<details>
<summary>Changes</summary>
`ItaniumABIRuntime::GetTypeInfo(...)`'s type resolution relies on the demangler to locate the type corresponding to a vtable address. As reported in #<!-- -->182762 this can break when the demangler and debug info don't agree on the spelling of the type's name.
This PR implements an opportunistic lookup based on a support member emitted by clang added in #<!-- -->130255 as `_vtable$`, later renamed to `__clang_vtable` in #<!-- -->183617. If it fails we fall back to the demangler route.
The initial implementation aims to only support the modern spelling with DWARF.
---
Full diff: https://github.com/llvm/llvm-project/pull/210584.diff
12 Files Affected:
- (modified) lldb/include/lldb/Symbol/SymbolFile.h (+6)
- (modified) lldb/include/lldb/Symbol/SymbolFileOnDemand.h (+4)
- (modified) lldb/include/lldb/Symbol/Variable.h (+2)
- (modified) lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABIRuntime.cpp (+58)
- (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp (+14)
- (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h (+2)
- (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp (+12)
- (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h (+2)
- (modified) lldb/source/Symbol/Variable.cpp (+7)
- (added) lldb/test/API/lang/cpp/dynamic-value-complex-typename/Makefile (+3)
- (added) lldb/test/API/lang/cpp/dynamic-value-complex-typename/TestDynamicValueComplexTypename.py (+35)
- (added) lldb/test/API/lang/cpp/dynamic-value-complex-typename/main.cpp (+34)
``````````diff
diff --git a/lldb/include/lldb/Symbol/SymbolFile.h b/lldb/include/lldb/Symbol/SymbolFile.h
index ae6504c016d7b..b49c5ae230062 100644
--- a/lldb/include/lldb/Symbol/SymbolFile.h
+++ b/lldb/include/lldb/Symbol/SymbolFile.h
@@ -537,6 +537,12 @@ class SymbolFile : public PluginInterface {
std::string GetObjectName() const;
+ /// Get the semantically innermost non-function type that encloses the
+ /// provided variable
+ virtual lldb::TypeSP GetTypeEnclosingVariableUID(lldb::user_id_t uid) {
+ return {};
+ }
+
protected:
void AssertModuleLock();
diff --git a/lldb/include/lldb/Symbol/SymbolFileOnDemand.h b/lldb/include/lldb/Symbol/SymbolFileOnDemand.h
index 46e33189d721b..9a2e0bc03f56f 100644
--- a/lldb/include/lldb/Symbol/SymbolFileOnDemand.h
+++ b/lldb/include/lldb/Symbol/SymbolFileOnDemand.h
@@ -253,6 +253,10 @@ class SymbolFileOnDemand : public lldb_private::SymbolFile {
return m_sym_file_impl->CopyType(other_type);
}
+ lldb::TypeSP GetTypeEnclosingVariableUID(lldb::user_id_t uid) override {
+ return m_sym_file_impl->GetTypeEnclosingVariableUID(uid);
+ }
+
private:
Log *GetLog() const { return ::lldb_private::GetLog(LLDBLog::OnDemand); }
diff --git a/lldb/include/lldb/Symbol/Variable.h b/lldb/include/lldb/Symbol/Variable.h
index 1e6a36d584212..97b6750797202 100644
--- a/lldb/include/lldb/Symbol/Variable.h
+++ b/lldb/include/lldb/Symbol/Variable.h
@@ -62,6 +62,8 @@ class Variable : public UserID, public std::enable_shared_from_this<Variable> {
Type *GetType();
+ lldb::TypeSP GetEnclosingType();
+
lldb::LanguageType GetLanguage() const;
lldb::ValueType GetScope() const { return m_scope; }
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABIRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABIRuntime.cpp
index 4d9cf31c8904d..de61e2a61102f 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABIRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABIRuntime.cpp
@@ -14,6 +14,9 @@
#include "lldb/Expression/FunctionCaller.h"
#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Symbol/VariableList.h"
+#include "lldb/ValueObject/ValueObjectVariable.h"
+
using namespace lldb;
using namespace lldb_private;
@@ -39,6 +42,61 @@ ItaniumABIRuntime::GetTypeInfo(ValueObject &in_value,
": static-type = '%s' has vtable symbol '%s'\n",
in_value.GetPointerValue().address,
in_value.GetTypeName().GetCString(), symbol_name.str().c_str());
+
+ // clang emits DW_TAG_variable '__clang_vtable' inside the enclosing
+ // class, try looking for that before falling back to name based type
+ // lookup
+ if (ModuleSP module_sp =
+ vtable_info.symbol->CalculateSymbolContextModule()) {
+ auto vptr = vtable_info.addr.GetLoadAddress(&m_process->GetTarget());
+
+ // NOTE: vptr points at the 'address point', not the vtable itself
+ auto vtable = vptr - m_process->GetAddressByteSize() * 2;
+
+ VariableList vars;
+
+ // SymbolFile doesn't provide an API to lookup a global variable with a
+ // specific location, so we have to resort to an unbounded name lookup
+ module_sp->FindGlobalVariables(ConstString("__clang_vtable"),
+ CompilerDeclContext(), -1, vars);
+
+ LLDB_LOGF(log,
+ "0x%16.16" PRIx64 ": found %zu __clang_vtable variables\n",
+ in_value.GetPointerValue().address, vars.GetSize());
+
+ for (lldb::VariableSP var : vars) {
+ auto valobj = ValueObjectVariable::Create(m_process, var);
+ if (valobj->GetLoadAddress() != vtable)
+ continue;
+
+ auto type_sp = var->GetEnclosingType();
+ if (!type_sp)
+ continue;
+
+ if (!TypeSystemClang::IsCXXClassType(
+ type_sp->GetForwardCompilerType()))
+ continue;
+
+ LLDB_LOGF(log,
+ "0x%16.16" PRIx64
+ ": static-type = '%s' has dynamic type: uid={0x%" PRIx64
+ "}, type-name='%s'\n",
+ in_value.GetPointerValue().address,
+ in_value.GetTypeName().AsCString(""), type_sp->GetID(),
+ type_sp->GetName().GetCString());
+
+ type_info.SetTypeSP(type_sp);
+
+ SetDynamicTypeInfo(vtable_info.addr, type_info);
+ return type_info;
+ }
+
+ LLDB_LOGF(log,
+ "0x%16.16" PRIx64 ": __clang_vtable based lookup failed, "
+ "falling back to demangled typename\n",
+ in_value.GetPointerValue().address);
+ }
+
// We are a C++ class, that's good. Get the class name and look it
// up:
llvm::StringRef class_name = symbol_name;
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 8075c8f61193c..bbc1668cfed36 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -2336,6 +2336,11 @@ void SymbolFileDWARF::FindGlobalVariables(
bool name_is_mangled = Mangled::GetManglingScheme(name.GetStringRef()) !=
Mangled::eManglingSchemeNone;
+ // Technically not a mangled name, but a support variable emitted by clang.
+ // Regardless, we need an exact lookup
+ if (name == "__clang_vtable")
+ name_is_mangled = true;
+
if (!CPlusPlusLanguage::ExtractContextAndIdentifier(name.GetStringRef(),
context, basename))
basename = name.GetStringRef();
@@ -4734,3 +4739,12 @@ DWOStats SymbolFileDWARF::GetDwoStats() {
return stats;
}
+
+lldb::TypeSP SymbolFileDWARF::GetTypeEnclosingVariableUID(lldb::user_id_t uid) {
+ DWARFDIE die = GetDIE(uid);
+
+ if (die.Tag() != DW_TAG_variable)
+ return nullptr;
+
+ return GetTypeForDIE(die.GetParentDeclContextDIE());
+}
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
index 21a190d1b38f0..a87428fb86743 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
@@ -378,6 +378,8 @@ class SymbolFileDWARF : public SymbolFileCommon {
/// Returns the DWARFIndex for this symbol, if it exists.
DWARFIndex *getIndex() { return m_index.get(); }
+ lldb::TypeSP GetTypeEnclosingVariableUID(lldb::user_id_t uid) override;
+
private:
/// Find the definition DIE for the specified \c label in this
/// SymbolFile.
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
index 219ab7bbf84cc..24aeae71e365e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.cpp
@@ -1642,6 +1642,18 @@ void SymbolFileDWARFDebugMap::GetCompileOptions(
});
}
+lldb::TypeSP
+SymbolFileDWARFDebugMap::GetTypeEnclosingVariableUID(lldb::user_id_t uid) {
+ lldb::TypeSP type;
+ ForEachSymbolFile("Looking up enclosing type for variable",
+ [&](SymbolFileDWARF &oso_dwarf) {
+ type = oso_dwarf.GetTypeEnclosingVariableUID(uid);
+ return type ? IterationAction::Stop
+ : IterationAction::Continue;
+ });
+ return type;
+}
+
llvm::Expected<SymbolContext>
SymbolFileDWARFDebugMap::ResolveFunctionCallLabel(FunctionCallLabel &label) {
const uint64_t oso_idx = GetOSOIndexFromUserID(label.symbol_id);
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
index 48b80974882b3..39141471cc40f 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDebugMap.h
@@ -147,6 +147,8 @@ class SymbolFileDWARFDebugMap : public SymbolFileCommon {
void
GetCompileOptions(std::unordered_map<lldb::CompUnitSP, Args> &args) override;
+ lldb::TypeSP GetTypeEnclosingVariableUID(lldb::user_id_t uid) override;
+
llvm::Expected<SymbolContext>
ResolveFunctionCallLabel(FunctionCallLabel &label) override;
diff --git a/lldb/source/Symbol/Variable.cpp b/lldb/source/Symbol/Variable.cpp
index 70a61fc7789c9..7bf7fcdd9c3b1 100644
--- a/lldb/source/Symbol/Variable.cpp
+++ b/lldb/source/Symbol/Variable.cpp
@@ -110,6 +110,13 @@ Type *Variable::GetType() {
return nullptr;
}
+lldb::TypeSP Variable::GetEnclosingType() {
+ Type *type = GetType();
+ if (type)
+ return type->GetSymbolFile()->GetTypeEnclosingVariableUID(GetID());
+ return nullptr;
+}
+
void Variable::Dump(Stream *s, bool show_context) const {
s->Printf("%p: ", static_cast<const void *>(this));
s->Indent();
diff --git a/lldb/test/API/lang/cpp/dynamic-value-complex-typename/Makefile b/lldb/test/API/lang/cpp/dynamic-value-complex-typename/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/lang/cpp/dynamic-value-complex-typename/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/lang/cpp/dynamic-value-complex-typename/TestDynamicValueComplexTypename.py b/lldb/test/API/lang/cpp/dynamic-value-complex-typename/TestDynamicValueComplexTypename.py
new file mode 100644
index 0000000000000..fedbb9dce27de
--- /dev/null
+++ b/lldb/test/API/lang/cpp/dynamic-value-complex-typename/TestDynamicValueComplexTypename.py
@@ -0,0 +1,35 @@
+import lldb
+import lldbsuite.test.lldbutil as lldbutil
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+
+
+class DynamicValueComplexTypename(TestBase):
+
+ @skipIf(compiler=no_match("clang"))
+ @skipIf(compiler="clang", compiler_version=["<", "24.0"]) # __clang_vtable
+ @expectedFailureAll(debug_info=no_match(["dwarf", "dsym"])) # Not implemented
+ @expectedFailureAll(oslist=["windows"]) # Not Itanium ABI
+ def test(self):
+ self.build()
+ lldbutil.run_to_source_breakpoint(
+ self, "break here", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.checkVarType("simple", "Simple")
+ self.checkVarType("templated", "Templated<")
+ self.checkVarType("complicated", "Complicated<")
+ self.checkVarType("evil", "Evil<")
+
+ def checkVarType(self, name, type_prefix):
+ var = self.frame().FindVariable(name)
+ self.assertTrue(var.IsValid())
+
+ # Check if we recognized the dynamic type. Most names are unreasonably
+ # complex to match against exactly, but all should have a sane prefix
+ self.assertStartsWith(var.GetType().GetName(), type_prefix)
+
+ # Check if we resolved the compiler type too
+ marker = var.Dereference().GetChildAtIndex(0)
+ self.assertEqual(marker.GetName(), f"is_{name}")
+ self.assertEqual(marker.GetValueAsUnsigned(), 1)
diff --git a/lldb/test/API/lang/cpp/dynamic-value-complex-typename/main.cpp b/lldb/test/API/lang/cpp/dynamic-value-complex-typename/main.cpp
new file mode 100644
index 0000000000000..840242391f84f
--- /dev/null
+++ b/lldb/test/API/lang/cpp/dynamic-value-complex-typename/main.cpp
@@ -0,0 +1,34 @@
+
+struct Interface {
+ virtual int Foo() { return 0; }
+};
+
+struct Simple : public Interface {
+ bool is_simple = true;
+};
+
+template <typename A, typename B> struct Templated : public Interface {
+ bool is_templated = true;
+};
+
+template <typename A, int B> struct Complicated : public Interface {
+ bool is_complicated = true;
+};
+
+template <typename A> struct Evil : public Interface {
+ bool is_evil = true;
+};
+
+namespace {
+template <typename A, typename B> struct Pair {};
+} // namespace
+
+int main() {
+ auto lambda = [] {};
+
+ Interface *simple = new Simple();
+ Interface *templated = new Templated<int, double>();
+ Interface *complicated = new Complicated<Pair<Interface, int>, 42>();
+ Interface *evil = new Evil<decltype(lambda)>();
+ return 0; // break here
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/210584
More information about the lldb-commits
mailing list