From f719d1d5565f7b7b1237b8e183c2985f006fa3e4 Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Sat, 1 Mar 2025 00:34:12 -0500 Subject: Refactor and fixup MSVC and Itanium RTTI Bunch of misc fixes and performance improvements --- plugins/rtti/itanium.cpp | 340 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 272 insertions(+), 68 deletions(-) (limited to 'plugins/rtti/itanium.cpp') diff --git a/plugins/rtti/itanium.cpp b/plugins/rtti/itanium.cpp index e1af724b..0714d17d 100644 --- a/plugins/rtti/itanium.cpp +++ b/plugins/rtti/itanium.cpp @@ -16,6 +16,8 @@ TypeInfo::TypeInfo(BinaryView *view, uint64_t address) reader.Seek(address); base = reader.ReadPointer(); auto typeNameAddr = reader.ReadPointer(); + if (!view->IsValidOffset(typeNameAddr)) + return; reader.Seek(typeNameAddr); type_name = reader.ReadCString(512); } @@ -36,8 +38,7 @@ BaseClassTypeInfo::BaseClassTypeInfo(BinaryView *view, uint64_t address) reader.Seek(address); base_type = reader.ReadPointer(); offset_flags = reader.Read32(); - // TODO: Test this... - offset_flags_masks = static_cast(reader.Read32()); + offset_flags_masks = reader.Read32(); } @@ -49,12 +50,11 @@ VMIClassTypeInfo::VMIClassTypeInfo(BinaryView *view, uint64_t address) : ClassTy flags = reader.Read32(); base_count = reader.Read32(); base_info = {}; - for (size_t i = 1; i < base_count; i++) + for (size_t i = 0; i < base_count; i++) { - // TODO: Verify this is correct. uint64_t currentBaseAddr = reader.GetOffset(); - base_info.emplace_back(view, reader.GetOffset()); - reader.Seek(currentBaseAddr + 12); + base_info.emplace_back(view, currentBaseAddr); + reader.Seek(currentBaseAddr + 0x10); } } @@ -183,13 +183,37 @@ Ref BaseClassTypeInfoType(BinaryView *view) } -Ref VMIClassTypeInfoType(BinaryView *view, int baseCount) +Ref VMIFlagsMasksType(BinaryView *view) +{ + auto typeId = Type::GenerateAutoTypeId(TYPE_SOURCE_ITANIUM, QualifiedName("VMIFlagsMasks")); + Ref typeCache = view->GetTypeById(typeId); + + if (typeCache == nullptr) + { + Ref arch = view->GetDefaultArchitecture(); + Ref uintType = Type::IntegerType(4, false); + + EnumerationBuilder enumerationBuilder; + enumerationBuilder.AddMemberWithValue("__non_diamond_repeat_mask", 0x1); + enumerationBuilder.AddMemberWithValue("__diamond_shaped_mask", 0x2); + + Ref enumerationType = TypeBuilder::EnumerationType(arch, enumerationBuilder.Finalize()).Finalize(); + view->DefineType(typeId, QualifiedName("__cxxabiv1::__flags_masks"), enumerationType); + + typeCache = view->GetTypeById(typeId); + } + + return typeCache; +} + + +Ref VMIClassTypeInfoType(BinaryView *view, uint64_t baseCount) { Ref arch = view->GetDefaultArchitecture(); Ref uintType = Type::IntegerType(4, false); StructureBuilder structureBuilder; - structureBuilder.AddMemberAtOffset(uintType, "__flags", 0x10); + structureBuilder.AddMemberAtOffset(VMIFlagsMasksType(view), "__flags", 0x10); structureBuilder.AddMemberAtOffset(uintType, "__base_count", 0x14); Ref baseInfoType = Type::ArrayType(BaseClassTypeInfoType(view), baseCount); structureBuilder.AddMemberAtOffset(baseInfoType, "__base_info", 0x18); @@ -238,9 +262,45 @@ std::optional ReadTypeInfoVariant(BinaryView *view, uint64_t ob } +std::optional ItaniumRTTIProcessor::ProcessVFTBaseClassInfo(uint64_t vftAddr, ClassInfo &classInfo) +{ + BinaryReader reader = BinaryReader(m_view); + // Because we have this we _need_ to have the adjustment stuff. + // NOTE: We assume two 0x4 ints with the first being what we want. + reader.Seek(vftAddr - 0x10); + + auto adjustmentOffset = static_cast(reader.Read32()); + auto baseIdx = static_cast(reader.Read32()); + uint64_t classOffset = std::abs(adjustmentOffset); + + std::optional selectedBaseClassInfo = std::nullopt; + // Assuming we do not have a baseClassInfo already passed we can deduce it here. + for (auto& baseClass : classInfo.baseClasses) + { + // if (baseClass.offset == 0) + // { + // // If the base class is at offset 0 that means it has yet to be adjusted. + // // NOTE: This should only happen for `TIVSIClass`. If this assigns more than + // // one base class to this offset we are screwed. + // baseClass.offset = classOffset; + // LogInfo("Adjusting base class offset for %llx to %llx", vftAddr, classOffset); + // } + + if (baseClass.offset == classOffset) + { + // Found the appropriate base class for this vtable. + selectedBaseClassInfo = baseClass; + } + } + + // Return the selected base class for use in later processing such as `ProcessVFT`. + return selectedBaseClassInfo; +} + + std::optional ItaniumRTTIProcessor::ProcessRTTI(uint64_t objectAddr) { - // TODO: You cant get subobject offsets from rtti, its stored above this ptr in vtable. + // TODO: You cant get sub-object offsets from rtti, its stored above this ptr in vtable. // Get object as type info then check to see if it's valid. auto typeInfoVariant = ReadTypeInfoVariant(m_view, objectAddr); if (!typeInfoVariant.has_value()) @@ -258,35 +318,89 @@ std::optional ItaniumRTTIProcessor::ProcessRTTI(uint64_t objectAddr) m_view->UndefineAutoSymbol(typeInfoSymbol); m_view->DefineAutoSymbol(new Symbol{DataSymbol, typeInfoName, objectAddr}); + auto nameFromTypeInfoSymbol = [&](uint64_t addr) -> std::optional { + auto sym = m_view->GetSymbolByAddress(addr); + if (sym == nullptr || sym->GetType() != ExternalSymbol) + return std::nullopt; + auto symName = sym->GetShortName(); + // Remove type info prefix. + if (symName.rfind("_typeinfo_for_", 0) != 0) + return std::nullopt; + return symName.substr(14); + }; + if (typeInfoVariant == TIVSIClass) { // Read the base class. auto siClassTypeInfo = SIClassTypeInfo(m_view, objectAddr); auto subTypeInfoVariant = ReadTypeInfoVariant(m_view, siClassTypeInfo.base_type); + std::string subTypeName; if (!subTypeInfoVariant.has_value()) - return std::nullopt; - auto subTypeInfo = TypeInfo(m_view, siClassTypeInfo.base_type); + { + // Allow externals to be used in place of a backed subtype. + // TODO: We should probably warn that vtables will likely be inaccurate. + // TODO: Because we wont know what offsets are valid. + auto externTypeName = nameFromTypeInfoSymbol(siClassTypeInfo.base_type); + if (!externTypeName.has_value()) + return std::nullopt; + m_logger->LogDebug("Non-backed external subtype for %llx", objectAddr); + subTypeName = externTypeName.value(); + } + else + { + auto subTypeInfo = TypeInfo(m_view, siClassTypeInfo.base_type); + subTypeName = subTypeInfo.type_name; + } // Demangle base class name and set - auto baseClassName = DemangleNameItanium(m_view, allowMangledClassNames, subTypeInfo.type_name); + auto baseClassName = DemangleNameItanium(m_view, allowMangledClassNames, subTypeName); if (!baseClassName.has_value()) { m_logger->LogWarn("Skipping base class with mangled name %llx", siClassTypeInfo.base_type); return std::nullopt; } - classInfo.baseClassName = baseClassName; // NOTE: The base class offset is not able to be resolved here. // NOTE: To resolve the base class offset you must go to the vtable. + uint64_t baseClassOffset = 0; + auto subBaseClassInfo = BaseClassInfo {baseClassName.value(), baseClassOffset}; + classInfo.baseClasses.emplace_back(subBaseClassInfo); m_view->DefineDataVariable(objectAddr, Confidence(SIClassTypeInfoType(m_view), 255)); } else if (typeInfoVariant == TIVVMIClass) { - // TODO: Read multiple base classes. auto vmiClassTypeInfo = VMIClassTypeInfo(m_view, objectAddr); m_view->DefineDataVariable(objectAddr, Confidence(VMIClassTypeInfoType(m_view, vmiClassTypeInfo.base_count), 255)); + for (const auto& baseInfo : vmiClassTypeInfo.base_info) + { + // Remove the flags and just get the offset + auto baseTypeInfoVariant = ReadTypeInfoVariant(m_view, baseInfo.base_type); + std::string subTypeName; + if (!baseTypeInfoVariant.has_value()) + { + // Allow externals to be used in place of a backed base type. + auto externTypeName = nameFromTypeInfoSymbol(baseInfo.base_type); + if (!externTypeName.has_value()) + return std::nullopt; + m_logger->LogDebug("Non-backed external subtype for %llx", objectAddr); + subTypeName = externTypeName.value(); + } else + { + auto baseTypeInfo = TypeInfo(m_view, baseInfo.base_type); + subTypeName = baseTypeInfo.type_name; + } + auto baseClassName = DemangleNameItanium(m_view, allowMangledClassNames, subTypeName); + if (!baseClassName.has_value()) + { + m_logger->LogWarn("Skipping base class with mangled name %llx", baseInfo.base_type); + continue; + } + // Shift off the flag bits. + uint64_t offset = baseInfo.offset_flags >> 8; + auto baseClassInfo = BaseClassInfo {baseClassName.value(), offset}; + classInfo.baseClasses.emplace_back(baseClassInfo); + } } else { - // auto classTypeInfo = ClassTypeInfo(m_view, objectAddr); m_view->DefineDataVariable(objectAddr, Confidence(ClassTypeInfoType(m_view), 255)); } @@ -294,13 +408,13 @@ std::optional ItaniumRTTIProcessor::ProcessRTTI(uint64_t objectAddr) } -std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_t vftAddr, ClassInfo &classInfo) +std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_t vftAddr, ClassInfo &classInfo, std::optional baseClassInfo) { VirtualFunctionTableInfo vftInfo = {vftAddr}; BinaryReader reader = BinaryReader(m_view); reader.Seek(vftAddr); // Gather all virtual functions - std::vector > virtualFunctions = {}; + std::vector virtualFunctions = {}; while (true) { uint64_t vFuncAddr = reader.ReadPointer(); @@ -310,17 +424,30 @@ std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_ Ref segment = m_view->GetSegmentAt(vFuncAddr); if (segment == nullptr || !(segment->GetFlags() & (SegmentExecutable | SegmentDenyWrite))) { - // Last CompleteObjectLocator or hit the next CompleteObjectLocator - break; + // TODO: Sometimes vFunc idx will be zeroed iirc. + // We allow vfuncs to point to extern functions. + auto vFuncSym = m_view->GetSymbolByAddress(vFuncAddr); + if (!vFuncSym) + break; + DataVariable dv; + bool foundDv = m_view->GetDataVariableAtAddress(vFuncAddr, dv); + // Last virtual function, or hit the next vtable. + if (!foundDv || !dv.type->m_object) + break; + // Void externs are very likely to be a func. + // TODO: Add some sanity checks for this! + if (!dv.type->IsFunction() && !(dv.type->IsVoid() && vFuncSym->GetType() == ExternalSymbol)) + break; + } + else + { + // TODO: Is likely a function check here? + m_logger->LogDebug("Discovered function from virtual function table... %llx", vFuncAddr); + m_view->AddFunctionForAnalysis(m_view->GetDefaultPlatform(), vFuncAddr, true); } - // TODO: Sometimes vFunc idx will be zeroed. - // TODO: Is likely a function check here? - m_logger->LogDebug("Discovered function from virtual function table... %llx", vFuncAddr); - auto vFunc = m_view->AddFunctionForAnalysis(m_view->GetDefaultPlatform(), vFuncAddr, true); - funcs.emplace_back(vFunc); } // Only ever add one function. - virtualFunctions.emplace_back(funcs.front()); + virtualFunctions.emplace_back(VirtualFunctionInfo{vFuncAddr}); } if (virtualFunctions.empty()) @@ -329,29 +456,12 @@ std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_ return std::nullopt; } - // All vft verification has been done, we can write the classOffset now. - if (classInfo.baseClassName.has_value() && !classInfo.classOffset.has_value()) - { - // Because we have this we _need_ to have the adjustment stuff. - // NOTE: We assume two 0x4 ints with the first being what we want. - // NOTE: This is where we actually classOffset is pulled. - reader.Seek(vftAddr - 0x10); - auto adjustmentOffset = static_cast(reader.Read32()); - auto _what = static_cast(reader.Read32()); - uint64_t classOffset = std::abs(adjustmentOffset); - classInfo.classOffset = classOffset; - } - - - for (auto &func: virtualFunctions) - vftInfo.virtualFunctions.emplace_back(VirtualFunctionInfo{func->GetStart()}); - // Create virtual function table type auto vftTypeName = fmt::format("{}::VTable", classInfo.className); - if (classInfo.baseClassName.has_value()) + if (baseClassInfo.has_value()) { // TODO: What is the correct form for the name? - vftTypeName = fmt::format("{}::{}", classInfo.baseClassName.value(), vftTypeName); + vftTypeName = fmt::format("{}::{}", baseClassInfo->className, vftTypeName); } // TODO: Hack the debug type id is used here to allow the PDB type (debug info) to overwrite the RTTI vtable type. auto typeId = Type::GenerateAutoDebugTypeId(vftTypeName); @@ -368,17 +478,18 @@ std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_ auto vftSize = virtualFunctions.size() * addrSize; vftBuilder.SetWidth(vftSize); - if (auto baseVft = classInfo.baseVft) + if (baseClassInfo.has_value() && baseClassInfo->vft.has_value()) { - if (baseVft->virtualFunctions.size() <= virtualFunctions.size()) + if (baseClassInfo->vft->virtualFunctions.size() <= virtualFunctions.size()) { // Adjust the current vFunc index to the end of the shared vFuncs. - vFuncIdx = baseVft->virtualFunctions.size(); + vFuncIdx = baseClassInfo->vft->virtualFunctions.size(); virtualFunctions.erase(virtualFunctions.begin(), virtualFunctions.begin() + vFuncIdx); // We should set the vtable as a base class so that xrefs are propagated (among other things). // NOTE: this means that `this` params will be assumed pre-adjusted, this is normally fine assuming type propagation // NOTE: never occurs on the vft types. Other-wise we need to change this. - auto baseVftTypeName = fmt::format("{}::VTable", classInfo.baseClassName.value()); + // TODO: Different type name please lol + auto baseVftTypeName = fmt::format("{}::VTable", baseClassInfo->className); NamedTypeReferenceBuilder baseVftNTR; baseVftNTR.SetName(baseVftTypeName); // Width is unresolved here so that we can keep non-base vfuncs un-inherited. @@ -393,20 +504,46 @@ std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_ for (auto &&vFunc: virtualFunctions) { + // NOTE: The analyzed function type might not be available here. + auto vFuncAnalysis = m_view->GetAnalysisFunctionsForAddress(vFunc.funcAddr); + Ref vFuncType = nullptr; + Ref vFuncSym = nullptr; + if (!vFuncAnalysis.empty()) + { + vFuncType = vFuncAnalysis[0]->GetType(); + vFuncSym = vFuncAnalysis[0]->GetSymbol(); + } else + { + DataVariable dv; + bool foundDv = m_view->GetDataVariableAtAddress(vFunc.funcAddr, dv); + if (!foundDv) + { + m_logger->LogWarn("Skipping vfunc with no type... %llx", vFunc.funcAddr); + return std::nullopt; + } + vFuncType = dv.type.GetValue(); + + vFuncSym = m_view->GetSymbolByAddress(vFunc.funcAddr); + if (vFuncSym == nullptr) + { + m_logger->LogWarn("Skipping vfunc with no symbol... %llx", vFunc.funcAddr); + return std::nullopt; + } + } + auto vFuncName = fmt::format("vFunc_{}", vFuncIdx); // If we have a better name, use it. - auto vFuncSymName = vFunc->GetSymbol()->GetShortName(); + auto vFuncSymName = vFuncSym->GetShortName(); if (vFuncSymName.compare(0, 4, "sub_") != 0) - vFuncName = vFunc->GetSymbol()->GetShortName(); + vFuncName = vFuncSym->GetShortName(); // MyClass::func -> func std::size_t pos = vFuncName.rfind("::"); if (pos != std::string::npos) vFuncName = vFuncName.substr(pos + 2); - // NOTE: The analyzed function type might not be available here. auto vFuncOffset = vFuncIdx * addrSize; vftBuilder.AddMemberAtOffset( - Type::PointerType(addrSize, vFunc->GetType(), true), vFuncName, vFuncOffset); + Type::PointerType(addrSize, vFuncType, true), vFuncName, vFuncOffset); vFuncIdx++; } m_view->DefineType(typeId, vftTypeName, @@ -415,8 +552,8 @@ std::optional ItaniumRTTIProcessor::ProcessVFT(uint64_ auto vftName = fmt::format("_vtable_for_{}", classInfo.className); // TODO: How to display base classes? - if (classInfo.baseClassName.has_value()) - vftName += fmt::format("{{for `{}'}}", classInfo.baseClassName.value()); + if (baseClassInfo.has_value()) + vftName += fmt::format("{{for `{}'}}", baseClassInfo->className); auto vftSymbol = m_view->GetSymbolByAddress(vftAddr); if (vftSymbol != nullptr) m_view->UndefineAutoSymbol(vftSymbol); @@ -459,6 +596,7 @@ void ItaniumRTTIProcessor::ProcessRTTI() } }; + m_view->BeginBulkModifySymbols(); // Scan data sections for rtti. for (const Ref
§ion: m_view->GetSections()) { @@ -468,6 +606,49 @@ void ItaniumRTTIProcessor::ProcessRTTI() scan(section); } } + m_view->EndBulkModifySymbols(); + + // Go through all classes and recurse into the base classes using the base class name + for (auto &[classAddr, classInfo]: m_classInfo) + { + std::set visitedBases; + std::deque baseQueue(classInfo.baseClasses.begin(), classInfo.baseClasses.end()); + + while (!baseQueue.empty()) + { + BaseClassInfo baseClass = baseQueue.front(); + baseQueue.pop_front(); + + if (visitedBases.find(baseClass.className) != visitedBases.end()) + continue; + + visitedBases.insert(baseClass.className); + + auto baseClassIt = std::find_if(m_classInfo.begin(), m_classInfo.end(), + [&](const auto &item) { + return item.second.className == baseClass.className; + }); + + if (baseClassIt != m_classInfo.end()) + { + const ClassInfo &nestedBaseClassInfo = baseClassIt->second; + baseQueue.insert(baseQueue.end(), nestedBaseClassInfo.baseClasses.begin(), + nestedBaseClassInfo.baseClasses.end()); + } + + classInfo.baseClasses.push_back(baseClass); + } + + // Remove duplicates in the baseClasses vector while preserving order + std::sort(classInfo.baseClasses.begin(), classInfo.baseClasses.end(), + [](const BaseClassInfo &a, const BaseClassInfo &b) { return a.className < b.className; }); + + classInfo.baseClasses.erase( + std::unique(classInfo.baseClasses.begin(), classInfo.baseClasses.end(), + [](const BaseClassInfo &a, const BaseClassInfo &b) { return a.className == b.className; }), + classInfo.baseClasses.end() + ); + } auto end_time = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed_time = end_time - start_time; @@ -477,7 +658,8 @@ void ItaniumRTTIProcessor::ProcessRTTI() void ItaniumRTTIProcessor::ProcessVFT() { - std::map vftMap = {}; + BinaryReader optReader = BinaryReader(m_view); + std::map> vftMap = {}; std::map> vftFinishedMap = {}; auto start_time = std::chrono::high_resolution_clock::now(); for (auto &[coLocatorAddr, classInfo]: m_classInfo) @@ -488,15 +670,21 @@ void ItaniumRTTIProcessor::ProcessVFT() DataVariable dv; if (m_view->GetDataVariableAtAddress(ref, dv) && m_classInfo.find(dv.address) != m_classInfo.end()) continue; + // Verify that there is two 4 byte values above the type info pointer + optReader.Seek(ref - 8); + auto beforeTypeInfoRef = optReader.ReadPointer(); + if (m_view->IsValidOffset(beforeTypeInfoRef)) + continue; // TODO: This is not pointing at where it should, remember that the vtable will be inside another structure. auto vftAddr = ref + m_view->GetAddressSize(); - vftMap[coLocatorAddr] = vftAddr; + // Found a vtable reference to colocator + // TODO: Access check here. + vftMap[coLocatorAddr].insert(vftAddr); } } if (virtualFunctionTableSweep) { - BinaryReader optReader = BinaryReader(m_view); auto addrSize = m_view->GetAddressSize(); auto scan = [&](const Ref &segment) { uint64_t startAddr = segment->GetStart(); @@ -509,7 +697,7 @@ void ItaniumRTTIProcessor::ProcessVFT() if (coLocator == m_classInfo.end()) continue; // Found a vtable reference to colocator. - vftMap[coLocatorAddr] = vtableAddr + addrSize; + // vftMap[coLocatorAddr] = vtableAddr + addrSize; } }; @@ -536,27 +724,35 @@ void ItaniumRTTIProcessor::ProcessVFT() auto cachedVftInfo = vftFinishedMap.find(vftAddr); if (cachedVftInfo != vftFinishedMap.end()) return cachedVftInfo->second; - auto vftInfo = ProcessVFT(vftAddr, classInfo); + // We need to have base class info available here. + // This works by reading off the adjustment and keying into the bases. + // If there is a base at that adjustment we assume this vtable we are creating is for that. + auto selectedBaseClass = ProcessVFTBaseClassInfo(vftAddr, classInfo); + auto vftInfo = ProcessVFT(vftAddr, classInfo, selectedBaseClass); vftFinishedMap[vftAddr] = vftInfo; return vftInfo; }; - for (const auto &[coLocatorAddr, vftAddr]: vftMap) - { + // Adds the VFT entries in class info and base class info. + // TODO: This is so cursed. + auto populateVftEntries = [&](uint64_t coLocatorAddr, uint64_t vftAddr) { auto classInfo = m_classInfo.find(coLocatorAddr)->second; - if (classInfo.baseClassName.has_value()) + for (auto& baseClassInfo : classInfo.baseClasses) { // Process base vtable and add it to the class info. - for (auto [baseCoLocAddr, baseClassInfo] : m_classInfo) + for (auto& [baseCoLocAddr, bClassInfo] : m_classInfo) { - if (baseClassInfo.className == classInfo.baseClassName.value()) + if (bClassInfo.className == baseClassInfo.className) { - uint64_t baseVftAddr = vftMap[baseCoLocAddr]; - if (auto baseVftInfo = GetCachedVFTInfo(baseVftAddr, baseClassInfo)) + // Recurse into base class and populate all of its vtables. + for (auto& baseVftAddr : vftMap[baseCoLocAddr]) { - classInfo.baseVft = baseVftInfo.value(); - break; + if (auto vftInfo = GetCachedVFTInfo(baseVftAddr, bClassInfo)) + bClassInfo.vft = vftInfo.value(); } + // Now that we have populated all the vtables for the base class, we can assign its + // root vtable to the base vft. + baseClassInfo.vft = bClassInfo.vft; } } } @@ -565,6 +761,14 @@ void ItaniumRTTIProcessor::ProcessVFT() classInfo.vft = vftInfo.value(); m_classInfo[coLocatorAddr] = classInfo; + }; + + for (const auto &[coLocatorAddr, vftAddrs]: vftMap) + { + for (const auto& vftAddr: vftAddrs) + { + populateVftEntries(coLocatorAddr, vftAddr); + } } auto end_time = std::chrono::high_resolution_clock::now(); -- cgit v1.3.1