llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-lldb <details> <summary>Changes</summary> Add the ability to get a C++ vtable ValueObject from another ValueObject. This patch adds the ability to ask a ValueObject for a ValueObject that represents the virtual function table for a C++ class. If the ValueObject is not a C++ class with a vtable, a valid ValueObject value will be returned that contains an appropriate error. If it is successful a valid ValueObject that represents vtable will be returned. The ValueObject that is returned will have a name that matches the demangled value for a C++ vtable mangled name like "vtable for <class-name>". It will have N children, one for each virtual function pointer. Each child's value is the function pointer itself, the summary is the symbolication of this function pointer, and the type will be a valid function pointer from the debug info if there is debug information corresponding to the virtual function pointer. The vtable SBValue will have the following: - SBValue::GetName() returns "vtable for <class>" - SBValue::GetValue() returns a string representation of the vtable address - SBValue::GetSummary() returns NULL - SBValue::GetType() returns a type appropriate for a uintptr_t type for the current process - SBValue::GetLoadAddress() returns the address of the vtable adderess - SBValue::GetValueAsUnsigned(...) returns the vtable address - SBValue::GetNumChildren() returns the number of virtual function pointers in the vtable - SBValue::GetChildAtIndex(...) returns a SBValue that represents a virtual function pointer The child SBValue objects that represent a virtual function pointer has the following values: - SBValue::GetName() returns "[%u]" where %u is the vtable function pointer index - SBValue::GetValue() returns a string representation of the virtual function pointer - SBValue::GetSummary() returns a symbolicated respresentation of the virtual function pointer - SBValue::GetType() returns the function prototype type if there is debug info, or a generic funtion prototype if there is no debug info - SBValue::GetLoadAddress() returns the address of the virtual function pointer - SBValue::GetValueAsUnsigned(...) returns the virtual function pointer - SBValue::GetNumChildren() returns 0 - SBValue::GetChildAtIndex(...) returns invalid SBValue for any index Examples of using this API via python: ``` (lldb) script vtable = lldb.frame.FindVariable("shape_ptr").GetVTable() (lldb) script vtable vtable for Shape = 0x0000000100004088 { [0] = 0x0000000100003d20 a.out`Shape::~Shape() at main.cpp:3 [1] = 0x0000000100003e4c a.out`Shape::~Shape() at main.cpp:3 [2] = 0x0000000100003e7c a.out`Shape::area() at main.cpp:4 [3] = 0x0000000100003e3c a.out`Shape::optional() at main.cpp:7 } (lldb) script c = vtable.GetChildAtIndex(0) (lldb) script c (void ()) [0] = 0x0000000100003d20 a.out`Shape::~Shape() at main.cpp:3 ``` --- Patch is 33.01 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/67599.diff 18 Files Affected: - (modified) lldb/include/lldb/API/SBValue.h (+28) - (modified) lldb/include/lldb/Core/ValueObject.h (+4) - (modified) lldb/include/lldb/Core/ValueObjectChild.h (+1) - (added) lldb/include/lldb/Core/ValueObjectVTable.h (+65) - (modified) lldb/include/lldb/Symbol/TypeSystem.h (+4) - (modified) lldb/include/lldb/lldb-enumerations.h (+3-1) - (modified) lldb/source/API/SBValue.cpp (+14-3) - (modified) lldb/source/Commands/CommandObjectFrame.cpp (+2) - (modified) lldb/source/Core/CMakeLists.txt (+1) - (modified) lldb/source/Core/ValueObject.cpp (+5) - (added) lldb/source/Core/ValueObjectVTable.cpp (+325) - (modified) lldb/source/DataFormatters/CXXFunctionPointer.cpp (+5-1) - (modified) lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp (+3-1) - (modified) lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp (+25-1) - (modified) lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h (+4) - (added) lldb/test/API/functionalities/vtable/Makefile (+3) - (added) lldb/test/API/functionalities/vtable/TestVTableValue.py (+135) - (added) lldb/test/API/functionalities/vtable/main.cpp (+37) ``````````diff diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h index b66c2d5642b6f95..333bdf1738eecaf 100644 --- a/lldb/include/lldb/API/SBValue.h +++ b/lldb/include/lldb/API/SBValue.h @@ -374,6 +374,34 @@ class LLDB_API SBValue { lldb::SBWatchpoint WatchPointee(bool resolve_location, bool read, bool write, SBError &error); + /// If this value represents a C++ class that has a vtable, return an value + /// that represents the virtual function table. + /// + /// SBValue::GetError() will be in the success state if this value represents + /// a C++ class with a vtable, or an appropriate error describing that the + /// object isn't a C++ class with a vtable or not a C++ class. + /// + /// SBValue::GetName() will be the demangled symbol name for the virtual + /// function table like "vtable for Baseclass". + /// + /// SBValue::GetValue() will be the address of the first vtable entry if the + /// current SBValue is a class with a vtable, or nothing the current SBValue + /// is not a C++ class or not a C++ class that has a vtable. + /// + /// SBValue::GetSummary() will contain the number of virtual function pointers + /// in the vtable like is done for arrays. + /// + /// SBValue::GetNumChildren() will return the number of virtual function + /// pointers in the vtable, or zero on error. + /// + /// SBValue::GetChildAtIndex(...) will return each virtual function pointer + /// as a SBValue object. The child SBValue objects name will be the array + /// index, value will be the virtual function pointer, summary will be the + /// symbolicated address description, and if the the adress resolves to a + /// function in debug info, the child type will be the function prototype as + /// a SBType object. + lldb::SBValue GetVTable(); + protected: friend class SBBlock; friend class SBFrame; diff --git a/lldb/include/lldb/Core/ValueObject.h b/lldb/include/lldb/Core/ValueObject.h index 3af94f0a86e2fcc..20b3086138457f7 100644 --- a/lldb/include/lldb/Core/ValueObject.h +++ b/lldb/include/lldb/Core/ValueObject.h @@ -620,6 +620,10 @@ class ValueObject { virtual lldb::ValueObjectSP CastPointerType(const char *name, lldb::TypeSP &type_sp); + /// If this object represents a C++ class with a vtable, return an object + /// that represents the virtual function table. If the object isn't a class + /// with a vtable, return a valid ValueObject with the error set correctly. + lldb::ValueObjectSP GetVTable(); // The backing bits of this value object were updated, clear any descriptive // string, so we know we have to refetch them. void ValueUpdated() { diff --git a/lldb/include/lldb/Core/ValueObjectChild.h b/lldb/include/lldb/Core/ValueObjectChild.h index 07b37aa8a405f7e..46b14e6840f0dc3 100644 --- a/lldb/include/lldb/Core/ValueObjectChild.h +++ b/lldb/include/lldb/Core/ValueObjectChild.h @@ -73,6 +73,7 @@ class ValueObjectChild : public ValueObject { friend class ValueObject; friend class ValueObjectConstResult; friend class ValueObjectConstResultImpl; + friend class ValueObjectVTable; ValueObjectChild(ValueObject &parent, const CompilerType &compiler_type, ConstString name, uint64_t byte_size, diff --git a/lldb/include/lldb/Core/ValueObjectVTable.h b/lldb/include/lldb/Core/ValueObjectVTable.h new file mode 100644 index 000000000000000..faa0af238e764f4 --- /dev/null +++ b/lldb/include/lldb/Core/ValueObjectVTable.h @@ -0,0 +1,65 @@ +//===-- ValueObjectVTable.h -------------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_CORE_VALUEOBJECTVTABLE_H +#define LLDB_CORE_VALUEOBJECTVTABLE_H + +#include "lldb/Core/ValueObject.h" + +namespace lldb_private { + +/// A class that represents a virtual function table for a C++ class. +/// +/// +class ValueObjectVTable : public ValueObject { +public: + ~ValueObjectVTable() override; + + static lldb::ValueObjectSP Create(ValueObject &parent); + + std::optional<uint64_t> GetByteSize() override; + + size_t CalculateNumChildren(uint32_t max) override; + + ValueObject *CreateChildAtIndex(size_t idx, bool synthetic_array_member, + int32_t synthetic_index) override; + + lldb::ValueType GetValueType() const override; + + ConstString GetTypeName() override; + + ConstString GetQualifiedTypeName() override; + + ConstString GetDisplayTypeName() override; + + bool IsInScope() override; + +protected: + bool UpdateValue() override; + + CompilerType GetCompilerTypeImpl() override; + + /// The symbol for the C++ virtual function table. + Symbol *m_vtable_symbol = nullptr; + /// Cache the number of vtable children when we update the value. + uint32_t m_num_vtable_entries = 0; + /// Cache the address size in bytes to avoid checking with the process to + /// many times. + uint32_t m_addr_size = 0; + +private: + ValueObjectVTable(ValueObject &parent); + + // For ValueObject only + ValueObjectVTable(const ValueObjectVTable &) = delete; + const ValueObjectVTable &operator=(const ValueObjectVTable &) = delete; +}; + +} // namespace lldb_private + +#endif // LLDB_CORE_VALUEOBJECTVTABLE_H diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h index eb6e453e1aec0d0..16b912c8113f969 100644 --- a/lldb/include/lldb/Symbol/TypeSystem.h +++ b/lldb/include/lldb/Symbol/TypeSystem.h @@ -444,6 +444,10 @@ class TypeSystem : public PluginInterface, virtual CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) = 0; + virtual CompilerType CreateGenericFunctionPrototype() { + return CompilerType(); + } + virtual CompilerType GetBuiltinTypeForEncodingAndBitSize(lldb::Encoding encoding, size_t bit_size) = 0; diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 21e098481ce8022..b9dae9ca5573d5b 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -322,7 +322,9 @@ enum ValueType { eValueTypeRegister = 5, ///< stack frame register value eValueTypeRegisterSet = 6, ///< A collection of stack frame register values eValueTypeConstResult = 7, ///< constant result variables - eValueTypeVariableThreadLocal = 8 ///< thread local storage variable + eValueTypeVariableThreadLocal = 8, ///< thread local storage variable + eValueTypeVTable = 9, ///< virtual function table + eValueTypeVTableEntry = 10, ///< function pointer in virtual function table }; /// Token size/granularities for Input Readers. diff --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp index 738773c93c49b03..cf2d862fa504844 100644 --- a/lldb/source/API/SBValue.cpp +++ b/lldb/source/API/SBValue.cpp @@ -114,7 +114,7 @@ class ValueImpl { Target *target = value_sp->GetTargetSP().get(); // If this ValueObject holds an error, then it is valuable for that. - if (value_sp->GetError().Fail()) + if (value_sp->GetError().Fail()) return value_sp; if (!target) @@ -1038,8 +1038,8 @@ lldb::ValueObjectSP SBValue::GetSP(ValueLocker &locker) const { // IsValid means that the SBValue has a value in it. But that's not the // only time that ValueObjects are useful. We also want to return the value // if there's an error state in it. - if (!m_opaque_sp || (!m_opaque_sp->IsValid() - && (m_opaque_sp->GetRootSP() + if (!m_opaque_sp || (!m_opaque_sp->IsValid() + && (m_opaque_sp->GetRootSP() && !m_opaque_sp->GetRootSP()->GetError().Fail()))) { locker.GetError().SetErrorString("No value"); return ValueObjectSP(); @@ -1498,3 +1498,14 @@ lldb::SBValue SBValue::Persist() { } return persisted_sb; } + +lldb::SBValue SBValue::GetVTable() { + SBValue vtable_sb; + ValueLocker locker; + lldb::ValueObjectSP value_sp(GetSP(locker)); + if (!value_sp) + return vtable_sb; + + vtable_sb.SetSP(value_sp->GetVTable()); + return vtable_sb; +} diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp index 1390fd8748dfaf6..2149d0f2f98da3d 100644 --- a/lldb/source/Commands/CommandObjectFrame.cpp +++ b/lldb/source/Commands/CommandObjectFrame.cpp @@ -495,6 +495,8 @@ may even involve JITing and running code in the target program.)"); case eValueTypeRegisterSet: case eValueTypeConstResult: case eValueTypeVariableThreadLocal: + case eValueTypeVTable: + case eValueTypeVTableEntry: return false; } } diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt index d7b4f2587a98bf9..b23cc5d2eaf3156 100644 --- a/lldb/source/Core/CMakeLists.txt +++ b/lldb/source/Core/CMakeLists.txt @@ -71,6 +71,7 @@ add_lldb_library(lldbCore ValueObjectSyntheticFilter.cpp ValueObjectUpdater.cpp ValueObjectVariable.cpp + ValueObjectVTable.cpp DEPENDS clang-tablegen-targets diff --git a/lldb/source/Core/ValueObject.cpp b/lldb/source/Core/ValueObject.cpp index 3e9116f2d922933..aa2a35bf2b2dc5f 100644 --- a/lldb/source/Core/ValueObject.cpp +++ b/lldb/source/Core/ValueObject.cpp @@ -17,6 +17,7 @@ #include "lldb/Core/ValueObjectDynamicValue.h" #include "lldb/Core/ValueObjectMemory.h" #include "lldb/Core/ValueObjectSyntheticFilter.h" +#include "lldb/Core/ValueObjectVTable.h" #include "lldb/DataFormatters/DataVisualization.h" #include "lldb/DataFormatters/DumpValueObjectOptions.h" #include "lldb/DataFormatters/FormatManager.h" @@ -3155,3 +3156,7 @@ ValueObjectSP ValueObject::Persist() { return persistent_var_sp->GetValueObject(); } + +lldb::ValueObjectSP ValueObject::GetVTable() { + return ValueObjectVTable::Create(*this); +} diff --git a/lldb/source/Core/ValueObjectVTable.cpp b/lldb/source/Core/ValueObjectVTable.cpp new file mode 100644 index 000000000000000..31c619986b825f1 --- /dev/null +++ b/lldb/source/Core/ValueObjectVTable.cpp @@ -0,0 +1,325 @@ +//===-- ValueObjectVTable.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 "lldb/Core/ValueObjectVTable.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/ValueObjectChild.h" +#include "lldb/Symbol/Function.h" +#include "lldb/lldb-defines.h" +#include "lldb/lldb-enumerations.h" +#include "lldb/lldb-forward.h" +#include "lldb/lldb-private-enumerations.h" +#include "clang/Tooling/Transformer/RangeSelector.h" +#include "llvm/Support/MathExtras.h" +#include <cstdint> + +using namespace lldb; +using namespace lldb_private; + +class ValueObjectVTableChild : public ValueObject { +public: + ValueObjectVTableChild(ValueObject &parent, uint32_t func_idx, + uint64_t addr_size) + : ValueObject(parent), m_func_idx(func_idx), m_addr_size(addr_size) { + SetFormat(eFormatPointer); + SetName(ConstString(llvm::formatv("[{0}]", func_idx).str())); + } + + ~ValueObjectVTableChild() override = default; + + std::optional<uint64_t> GetByteSize() override { return m_addr_size; }; + + size_t CalculateNumChildren(uint32_t max) override { return 0; }; + + ValueType GetValueType() const override { return eValueTypeVTableEntry; }; + + bool IsInScope() override { + ValueObject *parent = GetParent(); + if (parent) + return parent->IsInScope(); + return false; + }; + +protected: + bool UpdateValue() override { + SetValueIsValid(false); + m_value.Clear(); + ValueObject *parent = GetParent(); + if (!parent) { + m_error.SetErrorString("no parent object"); + return false; + } + + addr_t parent_addr = parent->GetValueAsUnsigned(LLDB_INVALID_ADDRESS); + if (parent_addr == LLDB_INVALID_ADDRESS) { + m_error.SetErrorString("parent has invalid address"); + return false; + } + + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) { + m_error.SetErrorString("no process"); + return false; + } + + TargetSP target_sp = GetTargetSP(); + if (!target_sp) { + m_error.SetErrorString("no target"); + return false; + } + + // Each `vtable_entry_addr` points to the function pointer. + addr_t vtable_entry_addr = parent_addr + m_func_idx * m_addr_size; + addr_t vfunc_ptr = + process_sp->ReadPointerFromMemory(vtable_entry_addr, m_error); + if (m_error.Fail()) { + m_error.SetErrorStringWithFormat( + "failed to read virtual function entry 0x%16.16" PRIx64, + vtable_entry_addr); + return false; + } + + Address resolved_vfunc_ptr_address; + target_sp->ResolveLoadAddress(vfunc_ptr, resolved_vfunc_ptr_address); + if (!resolved_vfunc_ptr_address.IsValid()) { + m_error.SetErrorStringWithFormat( + "unable to resolve func ptr address: 0x%16.16" PRIx64, vfunc_ptr); + return false; + } + + // Set our value to be the load address of the function pointer in memory + // and our type to be the function pointer type. + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = vtable_entry_addr; + + // See if our resolved address points to a function in the debug info. If + // it does, then we can report the type as a function prototype for this + // function. + Function *function = + resolved_vfunc_ptr_address.CalculateSymbolContextFunction(); + if (function) { + m_value.SetCompilerType(function->GetCompilerType()); + } else { + // Set our value's compiler type to a generic function protoype so that + // it displays as a hex function pointer for the value and the summary + // will display the address description. + auto type_system_or_err = + target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC_plus_plus); + if (type_system_or_err) { + CompilerType proto = (*type_system_or_err)->CreateGenericFunctionPrototype(); + if (proto.IsFunctionType()) + m_value.SetCompilerType(proto); + } else { + consumeError(type_system_or_err.takeError()); + } + } + + // Now read our value into m_data so that our we can use the default + // summary provider for C++ for function pointers which will get the + // address description for our function pointer. + if (m_error.Success()) { + const bool thread_and_frame_only_if_stopped = true; + ExecutionContext exe_ctx( + GetExecutionContextRef().Lock(thread_and_frame_only_if_stopped)); + m_error = m_value.GetValueAsData(&exe_ctx, m_data, GetModule().get()); + } + SetValueDidChange(true); + SetValueIsValid(true); + return true; + }; + + CompilerType GetCompilerTypeImpl() override { + return m_value.GetCompilerType(); + }; + + const uint32_t m_func_idx; + const uint64_t m_addr_size; + +private: + // For ValueObject only + ValueObjectVTableChild(const ValueObjectVTableChild &) = delete; + const ValueObjectVTableChild & + operator=(const ValueObjectVTableChild &) = delete; +}; + +ValueObjectSP ValueObjectVTable::Create(ValueObject &parent) { + return (new ValueObjectVTable(parent))->GetSP(); +} + +ValueObjectVTable::ValueObjectVTable(ValueObject &parent) + : ValueObject(parent) { + SetFormat(eFormatPointer); +} + +std::optional<uint64_t> ValueObjectVTable::GetByteSize() { + if (m_vtable_symbol) + return m_vtable_symbol->GetByteSize(); + else + return std::nullopt; +} + +size_t ValueObjectVTable::CalculateNumChildren(uint32_t max) { + if (UpdateValueIfNeeded(false)) + return m_num_vtable_entries <= max ? m_num_vtable_entries : max; + return 0; +} + +ValueType ValueObjectVTable::GetValueType() const { return eValueTypeVTable; } + +ConstString ValueObjectVTable::GetTypeName() { + if (m_vtable_symbol) + return m_vtable_symbol->GetName(); + return ConstString(); +} + +ConstString ValueObjectVTable::GetQualifiedTypeName() { return GetTypeName(); } + +ConstString ValueObjectVTable::GetDisplayTypeName() { + if (m_vtable_symbol) + return m_vtable_symbol->GetDisplayName(); + return ConstString(); +} + +bool ValueObjectVTable::IsInScope() { return GetParent()->IsInScope(); } + +ValueObject *ValueObjectVTable::CreateChildAtIndex(size_t idx, + bool synthetic_array_member, + int32_t synthetic_index) { + if (synthetic_array_member) + return nullptr; + return new ValueObjectVTableChild(*this, idx, m_addr_size); +} + +bool ValueObjectVTable::UpdateValue() { + m_error.Clear(); + m_flags.m_children_count_valid = false; + SetValueIsValid(false); + m_num_vtable_entries = 0; + ValueObject *parent = GetParent(); + if (!parent) { + m_error.SetErrorString("no parent object"); + return false; + } + + // Check to make sure the class has a vtable. + + // If we have a pointer or reference type, get the pointee type from this + // so we can ask if it has virtual functions below. + CompilerType value_type = parent->GetCompilerType(); + if (value_type.IsPointerOrReferenceType()) { + CompilerType pointee_type = value_type.GetPointeeType(); + if (pointee_type) + value_type = pointee_type; + } + + // Make sure this is a class or a struct first by checking the type class + // bitfield that gets returned. + if ((value_type.GetTypeClass() & (eTypeClassStruct | eTypeClassClass)) == 0) { + m_error.SetErrorStringWithFormat("type \"%s\" is not a class or struct", + value_type.GetTypeName().AsCString("<invalid>")); + return false; + } + + // Check if the type has virtual functions by asking it if it is polymorphic. + if (!value_type.IsPolymorphicClass()) { + m_error.SetErrorStringWithFormat("type \"%s\" doesn't have a vtable", + value_type.GetTypeName().AsCString("<invalid>")); + return false; + } + + TargetSP target_sp = GetTargetSP(); + if (!target_sp) { + m_error.SetErrorString("no target"); + return false; + } + + // Get the address of our parent. This will update the parent value object + // if needed and fetch the address of the parent. + AddressType addr_type; + addr_t parent_load_addr = + parent->GetAddressOf(/*scalar_is_load_address=*/true, &addr_type); + if (addr_type == eAddressTypeFile) { + ModuleSP module_sp(parent->GetModule()); + if (!module_sp) { + parent_load_addr = LLDB_INVALID_ADDRESS; + } else { + Address addr; + module_sp->ResolveFileAddress(parent_load_addr, addr); + parent_load_addr = addr.GetLoadAddress(target_sp.get()); + } + } else if (addr_type == eAddressTypeHost || + addr_type == eAddressTypeInvalid) { + parent_load_addr = LLDB_INVALID_ADDRESS; + } + + if (parent_load_addr == LLDB_INVALID_ADDRESS) { + m_error.SetErrorString("parent is not in memory"); + return false; + } + + m_value.Clear(); + + ProcessSP process_sp = GetProcessSP(); + if (!process_sp) { + m_error.SetErrorString("no process"); + return false; + } + + // We expect to find the vtable at the first block of memory. + addr_t possible_vtable_ptr = + process_sp->ReadPointerFromMemory(parent_load_addr, m_error); + if (m_error.Fail()) + return false; + + Address resolved_possible_vtable_address; + target_sp->ResolveLoadAddress(possible_vtable_ptr, + resolved_possible_vtable_address); + if (!resolved_possible_vtable_address.IsValid()) { + m_error.SetErrorStringWithFormat( + "unable to resolve 0x%" PRIx64 " to a section for vtable " + "symbol search", possible_vtable_ptr); + return false; + } + + m_vtable_symbol = + resolved_possible_vtable_address.CalculateSymbolContextSymbol(); + if (!(m_vtable_symbol && + m_vtable_symbol->GetName().GetStringRef().startswith("vtable for "))) { + m_error.SetErrorStringWithFormat( + "no vtable symbol found containing 0x%" PRIx64, possible_vtable_ptr); + return false; + } + + // Now that we know it's a vta... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/67599 _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits