Files
Alessandro Di Federico 5c9b6ed9b3 Rename DILogger into Log
2024-08-16 13:27:00 +02:00

1303 lines
45 KiB
C++

/// \file PDBImporter.cpp
//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include <optional>
#include "llvm/DebugInfo/CodeView/CVSymbolVisitor.h"
#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
#include "llvm/DebugInfo/CodeView/GUID.h"
#include "llvm/DebugInfo/CodeView/LazyRandomTypeCollection.h"
#include "llvm/DebugInfo/CodeView/SymbolDeserializer.h"
#include "llvm/DebugInfo/CodeView/SymbolRecord.h"
#include "llvm/DebugInfo/CodeView/SymbolVisitorCallbackPipeline.h"
#include "llvm/DebugInfo/CodeView/SymbolVisitorCallbacks.h"
#include "llvm/DebugInfo/CodeView/TypeDumpVisitor.h"
#include "llvm/DebugInfo/CodeView/TypeRecordHelpers.h"
#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
#include "llvm/DebugInfo/PDB/Native/GlobalsStream.h"
#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
#include "llvm/DebugInfo/PDB/Native/InputFile.h"
#include "llvm/DebugInfo/PDB/Native/ModuleDebugStream.h"
#include "llvm/DebugInfo/PDB/Native/NativeSession.h"
#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
#include "llvm/DebugInfo/PDB/Native/SymbolStream.h"
#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
#include "llvm/DebugInfo/PDB/PDB.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
#include "llvm/Support/Program.h"
#include "revng/Model/Binary.h"
#include "revng/Model/Importer/Binary/Options.h"
#include "revng/Model/Importer/DebugInfo/PDBImporter.h"
#include "revng/Model/Pass/AllPasses.h"
#include "revng/Model/Processing.h"
#include "revng/Model/TypeDefinition.h"
#include "revng/Support/Assert.h"
#include "revng/Support/CommandLine.h"
#include "revng/Support/Debug.h"
#include "revng/Support/MetaAddress.h"
#include "revng/Support/ProgramRunner.h"
#include "ImportDebugInfoHelper.h"
using namespace llvm;
using namespace llvm::codeview;
using namespace llvm::object;
using namespace llvm::pdb;
static Logger<> Log("pdb-importer");
// Force using a specific PDB.
static llvm::cl::opt<std::string> UsePDB("use-pdb",
llvm::cl::desc("Path to the PDB."),
llvm::cl::cat(MainCategory));
namespace {
class PDBImporterImpl {
private:
PDBImporter &Importer;
DenseMap<TypeIndex, model::UpcastableType> ProcessedTypes;
public:
PDBImporterImpl(PDBImporter &Importer) : Importer(Importer) {}
void run(NativeSession &Session);
private:
void populateTypes();
void populateSymbolsWithTypes(NativeSession &Session);
};
using ProcessedTypeMap = DenseMap<TypeIndex, model::UpcastableType>;
/// Visitor for CodeView type streams found in PDB files. It overrides callbacks
/// (from `TypeVisitorCallbacks`) to types of interest for the revng `Model`.
/// During the traversal of the graph from PDB that represents the type system,
/// the `Types:` field of the `Model` is being populated. Since each CodeView
/// type in the PDB has unique `TypeIndex` that will be used when a symbol from
/// PDB symbol stream uses a certain type, we also keep a map of such
/// `TypeIndex` to corresponding type generated within the `Model` (it is done
/// by using `ProcessedTypes`), so it can be used when connecting functions from
/// `Model` with corresponding prototypes.
class PDBImporterTypeVisitor : public TypeVisitorCallbacks {
TupleTree<model::Binary> &Model;
LazyRandomTypeCollection &Types;
ProcessedTypeMap &ProcessedTypes;
DenseMap<TypeIndex, TypeIndex> &ForwardReferencedTypes;
TpiStream &Tpi;
TypeIndex CurrentTypeIndex = TypeIndex::None();
std::map<TypeIndex, SmallVector<DataMemberRecord, 8>> InProgressMemberTypes;
std::map<TypeIndex, SmallVector<EnumeratorRecord, 8>>
InProgressEnumeratorTypes;
std::map<TypeIndex, ArgListRecord> InProgressArgumentsTypes;
// Methods of a Class type. It references concrete MemberFunctionRecord.
std::map<TypeIndex, SmallVector<OneMethodRecord, 8>>
InProgressFunctionMemberTypes;
DenseMap<TypeIndex, MemberFunctionRecord>
InProgressConcreteFunctionMemberTypes;
public:
PDBImporterTypeVisitor(TupleTree<model::Binary> &M,
LazyRandomTypeCollection &Types,
ProcessedTypeMap &ProcessedTypes,
DenseMap<TypeIndex, TypeIndex> &ForwardReferencedTypes,
TpiStream &Tpi) :
TypeVisitorCallbacks(),
Model(M),
Types(Types),
ProcessedTypes(ProcessedTypes),
ForwardReferencedTypes(ForwardReferencedTypes),
Tpi(Tpi) {}
Error visitTypeBegin(CVType &Record) override;
Error visitTypeBegin(CVType &Record, TypeIndex TI) override;
Error visitKnownRecord(CVType &Record, ClassRecord &Class) override;
Error visitKnownMember(CVMemberRecord &Record,
EnumeratorRecord &Member) override;
Error visitKnownRecord(CVType &Record, EnumRecord &Enum) override;
Error visitKnownRecord(CVType &Record, ProcedureRecord &Proc) override;
Error visitKnownRecord(CVType &Record, UnionRecord &Union) override;
Error visitKnownRecord(CVType &Record, ArgListRecord &Args) override;
Error visitKnownMember(CVMemberRecord &Record,
DataMemberRecord &Member) override;
Error visitKnownRecord(CVType &Record, FieldListRecord &FieldList) override;
Error visitKnownRecord(CVType &Record, PointerRecord &Ptr) override;
Error visitKnownRecord(CVType &Record, ModifierRecord &Modifier) override;
Error visitKnownRecord(CVType &Record, ArrayRecord &Array) override;
Error visitKnownMember(CVMemberRecord &Record,
OneMethodRecord &FnMember) override;
Error visitKnownRecord(CVType &CVR,
MemberFunctionRecord &MemberFnRecord) override;
model::UpcastableType makeModelTypeForIndex(TypeIndex Index);
model::UpcastableType createPrimitiveType(TypeIndex SimpleType);
};
/// Visitor for CodeView symbol streams found in PDB files. It is being used for
/// connecting functions from `Model` to their prototypes. We assume the PDB
/// type stream was traversed before invoking this class.
class PDBImporterSymbolVisitor : public SymbolVisitorCallbacks {
private:
TupleTree<model::Binary> &Model;
ProcessedTypeMap &ProcessedTypes;
NativeSession &Session;
MetaAddress &ImageBase;
public:
PDBImporterSymbolVisitor(TupleTree<model::Binary> &M,
ProcessedTypeMap &ProcessedTypes,
NativeSession &Session,
MetaAddress &ImageBase) :
Model(M),
ProcessedTypes(ProcessedTypes),
Session(Session),
ImageBase(ImageBase) {}
Error visitSymbolBegin(CVSymbol &Record) override;
Error visitSymbolBegin(CVSymbol &Record, uint32_t Offset) override;
Error visitKnownRecord(CVSymbol &Record, ProcSym &Proc) override;
};
} // namespace
void PDBImporterImpl::populateTypes() {
auto InputFile = InputFile::open(Importer.getPDBFile()->getFilePath());
if (not InputFile) {
revng_log(Log, "Unable to open PDB file " << InputFile.takeError());
consumeError(InputFile.takeError());
return;
}
auto StreamTpiOrErr = Importer.getPDBFile()->getPDBTpiStream();
if (not StreamTpiOrErr) {
revng_log(Log,
"Unable to find TPI in PDB file: " << StreamTpiOrErr.takeError());
consumeError(StreamTpiOrErr.takeError());
return;
}
// Those will be processed after all the types are visited.
DenseMap<TypeIndex, TypeIndex> ForwardReferencedTypes;
PDBImporterTypeVisitor TypeVisitor(Importer.getModel(),
InputFile->types(),
ProcessedTypes,
ForwardReferencedTypes,
*StreamTpiOrErr);
if (auto Err = visitTypeStream(InputFile->types(), TypeVisitor)) {
revng_log(Log, "Error during visiting types: " << Err);
consumeError(std::move(Err));
}
}
class PDBSymbolHandler {
private:
PDBImporter &Importer;
ProcessedTypeMap &ProcessedTypes;
NativeSession &Session;
InputFile &Input;
public:
PDBSymbolHandler(PDBImporter &Importer,
ProcessedTypeMap &ProcessedTypes,
NativeSession &Session,
InputFile &Input) :
Importer(Importer),
ProcessedTypes(ProcessedTypes),
Session(Session),
Input(Input) {}
Error operator()(uint32_t Modi, const SymbolGroup &SG) {
auto ExpectedModS = getModuleDebugStream(*Importer.getPDBFile(), Modi);
if (ExpectedModS) {
ModuleDebugStreamRef &ModS = *ExpectedModS;
SymbolVisitorCallbackPipeline Pipeline;
SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb);
PDBImporterSymbolVisitor SymVisitor(Importer.getModel(),
ProcessedTypes,
Session,
Importer.getBaseAddress());
Pipeline.addCallbackToPipeline(Deserializer);
Pipeline.addCallbackToPipeline(SymVisitor);
CVSymbolVisitor Visitor(Pipeline);
auto SS = ModS.getSymbolsSubstream();
if (auto Err = Visitor.visitSymbolStream(ModS.getSymbolArray(),
SS.Offset))
return createStringError(errorToErrorCode(std::move(Err)),
Input.getFilePath());
} else {
// If the module stream does not exist, it is not an
// error condition.
consumeError(ExpectedModS.takeError());
}
return Error::success();
}
};
void PDBImporterImpl::populateSymbolsWithTypes(NativeSession &Session) {
auto InputFile = InputFile::open(Importer.getPDBFile()->getFilePath());
if (not InputFile) {
revng_log(Log, "Unable to open PDB file: " << InputFile.takeError());
consumeError(InputFile.takeError());
return;
}
FilterOptions Filters{};
LinePrinter Printer(/*Indent=*/2, false, nulls(), Filters);
const PrintScope HeaderScope(Printer, /*IndentLevel=*/2);
PDBSymbolHandler SymbolHandler(Importer, ProcessedTypes, Session, *InputFile);
if (auto Err = iterateSymbolGroups(*InputFile, HeaderScope, SymbolHandler)) {
revng_log(Log, "Unable to parse symbols: " << Err);
consumeError(std::move(Err));
return;
}
}
void PDBImporterImpl::run(NativeSession &Session) {
populateTypes();
populateSymbolsWithTypes(Session);
TupleTree<model::Binary> &Model = Importer.getModel();
deduplicateEquivalentTypes(Model);
promoteOriginalName(Model);
purgeUnreachableTypes(Model);
revng_assert(Model->verify(true));
}
bool PDBImporter::loadDataFromPDB(StringRef PDBFileName) {
auto Err = loadDataForPDB(PDB_ReaderType::Native, PDBFileName, Session);
if (Err) {
revng_log(Log, "Unable to read PDB file: " << Err);
consumeError(std::move(Err));
return false;
}
TheNativeSession = static_cast<NativeSession *>(Session.get());
// TODO: We are using the static_cast due to lack of an LLVM RTTI
// support for this. Once it is improved in LLVM, we should avoid this.
auto SessionLoadAddress = Session->getLoadAddress();
auto NativeSessionLoadAddress = TheNativeSession->getLoadAddress();
revng_assert(SessionLoadAddress == NativeSessionLoadAddress);
ThePDBFile = &TheNativeSession->getPDBFile();
if (ExpectedGUID) {
if (auto PDBInfoStrm = ThePDBFile->getPDBInfoStream()) {
codeview::GUID GUIDFromPDBFile = PDBInfoStrm->getGuid();
if (ExpectedGUID != GUIDFromPDBFile) {
revng_log(Log, "Signatures from exe and PDB file mismatch");
return false;
}
}
}
return true;
}
static bool fileExists(const Twine &Path) {
bool Result = sys::fs::exists(Path);
if (Result) {
revng_log(Log, "Found: " << Path.str());
} else {
revng_log(Log, "The following path does not exist: " << Path.str());
}
return Result;
}
std::optional<std::string>
PDBImporter::getCachedPDBFilePath(std::string PDBFileID,
StringRef PDBBaseName) {
llvm::SmallString<128> ResultPath;
ResultPath.clear();
setXDG(ResultPath, "XDG_CACHE_HOME", ".cache");
llvm::sys::path::append(ResultPath, "revng", "debug-symbols", "pe");
llvm::sys::path::append(ResultPath, PDBFileID, PDBBaseName);
if (fileExists(ResultPath.str()))
return std::string(ResultPath.str());
return std::nullopt;
}
// Construct PDB file ID.
static std::string formatPDBFileID(ArrayRef<uint8_t> Bytes, uint16_t Age) {
std::string PDBGUID;
raw_string_ostream StringPDBGUID(PDBGUID);
StringPDBGUID << format_bytes(Bytes,
/*FirstByteOffset*/ {},
/*NumPerLine*/ 16,
/*ByteGroupSize*/ 16);
StringPDBGUID.flush();
// Let's format the PDB file ID.
// The PDB GUID is `7209ac2725e5fe841a88b1fe70d1603b` and `Age` is 2.
// The PDB ID `Hash` is: `27ac0972e52584fe1a88b1fe70d1603b2`.
std::string PDBFileID;
PDBFileID += PDBGUID[6];
PDBFileID += PDBGUID[7];
PDBFileID += PDBGUID[4];
PDBFileID += PDBGUID[5];
PDBFileID += PDBGUID[2];
PDBFileID += PDBGUID[3];
PDBFileID += PDBGUID[0];
PDBFileID += PDBGUID[1];
PDBFileID += PDBGUID[10];
PDBFileID += PDBGUID[11];
PDBFileID += PDBGUID[8];
PDBFileID += PDBGUID[9];
PDBFileID += PDBGUID[14];
PDBFileID += PDBGUID[15];
PDBFileID += PDBGUID[12];
PDBFileID += PDBGUID[13];
PDBFileID += PDBGUID.substr(16);
PDBFileID += ('0' + Age);
return PDBFileID;
}
void PDBImporter::import(const COFFObjectFile &TheBinary,
const ImporterOptions &Options) {
if (Options.DebugInfo == DebugInfoLevel::No)
return;
auto MaybePDBPath = getPDBFilePath(TheBinary);
if (not MaybePDBPath)
return;
if (not loadDataFromPDB(*MaybePDBPath))
return;
PDBImporterImpl ModelCreator(*this);
ModelCreator.run(*TheNativeSession);
}
static StringRef getBaseName(StringRef Path) {
auto PositionOfLastDirectoryChar = Path.rfind("\\");
if (PositionOfLastDirectoryChar != llvm::StringRef::npos) {
return Path.slice(PositionOfLastDirectoryChar + 1, Path.size());
}
return Path;
}
std::optional<std::string>
PDBImporter::getPDBFilePath(const COFFObjectFile &TheBinary) {
// Consider the --use-pdb argument
if (not UsePDB.empty()) {
if (not fileExists(UsePDB)) {
revng_log(Log, "Argument --use-pdb does not exist, ignoring.");
} else {
return UsePDB;
}
}
// Parse debug info in TheBinary
const codeview::DebugInfo *DebugInfo = nullptr;
std::string InternalPDBPath;
{
StringRef InternalPDBStringReference;
auto EC = TheBinary.getDebugPDBInfo(DebugInfo, InternalPDBStringReference);
if (EC) {
revng_log(Log, "getDebugPDBInfo failed: " << EC);
consumeError(std::move(EC));
return std::nullopt;
} else if (DebugInfo == nullptr) {
revng_log(Log, "Couldn't get codeview::DebugInfo");
return std::nullopt;
} else {
InternalPDBPath = InternalPDBStringReference.str();
}
}
// TODO: Handle PDB signature types other then PDB70, e.g. PDB20.
if (DebugInfo->Signature.CVSignature != OMF::Signature::PDB70) {
revng_log(Log, "A non-PDB70 signature was find, ignore.");
return std::nullopt;
}
// According to llvm/docs/PDB/PdbStream.rst, the `Signature` was never
// used the way as it was the initial idea. Instead, GUID is a 128-bit
// identifier guaranteed to be unique ID for both executable and
// corresponding PDB. Save the GUID for later to check the match.
ExpectedGUID = llvm::codeview::GUID();
llvm::copy(DebugInfo->PDB70.Signature, std::begin(ExpectedGUID->Guid));
if (InternalPDBPath.empty()) {
revng_log(Log, "The internal PDB path is empty");
return std::nullopt;
}
// If the internal PDB path exists, use that
if (fileExists(InternalPDBPath)) {
return InternalPDBPath;
}
// The path specified in the binary does not exist: extract the file name and
// look for it in other (canonical) places
StringRef PDBBaseName = getBaseName(InternalPDBPath);
// Try in the current directory
llvm::SmallString<128> ResultPath;
if (auto ErrorCode = llvm::sys::fs::current_path(ResultPath)) {
revng_log(Log, "Can't get current working path.");
} else {
llvm::sys::path::append(ResultPath, PDBBaseName);
if (fileExists(ResultPath.str()))
return ResultPath.str().str();
}
// Try main input path
ResultPath.clear();
if (not InputPath.empty()) {
llvm::sys::path::append(ResultPath,
llvm::sys::path::parent_path(InputPath),
PDBBaseName);
if (fileExists(ResultPath.str()))
return ResultPath.str().str();
}
// Compute the PDB file ID
auto PDBFileID = formatPDBFileID(DebugInfo->PDB70.Signature,
DebugInfo->PDB70.Age);
// Check if we already fetched it from PDB servers in the past
if (auto MaybeCachedPDBPath = getCachedPDBFilePath(PDBFileID, PDBBaseName))
return MaybeCachedPDBPath;
// Let's try finding it on web with the `fetch-debuginfo` tool.
// If the `revng` cannot be found, avoid finding debug info.
int ExitCode = runFetchDebugInfo(TheBinary.getFileName());
if (ExitCode != 0) {
revng_log(Log,
"Failed to find debug info with `revng model "
"fetch-debuginfo`.");
return std::nullopt;
}
// Try again to find the file
return getCachedPDBFilePath(PDBFileID, PDBBaseName);
}
// ==== Implementation of the Model type recordings. ==== //
Error PDBImporterTypeVisitor::visitTypeBegin(CVType &Record) {
return visitTypeBegin(Record, TypeIndex::fromArrayIndex(Types.size()));
}
Error PDBImporterTypeVisitor::visitTypeBegin(CVType &Record, TypeIndex TI) {
CurrentTypeIndex = TI;
return Error::success();
}
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
FieldListRecord &FieldList) {
if (auto EC = visitMemberRecordStream(FieldList.Data, *this))
return EC;
return Error::success();
}
// Determine the pointer size based on CodeView/PDB data.
static uint32_t getPointerSize(codeview::PointerKind K) {
switch (K) {
case codeview::PointerKind::Near64:
return 8;
case codeview::PointerKind::Near32:
return 4;
default:
// TODO: Handle all pointer kinds.
revng_abort();
}
}
// Parse LF_POINTER.
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
PointerRecord &Ptr) {
TypeIndex ReferencedType = Ptr.getReferentType();
auto ReferencedTypeFromModel = makeModelTypeForIndex(ReferencedType);
if (ReferencedTypeFromModel.isEmpty()) {
revng_log(Log,
"LF_POINTER: Unknown referenced type "
<< ReferencedType.getIndex());
return Error::success();
}
auto Pointer = model::PointerType::make(std::move(ReferencedTypeFromModel),
getPointerSize(Ptr.getPointerKind()));
auto [Typedef, NewType] = Model->makeTypedefDefinition();
Typedef.UnderlyingType() = std::move(Pointer);
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
return Error::success();
}
// Parse LF_ARRAY.
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
ArrayRecord &Array) {
TypeIndex ElementType = Array.getElementType();
auto ElementTypeFromModel = makeModelTypeForIndex(ElementType);
if (ElementTypeFromModel.isEmpty()) {
revng_log(Log, "LF_ARRAY: Unknown element type " << ElementType.getIndex());
} else {
auto MaybeSize = ElementTypeFromModel->size();
if (not MaybeSize or *MaybeSize == 0 or Array.getSize() == 0) {
revng_log(Log, "Skipping 0-sized array.");
return Error::success();
}
const uint64_t ArraySize = Array.getSize() / *MaybeSize;
auto NewA = model::ArrayType::make(std::move(ElementTypeFromModel),
ArraySize);
auto [_, NewType] = Model->makeTypedefDefinition(std::move(NewA));
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
}
return Error::success();
}
// Parse LF_MODIFIER.
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
ModifierRecord &Modifier) {
TypeIndex ReferencedType = Modifier.getModifiedType();
auto ModelType = makeModelTypeForIndex(ReferencedType);
if (ModelType.isEmpty()) {
revng_log(Log,
"LF_MODIFIER: Unknown referenced type "
<< ReferencedType.getIndex());
} else {
using ModifierOs = ModifierOptions;
if ((Modifier.getModifiers() & ModifierOs::Const) != ModifierOs::None) {
auto [_, NewType] = Model->makeTypedefDefinition(std::move(ModelType));
NewType->IsConst() = true;
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
}
}
return Error::success();
}
// Parse LF_MEMBER.
Error PDBImporterTypeVisitor::visitKnownMember(CVMemberRecord &Record,
DataMemberRecord &Member) {
InProgressMemberTypes[CurrentTypeIndex].push_back(Member);
return Error::success();
}
llvm::Error
PDBImporterTypeVisitor::visitKnownRecord(CVType &CVR,
MemberFunctionRecord &MemberFnRecord) {
InProgressConcreteFunctionMemberTypes[CurrentTypeIndex] = MemberFnRecord;
return Error::success();
}
// Parse LF_ONEMETHOD.
// This occurs within LF_CLASS and it references an LF_MFUNCTION.
Error PDBImporterTypeVisitor::visitKnownMember(CVMemberRecord &Record,
OneMethodRecord &FnMember) {
InProgressFunctionMemberTypes[CurrentTypeIndex].push_back(FnMember);
return Error::success();
}
// Parse LF_ENUMERATE.
Error PDBImporterTypeVisitor::visitKnownMember(CVMemberRecord &Record,
EnumeratorRecord &Member) {
InProgressEnumeratorTypes[CurrentTypeIndex].push_back(Member);
return Error::success();
}
// LF_CLASS, LF_STRUCTURE, LF_INTERFACE (TPI)
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
ClassRecord &Class) {
using namespace model;
if (isUdtForwardRef(Record)) {
Expected<TypeIndex> EFD = Tpi.findFullDeclForForwardRef(CurrentTypeIndex);
if (!EFD) {
consumeError(EFD.takeError());
revng_log(Log,
"LF_STRUCTURE: Cannot resolve fwd ref for the: "
<< CurrentTypeIndex.getIndex());
return Error::success();
}
// Remember forward reference, so we can process it later.
ForwardReferencedTypes[*EFD] = CurrentTypeIndex;
uint64_t ForwardTypeSize = getSizeInBytesForTypeRecord(Tpi.getType(*EFD));
if (ForwardTypeSize == 0) {
// 0-sized structs are typedef'ed to void. It can happen that there is
// an incomplete struct type.
model::UpcastableType Void = model::PrimitiveType::makeVoid();
auto [Typedef, NewType] = Model->makeTypedefDefinition(std::move(Void));
Typedef.OriginalName() = Class.getName();
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
} else {
// Pre-create the type that is being referenced by this type.
auto [Struct, NewType] = Model->makeStructDefinition();
Struct.OriginalName() = Class.getName();
Struct.Size() = ForwardTypeSize;
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
}
return Error::success();
}
TypeIndex FieldsTypeIndex = Class.getFieldList();
bool WasReferenced = ForwardReferencedTypes.count(CurrentTypeIndex) != 0;
if (InProgressMemberTypes.count(FieldsTypeIndex) != 0) {
model::StructDefinition *Struct = nullptr;
auto NewDefinition = makeTypeDefinition<model::StructDefinition>();
if (not WasReferenced) {
NewDefinition->OriginalName() = Class.getName();
auto &NewStruct = llvm::cast<model::StructDefinition>(*NewDefinition);
NewStruct.Size() = Class.getSize();
Struct = &NewStruct;
} else {
TypeIndex ForwardRef = ForwardReferencedTypes[CurrentTypeIndex];
Struct = &ProcessedTypes[ForwardRef]->toStruct();
}
auto &TheFields = InProgressMemberTypes[FieldsTypeIndex];
uint64_t MaxOffset = 0;
for (const auto &Field : TheFields) {
// Create new field.
uint64_t Offset = Field.getFieldOffset();
auto FieldModelType = makeModelTypeForIndex(Field.getType());
if (FieldModelType.isEmpty()) {
revng_log(Log,
"LF_STRUCTURE: Unknown field type "
<< Field.getType().getIndex());
// Avoid incomplete struct types.
return Error::success();
} else {
auto MaybeSize = FieldModelType->size();
uint64_t Size = MaybeSize.value_or(0);
if (Size == 0) {
// Skip 0-sized field.
revng_log(Log, "Skipping 0-sized struct field.");
continue;
}
// This is weird, but I've faced something like:
// PDB struct TYPE {
// offset_0: "sign" // size 1
// offset_1: "Local" // size 1
//
// // and again
// offset_0: "signLocal" // size 2
// }
uint64_t CurrFieldOffset = Offset + Size;
if (CurrFieldOffset > MaxOffset)
MaxOffset = CurrFieldOffset;
else
continue;
// TODO: How is this possible?
// Triggers:
// `Last field ends outside the struct`.
if (CurrFieldOffset > Struct->Size()) {
revng_log(Log, "Skipping struct field that is outside the struct.");
continue;
}
auto &FieldType = Struct->Fields()[Offset];
FieldType.OriginalName() = Field.getName().str();
FieldType.Type() = std::move(FieldModelType);
}
}
if (not WasReferenced) {
auto [_, NewType] = Model->recordNewType(std::move(NewDefinition));
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
} else {
TypeIndex ForwardRef = ForwardReferencedTypes[CurrentTypeIndex];
ProcessedTypes[CurrentTypeIndex] = ProcessedTypes[ForwardRef].copy();
}
}
// Process methods. Create C-like function prototype for it.
if (InProgressFunctionMemberTypes.contains(FieldsTypeIndex)) {
auto &TheFunctions = InProgressFunctionMemberTypes[FieldsTypeIndex];
for (auto &Function : TheFunctions) {
TypeIndex FnTypeIndex = Function.getType();
if (InProgressConcreteFunctionMemberTypes.count(FnTypeIndex) == 0)
continue;
// Get the proper LF_MFUNCTION.
auto &MemberFunction = InProgressConcreteFunctionMemberTypes[FnTypeIndex];
TypeIndex ReturnTypeIndex = MemberFunction.ReturnType;
auto ModelReturnType = makeModelTypeForIndex(ReturnTypeIndex);
if (ModelReturnType.isEmpty()) {
revng_log(Log,
"LF_MFUNCTION: Unknown return type "
<< ReturnTypeIndex.getIndex());
// Avoid function types that have incomplete type.
return Error::success();
}
auto NewDefinition = makeTypeDefinition<CABIFunctionDefinition>();
auto &Prototype = *cast<CABIFunctionDefinition>(NewDefinition.get());
Prototype.ABI() = Model->DefaultABI();
if (!ModelReturnType.isEmpty() && !ModelReturnType->isVoidPrimitive())
Prototype.ReturnType() = std::move(ModelReturnType);
TypeIndex ArgListTyIndex = MemberFunction.getArgumentList();
revng_assert(InProgressArgumentsTypes.contains(ArgListTyIndex));
auto ArgList = InProgressArgumentsTypes[ArgListTyIndex];
auto Indices = ArgList.getIndices();
uint32_t Size = Indices.size();
// Add `this` pointer as an argument if the method is not marked
// as `static` or `friend`.
if (Function.getMethodKind() != MethodKind::Static
and Function.getMethodKind() != MethodKind::Friend
and ProcessedTypes.count(CurrentTypeIndex) != 0) {
revng_assert(ProcessedTypes[CurrentTypeIndex].get());
auto MaybeSize = ProcessedTypes[CurrentTypeIndex].get()->size();
if (MaybeSize and *MaybeSize != 0) {
model::UpcastableType T = ProcessedTypes[CurrentTypeIndex].copy();
auto &Architecture = Model->Architecture();
Prototype.addArgument(model::PointerType::make(std::move(T),
Architecture));
} else {
revng_log(Log, "Skipping 0-sized argument.");
}
}
for (uint32_t I = 0; I < Size; ++I) {
TypeIndex ArgumentTypeIndex = Indices[I];
auto ArgumentTypeFromModel = makeModelTypeForIndex(ArgumentTypeIndex);
if (not ArgumentTypeFromModel) {
revng_log(Log,
"LF_MFUNCTION: Unknown arg type "
<< ArgumentTypeIndex.getIndex());
// Avoid function types that have incomplete type.
return Error::success();
} else {
auto MaybeSize = ArgumentTypeFromModel->size();
uint64_t Size = MaybeSize.value_or(0);
if (Size == 0) {
// Skip 0-sized type.
revng_log(Log, "Skipping 0-sized argument.");
continue;
}
Prototype.addArgument(std::move(ArgumentTypeFromModel));
}
}
auto [_, NewType] = Model->recordNewType(std::move(NewDefinition));
ProcessedTypes[FnTypeIndex] = std::move(NewType);
}
}
return Error::success();
}
// LF_ENUM (TPI)
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
EnumRecord &Enum) {
TypeIndex FieldsTypeIndex = Enum.getFieldList();
auto NewDefinition = model::makeTypeDefinition<model::EnumDefinition>();
auto &NewEnum = *cast<model::EnumDefinition>(NewDefinition.get());
NewEnum.OriginalName() = Enum.getName();
TypeIndex UnderlyingTypeIndex = Enum.getUnderlyingType();
auto UnderlyingModelType = makeModelTypeForIndex(UnderlyingTypeIndex);
if (not UnderlyingModelType) {
revng_log(Log,
"LF_ENUM: Unknown underlying type "
<< UnderlyingTypeIndex.getIndex());
return Error::success();
}
NewEnum.UnderlyingType() = std::move(UnderlyingModelType);
auto &TheFields = InProgressEnumeratorTypes[FieldsTypeIndex];
if (TheFields.empty())
return Error::success();
for (const auto &Entry : TheFields) {
auto &EnumEntry = NewEnum.Entries()[Entry.getValue().getExtValue()];
EnumEntry.OriginalName() = Entry.getName().str();
}
auto [_, NewType] = Model->recordNewType(std::move(NewDefinition));
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
return Error::success();
}
static inline constexpr model::ABI::Values
getMicrosoftABI(CallingConvention CallConv, model::Architecture::Values Arch) {
if (Arch == model::Architecture::x86_64) {
switch (CallConv) {
case CallingConvention::NearC:
case CallingConvention::NearFast:
case CallingConvention::NearStdCall:
case CallingConvention::NearSysCall:
case CallingConvention::ThisCall:
case CallingConvention::NearPascal:
return model::ABI::Microsoft_x86_64;
case CallingConvention::NearVector:
return model::ABI::Microsoft_x86_64_vectorcall;
case CallingConvention::ClrCall:
revng_abort("ClrCall is not currently supported");
default:
revng_abort();
}
} else if (Arch == model::Architecture::x86) {
switch (CallConv) {
case CallingConvention::NearC:
return model::ABI::Microsoft_x86_cdecl;
case CallingConvention::NearFast:
return model::ABI::Microsoft_x86_fastcall;
case CallingConvention::NearStdCall:
return model::ABI::Microsoft_x86_stdcall;
case CallingConvention::NearSysCall:
return model::ABI::Microsoft_x86_stdcall;
case CallingConvention::ThisCall:
return model::ABI::Microsoft_x86_thiscall;
case CallingConvention::ClrCall:
revng_abort("ClrCall is not currently supported");
case CallingConvention::NearPascal:
return model::ABI::Pascal_x86;
case CallingConvention::NearVector:
return model::ABI::Microsoft_x86_vectorcall;
default:
revng_abort();
}
} else if (Arch == model::Architecture::mips
and CallConv == CallingConvention::MipsCall) {
return model::ABI::SystemV_MIPS_o32;
} else if (Arch == model::Architecture::mipsel
and CallConv == CallingConvention::MipsCall) {
return model::ABI::SystemV_MIPSEL_o32;
} else if (Arch == model::Architecture::arm
and CallConv == CallingConvention::ArmCall) {
return model::ABI::AAPCS;
} else if (Arch == model::Architecture::aarch64
and CallConv == CallingConvention::ArmCall) {
return model::ABI::AAPCS64;
} else {
revng_abort();
}
}
// LF_PROCEDURE (TPI)
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
ProcedureRecord &Proc) {
TypeIndex ReturnTypeIndex = Proc.ReturnType;
auto ModelReturnType = makeModelTypeForIndex(ReturnTypeIndex);
if (not ModelReturnType) {
revng_log(Log,
"LF_PROCEDURE: Unknown return type "
<< ReturnTypeIndex.getIndex());
} else {
auto NewDef = model::makeTypeDefinition<model::CABIFunctionDefinition>();
auto Prototype = cast<model::CABIFunctionDefinition>(NewDef.get());
Prototype->ABI() = getMicrosoftABI(Proc.getCallConv(),
Model->Architecture());
if (!ModelReturnType.isEmpty() && !ModelReturnType->isVoidPrimitive())
Prototype->ReturnType() = std::move(ModelReturnType);
TypeIndex ArgListTyIndex = Proc.getArgumentList();
auto ArgumentList = InProgressArgumentsTypes[ArgListTyIndex];
auto Indices = ArgumentList.getIndices();
uint32_t Size = Indices.size();
for (uint32_t I = 0; I < Size; ++I) {
TypeIndex ArgumentTypeIndex = Indices[I];
if (ArgumentTypeIndex.isNoneType()) {
revng_log(Log,
"LF_PROCEDURE: A NoneType argument type "
<< ArgumentTypeIndex.getIndex());
continue;
}
auto ArgumentTypeFromModel = makeModelTypeForIndex(ArgumentTypeIndex);
if (not ArgumentTypeFromModel) {
revng_log(Log,
"LF_PROCEDURE: Unknown argument type "
<< ArgumentTypeIndex.getIndex());
// Avoid incomplete function types.
return Error::success();
} else {
auto MaybeSize = ArgumentTypeFromModel->size();
uint64_t Size = MaybeSize.value_or(0);
// Forward references are processed later.
if (Size == 0 and !isUdtForwardRef(Tpi.getType(ArgumentTypeIndex))) {
// Skip 0-sized type.
revng_log(Log, "Skipping 0-sized argument.");
continue;
}
Prototype->addArgument(std::move(ArgumentTypeFromModel));
}
}
auto [_, NewType] = Model->recordNewType(std::move(NewDef));
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
}
return Error::success();
}
// LF_UNION (TPI)
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
UnionRecord &Union) {
TypeIndex FieldsTypeIndex = Union.getFieldList();
auto &TheFields = InProgressMemberTypes[FieldsTypeIndex];
if (TheFields.size() == 0) {
// Handle an empty union, similar to 0-sized structs.
// Typedef it to void.
model::UpcastableType Void = model::PrimitiveType::makeVoid();
auto [Typedef, NewType] = Model->makeTypedefDefinition(std::move(Void));
Typedef.OriginalName() = Union.getName().str();
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
return Error::success();
}
auto NewDefinition = model::makeTypeDefinition<model::UnionDefinition>();
auto &NewUnion = llvm::cast<model::UnionDefinition>(*NewDefinition.get());
NewUnion.OriginalName() = Union.getName().str();
bool GeneratedAtLeastOneField = false;
for (const auto &Field : TheFields) {
// Create new field.
auto FieldModelType = makeModelTypeForIndex(Field.getType());
if (FieldModelType.isEmpty()) {
revng_log(Log,
"LF_UNION: Unknown field type " << Field.getType().getIndex());
// Avoid incomplete unions.
return Error::success();
} else {
uint64_t Size = FieldModelType->size().value_or(0);
if (Size == 0) {
// Skip 0-sized field.
revng_log(Log, "Skipping 0-sized union field.");
continue;
}
GeneratedAtLeastOneField = true;
auto &FieldType = NewUnion.addField(std::move(FieldModelType));
FieldType.OriginalName() = Field.getName().str();
}
}
if (GeneratedAtLeastOneField) {
auto [_, NewType] = Model->recordNewType(std::move(NewDefinition));
ProcessedTypes[CurrentTypeIndex] = std::move(NewType);
}
return Error::success();
}
Error PDBImporterTypeVisitor::visitKnownRecord(CVType &Record,
ArgListRecord &Args) {
InProgressArgumentsTypes[CurrentTypeIndex] = Args;
return Error::success();
}
// TODO: This can go into LLVM, but there is an ongoing review that should
// implement this.
static std::optional<uint64_t> getSizeinBytes(TypeIndex TI) {
if (not TI.isSimple())
return std::nullopt;
switch (TI.getSimpleKind()) {
case SimpleTypeKind::Void:
return 0;
case SimpleTypeKind::HResult:
return 4;
case SimpleTypeKind::SByte:
case SimpleTypeKind::Byte:
return 1;
case SimpleTypeKind::Int16Short:
case SimpleTypeKind::UInt16Short:
case SimpleTypeKind::Int16:
case SimpleTypeKind::UInt16:
return 2;
case SimpleTypeKind::Int32Long:
case SimpleTypeKind::UInt32Long:
case SimpleTypeKind::Int32:
case SimpleTypeKind::UInt32:
return 4;
case SimpleTypeKind::Int64Quad:
case SimpleTypeKind::UInt64Quad:
case SimpleTypeKind::Int64:
case SimpleTypeKind::UInt64:
return 8;
case SimpleTypeKind::Int128Oct:
case SimpleTypeKind::UInt128Oct:
case SimpleTypeKind::Int128:
case SimpleTypeKind::UInt128:
return 16;
case SimpleTypeKind::SignedCharacter:
case SimpleTypeKind::UnsignedCharacter:
case SimpleTypeKind::NarrowCharacter:
return 1;
case SimpleTypeKind::WideCharacter:
case SimpleTypeKind::Character16:
return 2;
case SimpleTypeKind::Character32:
return 4;
case SimpleTypeKind::Float16:
return 2;
case SimpleTypeKind::Float32:
return 4;
case SimpleTypeKind::Float64:
return 8;
case SimpleTypeKind::Float80:
return 10;
case SimpleTypeKind::Float128:
return 16;
case SimpleTypeKind::Boolean8:
return 1;
case SimpleTypeKind::Boolean16:
return 2;
case SimpleTypeKind::Boolean32:
return 4;
case SimpleTypeKind::Boolean64:
return 8;
case SimpleTypeKind::Boolean128:
return 16;
default:
return std::nullopt;
}
}
static model::PrimitiveKind::Values
codeviewSimpleTypeEncodingToModel(TypeIndex TI) {
if (not TI.isSimple())
return model::PrimitiveKind::Invalid;
switch (TI.getSimpleKind()) {
case SimpleTypeKind::Void:
return model::PrimitiveKind::Void;
case SimpleTypeKind::Boolean8:
case SimpleTypeKind::Boolean16:
case SimpleTypeKind::Boolean32:
case SimpleTypeKind::Boolean64:
case SimpleTypeKind::Boolean128:
case SimpleTypeKind::Byte:
case SimpleTypeKind::UInt16:
case SimpleTypeKind::UInt32:
case SimpleTypeKind::UInt64:
case SimpleTypeKind::UnsignedCharacter:
case SimpleTypeKind::UInt16Short:
case SimpleTypeKind::UInt32Long:
case SimpleTypeKind::UInt64Quad:
case SimpleTypeKind::UInt128Oct:
case SimpleTypeKind::UInt128:
return model::PrimitiveKind::Unsigned;
case SimpleTypeKind::SignedCharacter:
case SimpleTypeKind::WideCharacter:
case SimpleTypeKind::NarrowCharacter:
case SimpleTypeKind::Character16:
case SimpleTypeKind::Character32:
case SimpleTypeKind::Int16:
case SimpleTypeKind::Int16Short:
case SimpleTypeKind::SByte:
case SimpleTypeKind::Int32Long:
case SimpleTypeKind::Int32:
case SimpleTypeKind::Int64Quad:
case SimpleTypeKind::Int64:
case SimpleTypeKind::Int128Oct:
case SimpleTypeKind::Int128:
return model::PrimitiveKind::Signed;
case SimpleTypeKind::Float16:
case SimpleTypeKind::Float32:
case SimpleTypeKind::Float64:
case SimpleTypeKind::Float80:
case SimpleTypeKind::Float128:
return model::PrimitiveKind::Float;
default:
return model::PrimitiveKind::Invalid;
}
}
static bool isPointer(TypeIndex TI) {
if (TI.getSimpleMode() != SimpleTypeMode::Direct) {
// We have a native pointer.
switch (TI.getSimpleMode()) {
case SimpleTypeMode::NearPointer32:
case SimpleTypeMode::FarPointer32:
case SimpleTypeMode::NearPointer64:
return true;
default:
return false;
}
}
return false;
}
static bool isTwoBytesLongPointer(TypeIndex TI) {
if (TI.getSimpleMode() != SimpleTypeMode::Direct) {
// We have a native pointer.
switch (TI.getSimpleMode()) {
case SimpleTypeMode::NearPointer:
case SimpleTypeMode::FarPointer:
case SimpleTypeMode::HugePointer:
return true;
default:
return false;
}
}
return false;
}
static bool isSixteenBytesLongPointer(TypeIndex TI) {
if (TI.getSimpleMode() != SimpleTypeMode::Direct) {
// We have a native pointer.
switch (TI.getSimpleMode()) {
case SimpleTypeMode::NearPointer128:
return true;
default:
return false;
}
}
return false;
}
static std::optional<uint64_t> getPointerSizeFromPDB(TypeIndex TI) {
if (TI.getSimpleMode() != SimpleTypeMode::Direct) {
// We have a native pointer.
switch (TI.getSimpleMode()) {
case SimpleTypeMode::NearPointer:
case SimpleTypeMode::FarPointer:
case SimpleTypeMode::HugePointer:
return 2;
case SimpleTypeMode::NearPointer32:
case SimpleTypeMode::FarPointer32:
return 4;
case SimpleTypeMode::NearPointer64:
return 8;
case SimpleTypeMode::NearPointer128:
return 16;
default:
return std::nullopt;
}
}
return std::nullopt;
}
model::UpcastableType
PDBImporterTypeVisitor::createPrimitiveType(TypeIndex SimpleType) {
if (isTwoBytesLongPointer(SimpleType)) {
// If it is a pointer of size 2, lets create a PointerOrNumber for it.
using PT = model::PrimitiveType;
constexpr uint64_t MSDOS16Pointer = 2;
return ProcessedTypes[SimpleType] = PT::makePointerOrNumber(MSDOS16Pointer);
} else if (isSixteenBytesLongPointer(SimpleType)) {
// If it is a 128-bit long pointer, typedef it to void for now. It can be
// represented as a `struct { pointee; offset; }` since it is how it is
// implemented in the msvc compiler.
revng_abort("128-bit pointers are not supported for now.");
model::UpcastableType Void = model::PrimitiveType::makeVoid();
auto [_, Typedef] = Model->makeTypedefDefinition(std::move(Void));
return ProcessedTypes[SimpleType] = std::move(Typedef);
} else {
auto PrimitiveKind = codeviewSimpleTypeEncodingToModel(SimpleType);
auto PrimitiveSize = getSizeinBytes(SimpleType);
if (PrimitiveSize and PrimitiveKind != model::PrimitiveKind::Invalid) {
auto Primitive = model::PrimitiveType::make(PrimitiveKind,
*PrimitiveSize);
if (isPointer(SimpleType)) {
auto PointerSize = getPointerSizeFromPDB(SimpleType);
if (!PointerSize) {
revng_log(Log, "Invalid pointer size " << SimpleType.getIndex());
return model::UpcastableType::empty();
}
auto Pointer = model::PointerType::make(std::move(Primitive),
*PointerSize);
auto Typedef = Model->makeTypedefDefinition(std::move(Pointer)).second;
return ProcessedTypes[SimpleType] = std::move(Typedef);
} else {
// If it is not a pointer `SimpleKind` will be the same as `SimpleType`.
revng_assert(TypeIndex(SimpleType.getSimpleKind()) == SimpleType);
return ProcessedTypes[SimpleType] = std::move(Primitive);
}
} else {
revng_log(Log, "Invalid simple type " << SimpleType.getIndex());
return model::UpcastableType::empty();
}
}
}
model::UpcastableType
PDBImporterTypeVisitor::makeModelTypeForIndex(TypeIndex Index) {
if (Index.isSimple())
return createPrimitiveType(Index);
if (auto Iter = ProcessedTypes.find(Index); Iter != ProcessedTypes.end())
return Iter->second.copy();
else
return model::UpcastableType::empty();
}
// ==== Implementation of the Model Symbol-type connection. ==== //
Error PDBImporterSymbolVisitor::visitSymbolBegin(CVSymbol &Record) {
return visitSymbolBegin(Record, 0);
}
Error PDBImporterSymbolVisitor::visitSymbolBegin(CVSymbol &Record,
uint32_t Offset) {
return Error::success();
}
Error PDBImporterSymbolVisitor::visitKnownRecord(CVSymbol &Record,
ProcSym &Proc) {
revng_log(Log, "Importing " << Proc.Name);
// If it is not in the .idata already, we assume it is a static symbol.
if (not Model->ImportedDynamicFunctions().contains(Proc.Name.str())) {
uint64_t FunctionVirtualAddress = Session
.getRVAFromSectOffset(Proc.Segment,
Proc.CodeOffset);
// Relocate the symbol.
MetaAddress FunctionAddress = ImageBase + FunctionVirtualAddress;
if (not Model->Functions().contains(FunctionAddress)) {
model::Function &Function = Model->Functions()[FunctionAddress];
Function.OriginalName() = Proc.Name;
TypeIndex FunctionTypeIndex = Proc.FunctionType;
if (ProcessedTypes.find(FunctionTypeIndex) != ProcessedTypes.end())
Function.Prototype() = ProcessedTypes[FunctionTypeIndex];
} else {
auto It = Model->Functions().find(FunctionAddress);
TypeIndex FunctionTypeIndex = Proc.FunctionType;
if (ProcessedTypes.find(FunctionTypeIndex) != ProcessedTypes.end())
It->Prototype() = ProcessedTypes[FunctionTypeIndex];
}
}
// TODO: Handle Imported functions.
return Error::success();
}