| //===----------------------------------------------------------------------===// | |
| // | |
| // 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<int64_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; | |
| } | |
| 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; | |
| } | |
| { | |
| 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; | |
| } | |
| // 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()); | |
| // TODO: Ideally, we could also store the case where we can't find the | |
| // offset. This would need to be invalidated when new modules or debug info | |
| // is loaded, as those could contain info about the vtables. | |
| 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; | |
| // On all supported platforms running the MS ABI, `int` is four bytes. | |
| constexpr uint32_t int_size = 4; | |
| // 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, | |
| }); | |
| continue; | |
| } | |
| } 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(log, "{0:x16}: '{1}' and '{2}' are from different type systems", | |
| in_value.GetPointerValue().address, | |
| in_decl->getDeclName().getAsString(), | |
| target_decl->getDeclName().getAsString()); | |
| return std::nullopt; | |
| } | |
| std::optional<size_t> offset = GetOffsetOfBaseFromAST( | |
| ts->getASTContext(), target_decl, in_decl, vtable_components); | |
| if (!offset) { | |
| LLDB_LOG(log, "{0:x16}: failed to find offset of '{1}' in '{2}'", | |
| in_value.GetPointerValue().address, | |
| in_decl->getDeclName().getAsString(), | |
| target_decl->getDeclName().getAsString()); | |
| return std::nullopt; | |
| } | |
| return offset; | |
| } |