/// \file DwarfImporter.cpp /// \brief // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include #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/Process.h" #include "llvm/Support/raw_os_ostream.h" #include "llvm/Support/raw_ostream.h" #include "revng/ADT/STLExtras.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/Model/QualifiedType.h" #include "revng/Model/Type.h" #include "revng/Support/Assert.h" #include "revng/Support/Debug.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 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::PrimitiveTypeKind::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::PrimitiveTypeKind::Unsigned; case dwarf::DW_ATE_signed_char: case dwarf::DW_ATE_signed: return model::PrimitiveTypeKind::Signed; case dwarf::DW_ATE_float: return model::PrimitiveTypeKind::Float; default: return model::PrimitiveTypeKind::Invalid; } } static std::optional 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 getUnsignedOrSigned(const DWARFDie &Die, dwarf::Attribute Attribute) { auto Value = Die.find(Attribute); if (not Value) return {}; else return getUnsignedOrSigned(*Value); } static std::optional getAddress(const DWARFFormValue &Value) { auto MaybeResult = Value.getAsAddress(); if (MaybeResult) return *MaybeResult; else return {}; } static std::optional 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 Range = Die.getDwarfUnit()->findRnglistFromOffset(Offset); if (!Range) return {}; // TODO: This is a vector, so we may want to return LowPC from every range // we found. return Range->begin()->LowPC; } else { return getAddress(*Value); } } static bool isTrue(const DWARFFormValue &Value) { return getUnsignedOrSigned(Value) != 0; } template 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; size_t Index; size_t AltIndex; size_t TypesWithIdentityCount; DWARFContext &DICtx; std::map Placeholders; std::set InvalidPrimitives; std::set InProgressDies; public: DwarfToModelConverter(DwarfImporter &Importer, DWARFContext &DICtx, size_t Index, size_t AltIndex, uint64_t PreferredBaseAddress) : Importer(Importer), Model(Importer.getModel()), Index(Index), AltIndex(AltIndex), DICtx(DICtx) { Architecture = Model->Architecture(); BaseAddress = PreferredBaseAddress; // Ensure the architecture is consistent. auto Arch = model::Architecture::fromLLVMArchitecture(DICtx.getArch()); if (Model->Architecture() == model::Architecture::Invalid) Model->Architecture() = Arch; // Detect default ABI from the architecture. if (Model->DefaultABI() == model::ABI::Invalid) Model->DefaultABI() = model::ABI::getDefault(Model->Architecture()); } 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::QualifiedType &record(const DWARFDie &Die, const model::TypePath &Path, bool IsNotPlaceholder) { return record(Die, model::QualifiedType(Path, {}), IsNotPlaceholder); } const model::QualifiedType &record(const DWARFDie &Die, const model::QualifiedType &QT, bool IsNotPlaceholder) { size_t Offset = Die.getOffset(); revng_assert(QT.UnqualifiedType().isValid()); if (not IsNotPlaceholder) { revng_assert(QT.Qualifiers().size() == 0); Placeholders[Offset] = QT.UnqualifiedType().get(); } return Importer.recordType({ Index, Die.getOffset() }, QT); } enum TypeSearchResult { Invalid, Absent, PlaceholderType, RegularType }; std::pair findType(const DWARFDie &Die) { return findType(Die.getOffset()); } std::pair findType(uint64_t Offset) { model::QualifiedType *Result = Importer.findType({ Index, Offset }); TypeSearchResult ResultType = Invalid; if (Result == nullptr) { ResultType = Absent; } else { if (Placeholders.count(Offset) != 0) ResultType = PlaceholderType; else ResultType = RegularType; } return { ResultType, Result }; } const model::QualifiedType *findAltType(uint64_t Offset) { return Importer.findType({ AltIndex, Offset }); } 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(); } } template [[maybe_unused]] T *createPlaceholderType(const DWARFDie &Die) { auto NewType = model::makeType(); T *Result = cast(NewType.get()); record(Die, Model->recordNewType(std::move(NewType)), false); return Result; } void createInvalidPrimitivePlaceholder(const DWARFDie &Die) { InvalidPrimitives.insert(createPlaceholderType(Die)); } 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::PrimitiveTypeKind::Values Kind = model::PrimitiveTypeKind::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::PrimitiveTypeKind::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->getPrimitiveType(Kind, Size), true); } break; case llvm::dwarf::DW_TAG_subroutine_type: record(Die, Model->recordNewType(model::makeType()), false); break; case llvm::dwarf::DW_TAG_typedef: case llvm::dwarf::DW_TAG_restrict_type: case llvm::dwarf::DW_TAG_volatile_type: createPlaceholderType(Die); break; case llvm::dwarf::DW_TAG_structure_type: createPlaceholderType(Die); break; case llvm::dwarf::DW_TAG_union_type: createPlaceholderType(Die); break; case llvm::dwarf::DW_TAG_enumeration_type: createPlaceholderType(Die); 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->getPrimitiveType(model::PrimitiveTypeKind::Void, 0), true); } else { reportIgnoredDie(Die, "Unexpected declaration for tag " + llvm::dwarf::TagString(Tag)); } } void materializeTypesWithIdentity() { for (const auto &CU : DICtx.compile_units()) { for (const auto &Entry : CU->dies()) { DWARFDie Die = { CU.get(), &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(); } static std::string getName(const DWARFDie &Die) { auto MaybeName = Die.find(DW_AT_name); if (MaybeName) { auto MaybeString = MaybeName->getAsCString(); if (MaybeString) return *MaybeString; } 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 getType(const DWARFDie &Die) { auto MaybeType = Die.find(DW_AT_type); if (MaybeType) { if (MaybeType->getForm() == llvm::dwarf::DW_FORM_GNU_ref_alt) { rc_return findAltType(MaybeType->getRawUValue()); } else { DWARFDie InnerDie = DICtx.getDIEForOffset(*MaybeType->getAsReference()); rc_return rc_recur resolveType(InnerDie, false); } } else { rc_return nullptr; } } RecursiveCoroutine getTypeOrVoid(const DWARFDie &Die) { using namespace model; using PTK = model::PrimitiveTypeKind::Values; const model::QualifiedType *Result = rc_recur getType(Die); if (Result != nullptr) { rc_return *Result; } else { rc_return QualifiedType(Model->getPrimitiveType(PTK::Void, 0), {}); } } RecursiveCoroutine resolveTypeWithIdentity(const DWARFDie &Die, model::QualifiedType *TypePath) { using namespace model; auto Offset = Die.getOffset(); auto Tag = Die.getTag(); revng_assert(Placeholders.count(Offset) != 0); revng_assert(TypePath->Qualifiers().empty()); model::Type *T = TypePath->UnqualifiedType().get(); std::string Name = getName(Die); if (InvalidPrimitives.count(T) != 0) rc_return nullptr; switch (Tag) { case llvm::dwarf::DW_TAG_subroutine_type: { auto *FunctionType = cast(T); FunctionType->OriginalName() = Name; FunctionType->ABI() = getABI(); if (FunctionType->ABI() == model::ABI::Invalid) { reportIgnoredDie(Die, "Unknown calling convention"); rc_return nullptr; } FunctionType->ReturnType() = rc_recur getTypeOrVoid(Die); revng_assert(FunctionType->ReturnType().UnqualifiedType().isValid()); uint64_t Index = 0; for (const DWARFDie &ChildDie : Die.children()) { if (ChildDie.getTag() == DW_TAG_formal_parameter) { const QualifiedType *ArgumentType = rc_recur getType(ChildDie); if (ArgumentType == nullptr) { reportIgnoredDie(Die, "The type of argument " + Twine(Index + 1) + " cannot be resolved"); rc_return nullptr; } model::Argument &NewArgument = FunctionType->Arguments()[Index]; NewArgument.Type() = *ArgumentType; Index += 1; } } } break; case llvm::dwarf::DW_TAG_typedef: case llvm::dwarf::DW_TAG_restrict_type: case llvm::dwarf::DW_TAG_volatile_type: { model::QualifiedType TargetType = rc_recur getTypeOrVoid(Die); auto *Typedef = cast(T); Typedef->OriginalName() = Name; Typedef->UnderlyingType() = TargetType; revng_assert(Typedef->UnderlyingType().UnqualifiedType().isValid()); } 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 nullptr; } auto *Struct = cast(T); Struct->OriginalName() = 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; } const QualifiedType *MemberType = rc_recur getType(ChildDie); if (MemberType == nullptr) { reportIgnoredDie(Die, "The type of member " + Twine(Index + 1) + " cannot be resolved"); rc_return nullptr; } // Create new field auto &Field = Struct->Fields()[Offset]; Field.OriginalName() = getName(ChildDie); Field.Type() = *MemberType; ++Index; } } if (Index == 0) { reportIgnoredDie(Die, "Struct has no fields"); rc_return nullptr; } } break; case llvm::dwarf::DW_TAG_union_type: { auto *Union = cast(T); Union->OriginalName() = Name; uint64_t Index = 0; for (const DWARFDie &ChildDie : Die.children()) { if (ChildDie.getTag() == DW_TAG_member) { const QualifiedType *MemberType = rc_recur getType(ChildDie); if (MemberType == nullptr) { reportIgnoredDie(Die, "The type of member " + Twine(Index + 1) + " cannot be resolved"); rc_return nullptr; } // Create new field auto &Field = Union->Fields()[Index]; Field.OriginalName() = getName(ChildDie); Field.Type() = *MemberType; // Increment union index Index += 1; } } if (Index == 0) { reportIgnoredDie(Die, "Union has no fields"); rc_return nullptr; } } break; case llvm::dwarf::DW_TAG_enumeration_type: { auto *Enum = cast(T); Enum->OriginalName() = Name; const QualifiedType *QualifiedUnderlyingType = rc_recur getType(Die); if (QualifiedUnderlyingType == nullptr) { reportIgnoredDie(Die, "The enum underlying type cannot be resolved"); rc_return nullptr; } revng_assert(QualifiedUnderlyingType->Qualifiers().empty()); Enum->UnderlyingType() = *QualifiedUnderlyingType; 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 nullptr; } uint64_t Value = *MaybeValue; // Create new entry std::string EntryName = getName(ChildDie); // If it's the first time, set OriginalName auto It = Enum->Entries().find(Value); if (It == Enum->Entries().end()) { auto &Entry = Enum->Entries()[Value]; Entry.OriginalName() = EntryName; } else { // Ignore aliases } ++Index; } } } break; default: reportIgnoredDie(Die, "Unknown type"); rc_return nullptr; } Placeholders.erase(Offset); rc_return TypePath; } RecursiveCoroutine resolveType(const DWARFDie &Die, bool ResolveIfHasIdentity) { using model::QualifiedType; // Ensure there are no loops in the dies we're exploring using ScopedSetElement = ScopedSetElement; ScopedSetElement InProgressDie(InProgressDies, &Die); if (not InProgressDie.insert()) { reportIgnoredDie(Die, "Recursive die"); rc_return nullptr; } auto Tag = Die.getTag(); auto [MatchType, TypePath] = findType(Die); switch (MatchType) { case Absent: { // At this stage, all the unqualified types (i.e., those with an identity // in the model) should have been materialized. // Therefore, here we only deal with DWARF types the model represents as // qualifiers. /// \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::QualifiedType Type = rc_recur getTypeOrVoid(Die); switch (Tag) { case llvm::dwarf::DW_TAG_const_type: { model::Qualifier NewQualifier; NewQualifier.Kind() = model::QualifierKind::Const; Type.Qualifiers().insert(Type.Qualifiers().begin(), NewQualifier); } break; case llvm::dwarf::DW_TAG_array_type: { if (not HasType) { reportIgnoredDie(Die, "Array does not specify element type"); rc_return nullptr; } for (const DWARFDie &ChildDie : Die.children()) { if (ChildDie.getTag() == llvm::dwarf::DW_TAG_subrange_type) { model::Qualifier NewQualifier; NewQualifier.Kind() = model::QualifierKind::Array; NewQualifier.Size() = 0; 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 nullptr; } if (MaybeUpperBound) { NewQualifier.Size() = *MaybeUpperBound + 1; } else if (MaybeCount) { NewQualifier.Size() = *MaybeCount; } if (NewQualifier.Size() == 0) { reportIgnoredDie(Die, "Array upper bound/elements count missing or " "invalid"); rc_return nullptr; } Type.Qualifiers().insert(Type.Qualifiers().begin(), NewQualifier); } } } break; case llvm::dwarf::DW_TAG_pointer_type: { model::Qualifier NewQualifier; auto MaybeByteSize = Die.find(DW_AT_byte_size); if (not MaybeByteSize) { // TODO: force architecture pointer size reportIgnoredDie(Die, "Pointer has no size"); rc_return nullptr; } NewQualifier.Kind() = model::QualifierKind::Pointer; NewQualifier.Size() = *MaybeByteSize->getAsUnsignedConstant(); Type.Qualifiers().insert(Type.Qualifiers().begin(), NewQualifier); } break; default: reportIgnoredDie(Die, "Unknown type"); rc_return nullptr; } rc_return &record(Die, Type, true); } case PlaceholderType: { if (TypePath == nullptr) { reportIgnoredDie(Die, "Couldn't materialize type"); rc_return nullptr; } auto Offset = Die.getOffset(); revng_assert(Placeholders.count(Offset) != 0); // 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, TypePath); } break; case RegularType: if (TypePath == nullptr) { reportIgnoredDie(Die, "Couldn't materialize type"); rc_return nullptr; } break; default: revng_abort(); } rc_return TypePath; } void resolveAllTypes() { for (const auto &CU : DICtx.compile_units()) { for (const auto &Entry : CU->dies()) { DWARFDie Die = { CU.get(), &Entry }; if (not isType(Die.getTag())) continue; resolveType(Die, true); } } } std::optional getSubprogramPrototype(const DWARFDie &Die) { using namespace model; // Create function type UpcastableType NewType = makeType(); auto *FunctionType = cast(NewType.get()); // Detect ABI CallingConvention CC = DW_CC_normal; auto MaybeCC = getUnsignedOrSigned(Die, DW_AT_calling_convention); if (MaybeCC) CC = static_cast(*MaybeCC); FunctionType->ABI() = getABI(CC); if (FunctionType->ABI() == model::ABI::Invalid) { reportIgnoredDie(Die, "Unknown calling convention"); return std::nullopt; } // Arguments uint64_t Index = 0; for (const DWARFDie &ChildDie : Die.children()) { if (ChildDie.getTag() == DW_TAG_formal_parameter) { const model::QualifiedType *ArgumenType = getType(ChildDie); if (ArgumenType == nullptr) { reportIgnoredDie(Die, "The type of argument " + Twine(Index + 1) + " cannot be resolved"); return std::nullopt; } model::Argument &NewArgument = FunctionType->Arguments()[Index]; NewArgument.OriginalName() = getName(ChildDie); NewArgument.Type() = *ArgumenType; Index += 1; } } // Return type FunctionType->ReturnType() = getTypeOrVoid(Die); revng_assert(FunctionType->ReturnType().UnqualifiedType().isValid()); return Model->recordNewType(std::move(NewType)); } void createFunctions() { for (const auto &CU : DICtx.compile_units()) { for (const auto &Entry : CU->dies()) { DWARFDie Die = { CU.get(), &Entry }; if (Die.getTag() != DW_TAG_subprogram) continue; auto MaybePath = getSubprogramPrototype(Die); std::string SymbolName = getName(Die); auto MaybeLowPC = getAddress(Die); if (MaybeLowPC) { // Get/create the local function MetaAddress LowPC = relocate(fromPC(*MaybeLowPC)); auto &Function = Model->Functions()[LowPC]; if (MaybePath && not Function.Prototype().isValid()) Function.Prototype() = *MaybePath; if (SymbolName.size() != 0 and Function.OriginalName().size() == 0) { Function.OriginalName() = SymbolName; Function.ExportedNames().insert(SymbolName); } if (isNoReturn(*CU.get(), Die)) Function.Attributes().insert(model::FunctionAttribute::NoReturn); } else if (auto &Functions = Model->ImportedDynamicFunctions(); not SymbolName.empty() and Functions.count(SymbolName) != 0) { // It's a dynamic function if (not MaybePath) { 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().isValid()) continue; DynamicFunction.Prototype() = *MaybePath; if (isNoReturn(*CU.get(), Die)) { using namespace model; DynamicFunction.Attributes().insert(FunctionAttribute::NoReturn); } } else { reportIgnoredDie(Die, "Ignoring subprogram"); } } } } void cleanupTypeSystem() { std::set ToDrop; model::VerifyHelper VH; for (auto &Type : Model->Types()) { // // Drop zero-sized struct/union fields // if (auto *Struct = dyn_cast(Type.get())) { llvm::erase_if(Struct->Fields(), [&VH](model::StructField &Field) { std::optional MaybeSize = Field.Type().trySize(VH); return !MaybeSize || not *MaybeSize; }); } else if (auto *Union = dyn_cast(Type.get())) { llvm::erase_if(Union->Fields(), [&VH](model::UnionField &Field) { std::optional MaybeSize = Field.Type().trySize(VH); return !MaybeSize || not *MaybeSize; }); } // // Drop an empty enum. // if (auto *Enum = dyn_cast(Type.get())) { if (!Enum->Entries().size()) ToDrop.insert(Type.get()); } // // Collect array whose elements are zero-sized // for (const model::QualifiedType &QT : Type->edges()) { model::TypePath Unqualified = QT.UnqualifiedType(); if (Unqualified.isValid()) { std::optional Size = Unqualified.get()->trySize(VH); if (Size.has_value()) continue; } // At this point only invalid types and types with no size remain. auto Iterator = revng::find_last_if_not(QT.Qualifiers(), model::Qualifier::isConst); if (Iterator != QT.Qualifiers().rend()) if (Iterator->Kind() == model::QualifierKind::Array) ToDrop.insert(Type.get()); } } // // Collect unresolved types // for (const auto [_, Type] : Placeholders) ToDrop.insert(Type); unsigned DroppedTypes = dropTypesDependingOnTypes(Model, ToDrop); revng_log(DILogger, "Purging " << DroppedTypes << " types (out of " << TypesWithIdentityCount << ") due to " << Placeholders.size() << " unresolved types"); Placeholders.clear(); } public: void run() { materializeTypesWithIdentity(); resolveAllTypes(); createFunctions(); cleanupTypeSystem(); fixModel(Model); deduplicateEquivalentTypes(Model); promoteOriginalName(Model); purgeUnnamedAndUnreachableTypes(Model); revng_assert(Placeholders.size() == 0); Model->verify(true); } }; template ArrayRef getSectionsContents(StringRef Name, T &ELF) { auto MaybeSections = ELF.sections(); if (not MaybeSections) return {}; for (const auto &Section : *MaybeSections) { auto MaybeName = ELF.getSectionName(Section); if (MaybeName and *MaybeName == Name) { auto MaybeContents = 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 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(B)) { BuildID = Handler(ELF); } else if (auto *ELF = dyn_cast(B)) { BuildID = Handler(ELF); } else if (auto *ELF = dyn_cast(B)) { BuildID = Handler(ELF); } else if (auto *ELF = dyn_cast(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 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(Contents.data())); }; StringRef AltDebugLinkPath; if (auto *ELF = dyn_cast(B)) { AltDebugLinkPath = Handler(ELF); } else if (auto *ELF = dyn_cast(B)) { AltDebugLinkPath = Handler(ELF); } else if (auto *ELF = dyn_cast(B)) { AltDebugLinkPath = Handler(ELF); } else if (auto *ELF = dyn_cast(B)) { AltDebugLinkPath = Handler(ELF); } else { revng_abort(); } return llvm::sys::path::filename(AltDebugLinkPath); } static void error(StringRef Prefix, std::error_code EC) { if (!EC) return; std::string Str = Prefix.str(); Str += ": " + EC.message(); revng_abort(Str.c_str()); } static std::optional findDebugInfoFileByName(StringRef FileName, StringRef DebugFileName, llvm::object::ObjectFile *ELF) { // 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 (sys::fs::exists(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 (sys::fs::exists(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 (sys::fs::exists(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 (sys::fs::exists(ResultPath.str())) { return std::string(ResultPath.str()); } else { // Try in XDG_CACHE_HOME at the end. ResultPath.clear(); auto XDGCacheHome = llvm::sys::Process::GetEnv("XDG_CACHE_HOME"); SmallString<64> PathHome; sys::path::home_directory(PathHome); // Default debug directory. if (!XDGCacheHome) { llvm::sys::path::append(ResultPath, PathHome, ".local/share/revng/debug-symbols/elf/", BuildID, "debug"); } else { llvm::sys::path::append(ResultPath, *XDGCacheHome, "revng/debug-symbols/elf/", BuildID, "debug"); } if (sys::fs::exists(ResultPath.str())) { return std::string(ResultPath.str()); } else { 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) { using namespace llvm::object; ErrorOr> BuffOrErr = MemoryBuffer::getFileOrSTDIN(FileName); error(FileName, BuffOrErr.getError()); std::unique_ptr Buffer = std::move(BuffOrErr.get()); Expected> BinOrErr = object::createBinary(*Buffer); error(FileName, errorToErrorCode(BinOrErr.takeError())); // 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 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, &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 { import(*ExpectedBinary->getBinary(), TheDebugFile, Options.BaseAddress); } }; if (auto *ELF = dyn_cast(BinOrErr->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. auto DebugFile = getDebugFileName(BinOrErr->get()); if (!DebugFile.size()) { revng_log(DILogger, "Can't find file name of the debug file."); return; } 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 = runFetchDebugInfoWithLevel(FileName); 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); } } } import(*BinOrErr->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 computeEquivalentSymbols(const llvm::object::ObjectFile &ELF) { using namespace llvm::object; EquivalenceClasses 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 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 (not MaybeType or not MaybeAddress or not MaybeName or not MaybeFlags) 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) { EquivalenceClasses Aliases = computeEquivalentSymbols(ELF); auto &ImportedDynamicFunctions = Model->ImportedDynamicFunctions(); auto &Functions = Model->Functions(); std::unordered_map FunctionsByName; // Map functions by names, so we have faster lookup bellow. for (auto &Function : Functions) { if (Function.OriginalName().size()) { FunctionsByName[Function.OriginalName()] = &Function; } } for (auto AliasesIt = Aliases.begin(), E = Aliases.end(); AliasesIt != E; ++AliasesIt) { llvm::SmallVector CurrentAliases; if (AliasesIt->isLeader()) { SmallVector UnprototypedFunctionsNames; model::TypePath Prototype; for (auto AliasSetIt = Aliases.member_begin(AliasesIt); AliasSetIt != Aliases.member_end(); ++AliasSetIt) { std::string Name = AliasSetIt->str(); CurrentAliases.push_back(Name); // Create DynamicFunction, it doesn't exist already auto It = ImportedDynamicFunctions.find(Name); bool Found = It != ImportedDynamicFunctions.end(); // 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 It->Prototype().isValid()) { Prototype = It->Prototype(); } else { UnprototypedFunctionsNames.push_back(Name); } } // Check if we should add an ExportedName for local Functions. llvm::SmallVector 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 OriginalName 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 (Prototype.isValid()) { for (const std::string &Name : UnprototypedFunctionsNames) { auto It = ImportedDynamicFunctions.find(Name); if (It == ImportedDynamicFunctions.end()) It = ImportedDynamicFunctions.insert({ Name }).first; It->Prototype() = Prototype; } } } } } void DwarfImporter::import(const llvm::object::Binary &TheBinary, StringRef FileName, std::uint64_t PreferredBaseAddress) { using namespace llvm::object; if (auto *ELF = dyn_cast(&TheBinary)) { { using namespace model::Architecture; if (Model->Architecture() == Invalid) Model->Architecture() = fromLLVMArchitecture(ELF->getArch()); } // 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()); }