mirror of
https://github.com/revng/revng
synced 2026-06-21 14:07:57 +00:00
c8de226f56
Not checking for primitives there doesn't change anything anyway, as `purge-invalid-types` model pass will clean anything invalid still left after the importer.
1488 lines
48 KiB
C++
1488 lines
48 KiB
C++
/// \file DwarfImporter.cpp
|
|
|
|
//
|
|
// This file is distributed under the MIT License. See LICENSE.md for details.
|
|
//
|
|
|
|
#include <csignal>
|
|
#include <optional>
|
|
|
|
#include "llvm/ADT/DepthFirstIterator.h"
|
|
#include "llvm/ADT/EquivalenceClasses.h"
|
|
#include "llvm/ADT/Triple.h"
|
|
#include "llvm/BinaryFormat/Dwarf.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFContext.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFDie.h"
|
|
#include "llvm/DebugInfo/DWARF/DWARFFormValue.h"
|
|
#include "llvm/IR/DebugInfoMetadata.h"
|
|
#include "llvm/Object/ELFObjectFile.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/Process.h"
|
|
#include "llvm/Support/Progress.h"
|
|
#include "llvm/Support/raw_os_ostream.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include "revng/ADT/STLExtras.h"
|
|
#include "revng/Model/Binary.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/Processing.h"
|
|
#include "revng/Support/Assert.h"
|
|
#include "revng/Support/Debug.h"
|
|
#include "revng/Support/PathList.h"
|
|
#include "revng/Support/ProgramRunner.h"
|
|
|
|
#include "ImportDebugInfoHelper.h"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::dwarf;
|
|
|
|
static Logger<> DILogger("dwarf-importer");
|
|
static const std::string GlobalDebugDirectory = "/usr/lib/debug/";
|
|
|
|
template<typename M>
|
|
class ScopedSetElement {
|
|
private:
|
|
M &Set;
|
|
typename M::value_type ToInsert;
|
|
|
|
public:
|
|
ScopedSetElement(M &Set, typename M::value_type ToInsert) :
|
|
Set(Set), ToInsert(ToInsert) {}
|
|
~ScopedSetElement() { Set.erase(ToInsert); }
|
|
|
|
public:
|
|
bool insert() {
|
|
auto It = Set.find(ToInsert);
|
|
if (It != Set.end()) {
|
|
return false;
|
|
} else {
|
|
Set.insert(It, ToInsert);
|
|
return true;
|
|
}
|
|
}
|
|
};
|
|
|
|
static model::PrimitiveKind::Values dwarfEncodingToModel(uint32_t Encoding) {
|
|
switch (Encoding) {
|
|
case dwarf::DW_ATE_unsigned_char:
|
|
case dwarf::DW_ATE_unsigned:
|
|
case dwarf::DW_ATE_boolean:
|
|
return model::PrimitiveKind::Unsigned;
|
|
case dwarf::DW_ATE_signed_char:
|
|
case dwarf::DW_ATE_signed:
|
|
return model::PrimitiveKind::Signed;
|
|
case dwarf::DW_ATE_float:
|
|
return model::PrimitiveKind::Float;
|
|
default:
|
|
return model::PrimitiveKind::Invalid;
|
|
}
|
|
}
|
|
|
|
static std::optional<uint64_t>
|
|
getUnsignedOrSigned(const DWARFFormValue &Value) {
|
|
auto MaybeUnsigned = Value.getAsUnsignedConstant();
|
|
auto MaybeSigned = Value.getAsSignedConstant();
|
|
if (MaybeUnsigned)
|
|
return *MaybeUnsigned;
|
|
else if (MaybeSigned)
|
|
return *MaybeSigned;
|
|
else
|
|
return {};
|
|
}
|
|
|
|
static std::optional<uint64_t> getUnsignedOrSigned(const DWARFDie &Die,
|
|
dwarf::Attribute Attribute) {
|
|
auto Value = Die.find(Attribute);
|
|
if (not Value)
|
|
return {};
|
|
else
|
|
return getUnsignedOrSigned(*Value);
|
|
}
|
|
|
|
static std::optional<uint64_t> getAddress(const DWARFFormValue &Value) {
|
|
auto MaybeResult = Value.getAsAddress();
|
|
if (MaybeResult)
|
|
return *MaybeResult;
|
|
else
|
|
return {};
|
|
}
|
|
|
|
static std::optional<uint64_t> getAddress(const DWARFDie &Die) {
|
|
// TODO: Add support for DW_AT_pc, which is DWARF 5 Standard version of the
|
|
// attribute.
|
|
auto Value = Die.find(DW_AT_low_pc);
|
|
if (not Value) {
|
|
auto Ranges = Die.find(DW_AT_ranges);
|
|
if (not Ranges)
|
|
return {};
|
|
|
|
auto Offset = *Ranges->getAsSectionOffset();
|
|
auto MaybeRange = Die.getDwarfUnit()->findRnglistFromOffset(Offset);
|
|
if (auto Error = MaybeRange.takeError()) {
|
|
revng_log(DILogger, "findRnglistFromOffset failed: " << Error);
|
|
consumeError(std::move(Error));
|
|
return {};
|
|
}
|
|
|
|
// TODO: This is a vector, so we may want to return LowPC from every range
|
|
// we found.
|
|
return MaybeRange->begin()->LowPC;
|
|
} else {
|
|
return getAddress(*Value);
|
|
}
|
|
}
|
|
|
|
static bool isTrue(const DWARFFormValue &Value) {
|
|
return getUnsignedOrSigned(Value) != 0;
|
|
}
|
|
|
|
template<typename S, typename O, typename... A>
|
|
void dumpToStream(S &Stream, const O &Object, A... Args) {
|
|
std::string Buffer;
|
|
{
|
|
llvm::raw_string_ostream WrapperStream(Buffer);
|
|
Object.dump(WrapperStream, Args...);
|
|
}
|
|
Stream << Buffer;
|
|
}
|
|
|
|
static void commentDie(const DWARFDie &Die, const Twine &Reason) {
|
|
if (DILogger.isEnabled()) {
|
|
DILogger << Reason.str();
|
|
dumpToStream(DILogger, Die, 0);
|
|
DILogger << DoLog;
|
|
}
|
|
}
|
|
|
|
static void reportIgnoredDie(const DWARFDie &Die, const Twine &Reason) {
|
|
commentDie(Die, "Ignoring DWARF die: " + Reason);
|
|
}
|
|
|
|
class DwarfToModelConverter : public BinaryImporterHelper {
|
|
private:
|
|
DwarfImporter &Importer;
|
|
TupleTree<model::Binary> &Model;
|
|
size_t Index;
|
|
size_t AltIndex;
|
|
size_t TypesWithIdentityCount;
|
|
DWARFContext &Context;
|
|
std::map<size_t, const model::TypeDefinition *> Placeholders;
|
|
std::set<const model::TypeDefinition *> InvalidPrimitives;
|
|
std::set<const DWARFDie *> InProgressDies;
|
|
|
|
public:
|
|
DwarfToModelConverter(DwarfImporter &Importer,
|
|
DWARFContext &Context,
|
|
size_t Index,
|
|
size_t AltIndex,
|
|
uint64_t PreferredBaseAddress) :
|
|
BinaryImporterHelper(*Importer.getModel(), PreferredBaseAddress, DILogger),
|
|
Importer(Importer),
|
|
Model(Importer.getModel()),
|
|
Index(Index),
|
|
AltIndex(AltIndex),
|
|
Context(Context) {
|
|
|
|
// When we import DWARF, we assume we already have parsed Segments
|
|
processSegments();
|
|
|
|
BaseAddress = PreferredBaseAddress;
|
|
|
|
// Ensure the architecture is consistent.
|
|
auto Arch = model::Architecture::fromLLVMArchitecture(Context.getArch());
|
|
if (Model->Architecture() == model::Architecture::Invalid)
|
|
Model->Architecture() = Arch;
|
|
|
|
// Set default ABI
|
|
if (Model->DefaultABI() == model::ABI::Invalid) {
|
|
if (auto ABI = model::ABI::getDefaultForELF(Model->Architecture())) {
|
|
Model->DefaultABI() = ABI.value();
|
|
} else {
|
|
auto AName = model::Architecture::getName(Model->Architecture()).str();
|
|
revng_abort(("Unsupported architecture for ELF: " + AName).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
model::ABI::Values getABI(CallingConvention CC = DW_CC_normal) const {
|
|
if (CC != DW_CC_normal)
|
|
return model::ABI::Invalid;
|
|
|
|
// NOTE: static functions do not always follow the standard calling
|
|
// convention which is a problem since `CABIFunctionTypes` we generate
|
|
// for them do not correspond to the real functions, leading to
|
|
// problems downstream.
|
|
// TODO: investigate.
|
|
|
|
return Model->DefaultABI();
|
|
}
|
|
|
|
const model::UpcastableType &record(const DWARFDie &Die,
|
|
model::UpcastableType &&Type,
|
|
bool IsNotPlaceholder) {
|
|
revng_assert(!Type.isEmpty());
|
|
if (not IsNotPlaceholder) {
|
|
// `model::UpcastableType::empty()` (as in, no definition) represents
|
|
// a primitive placeholder.
|
|
Placeholders[Die.getOffset()] = Type->tryGetAsDefinition();
|
|
}
|
|
|
|
return Importer.recordType({ Index, Die.getOffset() }, std::move(Type));
|
|
}
|
|
|
|
enum class TypeSearchResult {
|
|
Absent,
|
|
PlaceholderType,
|
|
RegularType
|
|
};
|
|
|
|
std::pair<TypeSearchResult, model::UpcastableType>
|
|
findType(const DWARFDie &Die) {
|
|
auto Found = Importer.findType({ Index, Die.getOffset() });
|
|
if (Found.isEmpty())
|
|
return { TypeSearchResult::Absent, model::UpcastableType::empty() };
|
|
else if (Placeholders.contains(Die.getOffset()))
|
|
return { TypeSearchResult::PlaceholderType, std::move(Found) };
|
|
else
|
|
return { TypeSearchResult::RegularType, std::move(Found) };
|
|
}
|
|
|
|
private:
|
|
static bool isType(dwarf::Tag Tag) {
|
|
switch (Tag) {
|
|
case llvm::dwarf::DW_TAG_base_type:
|
|
case llvm::dwarf::DW_TAG_typedef:
|
|
case llvm::dwarf::DW_TAG_restrict_type:
|
|
case llvm::dwarf::DW_TAG_volatile_type:
|
|
case llvm::dwarf::DW_TAG_structure_type:
|
|
case llvm::dwarf::DW_TAG_union_type:
|
|
case llvm::dwarf::DW_TAG_enumeration_type:
|
|
case llvm::dwarf::DW_TAG_array_type:
|
|
case llvm::dwarf::DW_TAG_const_type:
|
|
case llvm::dwarf::DW_TAG_pointer_type:
|
|
case llvm::dwarf::DW_TAG_subroutine_type:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool hasModelIdentity(dwarf::Tag Tag) {
|
|
revng_assert(isType(Tag));
|
|
switch (Tag) {
|
|
case llvm::dwarf::DW_TAG_base_type:
|
|
case llvm::dwarf::DW_TAG_typedef:
|
|
case llvm::dwarf::DW_TAG_restrict_type:
|
|
case llvm::dwarf::DW_TAG_volatile_type:
|
|
case llvm::dwarf::DW_TAG_structure_type:
|
|
case llvm::dwarf::DW_TAG_union_type:
|
|
case llvm::dwarf::DW_TAG_enumeration_type:
|
|
case llvm::dwarf::DW_TAG_subroutine_type:
|
|
return true;
|
|
case llvm::dwarf::DW_TAG_array_type:
|
|
case llvm::dwarf::DW_TAG_const_type:
|
|
case llvm::dwarf::DW_TAG_pointer_type:
|
|
return false;
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
void createInvalidPrimitivePlaceholder(const DWARFDie &Die) {
|
|
auto &&[Definition, Type] = Model->makeTypedefDefinition();
|
|
record(Die, std::move(Type), false);
|
|
InvalidPrimitives.insert(&Definition);
|
|
}
|
|
|
|
void createType(const DWARFDie &Die) {
|
|
auto Tag = Die.getTag();
|
|
revng_assert(hasModelIdentity(Tag));
|
|
|
|
switch (Tag) {
|
|
case llvm::dwarf::DW_TAG_base_type: {
|
|
uint8_t Size = 0;
|
|
model::PrimitiveKind::Values Kind = model::PrimitiveKind::Invalid;
|
|
|
|
auto MaybeByteSize = Die.find(DW_AT_byte_size);
|
|
if (MaybeByteSize)
|
|
Size = *MaybeByteSize->getAsUnsignedConstant();
|
|
|
|
auto MaybeEncoding = Die.find(DW_AT_encoding);
|
|
if (MaybeEncoding)
|
|
Kind = dwarfEncodingToModel(*MaybeEncoding->getAsUnsignedConstant());
|
|
|
|
if (Kind == model::PrimitiveKind::Invalid) {
|
|
reportIgnoredDie(Die, "Unknown primitive type");
|
|
createInvalidPrimitivePlaceholder(Die);
|
|
return;
|
|
}
|
|
|
|
if (Size == 0) {
|
|
reportIgnoredDie(Die, "Invalid size for primitive type");
|
|
createInvalidPrimitivePlaceholder(Die);
|
|
return;
|
|
}
|
|
|
|
record(Die, model::PrimitiveType::make(Kind, Size), true);
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_subroutine_type:
|
|
record(Die, Model->makeCABIFunctionDefinition().second, false);
|
|
break;
|
|
|
|
case llvm::dwarf::DW_TAG_typedef:
|
|
case llvm::dwarf::DW_TAG_restrict_type:
|
|
case llvm::dwarf::DW_TAG_volatile_type:
|
|
record(Die, std::move(Model->makeTypedefDefinition().second), false);
|
|
break;
|
|
|
|
case llvm::dwarf::DW_TAG_structure_type:
|
|
record(Die, std::move(Model->makeStructDefinition().second), false);
|
|
break;
|
|
|
|
case llvm::dwarf::DW_TAG_union_type:
|
|
if (auto MaybeByteSize = Die.find(DW_AT_byte_size);
|
|
MaybeByteSize and not Die.hasChildren()) {
|
|
// Handle small empty unions, usually due to transparent unions
|
|
auto Size = *MaybeByteSize->getAsUnsignedConstant();
|
|
record(Die, model::PrimitiveType::makeGeneric(Size), true);
|
|
} else {
|
|
// Handle regular unions
|
|
record(Die, std::move(Model->makeUnionDefinition().second), false);
|
|
}
|
|
break;
|
|
|
|
case llvm::dwarf::DW_TAG_enumeration_type:
|
|
record(Die, std::move(Model->makeEnumDefinition().second), false);
|
|
break;
|
|
|
|
default:
|
|
reportIgnoredDie(Die, "Unexpected type");
|
|
}
|
|
}
|
|
|
|
void handleTypeDeclaration(const DWARFDie &Die) {
|
|
auto Tag = Die.getTag();
|
|
if ((Tag == llvm::dwarf::DW_TAG_structure_type
|
|
or Tag == llvm::dwarf::DW_TAG_union_type
|
|
or Tag == llvm::dwarf::DW_TAG_enumeration_type)) {
|
|
record(Die, model::PrimitiveType::makeVoid(), true);
|
|
} else {
|
|
reportIgnoredDie(Die,
|
|
"Unexpected declaration for tag "
|
|
+ llvm::dwarf::TagString(Tag));
|
|
}
|
|
}
|
|
|
|
void materializeTypesWithIdentity() {
|
|
SmallVector<llvm::DWARFUnit *, 16> CompileUnits;
|
|
for (const auto &CU : Context.compile_units())
|
|
CompileUnits.push_back(CU.get());
|
|
|
|
Task T(CompileUnits.size(), "Compile units");
|
|
for (llvm::DWARFUnit *CU : CompileUnits) {
|
|
T.advance("", true);
|
|
|
|
SmallVector<llvm::DWARFDebugInfoEntry *, 16> Dies;
|
|
for (llvm::DWARFDebugInfoEntry &Entry : CU->dies())
|
|
Dies.push_back(&Entry);
|
|
|
|
for (DWARFDebugInfoEntry *Entry : Dies) {
|
|
DWARFDie Die = { CU, Entry };
|
|
auto Tag = Die.getTag();
|
|
if (isType(Tag) and hasModelIdentity(Tag)) {
|
|
auto MaybeDeclaration = Die.find(DW_AT_declaration);
|
|
if (MaybeDeclaration && isTrue(*MaybeDeclaration)) {
|
|
handleTypeDeclaration(Die);
|
|
} else {
|
|
createType(Die);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TypesWithIdentityCount = Placeholders.size();
|
|
}
|
|
|
|
std::string getName(const DWARFDie &Die) const {
|
|
if (auto MaybeName = Die.find(DW_AT_name)) {
|
|
auto MaybeString = MaybeName->getAsCString();
|
|
if (auto Error = MaybeString.takeError()) {
|
|
revng_log(DILogger, "Can't get DIE name: " << Error);
|
|
consumeError(std::move(Error));
|
|
return {};
|
|
} else {
|
|
return *MaybeString;
|
|
}
|
|
} else if (auto MaybeOrigin = Die.find(DW_AT_abstract_origin)) {
|
|
DWARFDie Origin = Context.getDIEForOffset(*MaybeOrigin->getAsReference());
|
|
return getName(Origin);
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
static bool isNoReturn(DWARFUnit &CU, const DWARFDie &Die) {
|
|
auto Tag = Die.getTag();
|
|
revng_assert(Tag == DW_TAG_subprogram);
|
|
|
|
if (Die.find(DW_AT_noreturn))
|
|
return true;
|
|
|
|
// Check if the specification of this subprogram defines it.
|
|
auto SpecificationAttribute = Die.find(DW_AT_specification);
|
|
if (SpecificationAttribute) {
|
|
if (SpecificationAttribute->getAsReference()) {
|
|
auto DieOffset = *(SpecificationAttribute->getAsReference());
|
|
DWARFDie SpecificationDie = CU.getDIEForOffset(DieOffset);
|
|
if (SpecificationDie.find(DW_AT_noreturn))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
RecursiveCoroutine<model::UpcastableType> makeType(const DWARFDie &Die) {
|
|
if (auto Type = Die.find(DW_AT_type)) {
|
|
if (Type->getForm() == llvm::dwarf::DW_FORM_GNU_ref_alt) {
|
|
rc_return Importer.findType({ AltIndex, Type->getRawUValue() }).copy();
|
|
} else {
|
|
DWARFDie InnerDie = Context.getDIEForOffset(*Type->getAsReference());
|
|
rc_return rc_recur resolveType(InnerDie, false);
|
|
}
|
|
} else if (auto MaybeOrigin = Die.find(DW_AT_abstract_origin)) {
|
|
DWARFDie Origin = Context.getDIEForOffset(*MaybeOrigin->getAsReference());
|
|
rc_return rc_recur makeType(Origin);
|
|
} else {
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
}
|
|
|
|
RecursiveCoroutine<model::UpcastableType>
|
|
makeTypeOrVoid(const DWARFDie &Die) {
|
|
if (model::UpcastableType Result = rc_recur makeType(Die))
|
|
rc_return Result;
|
|
else
|
|
rc_return model::PrimitiveType::makeVoid();
|
|
}
|
|
|
|
RecursiveCoroutine<void>
|
|
resolveTypeWithIdentity(const DWARFDie &Die, model::UpcastableType &Type) {
|
|
using namespace model;
|
|
|
|
auto Offset = Die.getOffset();
|
|
auto Tag = Die.getTag();
|
|
|
|
revng_assert(Placeholders.contains(Offset));
|
|
|
|
std::string Name = getName(Die);
|
|
|
|
model::TypeDefinition &Definition = *Type->tryGetAsDefinition();
|
|
if (InvalidPrimitives.contains(&Definition))
|
|
rc_return;
|
|
|
|
switch (Tag) {
|
|
case llvm::dwarf::DW_TAG_subroutine_type: {
|
|
auto &FunctionType = cast<model::CABIFunctionDefinition>(Definition);
|
|
FunctionType.Name() = Name;
|
|
FunctionType.ABI() = getABI();
|
|
|
|
if (FunctionType.ABI() == model::ABI::Invalid) {
|
|
reportIgnoredDie(Die, "Unknown calling convention");
|
|
rc_return;
|
|
}
|
|
|
|
FunctionType.ReturnType() = rc_recur makeType(Die);
|
|
|
|
uint64_t Index = 0;
|
|
for (const DWARFDie &ChildDie : Die.children()) {
|
|
if (ChildDie.getTag() == DW_TAG_formal_parameter) {
|
|
|
|
model::UpcastableType ArgumentType = rc_recur makeType(ChildDie);
|
|
if (ArgumentType.isEmpty()) {
|
|
reportIgnoredDie(Die,
|
|
"The type of argument " + Twine(++Index)
|
|
+ " cannot be resolved");
|
|
rc_return;
|
|
}
|
|
|
|
// Note: at this stage we don't check the size. If an argument is
|
|
// unsized, the function will be purged later on.
|
|
FunctionType.addArgument(std::move(ArgumentType));
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_typedef:
|
|
case llvm::dwarf::DW_TAG_restrict_type:
|
|
case llvm::dwarf::DW_TAG_volatile_type: {
|
|
auto &Typedef = cast<model::TypedefDefinition>(Definition);
|
|
Typedef.Name() = Name;
|
|
Typedef.UnderlyingType() = rc_recur makeTypeOrVoid(Die);
|
|
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_structure_type: {
|
|
auto MaybeSize = Die.find(DW_AT_byte_size);
|
|
|
|
if (not MaybeSize) {
|
|
reportIgnoredDie(Die, "Struct has no size");
|
|
rc_return;
|
|
}
|
|
|
|
auto &Struct = cast<model::StructDefinition>(Definition);
|
|
Struct.Name() = Name;
|
|
Struct.Size() = *MaybeSize->getAsUnsignedConstant();
|
|
|
|
uint64_t Index = 0;
|
|
for (const DWARFDie &ChildDie : Die.children()) {
|
|
if (ChildDie.getTag() == DW_TAG_member) {
|
|
|
|
// Collect offset
|
|
auto MaybeOffset = ChildDie.find(DW_AT_data_member_location);
|
|
|
|
if (not MaybeOffset) {
|
|
reportIgnoredDie(ChildDie, "Struct field has no offset");
|
|
continue;
|
|
}
|
|
|
|
auto Offset = *MaybeOffset->getAsUnsignedConstant();
|
|
|
|
if (ChildDie.find(DW_AT_bit_size)) {
|
|
reportIgnoredDie(ChildDie, "Ignoring bitfield in struct");
|
|
continue;
|
|
}
|
|
|
|
model::UpcastableType MemberType = rc_recur makeType(ChildDie);
|
|
if (MemberType.isEmpty()) {
|
|
reportIgnoredDie(Die,
|
|
"The type of member " + Twine(Index + 1)
|
|
+ " cannot be resolved");
|
|
rc_return;
|
|
}
|
|
|
|
// Create new field
|
|
auto &Field = Struct.Fields()[Offset];
|
|
Field.Name() = getName(ChildDie);
|
|
Field.Type() = std::move(MemberType);
|
|
|
|
++Index;
|
|
}
|
|
}
|
|
|
|
if (Index == 0) {
|
|
reportIgnoredDie(Die, "Struct has no fields");
|
|
rc_return;
|
|
}
|
|
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_union_type: {
|
|
auto &Union = cast<model::UnionDefinition>(Definition);
|
|
Union.Name() = Name;
|
|
|
|
for (const DWARFDie &ChildDie : Die.children()) {
|
|
if (ChildDie.getTag() == DW_TAG_member) {
|
|
model::UpcastableType MemberType = rc_recur makeType(ChildDie);
|
|
if (MemberType.isEmpty()) {
|
|
reportIgnoredDie(Die,
|
|
"The type of one of the fields cannot be "
|
|
"resolved");
|
|
rc_return;
|
|
}
|
|
|
|
// Create new field
|
|
auto &Field = Union.addField(std::move(MemberType));
|
|
Field.Name() = getName(ChildDie);
|
|
}
|
|
}
|
|
|
|
if (Union.Fields().empty()) {
|
|
reportIgnoredDie(Die, "Union has no fields");
|
|
rc_return;
|
|
}
|
|
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_enumeration_type: {
|
|
auto &Enum = llvm::cast<model::EnumDefinition>(Definition);
|
|
Enum.Name() = Name;
|
|
|
|
const model::UpcastableType UnderlyingType = rc_recur makeType(Die);
|
|
if (UnderlyingType.isEmpty()) {
|
|
reportIgnoredDie(Die, "The enum underlying type cannot be resolved");
|
|
rc_return;
|
|
}
|
|
|
|
Enum.UnderlyingType() = std::move(UnderlyingType);
|
|
|
|
uint64_t Index = 0;
|
|
for (const DWARFDie &ChildDie : Die.children()) {
|
|
if (ChildDie.getTag() == DW_TAG_enumerator) {
|
|
// Collect value
|
|
auto MaybeValue = getUnsignedOrSigned(ChildDie, DW_AT_const_value);
|
|
if (not MaybeValue) {
|
|
reportIgnoredDie(ChildDie,
|
|
"Ignoring enum entry " + Twine(Index + 1)
|
|
+ " without a value");
|
|
rc_return;
|
|
}
|
|
|
|
uint64_t Value = *MaybeValue;
|
|
|
|
// Create new entry
|
|
std::string EntryName = getName(ChildDie);
|
|
|
|
// If it's the first time, set the name
|
|
auto *It = Enum.Entries().tryGet(Value);
|
|
if (It == nullptr) {
|
|
auto &Entry = Enum.Entries()[Value];
|
|
Entry.Name() = EntryName;
|
|
} else {
|
|
// Ignore aliases
|
|
}
|
|
|
|
++Index;
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
default:
|
|
reportIgnoredDie(Die, "Unknown type");
|
|
rc_return;
|
|
}
|
|
|
|
Placeholders.erase(Offset);
|
|
|
|
rc_return;
|
|
}
|
|
|
|
RecursiveCoroutine<model::UpcastableType>
|
|
resolveType(const DWARFDie &Die, bool ResolveIfHasIdentity) {
|
|
// Ensure there are no loops in the dies we're exploring
|
|
using ScopedSetElement = ScopedSetElement<decltype(InProgressDies)>;
|
|
ScopedSetElement InProgressDie(InProgressDies, &Die);
|
|
if (not InProgressDie.insert()) {
|
|
reportIgnoredDie(Die, "Recursive die");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
auto Tag = Die.getTag();
|
|
auto &&[SearchResult, Type] = findType(Die);
|
|
|
|
switch (SearchResult) {
|
|
case TypeSearchResult::Absent: {
|
|
// At this stage, all the type definitions (as in, types with an identity
|
|
// in the model) should have been materialized.
|
|
// Therefore, here we only deal with DWARF types the model represents as
|
|
// by nesting.
|
|
|
|
/// \note There could be some TAGs we do not handle/recognize as types.
|
|
if (isType(Tag))
|
|
revng_assert(not hasModelIdentity(Tag));
|
|
|
|
bool HasType = Die.find(DW_AT_type).has_value();
|
|
model::UpcastableType Result = rc_recur makeTypeOrVoid(Die);
|
|
|
|
switch (Tag) {
|
|
|
|
case llvm::dwarf::DW_TAG_const_type: {
|
|
revng_assert(Result->IsConst() == false);
|
|
Result->IsConst() = true;
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_array_type: {
|
|
|
|
if (not HasType) {
|
|
reportIgnoredDie(Die, "Array does not specify element type");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
for (const DWARFDie &ChildDie : Die.children()) {
|
|
if (ChildDie.getTag() == llvm::dwarf::DW_TAG_subrange_type) {
|
|
auto MaybeUpperBound = getUnsignedOrSigned(ChildDie,
|
|
DW_AT_upper_bound);
|
|
auto MaybeCount = getUnsignedOrSigned(ChildDie, DW_AT_count);
|
|
|
|
if (MaybeUpperBound and MaybeCount
|
|
and *MaybeUpperBound != *MaybeCount + 1) {
|
|
reportIgnoredDie(Die, "DW_AT_upper_bound != DW_AT_count + 1");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
if (MaybeUpperBound) {
|
|
Result = model::ArrayType::make(std::move(Result),
|
|
*MaybeUpperBound + 1);
|
|
} else if (MaybeCount) {
|
|
Result = model::ArrayType::make(std::move(Result), *MaybeCount);
|
|
} else {
|
|
reportIgnoredDie(Die,
|
|
"Array upper bound/elements count missing or "
|
|
"invalid");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case llvm::dwarf::DW_TAG_pointer_type: {
|
|
auto MaybeByteSize = Die.find(DW_AT_byte_size);
|
|
if (not MaybeByteSize) {
|
|
// TODO: force architecture pointer size
|
|
reportIgnoredDie(Die, "Pointer has no size");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
uint64_t PointerSize = *MaybeByteSize->getAsUnsignedConstant();
|
|
Result = model::PointerType::make(std::move(Result), PointerSize);
|
|
} break;
|
|
|
|
default:
|
|
reportIgnoredDie(Die, "Unknown type");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
rc_return record(Die, std::move(Result), true).copy();
|
|
}
|
|
case TypeSearchResult::PlaceholderType: {
|
|
if (Type.isEmpty()) {
|
|
reportIgnoredDie(Die, "Couldn't materialize type");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
revng_assert(Placeholders.contains(Die.getOffset()));
|
|
// This die is already present in the map. Either it has already been
|
|
// fully imported, or it's a type with an identity on the model.
|
|
// In the latter case, proceed only if explicitly told to do so.
|
|
if (ResolveIfHasIdentity)
|
|
rc_recur resolveTypeWithIdentity(Die, Type);
|
|
|
|
rc_return std::move(Type);
|
|
}
|
|
|
|
case TypeSearchResult::RegularType:
|
|
if (Type.isEmpty()) {
|
|
reportIgnoredDie(Die, "Couldn't materialize type");
|
|
rc_return model::UpcastableType::empty();
|
|
}
|
|
|
|
rc_return std::move(Type);
|
|
|
|
default:
|
|
revng_abort();
|
|
}
|
|
}
|
|
|
|
void resolveAllTypes() {
|
|
for (const auto &CU : Context.compile_units()) {
|
|
for (const auto &Entry : CU->dies()) {
|
|
DWARFDie Die = { CU.get(), &Entry };
|
|
if (not isType(Die.getTag()))
|
|
continue;
|
|
resolveType(Die, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
model::UpcastableType getSubprogramPrototype(const DWARFDie &Die) {
|
|
// Create function type
|
|
auto NewType = model::makeTypeDefinition<model::CABIFunctionDefinition>();
|
|
auto &FunctionType = cast<model::CABIFunctionDefinition>(*NewType.get());
|
|
|
|
// Detect ABI
|
|
CallingConvention CC = DW_CC_normal;
|
|
auto MaybeCC = getUnsignedOrSigned(Die, DW_AT_calling_convention);
|
|
if (MaybeCC)
|
|
CC = static_cast<CallingConvention>(*MaybeCC);
|
|
FunctionType.ABI() = getABI(CC);
|
|
|
|
if (FunctionType.ABI() == model::ABI::Invalid) {
|
|
reportIgnoredDie(Die, "Unknown calling convention");
|
|
return model::UpcastableType::empty();
|
|
}
|
|
|
|
// Arguments
|
|
uint64_t Index = 0;
|
|
for (const DWARFDie &ChildDie : Die.children()) {
|
|
if (ChildDie.getTag() == DW_TAG_formal_parameter) {
|
|
model::UpcastableType ArgumentType = makeType(ChildDie);
|
|
if (ArgumentType.isEmpty()) {
|
|
reportIgnoredDie(Die,
|
|
"The type of argument " + Twine(++Index)
|
|
+ " cannot be resolved");
|
|
return model::UpcastableType::empty();
|
|
}
|
|
|
|
// Note: at this stage we don't check the size. If an argument is
|
|
// unsized, the function will be purged later on.
|
|
model::Argument &A = FunctionType.addArgument(std::move(ArgumentType));
|
|
A.Name() = getName(ChildDie);
|
|
}
|
|
}
|
|
|
|
// Return type
|
|
FunctionType.ReturnType() = makeType(Die);
|
|
return Model->recordNewType(std::move(NewType)).second;
|
|
}
|
|
|
|
void createFunctions() {
|
|
revng_log(DILogger, "Creating functions");
|
|
for (const auto &CU : Context.compile_units()) {
|
|
for (const auto &Entry : CU->dies()) {
|
|
DWARFDie Die = { CU.get(), &Entry };
|
|
|
|
if (Die.getTag() != DW_TAG_subprogram)
|
|
continue;
|
|
|
|
auto &Functions = Model->ImportedDynamicFunctions();
|
|
model::UpcastableType Prototype = getSubprogramPrototype(Die);
|
|
std::string SymbolName = getName(Die);
|
|
|
|
MetaAddress LowPC;
|
|
if (auto MaybeLowPC = getAddress(Die)) {
|
|
// TODO: do a proper check to see if it's in a valid segment
|
|
if (*MaybeLowPC != 0)
|
|
LowPC = relocate(fromPC(*MaybeLowPC));
|
|
}
|
|
|
|
if (LowPC.isValid()) {
|
|
revng_log(DILogger,
|
|
"Found a subprogram with LowPC "
|
|
<< LowPC.toString() << " and name \"" << SymbolName
|
|
<< "\"");
|
|
|
|
// Get/create the local function
|
|
if (auto *Function = registerFunctionEntry(LowPC)) {
|
|
|
|
if (Prototype.isEmpty())
|
|
revng_log(DILogger, "Can't get the prototype");
|
|
else if (not Function->prototype())
|
|
Function->Prototype() = std::move(Prototype);
|
|
|
|
if (SymbolName.size() != 0) {
|
|
Function->ExportedNames().insert(SymbolName);
|
|
if (Function->Name().size() == 0)
|
|
Function->Name() = SymbolName;
|
|
}
|
|
|
|
if (isNoReturn(*CU.get(), Die))
|
|
Function->Attributes().insert(model::FunctionAttribute::NoReturn);
|
|
}
|
|
} else if (not SymbolName.empty() and Functions.contains(SymbolName)) {
|
|
// It's a dynamic function
|
|
if (Prototype.isEmpty()) {
|
|
reportIgnoredDie(Die, "Couldn't build subprogram prototype");
|
|
continue;
|
|
}
|
|
|
|
// Get/create dynamic function
|
|
auto &DynamicFunction = Model->ImportedDynamicFunctions()[SymbolName];
|
|
|
|
// If a function already has a valid prototype, don't override it
|
|
if (DynamicFunction.prototype() != nullptr)
|
|
continue;
|
|
|
|
const model::TypeDefinition &Def = *Prototype->tryGetAsDefinition();
|
|
revng_assert(isa<model::CABIFunctionDefinition>(Def));
|
|
DynamicFunction.Prototype() = std::move(Prototype);
|
|
|
|
if (isNoReturn(*CU.get(), Die)) {
|
|
using namespace model;
|
|
DynamicFunction.Attributes().insert(FunctionAttribute::NoReturn);
|
|
}
|
|
} else {
|
|
reportIgnoredDie(Die, "Ignoring subprogram");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void purgeUnresolvedPlaceholders() {
|
|
std::set<const model::TypeDefinition *> ToDrop;
|
|
for (auto &&[_, Type] : Placeholders)
|
|
ToDrop.insert(Type);
|
|
|
|
unsigned DroppedTypes = dropTypesDependingOnDefinitions(Model, ToDrop);
|
|
if (DroppedTypes > 0) {
|
|
revng_log(DILogger,
|
|
"Purging " << DroppedTypes << " types (out of "
|
|
<< TypesWithIdentityCount << ") due to "
|
|
<< Placeholders.size() << " unresolved types");
|
|
}
|
|
|
|
Placeholders.clear();
|
|
}
|
|
|
|
public:
|
|
void run() {
|
|
Task T(10, "Importing DWARF");
|
|
T.advance("Materialize types with an identity", true);
|
|
materializeTypesWithIdentity();
|
|
T.advance("Resolve types", true);
|
|
resolveAllTypes();
|
|
T.advance("Create model functions", true);
|
|
createFunctions();
|
|
T.advance("Remove types that depend on unresolved placeholders", true);
|
|
purgeUnresolvedPlaceholders();
|
|
T.advance("Remove types that couldn't be imported fully", true);
|
|
purgeInvalidTypes(Model);
|
|
T.advance("Flatten primitive typedefs", true);
|
|
model::flattenPrimitiveTypedefs(Model);
|
|
T.advance("Deduplicate equivalent types", true);
|
|
deduplicateEquivalentTypes(Model);
|
|
T.advance("Deduplicate colliding names", true);
|
|
model::deduplicateCollidingNames(Model);
|
|
T.advance("Purge unnamed unreachable types", true);
|
|
purgeUnnamedAndUnreachableTypes(Model);
|
|
T.advance("Verify the model", true);
|
|
Model->verify(true);
|
|
}
|
|
};
|
|
|
|
template<typename T>
|
|
ArrayRef<uint8_t> getSectionsContents(StringRef Name, T &ELF) {
|
|
auto MaybeSections = ELF.sections();
|
|
if (auto Error = MaybeSections.takeError()) {
|
|
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 {};
|
|
}
|
|
|
|
static std::string getBuildID(const object::Binary *B) {
|
|
using namespace llvm::object;
|
|
|
|
auto Handler = [&](auto *ELFObject) -> std::string {
|
|
const auto &ELF = ELFObject->getELFFile();
|
|
ArrayRef<uint8_t> Contents = getSectionsContents(".note.gnu.build-id", ELF);
|
|
if (Contents.size() == 0)
|
|
return {};
|
|
|
|
std::string StringForBytes;
|
|
raw_string_ostream OutputStream(StringForBytes);
|
|
for (uint8_t Byte : Contents)
|
|
OutputStream << format_hex_no_prefix(Byte, 2);
|
|
|
|
// Build ID uses SHA1, so it is 20 bytes long.
|
|
constexpr unsigned SHA1Size = 40;
|
|
return OutputStream.str().substr(OutputStream.str().size() - SHA1Size);
|
|
};
|
|
|
|
std::string BuildID;
|
|
if (auto *ELF = dyn_cast<ELF32BEObjectFile>(B)) {
|
|
BuildID = Handler(ELF);
|
|
} else if (auto *ELF = dyn_cast<ELF64BEObjectFile>(B)) {
|
|
BuildID = Handler(ELF);
|
|
} else if (auto *ELF = dyn_cast<ELF32LEObjectFile>(B)) {
|
|
BuildID = Handler(ELF);
|
|
} else if (auto *ELF = dyn_cast<ELF64LEObjectFile>(B)) {
|
|
BuildID = Handler(ELF);
|
|
} else {
|
|
revng_abort();
|
|
}
|
|
|
|
return BuildID;
|
|
}
|
|
|
|
static StringRef getDebugFileName(const object::Binary *B) {
|
|
using namespace llvm::object;
|
|
|
|
// TODO: Handle Split DWARF/DW_AT_GNU_dwo_name. Part of DWARF 5.
|
|
|
|
auto Handler = [&](auto *ELFObject) -> StringRef {
|
|
const auto &ELF = ELFObject->getELFFile();
|
|
ArrayRef<uint8_t> Contents = getSectionsContents(".gnu_debuglink", ELF);
|
|
if (Contents.size() == 0) {
|
|
// If there is no ".gnu_debuglink", try ".gnu_debugaltlink".
|
|
Contents = getSectionsContents(".gnu_debugaltlink", ELF);
|
|
}
|
|
|
|
if (Contents.size() == 0) {
|
|
// TODO: Handle .debug_sup, which is DWARF 5 implementation of GNU
|
|
// extension .gnu_debuglink sections.
|
|
return {};
|
|
}
|
|
|
|
// TODO: improve accuracy
|
|
// Extract path name and ignore everything after \0
|
|
return StringRef(reinterpret_cast<const char *>(Contents.data()));
|
|
};
|
|
|
|
StringRef AltDebugLinkPath;
|
|
if (auto *ELF = dyn_cast<ELF32BEObjectFile>(B)) {
|
|
AltDebugLinkPath = Handler(ELF);
|
|
} else if (auto *ELF = dyn_cast<ELF64BEObjectFile>(B)) {
|
|
AltDebugLinkPath = Handler(ELF);
|
|
} else if (auto *ELF = dyn_cast<ELF32LEObjectFile>(B)) {
|
|
AltDebugLinkPath = Handler(ELF);
|
|
} else if (auto *ELF = dyn_cast<ELF64LEObjectFile>(B)) {
|
|
AltDebugLinkPath = Handler(ELF);
|
|
} else {
|
|
revng_abort();
|
|
}
|
|
|
|
return llvm::sys::path::filename(AltDebugLinkPath);
|
|
}
|
|
|
|
static bool fileExists(const Twine &Path) {
|
|
bool Result = sys::fs::exists(Path);
|
|
|
|
if (Result) {
|
|
revng_log(DILogger, "Found: " << Path.str());
|
|
} else {
|
|
revng_log(DILogger, "The following path does not exist: " << Path.str());
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
static std::optional<std::string>
|
|
findDebugInfoFileByName(StringRef FileName,
|
|
StringRef DebugFileName,
|
|
llvm::object::ObjectFile *ELF) {
|
|
revng_log(DILogger,
|
|
"Looking for \"" << DebugFileName << "\" for " << FileName << "\"");
|
|
LoggerIndent<> Indent(DILogger);
|
|
|
|
// Let's find it in canonical places, where debug info was fetched.
|
|
// 1) Look for a .gnu_debuglink/.gnu_debugaltlink/.debug_sup section.
|
|
// The .debug file should be in canonical places.
|
|
// E.g., if the executable is `/usr/bin/ls`, we look for:
|
|
// - /usr/bin/ls.debug (current dir of exe)
|
|
// - /usr/bin/.debug/ls.debug
|
|
// - /usr/lib/debug/usr/bin/ls.debug
|
|
llvm::SmallString<128> ResultPath;
|
|
if (llvm::sys::path::has_parent_path(FileName)) {
|
|
llvm::sys::path::append(ResultPath,
|
|
llvm::sys::path::parent_path(FileName),
|
|
DebugFileName);
|
|
} else {
|
|
llvm::sys::path::append(ResultPath, DebugFileName);
|
|
}
|
|
|
|
if (fileExists(ResultPath.str())) {
|
|
return std::string(ResultPath.str());
|
|
} else {
|
|
// Try in .debug/ directory.
|
|
ResultPath.clear();
|
|
llvm::sys::path::append(ResultPath,
|
|
llvm::sys::path::parent_path(FileName),
|
|
".debug/",
|
|
DebugFileName);
|
|
|
|
if (fileExists(ResultPath.str())) {
|
|
return std::string(ResultPath.str());
|
|
} else {
|
|
// Try `/usr/lib/debug/usr/bin/ls.debug`-like path.
|
|
ResultPath.clear();
|
|
if (sys::path::is_absolute(FileName)) {
|
|
llvm::sys::path::append(ResultPath,
|
|
GlobalDebugDirectory,
|
|
llvm::sys::path::parent_path(FileName),
|
|
DebugFileName);
|
|
} else {
|
|
// Relative path.
|
|
llvm::SmallString<64> CurrentDirectory;
|
|
auto ErrorCode = llvm::sys::fs::current_path(CurrentDirectory);
|
|
if (!ErrorCode) {
|
|
llvm::sys::path::append(ResultPath,
|
|
GlobalDebugDirectory,
|
|
CurrentDirectory,
|
|
llvm::sys::path::parent_path(FileName),
|
|
DebugFileName);
|
|
} else {
|
|
revng_log(DILogger, "Can't get current working path.");
|
|
}
|
|
}
|
|
|
|
if (fileExists(ResultPath.str())) {
|
|
return std::string(ResultPath.str());
|
|
} else {
|
|
// Try If build-id is `abcdef1234`, we look for:
|
|
// - /usr/lib/debug/.build-id/ab/cdef1234.debug
|
|
ResultPath.clear();
|
|
auto BuildID = getBuildID(ELF);
|
|
if (BuildID.size()) {
|
|
// 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(BuildID.size() - 38);
|
|
auto DebugFileWithExtension = DebugFile.append(".debug");
|
|
llvm::sys::path::append(ResultPath,
|
|
GlobalDebugDirectory,
|
|
".build-id/",
|
|
DebugDir,
|
|
DebugFileWithExtension);
|
|
|
|
if (fileExists(ResultPath.str())) {
|
|
return std::string(ResultPath.str());
|
|
}
|
|
|
|
// Try in CacheDirectory at the end.
|
|
ResultPath.clear();
|
|
std::string CacheDir = getCacheDirectory();
|
|
ResultPath = joinPath(CacheDir,
|
|
"debug-symbols",
|
|
"elf",
|
|
BuildID,
|
|
"debug");
|
|
|
|
if (fileExists(ResultPath.str())) {
|
|
return ResultPath.str().str();
|
|
}
|
|
revng_log(DILogger, "Can't find " << DebugFileName);
|
|
} else {
|
|
revng_log(DILogger, "Can't parse build-id.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We have not found the debug info file on the device.
|
|
return std::nullopt;
|
|
}
|
|
|
|
void DwarfImporter::import(StringRef FileName, const ImporterOptions &Options) {
|
|
revng_log(DILogger,
|
|
"Importing DWARF information for "
|
|
<< llvm::sys::path::filename(FileName));
|
|
LoggerIndent<> Indent(DILogger);
|
|
|
|
Task T(3,
|
|
"Importing DWARF information for "
|
|
+ llvm::sys::path::filename(FileName));
|
|
|
|
T.advance("Fetching debug info", true);
|
|
|
|
using namespace llvm::object;
|
|
ErrorOr<std::unique_ptr<MemoryBuffer>>
|
|
MaybeBuffer = MemoryBuffer::getFileOrSTDIN(FileName);
|
|
|
|
if (not MaybeBuffer) {
|
|
revng_log(DILogger,
|
|
"Couldn't open file " << FileName << " "
|
|
<< MaybeBuffer.getError().message());
|
|
return;
|
|
}
|
|
|
|
std::unique_ptr<MemoryBuffer> Buffer = std::move(MaybeBuffer.get());
|
|
Expected<std::unique_ptr<Binary>> MaybeBinary = object::createBinary(*Buffer);
|
|
|
|
if (auto Error = MaybeBinary.takeError()) {
|
|
revng_log(DILogger, "Couldn't load binary: " << Error);
|
|
consumeError(std::move(Error));
|
|
return;
|
|
}
|
|
|
|
// Find Debugging Information.
|
|
// If the file has debug info sections within itself, no need for finding
|
|
// it on the device.
|
|
// TODO: When we add support for Split DWARF, this will need additional
|
|
// improvement.
|
|
auto HasDebugInfo = [](ObjectFile *Object) {
|
|
for (const SectionRef &Section : Object->sections()) {
|
|
StringRef SectionName;
|
|
if (Expected<StringRef> NameOrErr = Section.getName()) {
|
|
SectionName = *NameOrErr;
|
|
} else {
|
|
llvm::consumeError(NameOrErr.takeError());
|
|
continue;
|
|
}
|
|
|
|
// TODO: When adding support for Split dwarf, there will be
|
|
// .debug_info.dwo section, so we need to handle it.
|
|
if (SectionName == ".debug_info")
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
auto PerformImport = [this, &T, &Options](StringRef FilePath,
|
|
StringRef TheDebugFile) {
|
|
auto ExpectedBinary = object::createBinary(FilePath);
|
|
if (!ExpectedBinary) {
|
|
revng_log(DILogger, "Can't create binary for " << FilePath);
|
|
llvm::consumeError(ExpectedBinary.takeError());
|
|
} else {
|
|
revng_log(DILogger, "Importing " << TheDebugFile.str());
|
|
T.advance("Parsing detached debug info file "
|
|
+ llvm::sys::path::filename(TheDebugFile),
|
|
true);
|
|
import(*ExpectedBinary->getBinary(), TheDebugFile, Options.BaseAddress);
|
|
}
|
|
};
|
|
|
|
if (auto *ELF = dyn_cast<ObjectFile>(MaybeBinary->get())) {
|
|
if (Options.DebugInfo != DebugInfoLevel::No && !HasDebugInfo(ELF)) {
|
|
// There are no .debug_* sections in the file itself, let's try to find it
|
|
// on the device, otherwise find it on web by using the `fetch-debuginfo`
|
|
// tool.
|
|
std::string DebugFile = getDebugFileName(MaybeBinary->get()).str();
|
|
if (!DebugFile.size()) {
|
|
DebugFile = (llvm::sys::path::filename(FileName) + ".debug").str();
|
|
revng_log(DILogger,
|
|
"Can't find file name of the debug file, using "
|
|
<< DebugFile);
|
|
}
|
|
|
|
auto DebugFilePath = findDebugInfoFileByName(FileName, DebugFile, ELF);
|
|
if (!DebugFilePath) {
|
|
if (!::Runner.isProgramAvailable("revng")) {
|
|
revng_log(DILogger,
|
|
"Can't find `revng` binary to run `fetch-debuginfo`.");
|
|
return;
|
|
}
|
|
|
|
int ExitCode = runFetchDebugInfo(FileName, DILogger.isEnabled());
|
|
if (ExitCode != 0) {
|
|
revng_log(DILogger,
|
|
"Failed to find debug info with `revng model "
|
|
"fetch-debuginfo`.");
|
|
} else {
|
|
DebugFilePath = findDebugInfoFileByName(FileName, DebugFile, ELF);
|
|
if (DebugFilePath)
|
|
PerformImport(*DebugFilePath, DebugFile);
|
|
}
|
|
} else {
|
|
PerformImport(*DebugFilePath, DebugFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
T.advance("Parsing debug info in the binary itself", true);
|
|
import(*MaybeBinary->get(), FileName, Options.BaseAddress);
|
|
}
|
|
|
|
auto zipPairs(auto &&R) {
|
|
auto BeginIt = R.begin();
|
|
auto EndIt = R.end();
|
|
|
|
if (BeginIt == EndIt)
|
|
return zip(make_range(EndIt, EndIt), make_range(EndIt, EndIt));
|
|
|
|
auto First = BeginIt;
|
|
auto Second = ++BeginIt;
|
|
|
|
if (Second == EndIt)
|
|
return zip(make_range(EndIt, EndIt), make_range(EndIt, EndIt));
|
|
|
|
auto End = EndIt;
|
|
auto Last = --EndIt;
|
|
|
|
return zip(make_range(First, Last), make_range(Second, End));
|
|
}
|
|
|
|
/// This function considers all symbols with name of type STT_FUNC and clusters
|
|
/// them by address/type
|
|
static EquivalenceClasses<StringRef>
|
|
computeEquivalentSymbols(const llvm::object::ObjectFile &ELF) {
|
|
using namespace llvm::object;
|
|
|
|
EquivalenceClasses<StringRef> Result;
|
|
|
|
struct SymbolDescriptor {
|
|
uint64_t Address = 0;
|
|
// TODO: one day we will want to consider STT_OBJECT too
|
|
SymbolRef::Type Type = SymbolRef::ST_Unknown;
|
|
/// \note we ignore this field for comparison purposes
|
|
StringRef Name;
|
|
|
|
auto key() const { return std::tie(Address, Type); }
|
|
|
|
bool operator<(const SymbolDescriptor &Other) const {
|
|
return key() < Other.key();
|
|
}
|
|
|
|
bool operator==(const SymbolDescriptor &Other) const {
|
|
return key() == Other.key();
|
|
}
|
|
};
|
|
|
|
std::vector<SymbolDescriptor> Symbols;
|
|
for (const object::SymbolRef &Symbol : ELF.symbols()) {
|
|
SymbolDescriptor NewSymbol;
|
|
|
|
auto MaybeType = Symbol.getType();
|
|
auto MaybeAddress = Symbol.getAddress();
|
|
auto MaybeName = Symbol.getName();
|
|
auto MaybeFlags = Symbol.getFlags();
|
|
|
|
if (auto Error = MaybeType.takeError()) {
|
|
consumeError(std::move(Error));
|
|
continue;
|
|
} else if (auto Error = MaybeAddress.takeError()) {
|
|
consumeError(std::move(Error));
|
|
continue;
|
|
} else if (auto Error = MaybeName.takeError()) {
|
|
consumeError(std::move(Error));
|
|
continue;
|
|
} else if (auto Error = MaybeFlags.takeError()) {
|
|
consumeError(std::move(Error));
|
|
continue;
|
|
}
|
|
|
|
// Ignore unnamed and nullptr symbols
|
|
if (MaybeName->size() == 0 or *MaybeAddress == 0)
|
|
continue;
|
|
|
|
// Consider only STT_FUNC symbols
|
|
if (*MaybeType != SymbolRef::ST_Function)
|
|
continue;
|
|
|
|
// Consider only global symbols
|
|
if (!((*MaybeFlags) & SymbolRef::SF_Global))
|
|
continue;
|
|
|
|
Symbols.push_back({ *MaybeAddress, *MaybeType, *MaybeName });
|
|
}
|
|
|
|
llvm::sort(Symbols);
|
|
|
|
for (const auto &[Previous, Current] : zipPairs(Symbols))
|
|
if (Previous == Current)
|
|
Result.unionSets(Previous.Name, Current.Name);
|
|
|
|
return Result;
|
|
}
|
|
|
|
// TODO: it would be beneficial to do this even at other levels
|
|
inline void detectAliases(const llvm::object::ObjectFile &ELF,
|
|
TupleTree<model::Binary> &Model) {
|
|
EquivalenceClasses<StringRef> Aliases = computeEquivalentSymbols(ELF);
|
|
auto &ImportedDynamicFunctions = Model->ImportedDynamicFunctions();
|
|
auto &Functions = Model->Functions();
|
|
|
|
std::unordered_map<std::string, model::Function *> FunctionsByName;
|
|
// Map functions by names, so we have faster lookup below.
|
|
for (auto &Function : Functions) {
|
|
if (Function.Name().size()) {
|
|
FunctionsByName[Function.Name()] = &Function;
|
|
}
|
|
}
|
|
|
|
for (auto AliasesIt = Aliases.begin(), E = Aliases.end(); AliasesIt != E;
|
|
++AliasesIt) {
|
|
llvm::SmallVector<std::string, 4> CurrentAliases;
|
|
if (AliasesIt->isLeader()) {
|
|
SmallVector<std::string, 4> UnprototypedFunctionsNames;
|
|
model::UpcastableType Prototype;
|
|
for (auto AliasSetIt = Aliases.member_begin(AliasesIt);
|
|
AliasSetIt != Aliases.member_end();
|
|
++AliasSetIt) {
|
|
|
|
std::string Name = AliasSetIt->str();
|
|
if (Name.size() == 0)
|
|
continue;
|
|
|
|
CurrentAliases.push_back(Name);
|
|
|
|
// Create DynamicFunction, if it doesn't exist already
|
|
auto It = ImportedDynamicFunctions.tryGet(Name);
|
|
bool Found = It != nullptr;
|
|
|
|
// If DynamicFunction doesn't have a prototype, register it for copying
|
|
// it from the leader.
|
|
// Otherwise, record the type as the leader.
|
|
if (Found and not It->Prototype().isEmpty()) {
|
|
Prototype = It->Prototype().copy();
|
|
} else {
|
|
UnprototypedFunctionsNames.push_back(Name);
|
|
}
|
|
}
|
|
|
|
// Check if we should add an ExportedName for local Functions.
|
|
llvm::SmallVector<std::string, 4> PotentialExportedNamesToBeAdded;
|
|
bool IsLocalFunction = false;
|
|
model::Function *TheFunction = nullptr;
|
|
for (auto &Name : CurrentAliases) {
|
|
auto It = FunctionsByName.find(Name);
|
|
PotentialExportedNamesToBeAdded.push_back(Name);
|
|
if (It != FunctionsByName.end()) {
|
|
// We found a local function.
|
|
// TODO: In some situations Name is not in the ExportedNames?
|
|
// For example in the case of importing `__libc_calloc` from
|
|
// libc.so.6.
|
|
TheFunction = It->second;
|
|
}
|
|
}
|
|
// It is a local function. Populate the ExportedNames.
|
|
if (TheFunction) {
|
|
for (auto &Name : PotentialExportedNamesToBeAdded)
|
|
TheFunction->ExportedNames().insert(Name);
|
|
continue;
|
|
}
|
|
|
|
// Consider it as a Dynamic function.
|
|
if (not Prototype.isEmpty()) {
|
|
for (const std::string &Name : UnprototypedFunctionsNames) {
|
|
auto It = ImportedDynamicFunctions.find(Name);
|
|
if (It == ImportedDynamicFunctions.end())
|
|
It = ImportedDynamicFunctions.insert({ Name }).first;
|
|
|
|
It->Prototype() = std::move(Prototype);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DwarfImporter::import(const llvm::object::Binary &TheBinary,
|
|
StringRef FileName,
|
|
uint64_t PreferredBaseAddress) {
|
|
using namespace llvm::object;
|
|
|
|
if (auto *ELF = dyn_cast<ELFObjectFileBase>(&TheBinary)) {
|
|
|
|
{
|
|
using namespace model::Architecture;
|
|
if (Model->Architecture() == Invalid)
|
|
Model->Architecture() = fromLLVMArchitecture(ELF->getArch());
|
|
}
|
|
|
|
if (ELF->getEType() != ELF::ET_DYN)
|
|
PreferredBaseAddress = 0;
|
|
|
|
// Check if we already loaded the alt debug info file
|
|
size_t AltIndex = -1;
|
|
// Check if we already loaded the alt debug info file.
|
|
StringRef SeparateDebugFileName = getDebugFileName(ELF);
|
|
if (SeparateDebugFileName.size() > 0) {
|
|
auto Begin = LoadedFiles.begin();
|
|
auto End = LoadedFiles.end();
|
|
auto It = std::find(Begin, End, SeparateDebugFileName);
|
|
if (It != End)
|
|
AltIndex = It - Begin;
|
|
}
|
|
auto TheDWARFContext = DWARFContext::create(*ELF);
|
|
DwarfToModelConverter Converter(*this,
|
|
*TheDWARFContext,
|
|
LoadedFiles.size(),
|
|
AltIndex,
|
|
PreferredBaseAddress);
|
|
Converter.run();
|
|
|
|
detectAliases(*ELF, Model);
|
|
}
|
|
|
|
LoadedFiles.push_back(sys::path::filename(FileName).str());
|
|
}
|