Add the ability to get a C++ vtable ValueObject from another ValueObj… (#67599)

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
```
This commit is contained in:
Greg Clayton
2023-10-30 17:46:18 -07:00
committed by GitHub
parent 6eafe2cb7a
commit 7fbd427f5e
24 changed files with 1015 additions and 180 deletions

View File

@@ -139,19 +139,6 @@ SBType supports the eq/ne operator. For example,::
"
) lldb::SBType::IsReferenceType;
%feature("docstring",
"Returns true if this type is a function type.
Language-specific behaviour:
* C: Returns true for types that represent functions. Note that function
pointers are not function types (but their `GetPointeeType()` are function
types).
* C++: Same as in C.
* Objective-C: Returns false for all types.
"
) lldb::SBType::IsPolymorphicClass;
%feature("docstring",
"Returns true if this type is a polymorphic type.

View File

@@ -374,6 +374,52 @@ public:
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 <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::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 will have the following values:
///
/// SBValue::GetError() will indicate success if the vtable entry was
/// successfully read from memory, or an error if not.
///
/// SBValue::GetName() will be the vtable function index in the form "[%u]"
/// where %u is the index.
///
/// SBValue::GetValue() will be the virtual function pointer value as a
/// string.
///
/// SBValue::GetValueAtUnsigned(...) will return the virtual function
/// pointer value.
///
/// SBValue::GetLoadAddress() will return the address of the virtual function
/// pointer.
///
/// SBValue::GetNumChildren() returns 0
lldb::SBValue GetVTable();
protected:
friend class SBBlock;
friend class SBFrame;

View File

@@ -620,6 +620,10 @@ public:
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() {

View File

@@ -73,6 +73,7 @@ protected:
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,

View File

@@ -0,0 +1,105 @@
//===-- 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.
///
/// ValueObject::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.
///
/// ValueObject::GetName() will be the demangled symbol name for the virtual
/// function table like "vtable for <classname>".
///
/// ValueObject::GetValueAsCString() will be the address of the first vtable
/// entry if the current ValueObject is a class with a vtable, or nothing the
/// current ValueObject is not a C++ class or not a C++ class that has a
/// vtable.
///
/// ValueObject::GetValueAtUnsigned(...) will return the address of the first
/// vtable entry.
///
/// ValueObject::GetAddressOf() will return the address of the vtable pointer
/// found in the parent ValueObject.
///
/// ValueObject::GetNumChildren() will return the number of virtual function
/// pointers in the vtable, or zero on error.
///
/// ValueObject::GetChildAtIndex(...) will return each virtual function pointer
/// as a ValueObject object.
///
/// The child ValueObjects will have the following values:
///
/// ValueObject::GetError() will indicate success if the vtable entry was
/// successfully read from memory, or an error if not.
///
/// ValueObject::GetName() will be the vtable function index in the form "[%u]"
/// where %u is the index.
///
/// ValueObject::GetValueAsCString() will be the virtual function pointer value
///
/// ValueObject::GetValueAtUnsigned(...) will return the virtual function
/// pointer value.
///
/// ValueObject::GetAddressOf() will return the address of the virtual function
/// pointer.
///
/// ValueObject::GetNumChildren() returns 0
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.
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
/// 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

View File

@@ -420,6 +420,8 @@ public:
void SetName(const char *type_name_cstr);
void SetName(llvm::StringRef name);
void SetTypeSP(lldb::TypeSP type_sp);
void SetCompilerType(CompilerType compiler_type);

View File

@@ -437,6 +437,10 @@ public:
virtual CompilerType GetBasicTypeFromAST(lldb::BasicType basic_type) = 0;
virtual CompilerType CreateGenericFunctionPrototype() {
return CompilerType();
}
virtual CompilerType
GetBuiltinTypeForEncodingAndBitSize(lldb::Encoding encoding,
size_t bit_size) = 0;

View File

@@ -78,6 +78,32 @@ public:
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.
///
/// \param[in] in_value
/// The value object to try and extract the VTableInfo from.
///
/// \param[in] check_type
/// If true, the compiler type of \a in_value will be checked to see if
/// it is an instance to, or pointer or reference to a class or struct
/// that has a vtable. If the type doesn't meet the requirements, an
/// error will be returned explaining why the type isn't suitable.
///
/// \return
/// An error if anything goes wrong while trying to extract the vtable
/// or if \a check_type is true and the type doesn't have a vtable.
virtual llvm::Expected<VTableInfo> GetVTableInfo(ValueObject &in_value,
bool check_type) {
return llvm::createStringError(
std::errc::invalid_argument,
"language doesn't support getting vtable information");
}
// 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,

View File

@@ -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.

View File

@@ -114,7 +114,7 @@ public:
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();
@@ -1505,3 +1505,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;
}

View File

@@ -490,6 +490,8 @@ protected:
case eValueTypeRegisterSet:
case eValueTypeConstResult:
case eValueTypeVariableThreadLocal:
case eValueTypeVTable:
case eValueTypeVTableEntry:
return false;
}
}

View File

@@ -71,6 +71,7 @@ add_lldb_library(lldbCore
ValueObjectSyntheticFilter.cpp
ValueObjectUpdater.cpp
ValueObjectVariable.cpp
ValueObjectVTable.cpp
DEPENDS
clang-tablegen-targets

View File

@@ -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);
}

View File

@@ -0,0 +1,274 @@
//===-- 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/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"
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 {
if (ValueObject *parent = GetParent())
return parent->IsInScope();
return false;
};
protected:
bool UpdateValue() override {
SetValueIsValid(false);
m_value.Clear();
ValueObject *parent = GetParent();
if (!parent) {
m_error.SetErrorString("owning vtable object not valid");
return false;
}
addr_t parent_addr = parent->GetValueAsUnsigned(LLDB_INVALID_ADDRESS);
if (parent_addr == LLDB_INVALID_ADDRESS) {
m_error.SetErrorString("invalid vtable 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;
}
// 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 = 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().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.
// 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.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();
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;
}
ProcessSP process_sp = GetProcessSP();
if (!process_sp) {
m_error.SetErrorString("no process");
return false;
}
const LanguageType language = parent->GetObjectRuntimeLanguage();
LanguageRuntime *language_runtime = process_sp->GetLanguageRuntime(language);
if (language_runtime == nullptr) {
m_error.SetErrorStringWithFormat(
"no language runtime support for the language \"%s\"",
Language::GetNameForLanguageType(language));
return false;
}
// Get the vtable information from the language runtime.
llvm::Expected<LanguageRuntime::VTableInfo> vtable_info_or_err =
language_runtime->GetVTableInfo(*parent, /*check_type=*/true);
if (!vtable_info_or_err) {
m_error = vtable_info_or_err.takeError();
return false;
}
TargetSP target_sp = GetTargetSP();
const addr_t vtable_start_addr =
vtable_info_or_err->addr.GetLoadAddress(target_sp.get());
m_vtable_symbol = vtable_info_or_err->symbol;
if (!m_vtable_symbol) {
m_error.SetErrorStringWithFormat(
"no vtable symbol found containing 0x%" PRIx64, vtable_start_addr);
return false;
}
// Now that we know it's a vtable, we update the object's state.
SetName(GetTypeName());
// Calculate the number of entries
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();
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->GetAddressOf();
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;

View File

@@ -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;

View File

@@ -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;
@@ -1415,7 +1416,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;

View File

@@ -54,134 +54,228 @@ 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;
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;
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;
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());
// 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,
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();
}
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());
}
}
if (type_info)
SetDynamicTypeInfo(vtable_addr, type_info);
return type_info;
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());
}
}
}
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 (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();
}
llvm::Error ItaniumABILanguageRuntime::TypeHasVTable(CompilerType type) {
// Check to make sure the class has a vtable.
CompilerType original_type = type;
if (type.IsPointerOrReferenceType()) {
CompilerType pointee_type = type.GetPointeeType();
if (pointee_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,
"type \"%s\" doesn't have a vtable",
type.GetTypeName().AsCString("<invalid>"));
}
return llvm::Error::success();
}
// This function can accept both pointers or references to classes as well as
// instances of classes. If you are using this function during dynamic type
// detection, only valid ValueObjects that return true to
// CouldHaveDynamicValue(...) should call this function and \a check_type
// should be set to false. This function is also used by ValueObjectVTable
// and is can pass in instances of classes which is not suitable for dynamic
// type detection, these cases should pass true for \a check_type.
llvm::Expected<LanguageRuntime::VTableInfo>
ItaniumABILanguageRuntime::GetVTableInfo(ValueObject &in_value,
bool check_type) {
CompilerType type = in_value.GetCompilerType();
if (check_type) {
if (llvm::Error err = TypeHasVTable(type))
return std::move(err);
}
ExecutionContext exe_ctx(in_value.GetExecutionContextRef());
Process *process = exe_ctx.GetProcessPtr();
if (process == nullptr)
return llvm::createStringError(std::errc::invalid_argument,
"invalid process");
AddressType address_type;
lldb::addr_t original_ptr = LLDB_INVALID_ADDRESS;
if (type.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 llvm::createStringError(std::errc::invalid_argument,
"failed to get the address of the value");
Status error;
const lldb::addr_t vtable_load_addr =
process->ReadPointerFromMemory(original_ptr, error);
if (!error.Success() || vtable_load_addr == LLDB_INVALID_ADDRESS)
return llvm::createStringError(std::errc::invalid_argument,
"failed to read vtable pointer from memory at 0x%" PRIx64,
original_ptr);
;
// Find the symbol that contains the "vtable_load_addr" address
Address vtable_addr;
if (!process->GetTarget().ResolveLoadAddress(vtable_load_addr, vtable_addr))
return llvm::createStringError(std::errc::invalid_argument,
"failed to resolve vtable pointer 0x%"
PRIx64 "to a section", vtable_load_addr);
// 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 llvm::createStringError(std::errc::invalid_argument,
"no symbol found for 0x%" PRIx64,
vtable_load_addr);
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 llvm::createStringError(std::errc::invalid_argument,
"symbol found that contains 0x%" PRIx64 " is not a vtable symbol",
vtable_load_addr);
}
bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
ValueObject &in_value, lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name, Address &dynamic_address,
@@ -198,33 +292,23 @@ 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 an error, else it will return a valid resolved address. We don't
// want GetVTableInfo to check the type since we accept void * as a possible
// dynamic type and that won't pass the type check. We already checked the
// type above in CouldHaveDynamicValue(...).
llvm::Expected<VTableInfo> vtable_info_or_err =
GetVTableInfo(in_value, /*check_type=*/false);
if (!vtable_info_or_err) {
llvm::consumeError(vtable_info_or_err.takeError());
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 = vtable_info_or_err.get();
class_type_or_name = GetTypeInfo(in_value, vtable_info);
if (!class_type_or_name)
return false;
@@ -244,22 +328,27 @@ bool ItaniumABILanguageRuntime::GetDynamicTypeAndAddress(
}
// The offset_to_top is two pointers above the vtable pointer.
const uint32_t addr_byte_size = process->GetAddressByteSize();
const lldb::addr_t offset_to_top_location =
vtable_address_point - 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)
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 int64_t offset_to_top = process->ReadSignedIntegerFromMemory(
const uint32_t addr_byte_size = m_process->GetAddressByteSize();
const lldb::addr_t offset_to_top_location =
vtable_load_addr - 2 * addr_byte_size;
// Watch for underflow, offset_to_top_location should be less than
// vtable_load_addr
if (offset_to_top_location >= vtable_load_addr)
return false;
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);
}
@@ -582,10 +671,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;
@@ -593,7 +682,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();
@@ -603,6 +692,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;
}

View File

@@ -47,6 +47,10 @@ public:
return runtime->isA(&ID);
}
llvm::Expected<LanguageRuntime::VTableInfo>
GetVTableInfo(ValueObject &in_value, bool check_type) override;
bool GetDynamicTypeAndAddress(ValueObject &in_value,
lldb::DynamicValueType use_dynamic,
TypeAndOrName &class_type_or_name,
@@ -71,7 +75,7 @@ public:
bool catch_bp, bool throw_bp) override;
lldb::SearchFilterSP CreateExceptionSearchFilter() override;
lldb::ValueObjectSP GetExceptionObjectForThread(
lldb::ThreadSP thread_sp) override;
@@ -89,24 +93,33 @@ protected:
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);
void SetDynamicTypeInfo(const lldb_private::Address &vtable_addr,
const TypeAndOrName &type_info);
// Check if a compiler type has a vtable.
//
// If the compiler type is a pointer or a reference, this function will check
// if the pointee type has a vtable, else it will check the type passed in.
//
// Returns an error if the type of the value doesn't have a vtable with an
// explanation why, or returns an Error::success() if the type has a vtable.
llvm::Error TypeHasVTable(CompilerType compiler_type);
};
} // namespace lldb_private

View File

@@ -3557,8 +3557,15 @@ bool TypeSystemClang::IsPolymorphicClass(lldb::opaque_compiler_type_t type) {
if (record_decl) {
const clang::CXXRecordDecl *cxx_record_decl =
llvm::dyn_cast<clang::CXXRecordDecl>(record_decl);
if (cxx_record_decl)
return cxx_record_decl->isPolymorphic();
if (cxx_record_decl) {
// We can't just call is isPolymorphic() here because that just
// means the current class has virtual functions, it doesn't check
// if any inherited classes have virtual functions. The doc string
// in SBType::IsPolymorphicClass() says it is looking for both
// if the class has virtual methods or if any bases do, so this
// should be more correct.
return cxx_record_decl->isDynamicClass();
}
}
}
break;
@@ -4708,6 +4715,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 &
@@ -4824,7 +4846,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:

View File

@@ -802,6 +802,10 @@ public:
// 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;

View File

@@ -748,6 +748,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();

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -0,0 +1,186 @@
"""
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")
)
# 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")
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 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")
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(),
'no language runtime support for the language "c"')
@skipUnlessPlatform(["linux", "macosx"])
def test_overwrite_vtable(self):
self.build()
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
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")
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)
# Overwrite the first entry in the vtable and make sure we can still
# see the bogus value which should have no summary
vtable_addr = vtable.GetValueAsUnsigned()
data = str("\x01\x01\x01\x01\x01\x01\x01\x01")
error = lldb.SBError()
process.WriteMemory(vtable_addr, data, error)
scribbled_child = vtable.GetChildAtIndex(0)
self.assertEquals(scribbled_child.GetValueAsUnsigned(0),
0x0101010101010101)
self.assertEquals(scribbled_child.GetSummary(), None)
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
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()
self.assertEquals(str(sb_addr), summary)

View File

@@ -0,0 +1,38 @@
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 = &rect;
Shape &shape_ref = shape;
shape_ptr = &shape; // Shape is Rectangle
NotVirtual not_virtual; // Shape is Shape
return 0; // At the end
}