mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
Implement platform roots support
Refactor the binary import and dependency resolution infrastructure to support multiple platforms (Linux, Windows, macOS) via a configuration-driven root system. Major changes: * Overhaul PDB and DWARF importers for platform-aware debug info loading. * Refactor LDDTree into a template-based architecture with platform-specific implementations (ELF, PE/COFF). * Mostly rewrite the PDB importer, which had significant limitations.
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
|
||||
#include "revng/Model/BinaryIdentifier.h"
|
||||
#include "revng/Support/CommandLine.h"
|
||||
|
||||
namespace llvm {
|
||||
namespace object {
|
||||
class ELFObjectFileBase;
|
||||
class COFFObjectFile;
|
||||
class MachOObjectFile;
|
||||
} // namespace object
|
||||
} // namespace llvm
|
||||
|
||||
template<typename T>
|
||||
struct BinaryDescriptor {
|
||||
public:
|
||||
using ObjectFileType = T;
|
||||
|
||||
public:
|
||||
// Note: this has to be non-const because certain Mach-O methods mutate the
|
||||
// object.
|
||||
T &ObjectFile;
|
||||
|
||||
/// Path a temporary file containing the binary.
|
||||
///
|
||||
/// This path should never be used for binary-relative lookups (e.g., $ORIGIN
|
||||
/// or .debug).
|
||||
/// Use this only to pass it as an argument to external programs (e.g.,
|
||||
/// fetch-debug-info). In native code, you should use ObjectFile above.
|
||||
std::string FullPathForExternalTools;
|
||||
|
||||
const model::BinaryReference Reference;
|
||||
|
||||
/// This returns the path where the binary is supposed to be on the file
|
||||
/// system.
|
||||
///
|
||||
/// Use this for binary-relative lookups (e.g., $ORIGIN or .debug) but do not
|
||||
/// try to open, there are no guarantees there's an actual file a this path.
|
||||
///
|
||||
/// Also, this might be just a file name, a relative path or even empty.
|
||||
/// Use with caution.
|
||||
llvm::StringRef canonicalPath() const {
|
||||
if (Reference.isValid()) {
|
||||
return Reference.get()->CanonicalPath();
|
||||
} else if (not InputPath.empty()) {
|
||||
// Old pipeline only: use InputPath, a global variable set by hand in
|
||||
// Main.cpp
|
||||
// TODO: this should be dismissed along with the old pipeline
|
||||
return InputPath;
|
||||
} else {
|
||||
// TODO: this should be dismissed along with the old pipeline
|
||||
return llvm::sys::path::filename(FullPathForExternalTools);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ELFBinary = BinaryDescriptor<llvm::object::ELFObjectFileBase>;
|
||||
using COFFBinary = BinaryDescriptor<llvm::object::COFFObjectFile>;
|
||||
using MachOBinary = BinaryDescriptor<llvm::object::MachOObjectFile>;
|
||||
@@ -16,14 +16,14 @@ class ObjectFile;
|
||||
} // namespace llvm
|
||||
|
||||
struct ImporterOptions;
|
||||
|
||||
llvm::Error importBinary(TupleTree<model::Binary> &Model,
|
||||
llvm::object::ObjectFile &BinaryHandle,
|
||||
llvm::StringRef Filepath,
|
||||
llvm::object::ObjectFile &ObjectFile,
|
||||
llvm::StringRef FullPathForExternalTools,
|
||||
const ImporterOptions &Options,
|
||||
model::BinaryReference &BinaryReference);
|
||||
|
||||
llvm::Error importBinary(TupleTree<model::Binary> &Model,
|
||||
llvm::MemoryBuffer &Buffer,
|
||||
llvm::StringRef Filepath,
|
||||
llvm::StringRef FullPathForExternalTools,
|
||||
const ImporterOptions &Options,
|
||||
model::BinaryReference &BinaryReference);
|
||||
|
||||
@@ -9,20 +9,24 @@
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/Binary/Options.h"
|
||||
#include "revng/Model/Importer/ImportLogger.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
#include "revng/Support/MetaAddress.h"
|
||||
#include "revng/Support/MetaAddress/MetaAddressRange.h"
|
||||
|
||||
class BinaryImporterHelper {
|
||||
protected:
|
||||
model::Binary &Binary;
|
||||
TupleTree<model::Binary> &Binary;
|
||||
uint64_t BaseAddress = 0;
|
||||
Logger &Logger;
|
||||
MetaAddressRangeSet ExecutableRanges;
|
||||
bool SegmentsInitialized = false;
|
||||
|
||||
public:
|
||||
BinaryImporterHelper(model::Binary &Binary,
|
||||
BinaryImporterHelper(TupleTree<model::Binary> &Binary,
|
||||
uint64_t BaseAddress,
|
||||
::Logger &Logger) :
|
||||
Binary(Binary), BaseAddress(BaseAddress), Logger(Logger) {}
|
||||
@@ -39,8 +43,8 @@ public:
|
||||
MetaAddress toPC(const MetaAddress &Generic) const {
|
||||
revng_assert(Generic.isGeneric());
|
||||
using namespace model::Architecture;
|
||||
revng_assert(Binary.Architecture() != Invalid);
|
||||
return MetaAddress::fromPC(Binary.Architecture(),
|
||||
revng_assert(Binary->Architecture() != Invalid);
|
||||
return MetaAddress::fromPC(Binary->Architecture(),
|
||||
Generic.address(),
|
||||
Generic.epoch(),
|
||||
Generic.addressSpace());
|
||||
@@ -48,19 +52,19 @@ public:
|
||||
|
||||
MetaAddress fromPC(uint64_t PC) const {
|
||||
using namespace model::Architecture;
|
||||
revng_assert(Binary.Architecture() != Invalid);
|
||||
return MetaAddress::fromPC(Binary.Architecture(), PC);
|
||||
revng_assert(Binary->Architecture() != Invalid);
|
||||
return MetaAddress::fromPC(Binary->Architecture(), PC);
|
||||
}
|
||||
|
||||
MetaAddress fromGeneric(uint64_t Address) const {
|
||||
using namespace model::Architecture;
|
||||
revng_assert(Binary.Architecture() != Invalid);
|
||||
return MetaAddress::fromGeneric(Binary.Architecture(), Address);
|
||||
revng_assert(Binary->Architecture() != Invalid);
|
||||
return MetaAddress::fromGeneric(Binary->Architecture(), Address);
|
||||
}
|
||||
|
||||
public:
|
||||
void processSegments() {
|
||||
ExecutableRanges = Binary.executableRanges();
|
||||
ExecutableRanges = Binary->executableRanges();
|
||||
SegmentsInitialized = true;
|
||||
}
|
||||
|
||||
@@ -73,7 +77,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
Binary.ExtraCodeAddresses().insert(Address);
|
||||
Binary->ExtraCodeAddresses().insert(Address);
|
||||
}
|
||||
|
||||
model::Function *registerFunctionEntry(const MetaAddress &Address) {
|
||||
@@ -84,7 +88,10 @@ public:
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &Binary.Functions()[Address];
|
||||
if (not Binary->Functions().contains(Address))
|
||||
revng_log(Logger, "Registering new function at " << Address.toString());
|
||||
|
||||
return &Binary->Functions()[Address];
|
||||
}
|
||||
|
||||
void setEntryPoint(const MetaAddress &Address) {
|
||||
@@ -95,7 +102,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
Binary.EntryPoint() = Address;
|
||||
Binary->EntryPoint() = Address;
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -113,4 +120,13 @@ private:
|
||||
bool isExecutable(const MetaAddress &Address) const {
|
||||
return ExecutableRanges.contains(Address);
|
||||
}
|
||||
|
||||
protected:
|
||||
ImportLogger importLogger(llvm::StringRef Path) {
|
||||
return ImportLogger(Binary, Logger, Path);
|
||||
}
|
||||
|
||||
template<IsObjectFile T>
|
||||
std::optional<LDDTree>
|
||||
identifyDependencies(const T &ObjectFile, llvm::StringRef CanonicalPath);
|
||||
};
|
||||
|
||||
@@ -7,18 +7,29 @@
|
||||
#include "llvm/Object/Binary.h"
|
||||
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
|
||||
struct ImporterOptions;
|
||||
|
||||
class DwarfImporter {
|
||||
public:
|
||||
using AddressWhitelist = std::set<uint64_t>;
|
||||
|
||||
private:
|
||||
TupleTree<model::Binary> &Model;
|
||||
std::vector<std::string> LoadedFiles;
|
||||
using DwarfID = std::pair<size_t, size_t>;
|
||||
std::map<DwarfID, model::UpcastableType> DwarfToModel;
|
||||
const AddressWhitelist *FunctionWhitelist = nullptr;
|
||||
|
||||
public:
|
||||
DwarfImporter(TupleTree<model::Binary> &Model) : Model(Model) {}
|
||||
DwarfImporter(TupleTree<model::Binary> &Model,
|
||||
const std::optional<AddressWhitelist> &FunctionWhitelist) :
|
||||
Model(Model),
|
||||
FunctionWhitelist(FunctionWhitelist.has_value() ?
|
||||
&*FunctionWhitelist :
|
||||
static_cast<const AddressWhitelist *>(nullptr)) {}
|
||||
|
||||
public:
|
||||
model::UpcastableType findType(DwarfID ID) {
|
||||
@@ -35,16 +46,25 @@ public:
|
||||
|
||||
TupleTree<model::Binary> &getModel() { return Model; }
|
||||
|
||||
public:
|
||||
void import(llvm::StringRef FileName, const ImporterOptions &Options);
|
||||
bool isFunctionAllowed(uint64_t Address) const {
|
||||
if (FunctionWhitelist == nullptr)
|
||||
return true;
|
||||
|
||||
void import(llvm::MemoryBufferRef Buffer,
|
||||
llvm::StringRef Filepath,
|
||||
const ImporterOptions &Options,
|
||||
llvm::StringRef Filename);
|
||||
return FunctionWhitelist->contains(Address);
|
||||
}
|
||||
|
||||
public:
|
||||
size_t import(llvm::StringRef FileName, const ImporterOptions &Options);
|
||||
|
||||
/// \p Root Optional.
|
||||
size_t import(const revng::RootEntry *Root,
|
||||
const ELFBinary &Binary,
|
||||
const ImporterOptions &Options);
|
||||
|
||||
private:
|
||||
void import(const llvm::object::Binary &TheBinary,
|
||||
llvm::StringRef FileName,
|
||||
uint64_t PreferredBaseAddress);
|
||||
// \return the index the imported Dwarf file, use this for DwarfID.
|
||||
size_t import(const llvm::object::Binary &TheBinary,
|
||||
llvm::StringRef CanonicalPath,
|
||||
uint64_t PreferredBaseAddress,
|
||||
size_t AltIndex);
|
||||
};
|
||||
|
||||
@@ -11,38 +11,55 @@
|
||||
#include "llvm/DebugInfo/PDB/PDB.h"
|
||||
#include "llvm/Object/Binary.h"
|
||||
#include "llvm/Object/COFF.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporterHelper.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
|
||||
struct ImporterOptions;
|
||||
|
||||
class PDBImporter : public BinaryImporterHelper {
|
||||
public:
|
||||
using AddressWhitelist = std::set<uint64_t>;
|
||||
|
||||
private:
|
||||
TupleTree<model::Binary> &Model;
|
||||
MetaAddress ImageBase;
|
||||
llvm::pdb::PDBFile *ThePDBFile = nullptr;
|
||||
llvm::pdb::NativeSession *TheNativeSession = nullptr;
|
||||
std::unique_ptr<llvm::pdb::IPDBSession> Session;
|
||||
std::optional<llvm::codeview::GUID> ExpectedGUID;
|
||||
const AddressWhitelist *FunctionWhitelist = nullptr;
|
||||
|
||||
public:
|
||||
PDBImporter(TupleTree<model::Binary> &Model, const MetaAddress &ImageBase);
|
||||
PDBImporter(TupleTree<model::Binary> &Model,
|
||||
const MetaAddress &ImageBase,
|
||||
const std::optional<AddressWhitelist> &FunctionWhitelist);
|
||||
|
||||
TupleTree<model::Binary> &getModel() { return Model; }
|
||||
PDBImporter(TupleTree<model::Binary> &Model,
|
||||
const std::optional<AddressWhitelist> &FunctionWhitelist) :
|
||||
PDBImporter(Model,
|
||||
MetaAddress::fromGeneric(Model->Architecture(), 0),
|
||||
FunctionWhitelist) {}
|
||||
|
||||
TupleTree<model::Binary> &getModel() { return Binary; }
|
||||
const MetaAddress &getBaseAddress() { return ImageBase; }
|
||||
llvm::pdb::PDBFile *getPDBFile() { return ThePDBFile; }
|
||||
auto *getNativeSession() { return TheNativeSession; }
|
||||
|
||||
void import(const llvm::object::COFFObjectFile &TheBinary,
|
||||
llvm::StringRef BinaryPath,
|
||||
void import(const revng::RootEntry *Root,
|
||||
const COFFBinary &Binary,
|
||||
const ImporterOptions &Options);
|
||||
|
||||
void importPDB(llvm::StringRef PDBPath, const ImporterOptions &Options);
|
||||
|
||||
bool loadDataFromPDB(llvm::StringRef PDBFileName);
|
||||
|
||||
std::optional<std::string>
|
||||
getPDBFilePath(const llvm::object::COFFObjectFile &TheBinary,
|
||||
llvm::StringRef BinaryPath);
|
||||
bool isFunctionAllowed(uint64_t Address) const {
|
||||
if (FunctionWhitelist == nullptr)
|
||||
return true;
|
||||
|
||||
std::optional<std::string> getCachedPDBFilePath(std::string PDBFileID,
|
||||
llvm::StringRef PDBFilePath);
|
||||
return FunctionWhitelist->contains(Address);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,10 +5,289 @@
|
||||
//
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include "llvm/ADT/SmallVector.h"
|
||||
#include "llvm/ADT/DenseSet.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Object/COFF.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Object/MachO.h"
|
||||
#include "llvm/Object/ObjectFile.h"
|
||||
|
||||
using LDDTree = std::map<std::string, llvm::SmallVector<std::string, 10>>;
|
||||
void lddtree(LDDTree &Dependencies, llvm::StringRef Path, unsigned DepthLevel);
|
||||
#include "revng/Model/Architecture.h"
|
||||
#include "revng/Model/Importer/Binary/Options.h"
|
||||
#include "revng/Model/OperatingSystem.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
|
||||
namespace revng {
|
||||
class RootEntry;
|
||||
}
|
||||
|
||||
inline Logger LDDTreeLog("lddtree");
|
||||
|
||||
template<typename T>
|
||||
concept IsObjectFile = std::derived_from<T, llvm::object::ObjectFile>;
|
||||
|
||||
class LDDTree {
|
||||
public:
|
||||
using SymbolSet = std::set<std::string>;
|
||||
using SymbolAddressMap = std::map<std::string, uint64_t>;
|
||||
class DependencyFile;
|
||||
class Dependency;
|
||||
|
||||
public:
|
||||
const revng::RootEntry *Root = nullptr;
|
||||
std::map<std::string, Dependency> Dependencies;
|
||||
std::optional<LDDTree::SymbolSet> MissingSymbols;
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
void findMissingTypes(const ImporterOptions &Options);
|
||||
|
||||
public:
|
||||
/// \p MissingSymbols if specified, the function will try to identify the root
|
||||
/// that resolves the most.
|
||||
/// If not specified, the function will try to identify all the libraries,
|
||||
/// recursively.
|
||||
template<IsObjectFile T>
|
||||
static const std::optional<LDDTree>
|
||||
fromPath(llvm::StringRef CanonicalPath,
|
||||
const T &ObjectFile,
|
||||
std::optional<LDDTree::SymbolSet> MissingSymbols) {
|
||||
revng_log(LDDTreeLog, "Getting dependencies of " << CanonicalPath);
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
bool SymbolsRequested = MissingSymbols.has_value();
|
||||
|
||||
LDDTree Result;
|
||||
for (const auto &[Name, Root] : revng::configuration().Root) {
|
||||
revng_log(LDDTreeLog, "Inspecting root " << Root.Path);
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
auto IsCompatible = [](model::OperatingSystem::Values OperatingSystem) {
|
||||
using namespace llvm::object;
|
||||
using std::derived_from;
|
||||
if constexpr (derived_from<T, ELFObjectFileBase>) {
|
||||
return OperatingSystem == model::OperatingSystem::Linux;
|
||||
} else if constexpr (derived_from<T, MachOObjectFile>) {
|
||||
return OperatingSystem == model::OperatingSystem::MacOS;
|
||||
} else if constexpr (derived_from<T, COFFObjectFile>) {
|
||||
return OperatingSystem == model::OperatingSystem::Windows;
|
||||
} else {
|
||||
revng_abort();
|
||||
}
|
||||
};
|
||||
|
||||
if (not IsCompatible(Root.OperatingSystem)) {
|
||||
revng_log(LDDTreeLog,
|
||||
"Skipping incompatible rootfs: incompatible binary format");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto
|
||||
Architecture = model::Architecture::fromLLVMArchitecture(ObjectFile
|
||||
.getArch());
|
||||
if (Root.Architecture != Architecture) {
|
||||
revng_log(LDDTreeLog,
|
||||
"Skipping incompatible rootfs: incompatible architecture");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto NewResult = LDDTree::fromPath(Root,
|
||||
CanonicalPath,
|
||||
ObjectFile,
|
||||
MissingSymbols);
|
||||
|
||||
if (SymbolsRequested and NewResult.MissingSymbols.has_value()) {
|
||||
auto ResolvedSymbolsCount = (MissingSymbols->size()
|
||||
- NewResult.MissingSymbols->size());
|
||||
revng_log(LDDTreeLog,
|
||||
"Resolved " << ResolvedSymbolsCount << " out of "
|
||||
<< MissingSymbols->size() << " symbols");
|
||||
|
||||
if (NewResult.MissingSymbols->size() > 0) {
|
||||
if (LDDTreeLog.isEnabled()) {
|
||||
LDDTreeLog << "The following symbols have not been resolved:\n";
|
||||
for (const std::string &MissingSymbol : *NewResult.MissingSymbols) {
|
||||
LDDTreeLog << " " << MissingSymbol << "\n";
|
||||
}
|
||||
LDDTreeLog << DoLog;
|
||||
}
|
||||
}
|
||||
|
||||
auto UnresolvedSymbols = NewResult.MissingSymbols->size();
|
||||
if (UnresolvedSymbols == 0) {
|
||||
Result = std::move(NewResult);
|
||||
// Stop the search, we found all the symbols!
|
||||
break;
|
||||
} else if (UnresolvedSymbols < Result.MissingSymbols->size()) {
|
||||
revng_assert(NewResult.MissingSymbols.has_value());
|
||||
Result = std::move(NewResult);
|
||||
}
|
||||
} else {
|
||||
if (Result.Dependencies.size() < NewResult.Dependencies.size()) {
|
||||
Result = std::move(NewResult);
|
||||
}
|
||||
}
|
||||
|
||||
// It was the first attempt
|
||||
if (Result.Root == nullptr)
|
||||
Result = std::move(NewResult);
|
||||
}
|
||||
|
||||
if (Result.Root == nullptr) {
|
||||
revng_log(LDDTreeLog, "No matching rootfs found");
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<IsObjectFile T>
|
||||
static const LDDTree
|
||||
fromPath(const revng::RootEntry &Root,
|
||||
llvm::StringRef CanonicalPath,
|
||||
const T &ObjectFile,
|
||||
std::optional<LDDTree::SymbolSet> MissingSymbols);
|
||||
|
||||
public:
|
||||
void dump() const debug_function { dump(dbg); }
|
||||
|
||||
template<typename T>
|
||||
void dump(T &Stream) const;
|
||||
};
|
||||
|
||||
class LDDTree::DependencyFile {
|
||||
private:
|
||||
/// The loaded binary.
|
||||
llvm::object::OwningBinary<llvm::object::Binary> Binary;
|
||||
|
||||
/// The path where the file is. Use this only to pass it to external tools.
|
||||
/// Use ObjectFile otherwise.
|
||||
std::string FullPathForExternalTools;
|
||||
|
||||
/// The path where the file is supposed to be. Don't open this.
|
||||
std::string CanonicalPath;
|
||||
|
||||
public:
|
||||
DependencyFile(llvm::object::OwningBinary<llvm::object::Binary> &&Binary,
|
||||
std::string FullPathForExternalTools,
|
||||
std::string CanonicalPath) :
|
||||
Binary(std::move(Binary)),
|
||||
FullPathForExternalTools(std::move(FullPathForExternalTools)),
|
||||
CanonicalPath(std::move(CanonicalPath)) {}
|
||||
|
||||
DependencyFile(const DependencyFile &) = delete;
|
||||
DependencyFile(DependencyFile &&) = default;
|
||||
DependencyFile &operator=(const DependencyFile &) = delete;
|
||||
DependencyFile &operator=(DependencyFile &&) = default;
|
||||
|
||||
public:
|
||||
llvm::object::ObjectFile &objectFile() {
|
||||
return *llvm::cast<llvm::object::ObjectFile>(Binary.getBinary());
|
||||
}
|
||||
|
||||
const auto &fullPathForExternalTools() const {
|
||||
return FullPathForExternalTools;
|
||||
}
|
||||
|
||||
const auto &canonicalPath() const { return CanonicalPath; }
|
||||
|
||||
public:
|
||||
void dump() const debug_function { dump(dbg, ""); }
|
||||
|
||||
template<typename T>
|
||||
void dump(T &Stream, const char *Prefix) const {
|
||||
Stream << Prefix << "ObjectFile: ";
|
||||
if (auto *Binary = this->Binary.getBinary()) {
|
||||
Stream << Binary->getFileName().str();
|
||||
} else {
|
||||
Stream << "unavailable";
|
||||
}
|
||||
Stream << "\n";
|
||||
|
||||
Stream << Prefix << "CanonicalPath: " << CanonicalPath << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
class LDDTree::Dependency : public DependencyFile {
|
||||
private:
|
||||
/// Subset of the requested symbols that are provided by this library.
|
||||
SymbolAddressMap ProvidedSymbols;
|
||||
|
||||
/// Identifier the library requesting this.
|
||||
std::string RequestedBy;
|
||||
|
||||
public:
|
||||
Dependency(DependencyFile &&TheDependencyFile,
|
||||
SymbolAddressMap ProvidedSymbols,
|
||||
std::string RequestedBy) :
|
||||
DependencyFile(std::move(TheDependencyFile)),
|
||||
ProvidedSymbols(std::move(ProvidedSymbols)),
|
||||
RequestedBy(std::move(RequestedBy)) {}
|
||||
Dependency(const Dependency &) = delete;
|
||||
Dependency(Dependency &&) = default;
|
||||
Dependency &operator=(const Dependency &) = delete;
|
||||
Dependency &operator=(Dependency &&) = default;
|
||||
|
||||
public:
|
||||
const auto &providedSymbols() const { return ProvidedSymbols; }
|
||||
|
||||
const auto &requestedBy() const { return RequestedBy; }
|
||||
|
||||
public:
|
||||
void dump() const debug_function { dump(dbg, ""); }
|
||||
|
||||
template<typename T>
|
||||
void dump(T &Stream, const char *Prefix) const {
|
||||
DependencyFile::dump(Stream, Prefix);
|
||||
Stream << Prefix << "RequestedBy: " << RequestedBy << "\n";
|
||||
|
||||
Stream << Prefix << "Symbols provided:";
|
||||
if (ProvidedSymbols.size() == 0) {
|
||||
Stream << " none\n";
|
||||
} else {
|
||||
Stream << "\n";
|
||||
for (auto &[Name, Address] : ProvidedSymbols)
|
||||
Stream << Prefix << " " << Name << " at 0x"
|
||||
<< llvm::utohexstr(Address, true) << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline void
|
||||
dumpDependenciesOf(T &Stream,
|
||||
unsigned Depth,
|
||||
const std::map<std::string, LDDTree::Dependency> &Map,
|
||||
llvm::StringRef Name) {
|
||||
std::string Prefix;
|
||||
for (unsigned I = 0; I < Depth; ++I)
|
||||
Prefix += " ";
|
||||
for (auto &[DependencyName, Dependency] : Map) {
|
||||
if (Dependency.requestedBy() != Name)
|
||||
continue;
|
||||
|
||||
Stream << Prefix << DependencyName << ":\n";
|
||||
Dependency.dump(Stream, (Prefix + " ").c_str());
|
||||
|
||||
Stream << Prefix << " Dependencies:\n";
|
||||
dumpDependenciesOf(Stream, Depth + 2, Map, DependencyName);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline void LDDTree::dump(T &Stream) const {
|
||||
Stream << "Dependencies:\n";
|
||||
dumpDependenciesOf(Stream, 1, Dependencies, "");
|
||||
|
||||
if (MissingSymbols.has_value()) {
|
||||
Stream << "Missing symbols:\n";
|
||||
for (auto Name : *MissingSymbols)
|
||||
Stream << " " << Name << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <concepts>
|
||||
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
|
||||
template<typename T>
|
||||
concept IsELFObjectFile = std::derived_from<T, llvm::object::ELFObjectFileBase>;
|
||||
@@ -20,7 +20,7 @@ using namespace llvm;
|
||||
|
||||
Error importBinary(TupleTree<model::Binary> &Model,
|
||||
llvm::object::ObjectFile &ObjectFile,
|
||||
llvm::StringRef Filepath,
|
||||
llvm::StringRef FullPathForExternalTools,
|
||||
const ImporterOptions &Options,
|
||||
model::BinaryReference &BinaryReference) {
|
||||
using namespace llvm::object;
|
||||
@@ -39,20 +39,19 @@ Error importBinary(TupleTree<model::Binary> &Model,
|
||||
revng_check(not Result);
|
||||
if (auto *TheBinary = dyn_cast<ELFObjectFileBase>(&ObjectFile)) {
|
||||
ELFBinary Binary(*TheBinary,
|
||||
ObjectFile.getMemoryBufferRef(),
|
||||
Filepath,
|
||||
FullPathForExternalTools.str(),
|
||||
BinaryReference);
|
||||
Result = importELF(Model, Binary, Options);
|
||||
} else if (auto *TheBinary = dyn_cast<COFFObjectFile>(&ObjectFile)) {
|
||||
Model->OperatingSystem() = model::OperatingSystem::Windows;
|
||||
COFFBinary Binary(*TheBinary,
|
||||
ObjectFile.getMemoryBufferRef(),
|
||||
Filepath,
|
||||
FullPathForExternalTools.str(),
|
||||
BinaryReference);
|
||||
Result = importPECOFF(Model, Binary, Options);
|
||||
} else if (auto *TheBinary = dyn_cast<MachOObjectFile>(&ObjectFile)) {
|
||||
Model->OperatingSystem() = model::OperatingSystem::MacOS;
|
||||
MachOBinary Binary(*TheBinary,
|
||||
ObjectFile.getMemoryBufferRef(),
|
||||
Filepath,
|
||||
FullPathForExternalTools.str(),
|
||||
BinaryReference);
|
||||
Result = importMachO(Model, Binary, Options);
|
||||
} else {
|
||||
@@ -70,11 +69,18 @@ Error importBinary(TupleTree<model::Binary> &Model,
|
||||
}
|
||||
|
||||
Error importBinary(TupleTree<model::Binary> &Model,
|
||||
llvm::MemoryBuffer &Buffer,
|
||||
llvm::StringRef Filepath,
|
||||
llvm::StringRef FullPathForExternalTools,
|
||||
const ImporterOptions &Options,
|
||||
model::BinaryReference &BinaryReference) {
|
||||
auto BinaryOrError = object::createBinary(Buffer);
|
||||
|
||||
auto
|
||||
MaybeBuffer = llvm::MemoryBuffer::getFileOrSTDIN(FullPathForExternalTools,
|
||||
false,
|
||||
false);
|
||||
if (not MaybeBuffer)
|
||||
return llvm::errorCodeToError(MaybeBuffer.getError());
|
||||
|
||||
auto BinaryOrError = object::createBinary(**MaybeBuffer);
|
||||
if (not BinaryOrError)
|
||||
return BinaryOrError.takeError();
|
||||
|
||||
@@ -89,7 +95,7 @@ Error importBinary(TupleTree<model::Binary> &Model,
|
||||
|
||||
return importBinary(Model,
|
||||
cast<object::ObjectFile>(Binary),
|
||||
Filepath,
|
||||
FullPathForExternalTools,
|
||||
Options,
|
||||
BinaryReference);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/Object/COFF.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/BinaryIdentifier.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporter.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporterHelper.h"
|
||||
#include "revng/Model/Importer/Binary/Options.h"
|
||||
#include "revng/Model/Pass/DeduplicateCollidingNames.h"
|
||||
#include "revng/Model/Pass/DeduplicateEquivalentTypes.h"
|
||||
#include "revng/Model/Pass/FlattenPrimitiveTypedefs.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
|
||||
#include "CrossModelFindTypeHelper.h"
|
||||
|
||||
template<IsObjectFile T>
|
||||
std::optional<LDDTree>
|
||||
BinaryImporterHelper::identifyDependencies(const T &ObjectFile,
|
||||
llvm::StringRef CanonicalPath) {
|
||||
LDDTree::SymbolSet MissingSymbols;
|
||||
for (auto &Function : Binary->ImportedDynamicFunctions())
|
||||
if (Function.Prototype().isEmpty() and Function.Name().size() > 0)
|
||||
MissingSymbols.insert(Function.Name());
|
||||
|
||||
return LDDTree::fromPath(CanonicalPath, ObjectFile, MissingSymbols);
|
||||
}
|
||||
|
||||
using namespace llvm::object;
|
||||
|
||||
template std::optional<LDDTree>
|
||||
BinaryImporterHelper::identifyDependencies(const COFFObjectFile &ObjectFile,
|
||||
llvm::StringRef CanonicalPath);
|
||||
|
||||
template std::optional<LDDTree>
|
||||
BinaryImporterHelper::identifyDependencies(const ELF32LEObjectFile &ObjectFile,
|
||||
llvm::StringRef CanonicalPath);
|
||||
|
||||
template std::optional<LDDTree>
|
||||
BinaryImporterHelper::identifyDependencies(const ELF64LEObjectFile &ObjectFile,
|
||||
llvm::StringRef CanonicalPath);
|
||||
|
||||
template std::optional<LDDTree>
|
||||
BinaryImporterHelper::identifyDependencies(const ELF32BEObjectFile &ObjectFile,
|
||||
llvm::StringRef CanonicalPath);
|
||||
|
||||
template std::optional<LDDTree>
|
||||
BinaryImporterHelper::identifyDependencies(const ELF64BEObjectFile &ObjectFile,
|
||||
llvm::StringRef CanonicalPath);
|
||||
@@ -5,6 +5,7 @@
|
||||
revng_add_analyses_library_internal(
|
||||
revngModelImporterBinary
|
||||
BinaryImporter.cpp
|
||||
BinaryImporterHelper.cpp
|
||||
ELFImporter.cpp
|
||||
MachOImporter.cpp
|
||||
Options.cpp
|
||||
|
||||
@@ -4,12 +4,24 @@
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "revng/ADT/Concepts.h"
|
||||
#include "revng/Model/Architecture.h"
|
||||
#include "revng/Model/CABIFunctionDefinition.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/ImportLogger.h"
|
||||
#include "revng/Model/Importer/TypeCopier.h"
|
||||
#include "revng/Model/Pass/DeduplicateCollidingNames.h"
|
||||
#include "revng/Model/Pass/DeduplicateEquivalentTypes.h"
|
||||
#include "revng/Model/Pass/FlattenPrimitiveTypedefs.h"
|
||||
#include "revng/Model/Processing.h"
|
||||
#include "revng/Model/Segment.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
#include "revng/TupleTree/TupleTree.h"
|
||||
|
||||
using ModelMap = std::map<std::string, TupleTree<model::Binary>>;
|
||||
|
||||
@@ -87,3 +99,200 @@ findPrototype(llvm::StringRef Function, ModelMap &ModelsOfDynamicLibraries) {
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<StrictSpecializationOf<BinaryDescriptor> BinaryDescriptor,
|
||||
typename ImporterType>
|
||||
void findMissingTypes(LDDTree &Dependencies,
|
||||
const ImporterOptions &Options,
|
||||
Logger &Logger,
|
||||
TupleTree<model::Binary> &Binary) {
|
||||
using namespace llvm;
|
||||
|
||||
if (Options.DebugInfo != DebugInfoLevel::Yes)
|
||||
return;
|
||||
|
||||
ModelMap ModelsOfLibraries;
|
||||
TypeCopierMap TypeCopiers;
|
||||
|
||||
revng_log(Logger, "Importing dependencies");
|
||||
LoggerIndent Indent(Logger);
|
||||
|
||||
for (auto &[DependencyName, Dependency] : Dependencies.Dependencies) {
|
||||
using namespace model;
|
||||
|
||||
revng_log(Logger, "Importing debug info for: " << DependencyName);
|
||||
LoggerIndent Indent(Logger);
|
||||
if (Logger.isEnabled()) {
|
||||
Logger << "Dependency:\n";
|
||||
Dependency.dump(Logger, " ");
|
||||
Logger << DoLog;
|
||||
}
|
||||
|
||||
revng_assert(!ModelsOfLibraries.contains(DependencyName));
|
||||
|
||||
// Craft a minimal (temporary) model for the sole purpose of being imported
|
||||
auto &DependencyModel = ModelsOfLibraries[DependencyName];
|
||||
DependencyModel->Architecture() = Binary->Architecture();
|
||||
BinaryReference BinaryReference;
|
||||
BinaryIdentifier Identifier;
|
||||
Identifier.Index() = 0;
|
||||
Identifier.Hash() = ("00000000000000000000000000000000"
|
||||
"00000000000000000000000000000000");
|
||||
Identifier.CanonicalPath() = Dependency.canonicalPath();
|
||||
DependencyModel->Binaries().insert(std::move(Identifier));
|
||||
BinaryReference = DependencyModel->getBinaryIdentifierReference(0);
|
||||
|
||||
bool Is64 = Architecture::getPointerSize(Binary->Architecture()) == 8;
|
||||
Segment Universe(MetaAddress::fromGeneric(Binary->Architecture(), 0),
|
||||
Is64 ? std::numeric_limits<uint64_t>::max() :
|
||||
std::numeric_limits<uint32_t>::max());
|
||||
Universe.FileSize() = Universe.VirtualSize();
|
||||
Universe.IsExecutable() = true;
|
||||
Universe.IsReadable() = true;
|
||||
Universe.IsWriteable() = true;
|
||||
Universe.Binary() = BinaryReference;
|
||||
DependencyModel->Segments().insert(std::move(Universe));
|
||||
|
||||
ImporterOptions AdjustedOptions{
|
||||
.BaseAddress = Options.BaseAddress,
|
||||
.DebugInfo = DebugInfoLevel::IgnoreLibraries,
|
||||
.EnableRemoteDebugInfo = Options.EnableRemoteDebugInfo,
|
||||
.AdditionalDebugInfoPaths = Options.AdditionalDebugInfoPaths
|
||||
};
|
||||
|
||||
using ObjectFileType = typename BinaryDescriptor::ObjectFileType;
|
||||
BinaryDescriptor TheBinary(cast<ObjectFileType>(Dependency.objectFile()),
|
||||
Dependency.fullPathForExternalTools(),
|
||||
BinaryReference);
|
||||
|
||||
std::optional<std::set<uint64_t>> RequestedFunctions;
|
||||
RequestedFunctions.emplace();
|
||||
for (const auto &[Name, Address] : Dependency.providedSymbols()) {
|
||||
RequestedFunctions->insert(Address);
|
||||
// TODO: usually this reponsability is demanded to BinaryImporterHelper
|
||||
auto FunctionAddress = MetaAddress::fromPC(Binary->Architecture(),
|
||||
Options.BaseAddress + Address);
|
||||
auto &Function = DependencyModel->Functions()[FunctionAddress];
|
||||
Function.Name() = Name;
|
||||
Function.ExportedNames().insert(Name);
|
||||
}
|
||||
|
||||
ImportLogger ImportLogger(DependencyModel,
|
||||
Logger,
|
||||
TheBinary.canonicalPath());
|
||||
ImporterType Importer(DependencyModel, RequestedFunctions);
|
||||
Importer.import(Dependencies.Root, TheBinary, AdjustedOptions);
|
||||
|
||||
revng_log(Logger, DependencyName << " imported successfully");
|
||||
}
|
||||
|
||||
auto GetOrMakeACopier = [&](llvm::StringRef Name) -> TypeCopier & {
|
||||
if (auto It = TypeCopiers.find(Name.str()); It != TypeCopiers.end())
|
||||
return *It->second;
|
||||
|
||||
auto Iterator = ModelsOfLibraries.find(Name.str());
|
||||
revng_assert(Iterator != ModelsOfLibraries.end());
|
||||
|
||||
auto NewCopier = std::make_unique<TypeCopier>(Iterator->second, Binary);
|
||||
auto &&[Result, Success] = TypeCopiers.emplace(Name.str(),
|
||||
std::move(NewCopier));
|
||||
revng_assert(Success);
|
||||
return *Result->second;
|
||||
};
|
||||
|
||||
revng_log(Logger, "Importing prototypes for dynamic functions");
|
||||
LoggerIndent Indent2(Logger);
|
||||
|
||||
for (auto &DynamicFunction : Binary->ImportedDynamicFunctions()) {
|
||||
revng_log(Logger, "Considering " << DynamicFunction.Name());
|
||||
LoggerIndent Indent(Logger);
|
||||
|
||||
if (DynamicFunction.Name().empty()) {
|
||||
revng_log(Logger, "It has no name, bailing out");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto MaybeFunction = findPrototype(DynamicFunction.Name(),
|
||||
ModelsOfLibraries);
|
||||
|
||||
if (not MaybeFunction.has_value()) {
|
||||
revng_log(Logger,
|
||||
"Prototype for " << DynamicFunction.Name() << " not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not DynamicFunction.Prototype().isEmpty()) {
|
||||
// The prototype already exist, but maybe we can import some argument
|
||||
// names
|
||||
revng_log(Logger, "It already has a prototype");
|
||||
|
||||
auto &Old = *DynamicFunction.Prototype()->getPrototype();
|
||||
auto &New = *MaybeFunction->Prototype.getPrototype();
|
||||
|
||||
if (Old.Kind() != New.Kind()) {
|
||||
revng_log(Logger, "Old and new prototype have different kind");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Old.Kind() != model::TypeDefinitionKind::CABIFunctionDefinition) {
|
||||
revng_log(Logger, "Old prototype is not CABIFunctionDefinition");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &OldCABI = cast<model::CABIFunctionDefinition>(Old);
|
||||
auto &NewCABI = cast<model::CABIFunctionDefinition>(New);
|
||||
|
||||
if (OldCABI.Arguments().size() != NewCABI.Arguments().size()) {
|
||||
revng_log(Logger,
|
||||
"Old and new prototype have different number of "
|
||||
"arguments");
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned NamesGiven = 0;
|
||||
for (auto &&[OldArgument, NewArgument] :
|
||||
zip(OldCABI.Arguments(), NewCABI.Arguments())) {
|
||||
if (OldArgument.Name().empty() and not NewArgument.Name().empty()) {
|
||||
OldArgument.Name() = NewArgument.Name();
|
||||
++NamesGiven;
|
||||
}
|
||||
}
|
||||
|
||||
revng_log(Logger,
|
||||
"The name of " << NamesGiven
|
||||
<< " arguments have been imported");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
revng_assert(!MaybeFunction->ModuleName.empty());
|
||||
revng_assert(MaybeFunction->Prototype.verify(true));
|
||||
|
||||
using model::UpcastableTypeDefinition;
|
||||
UpcastableTypeDefinition SerializablePrototype = MaybeFunction->Prototype;
|
||||
revng_log(Logger,
|
||||
"Found type for " << DynamicFunction.Name() << " in "
|
||||
<< MaybeFunction->ModuleName << ": "
|
||||
<< toString(SerializablePrototype));
|
||||
TypeCopier &TheTypeCopier = GetOrMakeACopier(MaybeFunction->ModuleName);
|
||||
DynamicFunction.Prototype() = TheTypeCopier
|
||||
.copyTypeInto(MaybeFunction->Prototype);
|
||||
|
||||
// Copy all the Attributes except for `Inline`.
|
||||
for (auto &Attribute : MaybeFunction->Attributes)
|
||||
if (Attribute != model::FunctionAttribute::AlwaysInline)
|
||||
DynamicFunction.Attributes().insert(Attribute);
|
||||
}
|
||||
|
||||
// Finalize the copies
|
||||
for (auto &[_, TC] : TypeCopiers)
|
||||
TC->finalize();
|
||||
|
||||
// Purge cached references and update the reference to Root.
|
||||
Binary.disableReferenceCaching();
|
||||
Binary.initializeReferences();
|
||||
|
||||
model::flattenPrimitiveTypedefs(Binary);
|
||||
deduplicateEquivalentTypes(Binary);
|
||||
model::deduplicateCollidingNames(Binary);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,19 @@
|
||||
#include "revng/ABI/DefaultFunctionPrototype.h"
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/IRHelpers.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporter.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporterHelper.h"
|
||||
#include "revng/Model/Importer/Binary/Options.h"
|
||||
#include "revng/Model/Importer/DebugInfo/DwarfImporter.h"
|
||||
#include "revng/Model/Pass/AllPasses.h"
|
||||
#include "revng/Model/RawBinaryView.h"
|
||||
#include "revng/Support/CommandLine.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/Error.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
#include "revng/TupleTree/TupleTree.h"
|
||||
|
||||
#include "CrossModelFindTypeHelper.h"
|
||||
#include "DwarfReader.h"
|
||||
@@ -93,8 +98,13 @@ ArrayRef<T> FilePortion::extractAs() const {
|
||||
auto Data = extractData();
|
||||
|
||||
const size_t TypeSize = sizeof(T);
|
||||
if (Data.size() % TypeSize != 0)
|
||||
if (Data.size() % TypeSize != 0) {
|
||||
revng_log(ELFImporterLog,
|
||||
"Warning: cannot extract objects of size "
|
||||
<< TypeSize
|
||||
<< ". File portion size is not a multiple: " << Data.size());
|
||||
return {};
|
||||
}
|
||||
|
||||
return ArrayRef<T>(reinterpret_cast<const T *>(Data.data()),
|
||||
Data.size() / TypeSize);
|
||||
@@ -156,8 +166,9 @@ uint64_t symbolsCount(const FilePortion &Relocations) {
|
||||
|
||||
uint32_t SymbolsCount = 0;
|
||||
|
||||
for (Elf_Rel Relocation : Relocations.extractAs<Elf_Rel>())
|
||||
for (Elf_Rel Relocation : Relocations.extractAs<Elf_Rel>()) {
|
||||
SymbolsCount = std::max(SymbolsCount, Relocation.getSymbol(false) + 1);
|
||||
}
|
||||
|
||||
return SymbolsCount;
|
||||
}
|
||||
@@ -165,7 +176,9 @@ uint64_t symbolsCount(const FilePortion &Relocations) {
|
||||
template<typename T, bool HasAddend>
|
||||
Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
revng_log(ELFImporterLog, "Starting ELF import");
|
||||
llvm::Task Task(13, "Import ELF");
|
||||
LoggerIndent Indent(ELFImporterLog);
|
||||
|
||||
llvm::Task Task(14, "Import ELF");
|
||||
Task.advance("Parse ELF", true);
|
||||
|
||||
// Parse the ELF file
|
||||
@@ -174,6 +187,13 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
return TheELFOrErr.takeError();
|
||||
object::ELFFile<T> &TheELF = *TheELFOrErr;
|
||||
|
||||
// Set operating system from the ELF OSABI field
|
||||
{
|
||||
unsigned char OSABI = TheELF.getHeader().e_ident[llvm::ELF::EI_OSABI];
|
||||
if (OSABI == llvm::ELF::ELFOSABI_NONE or OSABI == llvm::ELF::ELFOSABI_LINUX)
|
||||
Model->OperatingSystem() = model::OperatingSystem::Linux;
|
||||
}
|
||||
|
||||
// Parse segments
|
||||
Task.advance("Parse segments", true);
|
||||
parseSegments(TheELF);
|
||||
@@ -311,6 +331,9 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
revng_log(ELFImporterLog, "Cannot access dynamic entries: " << Error);
|
||||
consumeError(std::move(Error));
|
||||
} else {
|
||||
revng_log(ELFImporterLog, "Parsing .dynamic");
|
||||
LoggerIndent Indent(ELFImporterLog);
|
||||
|
||||
SmallVector<uint64_t, 10> NeededLibraryNameOffsets;
|
||||
|
||||
// TODO: use std::optional
|
||||
@@ -351,6 +374,10 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
symbolsCount<T, HasAddend>(*RelpltPortion.get()));
|
||||
}
|
||||
|
||||
revng_log(ELFImporterLog, "SymbolsCount: " << SymbolsCount.value_or(0));
|
||||
revng_log(ELFImporterLog,
|
||||
"DynsymPortion->isAvailable(): " << DynsymPortion->isAvailable());
|
||||
|
||||
// Collect function addresses contained in dynamic symbols
|
||||
if (SymbolsCount and *SymbolsCount > 0 and DynsymPortion->isAvailable()) {
|
||||
Task.advance("Parse dynamic symbols", true);
|
||||
@@ -365,7 +392,8 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
|
||||
using Elf_Rel = llvm::object::Elf_Rel_Impl<T, HasAddend>;
|
||||
if (ReldynPortion->isAvailable()) {
|
||||
registerRelocations(ReldynPortion->extractAs<Elf_Rel>(),
|
||||
registerRelocations(".rela.dyn",
|
||||
ReldynPortion->extractAs<Elf_Rel>(),
|
||||
*DynsymPortion.get(),
|
||||
*DynstrPortion.get());
|
||||
}
|
||||
@@ -388,7 +416,8 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
}
|
||||
|
||||
if (RelpltPortion->isAvailable()) {
|
||||
registerRelocations(RelpltPortion->extractAs<Elf_Rel>(),
|
||||
registerRelocations(".rela.plt",
|
||||
RelpltPortion->extractAs<Elf_Rel>(),
|
||||
*DynsymPortion.get(),
|
||||
*DynstrPortion.get());
|
||||
}
|
||||
@@ -414,19 +443,47 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
Model->DefaultPrototype() = abi::registerDefaultFunctionPrototype(Ptr);
|
||||
|
||||
if (AdjustedOptions.DebugInfo != DebugInfoLevel::No) {
|
||||
Task.advance("Parse debug info", true);
|
||||
|
||||
// Import Dwarf
|
||||
DwarfImporter Importer(Model);
|
||||
auto Handler = [&](const auto &ObjectFile) {
|
||||
Task.advance("Identifying dependencies", true);
|
||||
std::optional<LDDTree>
|
||||
MaybeDependencies = identifyDependencies(ObjectFile,
|
||||
TheBinary.canonicalPath());
|
||||
|
||||
Importer.import(TheBinary.Buffer,
|
||||
TheBinary.Path,
|
||||
AdjustedOptions,
|
||||
TheBinary.getFilename());
|
||||
bool HasRoot = MaybeDependencies.has_value();
|
||||
const revng::RootEntry *Root = HasRoot ? MaybeDependencies->Root :
|
||||
nullptr;
|
||||
|
||||
// Now we try to find missing types in the dependencies.
|
||||
Task.advance("Find missing types from debug info", true);
|
||||
findMissingTypes(TheELF, AdjustedOptions);
|
||||
// Import Dwarf
|
||||
Task.advance("Parse debug info", true);
|
||||
{
|
||||
auto ImportLogger = importLogger(TheBinary.canonicalPath());
|
||||
DwarfImporter Importer(Model, std::nullopt);
|
||||
Importer.import(Root, TheBinary, AdjustedOptions);
|
||||
}
|
||||
|
||||
// Now we try to find missing types in the dependencies
|
||||
Task.advance("Find missing types from debug info", true);
|
||||
|
||||
if (HasRoot) {
|
||||
findMissingTypes<ELFBinary, DwarfImporter>(*MaybeDependencies,
|
||||
Options,
|
||||
Logger,
|
||||
Model);
|
||||
}
|
||||
};
|
||||
|
||||
auto *GenericObjectFile = &TheBinary.ObjectFile;
|
||||
if (auto *ObjectFile = dyn_cast<ELF32LEObjectFile>(GenericObjectFile))
|
||||
Handler(*ObjectFile);
|
||||
else if (auto *ObjectFile = dyn_cast<ELF32BEObjectFile>(GenericObjectFile))
|
||||
Handler(*ObjectFile);
|
||||
else if (auto *ObjectFile = dyn_cast<ELF64LEObjectFile>(GenericObjectFile))
|
||||
Handler(*ObjectFile);
|
||||
else if (auto *ObjectFile = dyn_cast<ELF64BEObjectFile>(GenericObjectFile))
|
||||
Handler(*ObjectFile);
|
||||
else
|
||||
revng_abort();
|
||||
}
|
||||
|
||||
Task.advance("Flatten primitive typedefs", true);
|
||||
@@ -438,123 +495,6 @@ Error ELFImporter<T, HasAddend>::import(const ImporterOptions &Options) {
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
template<typename T, bool HasAddend>
|
||||
void ELFImporter<T, HasAddend>::findMissingTypes(object::ELFFile<T> &TheELF,
|
||||
const ImporterOptions &Opts) {
|
||||
if (Opts.DebugInfo != DebugInfoLevel::Yes)
|
||||
return;
|
||||
|
||||
ModelMap ModelsOfLibraries;
|
||||
TypeCopierMap TypeCopiers;
|
||||
|
||||
// TODO: disclose a way to modify this value with
|
||||
// the `ImporterOptions::DebugInfo`, if the need ever arises.
|
||||
unsigned MaximumRecursionDepth = 1;
|
||||
|
||||
LDDTree Dependencies;
|
||||
lddtree(Dependencies, TheBinary.getFilename(), MaximumRecursionDepth);
|
||||
for (auto &Library : Dependencies) {
|
||||
revng_log(ELFImporterLog,
|
||||
"Importing Models for dependencies of " << Library.first << ":");
|
||||
for (auto &DependencyLibrary : Library.second) {
|
||||
if (ModelsOfLibraries.contains(DependencyLibrary))
|
||||
continue;
|
||||
revng_log(ELFImporterLog, " Importing Model for: " << DependencyLibrary);
|
||||
auto BinaryOrErr = llvm::object::createBinary(DependencyLibrary);
|
||||
if (auto Error = BinaryOrErr.takeError()) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(ELFImporterLog,
|
||||
"Can't create object for " << DependencyLibrary << " due to "
|
||||
<< Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &Object = *cast<llvm::object::ObjectFile>(BinaryOrErr->getBinary());
|
||||
auto *TheBinary = dyn_cast<ELFObjectFileBase>(&Object);
|
||||
if (!TheBinary) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(ELFImporterLog, "Can't parse the binary");
|
||||
continue;
|
||||
}
|
||||
|
||||
revng_assert(!ModelsOfLibraries.contains(DependencyLibrary));
|
||||
TupleTree<model::Binary> &DepModel = ModelsOfLibraries[DependencyLibrary];
|
||||
DepModel->Architecture() = Model->Architecture();
|
||||
ImporterOptions AdjustedOptions{
|
||||
.BaseAddress = Opts.BaseAddress,
|
||||
.DebugInfo = DebugInfoLevel::IgnoreLibraries,
|
||||
.EnableRemoteDebugInfo = Opts.EnableRemoteDebugInfo,
|
||||
.AdditionalDebugInfoPaths = Opts.AdditionalDebugInfoPaths
|
||||
};
|
||||
ELFBinary Binary(*TheBinary,
|
||||
TheBinary->getMemoryBufferRef(),
|
||||
DependencyLibrary,
|
||||
this->TheBinary.Reference);
|
||||
|
||||
if (auto Error = importELF(DepModel, Binary, AdjustedOptions)) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(ELFImporterLog,
|
||||
"Can't import model for " << DependencyLibrary << " due to "
|
||||
<< Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
ModelsOfLibraries.erase(DependencyLibrary);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto GetOrMakeACopier = [&](llvm::StringRef Name) -> TypeCopier & {
|
||||
if (auto It = TypeCopiers.find(Name.str()); It != TypeCopiers.end())
|
||||
return *It->second;
|
||||
|
||||
auto Iterator = ModelsOfLibraries.find(Name.str());
|
||||
revng_assert(Iterator != ModelsOfLibraries.end());
|
||||
|
||||
auto NewCopier = std::make_unique<TypeCopier>(Iterator->second, Model);
|
||||
auto &&[Result, Success] = TypeCopiers.emplace(Name.str(),
|
||||
std::move(NewCopier));
|
||||
revng_assert(Success);
|
||||
return *Result->second;
|
||||
};
|
||||
|
||||
for (auto &Fn : Model->ImportedDynamicFunctions()) {
|
||||
if (not Fn.Prototype().isEmpty() or Fn.Name().size() == 0)
|
||||
continue;
|
||||
|
||||
if (auto Found = findPrototype(Fn.Name(), ModelsOfLibraries)) {
|
||||
revng_assert(!Found->ModuleName.empty());
|
||||
revng_assert(Found->Prototype.verify(true));
|
||||
|
||||
model::UpcastableTypeDefinition SerializablePrototype = Found->Prototype;
|
||||
revng_log(ELFImporterLog,
|
||||
"Found type for " << Fn.Name() << " in " << Found->ModuleName
|
||||
<< ": " << toString(SerializablePrototype));
|
||||
TypeCopier &TheTypeCopier = GetOrMakeACopier(Found->ModuleName);
|
||||
Fn.Prototype() = TheTypeCopier.copyTypeInto(Found->Prototype);
|
||||
|
||||
// Copy all the Attributes except for `AlwaysInline`.
|
||||
for (auto &Attribute : Found->Attributes)
|
||||
if (Attribute != model::FunctionAttribute::AlwaysInline)
|
||||
Fn.Attributes().insert(Attribute);
|
||||
} else {
|
||||
revng_log(ELFImporterLog, "Prototype for " << Fn.Name() << " not found");
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize the copies
|
||||
for (auto &[_, TC] : TypeCopiers)
|
||||
TC->finalize();
|
||||
|
||||
// Purge cached references and update the reference to Root.
|
||||
Model.disableReferenceCaching();
|
||||
Model.initializeReferences();
|
||||
|
||||
model::flattenPrimitiveTypedefs(Model);
|
||||
deduplicateEquivalentTypes(Model);
|
||||
model::deduplicateCollidingNames(Model);
|
||||
}
|
||||
|
||||
using Libs = SmallVectorImpl<uint64_t>;
|
||||
template<typename T, bool HasAddend>
|
||||
void ELFImporter<T, HasAddend>::parseDynamicTag(uint64_t Tag,
|
||||
@@ -684,9 +624,6 @@ void ELFImporter<T, HasAddend>::parseSymbols(object::ELFFile<T> &TheELF,
|
||||
auto *Function = registerFunctionEntry(Address);
|
||||
if (Function != nullptr and MaybeName and MaybeName->size() > 0) {
|
||||
Function->Name() = *MaybeName;
|
||||
// Insert Original name into exported ones, since it is by default
|
||||
// true.
|
||||
Function->ExportedNames().insert((*MaybeName).str());
|
||||
}
|
||||
}
|
||||
} else if (IsDataObject and Size > 0) {
|
||||
@@ -732,6 +669,8 @@ void ELFImporter<T, HasAddend>::parseSegments(ELFFile<T> &TheELF) {
|
||||
|
||||
model::Segment NewSegment({ Start, ProgramHeader.p_memsz });
|
||||
|
||||
// TODO: do the following unconditionally once the old pipeline has been
|
||||
// dropped.
|
||||
if (TheBinary.Reference.isValid())
|
||||
NewSegment.Binary() = TheBinary.Reference;
|
||||
NewSegment.StartOffset() = ProgramHeader.p_offset;
|
||||
@@ -826,6 +765,10 @@ void ELFImporter<T, HasAddend>::parseDynamicSymbol(Elf_Sym_Impl<T> &Symbol,
|
||||
}
|
||||
|
||||
StringRef Name = *MaybeName;
|
||||
|
||||
revng_log(ELFImporterLog, "Considering dynamic symbol " << Name);
|
||||
LoggerIndent Indent(ELFImporterLog);
|
||||
|
||||
if (Name.contains('\0')) {
|
||||
revng_log(ELFImporterLog,
|
||||
"SymbolName contains a NUL character: \"" << Name.str() << "\"");
|
||||
@@ -835,17 +778,22 @@ void ELFImporter<T, HasAddend>::parseDynamicSymbol(Elf_Sym_Impl<T> &Symbol,
|
||||
bool IsCode = Symbol.getType() == ELF::STT_FUNC;
|
||||
bool IsDataObject = Symbol.getType() == ELF::STT_OBJECT;
|
||||
|
||||
if (shouldIgnoreSymbol(Name))
|
||||
if (shouldIgnoreSymbol(Name)) {
|
||||
revng_log(ELFImporterLog, "Ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Symbol.st_shndx == ELF::SHN_UNDEF) {
|
||||
revng_log(ELFImporterLog, "This is an imported symbol");
|
||||
if (IsCode) {
|
||||
// Create dynamic function symbol
|
||||
Model->ImportedDynamicFunctions()[Name.str()];
|
||||
} else {
|
||||
// TODO: create dynamic global variable
|
||||
revng_log(ELFImporterLog, "Ignoring non-code");
|
||||
}
|
||||
} else {
|
||||
revng_log(ELFImporterLog, "It's a local exported function");
|
||||
MetaAddress Address = MetaAddress::invalid();
|
||||
uint64_t Size = Symbol.st_size;
|
||||
|
||||
@@ -889,7 +837,7 @@ ELFImporter<T, HasAddend>::ehFrameFromEhFrameHdr() {
|
||||
ArrayRef<uint8_t> EHFrameHdr = *MaybeEHFrameHdr;
|
||||
|
||||
using namespace model::Architecture;
|
||||
DwarfReader<T> EHFrameHdrReader(Binary.Architecture(),
|
||||
DwarfReader<T> EHFrameHdrReader(Binary->Architecture(),
|
||||
EHFrameHdr,
|
||||
*EHFrameHdrAddress);
|
||||
|
||||
@@ -1127,8 +1075,6 @@ void ELFImporter<T, HasAddend>::parseEHFrame(MetaAddress EHFrameAddress,
|
||||
template<typename T, bool HasAddend>
|
||||
void ELFImporter<T, HasAddend>::parseLSDA(MetaAddress FDEStart,
|
||||
MetaAddress LSDAAddress) {
|
||||
logAddress(ELFImporterLog, "LSDAAddress: ", LSDAAddress);
|
||||
|
||||
auto MaybeLSDA = File.getFromAddressOn(LSDAAddress);
|
||||
if (not MaybeLSDA) {
|
||||
revng_log(ELFImporterLog, "LSDA not available in any segment");
|
||||
@@ -1147,8 +1093,6 @@ void ELFImporter<T, HasAddend>::parseLSDA(MetaAddress FDEStart,
|
||||
LandingPadBase = FDEStart;
|
||||
}
|
||||
|
||||
logAddress(ELFImporterLog, "LandingPadBase: ", LandingPadBase);
|
||||
|
||||
uint32_t TypeTableEncoding = LSDAReader.readNextU8();
|
||||
if (TypeTableEncoding != dwarf::DW_EH_PE_omit)
|
||||
LSDAReader.readULEB128();
|
||||
@@ -1195,11 +1139,11 @@ struct RelocationHelper<T, false> {
|
||||
};
|
||||
|
||||
template<typename T, bool HasAddend>
|
||||
void ELFImporter<T, HasAddend>::registerRelocations(Elf_Rel_Array Relocations,
|
||||
void ELFImporter<T, HasAddend>::registerRelocations(StringRef Name,
|
||||
Elf_Rel_Array Relocations,
|
||||
const FilePortion &Dynsym,
|
||||
const FilePortion &Dynstr) {
|
||||
using namespace llvm::object;
|
||||
using Elf_Rel = Elf_Rel_Impl<T, HasAddend>;
|
||||
using Elf_Sym = Elf_Sym_Impl<T>;
|
||||
|
||||
model::Segment *LowestSegment = nullptr;
|
||||
@@ -1210,7 +1154,10 @@ void ELFImporter<T, HasAddend>::registerRelocations(Elf_Rel_Array Relocations,
|
||||
if (Dynsym.isAvailable())
|
||||
Symbols = Dynsym.extractAs<Elf_Sym>();
|
||||
|
||||
for (Elf_Rel Relocation : Relocations) {
|
||||
revng_log(ELFImporterLog, "Parsing relocations in " << Name.str());
|
||||
LoggerIndent Indent(ELFImporterLog);
|
||||
|
||||
for (auto [Index, Relocation] : llvm::enumerate(Relocations)) {
|
||||
auto Type = static_cast<unsigned char>(Relocation.getType(false));
|
||||
uint64_t Addend = RelocationHelper<T, HasAddend>::getAddend(Relocation);
|
||||
MetaAddress Address = relocate(fromGeneric(Relocation.r_offset));
|
||||
@@ -1229,7 +1176,9 @@ void ELFImporter<T, HasAddend>::registerRelocations(Elf_Rel_Array Relocations,
|
||||
auto MaybeName = Symbol.getName(Dynstr.extractString());
|
||||
|
||||
if (auto Error = MaybeName.takeError()) {
|
||||
revng_log(ELFImporterLog, "Cannot access symbol name: " << Error);
|
||||
revng_log(ELFImporterLog,
|
||||
"Cannot access symbol name for relocation #" << Index << ": "
|
||||
<< Error);
|
||||
consumeError(std::move(Error));
|
||||
} else {
|
||||
SymbolName = *MaybeName;
|
||||
@@ -1246,7 +1195,8 @@ void ELFImporter<T, HasAddend>::registerRelocations(Elf_Rel_Array Relocations,
|
||||
Type);
|
||||
if (RelocationType == Invalid) {
|
||||
revng_log(ELFImporterLog,
|
||||
"Ignoring unknown relocation: " << RelocationName);
|
||||
"Ignoring unknown relocation #" << Index << ": "
|
||||
<< RelocationName);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1258,12 +1208,13 @@ void ELFImporter<T, HasAddend>::registerRelocations(Elf_Rel_Array Relocations,
|
||||
|
||||
if (HasName and IsBaseRelative) {
|
||||
revng_log(ELFImporterLog,
|
||||
"We found a base-relative relocation ("
|
||||
<< RelocationName << ") associated to a symbol, ignoring.");
|
||||
"We found a base-relative relocation #"
|
||||
<< Index << " (" << RelocationName
|
||||
<< ") associated to a symbol, ignoring.");
|
||||
} else if (not HasName and not IsBaseRelative) {
|
||||
if (ELFImporterLog.isEnabled()) {
|
||||
ELFImporterLog << "We found a non-base-relative relocation ("
|
||||
<< RelocationName
|
||||
ELFImporterLog << "We found a non-base-relative relocation #" << Index
|
||||
<< " (" << RelocationName
|
||||
<< ") not associated to a symbol, ignoring." << DoLog;
|
||||
}
|
||||
} else if (HasName) {
|
||||
@@ -1285,8 +1236,10 @@ void ELFImporter<T, HasAddend>::registerRelocations(Elf_Rel_Array Relocations,
|
||||
LowestSegment->Relocations().insert(NewRelocation);
|
||||
} else {
|
||||
revng_log(ELFImporterLog,
|
||||
"Found a base-relative relocation, but no segment is "
|
||||
"available! Ignoring.");
|
||||
"Found a base-relative relocation #" << Index
|
||||
<< ", but no segment is "
|
||||
"available! "
|
||||
"Ignoring.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
ELFImporter(TupleTree<model::Binary> &Model,
|
||||
const ELFBinary &TheBinary,
|
||||
uint64_t BaseAddress) :
|
||||
BinaryImporterHelper(*Model, BaseAddress, ELFImporterLog),
|
||||
BinaryImporterHelper(Model, BaseAddress, ELFImporterLog),
|
||||
File(*Model, toArrayRef(TheBinary.ObjectFile.getData())),
|
||||
Model(Model),
|
||||
TheBinary(TheBinary) {}
|
||||
@@ -146,15 +146,13 @@ private:
|
||||
void parseDynamicSymbol(llvm::object::Elf_Sym_Impl<T> &Symbol,
|
||||
llvm::StringRef Dynstr);
|
||||
|
||||
void findMissingTypes(llvm::object::ELFFile<T> &TheELF,
|
||||
const ImporterOptions &Options);
|
||||
|
||||
protected:
|
||||
template<typename Q>
|
||||
using SmallVectorImpl = llvm::SmallVectorImpl<Q>;
|
||||
|
||||
/// Register a label for each input relocation
|
||||
void registerRelocations(Elf_Rel_Array Relocations,
|
||||
void registerRelocations(llvm::StringRef Name,
|
||||
Elf_Rel_Array Relocations,
|
||||
const FilePortion &Dynsym,
|
||||
const FilePortion &Dynstr);
|
||||
|
||||
|
||||
@@ -14,15 +14,6 @@
|
||||
|
||||
using namespace revng::pipes;
|
||||
|
||||
static model::BinaryReference makeReference(model::Binary &Binary,
|
||||
size_t Index) {
|
||||
using Fields = TupleLikeTraits<model::Binary>::Fields;
|
||||
TupleTreePath BinaryPath;
|
||||
BinaryPath.push_back(static_cast<size_t>(Fields::Binaries));
|
||||
BinaryPath.push_back(Index);
|
||||
return model::BinaryReference{ &Binary, BinaryPath };
|
||||
}
|
||||
|
||||
llvm::Error ImportBinaryAnalysis::run(pipeline::ExecutionContext &Context,
|
||||
const BinaryFileContainer &SourceBinary) {
|
||||
if (not SourceBinary.exists())
|
||||
@@ -31,30 +22,26 @@ llvm::Error ImportBinaryAnalysis::run(pipeline::ExecutionContext &Context,
|
||||
TupleTree<model::Binary> &Model = getWritableModelFromContext(Context);
|
||||
|
||||
const ImporterOptions &Options = importerOptions();
|
||||
llvm::StringRef BinaryPath = *SourceBinary.path();
|
||||
auto MaybeBuffer = llvm::MemoryBuffer::getFileOrSTDIN(BinaryPath,
|
||||
false,
|
||||
false);
|
||||
if (not MaybeBuffer)
|
||||
return llvm::errorCodeToError(MaybeBuffer.getError());
|
||||
|
||||
llvm::Task T(2, "Import binary");
|
||||
T.advance("Import main binary", true);
|
||||
|
||||
model::BinaryReference Reference;
|
||||
if (Model->Binaries().size() > 0)
|
||||
Reference = makeReference(*Model.get(), 0);
|
||||
if (Model->Binaries().size() > 0) {
|
||||
// TODO: do this unconditionally once we drop the old pipeline
|
||||
Reference = Model->getBinaryIdentifierReference(0);
|
||||
}
|
||||
|
||||
if (llvm::Error Error = importBinary(Model,
|
||||
**MaybeBuffer,
|
||||
BinaryPath,
|
||||
*SourceBinary.path(),
|
||||
Options,
|
||||
Reference))
|
||||
Reference)) {
|
||||
return Error;
|
||||
}
|
||||
|
||||
T.advance("Import additional debug info", true);
|
||||
if (!Options.AdditionalDebugInfoPaths.empty()) {
|
||||
DwarfImporter Importer(Model);
|
||||
DwarfImporter Importer(Model, std::nullopt);
|
||||
llvm::Task T2(Options.AdditionalDebugInfoPaths.size(),
|
||||
"Import additional debug info");
|
||||
for (const std::string &Path : Options.AdditionalDebugInfoPaths) {
|
||||
@@ -70,6 +57,7 @@ static pipeline::RegisterAnalysis<ImportBinaryAnalysis> E;
|
||||
|
||||
namespace revng::pypeline::analyses {
|
||||
|
||||
// TODO: have a configuration to list the "preferred" roots to use for import
|
||||
llvm::Error ParseBinaryAnalysis::run(Model &Model,
|
||||
const Request &Incoming,
|
||||
llvm::StringRef Configuration,
|
||||
@@ -80,14 +68,9 @@ llvm::Error ParseBinaryAnalysis::run(Model &Model,
|
||||
T.advance("Import main binary", true);
|
||||
|
||||
for (size_t I = 0; I < Binaries.size(); I++) {
|
||||
llvm::ArrayRef<char> Ref = Binaries.getFile(I);
|
||||
auto Buffer = llvm::MemoryBuffer::getMemBuffer({ Ref.begin(), Ref.size() },
|
||||
"",
|
||||
false);
|
||||
|
||||
model::BinaryReference Reference = makeReference(*Model.get().get(), I);
|
||||
auto Reference = Model.get()->getBinaryIdentifierReference(I);
|
||||
if (llvm::Error Error = importBinary(Model.get(),
|
||||
*Buffer,
|
||||
Binaries.getFilePath(I),
|
||||
Options,
|
||||
Reference))
|
||||
@@ -95,8 +78,8 @@ llvm::Error ParseBinaryAnalysis::run(Model &Model,
|
||||
}
|
||||
|
||||
T.advance("Import additional debug info", true);
|
||||
if (!Options.AdditionalDebugInfoPaths.empty()) {
|
||||
DwarfImporter Importer(Model.get());
|
||||
if (not Options.AdditionalDebugInfoPaths.empty()) {
|
||||
DwarfImporter Importer(Model.get(), std::nullopt);
|
||||
llvm::Task T2(Options.AdditionalDebugInfoPaths.size(),
|
||||
"Import additional debug info");
|
||||
for (const std::string &Path : Options.AdditionalDebugInfoPaths) {
|
||||
|
||||
@@ -9,55 +9,19 @@
|
||||
#include "llvm/Support/Error.h"
|
||||
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Support/CommandLine.h"
|
||||
|
||||
namespace llvm {
|
||||
namespace object {
|
||||
class ELFObjectFileBase;
|
||||
class COFFObjectFile;
|
||||
class MachOObjectFile;
|
||||
} // namespace object
|
||||
} // namespace llvm
|
||||
|
||||
struct ImporterOptions;
|
||||
|
||||
template<typename T>
|
||||
struct BinaryDescriptor {
|
||||
public:
|
||||
T &ObjectFile;
|
||||
llvm::MemoryBufferRef Buffer;
|
||||
llvm::StringRef Path;
|
||||
model::BinaryReference &Reference;
|
||||
|
||||
llvm::StringRef getFilename() const {
|
||||
// The debug info discovery relies on knowing the filename of the input
|
||||
// binary, this is because, among the standard candidate files there are
|
||||
// options that depend on it such as `${FILENAME}.debug`. If the reference
|
||||
// to the `Binaries` entry is present, use the name from there as it's more
|
||||
// reliable than the filename of the file on disk (this is still used as a
|
||||
// fallback in order to be compatible with the old pipeline).
|
||||
if (Reference.isValid())
|
||||
return Reference.get()->Path();
|
||||
else if (not InputPath.empty())
|
||||
return InputPath;
|
||||
else if (not ObjectFile.getFileName().empty())
|
||||
return ObjectFile.getFileName();
|
||||
else
|
||||
return Path;
|
||||
}
|
||||
};
|
||||
|
||||
using ELFBinary = BinaryDescriptor<llvm::object::ELFObjectFileBase>;
|
||||
llvm::Error importELF(TupleTree<model::Binary> &Model,
|
||||
const ELFBinary &TheBinary,
|
||||
const ImporterOptions &Options);
|
||||
|
||||
using COFFBinary = BinaryDescriptor<llvm::object::COFFObjectFile>;
|
||||
llvm::Error importPECOFF(TupleTree<model::Binary> &Model,
|
||||
const COFFBinary &TheBinary,
|
||||
const ImporterOptions &Options);
|
||||
|
||||
using MachOBinary = BinaryDescriptor<llvm::object::MachOObjectFile>;
|
||||
llvm::Error importMachO(TupleTree<model::Binary> &Model,
|
||||
MachOBinary &TheBinary,
|
||||
const ImporterOptions &Options);
|
||||
|
||||
@@ -117,10 +117,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
if (GOT->isAvailable())
|
||||
this->registerRelocations(MIPSImplicitRelocations,
|
||||
if (GOT->isAvailable()) {
|
||||
this->registerRelocations("MIPS .got",
|
||||
MIPSImplicitRelocations,
|
||||
*this->DynsymPortion.get(),
|
||||
*this->DynstrPortion.get());
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Error import(const ImporterOptions &Options) override {
|
||||
|
||||
@@ -176,7 +176,7 @@ public:
|
||||
MachOImporter(TupleTree<model::Binary> &Model,
|
||||
MachOBinary &TheBinary,
|
||||
uint64_t BaseAddress) :
|
||||
BinaryImporterHelper(*Model, BaseAddress, Log),
|
||||
BinaryImporterHelper(Model, BaseAddress, Log),
|
||||
File(*Model, toArrayRef(TheBinary.ObjectFile.getData())),
|
||||
Model(Model),
|
||||
TheBinary(TheBinary) {}
|
||||
@@ -319,6 +319,8 @@ void MachOImporter::parseMachOSegment(ArrayRef<uint8_t> RawDataRef,
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: do the following unconditionally once the old pipeline has been
|
||||
// dropped.
|
||||
if (TheBinary.Reference.isValid())
|
||||
Segment.Binary() = TheBinary.Reference;
|
||||
Segment.Name() = SegmentCommand.segname;
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
#include "revng/ABI/DefaultFunctionPrototype.h"
|
||||
#include "revng/Model/Binary.h"
|
||||
#include "revng/Model/IRHelpers.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporterHelper.h"
|
||||
#include "revng/Model/Importer/Binary/Options.h"
|
||||
#include "revng/Model/Importer/DebugInfo/PDBImporter.h"
|
||||
#include "revng/Model/Pass/AllPasses.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/Error.h"
|
||||
#include "revng/Support/MetaAddress.h"
|
||||
|
||||
#include "CrossModelFindTypeHelper.h"
|
||||
@@ -25,6 +27,11 @@ using namespace llvm::object;
|
||||
|
||||
static Logger Log("pecoff-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));
|
||||
|
||||
using PELDDTree = std::map<std::string, std::vector<std::string>>;
|
||||
|
||||
class PECOFFImporter : public BinaryImporterHelper {
|
||||
@@ -38,7 +45,7 @@ public:
|
||||
PECOFFImporter(TupleTree<model::Binary> &Model,
|
||||
const COFFBinary &TheBinary,
|
||||
uint64_t BaseAddress) :
|
||||
BinaryImporterHelper(*Model, BaseAddress, Log),
|
||||
BinaryImporterHelper(Model, BaseAddress, Log),
|
||||
Model(Model),
|
||||
TheBinary(TheBinary) {
|
||||
revng_log(Log, "Creating binary importer helper");
|
||||
@@ -59,9 +66,6 @@ private:
|
||||
/// Parse delay dynamic symbols from the file.
|
||||
void parseDelayImportedSymbols();
|
||||
|
||||
/// Try to find prototypes in the Models of dynamic libraries.
|
||||
void findMissingTypes(const ImporterOptions &Options);
|
||||
|
||||
using DelayDirectoryRef = const DelayImportDirectoryEntryRef;
|
||||
void recordDelayImportedFunctions(DelayDirectoryRef &I,
|
||||
ImportedSymbolRange Range);
|
||||
@@ -112,6 +116,8 @@ Error PECOFFImporter::parseSectionsHeaders() {
|
||||
MetaAddress Start = ImageBase + u64(CoffRef->VirtualAddress);
|
||||
Segment Segment({ Start, u64(CoffRef->VirtualSize) });
|
||||
|
||||
// TODO: do the following unconditionally once the old pipeline has been
|
||||
// dropped.
|
||||
if (TheBinary.Reference.isValid())
|
||||
Segment.Binary() = TheBinary.Reference;
|
||||
Segment.StartOffset() = CoffRef->PointerToRawData;
|
||||
@@ -241,16 +247,27 @@ void PECOFFImporter::parseImportedSymbols() {
|
||||
TheBinary.ObjectFile.import_directories()) {
|
||||
StringRef Name;
|
||||
if (Error E = I.getName(Name)) {
|
||||
revng_log(Log, "Found an imported symbol without a name.");
|
||||
revng_log(Log, "Found an imported library without a name.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Let's find symbols of the dll associated with Name.
|
||||
|
||||
uint32_t ImportLookupTableEntry;
|
||||
// In PE/COFF you can have two tables for imported functions: the
|
||||
// ImportLookupTable (aka ILT) and the ImportAddressTable (aka IAT). On disk
|
||||
// they are supposed to be the same. At run-time the IAT will be patched
|
||||
// with the actual addresses.
|
||||
//
|
||||
// Exception: the IAT might be "bound", i.e., pre-filled. The loader checks
|
||||
// at run-time if the bound IAT is valid via a timestamp. If it isn't uses
|
||||
// the ILT. One could debate which one is more reliable, we choose to first
|
||||
// use the ILT, if absent, we use the IAT.
|
||||
|
||||
uint32_t ImportLookupTableEntry = 0;
|
||||
bool HasImportLookupTableEntry = true;
|
||||
if (Error E = I.getImportLookupTableRVA(ImportLookupTableEntry)) {
|
||||
revng_log(Log, "No ImportLookupTableRVA found for an import");
|
||||
continue;
|
||||
HasImportLookupTableEntry = false;
|
||||
}
|
||||
|
||||
uint32_t ImportAddressTableEntry;
|
||||
@@ -259,12 +276,14 @@ void PECOFFImporter::parseImportedSymbols() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not Model->ImportedLibraries().insert(Name.str()).second)
|
||||
if (not Model->ImportedLibraries().insert(Name.str()).second) {
|
||||
revng_log(Log, "Library " << Name.str() << " already imported, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
// The import lookup table can be missing with certain older linkers, so
|
||||
// fall back to the import address table in that case.
|
||||
if (ImportLookupTableEntry) {
|
||||
if (HasImportLookupTableEntry) {
|
||||
recordImportedFunctions(I.lookup_table_symbols(),
|
||||
ImportAddressTableEntry);
|
||||
} else {
|
||||
@@ -336,197 +355,6 @@ void PECOFFImporter::parseDelayImportedSymbols() {
|
||||
}
|
||||
}
|
||||
|
||||
/// \note For the PE/COFF, we are assuming that the libraries are in the current
|
||||
/// directory.
|
||||
static RecursiveCoroutine<void> getDependencies(StringRef FileName,
|
||||
PELDDTree Dependencies,
|
||||
unsigned CurrentLevel,
|
||||
unsigned Level) {
|
||||
revng_log(Log, "Computing dependencies of " << FileName.str());
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
if (CurrentLevel == Level) {
|
||||
revng_log(Log, "Maximum recursion depth exceeded, bailing out");
|
||||
rc_return;
|
||||
}
|
||||
|
||||
auto BinaryOrErr = object::createBinary(FileName);
|
||||
if (not BinaryOrErr) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(Log,
|
||||
"Can't create object for " << FileName << " due to \""
|
||||
<< toString(BinaryOrErr.takeError())
|
||||
<< "\"");
|
||||
llvm::consumeError(BinaryOrErr.takeError());
|
||||
rc_return;
|
||||
}
|
||||
|
||||
revng_log(Log, "Binary parsed successfully");
|
||||
|
||||
auto Object = cast<object::ObjectFile>(BinaryOrErr->getBinary());
|
||||
auto COFFObject = cast<COFFObjectFile>(Object);
|
||||
for (const ImportDirectoryEntryRef &I : COFFObject->import_directories()) {
|
||||
StringRef LibraryName;
|
||||
if (Error E = I.getName(LibraryName)) {
|
||||
revng_log(Log, "Found an imported symbol without a name.");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t ImportLookupTableEntry;
|
||||
if (Error E = I.getImportLookupTableRVA(ImportLookupTableEntry)) {
|
||||
revng_log(Log, "No ImportLookupTableRVA found for an import");
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t ImportAddressTableEntry;
|
||||
if (Error E = I.getImportAddressTableRVA(ImportAddressTableEntry)) {
|
||||
revng_log(Log, "No ImportAddressTableRVA found for an import");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto LibraryNameAsString = LibraryName.str();
|
||||
Dependencies[FileName.str()].push_back(LibraryNameAsString);
|
||||
}
|
||||
|
||||
++CurrentLevel;
|
||||
for (auto &Library : Dependencies) {
|
||||
revng_log(Log, "Dependencies for " << Library.first << ":\n");
|
||||
for (auto &DependingLibrary : Library.second)
|
||||
if (!Dependencies.contains(DependingLibrary))
|
||||
rc_recur getDependencies(DependingLibrary,
|
||||
Dependencies,
|
||||
CurrentLevel,
|
||||
Level);
|
||||
}
|
||||
}
|
||||
|
||||
void PECOFFImporter::findMissingTypes(const ImporterOptions &Opts) {
|
||||
revng_log(Log, "Looking for missing types in dynamic libraries");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
if (Opts.DebugInfo != DebugInfoLevel::Yes) {
|
||||
revng_log(Log, "Bailing out per user request");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: disclose a way to modify this value with
|
||||
// the `ImporterOptions::DebugInfo`, if the need ever arises.
|
||||
unsigned MaximumRecursionDepth = 2;
|
||||
|
||||
PELDDTree Dependencies;
|
||||
if (MaximumRecursionDepth > 0) {
|
||||
rc_eval(getDependencies(TheBinary.getFilename(),
|
||||
Dependencies,
|
||||
0,
|
||||
MaximumRecursionDepth));
|
||||
}
|
||||
|
||||
ModelMap ModelsOfLibraries;
|
||||
TypeCopierMap TypeCopiers;
|
||||
|
||||
for (auto &Library : Dependencies) {
|
||||
revng_log(Log,
|
||||
"Importing Models for dependencies of " << Library.first << ":");
|
||||
for (auto &DependencyLibrary : Library.second) {
|
||||
if (ModelsOfLibraries.contains(DependencyLibrary))
|
||||
continue;
|
||||
revng_log(Log, " Importing Model for: " << DependencyLibrary);
|
||||
auto BinaryOrErr = llvm::object::createBinary(DependencyLibrary);
|
||||
if (not BinaryOrErr) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(Log,
|
||||
"Can't create object for "
|
||||
<< DependencyLibrary << " due to "
|
||||
<< toString(BinaryOrErr.takeError()));
|
||||
llvm::consumeError(BinaryOrErr.takeError());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &File = *cast<llvm::object::ObjectFile>(BinaryOrErr->getBinary());
|
||||
auto *TheBinary = dyn_cast<COFFObjectFile>(&File);
|
||||
if (!TheBinary)
|
||||
continue;
|
||||
|
||||
ModelsOfLibraries[DependencyLibrary] = TupleTree<model::Binary>();
|
||||
auto &DepModel = ModelsOfLibraries[DependencyLibrary];
|
||||
DepModel->Architecture() = Model->Architecture();
|
||||
|
||||
ImporterOptions AdjustedOptions{
|
||||
.BaseAddress = Opts.BaseAddress,
|
||||
.DebugInfo = DebugInfoLevel::IgnoreLibraries,
|
||||
.EnableRemoteDebugInfo = Opts.EnableRemoteDebugInfo,
|
||||
.AdditionalDebugInfoPaths = Opts.AdditionalDebugInfoPaths
|
||||
};
|
||||
COFFBinary Binary(*TheBinary,
|
||||
TheBinary->getMemoryBufferRef(),
|
||||
DependencyLibrary,
|
||||
this->TheBinary.Reference);
|
||||
|
||||
if (auto E = importPECOFF(DepModel, Binary, AdjustedOptions)) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(Log,
|
||||
"Can't import model for " << DependencyLibrary << " due to "
|
||||
<< E);
|
||||
llvm::consumeError(std::move(E));
|
||||
ModelsOfLibraries.erase(DependencyLibrary);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto GetOrMakeACopier = [&](llvm::StringRef Name) -> TypeCopier & {
|
||||
if (auto It = TypeCopiers.find(Name.str()); It != TypeCopiers.end())
|
||||
return *It->second;
|
||||
|
||||
auto Iterator = ModelsOfLibraries.find(Name.str());
|
||||
revng_assert(Iterator != ModelsOfLibraries.end());
|
||||
|
||||
auto NewCopier = std::make_unique<TypeCopier>(Iterator->second, Model);
|
||||
auto &&[Result, Success] = TypeCopiers.emplace(Name.str(),
|
||||
std::move(NewCopier));
|
||||
revng_assert(Success);
|
||||
return *Result->second;
|
||||
};
|
||||
|
||||
for (auto &Fn : Model->ImportedDynamicFunctions()) {
|
||||
if (not Fn.Prototype().isEmpty() or Fn.Name().size() == 0)
|
||||
continue;
|
||||
|
||||
revng_log(Log, "Searching for prototype for " << Fn.Name());
|
||||
LoggerIndent Indent(Log);
|
||||
if (auto Found = findPrototype(Fn.Name(), ModelsOfLibraries)) {
|
||||
revng_assert(!Found->ModuleName.empty());
|
||||
revng_assert(Found->Prototype.verify(true));
|
||||
|
||||
model::UpcastableTypeDefinition SerializablePrototype = Found->Prototype;
|
||||
revng_log(Log,
|
||||
"Found type for " << Fn.Name() << " in " << Found->ModuleName
|
||||
<< ": " << toString(SerializablePrototype));
|
||||
TypeCopier &TheTypeCopier = GetOrMakeACopier(Found->ModuleName);
|
||||
Fn.Prototype() = TheTypeCopier.copyTypeInto(Found->Prototype);
|
||||
|
||||
// Copy all the Attributes except for `AlwaysInline`.
|
||||
for (auto &Attribute : Found->Attributes)
|
||||
if (Attribute != model::FunctionAttribute::AlwaysInline)
|
||||
Fn.Attributes().insert(Attribute);
|
||||
} else {
|
||||
revng_log(Log, "Not found");
|
||||
}
|
||||
}
|
||||
|
||||
// Finalize the copies
|
||||
for (auto &[_, TC] : TypeCopiers)
|
||||
TC->finalize();
|
||||
|
||||
// Purge cached references and update the reference to Root.
|
||||
Model.disableReferenceCaching();
|
||||
Model.initializeReferences();
|
||||
|
||||
model::flattenPrimitiveTypedefs(Model);
|
||||
deduplicateEquivalentTypes(Model);
|
||||
model::deduplicateCollidingNames(Model);
|
||||
}
|
||||
|
||||
Error PECOFFImporter::import(const ImporterOptions &Options) {
|
||||
if (Error E = parseSectionsHeaders())
|
||||
return E;
|
||||
@@ -560,11 +388,35 @@ Error PECOFFImporter::import(const ImporterOptions &Options) {
|
||||
revng_log(Log, "Importing PDB");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
PDBImporter PDBI(Model, ImageBase);
|
||||
PDBI.import(TheBinary.ObjectFile, TheBinary.Path, Options);
|
||||
// Consider the --use-pdb argument
|
||||
if (not UsePDB.empty()) {
|
||||
auto ImportLogger = importLogger(TheBinary.canonicalPath());
|
||||
PDBImporter PDBI(Model, ImageBase, std::nullopt);
|
||||
PDBI.importPDB(UsePDB, Options);
|
||||
}
|
||||
|
||||
// Now we try to find missing types in the dependencies.
|
||||
findMissingTypes(Options);
|
||||
// Identify dependencies
|
||||
std::optional<LDDTree>
|
||||
MaybeDependencies = identifyDependencies(TheBinary.ObjectFile,
|
||||
TheBinary.canonicalPath());
|
||||
|
||||
if (MaybeDependencies.has_value()) {
|
||||
// Import debug info
|
||||
{
|
||||
auto ImportLogger = importLogger(TheBinary.canonicalPath());
|
||||
PDBImporter PDBI(Model, ImageBase, std::nullopt);
|
||||
revng_assert(MaybeDependencies->Root != nullptr);
|
||||
PDBI.import(MaybeDependencies->Root, TheBinary, Options);
|
||||
}
|
||||
|
||||
// Now we try to find missing types in the dependencies
|
||||
findMissingTypes<COFFBinary, PDBImporter>(*MaybeDependencies,
|
||||
Options,
|
||||
Logger,
|
||||
Model);
|
||||
} else {
|
||||
revng_log(Log, "Couldn't find an appropriate rootfs");
|
||||
}
|
||||
}
|
||||
|
||||
model::flattenPrimitiveTypedefs(Model);
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
# This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
#
|
||||
|
||||
revng_add_library_internal(revngModelImporterDebugInfo SHARED DwarfImporter.cpp
|
||||
PDBImporter.cpp)
|
||||
revng_add_library_internal(
|
||||
revngModelImporterDebugInfo
|
||||
SHARED
|
||||
DwarfImporter.cpp
|
||||
DwarfToModelConverter.cpp
|
||||
PDBImporter.cpp
|
||||
PDBImporterImpl.cpp
|
||||
PDBTypeResolver.cpp)
|
||||
|
||||
llvm_map_components_to_libnames(
|
||||
LLVM_LIBRARIES
|
||||
|
||||
@@ -0,0 +1,537 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <concepts>
|
||||
#include <optional>
|
||||
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/BinaryFormat/ELF.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Support/Endian.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Progress.h"
|
||||
|
||||
#include "revng/Model/Importer/Binary/BinaryDescriptor.h"
|
||||
#include "revng/Model/Importer/DebugInfo/DwarfImporter.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/Generator.h"
|
||||
#include "revng/Support/ObjectFile.h"
|
||||
#include "revng/Support/PathList.h"
|
||||
|
||||
#include "ImportDebugInfoHelper.h"
|
||||
|
||||
inline const std::string GlobalDebugDirectory = "/usr/lib/debug/";
|
||||
|
||||
inline cppcoro::generator<const llvm::sys::fs::directory_entry &>
|
||||
listDirectory(const llvm::SmallString<16> &Path) {
|
||||
using namespace llvm::sys::fs;
|
||||
|
||||
revng_log(DILogger, "Listing " << Path.str());
|
||||
|
||||
std::error_code EC;
|
||||
auto DirectoryIt = directory_iterator(Path, EC);
|
||||
|
||||
if (EC) {
|
||||
revng_log(DILogger,
|
||||
"Error enumerating " << Path.str() << ": " << EC.message());
|
||||
} else {
|
||||
|
||||
for (const directory_iterator EndIt; DirectoryIt != EndIt;
|
||||
DirectoryIt.increment(EC)) {
|
||||
const auto &Entry = *DirectoryIt;
|
||||
co_yield Entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t gnuDebugLinkCRC32(llvm::ArrayRef<uint8_t> Data) {
|
||||
static const uint32_t Table[256] = {
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
|
||||
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
||||
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
|
||||
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
||||
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
||||
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
||||
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
||||
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
|
||||
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
||||
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
|
||||
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
||||
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
|
||||
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
||||
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
||||
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
|
||||
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
||||
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
|
||||
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
||||
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
||||
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
|
||||
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
||||
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
||||
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
||||
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
|
||||
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
|
||||
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
||||
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
|
||||
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
||||
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
|
||||
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
||||
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
||||
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
|
||||
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
||||
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
|
||||
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
||||
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
|
||||
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
};
|
||||
|
||||
uint32_t Result = 0;
|
||||
Result = ~Result & 0xffffffff;
|
||||
|
||||
for (uint8_t Byte : Data)
|
||||
Result = Table[(Result ^ Byte) & 0xff] ^ (Result >> 8);
|
||||
|
||||
return ~Result & 0xffffffff;
|
||||
}
|
||||
|
||||
template<IsELFObjectFile T>
|
||||
class DwarfDebugInfoFinder {
|
||||
public:
|
||||
const revng::RootEntry &Root;
|
||||
const ELFBinary &Binary;
|
||||
const T &ObjectFile;
|
||||
|
||||
private:
|
||||
DwarfDebugInfoFinder(const revng::RootEntry &Root,
|
||||
const ELFBinary &Binary,
|
||||
const T &ObjectFile) :
|
||||
Root(Root), Binary(Binary), ObjectFile(ObjectFile) {}
|
||||
|
||||
public:
|
||||
static std::string find(const revng::RootEntry &Root,
|
||||
const ELFBinary &Binary,
|
||||
const T &ObjectFile) {
|
||||
DwarfDebugInfoFinder Finder(Root, Binary, ObjectFile);
|
||||
return Finder.find();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string find() {
|
||||
using namespace llvm;
|
||||
|
||||
revng_log(DILogger, "Looking for separate debug info");
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
llvm::Task T1(3, "Looking for separate debug info");
|
||||
|
||||
std::string SeparateDebugInfoPath;
|
||||
|
||||
// Try to identify the separate debug info file locally
|
||||
revng_log(DILogger, "Searching locally");
|
||||
T1.advance("Searching locally", true);
|
||||
LoggerIndent Indent2(DILogger);
|
||||
SeparateDebugInfoPath = getSeparateDebugInfo();
|
||||
|
||||
if (SeparateDebugInfoPath.empty()) {
|
||||
// We couldn't find locally, run fetch-debuginfo and try again
|
||||
revng_log(DILogger, "Fetching debug info remotely");
|
||||
T1.advance("Fetching debug info remotely", true);
|
||||
LoggerIndent Indent(DILogger);
|
||||
int ExitCode = runFetchDebugInfo(Binary.FullPathForExternalTools,
|
||||
DILogger.isEnabled());
|
||||
if (ExitCode == 0) {
|
||||
revng_log(DILogger, "Searching locally again");
|
||||
T1.advance("Searching locally again", true);
|
||||
LoggerIndent Indent(DILogger);
|
||||
SeparateDebugInfoPath = getSeparateDebugInfo();
|
||||
} else {
|
||||
revng_log(DILogger,
|
||||
"Failed to find debug info with `revng model "
|
||||
"fetch-debuginfo`.");
|
||||
}
|
||||
}
|
||||
|
||||
return SeparateDebugInfoPath;
|
||||
}
|
||||
|
||||
std::string getSeparateDebugInfo() {
|
||||
// First, use build-id in all of its forms, i.e., .note.gnu.build-id and
|
||||
// .gnu_debuglink.
|
||||
// Then consider .gnu_debugaltlink and manual look up by .debug suffix
|
||||
// (e.g., bash.debug).
|
||||
|
||||
std::string Path = processBuildID();
|
||||
|
||||
if (Path.size() == 0) {
|
||||
Path = processGnuDebugLink();
|
||||
}
|
||||
|
||||
if (Path.size() == 0) {
|
||||
Path = processGnuDebugAltLink();
|
||||
}
|
||||
|
||||
if (Path.size() == 0) {
|
||||
Path = processDotDebugFile();
|
||||
}
|
||||
|
||||
if (Path.size() == 0) {
|
||||
revng_log(DILogger, "Not found");
|
||||
} else {
|
||||
revng_log(DILogger, "Found: " << Path);
|
||||
}
|
||||
|
||||
return Path;
|
||||
}
|
||||
|
||||
std::string processBuildID() {
|
||||
revng_log(DILogger, "Processing build ID");
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
std::string BuildID = getBuildID();
|
||||
if (BuildID.size() == 0) {
|
||||
revng_log(DILogger, "Can't parse build-id.");
|
||||
return "";
|
||||
}
|
||||
|
||||
revng_log(DILogger, "Build-id: " << BuildID);
|
||||
|
||||
llvm::SmallString<128> ResultPath;
|
||||
|
||||
{
|
||||
// First two chars of build-id forms the debug info file directory.
|
||||
auto DebugDir = BuildID.substr(0, 2);
|
||||
|
||||
// The rest of build-id forms the debug info file name.
|
||||
auto DebugFile = BuildID.substr(2);
|
||||
auto DebugFileWithExtension = DebugFile.append(".debug");
|
||||
llvm::sys::path::append(ResultPath,
|
||||
GlobalDebugDirectory,
|
||||
".build-id/",
|
||||
DebugDir,
|
||||
DebugFileWithExtension);
|
||||
|
||||
if (auto MaybeFullPath = Root.getExistingPath(ResultPath.str())) {
|
||||
revng_log(DILogger, "Debug info found in " << *MaybeFullPath);
|
||||
return *MaybeFullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Try in revng's debug symbols cache
|
||||
ResultPath.clear();
|
||||
std::string CacheDir = getCacheDirectory();
|
||||
ResultPath = joinPath(CacheDir, "debug-symbols", "elf", BuildID, "debug");
|
||||
|
||||
if (fileExists(ResultPath.str())) {
|
||||
revng_log(DILogger, "Debug info found in cache: " << ResultPath.str());
|
||||
return ResultPath.str().str();
|
||||
}
|
||||
|
||||
revng_log(DILogger, "Processing build-id did not lead to an existing file");
|
||||
return "";
|
||||
}
|
||||
|
||||
llvm::ArrayRef<uint8_t> findELFNote(llvm::StringRef Name, uint32_t Type) {
|
||||
const auto &ELF = ObjectFile.getELFFile();
|
||||
|
||||
auto MaybeProgramHeaders = ELF.program_headers();
|
||||
if (not MaybeProgramHeaders) {
|
||||
std::string Message = llvm::toString(MaybeProgramHeaders.takeError());
|
||||
revng_log(DILogger, "Couldn't parse program headers: " << Message);
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &ProgramHeader : *MaybeProgramHeaders) {
|
||||
if (ProgramHeader.p_type != llvm::ELF::PT_NOTE)
|
||||
continue;
|
||||
|
||||
llvm::Error NotesError = llvm::Error::success();
|
||||
for (const auto &ELFNote : ELF.notes(ProgramHeader, NotesError)) {
|
||||
if (ELFNote.getName() == Name and ELFNote.getType() == Type) {
|
||||
return ELFNote.getDesc();
|
||||
}
|
||||
}
|
||||
|
||||
if (NotesError) {
|
||||
std::string Message = llvm::toString(std::move(NotesError));
|
||||
revng_log(DILogger, "Failed to parse notes: " << Message);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string getBuildID() {
|
||||
using namespace llvm;
|
||||
|
||||
const auto &ELF = ObjectFile.getELFFile();
|
||||
|
||||
// This is the correct way to identify .note.gnu.build-id
|
||||
ArrayRef<uint8_t> Contents = findELFNote("GNU", ELF::NT_GNU_BUILD_ID);
|
||||
if (Contents.size() <= 2) {
|
||||
// We want at least two bytes
|
||||
revng_log(DILogger, "Empty build-id found, skipping");
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string StringForBytes;
|
||||
{
|
||||
// Convert to hex
|
||||
raw_string_ostream OutputStream(StringForBytes);
|
||||
for (uint8_t Byte : Contents)
|
||||
OutputStream << format_hex_no_prefix(Byte, 2);
|
||||
}
|
||||
|
||||
return StringForBytes;
|
||||
}
|
||||
|
||||
std::string processGnuDebugLink() {
|
||||
revng_log(DILogger, "Processing .gnu_debuglink");
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
auto [Path, Rest] = getStringFromSection(".gnu_debuglink");
|
||||
// .gnu_debuglink contains something like
|
||||
// d12d07f91f419ba28f2ab11d96355a54b0c2c2.debug or libc-2.31.so (which
|
||||
// could resolve to the input binary itself), followed by 0 to 3 bytes to
|
||||
// reach 4-bytes alignment and then a 4-bytes CRC.
|
||||
|
||||
// Bail out if it's a path, as opposed to just a file name
|
||||
if (Path.contains("/")) {
|
||||
revng_log(DILogger,
|
||||
".gnu_debuglink contains /, unexpected, bailing out.");
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Path.size() == 0)
|
||||
return {};
|
||||
|
||||
// Collect the CRC
|
||||
if (Rest.size() < 4) {
|
||||
revng_log(DILogger, "No CRC found in .gnu_debuglink");
|
||||
return "";
|
||||
}
|
||||
|
||||
using namespace llvm::object;
|
||||
bool IsLittleEndian = true;
|
||||
llvm::support::endianness Endianness = llvm::support::little;
|
||||
if constexpr (std::is_same_v<T, ELF32BEObjectFile>
|
||||
or std::is_same_v<T, ELF64BEObjectFile>) {
|
||||
Endianness = llvm::support::big;
|
||||
}
|
||||
|
||||
auto CRC = llvm::support::endian::read<uint32_t>(Rest.end() - 4,
|
||||
Endianness);
|
||||
return findByName(Path, CRC);
|
||||
}
|
||||
|
||||
std::string processGnuDebugAltLink() {
|
||||
revng_log(DILogger, "Processing .gnu_debugaltlink");
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
// Look for the absolute path
|
||||
auto [Path, _] = getStringFromSection(".gnu_debugaltlink");
|
||||
|
||||
if (Path.size() == 0)
|
||||
return {};
|
||||
|
||||
if (auto MaybeFullPath = Root.getExistingPath(Path)) {
|
||||
return *MaybeFullPath;
|
||||
}
|
||||
|
||||
// Not found: extract the file name and look in canonical places
|
||||
auto FileName = llvm::sys::path::filename(Path);
|
||||
|
||||
return findByName(FileName, std::nullopt);
|
||||
}
|
||||
|
||||
std::string processDotDebugFile() {
|
||||
revng_log(DILogger, "Processing the .debug file");
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
auto FileName = llvm::sys::path::filename(Binary.canonicalPath());
|
||||
|
||||
if (FileName.empty())
|
||||
return "";
|
||||
|
||||
return findByName((llvm::Twine(FileName) + ".debug").str(), std::nullopt);
|
||||
}
|
||||
|
||||
private:
|
||||
llvm::ArrayRef<uint8_t> getSectionsContents(llvm::StringRef Name) {
|
||||
auto ELF = ObjectFile.getELFFile();
|
||||
auto MaybeSections = ELF.sections();
|
||||
if (auto Error = MaybeSections.takeError()) {
|
||||
// TODO: emit a diagnostic message for the user.
|
||||
revng_log(DILogger, "Failed to get sections: " << Error);
|
||||
consumeError(std::move(Error));
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &Section : *MaybeSections) {
|
||||
auto MaybeName = expectedToOptional(ELF.getSectionName(Section));
|
||||
if (MaybeName and *MaybeName == Name) {
|
||||
auto MaybeContents = expectedToOptional(ELF
|
||||
.getSectionContents(Section));
|
||||
if (MaybeContents)
|
||||
return *MaybeContents;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::pair<llvm::StringRef, llvm::ArrayRef<uint8_t>>
|
||||
getStringFromSection(llvm::StringRef Name) {
|
||||
using namespace llvm;
|
||||
|
||||
auto ELF = ObjectFile.getELFFile();
|
||||
ArrayRef<uint8_t> SectionData = getSectionsContents(Name);
|
||||
if (SectionData.size() == 0)
|
||||
return {};
|
||||
|
||||
for (auto [Index, Byte] : llvm::enumerate(SectionData)) {
|
||||
if (Byte == 0) {
|
||||
const char *Start = reinterpret_cast<const char *>(SectionData.data());
|
||||
return { StringRef(Start, Index), SectionData.slice(Index + 1) };
|
||||
}
|
||||
}
|
||||
|
||||
return { {}, SectionData };
|
||||
}
|
||||
|
||||
std::optional<std::string> getPathWithCRC(llvm::StringRef RootRelativePath,
|
||||
std::optional<uint32_t> CRC) {
|
||||
auto MaybePath = Root.getExistingPath(RootRelativePath);
|
||||
if (not MaybePath)
|
||||
return std::nullopt;
|
||||
|
||||
if (CRC.has_value()) {
|
||||
revng_log(DILogger,
|
||||
"Checking if CRC is 0x" << llvm::utohexstr(*CRC, true));
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
auto MaybeFile = llvm::MemoryBuffer::getFile(*MaybePath, false, false);
|
||||
if (std::error_code ErrorCode = MaybeFile.getError()) {
|
||||
revng_log(DILogger,
|
||||
"Error opening the file to compute the CRC: " << ErrorCode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto ContentString = MaybeFile->get()->getBuffer();
|
||||
auto *Pointer = reinterpret_cast<const uint8_t *>(ContentString.data());
|
||||
auto ContentArray = llvm::ArrayRef<uint8_t>(Pointer,
|
||||
ContentString.size());
|
||||
|
||||
if (gnuDebugLinkCRC32(ContentArray) != *CRC) {
|
||||
revng_log(DILogger, "CRC mismatch");
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return MaybePath;
|
||||
}
|
||||
|
||||
std::string findByName(llvm::StringRef Name, std::optional<uint32_t> CRC) {
|
||||
revng_assert(not Name.empty());
|
||||
|
||||
using namespace llvm::sys::path;
|
||||
using namespace llvm::sys::fs;
|
||||
auto BinaryCanonicalPath = Binary.canonicalPath();
|
||||
bool HasAbsoluteCanonicalPath = is_absolute(BinaryCanonicalPath)
|
||||
and has_parent_path(BinaryCanonicalPath);
|
||||
|
||||
revng_log(DILogger,
|
||||
"Looking for \"" << Name << "\" for \"" << BinaryCanonicalPath
|
||||
<< "\"");
|
||||
LoggerIndent Indent(DILogger);
|
||||
|
||||
// If the binary canonical path is `/usr/bin/ls`, we look for:
|
||||
//
|
||||
// * /usr/bin/$DEBUG_FILE_NAME
|
||||
// * /usr/bin/.debug/$DEBUG_FILE_NAME
|
||||
// * /usr/lib/debug/usr/bin/$DEBUG_FILE_NAME
|
||||
// * /usr/lib/debug/.build-id/*/$DEBUG_FILE_NAME
|
||||
//
|
||||
// Typically $DEBUG_FILE_NAME is ls.debug or
|
||||
// d12d07f91f419ba28f2ab11d96355a54b0c2c2.debug
|
||||
//
|
||||
// Note: in theory we could look in $XDG_CACHE_DIR/revng/debug-symbols/elf/
|
||||
// as well but we store files as $BUILD_ID/debug. Also, $BUILD_ID is the
|
||||
// full build-id, while the name we get here is stripped of the first byte
|
||||
// (two hex-digits).
|
||||
|
||||
llvm::SmallString<128> ResultPath;
|
||||
|
||||
if (HasAbsoluteCanonicalPath) {
|
||||
// Try in the same directory
|
||||
append(ResultPath, parent_path(BinaryCanonicalPath), Name);
|
||||
|
||||
if (auto MaybeFullPath = getPathWithCRC(ResultPath.str(), CRC)) {
|
||||
return *MaybeFullPath;
|
||||
}
|
||||
|
||||
// Try in .debug/ directory.
|
||||
ResultPath.clear();
|
||||
append(ResultPath, parent_path(BinaryCanonicalPath), ".debug/", Name);
|
||||
|
||||
if (auto MaybeFullPath = getPathWithCRC(ResultPath.str(), CRC)) {
|
||||
return *MaybeFullPath;
|
||||
}
|
||||
|
||||
// Try `/usr/lib/debug/usr/bin/ls.debug`-like path.
|
||||
ResultPath.clear();
|
||||
append(ResultPath,
|
||||
GlobalDebugDirectory,
|
||||
parent_path(BinaryCanonicalPath),
|
||||
Name);
|
||||
|
||||
if (auto MaybeFullPath = getPathWithCRC(ResultPath.str(), CRC)) {
|
||||
return *MaybeFullPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Look in /usr/lib/debug/.build-id/*/
|
||||
// TODO: enumerating all the directories in .build-id is not very nice.
|
||||
llvm::SmallString<16> BasePath;
|
||||
append(BasePath, Root.Path, GlobalDebugDirectory, ".build-id");
|
||||
|
||||
for (const auto &Entry : listDirectory(BasePath)) {
|
||||
if (Entry.type() != file_type::directory_file)
|
||||
continue;
|
||||
|
||||
auto DirectoryName = filename(Entry.path());
|
||||
ResultPath.clear();
|
||||
append(ResultPath,
|
||||
GlobalDebugDirectory,
|
||||
".build-id",
|
||||
DirectoryName,
|
||||
Name);
|
||||
|
||||
if (auto MaybeFullPath = getPathWithCRC(ResultPath.str(), CRC)) {
|
||||
return *MaybeFullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool fileExists(const llvm::Twine &Path) const {
|
||||
using namespace llvm::sys;
|
||||
revng_assert(not Path.str().empty());
|
||||
bool Result = fs::exists(Path);
|
||||
if (not Result)
|
||||
revng_log(DILogger, "No file at the following path: " << Path.str());
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
|
||||
|
||||
#include "revng/ADT/RecursiveCoroutine.h"
|
||||
#include "revng/Model/Importer/Binary/BinaryImporterHelper.h"
|
||||
|
||||
class DwarfImporter;
|
||||
|
||||
class DwarfToModelConverter : public BinaryImporterHelper {
|
||||
private:
|
||||
DwarfImporter &Importer;
|
||||
TupleTree<model::Binary> &Model;
|
||||
size_t Index;
|
||||
size_t AltIndex;
|
||||
size_t TypesWithIdentityCount;
|
||||
llvm::DWARFContext &Context;
|
||||
std::map<size_t, const model::TypeDefinition *> Placeholders;
|
||||
std::set<const model::TypeDefinition *> InvalidPrimitives;
|
||||
std::set<const llvm::DWARFDie *> InProgressDies;
|
||||
|
||||
public:
|
||||
DwarfToModelConverter(DwarfImporter &Importer,
|
||||
llvm::DWARFContext &Context,
|
||||
size_t Index,
|
||||
size_t AltIndex,
|
||||
uint64_t PreferredBaseAddress);
|
||||
|
||||
public:
|
||||
void run();
|
||||
|
||||
private:
|
||||
model::ABI::Values
|
||||
getABI(llvm::dwarf::CallingConvention CC = llvm::dwarf::DW_CC_normal) const;
|
||||
|
||||
const model::UpcastableType &record(const llvm::DWARFDie &Die,
|
||||
model::UpcastableType &&Type);
|
||||
|
||||
const model::UpcastableType &recordPlaceholder(const llvm::DWARFDie &Die,
|
||||
model::UpcastableType &&Type);
|
||||
|
||||
enum class TypeSearchResult {
|
||||
Absent,
|
||||
PlaceholderType,
|
||||
RegularType
|
||||
};
|
||||
|
||||
std::pair<TypeSearchResult, model::UpcastableType>
|
||||
findType(const llvm::DWARFDie &Die);
|
||||
|
||||
private:
|
||||
static bool isType(llvm::dwarf::Tag Tag);
|
||||
|
||||
static bool hasModelIdentity(llvm::dwarf::Tag Tag);
|
||||
|
||||
void createInvalidPrimitivePlaceholder(const llvm::DWARFDie &Die);
|
||||
|
||||
void createType(const llvm::DWARFDie &Die);
|
||||
|
||||
void handleTypeDeclaration(const llvm::DWARFDie &Die);
|
||||
|
||||
void materializeTypesWithIdentity();
|
||||
|
||||
std::string getName(const llvm::DWARFDie &Die) const;
|
||||
|
||||
RecursiveCoroutine<model::UpcastableType> makeType(const llvm::DWARFDie &Die);
|
||||
|
||||
RecursiveCoroutine<model::UpcastableType>
|
||||
makeTypeOrVoid(const llvm::DWARFDie &Die);
|
||||
|
||||
RecursiveCoroutine<void> resolveTypeWithIdentity(const llvm::DWARFDie &Die,
|
||||
model::UpcastableType &Type);
|
||||
|
||||
RecursiveCoroutine<model::UpcastableType>
|
||||
resolveType(const llvm::DWARFDie &Die, bool ResolveIfHasIdentity);
|
||||
|
||||
void resolveAllTypes();
|
||||
|
||||
model::UpcastableType
|
||||
getSubprogramPrototype(const llvm::DWARFDie &InitialDie);
|
||||
|
||||
void createFunctions();
|
||||
|
||||
void purgeUnresolvedPlaceholders();
|
||||
};
|
||||
@@ -8,13 +8,17 @@
|
||||
#include "llvm/Support/Process.h"
|
||||
|
||||
#include "revng/Support/Assert.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/ProgramRunner.h"
|
||||
|
||||
inline int runFetchDebugInfo(llvm::StringRef InputFileName, bool Verbose) {
|
||||
inline Logger DILogger("dwarf-importer");
|
||||
|
||||
inline int runFetchDebugInfo(llvm::StringRef FullPath, bool Verbose) {
|
||||
if (llvm::sys::Process::GetEnv("REVNG_NO_FETCH_DEBUG_INFO"))
|
||||
return 1;
|
||||
|
||||
revng_assert(::Runner.isProgramAvailable("revng"));
|
||||
std::vector<std::string> Args{ "model",
|
||||
"fetch-debuginfo",
|
||||
InputFileName.str() };
|
||||
std::vector<std::string> Args{ "model", "fetch-debuginfo", FullPath.str() };
|
||||
if (Verbose)
|
||||
Args.insert(Args.begin(), "--verbose");
|
||||
return ::Runner.run("revng", Args);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,721 @@
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/DebugInfo/CodeView/CVSymbolVisitor.h"
|
||||
#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
|
||||
#include "llvm/DebugInfo/CodeView/SymbolDeserializer.h"
|
||||
#include "llvm/DebugInfo/CodeView/SymbolVisitorCallbackPipeline.h"
|
||||
#include "llvm/DebugInfo/CodeView/SymbolVisitorCallbacks.h"
|
||||
#include "llvm/DebugInfo/PDB/Native/DbiStream.h"
|
||||
#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
|
||||
|
||||
#include "revng/Model/CABIFunctionDefinition.h"
|
||||
#include "revng/Model/Pass/AllPasses.h"
|
||||
#include "revng/Model/PrimitiveType.h"
|
||||
#include "revng/Model/Processing.h"
|
||||
#include "revng/Model/TypedefDefinition.h"
|
||||
#include "revng/Support/OverflowSafeInt.h"
|
||||
|
||||
#include "PDBImporterImpl.h"
|
||||
#include "PDBTypeResolver.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::codeview;
|
||||
using namespace llvm::pdb;
|
||||
|
||||
void PDBImporterImpl::populateSymbolsWithTypes() {
|
||||
revng_log(Log, "populateSymbolsWithTypes");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
for (ProcSym &ProcedureSymbol : Symbols.ProcSymRecords)
|
||||
handleProcedureSymbol(ProcedureSymbol);
|
||||
|
||||
for (auto &[Index, Function] : FunctionRecords.FuncIdRecords) {
|
||||
auto Name = Function.getName().str();
|
||||
|
||||
// We use FuncIdRecords only to harvest the type of dynamic symbols.
|
||||
// The list also contains local symbols, but we already know about them from
|
||||
// ProcSymRecords, so we skip them to avoid defining both a local and
|
||||
// dynamic symbol for a certain string.
|
||||
if (LocalSymbolNames.contains(Name))
|
||||
continue;
|
||||
|
||||
if (not Model->ImportedDynamicFunctions().contains(Name)) {
|
||||
revng_log(Log,
|
||||
"Creating dynamic function " << Name << " due to FuncIdRecord "
|
||||
<< toString(Index));
|
||||
}
|
||||
|
||||
auto &DynamicFunction = Model->ImportedDynamicFunctions()[Name];
|
||||
if (auto *Type = tryGetType(Function.getFunctionType()))
|
||||
DynamicFunction.Prototype() = Type->copy();
|
||||
}
|
||||
}
|
||||
|
||||
class TypeRecordsCollector : public llvm::codeview::TypeVisitorCallbacks {
|
||||
public:
|
||||
using TypeIndex = llvm::codeview::TypeIndex;
|
||||
using CVType = llvm::codeview::CVType;
|
||||
|
||||
private:
|
||||
Records &Records;
|
||||
TypeIndex CurrentTypeIndex = TypeIndex::None();
|
||||
|
||||
public:
|
||||
TypeRecordsCollector(struct Records &Records, TypeIndex FirstTypeIndex) :
|
||||
Records(Records), CurrentTypeIndex(FirstTypeIndex) {}
|
||||
|
||||
public:
|
||||
llvm::Error visitTypeBegin(llvm::codeview::CVType &Record) override {
|
||||
revng_log(Log, "visitType " << toString(CurrentTypeIndex));
|
||||
Log.indent();
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Error visitTypeBegin(CVType &Record, TypeIndex TI) override {
|
||||
revng_abort("We don't expect this to be called");
|
||||
}
|
||||
|
||||
llvm::Error visitTypeEnd(CVType &Record) override {
|
||||
Log.unindent();
|
||||
++CurrentTypeIndex;
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
#define VISIT_RECORD(Name) \
|
||||
llvm::Error visitKnownRecord(CVType &Record, llvm::codeview::Name &Object) \
|
||||
override { \
|
||||
revng_log(Log, "Registering " #Name); \
|
||||
registerObject(Records.Name##s, std::move(Object)); \
|
||||
return llvm::Error::success(); \
|
||||
}
|
||||
|
||||
VISIT_RECORD(ClassRecord);
|
||||
VISIT_RECORD(EnumRecord);
|
||||
VISIT_RECORD(ProcedureRecord);
|
||||
VISIT_RECORD(MemberFunctionRecord);
|
||||
VISIT_RECORD(UnionRecord);
|
||||
VISIT_RECORD(ArgListRecord);
|
||||
VISIT_RECORD(PointerRecord);
|
||||
VISIT_RECORD(ModifierRecord);
|
||||
VISIT_RECORD(ArrayRecord);
|
||||
VISIT_RECORD(BitFieldRecord);
|
||||
VISIT_RECORD(AliasRecord);
|
||||
|
||||
VISIT_RECORD(FuncIdRecord);
|
||||
|
||||
#undef VISIT_RECORD
|
||||
|
||||
llvm::Error
|
||||
visitKnownRecord(CVType &Record,
|
||||
llvm::codeview::FieldListRecord &FieldList) override {
|
||||
revng_log(Log,
|
||||
"Visiting FieldListRecord (size: " << FieldList.Data.size()
|
||||
<< ")");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// We don't know what's going to contain
|
||||
Records.EnumeratorRecords.changeKey(CurrentTypeIndex);
|
||||
Records.DataMemberRecords.changeKey(CurrentTypeIndex);
|
||||
|
||||
if (auto Error = visitMemberRecordStream(FieldList.Data, *this)) {
|
||||
std::string Message = llvm::toString(std::move(Error));
|
||||
revng_log(Log, "Warning: failed to visit FieldListRecord: " << Message);
|
||||
}
|
||||
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
#define VISIT_MEMBER(Name) \
|
||||
llvm::Error visitKnownMember(llvm::codeview::CVMemberRecord &Record, \
|
||||
llvm::codeview::Name &Object) override { \
|
||||
revng_log(Log, "Registering " #Name); \
|
||||
Records.Name##s.pushBack(std::move(Object)); \
|
||||
return llvm::Error::success(); \
|
||||
}
|
||||
|
||||
VISIT_MEMBER(EnumeratorRecord);
|
||||
VISIT_MEMBER(DataMemberRecord);
|
||||
|
||||
#undef VISIT_MEMBER
|
||||
|
||||
private:
|
||||
template<typename T>
|
||||
void registerObject(Records::TypeList<T> &ObjectList, T &&Object) {
|
||||
if (Records.RecordsByIndex.count(CurrentTypeIndex) != 0) {
|
||||
revng_log(Log,
|
||||
"Warning: type with index " << toString(CurrentTypeIndex)
|
||||
<< " already registered, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
ObjectList.push_back({ CurrentTypeIndex, std::move(Object) });
|
||||
Records.RecordsByIndex[CurrentTypeIndex] = &ObjectList.back().second;
|
||||
}
|
||||
};
|
||||
|
||||
class SymbolsCollector : public llvm::codeview::SymbolVisitorCallbacks {
|
||||
public:
|
||||
using CVSymbol = llvm::codeview::CVSymbol;
|
||||
|
||||
private:
|
||||
Symbols &Symbols;
|
||||
|
||||
public:
|
||||
SymbolsCollector(struct Symbols &Symbols) : Symbols(Symbols) {}
|
||||
|
||||
public:
|
||||
llvm::Error visitKnownRecord(CVSymbol &Record,
|
||||
llvm::codeview::Compile2Sym &Object) override {
|
||||
Symbols.Compile2Records.push_back(std::move(Object));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Error visitKnownRecord(CVSymbol &Record,
|
||||
llvm::codeview::Compile3Sym &Object) override {
|
||||
Symbols.Compile3Records.push_back(std::move(Object));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Error visitKnownRecord(CVSymbol &Record,
|
||||
llvm::codeview::UDTSym &Object) override {
|
||||
Symbols.UDTSymRecords.push_back(std::move(Object));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
|
||||
llvm::Error visitKnownRecord(CVSymbol &Record,
|
||||
llvm::codeview::ProcSym &Object) override {
|
||||
Symbols.ProcSymRecords.push_back(std::move(Object));
|
||||
return llvm::Error::success();
|
||||
}
|
||||
};
|
||||
|
||||
void PDBImporterImpl::collectRecords() {
|
||||
revng_log(Log, "Running collectRecords");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
auto &PDBFile = *Importer.getPDBFile();
|
||||
|
||||
auto VisitStream = [](Expected<TpiStream &> MaybeStream, Records &Records) {
|
||||
if (not MaybeStream) {
|
||||
std::string Message = llvm::toString(MaybeStream.takeError());
|
||||
revng_log(Log, "Failed to get stream: " << Message);
|
||||
return;
|
||||
}
|
||||
|
||||
TypeRecordsCollector Collector(Records,
|
||||
TypeIndex(MaybeStream->TypeIndexBegin()));
|
||||
|
||||
bool HadError = false;
|
||||
auto Result = visitTypeStream(MaybeStream->types(&HadError), Collector);
|
||||
if (Result) {
|
||||
std::string Message = llvm::toString(std::move(Result));
|
||||
revng_log(Log, "Failure visiting the type stream: " << Message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (HadError) {
|
||||
revng_log(Log, "TPISream::types reported an error");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
revng_log(Log, "Visiting Tpi stream");
|
||||
LoggerIndent Indent(Log);
|
||||
VisitStream(PDBFile.getPDBTpiStream(), TypeRecords);
|
||||
}
|
||||
{
|
||||
revng_log(Log, "Visiting Ipi stream");
|
||||
LoggerIndent Indent(Log);
|
||||
VisitStream(PDBFile.getPDBIpiStream(), FunctionRecords);
|
||||
}
|
||||
|
||||
TypeRecords.finalize();
|
||||
|
||||
SymbolsCollector CompileSymbolsVisitor(Symbols);
|
||||
visitSymbols(CompileSymbolsVisitor);
|
||||
}
|
||||
|
||||
static model::Architecture::Values
|
||||
toModelArchitecture(llvm::codeview::CPUType Type) {
|
||||
using namespace llvm::codeview;
|
||||
switch (Type) {
|
||||
case CPUType::Intel8080:
|
||||
case CPUType::Intel8086:
|
||||
case CPUType::Intel80286:
|
||||
case CPUType::Intel80386:
|
||||
case CPUType::Intel80486:
|
||||
case CPUType::Pentium:
|
||||
case CPUType::PentiumPro:
|
||||
case CPUType::Pentium3:
|
||||
return model::Architecture::x86;
|
||||
|
||||
case CPUType::X64:
|
||||
return model::Architecture::x86_64;
|
||||
|
||||
case CPUType::ARM3:
|
||||
case CPUType::ARM4:
|
||||
case CPUType::ARM4T:
|
||||
case CPUType::ARM5:
|
||||
case CPUType::ARM5T:
|
||||
case CPUType::ARM6:
|
||||
case CPUType::ARM_XMAC:
|
||||
case CPUType::ARM_WMMX:
|
||||
case CPUType::ARM7:
|
||||
case CPUType::Thumb:
|
||||
case CPUType::ARMNT:
|
||||
return model::Architecture::arm;
|
||||
|
||||
case CPUType::ARM64:
|
||||
case CPUType::HybridX86ARM64:
|
||||
case CPUType::ARM64EC:
|
||||
case CPUType::ARM64X:
|
||||
return model::Architecture::aarch64;
|
||||
|
||||
default:
|
||||
return model::Architecture::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
void PDBImporterImpl::detectArchitecture() {
|
||||
revng_log(Log, "detectArchitecture");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// Identify the architecture from Compile{2,3}Sym
|
||||
model::Architecture::Values Architecture = model::Architecture::Invalid;
|
||||
|
||||
auto ProcessMachine = [&Architecture](CPUType Machine) {
|
||||
auto NewArchitecture = toModelArchitecture(Machine);
|
||||
if (Architecture == model::Architecture::Invalid) {
|
||||
Architecture = NewArchitecture;
|
||||
} else if (Architecture != NewArchitecture) {
|
||||
revng_log(Log,
|
||||
"Warning: PDB reports multiple architectures: "
|
||||
<< model::Architecture::getName(NewArchitecture));
|
||||
}
|
||||
};
|
||||
|
||||
for (auto &Compile2Symbol : Symbols.Compile2Records)
|
||||
ProcessMachine(Compile2Symbol.Machine);
|
||||
|
||||
for (auto &Compile3Symbol : Symbols.Compile3Records)
|
||||
ProcessMachine(Compile3Symbol.Machine);
|
||||
|
||||
if (Architecture == model::Architecture::Invalid)
|
||||
return;
|
||||
|
||||
auto &Model = Importer.getModel();
|
||||
if (Model->Architecture() == model::Architecture::Invalid) {
|
||||
Model->Architecture() = Architecture;
|
||||
if (Model->DefaultABI() == model::ABI::Invalid) {
|
||||
if (auto MaybeABI = model::ABI::getDefaultForPECOFF(Architecture)) {
|
||||
Model->DefaultABI() = *MaybeABI;
|
||||
}
|
||||
}
|
||||
} else if (Model->Architecture() != Architecture) {
|
||||
revng_log(Log,
|
||||
"Warning: PDB reports an unexpected architecture: "
|
||||
<< model::Architecture::getName(Architecture));
|
||||
}
|
||||
}
|
||||
|
||||
void PDBImporterImpl::preregisterTypeDefinitions() {
|
||||
using namespace model;
|
||||
|
||||
revng_log(Log, "Running preregisterTypeDefinitions");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.ClassRecords)
|
||||
if (not ProcessedTypes.hasBeenProcessed(TypeIndex))
|
||||
preregisterTypeDefinition<StructDefinition>(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.UnionRecords)
|
||||
if (not ProcessedTypes.hasBeenProcessed(TypeIndex))
|
||||
preregisterTypeDefinition<UnionDefinition>(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.EnumRecords)
|
||||
if (not ProcessedTypes.hasBeenProcessed(TypeIndex))
|
||||
preregisterTypeDefinition<EnumDefinition>(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.ProcedureRecords)
|
||||
if (not ProcessedTypes.hasBeenProcessed(TypeIndex))
|
||||
preregisterTypeDefinition<CABIFunctionDefinition>(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.MemberFunctionRecords)
|
||||
if (not ProcessedTypes.hasBeenProcessed(TypeIndex))
|
||||
preregisterTypeDefinition<CABIFunctionDefinition>(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.AliasRecords)
|
||||
if (not ProcessedTypes.hasBeenProcessed(TypeIndex))
|
||||
preregisterTypeDefinition<TypedefDefinition>(TypeIndex);
|
||||
}
|
||||
|
||||
void PDBImporterImpl::populateTypes() {
|
||||
using namespace model;
|
||||
|
||||
revng_log(Log, "Running populateTypes");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
TypeResolver Resolver(*this, Importer.getModel()->Architecture());
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.ClassRecords)
|
||||
Resolver.getTypeFor(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.UnionRecords)
|
||||
Resolver.getTypeFor(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.EnumRecords)
|
||||
Resolver.getTypeFor(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.ProcedureRecords)
|
||||
Resolver.getTypeFor(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.MemberFunctionRecords)
|
||||
Resolver.getTypeFor(TypeIndex);
|
||||
|
||||
for (auto &[TypeIndex, Object] : TypeRecords.AliasRecords)
|
||||
Resolver.getTypeFor(TypeIndex);
|
||||
}
|
||||
|
||||
void PDBImporterImpl::visitSymbols(SymbolVisitorCallbacks &Visitor) {
|
||||
auto &PDBFile = *Importer.getPDBFile();
|
||||
|
||||
auto MaybePDIStream = PDBFile.getPDBDbiStream();
|
||||
if (auto Error = MaybePDIStream.takeError()) {
|
||||
revng_log(Log, "Warning: error obtaining the PDI stream: " << Error);
|
||||
consumeError(std::move(Error));
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t ModulesCount = MaybePDIStream->modules().getModuleCount();
|
||||
|
||||
for (uint32_t StreamIndex = 0; StreamIndex < ModulesCount; ++StreamIndex) {
|
||||
revng_log(Log, "Handling stream " << StreamIndex);
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
auto MaybeDebugStream = getModuleDebugStream(PDBFile, StreamIndex);
|
||||
if (llvm::Error Error = MaybeDebugStream.takeError()) {
|
||||
std::string Message = llvm::toString(std::move(Error));
|
||||
revng_log(Log, "Warning: error reading from the PDB stream: " << Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
ModuleDebugStreamRef &DebugStream = *MaybeDebugStream;
|
||||
|
||||
SymbolVisitorCallbackPipeline Pipeline;
|
||||
SymbolDeserializer Deserializer(nullptr, CodeViewContainer::Pdb);
|
||||
|
||||
// This is not a demangler, this parses the raw bytes
|
||||
Pipeline.addCallbackToPipeline(Deserializer);
|
||||
Pipeline.addCallbackToPipeline(Visitor);
|
||||
CVSymbolVisitor Visitor(Pipeline);
|
||||
auto SS = DebugStream.getSymbolsSubstream();
|
||||
if (auto Error = Visitor.visitSymbolStream(DebugStream.getSymbolArray(),
|
||||
SS.Offset)) {
|
||||
std::string Message = llvm::toString(std::move(Error));
|
||||
revng_log(Log, "Warning: visiting the symbol stream: " << Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NameMap {
|
||||
public:
|
||||
using TypeIndex = llvm::codeview::TypeIndex;
|
||||
|
||||
public:
|
||||
enum class Status {
|
||||
NoMatches,
|
||||
OneMatch,
|
||||
MultipleMatches
|
||||
};
|
||||
|
||||
private:
|
||||
llvm::StringMap<TypeIndex> Definitions;
|
||||
|
||||
public:
|
||||
void registerName(llvm::StringRef Name, TypeIndex Index) {
|
||||
revng_assert(not Index.isNoneType());
|
||||
auto It = Definitions.find(Name);
|
||||
if (It != Definitions.end()) {
|
||||
It->second = TypeIndex::None();
|
||||
revng_log(Log, "Warning: multiple definitions of " << Name << " found");
|
||||
} else {
|
||||
Definitions[Name] = Index;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
std::pair<Status, TypeIndex> get(llvm::StringRef Name) const {
|
||||
auto It = Definitions.find(Name);
|
||||
if (It == Definitions.end())
|
||||
return { Status::NoMatches, TypeIndex::None() };
|
||||
else if (It->second.isNoneType())
|
||||
return { Status::MultipleMatches, TypeIndex::None() };
|
||||
else
|
||||
return { Status::OneMatch, It->second };
|
||||
}
|
||||
};
|
||||
|
||||
const model::UpcastableType &
|
||||
PDBImporterImpl::recordType(TypeIndex Index,
|
||||
model::UpcastableType &&Result,
|
||||
model::TypeDefinition *Definition,
|
||||
bool IsSizeAvailable) {
|
||||
using namespace llvm;
|
||||
using namespace model;
|
||||
|
||||
auto It = Typedefs.find(Index);
|
||||
bool IsTypedefd = It != Typedefs.end() and It->second != nullptr;
|
||||
|
||||
// Ensure that if we already processed this type, we processed exactly the
|
||||
// same type
|
||||
if (const auto *Entry = ProcessedTypes.tryGetEntry(Index)) {
|
||||
if (Result == Entry->Type) {
|
||||
revng_log(Log,
|
||||
toString(Index) << " was already resolved with an identical "
|
||||
"type");
|
||||
} else if (IsTypedefd) {
|
||||
auto *Defined = cast<DefinedType>(Entry->Type.get());
|
||||
auto *Typedef = cast<TypedefDefinition>(Defined->Definition().get());
|
||||
revng_assert(Typedef->UnderlyingType() == Result,
|
||||
(toString(Index)
|
||||
+ " was already resolved with a different type")
|
||||
.c_str());
|
||||
} else {
|
||||
revng_abort((toString(Index)
|
||||
+ " was already resolved with a different type")
|
||||
.c_str());
|
||||
}
|
||||
|
||||
// Upgrade SizeAvailable if the caller has now ensured children are
|
||||
// size-available.
|
||||
if (IsSizeAvailable and not Entry->IsSizeAvailable)
|
||||
ProcessedTypes.markSizeAvailable(Index);
|
||||
|
||||
return Entry->Type;
|
||||
}
|
||||
|
||||
if (IsTypedefd) {
|
||||
revng_log(Log, "Using typedef " << It->second->Name.str());
|
||||
// This type is associated to one and only one typedef.
|
||||
// Emit the typedef on the fly.
|
||||
auto [UDTTypedef, Type] = Model->makeTypeDefinition<TypedefDefinition>();
|
||||
UDTTypedef.Name() = It->second->Name;
|
||||
UDTTypedef.UnderlyingType() = std::move(Result);
|
||||
Result = std::move(Type);
|
||||
// The Definition pointer (if provided) still points to the inner
|
||||
// TypeDefinition to be populated by processDefinition; size-availability
|
||||
// of the outer wrapper follows from the inner.
|
||||
}
|
||||
|
||||
return ProcessedTypes
|
||||
.record(Index, std::move(Result), Definition, IsSizeAvailable)
|
||||
.Type;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void PDBImporterImpl::resolveForwardReferences(StringRef Name,
|
||||
Records::TypeList<T>
|
||||
&ObjectList) {
|
||||
revng_log(Log, "Resolving forward declarations for " << Name.str());
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
NameMap UniqueNamesMap;
|
||||
NameMap RegularNamesMap;
|
||||
|
||||
// Collect all the full definitions
|
||||
for (auto &[Index, Object] : ObjectList) {
|
||||
if (Object.isForwardRef())
|
||||
continue;
|
||||
|
||||
if (Object.hasUniqueName()) {
|
||||
UniqueNamesMap.registerName(Object.getUniqueName(), Index);
|
||||
} else {
|
||||
RegularNamesMap.registerName(Object.getName(), Index);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &[Index, Object] : ObjectList) {
|
||||
// Collect all the full definitions
|
||||
if (not Object.isForwardRef())
|
||||
continue;
|
||||
|
||||
NameMap::Status Status;
|
||||
TypeIndex Match;
|
||||
StringRef Name;
|
||||
if (Object.hasUniqueName()) {
|
||||
Name = Object.getUniqueName();
|
||||
std::tie(Status, Match) = UniqueNamesMap.get(Name);
|
||||
} else {
|
||||
Name = Object.getName();
|
||||
std::tie(Status, Match) = RegularNamesMap.get(Name);
|
||||
}
|
||||
|
||||
revng_log(Log, "Considering " << toString(Index) << ", " << Name.str());
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
if (Status == NameMap::Status::NoMatches) {
|
||||
revng_log(Log, "Unmatched forward declaration, typedef'ing to void");
|
||||
// TODO: maybe we could create an empty enum here, in case of a enum
|
||||
auto [Definition,
|
||||
Type] = registerTypeDefinition<model::TypedefDefinition>(Index);
|
||||
Definition.Name() = Name;
|
||||
Definition.UnderlyingType() = model::PrimitiveType::makeVoid();
|
||||
// Definition is now fully populated (UnderlyingType set to void).
|
||||
markSizeAvailable(Index);
|
||||
} else if (Status == NameMap::Status::MultipleMatches) {
|
||||
revng_log(Log, "Warning: ambiguous forward declaration, ignoring");
|
||||
recordType(Index, model::UpcastableType::empty(), true);
|
||||
} else {
|
||||
// Redirect to full definition
|
||||
TypeRecords.RecordsByIndex[Index] = TypeRecords.RecordsByIndex[Match];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PDBImporterImpl::loadInputFile() {
|
||||
revng_log(Log, "Running loadInputFile");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
auto Path = Importer.getPDBFile()->getFilePath();
|
||||
auto MaybeInputFile = llvm::pdb::InputFile::open(Path);
|
||||
if (not MaybeInputFile) {
|
||||
revng_log(Log,
|
||||
"Warning: unable to open PDB file "
|
||||
<< MaybeInputFile.takeError());
|
||||
consumeError(MaybeInputFile.takeError());
|
||||
return false;
|
||||
}
|
||||
|
||||
TheInput = &*MaybeInputFile;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PDBImporterImpl::createTypedefs() {
|
||||
revng_log(Log, "Running createTypedefs");
|
||||
LoggerIndent Indent(Log);
|
||||
for (llvm::codeview::UDTSym &UDTSymbol : Symbols.UDTSymRecords) {
|
||||
auto It = Typedefs.find(UDTSymbol.Type);
|
||||
if (It == Typedefs.end()) {
|
||||
Typedefs[UDTSymbol.Type] = &UDTSymbol;
|
||||
} else {
|
||||
Typedefs[UDTSymbol.Type] = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDBImporterImpl::resolveAllForwardReferences() {
|
||||
revng_log(Log, "Running resolveAllForwardReferences");
|
||||
LoggerIndent Indent(Log);
|
||||
resolveForwardReferences("ClassRecords", TypeRecords.ClassRecords);
|
||||
resolveForwardReferences("UnionRecords", TypeRecords.UnionRecords);
|
||||
resolveForwardReferences("EnumRecords", TypeRecords.EnumRecords);
|
||||
}
|
||||
|
||||
void PDBImporterImpl::run() {
|
||||
revng_log(Log, "Running PDBImporter");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
if (not loadInputFile())
|
||||
return;
|
||||
|
||||
collectRecords();
|
||||
|
||||
detectArchitecture();
|
||||
|
||||
createTypedefs();
|
||||
|
||||
resolveAllForwardReferences();
|
||||
|
||||
preregisterTypeDefinitions();
|
||||
|
||||
populateTypes();
|
||||
|
||||
// Only preregistered entries (Definition != nullptr) need to be size-
|
||||
// available: Modifier/BitField wrappers resolved behind a pointer path
|
||||
// legitimately stay IsSizeAvailable=false, but since they transitively
|
||||
// depend on preregistered definitions, checking those is sufficient.
|
||||
bool HasUnresolved = false;
|
||||
for (const auto &[Index, Entry] : ProcessedTypes) {
|
||||
if (Entry.Definition != nullptr and not Entry.IsSizeAvailable) {
|
||||
if (not HasUnresolved) {
|
||||
dbg << "The following TypeDefinitions were not resolved:\n";
|
||||
HasUnresolved = true;
|
||||
}
|
||||
dbg << " " << toString(Index) << ": ";
|
||||
Entry.Type->dump();
|
||||
}
|
||||
}
|
||||
|
||||
if (HasUnresolved)
|
||||
revng_abort();
|
||||
|
||||
populateSymbolsWithTypes();
|
||||
|
||||
revng_log(Log, "dropTypesDependingOnDefinitions");
|
||||
dropTypesDependingOnDefinitions(Model, ToDrop);
|
||||
|
||||
revng_log(Log, "flattenPrimitiveTypedefs");
|
||||
model::flattenPrimitiveTypedefs(Model);
|
||||
|
||||
revng_log(Log, "deduplicateEquivalentTypes");
|
||||
deduplicateEquivalentTypes(Model);
|
||||
|
||||
revng_log(Log, "deduplicateCollidingNames");
|
||||
model::deduplicateCollidingNames(Model);
|
||||
|
||||
revng_log(Log, "purgeUnreachableTypes");
|
||||
purgeUnreachableTypes(Model);
|
||||
|
||||
revng_log(Log, "purgeInvalidTypes");
|
||||
model::purgeInvalidTypes(Model);
|
||||
|
||||
revng_assert(Model->verify(true));
|
||||
}
|
||||
|
||||
void PDBImporterImpl::handleProcedureSymbol(ProcSym &Procedure) {
|
||||
TypeIndex FunctionTypeIndex = Procedure.FunctionType;
|
||||
|
||||
revng_log(Log,
|
||||
"Importing " << Procedure.Name << " with type "
|
||||
<< toString(FunctionTypeIndex));
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// If it is not in the .idata already, we assume it is a static symbol.
|
||||
auto &Model = Importer.getModel();
|
||||
|
||||
LocalSymbolNames.insert(Procedure.Name);
|
||||
|
||||
uint64_t FunctionVirtualAddress = Importer.getNativeSession()
|
||||
->getRVAFromSectOffset(Procedure.Segment,
|
||||
Procedure
|
||||
.CodeOffset);
|
||||
|
||||
if (not Importer.isFunctionAllowed(FunctionVirtualAddress)) {
|
||||
revng_log(Log, "Ignoring disallowed function");
|
||||
return;
|
||||
}
|
||||
|
||||
// Relocate the symbol.
|
||||
MetaAddress FunctionAddress = Importer.toPC(Importer.getBaseAddress()
|
||||
+ FunctionVirtualAddress);
|
||||
|
||||
if (not Model->Functions().contains(FunctionAddress)) {
|
||||
if (auto *Function = Importer.registerFunctionEntry(FunctionAddress)) {
|
||||
revng_log(Log, "New function registered");
|
||||
Function->Name() = Procedure.Name;
|
||||
if (auto *Type = tryGetType(FunctionTypeIndex))
|
||||
Function->Prototype() = Type->copy();
|
||||
}
|
||||
} else {
|
||||
auto It = Model->Functions().find(FunctionAddress);
|
||||
if (auto *Type = tryGetType(FunctionTypeIndex)) {
|
||||
revng_log(Log, "Updating prototype");
|
||||
It->Prototype() = Type->copy();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle Imported functions
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/ADT/StringSet.h"
|
||||
#include "llvm/DebugInfo/CodeView/SymbolRecord.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeIndex.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeRecord.h"
|
||||
|
||||
#include "revng/ADT/IndexedVector.h"
|
||||
#include "revng/Model/Importer/DebugInfo/PDBImporter.h"
|
||||
#include "revng/Model/Type.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
|
||||
namespace llvm::codeview {
|
||||
class SymbolVisitorCallbacks;
|
||||
}
|
||||
|
||||
inline Logger Log("pdb-importer");
|
||||
|
||||
inline std::string toString(llvm::codeview::TypeIndex Index) {
|
||||
return "0x" + llvm::utohexstr(Index.getIndex(), true);
|
||||
}
|
||||
|
||||
struct Records {
|
||||
using TypeRecord = llvm::codeview::TypeRecord;
|
||||
using TypeIndex = llvm::codeview::TypeIndex;
|
||||
|
||||
template<std::derived_from<TypeRecord> T>
|
||||
using TypeList = std::deque<std::pair<TypeIndex, T>>;
|
||||
|
||||
llvm::DenseMap<TypeIndex, TypeRecord *> RecordsByIndex;
|
||||
|
||||
TypeList<llvm::codeview::ClassRecord> ClassRecords;
|
||||
TypeList<llvm::codeview::EnumRecord> EnumRecords;
|
||||
TypeList<llvm::codeview::UnionRecord> UnionRecords;
|
||||
|
||||
TypeList<llvm::codeview::ProcedureRecord> ProcedureRecords;
|
||||
TypeList<llvm::codeview::MemberFunctionRecord> MemberFunctionRecords;
|
||||
|
||||
// Note: the following don' really need the TypeIndex, we never go through
|
||||
// them linearly.
|
||||
TypeList<llvm::codeview::ArgListRecord> ArgListRecords;
|
||||
TypeList<llvm::codeview::FieldListRecord> FieldListRecords;
|
||||
TypeList<llvm::codeview::PointerRecord> PointerRecords;
|
||||
TypeList<llvm::codeview::ModifierRecord> ModifierRecords;
|
||||
TypeList<llvm::codeview::ArrayRecord> ArrayRecords;
|
||||
TypeList<llvm::codeview::FuncIdRecord> FuncIdRecords;
|
||||
TypeList<llvm::codeview::BitFieldRecord> BitFieldRecords;
|
||||
TypeList<llvm::codeview::AliasRecord> AliasRecords;
|
||||
|
||||
IndexedVector<TypeIndex, llvm::codeview::EnumeratorRecord> EnumeratorRecords;
|
||||
IndexedVector<TypeIndex, llvm::codeview::DataMemberRecord> DataMemberRecords;
|
||||
|
||||
void finalize() {
|
||||
EnumeratorRecords.finalize();
|
||||
DataMemberRecords.finalize();
|
||||
}
|
||||
};
|
||||
|
||||
struct Symbols {
|
||||
std::vector<llvm::codeview::Compile2Sym> Compile2Records;
|
||||
std::vector<llvm::codeview::Compile3Sym> Compile3Records;
|
||||
std::vector<llvm::codeview::ProcSym> ProcSymRecords;
|
||||
std::vector<llvm::codeview::UDTSym> UDTSymRecords;
|
||||
};
|
||||
|
||||
class ProcessedTypeMap {
|
||||
public:
|
||||
using TypeIndex = llvm::codeview::TypeIndex;
|
||||
|
||||
struct Entry {
|
||||
model::UpcastableType Type;
|
||||
|
||||
/// The TypeDefinition that was pre-registered for this TypeIndex, if any.
|
||||
///
|
||||
/// This is the definition processDefinition should populate. It is not
|
||||
/// necessarily the same as Type->skipToDefinition(): when recordType wraps
|
||||
/// Type in an outer UDT typedef, skipToDefinition returns the wrapper, not
|
||||
/// the inner definition preregisterTypeDefinition created.
|
||||
model::TypeDefinition *Definition = nullptr;
|
||||
|
||||
/// True if and only if calling size() on Type is guaranteed to succeed.
|
||||
bool IsSizeAvailable = false;
|
||||
};
|
||||
|
||||
private:
|
||||
// Note: it would be nice to use a more compact data structure such as a
|
||||
// DenseMap but we need the pointer to the value to be stable.
|
||||
std::map<TypeIndex, Entry> Map;
|
||||
|
||||
public:
|
||||
Entry *tryGetEntry(TypeIndex Index) {
|
||||
auto It = Map.find(Index);
|
||||
if (It != Map.end())
|
||||
return &It->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
model::UpcastableType *tryGetType(TypeIndex Index) {
|
||||
if (auto *E = tryGetEntry(Index))
|
||||
return &E->Type;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool hasBeenProcessed(TypeIndex Index) const { return Map.contains(Index); }
|
||||
|
||||
auto begin() const { return Map.begin(); }
|
||||
auto end() const { return Map.end(); }
|
||||
|
||||
public:
|
||||
Entry &record(TypeIndex Index,
|
||||
model::UpcastableType &&Type,
|
||||
model::TypeDefinition *Definition,
|
||||
bool SizeAvailable) {
|
||||
auto [It, Inserted] = Map.insert({ Index,
|
||||
Entry{ std::move(Type),
|
||||
Definition,
|
||||
SizeAvailable } });
|
||||
revng_assert(Inserted);
|
||||
return It->second;
|
||||
}
|
||||
|
||||
void markSizeAvailable(TypeIndex Index) {
|
||||
auto It = Map.find(Index);
|
||||
revng_assert(It != Map.end());
|
||||
It->second.IsSizeAvailable = true;
|
||||
}
|
||||
};
|
||||
|
||||
class PDBImporterImpl {
|
||||
public:
|
||||
using TypeIndex = llvm::codeview::TypeIndex;
|
||||
|
||||
private:
|
||||
PDBImporter &Importer;
|
||||
|
||||
TupleTree<model::Binary> &Model;
|
||||
|
||||
llvm::pdb::InputFile *TheInput = nullptr;
|
||||
|
||||
/// A copy of everything in the TPI stream
|
||||
Records TypeRecords;
|
||||
|
||||
/// A copy of everything in the IPI stream
|
||||
Records FunctionRecords;
|
||||
|
||||
/// A copy of everything in the DBI stream
|
||||
Symbols Symbols;
|
||||
|
||||
/// A list of all the processed types
|
||||
///
|
||||
/// \note This might contain DefinedTypes that are not fully processed yet.
|
||||
ProcessedTypeMap ProcessedTypes;
|
||||
|
||||
/// A set of invalid TypeDefinition that we'll need to purge at the end
|
||||
std::set<const model::TypeDefinition *> ToDrop;
|
||||
|
||||
/// A list of UDT typedefs
|
||||
///
|
||||
/// These are used to create a typedef for each TypeIndex that is referenced
|
||||
/// from one (and only one) UDT.
|
||||
///
|
||||
/// Note that we also produce `typedef`s via handle AliasRecord, which is a
|
||||
/// more idiomatic, albeit less used, way of representing them.
|
||||
llvm::DenseMap<TypeIndex, llvm::codeview::UDTSym *> Typedefs;
|
||||
|
||||
/// Set of names of symbols that we now to be local. All the others will be
|
||||
/// considered dynamic.
|
||||
llvm::StringSet<> LocalSymbolNames;
|
||||
|
||||
public:
|
||||
PDBImporterImpl(PDBImporter &Importer) :
|
||||
Importer(Importer), Model(Importer.getModel()) {}
|
||||
|
||||
public:
|
||||
void run();
|
||||
|
||||
private:
|
||||
bool loadInputFile();
|
||||
|
||||
void collectRecords();
|
||||
|
||||
void detectArchitecture();
|
||||
|
||||
void createTypedefs();
|
||||
|
||||
void resolveAllForwardReferences();
|
||||
|
||||
void preregisterTypeDefinitions();
|
||||
|
||||
void populateTypes();
|
||||
|
||||
void populateSymbolsWithTypes();
|
||||
|
||||
public:
|
||||
template<std::derived_from<model::TypeDefinition> T>
|
||||
void preregisterTypeDefinition(TypeIndex TypeIndex) {
|
||||
revng_log(Log,
|
||||
"Recording "
|
||||
<< toString(TypeIndex) << " as "
|
||||
<< model::TypeDefinitionKind::getName(T::AssociatedKind));
|
||||
registerTypeDefinition<T>(TypeIndex);
|
||||
}
|
||||
|
||||
template<std::derived_from<model::TypeDefinition> T>
|
||||
std::pair<T &, const model::UpcastableType &>
|
||||
registerTypeDefinition(TypeIndex TypeIndex) {
|
||||
auto [Definition, Type] = Model->makeTypeDefinition<T>();
|
||||
const auto &CachedType = recordType(TypeIndex,
|
||||
std::move(Type),
|
||||
&Definition,
|
||||
false);
|
||||
return { Definition, CachedType };
|
||||
}
|
||||
|
||||
void registerInvalidDefinition(const model::TypeDefinition *Definition) {
|
||||
ToDrop.insert(Definition);
|
||||
}
|
||||
|
||||
const model::UpcastableType &recordType(TypeIndex Index,
|
||||
model::UpcastableType &&Result,
|
||||
bool SizeAvailable) {
|
||||
return recordType(Index, std::move(Result), nullptr, SizeAvailable);
|
||||
}
|
||||
|
||||
const model::UpcastableType &recordType(TypeIndex Index,
|
||||
model::UpcastableType &&Result,
|
||||
model::TypeDefinition *Definition,
|
||||
bool SizeAvailable);
|
||||
|
||||
ProcessedTypeMap::Entry *tryGetEntry(TypeIndex Index) {
|
||||
return ProcessedTypes.tryGetEntry(Index);
|
||||
}
|
||||
|
||||
model::UpcastableType *tryGetType(TypeIndex Index) {
|
||||
return ProcessedTypes.tryGetType(Index);
|
||||
}
|
||||
|
||||
void markSizeAvailable(TypeIndex Index) {
|
||||
ProcessedTypes.markSizeAvailable(Index);
|
||||
}
|
||||
|
||||
Records::TypeRecord *getTypeRecord(TypeIndex Index) {
|
||||
auto It = TypeRecords.RecordsByIndex.find(Index);
|
||||
if (It == TypeRecords.RecordsByIndex.end())
|
||||
return nullptr;
|
||||
return It->second;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T *getTypeRecord(TypeIndex Index) {
|
||||
auto It = TypeRecords.RecordsByIndex.find(Index);
|
||||
if (It == TypeRecords.RecordsByIndex.end())
|
||||
return nullptr;
|
||||
|
||||
llvm::codeview::TypeRecordKind Kind = It->second->Kind;
|
||||
|
||||
if constexpr (false) {
|
||||
#define TYPE_RECORD_ALIAS(lf_ename, value, name, alias_name)
|
||||
#define MEMBER_RECORD(lf_ename, value, name)
|
||||
#define TYPE_RECORD(lf_ename, value, name) \
|
||||
} \
|
||||
else if constexpr (std::is_same_v<T, llvm::codeview::name##Record>) { \
|
||||
if (Kind != llvm::codeview::TypeRecordKind::name) \
|
||||
return nullptr;
|
||||
#include "llvm/DebugInfo/CodeView/CodeViewTypes.def"
|
||||
#undef TYPE_RECORD
|
||||
#undef TYPE_RECORD_ALIAS
|
||||
#undef MEMBER_RECORD
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return reinterpret_cast<T *>(It->second);
|
||||
}
|
||||
|
||||
auto getDataMemberRecords(TypeIndex Index) {
|
||||
return TypeRecords.DataMemberRecords.getRange(Index);
|
||||
}
|
||||
|
||||
auto getEnumeratorRecords(TypeIndex Index) {
|
||||
return TypeRecords.EnumeratorRecords.getRange(Index);
|
||||
}
|
||||
|
||||
private:
|
||||
void visitSymbols(llvm::codeview::SymbolVisitorCallbacks &Visitor);
|
||||
|
||||
template<typename T>
|
||||
void resolveForwardReferences(llvm::StringRef Name,
|
||||
Records::TypeList<T> &ObjectList);
|
||||
|
||||
void handleProcedureSymbol(llvm::codeview::ProcSym &Procedure);
|
||||
};
|
||||
@@ -0,0 +1,828 @@
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "revng/ADT/ScopedExchange.h"
|
||||
#include "revng/Model/ArrayType.h"
|
||||
#include "revng/Model/CABIFunctionDefinition.h"
|
||||
#include "revng/Model/EnumDefinition.h"
|
||||
#include "revng/Model/PointerType.h"
|
||||
#include "revng/Model/PrimitiveType.h"
|
||||
#include "revng/Model/StructDefinition.h"
|
||||
#include "revng/Model/TypedefDefinition.h"
|
||||
#include "revng/Model/UnionDefinition.h"
|
||||
#include "revng/Support/OverflowSafeInt.h"
|
||||
|
||||
#include "PDBTypeResolver.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::codeview;
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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:
|
||||
case SimpleTypeKind::HResult:
|
||||
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;
|
||||
}
|
||||
|
||||
const model::UpcastableType *TypeResolver::handleSimpleType(TypeIndex Index) {
|
||||
using namespace model;
|
||||
|
||||
revng_log(Log, "handleSimpleType");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
if (isTwoBytesLongPointer(Index)) {
|
||||
// If it is a pointer of size 2, lets create a PointerOrNumber for it.
|
||||
constexpr uint64_t MSDOS16Pointer = 2;
|
||||
return record(Index,
|
||||
PrimitiveType::makePointerOrNumber(MSDOS16Pointer),
|
||||
true);
|
||||
|
||||
} else if (isSixteenBytesLongPointer(Index)) {
|
||||
// If it is a 128-bit long pointer, fail for now. It can be
|
||||
// represented as a `struct { pointee; offset; }` since it is how it is
|
||||
// implemented in the msvc compiler.
|
||||
return fail(Index);
|
||||
} else {
|
||||
auto PrimitiveKind = codeviewSimpleTypeEncodingToModel(Index);
|
||||
auto PrimitiveSize = getSizeInBytes(Index);
|
||||
if (not PrimitiveSize.has_value()
|
||||
or PrimitiveKind == PrimitiveKind::Invalid) {
|
||||
revng_log(Log,
|
||||
"Warning: invalid simple type "
|
||||
<< toString(Index) << " with simple kind "
|
||||
<< static_cast<uint32_t>(Index.getSimpleKind()));
|
||||
return fail(Index);
|
||||
}
|
||||
|
||||
auto Primitive = PrimitiveType::make(PrimitiveKind, *PrimitiveSize);
|
||||
|
||||
if (isPointer(Index)) {
|
||||
auto PointerSize = getPointerSizeFromPDB(Index);
|
||||
if (not PointerSize.has_value()) {
|
||||
revng_log(Log, "Warning: invalid pointer size " << toString(Index));
|
||||
return fail(Index);
|
||||
}
|
||||
|
||||
auto Pointer = model::PointerType::make(std::move(Primitive),
|
||||
*PointerSize);
|
||||
return record(Index, std::move(Pointer), true);
|
||||
} else {
|
||||
// If it is not a pointer `SimpleKind` will be the same as `SimpleType`.
|
||||
revng_assert(TypeIndex(Index.getSimpleKind()) == Index);
|
||||
return record(Index, std::move(Primitive), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
TypeResolver::handle(TypeIndex Index, PointerRecord &Pointer) {
|
||||
revng_log(Log, "Handling PointerRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
auto PointeeIndex = Pointer.getReferentType();
|
||||
|
||||
// Note: we set NeedsSize to false because we don't need the size here,
|
||||
// therefore we're OK with non-processed TypeDefinition. This enables
|
||||
// recursion via pointers.
|
||||
const model::UpcastableType *Pointee = nullptr;
|
||||
{
|
||||
ScopedExchange<bool> Guard(NeedsSize, false);
|
||||
Pointee = rc_recur getTypeForImpl(PointeeIndex);
|
||||
}
|
||||
revng_assert(Pointee != nullptr);
|
||||
|
||||
if (Pointee->isEmpty()) {
|
||||
revng_log(Log, "Pointee type " << toString(PointeeIndex) << " not found");
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
using PointerType = model::PointerType;
|
||||
auto PointerSize = getPointerSize(Pointer.getPointerKind());
|
||||
rc_return record(Index,
|
||||
PointerType::make(Pointee->copy(), PointerSize),
|
||||
true);
|
||||
}
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
TypeResolver::handle(TypeIndex Index, BitFieldRecord &BitField) {
|
||||
revng_log(Log, "Handling BitFieldRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// As of now we treat bitfields as their underlying type
|
||||
|
||||
auto UnderlyingIndex = BitField.getType();
|
||||
const auto &UnderlyingType = *rc_recur getTypeForImpl(UnderlyingIndex);
|
||||
|
||||
if (UnderlyingType.isEmpty()) {
|
||||
revng_log(Log,
|
||||
"Warning: underlying type " << toString(UnderlyingIndex)
|
||||
<< " not found");
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
// A bitfield's size follows its underlying: only size-available if the
|
||||
// underlying was fetched with NeedsSize=true, i.e., the current NeedsSize.
|
||||
rc_return record(Index, UnderlyingType.copy(), NeedsSize);
|
||||
}
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
TypeResolver::handle(TypeIndex Index, ModifierRecord &Modifier) {
|
||||
revng_log(Log, "Handling ModifierRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
const auto &Modified = *rc_recur getTypeForImpl(Modifier.getModifiedType());
|
||||
|
||||
if (Modified.isEmpty()) {
|
||||
revng_log(Log, "Warning: modified type not found");
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
auto Result = Modified.copy();
|
||||
|
||||
using Modifiers = ModifierOptions;
|
||||
if ((Modifier.getModifiers() & Modifiers::Const) != Modifiers::None) {
|
||||
Result->IsConst() = true;
|
||||
}
|
||||
|
||||
// A modifier wraps the modified type: size is available only when the
|
||||
// modified was fetched with NeedsSize=true, i.e., the current NeedsSize.
|
||||
rc_return record(Index, std::move(Result), NeedsSize);
|
||||
}
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
TypeResolver::handle(TypeIndex Index, ArrayRecord &Array) {
|
||||
revng_log(Log, "Handling ArrayRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// We need the element's size to compute the element count, so force a
|
||||
// size-computing resolution of the element regardless of our caller's
|
||||
// NeedsSize.
|
||||
const model::UpcastableType *ElementType = nullptr;
|
||||
{
|
||||
ScopedExchange<bool> Guard(NeedsSize, true);
|
||||
ElementType = rc_recur getTypeForImpl(Array.getElementType());
|
||||
}
|
||||
revng_assert(ElementType != nullptr);
|
||||
|
||||
if (ElementType->isEmpty()) {
|
||||
revng_log(Log, "Warning: element type not found");
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
if (Array.getSize() == 0) {
|
||||
revng_log(Log, "Skipping 0-sized array");
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
auto MaybeSize = (*ElementType)->size();
|
||||
if (not MaybeSize.has_value()) {
|
||||
revng_log(Log, "Array of 0-sized elements, skipping");
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
const uint64_t ArraySize = Array.getSize() / *MaybeSize;
|
||||
rc_return record(Index,
|
||||
model::ArrayType::make(ElementType->copy(), ArraySize),
|
||||
true);
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processDefinition(ClassRecord &ClassRecord,
|
||||
model::StructDefinition &Struct) {
|
||||
revng_log(Log, "Handling ClassRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// TODO: we could process the LF_ONEMETHOD entries and build the virtual table
|
||||
|
||||
// Handle size
|
||||
Struct.Name() = ClassRecord.getName();
|
||||
Struct.Size() = ClassRecord.getSize();
|
||||
if (Struct.Size() == 0) {
|
||||
revng_log(Log, "Warning: ignoring 0-sized struct");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
// TODO: handle LF_BCLASS by simply adding a field at offset 0
|
||||
|
||||
// Process fields
|
||||
auto FieldListIndex = ClassRecord.getFieldList();
|
||||
auto MaybeFields = Importer.getDataMemberRecords(FieldListIndex);
|
||||
if (not MaybeFields.has_value()) {
|
||||
revng_log(Log,
|
||||
"The struct has no fields (" << toString(FieldListIndex)
|
||||
<< "), keeping empty struct");
|
||||
rc_return true;
|
||||
}
|
||||
|
||||
revng_log(Log, "Processing fields");
|
||||
LoggerIndent Indent2(Log);
|
||||
|
||||
for (const auto &[Index, Field] : llvm::enumerate(*MaybeFields)) {
|
||||
revng_log(Log,
|
||||
"Processing field #" << Index << " with type "
|
||||
<< toString(Field.getType()));
|
||||
LoggerIndent Indent3(Log);
|
||||
|
||||
// Create new field
|
||||
uint64_t Offset = Field.getFieldOffset();
|
||||
auto &FieldType = notNull(rc_recur getTypeForImpl(Field.getType()));
|
||||
if (FieldType.isEmpty()) {
|
||||
revng_log(Log,
|
||||
"Field type not found: " << toString(Field.getType())
|
||||
<< ", skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto MaybeSize = FieldType->size();
|
||||
uint64_t Size = MaybeSize.value_or(0);
|
||||
if (Size == 0) {
|
||||
// Skip 0-sized field.
|
||||
revng_log(Log, "Skipping 0-sized field");
|
||||
continue;
|
||||
}
|
||||
|
||||
OverflowSafeInt<uint64_t> CurrentFieldOffset = Offset;
|
||||
CurrentFieldOffset += Size;
|
||||
if (not CurrentFieldOffset) {
|
||||
revng_log(Log, "Warning: skipping struct field due to overflow.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*CurrentFieldOffset > Struct.Size()) {
|
||||
revng_log(Log,
|
||||
"Warning: skipping struct field that is outside the struct.");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &NewField = Struct.Fields()[Offset];
|
||||
NewField.Name() = Field.getName().str();
|
||||
NewField.Type() = FieldType.copy();
|
||||
}
|
||||
|
||||
rc_return true;
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processDefinition(UnionRecord &UnionRecord,
|
||||
model::UnionDefinition &Union) {
|
||||
revng_log(Log, "Handling UnionRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
Union.Name() = UnionRecord.getName();
|
||||
|
||||
// Process fields
|
||||
auto MaybeFields = Importer.getDataMemberRecords(UnionRecord.getFieldList());
|
||||
if (not MaybeFields.has_value()) {
|
||||
revng_log(Log, "Warning: couldn't get fields");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
if (MaybeFields->size() == 0) {
|
||||
revng_log(Log, "Warning: union has no fields");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
for (const auto &[Index, Field] : llvm::enumerate(*MaybeFields)) {
|
||||
revng_log(Log, "Processing field " << Index);
|
||||
LoggerIndent Indent3(Log);
|
||||
|
||||
// Create new field
|
||||
uint64_t Offset = Field.getFieldOffset();
|
||||
auto FieldType = rc_recur getTypeForImpl(Field.getType());
|
||||
if (FieldType->isEmpty()) {
|
||||
revng_log(Log, "Field type not found: " << toString(Field.getType()));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto MaybeSize = (*FieldType)->size();
|
||||
uint64_t Size = MaybeSize.value_or(0);
|
||||
if (Size == 0) {
|
||||
revng_log(Log, "Warning: skipping 0-sized field");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &NewField = Union.addField(FieldType->copy());
|
||||
NewField.Name() = Field.getName().str();
|
||||
}
|
||||
|
||||
if (Union.size() > 0) {
|
||||
rc_return true;
|
||||
} else {
|
||||
rc_return false;
|
||||
}
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processDefinition(EnumRecord &EnumRecord,
|
||||
model::EnumDefinition &Enum) {
|
||||
revng_log(Log, "Handling EnumRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
Enum.Name() = EnumRecord.getName();
|
||||
|
||||
const auto &UnderlyingType = rc_recur getTypeForImpl(EnumRecord
|
||||
.getUnderlyingType());
|
||||
if (UnderlyingType->isEmpty()) {
|
||||
revng_log(Log,
|
||||
"Underlying type not found: "
|
||||
<< toString(EnumRecord.getUnderlyingType()));
|
||||
Enum.UnderlyingType() = model::PrimitiveType::makeVoid();
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
Enum.UnderlyingType() = UnderlyingType->copy();
|
||||
|
||||
auto MaybeFields = Importer.getEnumeratorRecords(EnumRecord.getFieldList());
|
||||
if (not MaybeFields.has_value()) {
|
||||
// TODO: not nice, we're losing the name
|
||||
revng_log(Log, "No entries, ignoring");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
for (const auto &[Index, Entry] : llvm::enumerate(*MaybeFields)) {
|
||||
revng_log(Log, "Processing entry " << Index);
|
||||
LoggerIndent Indent3(Log);
|
||||
|
||||
auto &EnumEntry = Enum.Entries()[Entry.getValue().getExtValue()];
|
||||
EnumEntry.Name() = Entry.getName().str();
|
||||
}
|
||||
|
||||
rc_return true;
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processDefinition(ProcedureRecord &ProcedureRecord,
|
||||
model::CABIFunctionDefinition &Prototype) {
|
||||
revng_log(Log, "Handling ProcedureRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
rc_return rc_recur
|
||||
processFunctionDefinition(ProcedureRecord.getCallConv(),
|
||||
ProcedureRecord.getReturnType(),
|
||||
ProcedureRecord.getArgumentList(),
|
||||
Prototype);
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processDefinition(MemberFunctionRecord &MemberFunctionRecord,
|
||||
model::CABIFunctionDefinition &Prototype) {
|
||||
revng_log(Log, "Handling MemberFunctionRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// Handle this pointer
|
||||
if (MemberFunctionRecord.getThisPointerAdjustment() != 0) {
|
||||
revng_log(Log,
|
||||
"Warning: multiple virtual inheritance is not supported right "
|
||||
"now");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
auto ThisTypeIndex = MemberFunctionRecord.getThisType();
|
||||
if (not ThisTypeIndex.isNoneType()) {
|
||||
const auto &ThisType = *rc_recur getTypeForImpl(ThisTypeIndex);
|
||||
if (ThisType.isEmpty()) {
|
||||
revng_log(Log, "Warning: couldn't resolve this type");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
revng_assert(ThisType->isPointer());
|
||||
Prototype.addArgument(ThisType.copy());
|
||||
}
|
||||
|
||||
// Delegate all the rest to handleFunction
|
||||
rc_return rc_recur
|
||||
processFunctionDefinition(MemberFunctionRecord.getCallConv(),
|
||||
MemberFunctionRecord.getReturnType(),
|
||||
MemberFunctionRecord.getArgumentList(),
|
||||
Prototype);
|
||||
}
|
||||
|
||||
static model::ABI::Values getABI(llvm::codeview::CallingConvention CallConv,
|
||||
model::Architecture::Values Architecture) {
|
||||
using namespace llvm::codeview;
|
||||
if (Architecture == model::Architecture::x86_64) {
|
||||
switch (CallConv) {
|
||||
case CallingConvention::NearC:
|
||||
case CallingConvention::NearFast:
|
||||
case CallingConvention::NearStdCall:
|
||||
case CallingConvention::NearSysCall:
|
||||
case CallingConvention::ThisCall:
|
||||
return model::ABI::Microsoft_x86_64;
|
||||
case CallingConvention::NearPascal:
|
||||
revng_log(Log, "Pascal is not currently supported");
|
||||
return model::ABI::Invalid;
|
||||
case CallingConvention::NearVector:
|
||||
return model::ABI::Microsoft_x86_64_vectorcall;
|
||||
case CallingConvention::ClrCall:
|
||||
revng_log(Log, "ClrCall is not currently supported");
|
||||
return model::ABI::Invalid;
|
||||
default:
|
||||
revng_abort();
|
||||
}
|
||||
} else if (Architecture == 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_log(Log, "ClrCall is not currently supported");
|
||||
return model::ABI::Invalid;
|
||||
case CallingConvention::NearPascal:
|
||||
revng_log(Log, "Pascal is not currently supported");
|
||||
return model::ABI::Invalid;
|
||||
case CallingConvention::NearVector:
|
||||
return model::ABI::Microsoft_x86_vectorcall;
|
||||
default:
|
||||
revng_log(Log, "Unknown ABI");
|
||||
return model::ABI::Invalid;
|
||||
}
|
||||
} else if (Architecture == model::Architecture::mips
|
||||
and CallConv == CallingConvention::MipsCall) {
|
||||
return model::ABI::SystemV_MIPS_o32;
|
||||
} else if (Architecture == model::Architecture::mipsel
|
||||
and CallConv == CallingConvention::MipsCall) {
|
||||
return model::ABI::SystemV_MIPSEL_o32;
|
||||
} else if (Architecture == model::Architecture::arm
|
||||
and CallConv == CallingConvention::ArmCall) {
|
||||
return model::ABI::AAPCS;
|
||||
} else if (Architecture == model::Architecture::aarch64
|
||||
/* and CallConv == CallingConvention::ArmCall
|
||||
(I'm seeing CallingConvention::NearC)
|
||||
*/) {
|
||||
return model::ABI::Microsoft_AAPCS64;
|
||||
} else {
|
||||
return model::ABI::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processFunctionDefinition(CallingConvention CallingConvention,
|
||||
TypeIndex ReturnTypeIndex,
|
||||
TypeIndex ArgumentListIndex,
|
||||
model::CABIFunctionDefinition
|
||||
&Prototype) {
|
||||
// Handle ABI
|
||||
Prototype.ABI() = getABI(CallingConvention, Architecture);
|
||||
|
||||
// Handle return type
|
||||
const auto &ReturnType = *rc_recur getTypeForImpl(ReturnTypeIndex);
|
||||
if (ReturnType.isEmpty()) {
|
||||
revng_log(Log, "Return type not found: " << toString(ReturnTypeIndex));
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
if (not ReturnType->isVoidPrimitive())
|
||||
Prototype.ReturnType() = ReturnType.copy();
|
||||
|
||||
// Handle arguments
|
||||
auto *MaybeArgumentList = Importer
|
||||
.getTypeRecord<ArgListRecord>(ArgumentListIndex);
|
||||
if (MaybeArgumentList == nullptr) {
|
||||
revng_log(Log,
|
||||
"Warning: argument list has an unexpected type: "
|
||||
<< toString(ArgumentListIndex));
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
auto ArgumentTypeIndices = MaybeArgumentList->getIndices();
|
||||
bool IsVariadic = (ArgumentTypeIndices.size() != 0
|
||||
and ArgumentTypeIndices.back().isNoneType());
|
||||
if (IsVariadic) {
|
||||
revng_log(Log, "Ignoring variadic function");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
for (auto &[ArgumentIndex, ArgumentTypeIndex] :
|
||||
llvm::enumerate(ArgumentTypeIndices)) {
|
||||
revng_log(Log, "Processing argument #" << ArgumentIndex);
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
const auto &ArgumentType = *rc_recur getTypeForImpl(ArgumentTypeIndex);
|
||||
|
||||
if (ArgumentType.isEmpty()) {
|
||||
revng_log(Log, "Warning: couldn't get the argument type");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
auto MaybeSize = ArgumentType->size();
|
||||
uint64_t Size = MaybeSize.value_or(0);
|
||||
if (Size == 0) {
|
||||
revng_log(Log, "Warning: 0-sized argument, bailing out");
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
Prototype.addArgument(ArgumentType.copy());
|
||||
}
|
||||
|
||||
rc_return true;
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
TypeResolver::processDefinition(AliasRecord &AliasRecord,
|
||||
model::TypedefDefinition &Typedef) {
|
||||
revng_log(Log, "Handling AliasRecord");
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
Typedef.Name() = AliasRecord.Name;
|
||||
|
||||
const auto &UnderlyingType = rc_recur getTypeForImpl(AliasRecord
|
||||
.UnderlyingType);
|
||||
if (UnderlyingType->isEmpty()) {
|
||||
revng_log(Log,
|
||||
"Underlying type not found: "
|
||||
<< toString(AliasRecord.UnderlyingType));
|
||||
Typedef.UnderlyingType() = model::PrimitiveType::makeVoid();
|
||||
rc_return false;
|
||||
}
|
||||
|
||||
Typedef.UnderlyingType() = UnderlyingType->copy();
|
||||
|
||||
rc_return true;
|
||||
}
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
TypeResolver::getTypeForImpl(TypeIndex Index) {
|
||||
using namespace model;
|
||||
|
||||
revng_log(Log, "Resolving type " << toString(Index));
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
// First of all, handle primitive types
|
||||
if (Index.isSimple()) {
|
||||
// Check cache
|
||||
if (auto *Result = Importer.tryGetType(Index)) {
|
||||
revng_log(Log, "Simple type " + toString(Index) + " found in cache");
|
||||
revng_assert(Result != nullptr);
|
||||
rc_return Result;
|
||||
}
|
||||
|
||||
rc_return handleSimpleType(Index);
|
||||
}
|
||||
|
||||
// Check if we the data for this index
|
||||
TypeRecord *TheType = Importer.getTypeRecord(Index);
|
||||
if (TheType == nullptr) {
|
||||
revng_log(Log, "Warning: couldn't find type " << toString(Index));
|
||||
rc_return record(Index, UpcastableType::empty(), true);
|
||||
}
|
||||
|
||||
// Resolve non-trivial types
|
||||
|
||||
switch (TheType->Kind) {
|
||||
|
||||
case TypeRecordKind::BitField: {
|
||||
rc_return rc_recur handle<BitFieldRecord>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Pointer: {
|
||||
rc_return rc_recur handle<PointerRecord>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Modifier: {
|
||||
rc_return rc_recur handle<ModifierRecord>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Array: {
|
||||
rc_return rc_recur handle<ArrayRecord>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Class:
|
||||
case TypeRecordKind::Struct:
|
||||
case TypeRecordKind::Interface: {
|
||||
rc_return rc_recur handle<ClassRecord, StructDefinition>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Union: {
|
||||
rc_return rc_recur handle<UnionRecord, UnionDefinition>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Enum: {
|
||||
rc_return rc_recur handle<EnumRecord, EnumDefinition>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Procedure: {
|
||||
rc_return rc_recur handle<ProcedureRecord, CABIFunctionDefinition>(Index,
|
||||
TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::MemberFunction: {
|
||||
rc_return rc_recur
|
||||
handle<MemberFunctionRecord, CABIFunctionDefinition>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::Alias: {
|
||||
rc_return rc_recur handle<AliasRecord, TypedefDefinition>(Index, TheType);
|
||||
}
|
||||
|
||||
case TypeRecordKind::ArgList:
|
||||
case TypeRecordKind::FieldList:
|
||||
revng_log(Log, "Warning: requesting a type for non-type record, ignoring");
|
||||
rc_return fail(Index);
|
||||
|
||||
default:
|
||||
revng_log(Log,
|
||||
"Warning: ignoring unknown TypeRecord kind: "
|
||||
<< static_cast<int>(TheType->Kind));
|
||||
rc_return fail(Index);
|
||||
}
|
||||
|
||||
revng_abort();
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "revng/ADT/RecursiveCoroutine.h"
|
||||
#include "revng/Model/ArrayType.h"
|
||||
#include "revng/Model/CABIFunctionDefinition.h"
|
||||
#include "revng/Model/EnumDefinition.h"
|
||||
#include "revng/Model/PointerType.h"
|
||||
#include "revng/Model/PrimitiveType.h"
|
||||
#include "revng/Model/StructDefinition.h"
|
||||
#include "revng/Model/TypedefDefinition.h"
|
||||
#include "revng/Model/UnionDefinition.h"
|
||||
|
||||
#include "PDBImporterImpl.h"
|
||||
|
||||
class TypeResolver {
|
||||
public:
|
||||
using TypeIndex = llvm::codeview::TypeIndex;
|
||||
using TypeRecord = llvm::codeview::TypeRecord;
|
||||
|
||||
private:
|
||||
PDBImporterImpl &Importer;
|
||||
model::Architecture::Values Architecture = model::Architecture::Invalid;
|
||||
|
||||
bool NeedsSize = true;
|
||||
llvm::DenseSet<const model::TypeDefinition *> CurrentlyProcessing;
|
||||
|
||||
public:
|
||||
TypeResolver(PDBImporterImpl &Importer,
|
||||
model::Architecture::Values Architecture) :
|
||||
Importer(Importer), Architecture(Architecture) {}
|
||||
|
||||
public:
|
||||
const model::UpcastableType &getTypeFor(TypeIndex Index) {
|
||||
return *getTypeForImpl(Index);
|
||||
}
|
||||
|
||||
private:
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
getTypeForImpl(TypeIndex Index);
|
||||
|
||||
private:
|
||||
const model::UpcastableType *handleSimpleType(TypeIndex SimpleType);
|
||||
|
||||
private:
|
||||
//
|
||||
// Type modifiers
|
||||
//
|
||||
template<typename RecordType>
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
handle(TypeIndex Index, TypeRecord *TheType) {
|
||||
// Check cache
|
||||
if (auto *Entry = Importer.tryGetEntry(Index)) {
|
||||
if (not NeedsSize or Entry->IsSizeAvailable) {
|
||||
revng_log(Log, toString(Index) + " found in cache");
|
||||
rc_return & Entry->Type;
|
||||
}
|
||||
revng_log(Log,
|
||||
toString(Index) + " cached but size unavailable, re-resolving");
|
||||
}
|
||||
|
||||
// Resolve
|
||||
auto &Record = *reinterpret_cast<RecordType *>(TheType);
|
||||
rc_return rc_recur handle(Index, Record);
|
||||
}
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
handle(TypeIndex Index, llvm::codeview::BitFieldRecord &Pointer);
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
handle(TypeIndex Index, llvm::codeview::PointerRecord &Pointer);
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
handle(TypeIndex Index, llvm::codeview::ModifierRecord &Pointer);
|
||||
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
handle(TypeIndex Index, llvm::codeview::ArrayRecord &Pointer);
|
||||
|
||||
private:
|
||||
//
|
||||
// Type definitions
|
||||
//
|
||||
template<std::derived_from<TypeRecord> RecordType,
|
||||
std::derived_from<model::TypeDefinition> DefinitionType>
|
||||
RecursiveCoroutine<const model::UpcastableType *>
|
||||
handle(TypeIndex Index, TypeRecord *TheType) {
|
||||
using namespace llvm;
|
||||
|
||||
auto &Entry = notNull(Importer.tryGetEntry(Index));
|
||||
|
||||
// If we don't need the size, no need to process the type definition.
|
||||
// Also skip if we've already processed it, or if this cache entry is not
|
||||
// backed by a pre-registered TypeDefinition (e.g. forward-ref fallbacks).
|
||||
if (NeedsSize and not Entry.IsSizeAvailable) {
|
||||
// Unfortunately, no LLVM-style RTTI here
|
||||
auto &Record = *reinterpret_cast<RecordType *>(TheType);
|
||||
revng_assert(Entry.Definition != nullptr,
|
||||
"handle<RecordType, DefinitionType> requires a "
|
||||
"pre-registered TypeDefinition");
|
||||
auto *Definition = cast<DefinitionType>(Entry.Definition);
|
||||
|
||||
// Flip size-available up front so that if processDefinition transitively
|
||||
// reaches us via a non-pointer edge, the recursion check triggers
|
||||
// instead of re-entering processing.
|
||||
Importer.markSizeAvailable(Index);
|
||||
|
||||
// Detect recursion
|
||||
if (CurrentlyProcessing.contains(Definition)) {
|
||||
revng_log(Log,
|
||||
"Recursion found. Recursion is allowed only via pointers");
|
||||
Importer.registerInvalidDefinition(Definition);
|
||||
rc_return &Entry.Type;
|
||||
}
|
||||
|
||||
// Register current type to detect recursion
|
||||
CurrentlyProcessing.insert(Definition);
|
||||
|
||||
// Populate this type definition
|
||||
bool Result = rc_recur processDefinition(Record, *Definition);
|
||||
|
||||
// Expunge current type from currently processing
|
||||
CurrentlyProcessing.erase(Definition);
|
||||
|
||||
if (not Result) {
|
||||
revng_log(Log, "Registering type definition for being purged");
|
||||
Importer.registerInvalidDefinition(Definition);
|
||||
}
|
||||
}
|
||||
|
||||
rc_return &Entry.Type;
|
||||
}
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processDefinition(llvm::codeview::ClassRecord &ClassRecord,
|
||||
model::StructDefinition &Struct);
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processDefinition(llvm::codeview::UnionRecord &UnionRecord,
|
||||
model::UnionDefinition &Union);
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processDefinition(llvm::codeview::EnumRecord &UnionRecord,
|
||||
model::EnumDefinition &Union);
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processDefinition(llvm::codeview::ProcedureRecord &ProcedureRecord,
|
||||
model::CABIFunctionDefinition &Prototype);
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processDefinition(llvm::codeview::MemberFunctionRecord &MemberFunctionRecord,
|
||||
model::CABIFunctionDefinition &Prototype);
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processFunctionDefinition(llvm::codeview::CallingConvention CallingConvention,
|
||||
TypeIndex ReturnTypeIndex,
|
||||
TypeIndex ArgumentListIndex,
|
||||
model::CABIFunctionDefinition &Prototype);
|
||||
|
||||
RecursiveCoroutine<bool>
|
||||
processDefinition(llvm::codeview::AliasRecord &AliasRecord,
|
||||
model::TypedefDefinition &Typedef);
|
||||
|
||||
private:
|
||||
const model::UpcastableType *record(TypeIndex Index,
|
||||
model::UpcastableType &&Result,
|
||||
bool IsSizeAvailable) {
|
||||
return &Importer.recordType(Index, std::move(Result), IsSizeAvailable);
|
||||
}
|
||||
|
||||
const model::UpcastableType *fail(TypeIndex Index) {
|
||||
return record(Index, model::UpcastableType::empty(), true);
|
||||
}
|
||||
};
|
||||
@@ -14,15 +14,16 @@ revng_add_library_internal(
|
||||
CommonOptions.cpp
|
||||
CustomizedLLVMPasses.cpp
|
||||
Debug.cpp
|
||||
ELFLDDTree.cpp
|
||||
ExplicitSpecializations.cpp
|
||||
FileSystem.cpp
|
||||
InitRevng.cpp
|
||||
IRHelpers.cpp
|
||||
LDDTree.cpp
|
||||
MetaAddress.cpp
|
||||
ModuleStatistics.cpp
|
||||
OnQuit.cpp
|
||||
PathList.cpp
|
||||
PECOFFLDDTree.cpp
|
||||
Progress.cpp
|
||||
ResourceFinder.cpp
|
||||
Statistics.cpp
|
||||
|
||||
@@ -17,12 +17,13 @@ extern "C" {
|
||||
#include "revng/ADT/STLExtras.h"
|
||||
#include "revng/Model/OperatingSystem.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/FileSystem.h"
|
||||
#include "revng/Support/Generator.h"
|
||||
#include "revng/Support/PathList.h"
|
||||
#include "revng/Support/WindowsApiSetSchemaParser.h"
|
||||
#include "revng/Support/YAMLTraits.h"
|
||||
|
||||
#include "LDDTreeObjectFileTraits.h"
|
||||
|
||||
using namespace llvm;
|
||||
using yaml::IO;
|
||||
using yaml::MappingTraits;
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/BinaryFormat/ELF.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
|
||||
#include "revng/Support/ObjectFile.h"
|
||||
#include "revng/Support/OverflowSafeInt.h"
|
||||
|
||||
#include "LDDTreeObjectFileTraits.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::object;
|
||||
|
||||
template<class ELFT>
|
||||
std::optional<std::string> getDynamicString(const ELFFile<ELFT> &TheELF,
|
||||
StringRef DynamicStringTable,
|
||||
uint64_t Value) {
|
||||
if (DynamicStringTable.empty() && !DynamicStringTable.data())
|
||||
return std::nullopt;
|
||||
uint64_t FileSize = TheELF.getBufSize();
|
||||
uint64_t Offset = reinterpret_cast<const uint8_t *>(DynamicStringTable.data())
|
||||
- TheELF.base();
|
||||
if (DynamicStringTable.size() > FileSize - Offset)
|
||||
return std::nullopt;
|
||||
if (Value >= DynamicStringTable.size())
|
||||
return std::nullopt;
|
||||
if (DynamicStringTable.back() != '\0')
|
||||
return std::nullopt;
|
||||
return StringRef(DynamicStringTable.data() + Value).str();
|
||||
}
|
||||
|
||||
template<class ELFT>
|
||||
static std::optional<StringRef>
|
||||
getDynamicStringTable(const ELFT &ELFObjectFile,
|
||||
typename ELFT::Elf_Dyn_Range &DynamicEntries) {
|
||||
// Find address and size of .dynstr using dynamic entries
|
||||
uint64_t DynstrAddress = 0;
|
||||
uint64_t DynstrSize = 0;
|
||||
for (const auto &DynamicTag : DynamicEntries) {
|
||||
auto TheTag = DynamicTag.getTag();
|
||||
auto TheVal = DynamicTag.getVal();
|
||||
if (TheTag == llvm::ELF::DT_STRTAB) {
|
||||
DynstrAddress = TheVal;
|
||||
} else if (TheTag == llvm::ELF::DT_STRSZ) {
|
||||
DynstrSize = TheVal;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute end address
|
||||
auto MaybeEndAddress = OverflowSafeInt(DynstrAddress) + DynstrSize;
|
||||
if (DynstrAddress == 0 or DynstrSize == 0 or not MaybeEndAddress)
|
||||
return {};
|
||||
uint64_t EndAddress = *MaybeEndAddress;
|
||||
|
||||
//
|
||||
// Convert address to offset
|
||||
//
|
||||
|
||||
// Collect program headers
|
||||
auto MaybeProgramHeaders = ELFObjectFile.getELFFile().program_headers();
|
||||
if (auto Error = MaybeProgramHeaders.takeError()) {
|
||||
revng_log(LDDTreeLog, "No valid program headers available: " << Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
return {};
|
||||
}
|
||||
|
||||
// Find the correct program header
|
||||
using Elf_Phdr = ELFT::Elf_Phdr;
|
||||
for (const Elf_Phdr &Phdr : *MaybeProgramHeaders) {
|
||||
if (Phdr.p_type != llvm::ELF::PT_LOAD)
|
||||
continue;
|
||||
|
||||
uint64_t StartAddress = Phdr.p_vaddr;
|
||||
uint64_t FileSize = Phdr.p_filesz;
|
||||
auto SegmentEndAddress = OverflowSafeInt(StartAddress) + FileSize;
|
||||
if (SegmentEndAddress and DynstrAddress >= Phdr.p_vaddr
|
||||
and EndAddress <= *SegmentEndAddress) {
|
||||
uint64_t SegmentStartOffset = Phdr.p_offset;
|
||||
auto MaybeSegmentEndOffset = OverflowSafeInt(SegmentStartOffset)
|
||||
+ FileSize;
|
||||
auto MaybeDynstrOffset = OverflowSafeInt(DynstrAddress) - StartAddress;
|
||||
auto MaybeDynstrEnd = MaybeDynstrOffset + DynstrSize;
|
||||
|
||||
StringRef RawData = ELFObjectFile.getData();
|
||||
if (MaybeSegmentEndOffset and MaybeDynstrOffset and MaybeDynstrEnd
|
||||
and SegmentStartOffset <= *MaybeSegmentEndOffset
|
||||
and SegmentStartOffset <= RawData.size()
|
||||
and *MaybeSegmentEndOffset <= RawData.size()) {
|
||||
return RawData.slice(SegmentStartOffset, *MaybeSegmentEndOffset)
|
||||
.slice(*MaybeDynstrOffset, *MaybeDynstrEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template<IsELFObjectFile T>
|
||||
class ELFObjectFileTraits {
|
||||
public:
|
||||
using ObjectFile = T;
|
||||
|
||||
struct ResolutionInfo {
|
||||
bool Is64 = false;
|
||||
bool NoDefault = false;
|
||||
uint16_t EMachine = 0;
|
||||
std::optional<std::string> RPath;
|
||||
std::optional<std::string> RunPath;
|
||||
};
|
||||
|
||||
using QueueEntry = typename Queue<ResolutionInfo>::Entry;
|
||||
|
||||
public:
|
||||
static std::vector<std::pair<llvm::StringRef, uint64_t>>
|
||||
getExportedSymbols(const T &ELFObjectFile) {
|
||||
std::vector<std::pair<llvm::StringRef, uint64_t>> Result;
|
||||
|
||||
for (object::ELFSymbolRef Symbol :
|
||||
llvm::make_range(ELFObjectFile.dynamic_symbol_begin(),
|
||||
ELFObjectFile.dynamic_symbol_end())) {
|
||||
|
||||
// TODO: consider global objects as well
|
||||
if (Symbol.getELFType() != ELF::STT_FUNC
|
||||
and Symbol.getELFType() != ELF::STT_GNU_IFUNC)
|
||||
continue;
|
||||
|
||||
auto MaybeName = Symbol.getName();
|
||||
if (auto Error = MaybeName.takeError()) {
|
||||
revng_log(LDDTreeLog, "Couldn't get the name of a dynamic symbol");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto MaybeSection = Symbol.getSection();
|
||||
if (auto Error = MaybeSection.takeError()) {
|
||||
revng_log(LDDTreeLog,
|
||||
"Couldn't get the section of dynamic symbol " << *MaybeName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip undefined symbols
|
||||
if (*MaybeSection == ELFObjectFile.section_end())
|
||||
continue;
|
||||
|
||||
auto MaybeAddress = Symbol.getAddress();
|
||||
if (auto Error = MaybeAddress.takeError()) {
|
||||
revng_log(LDDTreeLog,
|
||||
"Couldn't get the address of dynamic symbol " << *MaybeName);
|
||||
continue;
|
||||
}
|
||||
|
||||
Result.push_back({ *MaybeName, *MaybeAddress });
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static std::vector<QueueEntry>
|
||||
getDependencies(const revng::RootEntry &Root,
|
||||
StringRef ImporterIdentifier,
|
||||
StringRef ImporterCanonicalPath,
|
||||
const ObjectFile &ELFObjectFile) {
|
||||
std::vector<QueueEntry> Results;
|
||||
const auto &TheELF = ELFObjectFile.getELFFile();
|
||||
|
||||
auto MaybeDynamicEntries = TheELF.dynamicEntries();
|
||||
|
||||
if (auto Error = MaybeDynamicEntries.takeError()) {
|
||||
revng_log(LDDTreeLog, "Cannot access dynamic entries: " << Error);
|
||||
consumeError(std::move(Error));
|
||||
return {};
|
||||
}
|
||||
|
||||
using Elf_Dyn_Range = ObjectFile::Elf_Dyn_Range;
|
||||
Elf_Dyn_Range DynamicEntries = *MaybeDynamicEntries;
|
||||
|
||||
std::optional<std::string> RunPath;
|
||||
std::optional<std::string> RPath;
|
||||
bool NoDefault = false;
|
||||
|
||||
// Look for .dynstr
|
||||
StringRef DynamicStringTable;
|
||||
if (auto MaybeDynamicStringTable = getDynamicStringTable(ELFObjectFile,
|
||||
DynamicEntries))
|
||||
DynamicStringTable = *MaybeDynamicStringTable;
|
||||
|
||||
if (DynamicStringTable.empty()) {
|
||||
revng_log(LDDTreeLog, "Cannot find .dynstr");
|
||||
return {};
|
||||
}
|
||||
|
||||
// Look for DT_RPATH and DT_RUNPATH
|
||||
using Elf_Dyn = ObjectFile::Elf_Dyn;
|
||||
for (const Elf_Dyn &DynamicTag : DynamicEntries) {
|
||||
auto TheTag = DynamicTag.getTag();
|
||||
auto TheVal = DynamicTag.getVal();
|
||||
if (TheTag == llvm::ELF::DT_RUNPATH) {
|
||||
RunPath = getDynamicString(TheELF, DynamicStringTable, TheVal);
|
||||
} else if (TheTag == llvm::ELF::DT_RPATH) {
|
||||
RPath = getDynamicString(TheELF, DynamicStringTable, TheVal);
|
||||
} else if (TheTag == llvm::ELF::DT_FLAGS_1) {
|
||||
NoDefault = (TheVal & llvm::ELF::DF_1_NODEFLIB) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool Is64 = (std::is_same_v<ObjectFile, object::ELF64LEObjectFile>
|
||||
or std::is_same_v<ObjectFile, object::ELF64BEObjectFile>);
|
||||
|
||||
for (const Elf_Dyn &DynamicTag : DynamicEntries) {
|
||||
auto TheTag = DynamicTag.getTag();
|
||||
auto TheVal = DynamicTag.getVal();
|
||||
if (TheTag == llvm::ELF::DT_NEEDED) {
|
||||
auto LibName = getDynamicString(TheELF, DynamicStringTable, TheVal);
|
||||
if (!LibName) {
|
||||
revng_log(LDDTreeLog, "Unable to parse needed library name");
|
||||
continue;
|
||||
}
|
||||
|
||||
Results.push_back({ *LibName,
|
||||
ImporterIdentifier.str(),
|
||||
ImporterCanonicalPath.str(),
|
||||
{ Is64,
|
||||
NoDefault,
|
||||
TheELF.getHeader().e_machine,
|
||||
RPath,
|
||||
RunPath } });
|
||||
}
|
||||
}
|
||||
|
||||
return Results;
|
||||
}
|
||||
|
||||
/// \see man ld.so
|
||||
static SmallVector<std::string, 16>
|
||||
computeSearchPaths(const revng::RootEntry &Root, QueueEntry &Entry) {
|
||||
// Note: despite taking Root, the results should not be prefixed with root!
|
||||
|
||||
auto &Dependency = Entry.Dependency;
|
||||
|
||||
SmallVector<std::string, 16> SearchPaths;
|
||||
|
||||
// Process DT_RPATH
|
||||
if (Dependency.RPath and Dependency.RPath->size())
|
||||
for (StringRef Path : split(*Dependency.RPath, ":"))
|
||||
SearchPaths.push_back(Path.str());
|
||||
|
||||
using namespace llvm::sys;
|
||||
#ifdef __linux__
|
||||
// Process the `LD_LIBRARY_PATH`
|
||||
if (Root.isHost())
|
||||
if (auto MaybeLibraryPath = Process::GetEnv("LD_LIBRARY_PATH"))
|
||||
for (StringRef Path : split(*MaybeLibraryPath, ":"))
|
||||
SearchPaths.push_back(Path.str());
|
||||
#endif
|
||||
|
||||
// Process DT_RUNPATH
|
||||
std::string Origin;
|
||||
{
|
||||
SmallString<32> OriginSmallString;
|
||||
OriginSmallString += Entry.ImporterCanonicalPath;
|
||||
path::remove_filename(OriginSmallString);
|
||||
Origin = OriginSmallString.str();
|
||||
}
|
||||
|
||||
if (Origin.size() == 0)
|
||||
Origin = path::get_separator();
|
||||
|
||||
std::string LibName = Dependency.Is64 ? "lib64" : "lib";
|
||||
if (Dependency.RunPath and Dependency.RunPath->size()) {
|
||||
for (StringRef Path : split(*Dependency.RunPath, ":")) {
|
||||
std::string PathString = Path.str();
|
||||
replaceAll(PathString, "$ORIGIN", Origin);
|
||||
replaceAll(PathString, "${ORIGIN}", Origin);
|
||||
replaceAll(PathString, "$LIB", LibName);
|
||||
replaceAll(PathString, "${LIB}", LibName);
|
||||
// TODO: handle $PLATFORM
|
||||
|
||||
SearchPaths.push_back(PathString);
|
||||
}
|
||||
}
|
||||
|
||||
if (not Dependency.NoDefault) {
|
||||
auto &LinuxRoot = Root.osSpecific<revng::LinuxRootFS>();
|
||||
LinuxRoot.addLdSoConfPaths(SearchPaths);
|
||||
SearchPaths.push_back("/" + LibName);
|
||||
SearchPaths.push_back("/usr/" + LibName);
|
||||
}
|
||||
|
||||
return SearchPaths;
|
||||
}
|
||||
|
||||
static bool isValidObjectFor(QueueEntry &Entry, const T &Elf) {
|
||||
auto &Dependency = Entry.Dependency;
|
||||
auto Machine = static_cast<const ELFObjectFileBase &>(Elf).getEMachine();
|
||||
|
||||
if (Machine != Dependency.EMachine) {
|
||||
revng_log(LDDTreeLog,
|
||||
"Found candidate but it has the wrong e_machine: "
|
||||
<< Machine << " (expected " << Dependency.EMachine << ").");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
class LDDTreeObjectFileTraits<ELF32LEObjectFile>
|
||||
: public ELFObjectFileTraits<ELF32LEObjectFile> {};
|
||||
|
||||
template<>
|
||||
class LDDTreeObjectFileTraits<ELF32BEObjectFile>
|
||||
: public ELFObjectFileTraits<ELF32BEObjectFile> {};
|
||||
|
||||
template<>
|
||||
class LDDTreeObjectFileTraits<ELF64LEObjectFile>
|
||||
: public ELFObjectFileTraits<ELF64LEObjectFile> {};
|
||||
|
||||
template<>
|
||||
class LDDTreeObjectFileTraits<ELF64BEObjectFile>
|
||||
: public ELFObjectFileTraits<ELF64BEObjectFile> {};
|
||||
|
||||
template<typename T>
|
||||
concept Check = LDDTreeObjectFileTraitsConcept<LDDTreeObjectFileTraits<T>>;
|
||||
|
||||
static_assert(Check<ELF32LEObjectFile>);
|
||||
static_assert(Check<ELF64LEObjectFile>);
|
||||
static_assert(Check<ELF32BEObjectFile>);
|
||||
static_assert(Check<ELF64BEObjectFile>);
|
||||
|
||||
template const LDDTree
|
||||
LDDTree::fromPath<ELF32LEObjectFile>(const revng::RootEntry &Root,
|
||||
StringRef Path,
|
||||
const ELF32LEObjectFile &Binary,
|
||||
std::optional<LDDTree::SymbolSet> Symbols);
|
||||
|
||||
template const LDDTree
|
||||
LDDTree::fromPath<ELF64LEObjectFile>(const revng::RootEntry &Root,
|
||||
StringRef Path,
|
||||
const ELF64LEObjectFile &Binary,
|
||||
std::optional<LDDTree::SymbolSet> Symbols);
|
||||
|
||||
template const LDDTree
|
||||
LDDTree::fromPath<ELF32BEObjectFile>(const revng::RootEntry &Root,
|
||||
StringRef Path,
|
||||
const ELF32BEObjectFile &Binary,
|
||||
std::optional<LDDTree::SymbolSet> Symbols);
|
||||
|
||||
template const LDDTree
|
||||
LDDTree::fromPath<ELF64BEObjectFile>(const revng::RootEntry &Root,
|
||||
StringRef Path,
|
||||
const ELF64BEObjectFile &Binary,
|
||||
std::optional<LDDTree::SymbolSet> Symbols);
|
||||
@@ -1,453 +0,0 @@
|
||||
/// Implementation of lddtree like API.
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
extern "C" {
|
||||
#include "glob.h"
|
||||
}
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "llvm/Object/Binary.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Object/ObjectFile.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
|
||||
#include "revng/ADT/RecursiveCoroutine.h"
|
||||
#include "revng/ADT/STLExtras.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/Generator.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
#include "revng/Support/OverflowSafeInt.h"
|
||||
|
||||
static Logger Log("lddtree");
|
||||
|
||||
using namespace llvm;
|
||||
|
||||
constexpr unsigned MaxIncludeDepth = 5;
|
||||
|
||||
/// \see ldconfig.c from glibc
|
||||
class LdSoConfParser {
|
||||
private:
|
||||
SmallVectorImpl<std::string> &SearchPaths;
|
||||
std::set<std::string> VisitedFiles;
|
||||
|
||||
public:
|
||||
LdSoConfParser(SmallVectorImpl<std::string> &SearchPaths) :
|
||||
SearchPaths(SearchPaths) {}
|
||||
|
||||
public:
|
||||
void parse() {
|
||||
std::set<std::string> VisitedFiles;
|
||||
// TODO: add support for prefix?
|
||||
parseImpl("/etc/ld.so.conf", 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void parseImpl(StringRef Path, unsigned Depth) {
|
||||
revng_log(Log, "Parsing " << Path);
|
||||
|
||||
if (Depth > MaxIncludeDepth) {
|
||||
revng_log(Log,
|
||||
"More than " << MaxIncludeDepth
|
||||
<< " nested includes in ld.so.conf");
|
||||
return;
|
||||
}
|
||||
|
||||
bool IsNew = VisitedFiles.insert(Path.str()).second;
|
||||
if (not IsNew) {
|
||||
revng_log(Log, "We already visited " << Path.str() << ". Ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
using namespace llvm::sys;
|
||||
StringRef Directory = path::parent_path(Path);
|
||||
|
||||
auto MaybeBuffer = llvm::MemoryBuffer::getFile(Path);
|
||||
if (not MaybeBuffer) {
|
||||
revng_log(Log, "Can't open " << Path);
|
||||
return;
|
||||
}
|
||||
|
||||
StringRef Data = MaybeBuffer->get()->getBuffer();
|
||||
|
||||
// Split in lines
|
||||
SmallVector<StringRef, 8> Lines;
|
||||
Data.split(Lines, "\n");
|
||||
|
||||
for (StringRef Line : Lines) {
|
||||
// Remove comments
|
||||
Line = Line.split("#").first;
|
||||
|
||||
// Strip white spaces
|
||||
Line = Line.trim();
|
||||
|
||||
// Ignore empty lines
|
||||
if (Line.size() == 0)
|
||||
continue;
|
||||
|
||||
if (Line.consume_front("include ")) {
|
||||
Line = Line.trim();
|
||||
|
||||
SmallString<32> GlobExpression = makeAbsolute(Line, Directory);
|
||||
for (StringRef File : glob(GlobExpression))
|
||||
parseImpl(File, Depth + 1);
|
||||
|
||||
} else if (Line.consume_front("hwcap ")) {
|
||||
|
||||
revng_log(Log, "Ignoring hwcap line");
|
||||
|
||||
} else {
|
||||
|
||||
// Add the path
|
||||
SmallString<32> SearchPath = makeAbsolute(Line, Directory);
|
||||
revng_log(Log, "Registering " << SearchPath.str().str());
|
||||
SearchPaths.push_back(SearchPath.str().str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static SmallString<32> makeAbsolute(StringRef Path,
|
||||
StringRef CurrentDirectory) {
|
||||
SmallString<32> Result;
|
||||
|
||||
if (Path.starts_with("/")) {
|
||||
Result.append(Path);
|
||||
} else {
|
||||
Result = CurrentDirectory;
|
||||
llvm::sys::path::append(Result, Path);
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static cppcoro::generator<StringRef> glob(StringRef Pattern) {
|
||||
glob64_t GlobResults;
|
||||
int Result = glob64(Pattern.str().data(), 0, NULL, &GlobResults);
|
||||
|
||||
switch (Result) {
|
||||
case 0:
|
||||
for (size_t I = 0; I < GlobResults.gl_pathc; ++I)
|
||||
co_yield StringRef(GlobResults.gl_pathv[I]);
|
||||
globfree64(&GlobResults);
|
||||
break;
|
||||
|
||||
case GLOB_NOMATCH:
|
||||
break;
|
||||
|
||||
case GLOB_NOSPACE:
|
||||
case GLOB_ABORTED:
|
||||
revng_log(Log, "Cannot read directory");
|
||||
break;
|
||||
|
||||
default:
|
||||
revng_abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// \see man ld.so
|
||||
static std::optional<std::string>
|
||||
findLibrary(StringRef ToImport,
|
||||
StringRef ImporterPath,
|
||||
bool Is64,
|
||||
bool NoDefault,
|
||||
uint16_t EMachine,
|
||||
std::optional<StringRef> RPath,
|
||||
std::optional<StringRef> RunPath) {
|
||||
revng_log(Log, "Looking for " << ToImport);
|
||||
LoggerIndent Indent(Log);
|
||||
|
||||
SmallVector<std::string, 16> SearchPaths;
|
||||
|
||||
// Process DT_RPATH
|
||||
if (RPath and RPath->size())
|
||||
for (StringRef Path : split(*RPath, ":"))
|
||||
SearchPaths.push_back(Path.str());
|
||||
|
||||
// Process the `LD_LIBRARY_PATH`
|
||||
if (auto MaybeLibraryPath = llvm::sys::Process::GetEnv("LD_LIBRARY_PATH"))
|
||||
for (StringRef Path : split(*MaybeLibraryPath, ":"))
|
||||
SearchPaths.push_back(Path.str());
|
||||
|
||||
// Process DT_RUNPATH
|
||||
std::string Origin = llvm::sys::path::parent_path(ImporterPath).str();
|
||||
std::string LibName = Is64 ? "lib64" : "lib";
|
||||
if (RunPath and RunPath->size()) {
|
||||
for (StringRef Path : split(*RunPath, ":")) {
|
||||
std::string PathString = Path.str();
|
||||
replaceAll(PathString, "$ORIGIN", Origin);
|
||||
replaceAll(PathString, "${ORIGIN}", Origin);
|
||||
replaceAll(PathString, "$LIB", LibName);
|
||||
replaceAll(PathString, "${LIB}", LibName);
|
||||
// TODO: handle PLATFORM
|
||||
SearchPaths.push_back(PathString);
|
||||
}
|
||||
}
|
||||
|
||||
if (not NoDefault) {
|
||||
LdSoConfParser(SearchPaths).parse();
|
||||
SearchPaths.push_back("/" + LibName);
|
||||
SearchPaths.push_back("/usr/" + LibName);
|
||||
// This is an hack
|
||||
SearchPaths.push_back(Origin);
|
||||
}
|
||||
|
||||
if (Log.isEnabled()) {
|
||||
Log << "List of search paths:\n";
|
||||
for (const std::string &SearchPath : SearchPaths)
|
||||
Log << " " << SearchPath << "\n";
|
||||
Log << DoLog;
|
||||
}
|
||||
|
||||
for (const std::string &SearchPath : SearchPaths) {
|
||||
SmallString<128> Candidate;
|
||||
sys::path::append(Candidate, SearchPath, ToImport);
|
||||
|
||||
if (not sys::fs::exists(Candidate)) {
|
||||
revng_log(Log, Candidate.str() << " does not exist");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the binary
|
||||
auto MaybeBinary = object::createBinary(Candidate);
|
||||
if (auto Error = MaybeBinary.takeError()) {
|
||||
revng_log(Log, "Can't create binary: " << Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure it's an ELF
|
||||
using namespace object;
|
||||
auto *Elf = dyn_cast<ELFObjectFileBase>(MaybeBinary->getBinary());
|
||||
if (Elf == nullptr) {
|
||||
revng_log(Log, "Found " << Candidate.str() << " but it's not an ELF.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure it's the right machine
|
||||
if (Elf->getEMachine() != EMachine) {
|
||||
revng_log(Log,
|
||||
"Found " << Candidate.str()
|
||||
<< " but it has the wrong e_machine: "
|
||||
<< Elf->getEMachine() << " (expected " << EMachine
|
||||
<< ").");
|
||||
continue;
|
||||
}
|
||||
|
||||
revng_log(Log, "Found: " << Candidate.str());
|
||||
|
||||
return { Candidate.str().str() };
|
||||
}
|
||||
|
||||
revng_log(Log, ToImport << " not found");
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template<class ELFT>
|
||||
std::optional<StringRef>
|
||||
getDynamicString(const llvm::object::ELFFile<ELFT> &TheELF,
|
||||
StringRef DynamicStringTable,
|
||||
uint64_t Value) {
|
||||
if (DynamicStringTable.empty() && !DynamicStringTable.data())
|
||||
return std::nullopt;
|
||||
uint64_t FileSize = TheELF.getBufSize();
|
||||
uint64_t Offset = reinterpret_cast<const uint8_t *>(DynamicStringTable.data())
|
||||
- TheELF.base();
|
||||
if (DynamicStringTable.size() > FileSize - Offset)
|
||||
return std::nullopt;
|
||||
if (Value >= DynamicStringTable.size())
|
||||
return std::nullopt;
|
||||
if (DynamicStringTable.back() != '\0')
|
||||
return std::nullopt;
|
||||
return DynamicStringTable.data() + Value;
|
||||
}
|
||||
|
||||
template<class ELFT>
|
||||
static std::optional<StringRef>
|
||||
getDynamicStringTable(const ELFT &ELFObjectFile,
|
||||
typename ELFT::Elf_Dyn_Range &DynamicEntries) {
|
||||
// Find address and size of .dynstr using dynamic entries
|
||||
uint64_t DynstrAddress = 0;
|
||||
uint64_t DynstrSize = 0;
|
||||
for (const auto &DynamicTag : DynamicEntries) {
|
||||
auto TheTag = DynamicTag.getTag();
|
||||
auto TheVal = DynamicTag.getVal();
|
||||
if (TheTag == llvm::ELF::DT_STRTAB) {
|
||||
DynstrAddress = TheVal;
|
||||
} else if (TheTag == llvm::ELF::DT_STRSZ) {
|
||||
DynstrSize = TheVal;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute end address
|
||||
auto MaybeEndAddress = OverflowSafeInt(DynstrAddress) + DynstrSize;
|
||||
if (DynstrAddress == 0 or DynstrSize == 0 or not MaybeEndAddress)
|
||||
return {};
|
||||
uint64_t EndAddress = *MaybeEndAddress;
|
||||
|
||||
//
|
||||
// Convert address to offset
|
||||
//
|
||||
|
||||
// Collect program headers
|
||||
auto MaybeProgramHeaders = ELFObjectFile.getELFFile().program_headers();
|
||||
if (auto Error = MaybeProgramHeaders.takeError()) {
|
||||
revng_log(Log, "No valid program headers available: " << Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
return {};
|
||||
}
|
||||
|
||||
// Find the correct program header
|
||||
using Elf_Phdr = ELFT::Elf_Phdr;
|
||||
for (const Elf_Phdr &Phdr : *MaybeProgramHeaders) {
|
||||
if (Phdr.p_type != llvm::ELF::PT_LOAD)
|
||||
continue;
|
||||
|
||||
uint64_t StartAddress = Phdr.p_vaddr;
|
||||
uint64_t FileSize = Phdr.p_filesz;
|
||||
auto SegmentEndAddress = OverflowSafeInt(StartAddress) + FileSize;
|
||||
if (SegmentEndAddress and DynstrAddress >= Phdr.p_vaddr
|
||||
and EndAddress <= *SegmentEndAddress) {
|
||||
uint64_t SegmentStartOffset = Phdr.p_offset;
|
||||
auto MaybeSegmentEndOffset = OverflowSafeInt(SegmentStartOffset)
|
||||
+ FileSize;
|
||||
auto MaybeDynstrOffset = OverflowSafeInt(DynstrAddress) - StartAddress;
|
||||
auto MaybeDynstrEnd = MaybeDynstrOffset + DynstrSize;
|
||||
|
||||
StringRef RawData = ELFObjectFile.getData();
|
||||
if (MaybeSegmentEndOffset and MaybeDynstrOffset and MaybeDynstrEnd
|
||||
and SegmentStartOffset <= *MaybeSegmentEndOffset
|
||||
and SegmentStartOffset <= RawData.size()
|
||||
and *MaybeSegmentEndOffset <= RawData.size()) {
|
||||
return RawData.slice(SegmentStartOffset, *MaybeSegmentEndOffset)
|
||||
.slice(*MaybeDynstrOffset, *MaybeDynstrEnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template<class ELFT>
|
||||
void lddtreeResolve(LDDTree &Dependencies,
|
||||
StringRef FileName,
|
||||
const ELFT &ELFObjectFile) {
|
||||
const auto &TheELF = ELFObjectFile.getELFFile();
|
||||
|
||||
auto MaybeDynamicEntries = TheELF.dynamicEntries();
|
||||
|
||||
if (auto Error = MaybeDynamicEntries.takeError()) {
|
||||
revng_log(Log, "Cannot access dynamic entries: " << Error);
|
||||
consumeError(std::move(Error));
|
||||
return;
|
||||
}
|
||||
|
||||
using Elf_Dyn_Range = ELFT::Elf_Dyn_Range;
|
||||
Elf_Dyn_Range DynamicEntries = *MaybeDynamicEntries;
|
||||
|
||||
std::optional<StringRef> RunPath;
|
||||
std::optional<StringRef> RPath;
|
||||
bool NoDefault = false;
|
||||
|
||||
// Look for .dynstr
|
||||
StringRef DynamicStringTable;
|
||||
if (auto MaybeDynamicStringTable = getDynamicStringTable(ELFObjectFile,
|
||||
DynamicEntries))
|
||||
DynamicStringTable = *MaybeDynamicStringTable;
|
||||
|
||||
if (DynamicStringTable.empty()) {
|
||||
revng_log(Log, "Cannot find .dynstr");
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for DT_RPATH and DT_RUNPATH
|
||||
using Elf_Dyn = ELFT::Elf_Dyn;
|
||||
for (const Elf_Dyn &DynamicTag : DynamicEntries) {
|
||||
auto TheTag = DynamicTag.getTag();
|
||||
auto TheVal = DynamicTag.getVal();
|
||||
if (TheTag == llvm::ELF::DT_RUNPATH) {
|
||||
RunPath = getDynamicString(TheELF, DynamicStringTable, TheVal);
|
||||
} else if (TheTag == llvm::ELF::DT_RPATH) {
|
||||
RPath = getDynamicString(TheELF, DynamicStringTable, TheVal);
|
||||
} else if (TheTag == llvm::ELF::DT_FLAGS_1) {
|
||||
NoDefault = (TheVal & llvm::ELF::DF_1_NODEFLIB) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool Is64 = (std::is_same_v<ELFT, object::ELF64LEObjectFile>
|
||||
or std::is_same_v<ELFT, object::ELF64BEObjectFile>);
|
||||
|
||||
for (const Elf_Dyn &DynamicTag : DynamicEntries) {
|
||||
auto TheTag = DynamicTag.getTag();
|
||||
auto TheVal = DynamicTag.getVal();
|
||||
if (TheTag == llvm::ELF::DT_NEEDED) {
|
||||
auto LibName = getDynamicString(TheELF, DynamicStringTable, TheVal);
|
||||
if (!LibName) {
|
||||
revng_log(Log, "Unable to parse needed library name");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto LocOfLib = findLibrary(*LibName,
|
||||
FileName,
|
||||
Is64,
|
||||
NoDefault,
|
||||
TheELF.getHeader().e_machine,
|
||||
RPath,
|
||||
RunPath))
|
||||
Dependencies[FileName.str()].push_back(*LocOfLib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static RecursiveCoroutine<void> lddtreeHelper(LDDTree &Dependencies,
|
||||
llvm::StringRef Path,
|
||||
unsigned CurrentLevel,
|
||||
unsigned DepthLevel) {
|
||||
revng_log(Log, "lddtree for " << Path << "\n");
|
||||
LoggerIndent Ident(Log);
|
||||
|
||||
using namespace object;
|
||||
auto MaybeBinary = createBinary(Path);
|
||||
if (auto Error = MaybeBinary.takeError()) {
|
||||
revng_log(Log, "Can't create binary: " << Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
rc_return;
|
||||
}
|
||||
|
||||
auto *Binary = MaybeBinary->getBinary();
|
||||
if (auto *ELFObjectFile = dyn_cast<ELF32LEObjectFile>(Binary))
|
||||
lddtreeResolve(Dependencies, Path, *ELFObjectFile);
|
||||
else if (auto *ELFObjectFile = dyn_cast<ELF32BEObjectFile>(Binary))
|
||||
lddtreeResolve(Dependencies, Path, *ELFObjectFile);
|
||||
else if (auto *ELFObjectFile = dyn_cast<ELF64LEObjectFile>(Binary))
|
||||
lddtreeResolve(Dependencies, Path, *ELFObjectFile);
|
||||
else if (auto *ELFObjectFile = dyn_cast<ELF64BEObjectFile>(Binary))
|
||||
lddtreeResolve(Dependencies, Path, *ELFObjectFile);
|
||||
else
|
||||
revng_log(Log, "Not an ELF.");
|
||||
|
||||
if (CurrentLevel == DepthLevel)
|
||||
rc_return;
|
||||
|
||||
++CurrentLevel;
|
||||
for (auto &I : Dependencies) {
|
||||
revng_log(Log, "Dependencies for " << I.first << ":\n");
|
||||
for (auto &J : I.second)
|
||||
if (!Dependencies.contains(J))
|
||||
rc_recur lddtreeHelper(Dependencies, J, CurrentLevel, DepthLevel);
|
||||
}
|
||||
}
|
||||
|
||||
void lddtree(LDDTree &Dependencies, llvm::StringRef Path, unsigned DepthLevel) {
|
||||
if (DepthLevel > 0)
|
||||
lddtreeHelper(Dependencies, Path, 1, DepthLevel);
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <concepts>
|
||||
#include <queue>
|
||||
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Object/ObjectFile.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/Debug.h"
|
||||
#include "revng/Support/FileSystem.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
|
||||
template<IsObjectFile T>
|
||||
class LDDTreeObjectFileTraits;
|
||||
|
||||
template<typename D>
|
||||
class Queue {
|
||||
public:
|
||||
struct Entry {
|
||||
/// Identifier of the library to lookup
|
||||
std::string ToImport;
|
||||
|
||||
/// Identifier of the library importing this library
|
||||
std::string ImporterIdentifier;
|
||||
|
||||
/// The canonical path of the importer
|
||||
std::string ImporterCanonicalPath;
|
||||
|
||||
/// Image format-specific information for resolve
|
||||
D Dependency;
|
||||
};
|
||||
|
||||
private:
|
||||
std::queue<Entry> Queue;
|
||||
|
||||
public:
|
||||
bool empty() const { return Queue.empty(); }
|
||||
|
||||
public:
|
||||
void enqueue(std::vector<Entry> &&Entries) {
|
||||
for (Entry &NewEntry : Entries)
|
||||
Queue.push(std::move(NewEntry));
|
||||
}
|
||||
|
||||
Entry pop() {
|
||||
Entry Result = Queue.front();
|
||||
Queue.pop();
|
||||
return Result;
|
||||
}
|
||||
};
|
||||
|
||||
template<IsObjectFile T>
|
||||
using ResolutionInfo = typename LDDTreeObjectFileTraits<T>::ResolutionInfo;
|
||||
|
||||
template<IsObjectFile T>
|
||||
using QueueEntry = typename Queue<ResolutionInfo<T>>::Entry;
|
||||
template<typename T,
|
||||
typename ObjectFile = typename T::ObjectFile,
|
||||
typename ResolutionInfo = typename T::ResolutionInfo,
|
||||
typename QueueEntry = typename Queue<ResolutionInfo>::Entry>
|
||||
concept LDDTreeObjectFileTraitsConcept = requires(T Trait,
|
||||
ObjectFile Object,
|
||||
llvm::StringRef String,
|
||||
QueueEntry Entry,
|
||||
const revng::RootEntry
|
||||
&Root) {
|
||||
typename T::ResolutionInfo;
|
||||
|
||||
{
|
||||
T::getExportedSymbols(Object)
|
||||
} -> std::same_as<std::vector<std::pair<llvm::StringRef, uint64_t>>>;
|
||||
|
||||
{
|
||||
T::getDependencies(Root, String, String, Object)
|
||||
} -> std::same_as<std::vector<QueueEntry>>;
|
||||
|
||||
{
|
||||
T::computeSearchPaths(Root, Entry)
|
||||
} -> std::same_as<llvm::SmallVector<std::string, 16>>;
|
||||
|
||||
{ T::isValidObjectFor(Entry, Object) } -> std::same_as<bool>;
|
||||
};
|
||||
|
||||
template<IsObjectFile T>
|
||||
std::optional<LDDTree::DependencyFile>
|
||||
resolve(const revng::RootEntry &Root, QueueEntry<T> &Entry) {
|
||||
using namespace llvm;
|
||||
using Traits = LDDTreeObjectFileTraits<T>;
|
||||
|
||||
revng_log(LDDTreeLog, "Looking for " << Entry.ToImport);
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
auto &Dependency = Entry.Dependency;
|
||||
|
||||
// TODO: computeSearchPaths could be a generator
|
||||
SmallVector<std::string, 16> SearchPaths = Traits::computeSearchPaths(Root,
|
||||
Entry);
|
||||
|
||||
revng_assert(llvm::none_of(SearchPaths, [](const std::string &Path) {
|
||||
return Path.empty();
|
||||
}));
|
||||
|
||||
for (const auto &Path : SearchPaths) {
|
||||
llvm::SmallString<128> Candidate;
|
||||
llvm::sys::path::append(Candidate, Path, Entry.ToImport);
|
||||
auto MaybeFullPath = Root.getExistingPath(Candidate);
|
||||
|
||||
if (not MaybeFullPath.has_value())
|
||||
continue;
|
||||
|
||||
// Parse the binary
|
||||
auto MaybeBinary = object::createBinary(*MaybeFullPath);
|
||||
if (auto Error = MaybeBinary.takeError()) {
|
||||
revng_log(LDDTreeLog, "Can't create binary: " << Error);
|
||||
llvm::consumeError(std::move(Error));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure it's an object of the right type
|
||||
using namespace object;
|
||||
auto *Binary = dyn_cast<T>(MaybeBinary->getBinary());
|
||||
if (Binary == nullptr) {
|
||||
revng_log(LDDTreeLog,
|
||||
"Found " << Candidate.str()
|
||||
<< " but it's not the right type of object file.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (not Traits::isValidObjectFor(Entry, *Binary)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
revng_log(LDDTreeLog, "Found: " << Candidate.str());
|
||||
|
||||
return LDDTree::DependencyFile(std::move(*MaybeBinary),
|
||||
std::move(*MaybeFullPath),
|
||||
Candidate.str().str());
|
||||
}
|
||||
|
||||
revng_log(LDDTreeLog, Entry.ToImport << " not found");
|
||||
|
||||
if (LDDTreeLog.isEnabled()) {
|
||||
LDDTreeLog << "List of searched paths:\n";
|
||||
for (const auto &Path : SearchPaths)
|
||||
LDDTreeLog << " " << Path << "\n";
|
||||
LDDTreeLog << DoLog;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
/// \p MissingSymbols if specified, the function will try to solve all the
|
||||
// symbols.
|
||||
/// If not specified, the function will try to identify all the libraries,
|
||||
/// recursively.
|
||||
template<IsObjectFile T>
|
||||
const LDDTree LDDTree::fromPath(const revng::RootEntry &Root,
|
||||
llvm::StringRef CanonicalPath,
|
||||
const T &Binary,
|
||||
std::optional<LDDTree::SymbolSet> Symbols) {
|
||||
revng_log(LDDTreeLog, "Computing dependencies of " << CanonicalPath);
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
if (Symbols.has_value())
|
||||
revng_log(LDDTreeLog,
|
||||
"We need to resolve " << Symbols->size() << " symbols");
|
||||
|
||||
using ObjectFileTraits = LDDTreeObjectFileTraits<T>;
|
||||
static_assert(LDDTreeObjectFileTraitsConcept<ObjectFileTraits>);
|
||||
|
||||
LDDTree Result(&Root);
|
||||
|
||||
bool SymbolsRequested = Symbols.has_value();
|
||||
if (SymbolsRequested)
|
||||
Result.MissingSymbols = std::move(*Symbols);
|
||||
|
||||
Queue<typename ObjectFileTraits::ResolutionInfo> Queue;
|
||||
std::set<std::string> Visited;
|
||||
|
||||
Queue.enqueue(ObjectFileTraits::getDependencies(Root,
|
||||
"",
|
||||
CanonicalPath,
|
||||
Binary));
|
||||
|
||||
while (not Queue.empty()) {
|
||||
auto Entry = Queue.pop();
|
||||
|
||||
revng_log(LDDTreeLog, "Considering dependency " << Entry.ToImport);
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
if (Visited.contains(Entry.ToImport)
|
||||
or Result.Dependencies.contains(Entry.ToImport)) {
|
||||
revng_log(LDDTreeLog, "Skipping, we already have this library.");
|
||||
continue;
|
||||
}
|
||||
Visited.insert(Entry.ToImport);
|
||||
|
||||
// Resolve the path of the entry in the queue
|
||||
auto MaybeDependencyFile = resolve<T>(Root, Entry);
|
||||
if (not MaybeDependencyFile.has_value()) {
|
||||
revng_log(LDDTreeLog, "Couldn't resolve the dependency.");
|
||||
continue;
|
||||
}
|
||||
|
||||
revng_log(LDDTreeLog,
|
||||
"Found at " << MaybeDependencyFile->fullPathForExternalTools());
|
||||
|
||||
auto *Binary = llvm::dyn_cast<T>(&MaybeDependencyFile->objectFile());
|
||||
if (Binary == nullptr) {
|
||||
revng_log(LDDTreeLog,
|
||||
"We found a binary, but it is not of the expected type");
|
||||
continue;
|
||||
}
|
||||
|
||||
LDDTree::SymbolAddressMap ProvidedSymbols;
|
||||
|
||||
// Purge from MissingSymbols those exported
|
||||
if (SymbolsRequested) {
|
||||
for (auto [SymbolName, SymbolAddress] :
|
||||
ObjectFileTraits::getExportedSymbols(*Binary)) {
|
||||
auto SymbolNameString = SymbolName.str();
|
||||
if (Result.MissingSymbols->contains(SymbolNameString)) {
|
||||
Result.MissingSymbols->erase(SymbolNameString);
|
||||
ProvidedSymbols[SymbolNameString] = SymbolAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (not SymbolsRequested or ProvidedSymbols.size() > 0) {
|
||||
revng_log(LDDTreeLog, "Registering the library as a dependency");
|
||||
|
||||
if (SymbolsRequested) {
|
||||
if (LDDTreeLog.isEnabled()) {
|
||||
LDDTreeLog << "The library provides " << ProvidedSymbols.size()
|
||||
<< " symbols:\n";
|
||||
for (auto &Symbol : ProvidedSymbols) {
|
||||
LDDTreeLog << " " << Symbol.first << "\n";
|
||||
}
|
||||
LDDTreeLog << DoLog;
|
||||
}
|
||||
}
|
||||
|
||||
// We fulfilled at least a symbol, record it as a dependency
|
||||
LDDTree::Dependency NewDependency(std::move(*MaybeDependencyFile),
|
||||
std::move(ProvidedSymbols),
|
||||
Entry.ImporterIdentifier);
|
||||
Result.Dependencies.emplace(Entry.ToImport, std::move(NewDependency));
|
||||
} else {
|
||||
revng_log(LDDTreeLog, "Ignoring this library");
|
||||
}
|
||||
|
||||
// Check if completed the quest. If so, no need to proceed any further!
|
||||
if (SymbolsRequested and Result.MissingSymbols->size() == 0) {
|
||||
revng_log(LDDTreeLog, "We found all the symbols!");
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Enqueue new entries, once for each dependency
|
||||
revng_log(LDDTreeLog, "Collecting dependencies");
|
||||
LoggerIndent Indent2(LDDTreeLog);
|
||||
|
||||
auto DependencyCanonicalPath = MaybeDependencyFile->canonicalPath();
|
||||
Queue.enqueue(ObjectFileTraits::getDependencies(Root,
|
||||
Entry.ToImport,
|
||||
DependencyCanonicalPath,
|
||||
*Binary));
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include "llvm/Object/Binary.h"
|
||||
#include "llvm/Object/COFF.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
#include "llvm/TargetParser/Triple.h"
|
||||
|
||||
#include "revng/Model/Architecture.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
|
||||
#include "LDDTreeObjectFileTraits.h"
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::object;
|
||||
|
||||
template<>
|
||||
class LDDTreeObjectFileTraits<COFFObjectFile> {
|
||||
public:
|
||||
using ObjectFile = COFFObjectFile;
|
||||
|
||||
struct ResolutionInfo {
|
||||
llvm::Triple::ArchType Architecture{};
|
||||
|
||||
bool is64() const {
|
||||
return Architecture == llvm::Triple::x86_64
|
||||
or Architecture == llvm::Triple::aarch64;
|
||||
}
|
||||
};
|
||||
|
||||
using QueueEntry = typename Queue<ResolutionInfo>::Entry;
|
||||
|
||||
public:
|
||||
static std::vector<std::pair<llvm::StringRef, uint64_t>>
|
||||
getExportedSymbols(const COFFObjectFile &COFFObjectFile) {
|
||||
std::vector<std::pair<llvm::StringRef, uint64_t>> Result;
|
||||
|
||||
for (const ExportDirectoryEntryRef &Entry :
|
||||
COFFObjectFile.export_directories()) {
|
||||
|
||||
StringRef SymbolName;
|
||||
if (Error E = Entry.getSymbolName(SymbolName)) {
|
||||
std::string Message = llvm::toString(std::move(E));
|
||||
revng_log(LDDTreeLog,
|
||||
"Couldn't get symbol name for export entry: " << Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t RVA = 0;
|
||||
if (Error E = Entry.getExportRVA(RVA)) {
|
||||
std::string Message = llvm::toString(std::move(E));
|
||||
revng_log(LDDTreeLog, "Couldn't get symbol RVA: " << Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
Result.push_back({ SymbolName, RVA });
|
||||
}
|
||||
|
||||
return Result;
|
||||
}
|
||||
|
||||
static std::vector<QueueEntry>
|
||||
getDependencies(const revng::RootEntry &Root,
|
||||
StringRef ImporterIdentifier,
|
||||
StringRef ImporterCanonicalPath,
|
||||
const COFFObjectFile &COFFObjectFile) {
|
||||
auto &WindowsRoot = Root.osSpecific<revng::WindowsRoot>();
|
||||
|
||||
std::vector<QueueEntry> Results;
|
||||
for (const ImportDirectoryEntryRef &I :
|
||||
COFFObjectFile.import_directories()) {
|
||||
StringRef LibraryName;
|
||||
if (Error E = I.getName(LibraryName)) {
|
||||
revng_log(LDDTreeLog, "Found an imported symbol without a name.");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto LibraryNameAsString = LibraryName.str();
|
||||
|
||||
revng_log(LDDTreeLog, "Considering library " << LibraryNameAsString);
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
// Get redirections from apisetschema.dll for these libraries
|
||||
auto Redirections = WindowsRoot.getRedirectionsFor(LibraryNameAsString);
|
||||
if (not Redirections.empty()) {
|
||||
revng_log(LDDTreeLog, "Redirecting to:");
|
||||
LoggerIndent Indent(LDDTreeLog);
|
||||
|
||||
for (auto &Redirection : Redirections) {
|
||||
revng_log(LDDTreeLog, Redirection.str());
|
||||
Results.push_back({ Redirection.str(),
|
||||
ImporterIdentifier.str(),
|
||||
ImporterCanonicalPath.str(),
|
||||
{ COFFObjectFile.getArch() } });
|
||||
}
|
||||
} else {
|
||||
Results.push_back({ LibraryNameAsString,
|
||||
ImporterIdentifier.str(),
|
||||
ImporterCanonicalPath.str(),
|
||||
{ COFFObjectFile.getArch() } });
|
||||
}
|
||||
}
|
||||
|
||||
return Results;
|
||||
}
|
||||
|
||||
/// \see https://tinyurl.com/4yduz682
|
||||
static SmallVector<std::string, 16>
|
||||
computeSearchPaths(const revng::RootEntry &Root, QueueEntry &Entry) {
|
||||
// Note: despite taking Root, the results should not be prefixed with root!
|
||||
|
||||
auto &WindowsRoot = Root.osSpecific<revng::WindowsRoot>();
|
||||
bool InputIs64 = Entry.Dependency.is64();
|
||||
|
||||
using namespace llvm::sys::path;
|
||||
|
||||
SmallVector<std::string, 16> SearchPaths;
|
||||
|
||||
// Register the directory of the binary itself
|
||||
SmallString<16> Path;
|
||||
append(Path, Entry.ImporterCanonicalPath);
|
||||
remove_filename(Path);
|
||||
if (not Path.empty())
|
||||
SearchPaths.push_back(Path.str().str());
|
||||
|
||||
// Note: in "non-safe" DLL search mode we should look in the *current*
|
||||
// directory. We really don't want to do that.
|
||||
|
||||
// Register the "system directory"
|
||||
SearchPaths.push_back(WindowsRoot.getSystemDirectory());
|
||||
|
||||
// Note: we should now look in the "16-bit system directory". We really
|
||||
// don't want to do that.
|
||||
|
||||
// Register the "Windows directory"
|
||||
Path.clear();
|
||||
append(Path, StringRef("Windows"));
|
||||
SearchPaths.push_back(Path.str().str());
|
||||
|
||||
#if defined(__WIN32__) || defined(__MINGW32__) || defined(_WIN32)
|
||||
// Process `PATH` environment variable
|
||||
if (Root.isHost()) {
|
||||
if (auto MaybeLibraryPath = llvm::sys::Process::GetEnv("PATH")) {
|
||||
for (StringRef PathFromPath : split(*MaybeLibraryPath, ":")) {
|
||||
Path.clear();
|
||||
append(Path, PathFromPath);
|
||||
SearchPaths.push_back(Path.str().str());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return SearchPaths;
|
||||
}
|
||||
|
||||
static bool isValidObjectFor(QueueEntry &Entry,
|
||||
const COFFObjectFile &COFFObjectFile) {
|
||||
if (COFFObjectFile.getArch() != Entry.Dependency.Architecture) {
|
||||
revng_log(LDDTreeLog, "Incompatible architecture, ignoring.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
using Trait = LDDTreeObjectFileTraits<COFFObjectFile>;
|
||||
static_assert(LDDTreeObjectFileTraitsConcept<Trait>);
|
||||
|
||||
template const LDDTree
|
||||
LDDTree::fromPath<COFFObjectFile>(const revng::RootEntry &Root,
|
||||
StringRef Path,
|
||||
const COFFObjectFile &Binary,
|
||||
std::optional<LDDTree::SymbolSet> Symbols);
|
||||
@@ -21,15 +21,13 @@ We run the [`import-binary` analysis](../../references/analyses.md#import-binary
|
||||
|
||||
```{bash ignore="VirtualSize|FileSize"}
|
||||
$ revng analyze import-binary example -o example.yml
|
||||
$ grep -A7 'Segments:' example.yml
|
||||
$ grep -A5 'Segments:' example.yml
|
||||
Segments:
|
||||
- StartAddress: "0x400000:Generic64"
|
||||
VirtualSize: 2528
|
||||
FileSize: 2528
|
||||
IsReadable: true
|
||||
IsExecutable: true
|
||||
Relocations:
|
||||
- Address: "0x401d88:Generic64"
|
||||
```
|
||||
|
||||
However, the typical workflow does not require the user to manually specify what analyses to run, but there's a set of predefined analyses that should be run on a new project, the *initial autoanalyses*.
|
||||
|
||||
@@ -21,19 +21,6 @@ Functions:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/17913232940792519997-CABIFunctionDefinition"
|
||||
TypeDefinitions:
|
||||
- Kind: TypedefDefinition
|
||||
ID: 14210609572455956746
|
||||
UnderlyingType:
|
||||
Kind: PrimitiveType
|
||||
PrimitiveKind: Void
|
||||
- Kind: TypedefDefinition
|
||||
ID: 3290784804431442976
|
||||
UnderlyingType:
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: DefinedType
|
||||
IsConst: true
|
||||
Definition: "/TypeDefinitions/14210609572455956746-TypedefDefinition"
|
||||
- Kind: CABIFunctionDefinition
|
||||
ID: 5944255354151483434
|
||||
ReturnType:
|
||||
@@ -43,19 +30,27 @@ TypeDefinitions:
|
||||
Arguments:
|
||||
- Index: 0
|
||||
Type:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/3290784804431442976-TypedefDefinition"
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: PrimitiveType
|
||||
PrimitiveKind: Void
|
||||
IsConst: true
|
||||
- Index: 1
|
||||
Type:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/3290784804431442976-TypedefDefinition"
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: PrimitiveType
|
||||
PrimitiveKind: Void
|
||||
IsConst: true
|
||||
- Kind: CABIFunctionDefinition
|
||||
ID: 17913232940792519997
|
||||
Arguments:
|
||||
- Index: 0
|
||||
Type:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/9032570991415481170-TypedefDefinition"
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: PrimitiveType
|
||||
PrimitiveKind: Void
|
||||
- Index: 1
|
||||
Type:
|
||||
Kind: PrimitiveType
|
||||
@@ -66,19 +61,7 @@ TypeDefinitions:
|
||||
PrimitiveKind: Unsigned
|
||||
- Index: 3
|
||||
Type:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/17163826859945186478-TypedefDefinition"
|
||||
- Kind: TypedefDefinition
|
||||
ID: 9032570991415481170
|
||||
UnderlyingType:
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: PrimitiveType
|
||||
PrimitiveKind: Void
|
||||
- Kind: TypedefDefinition
|
||||
ID: 17163826859945186478
|
||||
UnderlyingType:
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/5944255354151483434-CABIFunctionDefinition"
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/5944255354151483434-CABIFunctionDefinition"
|
||||
|
||||
@@ -32,15 +32,10 @@ TypeDefinitions:
|
||||
Arguments:
|
||||
- Index: 0
|
||||
Type:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/1817-TypedefDefinition"
|
||||
- Kind: TypedefDefinition
|
||||
ID: 1817
|
||||
UnderlyingType:
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/1822-StructDefinition"
|
||||
Kind: PointerType
|
||||
PointeeType:
|
||||
Kind: DefinedType
|
||||
Definition: "/TypeDefinitions/1822-StructDefinition"
|
||||
- Kind: StructDefinition
|
||||
ID: 1822
|
||||
Name: Foo
|
||||
|
||||
+69
-15
@@ -2,15 +2,21 @@
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "llvm/Object/COFF.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/DynamicLibrary.h"
|
||||
#include "llvm/Support/raw_os_ostream.h"
|
||||
|
||||
#include "revng/Support/CommandLine.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/InitRevng.h"
|
||||
#include "revng/Support/LDDTree.h"
|
||||
|
||||
namespace cl = llvm::cl;
|
||||
using namespace llvm;
|
||||
using namespace llvm::object;
|
||||
|
||||
class StringPositionalArgument : public cl::opt<std::string> {
|
||||
public:
|
||||
@@ -21,26 +27,74 @@ public:
|
||||
cl::cat(MainCategory)) {}
|
||||
};
|
||||
|
||||
static cl::opt<unsigned> DependencyLevel("dependency-level",
|
||||
cl::desc("Resolve dependencies of the "
|
||||
"depending libraries as "
|
||||
"well."),
|
||||
cl::cat(MainCategory),
|
||||
cl::init(1));
|
||||
static StringPositionalArgument Input("BINARY");
|
||||
|
||||
// TODO: Add --root option to act as SYSROOT.
|
||||
static cl::opt<std::string> CustomCanonicalPath("canonical-path",
|
||||
cl::desc("Canonical path for "
|
||||
"the lookup of "
|
||||
"dependencies "
|
||||
"and debug symbols"),
|
||||
cl::cat(MainCategory));
|
||||
|
||||
StringPositionalArgument Input("Input binary");
|
||||
static cl::list<std::string>
|
||||
Symbols(cl::desc("[SYMBOL [SYMBOL ...]"), cl::Positional, cl::ZeroOrMore);
|
||||
|
||||
static llvm::ExitOnError AbortOnError;
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
revng::InitRevng X(argc, argv, "", { &MainCategory });
|
||||
|
||||
LDDTree Dependencies;
|
||||
lddtree(Dependencies, Input, DependencyLevel);
|
||||
for (auto &Library : Dependencies) {
|
||||
llvm::outs() << "Dependencies for " << Library.first << ":\n";
|
||||
for (auto &DependencyLibrary : Library.second)
|
||||
llvm::outs() << " " << DependencyLibrary << "\n";
|
||||
// Collect requested symbols
|
||||
std::optional<LDDTree::SymbolSet> RequestedSymbols;
|
||||
RequestedSymbols.emplace();
|
||||
for (std::string &Symbol : Symbols)
|
||||
RequestedSymbols->insert(Symbol);
|
||||
|
||||
// Load the binary
|
||||
auto MaybeBuffer = llvm::MemoryBuffer::getFileOrSTDIN(Input, false, false);
|
||||
AbortOnError(llvm::errorCodeToError(MaybeBuffer.getError()));
|
||||
|
||||
auto BinaryOrError = llvm::object::createBinary(**MaybeBuffer);
|
||||
AbortOnError(BinaryOrError.takeError());
|
||||
|
||||
StringRef CanonicalPath = Input;
|
||||
|
||||
if (CustomCanonicalPath.getNumOccurrences() > 0) {
|
||||
CanonicalPath = CustomCanonicalPath;
|
||||
}
|
||||
|
||||
// Collect dependencies
|
||||
auto Handler = [&](auto &ObjectFile) {
|
||||
return LDDTree::fromPath(CanonicalPath,
|
||||
ObjectFile,
|
||||
RequestedSymbols->size() > 0 ? RequestedSymbols :
|
||||
std::nullopt);
|
||||
};
|
||||
|
||||
std::optional<LDDTree> MaybeDependencies;
|
||||
llvm::object::Binary *Binary = BinaryOrError->get();
|
||||
if (auto *ObjectFile = dyn_cast<llvm::object::ELF32BEObjectFile>(Binary)) {
|
||||
MaybeDependencies = Handler(*ObjectFile);
|
||||
} else if (auto *ObjectFile = dyn_cast<ELF32LEObjectFile>(Binary)) {
|
||||
MaybeDependencies = Handler(*ObjectFile);
|
||||
} else if (auto *ObjectFile = dyn_cast<ELF32BEObjectFile>(Binary)) {
|
||||
MaybeDependencies = Handler(*ObjectFile);
|
||||
} else if (auto *ObjectFile = dyn_cast<ELF64LEObjectFile>(Binary)) {
|
||||
MaybeDependencies = Handler(*ObjectFile);
|
||||
} else if (auto *ObjectFile = dyn_cast<ELF64BEObjectFile>(Binary)) {
|
||||
MaybeDependencies = Handler(*ObjectFile);
|
||||
} else if (auto *ObjectFile = dyn_cast<COFFObjectFile>(Binary)) {
|
||||
MaybeDependencies = Handler(*ObjectFile);
|
||||
} else {
|
||||
dbg << "Unknown image format";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Dump
|
||||
if (MaybeDependencies.has_value()) {
|
||||
MaybeDependencies->dump(llvm::outs());
|
||||
} else {
|
||||
dbg << "Couldn't compute dependencies\n";
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
@@ -2,12 +2,17 @@
|
||||
// This file is distributed under the MIT License. See LICENSE.md for details.
|
||||
//
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
|
||||
#include "llvm/BinaryFormat/Magic.h"
|
||||
#include "llvm/Object/COFF.h"
|
||||
#include "llvm/Object/ELFObjectFile.h"
|
||||
#include "llvm/Object/ObjectFile.h"
|
||||
#include "llvm/Support/Casting.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/ToolOutputFile.h"
|
||||
|
||||
#include "revng/ABI/DefaultFunctionPrototype.h"
|
||||
@@ -15,10 +20,11 @@
|
||||
#include "revng/Model/Importer/Binary/Options.h"
|
||||
#include "revng/Model/Importer/DebugInfo/DwarfImporter.h"
|
||||
#include "revng/Model/Importer/DebugInfo/PDBImporter.h"
|
||||
#include "revng/Support/Configuration.h"
|
||||
#include "revng/Support/InitRevng.h"
|
||||
#include "revng/Support/MetaAddress.h"
|
||||
|
||||
namespace cl = llvm::cl;
|
||||
using namespace llvm;
|
||||
|
||||
static Logger Log("import-debug-info");
|
||||
|
||||
@@ -30,46 +36,61 @@ static cl::opt<std::string> InputFilename(cl::Positional,
|
||||
cl::init("-"),
|
||||
cl::value_desc("filename"));
|
||||
|
||||
static cl::opt<std::string> OutputFilename("o",
|
||||
cl::cat(ThisToolCategory),
|
||||
llvm::cl::desc("Override output "
|
||||
"filename"),
|
||||
llvm::cl::init("-"),
|
||||
llvm::cl::value_desc("filename"));
|
||||
static cl::opt<std::string> OutputPath("o",
|
||||
cl::cat(ThisToolCategory),
|
||||
cl::desc("Override output "
|
||||
"filename"),
|
||||
cl::init("-"),
|
||||
cl::value_desc("filename"));
|
||||
|
||||
static cl::opt<std::string> Root("root",
|
||||
cl::cat(ThisToolCategory),
|
||||
cl::desc("Root where to look for debug "
|
||||
"symbols"),
|
||||
cl::init("/"),
|
||||
cl::value_desc("ROOT_PATH"));
|
||||
|
||||
int main(int Argc, char *Argv[]) {
|
||||
revng::InitRevng X(Argc, Argv, "", { &ThisToolCategory });
|
||||
|
||||
// Open output.
|
||||
llvm::ExitOnError ExitOnError;
|
||||
ExitOnError ExitOnError;
|
||||
std::error_code EC;
|
||||
llvm::ToolOutputFile OutputFile(OutputFilename,
|
||||
EC,
|
||||
llvm::sys::fs::OpenFlags::OF_Text);
|
||||
ToolOutputFile OutputFile(OutputPath, EC, sys::fs::OpenFlags::OF_Text);
|
||||
if (EC)
|
||||
ExitOnError(llvm::createStringError(EC, EC.message()));
|
||||
|
||||
auto BinaryOrErr = llvm::object::createBinary(InputFilename);
|
||||
if (not BinaryOrErr) {
|
||||
revng_log(Log, "Unable to create binary: " << BinaryOrErr.takeError());
|
||||
llvm::consumeError(BinaryOrErr.takeError());
|
||||
return 1;
|
||||
}
|
||||
|
||||
using ObjectFileType = llvm::object::ObjectFile;
|
||||
using COFFObjectFileType = llvm::object::COFFObjectFile;
|
||||
auto &ObjectFile = *llvm::cast<ObjectFileType>(BinaryOrErr->getBinary());
|
||||
ExitOnError(createStringError(EC, EC.message()));
|
||||
|
||||
const ImporterOptions &Options = importerOptions();
|
||||
|
||||
// Import debug info from both PE and ELF.
|
||||
TupleTree<model::Binary> Model;
|
||||
if (llvm::isa<llvm::object::ELFObjectFileBase>(&ObjectFile)) {
|
||||
DwarfImporter Importer(Model);
|
||||
|
||||
auto MaybeBinary = object::createBinary(InputFilename);
|
||||
|
||||
using ObjectFileType = object::ObjectFile;
|
||||
using COFFObjectFileType = object::COFFObjectFile;
|
||||
|
||||
ObjectFileType *ObjectFile = nullptr;
|
||||
if (MaybeBinary) {
|
||||
ObjectFile = cast<ObjectFileType>(MaybeBinary->getBinary());
|
||||
} else {
|
||||
consumeError(MaybeBinary.takeError());
|
||||
}
|
||||
|
||||
file_magic FileType;
|
||||
EC = identify_magic(InputFilename, FileType);
|
||||
|
||||
if (EC) {
|
||||
dbg << "Couldn't identify file type: " << EC.message() << "\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (dyn_cast_or_null<object::ELFObjectFileBase>(ObjectFile)) {
|
||||
DwarfImporter Importer(Model, std::nullopt);
|
||||
Importer.import(InputFilename, Options);
|
||||
} else if (auto *Binary = llvm::dyn_cast<COFFObjectFileType>(&ObjectFile)) {
|
||||
} else if (auto *Binary = dyn_cast_or_null<COFFObjectFileType>(ObjectFile)) {
|
||||
MetaAddress ImageBase = MetaAddress::invalid();
|
||||
auto LLVMArch = ObjectFile.makeTriple().getArch();
|
||||
auto LLVMArch = ObjectFile->makeTriple().getArch();
|
||||
Model->Architecture() = model::Architecture::fromLLVMArchitecture(LLVMArch);
|
||||
|
||||
if (Model->DefaultABI() == model::ABI::Invalid) {
|
||||
@@ -85,21 +106,27 @@ int main(int Argc, char *Argv[]) {
|
||||
// Create a default prototype.
|
||||
Model->DefaultPrototype() = abi::registerDefaultFunctionPrototype(*Model);
|
||||
|
||||
const llvm::object::pe32_header *PE32Header = Binary->getPE32Header();
|
||||
const object::pe32_header *PE32Header = Binary->getPE32Header();
|
||||
auto Architecture = model::Architecture::fromLLVMArchitecture(LLVMArch);
|
||||
if (PE32Header) {
|
||||
ImageBase = MetaAddress::fromPC(Architecture, PE32Header->ImageBase);
|
||||
} else {
|
||||
const llvm::object::pe32plus_header
|
||||
*PE32PlusHeader = Binary->getPE32PlusHeader();
|
||||
const auto *PE32PlusHeader = Binary->getPE32PlusHeader();
|
||||
if (not PE32PlusHeader)
|
||||
return EXIT_FAILURE;
|
||||
|
||||
// PE32+ Header.
|
||||
ImageBase = MetaAddress::fromPC(Architecture, PE32PlusHeader->ImageBase);
|
||||
}
|
||||
PDBImporter Importer(Model, ImageBase);
|
||||
Importer.import(*Binary, InputFilename, Options);
|
||||
|
||||
PDBImporter Importer(Model, ImageBase, std::nullopt);
|
||||
Importer.importPDB(InputFilename, Options);
|
||||
} else if (FileType == file_magic::pdb) {
|
||||
PDBImporter Importer(Model, MetaAddress::invalid(), std::nullopt);
|
||||
Importer.importPDB(InputFilename, Options);
|
||||
} else {
|
||||
dbg << "Unexpected file format\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Serialize the model.
|
||||
|
||||
Reference in New Issue
Block a user