[Lldb-commits] [lldb] [LLDB] FindLibCppStdFunctionCallableInfo improvements (PR #111892)
David Mentler via lldb-commits
lldb-commits at lists.llvm.org
Fri Oct 11 14:45:18 PDT 2024
https://github.com/mentlerd updated https://github.com/llvm/llvm-project/pull/111892
>From b1e6178b1130135262884d99262716fcc0ada86e Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Mon, 7 Oct 2024 21:46:50 +0200
Subject: [PATCH 1/8] Make existing tests break
---
.../data-formatter-stl/libcxx/function/main.cpp | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp
index ef7c97470652fc..b17aba3fbbc70e 100644
--- a/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp
+++ b/lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/function/main.cpp
@@ -1,5 +1,10 @@
#include <functional>
+template<typename = bool, typename = int>
+struct Dummy {
+ // Used to make lambda host function's symbol more complex
+};
+
int foo(int x, int y) {
return x + y - 1;
}
@@ -18,7 +23,7 @@ struct Bar {
}
} ;
-int foo2() {
+int foo2(Dummy<> dummy = {}) {
auto f = [](int x) {
return x+1;
};
>From d976756f47781d58c0367835a56b5b8e9130191b Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Wed, 9 Oct 2024 20:40:03 +0200
Subject: [PATCH 2/8] Expose conversion from DeclContext to Decl
---
lldb/include/lldb/Symbol/CompilerDeclContext.h | 2 ++
lldb/include/lldb/Symbol/TypeSystem.h | 4 ++++
lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp | 7 +++++++
lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h | 2 ++
lldb/source/Symbol/CompilerDeclContext.cpp | 6 ++++++
5 files changed, 21 insertions(+)
diff --git a/lldb/include/lldb/Symbol/CompilerDeclContext.h b/lldb/include/lldb/Symbol/CompilerDeclContext.h
index 89b4a9787688bc..3954ddd8e52087 100644
--- a/lldb/include/lldb/Symbol/CompilerDeclContext.h
+++ b/lldb/include/lldb/Symbol/CompilerDeclContext.h
@@ -110,6 +110,8 @@ class CompilerDeclContext {
ConstString GetScopeQualifiedName() const;
+ CompilerDecl GetDecl() const;
+
private:
TypeSystem *m_type_system = nullptr;
void *m_opaque_decl_ctx = nullptr;
diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h
index 7d48f9b316138c..4b531a83dd5fbb 100644
--- a/lldb/include/lldb/Symbol/TypeSystem.h
+++ b/lldb/include/lldb/Symbol/TypeSystem.h
@@ -134,6 +134,10 @@ class TypeSystem : public PluginInterface,
virtual lldb::LanguageType DeclContextGetLanguage(void *opaque_decl_ctx) = 0;
+ virtual CompilerDecl DeclContextGetDecl(void *opaque_decl_ctx) {
+ return CompilerDecl();
+ }
+
/// Returns the direct parent context of specified type
virtual CompilerDeclContext
GetCompilerDeclContextForType(const CompilerType &type);
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index fe0c53a7e9a3ea..678ac8d65331b7 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -9595,6 +9595,13 @@ TypeSystemClang::DeclContextGetLanguage(void *opaque_decl_ctx) {
return eLanguageTypeUnknown;
}
+CompilerDecl TypeSystemClang::DeclContextGetDecl(void *opaque_decl_ctx) {
+ if (auto *decl_ctx = (clang::DeclContext *)opaque_decl_ctx)
+ if (auto* decl = dyn_cast_or_null<clang::Decl>(decl_ctx))
+ return CompilerDecl(this, decl);
+ return CompilerDecl();
+}
+
static bool IsClangDeclContext(const CompilerDeclContext &dc) {
return dc.IsValid() && isa<TypeSystemClang>(dc.GetTypeSystem());
}
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
index e39aedec7e3902..e7ac6e320f20b7 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
@@ -595,6 +595,8 @@ class TypeSystemClang : public TypeSystem {
lldb::LanguageType DeclContextGetLanguage(void *opaque_decl_ctx) override;
+ CompilerDecl DeclContextGetDecl(void *opaque_decl_ctx) override;
+
std::vector<lldb_private::CompilerContext>
DeclContextGetCompilerContext(void *opaque_decl_ctx) override;
diff --git a/lldb/source/Symbol/CompilerDeclContext.cpp b/lldb/source/Symbol/CompilerDeclContext.cpp
index b40a08e9b1953d..7a72c4301b0b5c 100644
--- a/lldb/source/Symbol/CompilerDeclContext.cpp
+++ b/lldb/source/Symbol/CompilerDeclContext.cpp
@@ -34,6 +34,12 @@ ConstString CompilerDeclContext::GetScopeQualifiedName() const {
return ConstString();
}
+CompilerDecl CompilerDeclContext::GetDecl() const {
+ if (IsValid())
+ return m_type_system->DeclContextGetDecl(m_opaque_decl_ctx);
+ return CompilerDecl();
+}
+
bool CompilerDeclContext::IsClassMethod() {
if (IsValid())
return m_type_system->DeclContextIsClassMethod(m_opaque_decl_ctx);
>From 2e75bb0aac608d626fba5878315e9118bfcf9c8a Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Wed, 9 Oct 2024 20:41:04 +0200
Subject: [PATCH 3/8] IsPolymorphicClass already checks for record types and
handles pointers to typenames
---
.../CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp | 8 --------
1 file changed, 8 deletions(-)
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
index 4c547afe30fe81..4ac1e565cb2619 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp
@@ -191,14 +191,6 @@ llvm::Error ItaniumABILanguageRuntime::TypeHasVTable(CompilerType type) {
type = pointee_type;
}
- // Make sure this is a class or a struct first by checking the type class
- // bitfield that gets returned.
- if ((type.GetTypeClass() & (eTypeClassStruct | eTypeClassClass)) == 0) {
- return llvm::createStringError(std::errc::invalid_argument,
- "type \"%s\" is not a class or struct or a pointer to one",
- original_type.GetTypeName().AsCString("<invalid>"));
- }
-
// Check if the type has virtual functions by asking it if it is polymorphic.
if (!type.IsPolymorphicClass()) {
return llvm::createStringError(std::errc::invalid_argument,
>From 248be8c68221edc03ee0691b3b2c83eda0789324 Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Wed, 9 Oct 2024 22:23:48 +0200
Subject: [PATCH 4/8] Digging for wrapped callable works
---
.../CPlusPlus/CPPLanguageRuntime.cpp | 132 +++++++++++++++++-
1 file changed, 131 insertions(+), 1 deletion(-)
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index faa05e8f834ea1..7167cd4dc75f90 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -174,6 +174,137 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
if (!valobj_sp)
return optional_info;
+ // std::function has many variants, try to disambiguate
+ ValueObjectSP func_as_base_ptr;
+ {
+ ValueObjectSP outer_f = valobj_sp->GetChildMemberWithName("__f_");
+
+ if (!outer_f)
+ return optional_info; // Unrecognized implementation
+
+ if (outer_f->IsPointerType()) {
+ // git: 3e519524c118651123eecf60c2bbc5d65ad9bac3
+ //
+ // class function<_Rp()> {
+ // aligned_storage<3*sizeof(void*)>::type __buf_;
+ // __base<_Rp>* __f_;
+ // }
+
+ func_as_base_ptr = std::move(outer_f);
+ } else if (auto inner_f = outer_f->GetChildMemberWithName("__f_")) {
+ // git: 050b064f15ee56ee0b42c9b957a3dd0f32532394
+ //
+ // class function<_Rp(_ArgTypes...)> {
+ // __value_func<_Rp(_ArgTypes...)> __f_;
+ // }
+ //
+ // class __value_func<_Rp(_ArgTypes...)> {
+ // aligned_storage<3 * sizeof(void*)>::type __buf_;
+ // __base<_Rp(_ArgTypes...)>* __f_;
+ // }
+
+ func_as_base_ptr = std::move(inner_f);
+ } else
+ return optional_info; // Unrecognized implementation
+ }
+
+ // __base<...> is a pure virtual class with an interface to create/copy/destroy/invoke
+ // the underlying value. This interface is implemented by partial specializations of the
+ // __func<_Fp, _Alloc, ...> template where _Fp is the wrapped functor object
+ Status status;
+ ValueObjectSP func_as_base = func_as_base_ptr->Dereference(status);
+ if (status.Fail())
+ return optional_info;
+
+ // First we'll try to extract the __func<...> template instantiation's type by looking up
+ // the declarations of the member function pointers in it's vtable
+ CompilerType func_type;
+ {
+ ValueObjectSP vtable = func_as_base->GetVTable();
+
+ llvm::Expected<uint32_t> num_entries = vtable->GetNumChildren();
+ if (num_entries.takeError())
+ return optional_info;
+
+ // __base is pure virtual, __func is final. All member function pointers are equally
+ // good candidates to find the enclosing class.
+ //
+ // In practice the first two vtable entries point to artificial destructors which the
+ // type system refuses to elaborate as their artificial specifications are not added
+ // to the enclosing class' declaration context. This causes various warnings, and dont
+ // get us any closer to the concrete type thus we skip them.
+ for (uint32_t idx = 2; idx < *num_entries; idx++) {
+ ValueObjectSP entry = vtable->GetChildAtIndex(idx);
+
+ // Points to a potentially interesting member function
+ addr_t mfunc_load_addr = entry->GetValueAsUnsigned(0);
+ if (!mfunc_load_addr)
+ continue;
+
+ Address mfunc_symbol_addr;
+ if (!valobj_sp->GetTargetSP()->ResolveLoadAddress(mfunc_load_addr, mfunc_symbol_addr))
+ continue;
+
+ Function* func = mfunc_symbol_addr.CalculateSymbolContextFunction();
+ if (!func)
+ continue;
+
+ CompilerDeclContext mfunc_decl_ctx = func->GetDeclContext();
+ if (!mfunc_decl_ctx.IsClassMethod())
+ continue;
+
+ // Member functions are contained in their enclosing class' decl context
+ CompilerDeclContext mfunc_parent = mfunc_decl_ctx.GetDecl().GetDeclContext();
+ if (!mfunc_parent.IsValid())
+ continue;
+
+ func_type = mfunc_parent.GetDecl().GetType();
+ break;
+ }
+ }
+
+ // Regardless of what std::function wraps we are looking for the load address of a function to call
+ std::optional<addr_t> target_func_load_addr;
+
+ if (CompilerType callable_type = func_type.GetTypeTemplateArgument(0)) {
+ if (callable_type.IsFunctionPointerType() || callable_type.IsMemberFunctionPointerType()) {
+ // TODO: The previous implementation just does raw pointer arithmetic and reads
+ // 'a pointer' to a function right after the vtable.
+ //
+ // What is the preferred approach? Go digging for the compressed_pair.first in __func
+ // or assume layout citing ABI compatibility requirements?
+ } else if (callable_type.IsRecordType()) {
+ // Target is a lambda, or a generic callable. Search for a single operator() overload
+ std::optional<ConstString> mangled_func_name;
+
+ for (uint32_t idx = 0; idx < callable_type.GetNumMemberFunctions(); idx++) {
+ TypeMemberFunctionImpl mfunc = callable_type.GetMemberFunctionAtIndex(idx);
+
+ if (mfunc.GetKind() != eMemberFunctionKindInstanceMethod)
+ continue;
+
+ if (mfunc.GetName() != "operator()")
+ continue;
+
+ if (mangled_func_name)
+ return optional_info; // Cannot resolve ambiguous target
+
+ mangled_func_name = mfunc.GetMangledName();
+ }
+
+ // TODO: The SymbolFile did a bunch of work to reconstruct `callable_type`,
+ // including it's member functions. Surely it knows there they are loaded?
+ }
+ } else {
+ // TODO: What if we don't have debug info for callable_type? Do we fallback to
+ // treating the std::function as wrapping a function/member function pointer
+ // due to lack of options, or give up to avoid guessing wrong?
+ }
+
+ if (!target_func_load_addr)
+ return optional_info;
+
+
// Member __f_ has type __base*, the contents of which will hold:
// 1) a vtable entry which may hold type information needed to discover the
// lambda being called
@@ -232,7 +363,6 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
return optional_info;
uint32_t address_size = process->GetAddressByteSize();
- Status status;
// First item pointed to by __f_ should be the pointer to the vtable for
// a __base object.
>From e7612f5c6417ababebfb7509d1dba697f07250db Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Thu, 10 Oct 2024 23:08:01 +0200
Subject: [PATCH 5/8] RecordType callable identification works, more questions
---
.../CPlusPlus/CPPLanguageRuntime.cpp | 113 ++++++++++++------
1 file changed, 76 insertions(+), 37 deletions(-)
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index 7167cd4dc75f90..a3c719528e4e4b 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -219,6 +219,7 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
// First we'll try to extract the __func<...> template instantiation's type by looking up
// the declarations of the member function pointers in it's vtable
CompilerType func_type;
+ Address func_method_addr;
{
ValueObjectSP vtable = func_as_base->GetVTable();
@@ -241,11 +242,10 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
if (!mfunc_load_addr)
continue;
- Address mfunc_symbol_addr;
- if (!valobj_sp->GetTargetSP()->ResolveLoadAddress(mfunc_load_addr, mfunc_symbol_addr))
+ if (!valobj_sp->GetTargetSP()->ResolveLoadAddress(mfunc_load_addr, func_method_addr))
continue;
- Function* func = mfunc_symbol_addr.CalculateSymbolContextFunction();
+ Function* func = func_method_addr.CalculateSymbolContextFunction();
if (!func)
continue;
@@ -263,47 +263,86 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
}
}
- // Regardless of what std::function wraps we are looking for the load address of a function to call
- std::optional<addr_t> target_func_load_addr;
-
- if (CompilerType callable_type = func_type.GetTypeTemplateArgument(0)) {
- if (callable_type.IsFunctionPointerType() || callable_type.IsMemberFunctionPointerType()) {
- // TODO: The previous implementation just does raw pointer arithmetic and reads
- // 'a pointer' to a function right after the vtable.
- //
- // What is the preferred approach? Go digging for the compressed_pair.first in __func
- // or assume layout citing ABI compatibility requirements?
- } else if (callable_type.IsRecordType()) {
- // Target is a lambda, or a generic callable. Search for a single operator() overload
- std::optional<ConstString> mangled_func_name;
+ CompilerType callable_type = func_type.GetTypeTemplateArgument(0);
+ if (!callable_type)
+ return optional_info;
+
+ if (callable_type.IsFunctionPointerType() || callable_type.IsMemberFunctionPointerType()) {
+ // TODO: The previous implementation just does raw pointer arithmetic and reads
+ // 'a pointer' to a function right after the vtable.
+ //
+ // What is the preferred approach? Go digging for the compressed_pair.first in __func
+ // or assume layout citing ABI compatibility requirements?
+ } else if (callable_type.IsRecordType()) {
+ // Target is a lambda, or a generic callable. Search for a single operator() overload
+ std::optional<ConstString> mangled_func_name;
+
+ // TODO: I am still not sure whether it is a good idea to reconstruct the full type
+ // here.. it seems there are handy FindFunctions that could perhaps to a good job
+ // at locating candidates. However even when limiting the search to the decl_ctx of
+ // the class the code seems to iterate over way more DIEs than I expected. What to do?
+
+ // TODO: Because we have access to the type we know a _lot_ about callable_type, we
+ // could even extract a ValueObjectSP to it if we wanted. It would be cool to make
+ // std::function have a synt children provider showing the wrapped lambda/callable!
+
+ for (uint32_t idx = 0; idx < callable_type.GetNumMemberFunctions(); idx++) {
+ TypeMemberFunctionImpl mfunc = callable_type.GetMemberFunctionAtIndex(idx);
- for (uint32_t idx = 0; idx < callable_type.GetNumMemberFunctions(); idx++) {
- TypeMemberFunctionImpl mfunc = callable_type.GetMemberFunctionAtIndex(idx);
-
- if (mfunc.GetKind() != eMemberFunctionKindInstanceMethod)
- continue;
-
- if (mfunc.GetName() != "operator()")
- continue;
-
- if (mangled_func_name)
- return optional_info; // Cannot resolve ambiguous target
+ if (mfunc.GetKind() != eMemberFunctionKindInstanceMethod)
+ continue;
+
+ if (mfunc.GetName() != "operator()")
+ continue;
- mangled_func_name = mfunc.GetMangledName();
- }
+ if (mangled_func_name)
+ return optional_info; // Cannot resolve ambiguous target
+
+ mangled_func_name = mfunc.GetMangledName();
+ }
+
+ // Locate the symbol context corresponding to the target function
+ SymbolContext sc;
+ {
+ // We'll assume that callable_type is in the same module as the vtable
+ ModuleSP mod = func_method_addr.CalculateSymbolContextModule();
+
+ // Limit our lookup to callable_type
+ CompilerDeclContext decl_ctx = callable_type.GetTypeSystem()->GetCompilerDeclContextForType(callable_type);
- // TODO: The SymbolFile did a bunch of work to reconstruct `callable_type`,
- // including it's member functions. Surely it knows there they are loaded?
+ SymbolContextList list;
+ mod->FindFunctions(*mangled_func_name, decl_ctx, eFunctionNameTypeFull, {}, list);
+
+ if (list.GetSize() != 1)
+ return optional_info;
+
+ list.GetContextAtIndex(0, sc);
}
- } else {
- // TODO: What if we don't have debug info for callable_type? Do we fallback to
- // treating the std::function as wrapping a function/member function pointer
- // due to lack of options, or give up to avoid guessing wrong?
+
+ // TODO: This feels a bit clunky, I am probably misusing the API? FindFunctions returns me
+ // SymbolContexts with the .function set but not .symbol ... At first glance it seemed like
+ // if we know the function there must be a symbol too!
+ if (!sc.function)
+ return optional_info;
+
+ Symbol* symbol = sc.function->GetAddressRange().GetBaseAddress().CalculateSymbolContextSymbol();
+ if (!symbol)
+ return optional_info;
+
+ return LibCppStdFunctionCallableInfo {
+ .callable_symbol = *symbol,
+ .callable_address = symbol->GetAddress(),
+ .callable_line_entry = sc.GetFunctionStartLineEntry(),
+
+ // TODO: Can't tell lambdas apart from generic callables.. do we really need to?
+ // Is it important to have the correct qualification in the summary?
+ .callable_case = LibCppStdFunctionCallableCase::Lambda
+ };
}
- if (!target_func_load_addr)
+ // Unrecognized callable type - skip the original implementation for now
+ if (!callable_type.IsVoidType())
return optional_info;
-
// Member __f_ has type __base*, the contents of which will hold:
// 1) a vtable entry which may hold type information needed to discover the
>From a6b489d95cf4be1e779c7aa5092f4e2dd75f239d Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Thu, 10 Oct 2024 23:48:23 +0200
Subject: [PATCH 6/8] Accessing template type arguments fully completes __func,
enumerating members is free
---
.../Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp | 5 -----
1 file changed, 5 deletions(-)
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index a3c719528e4e4b..019f4fa67e1722 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -277,11 +277,6 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
// Target is a lambda, or a generic callable. Search for a single operator() overload
std::optional<ConstString> mangled_func_name;
- // TODO: I am still not sure whether it is a good idea to reconstruct the full type
- // here.. it seems there are handy FindFunctions that could perhaps to a good job
- // at locating candidates. However even when limiting the search to the decl_ctx of
- // the class the code seems to iterate over way more DIEs than I expected. What to do?
-
// TODO: Because we have access to the type we know a _lot_ about callable_type, we
// could even extract a ValueObjectSP to it if we wanted. It would be cool to make
// std::function have a synt children provider showing the wrapped lambda/callable!
>From 2cce9366dc5e15e41539a198e472846e012118f5 Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Fri, 11 Oct 2024 00:58:39 +0200
Subject: [PATCH 7/8] Extract callable ValueObject, symbolicate raw function
pointers
---
.../CPlusPlus/CPPLanguageRuntime.cpp | 80 ++++++++++++++++---
1 file changed, 71 insertions(+), 9 deletions(-)
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index 019f4fa67e1722..1837fb79f6c5d3 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -267,20 +267,82 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
if (!callable_type)
return optional_info;
- if (callable_type.IsFunctionPointerType() || callable_type.IsMemberFunctionPointerType()) {
- // TODO: The previous implementation just does raw pointer arithmetic and reads
- // 'a pointer' to a function right after the vtable.
+ // Now that the __func is a known type we can dig for the wrapped callable
+ ValueObjectSP callable;
+ {
+ // class __func<_Fp, _Alloc, _Rp(_ArgTypes...)> : __base<_Rp(_ArgTypes...)> {
+ // __alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> __f_;
+ // }
+ //
+ // class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)> {
+ // __compressed_pair<_Fp, _Ap> __f_;
+ // }
//
- // What is the preferred approach? Go digging for the compressed_pair.first in __func
- // or assume layout citing ABI compatibility requirements?
+ // class __compressed_pair : __compressed_pair_elem<_T1, 0>,
+ // __compressed_pair_elem<_T2, 1> {
+ // }
+ //
+ // struct __compressed_pair_elem {
+ // _Tp __value_;
+ // }
+ ValueObjectSP alloc_func = func_as_base->Cast(func_type);
+ if (!alloc_func)
+ return optional_info;
+
+ ValueObjectSP pair = alloc_func->GetChildAtNamePath({"__f_", "__f_"});
+ if (!pair)
+ return optional_info;
+
+ if (callable_type.IsRecordType() && callable_type.GetNumFields() == 0) {
+ // callable_type is an empty class, and has been optimized away! Serve a dummy
+ callable = valobj_sp->CreateValueObjectFromAddress("__value_",
+ pair->GetLoadAddress(),
+ pair->GetExecutionContextRef(),
+ callable_type);
+ } else {
+ ValueObjectSP elem0 = pair->GetChildAtIndex(0);
+ if (!elem0)
+ return optional_info;
+
+ callable = elem0->GetChildMemberWithName("__value_");
+ if (!callable)
+ return optional_info;
+ }
+ }
+
+ if (callable_type.IsFunctionPointerType()) {
+ addr_t target_load_addr = callable->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
+
+ ModuleSP mod = func_method_addr.CalculateSymbolContextModule();
+
+ Address callable_addr;
+ if (!mod->ResolveFileAddress(target_load_addr, callable_addr))
+ return optional_info;
+
+ SymbolContext sc;
+ mod->ResolveSymbolContextForAddress(callable_addr, eSymbolContextSymbol | eSymbolContextLineEntry, sc);
+
+ if (!sc.symbol)
+ return optional_info;
+
+ return LibCppStdFunctionCallableInfo {
+ .callable_symbol = *sc.symbol,
+ .callable_address = sc.symbol->GetAddress(),
+ .callable_line_entry = sc.line_entry,
+ .callable_case = LibCppStdFunctionCallableCase::FreeOrMemberFunction
+ };
+ } else if (callable_type.IsMemberFunctionPointerType()) {
+ // TODO: Member function's unsigned value comes back as invalid! I am guessing
+ // the ValueObject wants to let me know that this is not necessarily as simple
+ // as that.. I remember reading something in the Itanium ABI about member
+ // pointers taking up two pointers of space. Perhaps that's why it is not legal
+ // to read their underlying value raw?
+
+ printf("Curious!");
} else if (callable_type.IsRecordType()) {
// Target is a lambda, or a generic callable. Search for a single operator() overload
std::optional<ConstString> mangled_func_name;
- // TODO: Because we have access to the type we know a _lot_ about callable_type, we
- // could even extract a ValueObjectSP to it if we wanted. It would be cool to make
- // std::function have a synt children provider showing the wrapped lambda/callable!
-
for (uint32_t idx = 0; idx < callable_type.GetNumMemberFunctions(); idx++) {
TypeMemberFunctionImpl mfunc = callable_type.GetMemberFunctionAtIndex(idx);
>From a26c7221c943d102741da9c89871bd02ea888cbd Mon Sep 17 00:00:00 2001
From: David Mentler <david.mentler at shapr3d.com>
Date: Fri, 11 Oct 2024 22:59:52 +0200
Subject: [PATCH 8/8] Cleanup pass 1
---
.../CPlusPlus/CPPLanguageRuntime.cpp | 594 ++++++------------
.../CPlusPlus/CPPLanguageRuntime.h | 15 +-
2 files changed, 207 insertions(+), 402 deletions(-)
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
index 1837fb79f6c5d3..390efda6271443 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp
@@ -125,115 +125,104 @@ CPPLanguageRuntime::GetObjectDescription(Stream &str, Value &value,
return llvm::createStringError("C++ does not support object descriptions");
}
-bool contains_lambda_identifier(llvm::StringRef &str_ref) {
- return str_ref.contains("$_") || str_ref.contains("'lambda'");
-}
-
-CPPLanguageRuntime::LibCppStdFunctionCallableInfo
-line_entry_helper(Target &target, const SymbolContext &sc, Symbol *symbol,
- llvm::StringRef first_template_param_sref, bool has_invoke) {
-
- CPPLanguageRuntime::LibCppStdFunctionCallableInfo optional_info;
-
- AddressRange range;
- sc.GetAddressRange(eSymbolContextEverything, 0, false, range);
-
- Address address = range.GetBaseAddress();
-
- Address addr;
- if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target),
- addr)) {
- LineEntry line_entry;
- addr.CalculateSymbolContextLineEntry(line_entry);
-
- if (contains_lambda_identifier(first_template_param_sref) || has_invoke) {
- // Case 1 and 2
- optional_info.callable_case = lldb_private::CPPLanguageRuntime::
- LibCppStdFunctionCallableCase::Lambda;
- } else {
- // Case 3
- optional_info.callable_case = lldb_private::CPPLanguageRuntime::
- LibCppStdFunctionCallableCase::CallableObject;
- }
-
- optional_info.callable_symbol = *symbol;
- optional_info.callable_line_entry = line_entry;
- optional_info.callable_address = addr;
- }
-
- return optional_info;
-}
-
-CPPLanguageRuntime::LibCppStdFunctionCallableInfo
-CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
- lldb::ValueObjectSP &valobj_sp) {
- LLDB_SCOPED_TIMER();
-
- LibCppStdFunctionCallableInfo optional_info;
-
+llvm::Expected<CPPLanguageRuntime::UnwrappedLibCppFunction>
+CPPLanguageRuntime::UnwrapLibCppFunction(lldb::ValueObjectSP &valobj_sp) {
if (!valobj_sp)
- return optional_info;
-
- // std::function has many variants, try to disambiguate
- ValueObjectSP func_as_base_ptr;
+ return llvm::createStringError("valobj in invalid state");
+
+ // std::function has many variants, try to disambiguate going from most
+ // recent to oldest known implementation
+ ValueObjectSP base_ptr;
{
ValueObjectSP outer_f = valobj_sp->GetChildMemberWithName("__f_");
-
- if (!outer_f)
- return optional_info; // Unrecognized implementation
-
- if (outer_f->IsPointerType()) {
- // git: 3e519524c118651123eecf60c2bbc5d65ad9bac3
- //
- // class function<_Rp()> {
- // aligned_storage<3*sizeof(void*)>::type __buf_;
- // __base<_Rp>* __f_;
- // }
-
- func_as_base_ptr = std::move(outer_f);
- } else if (auto inner_f = outer_f->GetChildMemberWithName("__f_")) {
- // git: 050b064f15ee56ee0b42c9b957a3dd0f32532394
+
+ if (!base_ptr) {
+ // git: 050b064f15ee56ee0b42c9b957a3dd0f32532394 __functional/function.h
//
+ // template<class _Rp, class ..._ArgTypes>
// class function<_Rp(_ArgTypes...)> {
- // __value_func<_Rp(_ArgTypes...)> __f_;
+ // #ifndef _LIBCPP_ABI_OPTIMIZED_FUNCTION
+ // typedef __function::__value_func<_Rp(_ArgTypes...)> __func;
+ // #else
+ // typedef __function::__policy_func<_Rp(_ArgTypes...)> __func;
+ // #endif
+ //
+ // __func __f_;
// }
//
+ // template <class _Fp> class __value_func;
+ // template <class _Fp> class __policy_func;
+ //
+ // template <class _Rp, class... _ArgTypes>
// class __value_func<_Rp(_ArgTypes...)> {
- // aligned_storage<3 * sizeof(void*)>::type __buf_;
- // __base<_Rp(_ArgTypes...)>* __f_;
+ // typename aligned_storage<3 * sizeof(void*)>::type __buf_;
+ //
+ // typedef __base<_Rp(_ArgTypes...)> __func;
+ // __func* __f_;
// }
-
- func_as_base_ptr = std::move(inner_f);
- } else
- return optional_info; // Unrecognized implementation
+ //
+ // template <class _Rp, class... _ArgTypes>
+ // class __policy_func<_Rp(_ArgTypes...)> {
+ // __policy_storage __buf_;
+ //
+ // typedef __function::__policy_invoker<_Rp(_ArgTypes...)> __invoker;
+ // __invoker __invoker_;
+ //
+ // const __policy* __policy_;
+ // }
+ if (auto outer_f = valobj_sp->GetChildMemberWithName("__f_")) {
+ if (auto inner_f = outer_f->GetChildMemberWithName("__f_"))
+ base_ptr = std::move(inner_f);
+ } else if (valobj_sp->GetChildMemberWithName("__invoker_"))
+ return llvm::createStringError("__policy_func implementation is "
+ "not supported");
+ }
+
+ if (!base_ptr) {
+ // git: 3e519524c118651123eecf60c2bbc5d65ad9bac3 include/__functional_03
+ //
+ // class function<_Rp()> {
+ // aligned_storage<3*sizeof(void*)>::type __buf_;
+ // __base<_Rp>* __f_;
+ // }
+ if (auto outer_f = valobj_sp->GetChildMemberWithName("__f_"))
+ base_ptr = std::move(outer_f);
+ }
+
+ if (!base_ptr)
+ return llvm::createStringError("unrecognized implementation");
}
-
- // __base<...> is a pure virtual class with an interface to create/copy/destroy/invoke
- // the underlying value. This interface is implemented by partial specializations of the
- // __func<_Fp, _Alloc, ...> template where _Fp is the wrapped functor object
+
+ // __base<...> is a pure virtual class with an interface to manage the
+ // the wrapped value. This interface is implemented by partial specializations
+ // of the __func<_Fp, _Alloc, ...> template where _Fp is the wrapped callable
+ //
+ // We'll try to extract the concrete __func type pointed to by base_ptr by
+ // analysing it's vtable.
Status status;
- ValueObjectSP func_as_base = func_as_base_ptr->Dereference(status);
+ ValueObjectSP base = base_ptr->Dereference(status);
if (status.Fail())
- return optional_info;
+ return llvm::createStringError("failed to dereference __base pointer");
- // First we'll try to extract the __func<...> template instantiation's type by looking up
- // the declarations of the member function pointers in it's vtable
+ Address virtual_method_addr;
CompilerType func_type;
- Address func_method_addr;
{
- ValueObjectSP vtable = func_as_base->GetVTable();
+ ValueObjectSP vtable = base->GetVTable();
llvm::Expected<uint32_t> num_entries = vtable->GetNumChildren();
- if (num_entries.takeError())
- return optional_info;
+ if (auto error = num_entries.takeError())
+ return error;
- // __base is pure virtual, __func is final. All member function pointers are equally
- // good candidates to find the enclosing class.
+ // __base is pure virtual, __func is final. All member function pointers
+ // are equally good candidates to find the enclosing class.
+ //
+ // In practice the first two vtable entries point to artificial destructors
+ // which the type system refuses to elaborate as their artificial
+ // specifications are not added to the enclosing class' declaration
+ // context.
//
- // In practice the first two vtable entries point to artificial destructors which the
- // type system refuses to elaborate as their artificial specifications are not added
- // to the enclosing class' declaration context. This causes various warnings, and dont
- // get us any closer to the concrete type thus we skip them.
+ // This causes various warnings, and don't get us any closer to the
+ // concrete type thus we skip them.
for (uint32_t idx = 2; idx < *num_entries; idx++) {
ValueObjectSP entry = vtable->GetChildAtIndex(idx);
@@ -242,86 +231,132 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
if (!mfunc_load_addr)
continue;
- if (!valobj_sp->GetTargetSP()->ResolveLoadAddress(mfunc_load_addr, func_method_addr))
+ if (!valobj_sp->GetTargetSP()->ResolveLoadAddress(mfunc_load_addr,
+ virtual_method_addr))
continue;
- Function* func = func_method_addr.CalculateSymbolContextFunction();
+ Function* func = virtual_method_addr.CalculateSymbolContextFunction();
if (!func)
continue;
- CompilerDeclContext mfunc_decl_ctx = func->GetDeclContext();
- if (!mfunc_decl_ctx.IsClassMethod())
+ CompilerDeclContext decl_ctx = func->GetDeclContext();
+ if (!decl_ctx.IsClassMethod())
continue;
// Member functions are contained in their enclosing class' decl context
- CompilerDeclContext mfunc_parent = mfunc_decl_ctx.GetDecl().GetDeclContext();
- if (!mfunc_parent.IsValid())
+ CompilerDeclContext enclosing_class = decl_ctx.GetDecl().GetDeclContext();
+ if (!enclosing_class.IsValid())
continue;
- func_type = mfunc_parent.GetDecl().GetType();
+ func_type = enclosing_class.GetDecl().GetType();
break;
}
}
+ if (!func_type)
+ return llvm::createStringError("failed to find suitable virtual function "
+ "to determine __func type");
+
+ ValueObjectSP func = base->Cast(func_type);
+ if (!func)
+ return llvm::createStringError("failed to cast __base to __func type");
+
+ // Now that the __func is a known type we can dig for the wrapped callable
CompilerType callable_type = func_type.GetTypeTemplateArgument(0);
if (!callable_type)
+ return llvm::createStringError("failed to get wrapped callable type from "
+ "first template parameter");
+
+ // git: 050b064f15ee56ee0b42c9b957a3dd0f32532394
+ //
+ // class __func<_Fp, _Alloc, _Rp(_ArgTypes...)>
+ // : __base<_Rp(_ArgTypes...)> {
+ // __alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> __f_;
+ // }
+ //
+ // class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)> {
+ // __compressed_pair<_Fp, _Ap> __f_;
+ // }
+ //
+ // class __compressed_pair : __compressed_pair_elem<_T1, 0>,
+ // __compressed_pair_elem<_T2, 1> {
+ // }
+ //
+ // struct __compressed_pair_elem {
+ // _Tp __value_;
+ // }
+ ValueObjectSP pair = func->GetChildAtNamePath({"__f_", "__f_"});
+ if (!pair)
+ return llvm::createStringError("__compressed_pair is not where expected");
+
+ // The callable may be an empty class in which case the empty base class
+ // optimization will eliminate it completely from the type hierarchy
+ //
+ // Serve a dummy value which for all intents and purposes is just as good
+ if (callable_type.IsRecordType() && callable_type.GetNumFields() == 0)
+ return UnwrappedLibCppFunction {
+ .callable = valobj_sp->CreateValueObjectFromAddress(
+ "__value_",
+ pair->GetLoadAddress(),
+ pair->GetExecutionContextRef(),
+ callable_type
+ ),
+ .in_module = virtual_method_addr.CalculateSymbolContextModule()
+ };
+
+ ValueObjectSP elem0 = pair->GetChildAtIndex(0);
+ if (!elem0)
+ return llvm::createStringError("__compressed_pair element 0 not where "
+ "expected");
+
+ ValueObjectSP callable = elem0->GetChildMemberWithName("__value_");
+ if (!callable)
+ return llvm::createStringError("__compressed_pair element value not "
+ "where expected");
+
+ return UnwrappedLibCppFunction {
+ .callable = std::move(callable),
+ .in_module = virtual_method_addr.CalculateSymbolContextModule(),
+ };
+}
+
+CPPLanguageRuntime::LibCppStdFunctionCallableInfo
+CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
+ lldb::ValueObjectSP &valobj_sp) {
+ LLDB_SCOPED_TIMER();
+
+ LibCppStdFunctionCallableInfo optional_info;
+
+ auto unwrap_r = UnwrapLibCppFunction(valobj_sp);
+ if (unwrap_r.takeError())
return optional_info;
- // Now that the __func is a known type we can dig for the wrapped callable
- ValueObjectSP callable;
- {
- // class __func<_Fp, _Alloc, _Rp(_ArgTypes...)> : __base<_Rp(_ArgTypes...)> {
- // __alloc_func<_Fp, _Alloc, _Rp(_ArgTypes...)> __f_;
- // }
- //
- // class __alloc_func<_Fp, _Ap, _Rp(_ArgTypes...)> {
- // __compressed_pair<_Fp, _Ap> __f_;
- // }
- //
- // class __compressed_pair : __compressed_pair_elem<_T1, 0>,
- // __compressed_pair_elem<_T2, 1> {
- // }
- //
- // struct __compressed_pair_elem {
- // _Tp __value_;
- // }
- ValueObjectSP alloc_func = func_as_base->Cast(func_type);
- if (!alloc_func)
- return optional_info;
+ UnwrappedLibCppFunction& wrapped = unwrap_r.get();
+
+ CompilerType callable_t = wrapped.callable->GetCompilerType();
+
+ if (callable_t.IsFunctionPointerType() ||
+ callable_t.IsMemberFunctionPointerType()) {
+ // Target is a standard function pointer, or member function pointer.
+ // In either case on Itanium both contain a function address
+ AddressType addr_type;
+ addr_t addr = wrapped.callable->GetPointerValue(&addr_type);
- ValueObjectSP pair = alloc_func->GetChildAtNamePath({"__f_", "__f_"});
- if (!pair)
+ if (!addr || addr == LLDB_INVALID_ADDRESS)
return optional_info;
- if (callable_type.IsRecordType() && callable_type.GetNumFields() == 0) {
- // callable_type is an empty class, and has been optimized away! Serve a dummy
- callable = valobj_sp->CreateValueObjectFromAddress("__value_",
- pair->GetLoadAddress(),
- pair->GetExecutionContextRef(),
- callable_type);
- } else {
- ValueObjectSP elem0 = pair->GetChildAtIndex(0);
- if (!elem0)
- return optional_info;
-
- callable = elem0->GetChildMemberWithName("__value_");
- if (!callable)
- return optional_info;
- }
- }
-
- if (callable_type.IsFunctionPointerType()) {
- addr_t target_load_addr = callable->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
-
- ModuleSP mod = func_method_addr.CalculateSymbolContextModule();
+ if (addr_type != eAddressTypeLoad)
+ return optional_info;
Address callable_addr;
- if (!mod->ResolveFileAddress(target_load_addr, callable_addr))
+ if (!wrapped.in_module->ResolveFileAddress(addr, callable_addr))
return optional_info;
SymbolContext sc;
- mod->ResolveSymbolContextForAddress(callable_addr, eSymbolContextSymbol | eSymbolContextLineEntry, sc);
-
+ wrapped.in_module->ResolveSymbolContextForAddress(callable_addr,
+ eSymbolContextSymbol |
+ eSymbolContextLineEntry,
+ sc);
if (!sc.symbol)
return optional_info;
@@ -331,20 +366,13 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
.callable_line_entry = sc.line_entry,
.callable_case = LibCppStdFunctionCallableCase::FreeOrMemberFunction
};
- } else if (callable_type.IsMemberFunctionPointerType()) {
- // TODO: Member function's unsigned value comes back as invalid! I am guessing
- // the ValueObject wants to let me know that this is not necessarily as simple
- // as that.. I remember reading something in the Itanium ABI about member
- // pointers taking up two pointers of space. Perhaps that's why it is not legal
- // to read their underlying value raw?
-
- printf("Curious!");
- } else if (callable_type.IsRecordType()) {
- // Target is a lambda, or a generic callable. Search for a single operator() overload
+ } else if (callable_t.IsRecordType()) {
+ // Target is a lambda, or a generic callable. Search for a single
+ // operator() overload and assume it is the target
std::optional<ConstString> mangled_func_name;
- for (uint32_t idx = 0; idx < callable_type.GetNumMemberFunctions(); idx++) {
- TypeMemberFunctionImpl mfunc = callable_type.GetMemberFunctionAtIndex(idx);
+ for (uint32_t idx = 0; idx < callable_t.GetNumMemberFunctions(); idx++) {
+ TypeMemberFunctionImpl mfunc = callable_t.GetMemberFunctionAtIndex(idx);
if (mfunc.GetKind() != eMemberFunctionKindInstanceMethod)
continue;
@@ -361,28 +389,28 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
// Locate the symbol context corresponding to the target function
SymbolContext sc;
{
- // We'll assume that callable_type is in the same module as the vtable
- ModuleSP mod = func_method_addr.CalculateSymbolContextModule();
-
// Limit our lookup to callable_type
- CompilerDeclContext decl_ctx = callable_type.GetTypeSystem()->GetCompilerDeclContextForType(callable_type);
+ CompilerDeclContext decl_ctx = callable_t.GetTypeSystem()
+ ->GetCompilerDeclContextForType(callable_t);
SymbolContextList list;
- mod->FindFunctions(*mangled_func_name, decl_ctx, eFunctionNameTypeFull, {}, list);
-
+ wrapped.in_module->FindFunctions(*mangled_func_name, decl_ctx,
+ eFunctionNameTypeFull, {}, list);
if (list.GetSize() != 1)
return optional_info;
list.GetContextAtIndex(0, sc);
}
- // TODO: This feels a bit clunky, I am probably misusing the API? FindFunctions returns me
- // SymbolContexts with the .function set but not .symbol ... At first glance it seemed like
- // if we know the function there must be a symbol too!
+ // TODO: This feels a bit clunky, I am probably misusing the API?
+ // FindFunctions returns me SymbolContexts with the .function set but not
+ // .symbol ... At first glance it seemed like if we know the function there
+ // must be a symbol too!
if (!sc.function)
return optional_info;
- Symbol* symbol = sc.function->GetAddressRange().GetBaseAddress().CalculateSymbolContextSymbol();
+ Symbol* symbol = sc.function->GetAddressRange().GetBaseAddress()
+ .CalculateSymbolContextSymbol();
if (!symbol)
return optional_info;
@@ -391,240 +419,14 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
.callable_address = symbol->GetAddress(),
.callable_line_entry = sc.GetFunctionStartLineEntry(),
- // TODO: Can't tell lambdas apart from generic callables.. do we really need to?
- // Is it important to have the correct qualification in the summary?
+ // TODO: Can't tell lambdas apart from generic callables.. do we really
+ // need to? Is it important to have the correct qualification in the
+ // summary?
.callable_case = LibCppStdFunctionCallableCase::Lambda
};
}
- // Unrecognized callable type - skip the original implementation for now
- if (!callable_type.IsVoidType())
- return optional_info;
-
- // Member __f_ has type __base*, the contents of which will hold:
- // 1) a vtable entry which may hold type information needed to discover the
- // lambda being called
- // 2) possibly hold a pointer to the callable object
- // e.g.
- //
- // (lldb) frame var -R f_display
- // (std::__1::function<void (int)>) f_display = {
- // __buf_ = {
- // …
- // }
- // __f_ = 0x00007ffeefbffa00
- // }
- // (lldb) memory read -fA 0x00007ffeefbffa00
- // 0x7ffeefbffa00: ... `vtable for std::__1::__function::__func<void (*) ...
- // 0x7ffeefbffa08: ... `print_num(int) at std_function_cppreference_exam ...
- //
- // We will be handling five cases below, std::function is wrapping:
- //
- // 1) a lambda we know at compile time. We will obtain the name of the lambda
- // from the first template pameter from __func's vtable. We will look up
- // the lambda's operator()() and obtain the line table entry.
- // 2) a lambda we know at runtime. A pointer to the lambdas __invoke method
- // will be stored after the vtable. We will obtain the lambdas name from
- // this entry and lookup operator()() and obtain the line table entry.
- // 3) a callable object via operator()(). We will obtain the name of the
- // object from the first template parameter from __func's vtable. We will
- // look up the objects operator()() and obtain the line table entry.
- // 4) a member function. A pointer to the function will stored after the
- // we will obtain the name from this pointer.
- // 5) a free function. A pointer to the function will stored after the vtable
- // we will obtain the name from this pointer.
- ValueObjectSP member_f_(valobj_sp->GetChildMemberWithName("__f_"));
-
- if (member_f_) {
- ValueObjectSP sub_member_f_(member_f_->GetChildMemberWithName("__f_"));
-
- if (sub_member_f_)
- member_f_ = sub_member_f_;
- }
-
- if (!member_f_)
- return optional_info;
-
- lldb::addr_t member_f_pointer_value = member_f_->GetValueAsUnsigned(0);
-
- optional_info.member_f_pointer_value = member_f_pointer_value;
-
- if (!member_f_pointer_value)
- return optional_info;
-
- ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef());
- Process *process = exe_ctx.GetProcessPtr();
-
- if (process == nullptr)
- return optional_info;
-
- uint32_t address_size = process->GetAddressByteSize();
-
- // First item pointed to by __f_ should be the pointer to the vtable for
- // a __base object.
- lldb::addr_t vtable_address =
- process->ReadPointerFromMemory(member_f_pointer_value, status);
-
- if (status.Fail())
- return optional_info;
-
- lldb::addr_t vtable_address_first_entry =
- process->ReadPointerFromMemory(vtable_address + address_size, status);
-
- if (status.Fail())
- return optional_info;
-
- lldb::addr_t address_after_vtable = member_f_pointer_value + address_size;
- // As commented above we may not have a function pointer but if we do we will
- // need it.
- lldb::addr_t possible_function_address =
- process->ReadPointerFromMemory(address_after_vtable, status);
-
- if (status.Fail())
- return optional_info;
-
- Target &target = process->GetTarget();
-
- if (target.GetSectionLoadList().IsEmpty())
- return optional_info;
-
- Address vtable_first_entry_resolved;
-
- if (!target.GetSectionLoadList().ResolveLoadAddress(
- vtable_address_first_entry, vtable_first_entry_resolved))
- return optional_info;
-
- Address vtable_addr_resolved;
- SymbolContext sc;
- Symbol *symbol = nullptr;
-
- if (!target.GetSectionLoadList().ResolveLoadAddress(vtable_address,
- vtable_addr_resolved))
- return optional_info;
-
- target.GetImages().ResolveSymbolContextForAddress(
- vtable_addr_resolved, eSymbolContextEverything, sc);
- symbol = sc.symbol;
-
- if (symbol == nullptr)
- return optional_info;
-
- llvm::StringRef vtable_name(symbol->GetName().GetStringRef());
- bool found_expected_start_string =
- vtable_name.starts_with("vtable for std::__1::__function::__func<");
-
- if (!found_expected_start_string)
- return optional_info;
-
- // Given case 1 or 3 we have a vtable name, we are want to extract the first
- // template parameter
- //
- // ... __func<main::$_0, std::__1::allocator<main::$_0> ...
- // ^^^^^^^^^
- //
- // We could see names such as:
- // main::$_0
- // Bar::add_num2(int)::'lambda'(int)
- // Bar
- //
- // We do this by find the first < and , and extracting in between.
- //
- // This covers the case of the lambda known at compile time.
- size_t first_open_angle_bracket = vtable_name.find('<') + 1;
- size_t first_comma = vtable_name.find(',');
-
- llvm::StringRef first_template_parameter =
- vtable_name.slice(first_open_angle_bracket, first_comma);
-
- Address function_address_resolved;
-
- // Setup for cases 2, 4 and 5 we have a pointer to a function after the
- // vtable. We will use a process of elimination to drop through each case
- // and obtain the data we need.
- if (target.GetSectionLoadList().ResolveLoadAddress(
- possible_function_address, function_address_resolved)) {
- target.GetImages().ResolveSymbolContextForAddress(
- function_address_resolved, eSymbolContextEverything, sc);
- symbol = sc.symbol;
- }
-
- // These conditions are used several times to simplify statements later on.
- bool has_invoke =
- (symbol ? symbol->GetName().GetStringRef().contains("__invoke") : false);
- auto calculate_symbol_context_helper = [](auto &t,
- SymbolContextList &sc_list) {
- SymbolContext sc;
- t->CalculateSymbolContext(&sc);
- sc_list.Append(sc);
- };
-
- // Case 2
- if (has_invoke) {
- SymbolContextList scl;
- calculate_symbol_context_helper(symbol, scl);
-
- return line_entry_helper(target, scl[0], symbol, first_template_parameter,
- has_invoke);
- }
-
- // Case 4 or 5
- if (symbol && !symbol->GetName().GetStringRef().starts_with("vtable for") &&
- !contains_lambda_identifier(first_template_parameter) && !has_invoke) {
- optional_info.callable_case =
- LibCppStdFunctionCallableCase::FreeOrMemberFunction;
- optional_info.callable_address = function_address_resolved;
- optional_info.callable_symbol = *symbol;
-
- return optional_info;
- }
-
- std::string func_to_match = first_template_parameter.str();
-
- auto it = CallableLookupCache.find(func_to_match);
- if (it != CallableLookupCache.end())
- return it->second;
-
- SymbolContextList scl;
-
- CompileUnit *vtable_cu =
- vtable_first_entry_resolved.CalculateSymbolContextCompileUnit();
- llvm::StringRef name_to_use = func_to_match;
-
- // Case 3, we have a callable object instead of a lambda
- //
- // TODO
- // We currently don't support this case a callable object may have multiple
- // operator()() varying on const/non-const and number of arguments and we
- // don't have a way to currently distinguish them so we will bail out now.
- if (!contains_lambda_identifier(name_to_use))
- return optional_info;
-
- if (vtable_cu && !has_invoke) {
- lldb::FunctionSP func_sp =
- vtable_cu->FindFunction([name_to_use](const FunctionSP &f) {
- auto name = f->GetName().GetStringRef();
- if (name.starts_with(name_to_use) && name.contains("operator"))
- return true;
-
- return false;
- });
-
- if (func_sp) {
- calculate_symbol_context_helper(func_sp, scl);
- }
- }
-
- if (symbol == nullptr)
- return optional_info;
-
- // Case 1 or 3
- if (scl.GetSize() >= 1) {
- optional_info = line_entry_helper(target, scl[0], symbol,
- first_template_parameter, has_invoke);
- }
-
- CallableLookupCache[func_to_match] = optional_info;
-
+ // Unrecognized callable type
return optional_info;
}
diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h
index 57cfe28245808c..0368e752ff33c0 100644
--- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h
+++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h
@@ -28,6 +28,14 @@ class CPPLanguageRuntime : public LanguageRuntime {
Invalid
};
+ struct UnwrappedLibCppFunction {
+ lldb::ValueObjectSP callable;
+ lldb::ModuleSP in_module;
+ };
+
+ llvm::Expected<UnwrappedLibCppFunction>
+ UnwrapLibCppFunction(lldb::ValueObjectSP &valobj_sp);
+
struct LibCppStdFunctionCallableInfo {
Symbol callable_symbol;
Address callable_address;
@@ -37,6 +45,7 @@ class CPPLanguageRuntime : public LanguageRuntime {
LibCppStdFunctionCallableCase::Invalid;
};
+
LibCppStdFunctionCallableInfo
FindLibCppStdFunctionCallableInfo(lldb::ValueObjectSP &valobj_sp);
@@ -81,12 +90,6 @@ class CPPLanguageRuntime : public LanguageRuntime {
protected:
// Classes that inherit from CPPLanguageRuntime can see and modify these
CPPLanguageRuntime(Process *process);
-
-private:
- using OperatorStringToCallableInfoMap =
- llvm::StringMap<CPPLanguageRuntime::LibCppStdFunctionCallableInfo>;
-
- OperatorStringToCallableInfoMap CallableLookupCache;
};
} // namespace lldb_private
More information about the lldb-commits
mailing list