Files
revng-revng/lib/Model/Importer/DebugInfo/DwarfToModelConverter.cpp
T
Alessandro Di Federico 4c2eb97a18 DwarfImporteR: handle STT_GNU_IFUNC symbols
In DWARF, `STT_GNU_IFUNC` symbols are associated to a function prototype
returning the actual prototype.
2026-05-29 16:00:44 +02:00

1065 lines
34 KiB
C++

//
// This file is distributed under the MIT License. See LICENSE.md for details.
//
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Progress.h"
#include "revng/Model/Importer/DebugInfo/DwarfImporter.h"
#include "revng/Model/Pass/DeduplicateCollidingNames.h"
#include "revng/Model/Pass/DeduplicateEquivalentTypes.h"
#include "revng/Model/Pass/FlattenPrimitiveTypedefs.h"
#include "revng/Model/Pass/PurgeUnnamedAndUnreachableTypes.h"
#include "revng/Model/Processing.h"
#include "DwarfToModelConverter.h"
#include "ImportDebugInfoHelper.h"
using namespace llvm;
using namespace llvm::dwarf;
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;
}
}
};
// Iterate the children of `Die`, skipping (and logging) invalid ones.
static auto validChildren(const DWARFDie &Die) {
return llvm::make_filter_range(Die.children(), [&Die](const DWARFDie &Child) {
if (Child.isValid())
return true;
revng_log(DILogger, "Skipping invalid child DIE of " << Die.getOffset());
return false;
});
}
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;
}
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;
}
}
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);
}
DwarfToModelConverter::DwarfToModelConverter(DwarfImporter &Importer,
llvm::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());
}
}
}
model::ABI::Values
DwarfToModelConverter::getABI(llvm::dwarf::CallingConvention CC) const {
if (CC != llvm::dwarf::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 &
DwarfToModelConverter::record(const llvm::DWARFDie &Die,
model::UpcastableType &&Type) {
revng_assert(!Type.isEmpty());
return Importer.recordType({ Index, Die.getOffset() }, std::move(Type));
}
const model::UpcastableType &
DwarfToModelConverter::recordPlaceholder(const llvm::DWARFDie &Die,
model::UpcastableType &&Type) {
// `model::UpcastableType::empty()` (as in, no definition) represents
// a primitive placeholder.
Placeholders[Die.getOffset()] = Type->tryGetAsDefinition();
return record(Die, std::move(Type));
}
std::pair<DwarfToModelConverter::TypeSearchResult, model::UpcastableType>
DwarfToModelConverter::findType(const llvm::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) };
}
bool DwarfToModelConverter::isType(llvm::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;
}
}
bool DwarfToModelConverter::hasModelIdentity(llvm::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 DwarfToModelConverter::createInvalidPrimitivePlaceholder(const DWARFDie
&Die) {
auto &&[Definition, Type] = Model->makeTypedefDefinition();
recordPlaceholder(Die, std::move(Type));
InvalidPrimitives.insert(&Definition);
}
void DwarfToModelConverter::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));
} break;
case llvm::dwarf::DW_TAG_subroutine_type:
recordPlaceholder(Die, Model->makeCABIFunctionDefinition().second);
break;
case llvm::dwarf::DW_TAG_typedef:
case llvm::dwarf::DW_TAG_restrict_type:
case llvm::dwarf::DW_TAG_volatile_type:
recordPlaceholder(Die, std::move(Model->makeTypedefDefinition().second));
break;
case llvm::dwarf::DW_TAG_structure_type:
recordPlaceholder(Die, std::move(Model->makeStructDefinition().second));
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));
} else {
// Handle regular unions
recordPlaceholder(Die, std::move(Model->makeUnionDefinition().second));
}
break;
case llvm::dwarf::DW_TAG_enumeration_type:
recordPlaceholder(Die, std::move(Model->makeEnumDefinition().second));
break;
default:
reportIgnoredDie(Die, "Unexpected type");
}
}
void DwarfToModelConverter::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());
} else {
reportIgnoredDie(Die,
"Unexpected declaration for tag "
+ llvm::dwarf::TagString(Tag));
}
}
void DwarfToModelConverter::materializeTypesWithIdentity() {
revng_log(DILogger, "materializeTypesWithIdentity");
LoggerIndent Indent(DILogger);
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 DwarfToModelConverter::getName(const DWARFDie &InitialDie) const {
DWARFDie Die = InitialDie;
std::set<uint64_t> Visited;
while (Die.isValid()) {
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 {};
}
return *MaybeString;
}
auto MaybeOrigin = Die.find(DW_AT_abstract_origin);
if (not MaybeOrigin)
return {};
if (not Visited.insert(Die.getOffset()).second)
return {};
Die = Context.getDIEForOffset(*MaybeOrigin->getAsReference());
}
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>
DwarfToModelConverter::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>
DwarfToModelConverter::makeTypeOrVoid(const DWARFDie &Die) {
if (model::UpcastableType Result = rc_recur makeType(Die))
rc_return Result;
else
rc_return model::PrimitiveType::makeVoid();
}
RecursiveCoroutine<void>
DwarfToModelConverter::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();
revng_log(DILogger, "Handling type " << Definition.ID());
LoggerIndent Indent(DILogger);
if (InvalidPrimitives.contains(&Definition)) {
revng_log(DILogger, "Skipping invalid primitive");
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 : validChildren(Die)) {
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 : validChildren(Die)) {
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 : validChildren(Die)) {
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 : validChildren(Die)) {
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>
DwarfToModelConverter::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 : validChildren(Die)) {
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)).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 DwarfToModelConverter::resolveAllTypes() {
revng_log(DILogger, "resolveAllTypes");
LoggerIndent Indent(DILogger);
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
DwarfToModelConverter::getSubprogramPrototype(const DWARFDie &InitialDie) {
using namespace llvm::dwarf;
DWARFDie Die = InitialDie;
// Skip over DW_AT_abstract_origin
std::set<uint64_t> Visited;
Visited.insert(Die.getOffset());
while (auto MaybeOrigin = Die.find(DW_AT_abstract_origin)) {
DWARFDie Origin = Context.getDIEForOffset(*MaybeOrigin->getAsReference());
if (not Origin.isValid()) {
reportIgnoredDie(Die, "DW_AT_abstract_origin resolves to an invalid DIE");
return model::UpcastableType::empty();
}
if (Visited.contains(Origin.getOffset())) {
reportIgnoredDie(Die, "Found a loop in DW_AT_abstract_origin references");
return model::UpcastableType::empty();
}
Die = Origin;
Visited.insert(Die.getOffset());
}
// 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 : validChildren(Die)) {
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);
}
}
bool IsPrototyped = getUnsignedOrSigned(Die, DW_AT_prototyped)
.value_or(false);
bool IsDeclaration = getUnsignedOrSigned(Die, DW_AT_declaration)
.value_or(false);
bool HasType = Die.find(DW_AT_type).has_value();
bool HasArguments = not FunctionType.Arguments().empty();
if (IsDeclaration and not HasType and not IsPrototyped and not HasArguments) {
// Ignore declaration without a prototype
reportIgnoredDie(Die,
"Declaration without any useful prototype information");
return model::UpcastableType::empty();
}
// Return type
FunctionType.ReturnType() = makeType(Die);
return Model->recordNewType(std::move(NewType)).second;
}
/// Substitute a glibc IFUNC resolver prototype (no args, returns a pointer
/// to a CABIFunctionDefinition) with the pointee prototype.
static model::UpcastableType
unwrapIfuncResolverPrototype(model::Binary &Binary,
const MetaAddress &Address,
model::UpcastableType Prototype) {
auto *Definition = Prototype->tryGetAsDefinition();
auto *Resolver = dyn_cast_or_null<model::CABIFunctionDefinition>(Definition);
if (Resolver == nullptr or not Resolver->Arguments().empty()
or Resolver->ReturnType().isEmpty()
or not Resolver->ReturnType()->isPointer())
return Prototype;
auto *PointeeDefinition = Resolver->ReturnType()
->getPointee()
.tryGetAsDefinition();
if (not isa_and_nonnull<model::CABIFunctionDefinition>(PointeeDefinition))
return Prototype;
revng_log(DILogger,
"Ifunc resolver at " << Address.toString()
<< ": substituting resolver prototype "
<< Resolver->ID() << " with pointee CABI "
<< PointeeDefinition->ID());
return Binary.makeType(PointeeDefinition->key());
}
void DwarfToModelConverter::createFunctions() {
revng_log(DILogger, "createFunctions");
LoggerIndent Indent(DILogger);
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 &DynamicFunctions = Model->ImportedDynamicFunctions();
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) {
// Relocate the raw DWARF address first, then let matchFunctionEntry
// pick the code type that matches the model.
uint64_t Relocated = relocate(*MaybeLowPC).address();
MetaAddress Match = matchFunctionEntry(Relocated,
Model->Architecture());
if (Match.isValid() and Importer.isFunctionAllowed(Match)) {
LowPC = Match;
} else {
revng_log(DILogger,
"Ignoring disallowed function at 0x"
<< llvm::utohexstr(*MaybeLowPC, true) << " with name \""
<< SymbolName << "\"");
}
}
}
revng_log(DILogger,
"Considering function at "
<< LowPC.toString() << " with name \"" << SymbolName << "\"");
LoggerIndent Indent(DILogger);
model::UpcastableType Prototype = getSubprogramPrototype(Die);
if (LowPC.isValid()) {
revng_log(DILogger,
"Found a subprogram with LowPC "
<< LowPC.toString() << " and name \"" << SymbolName
<< "\"");
// Use the existing model::Function or create a new one at LowPC.
auto &Function = Model->Functions()[LowPC];
if (Prototype.isEmpty()) {
revng_log(DILogger, "Can't get the prototype");
} else if (not Function.prototype()) {
// For STT_GNU_IFUNC the DWARF describes the resolver, not the
// resolved function; unwrap it.
if (Importer.isIfunc(LowPC))
Prototype = unwrapIfuncResolverPrototype(*Model,
LowPC,
std::move(Prototype));
revng_log(DILogger,
"Assigning prototype "
<< Prototype->tryGetAsDefinition()->ID());
Function.Prototype() = std::move(Prototype);
} else {
revng_log(DILogger,
"Function already has a prototype, not setting it.");
}
if (SymbolName.size() != 0) {
// Note: DWARF support
if (Function.Name().empty()) {
Function.Name() = SymbolName;
} else {
revng_log(DILogger,
"Function already has a name: " << Function.Name()
<< ". Not updating "
"it.");
}
Function.ExportedNames().insert(SymbolName);
}
if (isNoReturn(*CU.get(), Die))
Function.Attributes().insert(model::FunctionAttribute::NoReturn);
} else if (not SymbolName.empty()
and DynamicFunctions.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) {
revng_log(DILogger, "This function already has a prototype");
continue;
}
const auto &Definition = *Prototype->tryGetAsDefinition();
revng_log(DILogger, "Assigning prototype " << Definition.ID());
revng_assert(isa<model::CABIFunctionDefinition>(Definition));
DynamicFunction.Prototype() = std::move(Prototype);
if (isNoReturn(*CU.get(), Die)) {
using namespace model;
DynamicFunction.Attributes().insert(FunctionAttribute::NoReturn);
}
} else {
reportIgnoredDie(Die, "Ignoring subprogram");
}
}
}
}
void DwarfToModelConverter::purgeUnresolvedPlaceholders() {
revng_log(DILogger, "purgeUnresolvedPlaceholders");
LoggerIndent Indent(DILogger);
std::set<const model::TypeDefinition *> ToDrop;
for (auto &&[_, Type] : Placeholders)
ToDrop.insert(Type);
unsigned DroppedTypes = dropTypesDependingOnDefinitions(Model, ToDrop);
if (DroppedTypes > 0) {
// TODO: emit a diagnostic message for the user.
revng_log(DILogger,
"Purging " << DroppedTypes << " types (out of "
<< TypesWithIdentityCount << ") due to "
<< Placeholders.size() << " unresolved types");
}
Placeholders.clear();
}
void DwarfToModelConverter::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();
revng_log(DILogger, "Cleaning up and verifying the model");
LoggerIndent Indent(DILogger);
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);
}