[Lldb-commits] [lldb] [llvm] [lldb] Add initial MicrosoftABIRuntime (PR #235090)

Charles Zablit via lldb-commits lldb-commits at lists.llvm.org
Thu Oct 29 17:53:44 PDT 2026


================
@@ -0,0 +1,426 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "MicrosoftABIRuntime.h"
+
+#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "clang/AST/RecordLayout.h"
+
+#include <deque>
+#include <mutex>
+
+using namespace lldb;
+using namespace lldb_private;
+
+static const llvm::StringRef s_vtable_demangled_indicator = "::`vftable'";
+static const llvm::StringRef s_rtti_complete_object_locator_indicator =
+    "::`RTTI Complete Object Locator'";
+
+static llvm::StringRef StripDemangledVTableSuffix(llvm::StringRef demangled) {
+  llvm::StringRef name = demangled.split(s_vtable_demangled_indicator).first;
+  name.consume_front("const ");
+  return name;
+}
+
+static bool
+SplitVTableComponents(llvm::StringRef demangled,
+                      llvm::SmallVectorImpl<llvm::StringRef> &components) {
+  llvm::StringRef suffix = demangled.split(s_vtable_demangled_indicator).second;
+  if (suffix.empty())
+    return true; // e.g. MyClass::`vftable'
+
+  // Otherwise, suffix looks like "{for `MyBase2's `MyBase1'}" or
+  // "{for `MyBase'}".
+  if (!suffix.consume_front("{for `") || !suffix.consume_back("'}"))
+    return false;
+
+  suffix.split(components, "'s `");
+  return true;
+}
+
+MicrosoftABIRuntime::MicrosoftABIRuntime(Process *process)
+    : CommonABIRuntime(process) {}
+
+bool MicrosoftABIRuntime::GetDynamicTypeAndAddress(
+    ValueObject &in_value, lldb::DynamicValueType use_dynamic,
+    const LanguageRuntime::VTableInfo &vtable_info,
+    TypeAndOrName &class_type_or_name, Address &dynamic_address) {
+  std::optional<size_t> offset_to_top =
+      GetOffsetToTop(in_value, class_type_or_name, vtable_info);
+  if (!offset_to_top)
+    return false;
+
+  lldb::addr_t dynamic_addr =
+      in_value.GetPointerValue().address - *offset_to_top;
+  if (!m_process->GetTarget().ResolveLoadAddress(dynamic_addr, dynamic_address))
+    dynamic_address.SetRawAddress(dynamic_addr);
+
+  return true;
+}
+
+bool MicrosoftABIRuntime::IsVTableSymbol(Mangled &mangled) const {
+  return mangled.GetDemangledName().GetStringRef().contains(
+      s_vtable_demangled_indicator);
+}
+
+TypeAndOrName MicrosoftABIRuntime::GetTypeInfo(
+    ValueObject &in_value, const LanguageRuntime::VTableInfo &vtable_info,
+    llvm::SmallVectorImpl<llvm::StringRef> &vtable_components) {
+  if (!vtable_info.addr.IsSectionOffset() || !vtable_info.symbol)
+    return {};
+
+  // See if we have cached info for this type already
+  TypeAndOrName type_info = GetDynamicTypeInfo(vtable_info.addr);
+  if (type_info)
+    return type_info;
+
+  Log *log = GetLog(LLDBLog::Object);
+  llvm::StringRef symbol_name =
+      vtable_info.symbol->GetMangled().GetDemangledName().GetStringRef();
+  if (!SplitVTableComponents(symbol_name, vtable_components)) {
+    LLDB_LOG(log, "{0:x16}: '{1}' is not a valid vtable symbol",
+             in_value.GetPointerValue().address, symbol_name);
+    return {};
+  }
+
+  llvm::StringRef class_name = StripDemangledVTableSuffix(symbol_name);
+
+  type_info.SetName(class_name);
+  bool any_found = false;
+  TypeSP type_sp = LookupTypeByName(
+      class_name, vtable_info.symbol->CalculateSymbolContextModule(),
+      any_found);
+  if (!any_found)
+    return TypeAndOrName(); // Type is not dynamic.
+
+  if (type_sp) {
+    LLDB_LOG(log,
+             "static-type = '{0}' has dynamic type: uid={1:x}, type-name='{2}'",
+             in_value.GetTypeName(), type_sp->GetID(), type_sp->GetName());
+    type_info.SetTypeSP(std::move(type_sp));
+  }
+
+  if (type_info)
+    SetDynamicTypeInfo(vtable_info.addr, type_info);
+  return type_info;
+}
+
+std::optional<int64_t> MicrosoftABIRuntime::GetOffsetToTop(
+    ValueObject &in_value, TypeAndOrName &class_type_or_name,
+    const LanguageRuntime::VTableInfo &vtable_info) {
+  Log *log = GetLog(LLDBLog::Object);
+
+  llvm::SmallVector<llvm::StringRef, 2> vtable_components;
+  class_type_or_name = GetTypeInfo(in_value, vtable_info, vtable_components);
+  if (!class_type_or_name) {
+    LLDB_LOG(log, "{0:x16}: failed to get type info",
+             in_value.GetPointerValue().address);
+    return std::nullopt;
+  }
+
+  {
+    std::scoped_lock<std::mutex> g(m_mutex);
+    auto offset_to_top_it = m_offset_to_top_map.find(vtable_info.addr);
+    if (offset_to_top_it != m_offset_to_top_map.end())
+      return offset_to_top_it->second;
+  }
+
+  CompilerType type = class_type_or_name.GetCompilerType();
+  if (!type) {
+    LLDB_LOG(log, "{0:x16}: failed to get type info (no compiler type)",
+             in_value.GetPointerValue().address);
+    return std::nullopt;
+  }
+
+  CompilerType in_type = in_value.GetCompilerType();
+  if (in_type.IsPointerOrReferenceType())
+    in_type = in_type.GetPointeeType();
+  if (!in_type.GetCompleteType()) {
+    LLDB_LOG(log, "{0:x16}: {1} is not a complete type",
+             in_value.GetPointerValue().address, in_type.GetTypeName());
+    return std::nullopt;
+  }
+  if (!type.GetCompleteType()) {
+    LLDB_LOG(log, "{0:x16}: {1} is not a complete type",
+             in_value.GetPointerValue().address,
+             type.GetTypeName().GetCString());
+    return std::nullopt;
+  }
+
+  if (TypeSystemClang::AreTypesSame(in_type, type)) {
+    LLDB_LOG(log, "{0:x16}: in type is the same as the dynamic type",
+             in_value.GetPointerValue().address);
+
+    // The dynamic type we found was the same type, so we don't have a
+    // dynamic type here, but if a derived type encounters this vtable symbol,
+    // remember that it has offset 0.
+    std::scoped_lock<std::mutex> g(m_mutex);
+    m_offset_to_top_map[vtable_info.addr] = 0;
+    return std::nullopt;
+  }
+
+  // Unlike in the Itanium ABI, the VTable itself doesn't have a field telling
+  // us the offset to the dynamic type.
+  // When compiled with RTTI, we can use the complete object locator to get this
+  // offset. This is preferred, because it can resolve types across different
+  // modules and handle virtual bases with PDB.
+  // If this fails (e.g. no RTTI available), the offset is determined from the
+  // AST. This requires the types to be from the same type system. Because PDB
+  // doesn't include the offsets of virtual bases, we don't attempt to resolve
+  // these. They're technically present in the AST, but they often have
+  // incorrect offsets.
+  std::optional<int64_t> offset = GetOffsetToTopFromRTTI(vtable_info, type);
+  llvm::StringRef strategy = "RTTI";
+  if (!offset) {
+    offset = GetOffsetToTopFromAST(in_type, in_value, type, vtable_info,
+                                   vtable_components);
+    strategy = "AST";
+  }
+
+  if (offset) {
+    LLDB_LOG(GetLog(LLDBLog::Object),
+             "{0:x16}: [{1}] '{2}' has offset {3} in '{4}'",
+             in_value.GetPointerValue().address, strategy,
+             in_type.GetTypeName(), *offset, type.GetTypeName());
+
+    std::scoped_lock<std::mutex> g(m_mutex);
+    m_offset_to_top_map[vtable_info.addr] = offset;
+  }
+
+  return offset;
+}
+
+std::optional<int64_t> MicrosoftABIRuntime::GetOffsetToTopFromRTTI(
+    const LanguageRuntime::VTableInfo &vtable_info,
+    const CompilerType &dynamic_type) {
+  // The pointer to the RTTI complete object locator sits one pointer size above
+  // the vftable.
+  Target &target = m_process->GetTarget();
+  const addr_t vtable_load_addr = vtable_info.addr.GetLoadAddress(&target);
+  if (vtable_load_addr == LLDB_INVALID_ADDRESS)
+    return std::nullopt;
+  const uint32_t addr_byte_size = m_process->GetAddressByteSize();
+  const lldb::addr_t complete_object_locator_load_addr =
+      vtable_load_addr - addr_byte_size;
+  // Check for underflow.
+  if (complete_object_locator_load_addr >= vtable_load_addr)
+    return std::nullopt;
+
+  Log *log = GetLog(LLDBLog::Object);
+  // Pointer to the RTTI complete object locator.
+  llvm::Expected<lldb::addr_t> rtti_locator_base_load_addr =
+      m_process->ReadPointerFromMemory(complete_object_locator_load_addr);
+  if (!rtti_locator_base_load_addr ||
+      *rtti_locator_base_load_addr == LLDB_INVALID_ADDRESS) {
+    LLDB_LOG_ERROR(log, rtti_locator_base_load_addr.takeError(),
+                   "Failed to read address of RTTI complete object locator at "
+                   "{1:x16}: {0}",
+                   complete_object_locator_load_addr);
+    return std::nullopt;
+  }
+
+  Status error;
+  Address rtti_locator_base_addr;
+  if (!m_process->GetTarget().ResolveLoadAddress(*rtti_locator_base_load_addr,
+                                                 rtti_locator_base_addr)) {
+    LLDB_LOG_ERROR(log, error.takeError(),
+                   "Failed to resolve load address of RTTI complete object "
+                   "locator at {1:x16}: {0}",
+                   *rtti_locator_base_load_addr);
+    return std::nullopt;
+  }
+
+  // Check if we're actually looking at the RTTI complete object locator.
+  Symbol *symbol = rtti_locator_base_addr.CalculateSymbolContextSymbol();
+  if (symbol == nullptr)
+    return std::nullopt;
+  if (!symbol->GetName().GetStringRef().contains(
+          s_rtti_complete_object_locator_indicator))
+    return std::nullopt;
+
+  LLDB_LOG(log, "Found RTTI complete object locator: {0:x16}",
+           *rtti_locator_base_load_addr);
+
+  auto ts = dynamic_type.GetTypeSystem();
+  if (!ts)
+    return std::nullopt;
+
+  llvm::Expected<uint64_t> int_size =
+      ts->GetBasicTypeFromAST(lldb::eBasicTypeInt).GetByteSize(nullptr);
+  if (!int_size) {
+    LLDB_LOG_ERROR(log, int_size.takeError(),
+                   "Failed to get size of int from AST: {0}");
+    return std::nullopt;
+  }
+
+  // See MSRTTIBuilder::getCompleteObjectLocator in Clang for the layout.
+  // We're interested in the offset which is the second int in the struct.
+  Address offset_from_top_addr = rtti_locator_base_addr;
+  offset_from_top_addr.Slide(*int_size);
+
+  error.Clear();
+  const int64_t offset_from_top = target.ReadSignedIntegerFromMemory(
+      offset_from_top_addr, *int_size, INT64_MIN, error);
+  if (offset_from_top == INT64_MIN || error.Fail()) {
+    LLDB_LOG_ERROR(log, error.takeError(),
+                   "Failed to read 'offset from top' from RTTI complete object "
+                   "locator at {1:x16}: {0}",
+                   offset_from_top_addr.GetOffset());
+    return std::nullopt;
+  }
+
+  return offset_from_top;
+}
+
+/// Try to find the offset of `target_base` inside `haystack` while visiting all
+/// `checkpoints`.
+static std::optional<size_t>
+GetOffsetOfBaseFromAST(clang::ASTContext &ast,
+                       const clang::CXXRecordDecl *haystack,
+                       const clang::CXXRecordDecl *target_base,
+                       const llvm::ArrayRef<llvm::StringRef> checkpoints) {
+  struct BaseEntry {
+    const clang::CXXRecordDecl *decl;
+    /// If this is `std::nullopt`, then this entry is on a path where we can't
+    /// determine the offset.
+    std::optional<size_t> offset;
+    /// Remaining checkpoints on this path.
+    llvm::ArrayRef<llvm::StringRef> checkpoints;
+    /// Was `target_base` found on this path?
+    bool found_target = false;
+  };
+
+  // This does a breadth-first search over all bases. Starting point is the root
+  // (`haystack`).
+  std::deque<BaseEntry> bases;
+  bases.emplace_back(BaseEntry{
+      /*decl=*/haystack,
+      /*offset=*/0,
+      /*checkpoints=*/checkpoints,
+  });
+
+  // Go through virtual bases first.
+  std::string base_name;
+  for (const clang::CXXBaseSpecifier &base : haystack->vbases()) {
+    const clang::CXXRecordDecl *base_decl = llvm::cast<clang::CXXRecordDecl>(
+        base.getType()->castAs<clang::RecordType>()->getDecl());
+    // Although clang gives us some offset here, this offset is not from the
+    // debug info and thus likely wrong. Neither PDB nor DWARF include it.
+    if (!checkpoints.empty()) {
+      base_name = base_decl->getDeclName().getAsString();
+      if (base_name == checkpoints.back())
+        return std::nullopt;
+    } else if (base_decl == target_base) {
+      return std::nullopt;
+    }
+
+    // We still need to keep track of this class as `target_base` might be
+    // inside this base.
+    bases.emplace_back(BaseEntry{
+        /*decl=*/base_decl,
+        /*offset=*/std::nullopt,
+        /*checkpoints=*/checkpoints,
+    });
+  }
+
+  while (!bases.empty()) {
+    BaseEntry current = bases.front();
+    assert(
+        (!current.found_target || !current.checkpoints.empty()) &&
+        "if we found the target we should only need to check the checkpoints");
+    bases.pop_front();
+    const clang::ASTRecordLayout &record_layout =
+        ast.getASTRecordLayout(current.decl);
+
+    for (const clang::CXXBaseSpecifier &base : current.decl->bases()) {
+      if (base.isVirtual())
+        continue; // We already looked through virtual bases.
+
+      const clang::CXXRecordDecl *base_decl = llvm::cast<clang::CXXRecordDecl>(
+          base.getType()->castAs<clang::RecordType>()->getDecl());
+      bool found_target = base_decl == target_base || current.found_target;
+      std::optional<size_t> offset = current.offset;
+      if (offset)
+        *offset += record_layout.getBaseClassOffset(base_decl).getQuantity();
+
+      if (!current.checkpoints.empty()) {
+        base_name = base_decl->getDeclName().getAsString();
+        if (base_name == current.checkpoints.back()) {
+          // Last component was the base we were looking for.
+          if (found_target && current.checkpoints.size() == 1)
+            return offset;
+
+          // Found a checkpoint, so we have to take this path.
+          bases.clear();
+          bases.emplace_back(BaseEntry{
+              /*decl=*/base_decl,
+              /*offset=*/offset,
+              /*checkpoints=*/current.checkpoints.drop_back(),
+              /*found_target=*/found_target,
+          });
+        }
+      } else if (found_target) {
+        return offset;
+      }
+
+      bases.emplace_back(BaseEntry{
+          /*decl=*/base_decl,
+          /*offset=*/offset,
+          /*checkpoints=*/current.checkpoints,
+          /*found_target=*/found_target,
+      });
+    }
+  }
+
+  return std::nullopt;
+}
+
+std::optional<int64_t> MicrosoftABIRuntime::GetOffsetToTopFromAST(
+    const CompilerType &in_type, ValueObject &in_value,
+    const CompilerType &dyn_type,
+    const LanguageRuntime::VTableInfo &vtable_info,
+    llvm::ArrayRef<llvm::StringRef> vtable_components) {
+  Log *log = GetLog(LLDBLog::Object);
+
+  CompilerType in_base_type = in_type;
+
+  clang::CXXRecordDecl *in_decl =
+      TypeSystemClang::GetAsCXXRecordDecl(in_base_type.GetOpaqueQualType());
+  clang::CXXRecordDecl *target_decl =
+      TypeSystemClang::GetAsCXXRecordDecl(dyn_type.GetOpaqueQualType());
+  if (!in_decl || !target_decl) {
+    LLDB_LOG(log, "{0:x16}: types are not record types (in={1}, target={2})",
+             in_value.GetPointerValue().address, in_base_type.GetTypeName(),
+             dyn_type.GetTypeName());
+    return std::nullopt;
+  }
+
+  TypeSystemClangSP ts = dyn_type.GetTypeSystem<TypeSystemClang>();
+  if (!ts || ts != in_base_type.GetTypeSystem<TypeSystemClang>()) {
+    LLDB_LOG(GetLog(LLDBLog::Object),
----------------
charles-zablit wrote:

```suggestion
    LLDB_LOG(log,
```

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


More information about the lldb-commits mailing list