https://github.com/clayborg updated https://github.com/llvm/llvm-project/pull/67599
>From 8fa9aae354ac455f4ea443de1bb5f753fe93fb51 Mon Sep 17 00:00:00 2001 From: Greg Clayton <gclay...@fb.com> Date: Wed, 27 Sep 2023 13:03:40 -0700 Subject: [PATCH 1/2] 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 ``` --- lldb/include/lldb/API/SBValue.h | 28 ++ lldb/include/lldb/Core/ValueObject.h | 4 + lldb/include/lldb/Core/ValueObjectChild.h | 1 + lldb/include/lldb/Core/ValueObjectVTable.h | 65 ++++ lldb/include/lldb/Symbol/TypeSystem.h | 4 + lldb/include/lldb/lldb-enumerations.h | 4 +- lldb/source/API/SBValue.cpp | 17 +- lldb/source/Commands/CommandObjectFrame.cpp | 2 + lldb/source/Core/CMakeLists.txt | 1 + lldb/source/Core/ValueObject.cpp | 5 + lldb/source/Core/ValueObjectVTable.cpp | 325 ++++++++++++++++++ .../DataFormatters/CXXFunctionPointer.cpp | 6 +- .../Language/CPlusPlus/CPlusPlusLanguage.cpp | 4 +- .../TypeSystem/Clang/TypeSystemClang.cpp | 26 +- .../TypeSystem/Clang/TypeSystemClang.h | 4 + lldb/test/API/functionalities/vtable/Makefile | 3 + .../functionalities/vtable/TestVTableValue.py | 135 ++++++++ lldb/test/API/functionalities/vtable/main.cpp | 37 ++ 18 files changed, 664 insertions(+), 7 deletions(-) create mode 100644 lldb/include/lldb/Core/ValueObjectVTable.h create mode 100644 lldb/source/Core/ValueObjectVTable.cpp create mode 100644 lldb/test/API/functionalities/vtable/Makefile create mode 100644 lldb/test/API/functionalities/vtable/TestVTableValue.py create mode 100644 lldb/test/API/functionalities/vtable/main.cpp 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 vtable, we update the object's state. + SetName(GetTypeName()); + + // Calculate the number of entries + assert(m_vtable_symbol->GetByteSizeIsValid()); + m_addr_size = process_sp->GetAddressByteSize(); + addr_t symbol_end_addr = m_vtable_symbol->GetLoadAddress(target_sp.get()) + + m_vtable_symbol->GetByteSize(); + m_num_vtable_entries = (symbol_end_addr - possible_vtable_ptr) / m_addr_size; + + m_value.SetValueType(Value::ValueType::LoadAddress); + m_value.GetScalar() = parent_load_addr; + auto type_system_or_err = + target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC_plus_plus); + if (type_system_or_err) { + m_value.SetCompilerType( + (*type_system_or_err)->GetBasicTypeFromAST(eBasicTypeUnsignedLong)); + } else { + consumeError(type_system_or_err.takeError()); + } + SetValueDidChange(true); + SetValueIsValid(true); + return true; +} + +CompilerType ValueObjectVTable::GetCompilerTypeImpl() { return CompilerType(); } + +ValueObjectVTable::~ValueObjectVTable() = default; diff --git a/lldb/source/DataFormatters/CXXFunctionPointer.cpp b/lldb/source/DataFormatters/CXXFunctionPointer.cpp index d7df280e56efb0d..6543433d17ff45f 100644 --- a/lldb/source/DataFormatters/CXXFunctionPointer.cpp +++ b/lldb/source/DataFormatters/CXXFunctionPointer.cpp @@ -13,6 +13,7 @@ #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/Target.h" #include "lldb/Utility/Stream.h" +#include "lldb/lldb-enumerations.h" #include <string> @@ -76,7 +77,10 @@ bool lldb_private::formatters::CXXFunctionPointerSummaryProvider( } } if (sstr.GetSize() > 0) { - stream.Printf("(%s)", sstr.GetData()); + if (valobj.GetValueType() == lldb::eValueTypeVTableEntry) + stream.PutCString(sstr.GetData()); + else + stream.Printf("(%s)", sstr.GetData()); return true; } else return false; diff --git a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp index 3d709e3d6759556..147088edeb15b8d 100644 --- a/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp +++ b/lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp @@ -45,6 +45,7 @@ #include "LibCxxVariant.h" #include "LibStdcpp.h" #include "MSVCUndecoratedNameParser.h" +#include "lldb/lldb-enumerations.h" using namespace lldb; using namespace lldb_private; @@ -1356,7 +1357,8 @@ CPlusPlusLanguage::GetHardcodedSummaries() { lldb_private::formatters::CXXFunctionPointerSummaryProvider, "Function pointer summary provider")); if (CompilerType CT = valobj.GetCompilerType(); - CT.IsFunctionPointerType() || CT.IsMemberFunctionPointerType()) { + CT.IsFunctionPointerType() || CT.IsMemberFunctionPointerType() || + valobj.GetValueType() == lldb::eValueTypeVTableEntry) { return formatter_sp; } return nullptr; diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index 69cff0f35ae4ab2..37ba8b0f6e2afcf 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -4700,6 +4700,21 @@ TypeSystemClang::GetTypedefedType(lldb::opaque_compiler_type_t type) { CompilerType TypeSystemClang::GetBasicTypeFromAST(lldb::BasicType basic_type) { return TypeSystemClang::GetBasicType(basic_type); } + +CompilerType TypeSystemClang::CreateGenericFunctionPrototype() { + clang::ASTContext &ast = getASTContext(); + const FunctionType::ExtInfo generic_ext_info( + /*noReturn=*/false, + /*hasRegParm=*/false, + /*regParm=*/0, + CallingConv::CC_C, + /*producesResult=*/false, + /*noCallerSavedRegs=*/false, + /*NoCfCheck=*/false, + /*cmseNSCall=*/false); + QualType func_type = ast.getFunctionNoProtoType(ast.VoidTy, generic_ext_info); + return GetType(func_type); +} // Exploring the type const llvm::fltSemantics & @@ -4733,6 +4748,15 @@ TypeSystemClang::GetBitSize(lldb::opaque_compiler_type_t type, return std::nullopt; break; + case clang::Type::FunctionProto: + case clang::Type::FunctionNoProto: { + ExecutionContext exe_ctx(exe_scope); + Process *process = exe_ctx.GetProcessPtr(); + if (process) + return process->GetAddressByteSize() * 8; + break; + } + case clang::Type::ObjCInterface: case clang::Type::ObjCObject: { ExecutionContext exe_ctx(exe_scope); @@ -4816,7 +4840,7 @@ lldb::Encoding TypeSystemClang::GetEncoding(lldb::opaque_compiler_type_t type, case clang::Type::FunctionNoProto: case clang::Type::FunctionProto: - break; + return lldb::eEncodingUint; case clang::Type::IncompleteArray: case clang::Type::VariableArray: diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h index 0544de3cd33befb..baf19156e41bba5 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h @@ -800,6 +800,10 @@ class TypeSystemClang : public TypeSystem { // Create related types using the current type's AST CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) override; + // Create a generic function prototype that can be used in ValuObject types + // to correctly display a function pointer with the right value and summary. + CompilerType CreateGenericFunctionPrototype() override; + // Exploring the type const llvm::fltSemantics &GetFloatTypeSemantics(size_t byte_size) override; diff --git a/lldb/test/API/functionalities/vtable/Makefile b/lldb/test/API/functionalities/vtable/Makefile new file mode 100644 index 000000000000000..99998b20bcb0502 --- /dev/null +++ b/lldb/test/API/functionalities/vtable/Makefile @@ -0,0 +1,3 @@ +CXX_SOURCES := main.cpp + +include Makefile.rules diff --git a/lldb/test/API/functionalities/vtable/TestVTableValue.py b/lldb/test/API/functionalities/vtable/TestVTableValue.py new file mode 100644 index 000000000000000..ce3da0a2185f501 --- /dev/null +++ b/lldb/test/API/functionalities/vtable/TestVTableValue.py @@ -0,0 +1,135 @@ +""" +Make sure the getting a variable path works and doesn't crash. +""" + + +import lldb +import lldbsuite.test.lldbutil as lldbutil +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + +class TestVTableValue(TestBase): + # If your test case doesn't stress debug info, then + # set this to true. That way it won't be run once for + # each debug info format. + NO_DEBUG_INFO_TESTCASE = True + + @skipUnlessPlatform(["linux", "macosx"]) + def test_vtable(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "At the end", lldb.SBFileSpec("main.cpp") + ) + + shape = self.frame().FindVariable("shape") + vtable = shape.GetVTable() + self.assertEquals(vtable.GetName(), "vtable for Shape") + self.assertEquals(vtable.GetTypeName(), "vtable for Shape") + # Make sure we have the right number of virtual functions in our vtable + # for the shape class. + self.assertEquals(vtable.GetNumChildren(), 4) + + # Verify vtable address + vtable_addr = vtable.GetValueAsUnsigned(0) + expected_addr = self.expected_vtable_addr(shape) + self.assertEquals(vtable_addr, expected_addr) + + for (idx, vtable_entry) in enumerate(vtable.children): + self.verify_vtable_entry(vtable_entry, vtable_addr, idx) + + rect = self.frame().FindVariable("rect") + vtable = rect.GetVTable() + self.assertEquals(vtable.GetName(), "vtable for Rectangle") + self.assertEquals(vtable.GetTypeName(), "vtable for Rectangle") + + # Make sure we have the right number of virtual functions in our vtable + # with the extra virtual function added by the Rectangle class + self.assertEquals(vtable.GetNumChildren(), 5) + + # Verify vtable address + vtable_addr = vtable.GetValueAsUnsigned() + expected_addr = self.expected_vtable_addr(rect) + self.assertEquals(vtable_addr, expected_addr) + + for (idx, vtable_entry) in enumerate(vtable.children): + self.verify_vtable_entry(vtable_entry, vtable_addr, idx) + + @skipUnlessPlatform(["linux", "macosx"]) + def test_base_class_ptr(self): + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint( + self, "Shape is Rectangle", lldb.SBFileSpec("main.cpp") + ) + + shape = self.frame().FindVariable("shape") + rect = self.frame().FindVariable("rect") + + shape_ptr = self.frame().FindVariable("shape_ptr") + shape_ptr_vtable = shape_ptr.GetVTable() + self.assertEquals(shape_ptr_vtable.GetName(), "vtable for Rectangle") + self.assertEquals(shape_ptr_vtable.GetNumChildren(), 5) + self.assertEquals(shape_ptr.GetValueAsUnsigned(0), + rect.GetLoadAddress()) + lldbutil.continue_to_source_breakpoint( + self, process, "Shape is Shape", lldb.SBFileSpec("main.cpp") + ) + self.assertEquals(shape_ptr.GetValueAsUnsigned(0), + shape.GetLoadAddress()) + self.assertEquals(shape_ptr_vtable.GetNumChildren(), 4) + self.assertEquals(shape_ptr_vtable.GetName(), "vtable for Shape") + + @skipUnlessPlatform(["linux", "macosx"]) + def test_no_vtable(self): + self.build() + lldbutil.run_to_source_breakpoint( + self, "At the end", lldb.SBFileSpec("main.cpp") + ) + + var = self.frame().FindVariable("not_virtual") + self.assertEqual(var.GetVTable().GetError().GetCString(), + 'type "NotVirtual" doesn\'t have a vtable') + + var = self.frame().FindVariable("argc") + self.assertEqual(var.GetVTable().GetError().GetCString(), + 'type "int" is not a class or struct') + + def expected_vtable_addr(self, var: lldb.SBValue) -> int: + load_addr = var.GetLoadAddress() + read_from_memory_error = lldb.SBError() + vtable_addr = self.process().ReadPointerFromMemory( + load_addr, read_from_memory_error + ) + self.assertTrue(read_from_memory_error.Success()) + return vtable_addr + + def expected_vtable_entry_func_ptr(self, vtable_addr: int, idx: int): + vtable_entry_addr = vtable_addr + idx * self.process().GetAddressByteSize() + read_func_ptr_error = lldb.SBError() + func_ptr = self.process().ReadPointerFromMemory(vtable_entry_addr, + read_func_ptr_error) + self.assertTrue(read_func_ptr_error.Success()) + return func_ptr + + def verify_vtable_entry(self, vtable_entry: lldb.SBValue, vtable_addr: int, + idx: int): + """Verify the vtable entry looks something like: + + (double ()) [0] = 0x0000000100003a10 a.out`Rectangle::Area() at main.cpp:14 + + """ + # Check function ptr + vtable_entry_func_ptr = vtable_entry.GetValueAsUnsigned(0) + self.assertEquals( + vtable_entry_func_ptr, + self.expected_vtable_entry_func_ptr(vtable_addr, idx), + ) + + sb_addr = self.target().ResolveLoadAddress(vtable_entry_func_ptr) + sym_ctx = sb_addr.GetSymbolContext(lldb.eSymbolContextEverything) + + # Make sure the type is the same as the function type + self.assertEquals(vtable_entry.GetType(), sym_ctx.GetFunction().GetType()) + + # The summary should be the address description of the function pointer + summary = vtable_entry.GetSummary() + self.assertEquals(str(sb_addr), summary) diff --git a/lldb/test/API/functionalities/vtable/main.cpp b/lldb/test/API/functionalities/vtable/main.cpp new file mode 100644 index 000000000000000..7113eff343d43f8 --- /dev/null +++ b/lldb/test/API/functionalities/vtable/main.cpp @@ -0,0 +1,37 @@ +class Shape { +public: + virtual double Area() { return 1.0; } + virtual double Perimeter() { return 1.0; } + // Note that destructors generate two entries in the vtable: base object + // destructor and deleting destructor. + virtual ~Shape() = default; +}; + +class Rectangle : public Shape { +public: + ~Rectangle() override = default; + double Area() override { return 2.0; } + double Perimeter() override { return 2.0; } + virtual void RectangleOnly() {} + // This *shouldn't* show up in the vtable. + void RectangleSpecific() { return; } +}; + +// Make a class that looks like it would be virtual because the first ivar is +// a virtual class and if we inspect memory at the address of this class it +// would appear to be a virtual class. We need to make sure we don't get a +// valid vtable from this object. +class NotVirtual { + Rectangle m_rect; +public: + NotVirtual() = default; +}; + +int main(int argc, const char **argv) { + Shape shape; + Rectangle rect; + Shape *shape_ptr = ▭ + shape_ptr = &shape; // Shape is Rectangle + NotVirtual not_virtual; // Shape is Shape + return 0; // At the end +} >From 9d3e2750b7ee8df38055f2f251d8558d9570f1bc Mon Sep 17 00:00:00 2001 From: Greg Clayton <gclay...@fb.com> Date: Thu, 28 Sep 2023 11:14:28 -0700 Subject: [PATCH 2/2] Use LanguageRuntime to extract the vtable information by language. Also revert changes that allowed a bit size for function prototypes as it was affecting the test suite. We now create function prototype pointers as the type of each vtable child instead of just a function prototype (no pointer). --- lldb/include/lldb/API/SBValue.h | 12 +- lldb/include/lldb/Core/ValueObjectVTable.h | 2 +- lldb/include/lldb/Symbol/Type.h | 2 + lldb/include/lldb/Target/LanguageRuntime.h | 12 + lldb/source/Core/ValueObjectVTable.cpp | 123 +++---- .../ItaniumABI/ItaniumABILanguageRuntime.cpp | 335 ++++++++++-------- .../ItaniumABI/ItaniumABILanguageRuntime.h | 17 +- .../TypeSystem/Clang/TypeSystemClang.cpp | 9 - lldb/source/Symbol/Type.cpp | 4 + .../functionalities/vtable/TestVTableValue.py | 25 +- lldb/test/API/functionalities/vtable/main.cpp | 1 + 11 files changed, 298 insertions(+), 244 deletions(-) diff --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h index 333bdf1738eecaf..5e6e95e67a796b9 100644 --- a/lldb/include/lldb/API/SBValue.h +++ b/lldb/include/lldb/API/SBValue.h @@ -382,12 +382,18 @@ class LLDB_API SBValue { /// 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". + /// function table like "vtable for <classname>". /// /// 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::GetValueAtUnsigned(...) will return the address of the first + /// vtable entry. + /// + /// SBValue::GetLoadAddress() will return the address of the vtable pointer + /// found in the parent SBValue. + /// /// SBValue::GetSummary() will contain the number of virtual function pointers /// in the vtable like is done for arrays. /// @@ -398,8 +404,8 @@ class LLDB_API SBValue { /// 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. + /// function in debug info, the child type will be a pointer to the function + /// prototype. lldb::SBValue GetVTable(); protected: diff --git a/lldb/include/lldb/Core/ValueObjectVTable.h b/lldb/include/lldb/Core/ValueObjectVTable.h index faa0af238e764f4..6aaa40134240bdb 100644 --- a/lldb/include/lldb/Core/ValueObjectVTable.h +++ b/lldb/include/lldb/Core/ValueObjectVTable.h @@ -45,7 +45,7 @@ class ValueObjectVTable : public ValueObject { CompilerType GetCompilerTypeImpl() override; /// The symbol for the C++ virtual function table. - Symbol *m_vtable_symbol = nullptr; + const 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 diff --git a/lldb/include/lldb/Symbol/Type.h b/lldb/include/lldb/Symbol/Type.h index 046501931d211a7..c52eb5d6af0b662 100644 --- a/lldb/include/lldb/Symbol/Type.h +++ b/lldb/include/lldb/Symbol/Type.h @@ -427,6 +427,8 @@ class TypeAndOrName { void SetName(const char *type_name_cstr); + void SetName(llvm::StringRef name); + void SetTypeSP(lldb::TypeSP type_sp); void SetCompilerType(CompilerType compiler_type); diff --git a/lldb/include/lldb/Target/LanguageRuntime.h b/lldb/include/lldb/Target/LanguageRuntime.h index eff79a0bf0d0622..fd25476036640db 100644 --- a/lldb/include/lldb/Target/LanguageRuntime.h +++ b/lldb/include/lldb/Target/LanguageRuntime.h @@ -78,6 +78,18 @@ class LanguageRuntime : public Runtime, public PluginInterface { virtual bool GetObjectDescription(Stream &str, Value &value, ExecutionContextScope *exe_scope) = 0; + struct VTableInfo { + Address addr; /// Address of the vtable's virtual function table + Symbol *symbol; /// The vtable symbol from the symbol table + }; + // Get the vtable information for a given value. + // + // If the value doesn't represent something that has a vtable, then return + // std::nullopt. Else return the a valid VTableInfo structure. + virtual std::optional<VTableInfo> GetVTableInfo(ValueObject &in_value) { + return std::nullopt; + } + // this call should return true if it could set the name and/or the type virtual bool GetDynamicTypeAndAddress(ValueObject &in_value, lldb::DynamicValueType use_dynamic, diff --git a/lldb/source/Core/ValueObjectVTable.cpp b/lldb/source/Core/ValueObjectVTable.cpp index 31c619986b825f1..6d14571dcfb57f1 100644 --- a/lldb/source/Core/ValueObjectVTable.cpp +++ b/lldb/source/Core/ValueObjectVTable.cpp @@ -10,13 +10,12 @@ #include "lldb/Core/Module.h" #include "lldb/Core/ValueObjectChild.h" #include "lldb/Symbol/Function.h" +#include "lldb/Target/Language.h" +#include "lldb/Target/LanguageRuntime.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; @@ -84,13 +83,6 @@ class ValueObjectVTableChild : public ValueObject { 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. @@ -100,22 +92,28 @@ class ValueObjectVTableChild : public ValueObject { // 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(); + Function *function = nullptr; + Address resolved_vfunc_ptr_address; + target_sp->ResolveLoadAddress(vfunc_ptr, resolved_vfunc_ptr_address); + if (resolved_vfunc_ptr_address.IsValid()) + function = resolved_vfunc_ptr_address.CalculateSymbolContextFunction(); if (function) { - m_value.SetCompilerType(function->GetCompilerType()); + m_value.SetCompilerType(function->GetCompilerType().GetPointerType()); } 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); + + // Get the original type that this vtable is based off of so we can get + // the language from it correctly. + ValueObject *val = parent->GetParent(); + auto type_system = target_sp->GetScratchTypeSystemForLanguage( + val ? val->GetObjectRuntimeLanguage() : eLanguageTypeC_plus_plus); + if (type_system) { + m_value.SetCompilerType( + (*type_system)->CreateGenericFunctionPrototype().GetPointerType()); } else { - consumeError(type_system_or_err.takeError()); + consumeError(type_system.takeError()); } } @@ -206,7 +204,7 @@ bool ValueObjectVTable::UpdateValue() { return false; } - // Check to make sure the class has a vtable. + // 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. @@ -232,66 +230,36 @@ bool ValueObjectVTable::UpdateValue() { 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()) + const LanguageType language = parent->GetObjectRuntimeLanguage(); + LanguageRuntime *language_runtime = process_sp->GetLanguageRuntime(language); + + if (language_runtime == nullptr) { + m_error.SetErrorString("value doesn't have a vtable"); 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); + // Get the vtable information from the language runtime. + std::optional<LanguageRuntime::VTableInfo> opt_vtable_info = + language_runtime->GetVTableInfo(*parent); + if (!opt_vtable_info) { + m_error.SetErrorString("value doesn't have a vtable"); return false; } - m_vtable_symbol = - resolved_possible_vtable_address.CalculateSymbolContextSymbol(); - if (!(m_vtable_symbol && - m_vtable_symbol->GetName().GetStringRef().startswith("vtable for "))) { + TargetSP target_sp = GetTargetSP(); + const addr_t vtable_start_addr = + opt_vtable_info->addr.GetLoadAddress(target_sp.get()); + + m_vtable_symbol = opt_vtable_info->symbol; + if (!m_vtable_symbol) { m_error.SetErrorStringWithFormat( - "no vtable symbol found containing 0x%" PRIx64, possible_vtable_ptr); + "no vtable symbol found containing 0x%" PRIx64, vtable_start_addr); return false; } @@ -299,14 +267,21 @@ bool ValueObjectVTable::UpdateValue() { SetName(GetTypeName()); // Calculate the number of entries - assert(m_vtable_symbol->GetByteSizeIsValid()); + if (!m_vtable_symbol->GetByteSizeIsValid()) { + m_error.SetErrorStringWithFormat( + "vtable symbol \"%s\" doesn't have a valid size", + m_vtable_symbol->GetMangled().GetDemangledName().GetCString()); + return false; + } + m_addr_size = process_sp->GetAddressByteSize(); - addr_t symbol_end_addr = m_vtable_symbol->GetLoadAddress(target_sp.get()) + - m_vtable_symbol->GetByteSize(); - m_num_vtable_entries = (symbol_end_addr - possible_vtable_ptr) / m_addr_size; + const addr_t vtable_end_addr = + m_vtable_symbol->GetLoadAddress(target_sp.get()) + + m_vtable_symbol->GetByteSize(); + m_num_vtable_entries = (vtable_end_addr - vtable_start_addr) / m_addr_size; m_value.SetValueType(Value::ValueType::LoadAddress); - m_value.GetScalar() = parent_load_addr; + m_value.GetScalar() = parent->GetAddressOf(); auto type_system_or_err = target_sp->GetScratchTypeSystemForLanguage(eLanguageTypeC_plus_plus); if (type_system_or_err) { diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp index 711a696ff9b4d67..a9ab789703ac155 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.cpp @@ -54,134 +54,182 @@ bool ItaniumABILanguageRuntime::CouldHaveDynamicValue(ValueObject &in_value) { check_objc); } -TypeAndOrName ItaniumABILanguageRuntime::GetTypeInfoFromVTableAddress( - ValueObject &in_value, lldb::addr_t original_ptr, - lldb::addr_t vtable_load_addr) { - if (m_process && vtable_load_addr != LLDB_INVALID_ADDRESS) { - // Find the symbol that contains the "vtable_load_addr" address - Address vtable_addr; - Target &target = m_process->GetTarget(); - if (!target.GetSectionLoadList().IsEmpty()) { - if (target.GetSectionLoadList().ResolveLoadAddress(vtable_load_addr, - vtable_addr)) { - // See if we have cached info for this type already - TypeAndOrName type_info = GetDynamicTypeInfo(vtable_addr); - if (type_info) - return type_info; - - SymbolContext sc; - target.GetImages().ResolveSymbolContextForAddress( - vtable_addr, eSymbolContextSymbol, sc); - Symbol *symbol = sc.symbol; - if (symbol != nullptr) { - const char *name = - symbol->GetMangled().GetDemangledName().AsCString(); - if (name && strstr(name, vtable_demangled_prefix) == name) { - Log *log = GetLog(LLDBLog::Object); - LLDB_LOGF(log, - "0x%16.16" PRIx64 - ": static-type = '%s' has vtable symbol '%s'\n", - original_ptr, in_value.GetTypeName().GetCString(), name); - // We are a C++ class, that's good. Get the class name and look it - // up: - const char *class_name = name + strlen(vtable_demangled_prefix); - // We know the class name is absolute, so tell FindTypes that by - // prefixing it with the root namespace: - std::string lookup_name("::"); - lookup_name.append(class_name); - - type_info.SetName(class_name); - const bool exact_match = true; - TypeList class_types; - - // First look in the module that the vtable symbol came from and - // look for a single exact match. - llvm::DenseSet<SymbolFile *> searched_symbol_files; - if (sc.module_sp) - sc.module_sp->FindTypes(ConstString(lookup_name), exact_match, 1, +TypeAndOrName ItaniumABILanguageRuntime::GetTypeInfo( + ValueObject &in_value, const VTableInfo &vtable_info) { + if (vtable_info.addr.IsSectionOffset()) { + // See if we have cached info for this type already + TypeAndOrName type_info = GetDynamicTypeInfo(vtable_info.addr); + if (type_info) + return type_info; + + if (vtable_info.symbol) { + Log *log = GetLog(LLDBLog::Object); + llvm::StringRef symbol_name = + vtable_info.symbol->GetMangled().GetDemangledName().GetStringRef(); + LLDB_LOGF(log, + "0x%16.16" PRIx64 + ": static-type = '%s' has vtable symbol '%s'\n", + in_value.GetPointerValue(), + in_value.GetTypeName().GetCString(), + symbol_name.str().c_str()); + // We are a C++ class, that's good. Get the class name and look it + // up: + llvm::StringRef class_name = symbol_name; + class_name.consume_front(vtable_demangled_prefix); + // We know the class name is absolute, so tell FindTypes that by + // prefixing it with the root namespace: + std::string lookup_name("::"); + lookup_name.append(class_name.data(), class_name.size()); + + type_info.SetName(class_name); + const bool exact_match = true; + TypeList class_types; + + // First look in the module that the vtable symbol came from and + // look for a single exact match. + llvm::DenseSet<SymbolFile *> searched_symbol_files; + ModuleSP module_sp = vtable_info.symbol->CalculateSymbolContextModule(); + if (module_sp) + module_sp->FindTypes(ConstString(lookup_name), exact_match, 1, + searched_symbol_files, class_types); + + // If we didn't find a symbol, then move on to the entire module + // list in the target and get as many unique matches as possible + Target &target = m_process->GetTarget(); + if (class_types.Empty()) + target.GetImages().FindTypes(nullptr, ConstString(lookup_name), + exact_match, UINT32_MAX, searched_symbol_files, class_types); - // If we didn't find a symbol, then move on to the entire module - // list in the target and get as many unique matches as possible - if (class_types.Empty()) - target.GetImages().FindTypes(nullptr, ConstString(lookup_name), - exact_match, UINT32_MAX, - searched_symbol_files, class_types); - - lldb::TypeSP type_sp; - if (class_types.Empty()) { - LLDB_LOGF(log, "0x%16.16" PRIx64 ": is not dynamic\n", - original_ptr); - return TypeAndOrName(); + lldb::TypeSP type_sp; + if (class_types.Empty()) { + LLDB_LOGF(log, "0x%16.16" PRIx64 ": is not dynamic\n", + in_value.GetPointerValue()); + return TypeAndOrName(); + } + if (class_types.GetSize() == 1) { + type_sp = class_types.GetTypeAtIndex(0); + if (type_sp) { + if (TypeSystemClang::IsCXXClassType( + type_sp->GetForwardCompilerType())) { + LLDB_LOGF( + log, + "0x%16.16" PRIx64 + ": static-type = '%s' has dynamic type: uid={0x%" PRIx64 + "}, type-name='%s'\n", + in_value.GetPointerValue(), in_value.GetTypeName().AsCString(), + type_sp->GetID(), type_sp->GetName().GetCString()); + type_info.SetTypeSP(type_sp); + } + } + } else { + size_t i; + if (log) { + for (i = 0; i < class_types.GetSize(); i++) { + type_sp = class_types.GetTypeAtIndex(i); + if (type_sp) { + LLDB_LOGF( + log, + "0x%16.16" PRIx64 + ": static-type = '%s' has multiple matching dynamic " + "types: uid={0x%" PRIx64 "}, type-name='%s'\n", + in_value.GetPointerValue(), + in_value.GetTypeName().AsCString(), + type_sp->GetID(), type_sp->GetName().GetCString()); } - if (class_types.GetSize() == 1) { - type_sp = class_types.GetTypeAtIndex(0); - if (type_sp) { - if (TypeSystemClang::IsCXXClassType( - type_sp->GetForwardCompilerType())) { - LLDB_LOGF( - log, - "0x%16.16" PRIx64 - ": static-type = '%s' has dynamic type: uid={0x%" PRIx64 - "}, type-name='%s'\n", - original_ptr, in_value.GetTypeName().AsCString(), - type_sp->GetID(), type_sp->GetName().GetCString()); - type_info.SetTypeSP(type_sp); - } - } - } else { - size_t i; - if (log) { - for (i = 0; i < class_types.GetSize(); i++) { - type_sp = class_types.GetTypeAtIndex(i); - if (type_sp) { - LLDB_LOGF( - log, - "0x%16.16" PRIx64 - ": static-type = '%s' has multiple matching dynamic " - "types: uid={0x%" PRIx64 "}, type-name='%s'\n", - original_ptr, in_value.GetTypeName().AsCString(), - type_sp->GetID(), type_sp->GetName().GetCString()); - } - } - } - - for (i = 0; i < class_types.GetSize(); i++) { - type_sp = class_types.GetTypeAtIndex(i); - if (type_sp) { - if (TypeSystemClang::IsCXXClassType( - type_sp->GetForwardCompilerType())) { - LLDB_LOGF( - log, - "0x%16.16" PRIx64 ": static-type = '%s' has multiple " - "matching dynamic types, picking " - "this one: uid={0x%" PRIx64 "}, type-name='%s'\n", - original_ptr, in_value.GetTypeName().AsCString(), - type_sp->GetID(), type_sp->GetName().GetCString()); - type_info.SetTypeSP(type_sp); - } - } - } - - if (log) { - LLDB_LOGF(log, - "0x%16.16" PRIx64 - ": static-type = '%s' has multiple matching dynamic " - "types, didn't find a C++ match\n", - original_ptr, in_value.GetTypeName().AsCString()); - } + } + } + + for (i = 0; i < class_types.GetSize(); i++) { + type_sp = class_types.GetTypeAtIndex(i); + if (type_sp) { + if (TypeSystemClang::IsCXXClassType( + type_sp->GetForwardCompilerType())) { + LLDB_LOGF( + log, + "0x%16.16" PRIx64 ": static-type = '%s' has multiple " + "matching dynamic types, picking " + "this one: uid={0x%" PRIx64 "}, type-name='%s'\n", + in_value.GetPointerValue(), + in_value.GetTypeName().AsCString(), + type_sp->GetID(), type_sp->GetName().GetCString()); + type_info.SetTypeSP(type_sp); } - if (type_info) - SetDynamicTypeInfo(vtable_addr, type_info); - return type_info; } } + + if (log) { + LLDB_LOGF(log, + "0x%16.16" PRIx64 + ": static-type = '%s' has multiple matching dynamic " + "types, didn't find a C++ match\n", + in_value.GetPointerValue(), + in_value.GetTypeName().AsCString()); + } } + if (type_info) + SetDynamicTypeInfo(vtable_info.addr, type_info); + return type_info; } } return TypeAndOrName(); } +// This function can accept both pointers or references to classes as well +// as instances of classes. For dynamic type detection, only valid ValueObjects +// that return true to CouldHaveDynamicValue(...) should call this function. +// This function is also used by ValueObjectVTable and is can pass in +// instances of classes. + std::optional<LanguageRuntime::VTableInfo> + ItaniumABILanguageRuntime::GetVTableInfo(ValueObject &in_value) { + ExecutionContext exe_ctx(in_value.GetExecutionContextRef()); + Process *process = exe_ctx.GetProcessPtr(); + if (process == nullptr) + return std::nullopt; + + AddressType address_type; + lldb::addr_t original_ptr = LLDB_INVALID_ADDRESS; + if (in_value.GetCompilerType().IsPointerOrReferenceType()) + original_ptr = in_value.GetPointerValue(&address_type); + else + original_ptr = in_value.GetAddressOf(/*scalar_is_load_address=*/true, + &address_type); + if (original_ptr == LLDB_INVALID_ADDRESS || address_type != eAddressTypeLoad) + return std::nullopt; + + Status error; + const lldb::addr_t vtable_load_addr = + process->ReadPointerFromMemory(original_ptr, error); + + if (!error.Success() || vtable_load_addr == LLDB_INVALID_ADDRESS) + return std::nullopt; + + // Find the symbol that contains the "vtable_load_addr" address + Address vtable_addr; + if (!process->GetTarget().ResolveLoadAddress(vtable_load_addr, vtable_addr)) + return std::nullopt; + + // Check our cache first to see if we already have this info + { + std::lock_guard<std::mutex> locker(m_mutex); + auto pos = m_vtable_info_map.find(vtable_addr); + if (pos != m_vtable_info_map.end()) + return pos->second; + } + + Symbol *symbol = vtable_addr.CalculateSymbolContextSymbol(); + if (symbol == nullptr) + return std::nullopt; + llvm::StringRef name = symbol->GetMangled().GetDemangledName().GetStringRef(); + if (name.startswith(vtable_demangled_prefix)) { + VTableInfo info = {vtable_addr, symbol}; + std::lock_guard<std::mutex> locker(m_mutex); + auto pos = m_vtable_info_map[vtable_addr] = info; + return info; + } + return std::nullopt; +} + bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress( ValueObject &in_value, lldb::DynamicValueType use_dynamic, TypeAndOrName &class_type_or_name, Address &dynamic_address, @@ -198,33 +246,17 @@ bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress( class_type_or_name.Clear(); value_type = Value::ValueType::Scalar; - // Only a pointer or reference type can have a different dynamic and static - // type: if (!CouldHaveDynamicValue(in_value)) return false; - // First job, pull out the address at 0 offset from the object. - AddressType address_type; - lldb::addr_t original_ptr = in_value.GetPointerValue(&address_type); - if (original_ptr == LLDB_INVALID_ADDRESS) + // Check if we have a vtable pointer in this value. If we don't it will + // return std::nullopt, else it will return a valid resolved address. + std::optional<VTableInfo> opt_vtable_info = GetVTableInfo(in_value); + if (!opt_vtable_info) return false; - ExecutionContext exe_ctx(in_value.GetExecutionContextRef()); - - Process *process = exe_ctx.GetProcessPtr(); - - if (process == nullptr) - return false; - - Status error; - const lldb::addr_t vtable_address_point = - process->ReadPointerFromMemory(original_ptr, error); - - if (!error.Success() || vtable_address_point == LLDB_INVALID_ADDRESS) - return false; - - class_type_or_name = GetTypeInfoFromVTableAddress(in_value, original_ptr, - vtable_address_point); + const VTableInfo &vtable_info = opt_vtable_info.value(); + class_type_or_name = GetTypeInfo(in_value, vtable_info); if (!class_type_or_name) return false; @@ -244,22 +276,27 @@ bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress( } // The offset_to_top is two pointers above the vtable pointer. - const uint32_t addr_byte_size = process->GetAddressByteSize(); + Target &target = m_process->GetTarget(); + const addr_t vtable_load_addr = vtable_info.addr.GetLoadAddress(&target); + if (vtable_load_addr == LLDB_INVALID_ADDRESS) + return false; + const uint32_t addr_byte_size = m_process->GetAddressByteSize(); const lldb::addr_t offset_to_top_location = - vtable_address_point - 2 * addr_byte_size; + vtable_load_addr - 2 * addr_byte_size; // Watch for underflow, offset_to_top_location should be less than - // vtable_address_point - if (offset_to_top_location >= vtable_address_point) + // vtable_load_addr + if (offset_to_top_location >= vtable_load_addr) return false; - const int64_t offset_to_top = process->ReadSignedIntegerFromMemory( + Status error; + const int64_t offset_to_top = m_process->ReadSignedIntegerFromMemory( offset_to_top_location, addr_byte_size, INT64_MIN, error); if (offset_to_top == INT64_MIN) return false; // So the dynamic type is a value that starts at offset_to_top above // the original address. - lldb::addr_t dynamic_addr = original_ptr + offset_to_top; - if (!process->GetTarget().GetSectionLoadList().ResolveLoadAddress( + lldb::addr_t dynamic_addr = in_value.GetPointerValue() + offset_to_top; + if (!m_process->GetTarget().ResolveLoadAddress( dynamic_addr, dynamic_address)) { dynamic_address.SetRawAddress(dynamic_addr); } @@ -583,10 +620,10 @@ ValueObjectSP ItaniumABILanguageRuntime::GetExceptionObjectForThread( ValueObjectSP exception = ValueObject::CreateValueObjectFromData( "exception", exception_isw.GetAsData(m_process->GetByteOrder()), exe_ctx, voidstar); - ValueObjectSP dyn_exception + ValueObjectSP dyn_exception = exception->GetDynamicValue(eDynamicDontRunTarget); // If we succeed in making a dynamic value, return that: - if (dyn_exception) + if (dyn_exception) return dyn_exception; return exception; @@ -594,7 +631,7 @@ ValueObjectSP ItaniumABILanguageRuntime::GetExceptionObjectForThread( TypeAndOrName ItaniumABILanguageRuntime::GetDynamicTypeInfo( const lldb_private::Address &vtable_addr) { - std::lock_guard<std::mutex> locker(m_dynamic_type_map_mutex); + std::lock_guard<std::mutex> locker(m_mutex); DynamicTypeCache::const_iterator pos = m_dynamic_type_map.find(vtable_addr); if (pos == m_dynamic_type_map.end()) return TypeAndOrName(); @@ -604,6 +641,6 @@ TypeAndOrName ItaniumABILanguageRuntime::GetDynamicTypeInfo( void ItaniumABILanguageRuntime::SetDynamicTypeInfo( const lldb_private::Address &vtable_addr, const TypeAndOrName &type_info) { - std::lock_guard<std::mutex> locker(m_dynamic_type_map_mutex); + std::lock_guard<std::mutex> locker(m_mutex); m_dynamic_type_map[vtable_addr] = type_info; } diff --git a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h index ca8d5ab1a93a1b9..f6afc7a08a32db9 100644 --- a/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h +++ b/lldb/source/Plugins/LanguageRuntime/CPlusPlus/ItaniumABI/ItaniumABILanguageRuntime.h @@ -47,6 +47,9 @@ class ItaniumABILanguageRuntime : public lldb_private::CPPLanguageRuntime { return runtime->isA(&ID); } + std::optional<LanguageRuntime::VTableInfo> + GetVTableInfo(ValueObject &in_value) override; + bool GetDynamicTypeAndAddress(ValueObject &in_value, lldb::DynamicValueType use_dynamic, TypeAndOrName &class_type_or_name, @@ -71,7 +74,7 @@ class ItaniumABILanguageRuntime : public lldb_private::CPPLanguageRuntime { bool catch_bp, bool throw_bp) override; lldb::SearchFilterSP CreateExceptionSearchFilter() override; - + lldb::ValueObjectSP GetExceptionObjectForThread( lldb::ThreadSP thread_sp) override; @@ -89,19 +92,19 @@ class ItaniumABILanguageRuntime : public lldb_private::CPPLanguageRuntime { private: typedef std::map<lldb_private::Address, TypeAndOrName> DynamicTypeCache; + typedef std::map<lldb_private::Address, VTableInfo> VTableInfoCache; ItaniumABILanguageRuntime(Process *process) : // Call CreateInstance instead. - lldb_private::CPPLanguageRuntime(process), m_cxx_exception_bp_sp(), - m_dynamic_type_map(), m_dynamic_type_map_mutex() {} + lldb_private::CPPLanguageRuntime(process) {} lldb::BreakpointSP m_cxx_exception_bp_sp; DynamicTypeCache m_dynamic_type_map; - std::mutex m_dynamic_type_map_mutex; + VTableInfoCache m_vtable_info_map; + std::mutex m_mutex; - TypeAndOrName GetTypeInfoFromVTableAddress(ValueObject &in_value, - lldb::addr_t original_ptr, - lldb::addr_t vtable_addr); + TypeAndOrName GetTypeInfo(ValueObject &in_value, + const VTableInfo &vtable_info); TypeAndOrName GetDynamicTypeInfo(const lldb_private::Address &vtable_addr); diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp index 37ba8b0f6e2afcf..27807e4ccf681da 100644 --- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp +++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp @@ -4748,15 +4748,6 @@ TypeSystemClang::GetBitSize(lldb::opaque_compiler_type_t type, return std::nullopt; break; - case clang::Type::FunctionProto: - case clang::Type::FunctionNoProto: { - ExecutionContext exe_ctx(exe_scope); - Process *process = exe_ctx.GetProcessPtr(); - if (process) - return process->GetAddressByteSize() * 8; - break; - } - case clang::Type::ObjCInterface: case clang::Type::ObjCObject: { ExecutionContext exe_ctx(exe_scope); diff --git a/lldb/source/Symbol/Type.cpp b/lldb/source/Symbol/Type.cpp index 66284eb73cad038..2ed4717467587ea 100644 --- a/lldb/source/Symbol/Type.cpp +++ b/lldb/source/Symbol/Type.cpp @@ -790,6 +790,10 @@ void TypeAndOrName::SetName(const char *type_name_cstr) { m_type_name.SetCString(type_name_cstr); } +void TypeAndOrName::SetName(llvm::StringRef type_name) { + m_type_name.SetString(type_name); +} + void TypeAndOrName::SetTypeSP(lldb::TypeSP type_sp) { if (type_sp) { m_compiler_type = type_sp->GetForwardCompilerType(); diff --git a/lldb/test/API/functionalities/vtable/TestVTableValue.py b/lldb/test/API/functionalities/vtable/TestVTableValue.py index ce3da0a2185f501..c1d207371aa6b29 100644 --- a/lldb/test/API/functionalities/vtable/TestVTableValue.py +++ b/lldb/test/API/functionalities/vtable/TestVTableValue.py @@ -21,6 +21,7 @@ def test_vtable(self): self, "At the end", lldb.SBFileSpec("main.cpp") ) + # Test a shape instance to make sure we get the vtable correctly. shape = self.frame().FindVariable("shape") vtable = shape.GetVTable() self.assertEquals(vtable.GetName(), "vtable for Shape") @@ -37,6 +38,25 @@ def test_vtable(self): for (idx, vtable_entry) in enumerate(vtable.children): self.verify_vtable_entry(vtable_entry, vtable_addr, idx) + # Test a shape reference to make sure we get the vtable correctly. + shape = self.frame().FindVariable("shape_ref") + vtable = shape.GetVTable() + self.assertEquals(vtable.GetName(), "vtable for Shape") + self.assertEquals(vtable.GetTypeName(), "vtable for Shape") + # Make sure we have the right number of virtual functions in our vtable + # for the shape class. + self.assertEquals(vtable.GetNumChildren(), 4) + + # Verify vtable address + vtable_addr = vtable.GetValueAsUnsigned(0) + expected_addr = self.expected_vtable_addr(shape) + self.assertEquals(vtable_addr, expected_addr) + + for (idx, vtable_entry) in enumerate(vtable.children): + self.verify_vtable_entry(vtable_entry, vtable_addr, idx) + + + # Test we get the right vtable for the Rectangle instance. rect = self.frame().FindVariable("rect") vtable = rect.GetVTable() self.assertEquals(vtable.GetName(), "vtable for Rectangle") @@ -128,7 +148,10 @@ def verify_vtable_entry(self, vtable_entry: lldb.SBValue, vtable_addr: int, sym_ctx = sb_addr.GetSymbolContext(lldb.eSymbolContextEverything) # Make sure the type is the same as the function type - self.assertEquals(vtable_entry.GetType(), sym_ctx.GetFunction().GetType()) + func_type = sym_ctx.GetFunction().GetType() + if func_type.IsValid(): + self.assertEquals(vtable_entry.GetType(), + func_type.GetPointerType()) # The summary should be the address description of the function pointer summary = vtable_entry.GetSummary() diff --git a/lldb/test/API/functionalities/vtable/main.cpp b/lldb/test/API/functionalities/vtable/main.cpp index 7113eff343d43f8..498a5765a3f6ff7 100644 --- a/lldb/test/API/functionalities/vtable/main.cpp +++ b/lldb/test/API/functionalities/vtable/main.cpp @@ -31,6 +31,7 @@ int main(int argc, const char **argv) { Shape shape; Rectangle rect; Shape *shape_ptr = ▭ + Shape &shape_ref = shape; shape_ptr = &shape; // Shape is Rectangle NotVirtual not_virtual; // Shape is Shape return 0; // At the end _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits