[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 16:04:34 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:

That's not quite what I was saying.  The Dynamic Type Detection code in the ItaniumLangaugeRuntime has two phases.  First, given a ValueObject, it has to find its vtable pointer.  Then given the vtable pointer, it gets the dynamic class, figures out the offset to the most specific type and produces the dynamic value.  That's all in one function right now.  I was suggesting pulling out the part of the runtime code that gets the vtable address into a new function in the LanguageRuntime:

lldb::addr_t GetVTablePointer(ValueObject &vo);

implementing that in the ItaniumLanguageRuntime, and then rejiggering both GetDynamicValueAndType and your VTable Update to use this new function.

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


More information about the lldb-commits mailing list