[Lldb-commits] [lldb] Add the ability to get a C++ vtable ValueObject from another ValueObj… (PR #67599)

via lldb-commits lldb-commits at lists.llvm.org
Wed Sep 27 14:46:11 PDT 2023


================
@@ -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.
----------------
jimingham wrote:

The C++ Language Runtime has to find the VTable pointer so it can determine the dynamic type of the ValueObject, can we reuse that code here?

https://github.com/llvm/llvm-project/pull/67599


More information about the lldb-commits mailing list