diff --git a/llvm/test/tools/dsymutil/basic-linking.test b/llvm/test/tools/dsymutil/basic-linking.test index adbd595c457b..5de6c13c21f0 100644 --- a/llvm/test/tools/dsymutil/basic-linking.test +++ b/llvm/test/tools/dsymutil/basic-linking.test @@ -4,23 +4,59 @@ RUN: llvm-dsymutil -v -oso-prepend-path=%p %p/Inputs/basic-archive.macho.x86_64 This test check the basic Dwarf linking process through the debug dumps. +================================= Simple link ================================ CHECK: DEBUG MAP OBJECT: {{.*}}basic1.macho.x86_64.o CHECK: Input compilation unit: CHECK-NEXT: TAG_compile_unit CHECK-NOT: TAG CHECK: AT_name {{.*}}basic1.c +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _main 0000000000000000 => 0000000100000ea0 +CHECK-NEXT: DW_TAG_subprogram +CHECK-NEXT: DW_AT_name{{.*}}"main" + CHECK: DEBUG MAP OBJECT: {{.*}}basic2.macho.x86_64.o CHECK: Input compilation unit: CHECK-NEXT: TAG_compile_unit CHECK-NOT: TAG CHECK: AT_name {{.*}}basic2.c +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _private_int 0000000000000560 => 0000000100001008 +CHECK-NEXT: DW_TAG_variable +CHECK-NEXT: DW_AT_name {{.*}}"private_int" +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _baz 0000000000000310 => 0000000100001000 +CHECK-NEXT: DW_TAG_variable +CHECK-NEXT: DW_AT_name {{.*}}"baz" +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _foo 0000000000000020 => 0000000100000ed0 +CHECK-NEXT: DW_TAG_subprogram +CHECK-NEXT: DW_AT_name {{.*}}"foo" +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _inc 0000000000000070 => 0000000100000f20 +CHECK-NEXT: DW_TAG_subprogram +CHECK-NEXT: DW_AT_name {{.*}}"inc" + CHECK: DEBUG MAP OBJECT: {{.*}}basic3.macho.x86_64.o CHECK: Input compilation unit: CHECK-NEXT: TAG_compile_unit CHECK-NOT: TAG CHECK: AT_name {{.*}}basic3.c +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _val 0000000000000004 => 0000000100001004 +CHECK-NEXT: DW_TAG_variable +CHECK-NEXT: DW_AT_name {{.*}}"val" +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _bar 0000000000000020 => 0000000100000f40 +CHECK-NEXT: DW_TAG_subprogram +CHECK-NEXT: DW_AT_name {{.*}}"bar" +CHECK-NOT: Found valid debug map entry +CHECK: Found valid debug map entry: _inc 0000000000000070 => 0000000100000f90 +CHECK-NEXT: DW_TAG_subprogram +CHECK-NEXT: DW_AT_name {{.*}}"inc") +================================= LTO link ================================ CHECK-LTO: DEBUG MAP OBJECT: {{.*}}basic-lto.macho.x86_64.o CHECK-LTO: Input compilation unit: CHECK-LTO-NEXT: TAG_compile_unit @@ -35,20 +71,79 @@ CHECK-LTO-NEXT: TAG_compile_unit CHECK-LTO-NOT: TAG CHECK-LTO: AT_name {{.*}}basic3.c +CHECK-LTO-NOT: Found valid debug map entry +CHECK-LTO: Found valid debug map entry: _main 0000000000000000 => 0000000100000f40 +CHECK-LTO-NEXT: DW_TAG_subprogram +CHECK-LTO-NEXT: DW_AT_name {{.*}}"main" +CHECK-LTO-NOT: Found valid debug map entry +CHECK-LTO: Found valid debug map entry: _private_int 00000000000008e8 => 0000000100001008 +CHECK-LTO-NEXT: DW_TAG_variable +CHECK-LTO-NEXT: DW_AT_name {{.*}}"private_int" +CHECK-LTO-NOT: Found valid debug map entry +CHECK-LTO: Found valid debug map entry: _baz 0000000000000658 => 0000000100001000 +CHECK-LTO-NEXT: DW_TAG_variable +CHECK-LTO-NEXT: DW_AT_name {{.*}} "baz" +CHECK-LTO-NOT: Found valid debug map entry +CHECK-LTO: Found valid debug map entry: _foo 0000000000000010 => 0000000100000f50 +CHECK-LTO-NEXT: DW_TAG_subprogram +CHECK-LTO-NEXT: DW_AT_name {{.*}}"foo" +CHECK-LTO-NOT: Found valid debug map entry +CHECK-LTO: Found valid debug map entry: _val 00000000000008ec => 0000000100001004 +CHECK-LTO-NEXT: DW_TAG_variable +CHECK-LTO-NEXT: DW_AT_name {{.*}}"val" +CHECK-LTO-NOT: Found valid debug map entry +CHECK-LTO: Found valid debug map entry: _bar 0000000000000050 => 0000000100000f90 +CHECK-LTO-NEXT: DW_TAG_subprogram +CHECK-LTO-NEXT: DW_AT_name {{.*}}"bar" + +================================= Archive link ================================ CHECK-ARCHIVE: DEBUG MAP OBJECT: {{.*}}basic1.macho.x86_64.o CHECK-ARCHIVE: Input compilation unit: CHECK-ARCHIVE-NEXT: TAG_compile_unit CHECK-ARCHIVE-NOT: TAG CHECK-ARCHIVE: AT_name {{.*}}basic1.c +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _main 0000000000000000 => 0000000100000ea0 +CHECK-ARCHIVE-NEXT: DW_TAG_subprogram +CHECK-ARCHIVE-NEXT: DW_AT_name{{.*}}"main" + CHECK-ARCHIVE: DEBUG MAP OBJECT: {{.*}}libbasic.a(basic2.macho.x86_64.o) CHECK-ARCHIVE: Input compilation unit: CHECK-ARCHIVE-NEXT: TAG_compile_unit CHECK-ARCHIVE-NOT: TAG CHECK-ARCHIVE: AT_name {{.*}}basic2.c +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _private_int 0000000000000560 => 0000000100001004 +CHECK-ARCHIVE-NEXT: DW_TAG_variable +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"private_int" +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _baz 0000000000000310 => 0000000100001000 +CHECK-ARCHIVE-NEXT: DW_TAG_variable +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"baz" +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _foo 0000000000000020 => 0000000100000ed0 +CHECK-ARCHIVE-NEXT: DW_TAG_subprogram +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"foo" +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _inc 0000000000000070 => 0000000100000f20 +CHECK-ARCHIVE-NEXT: DW_TAG_subprogram +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"inc" + CHECK-ARCHIVE: DEBUG MAP OBJECT: {{.*}}libbasic.a(basic3.macho.x86_64.o) CHECK-ARCHIVE: Input compilation unit: CHECK-ARCHIVE-NEXT: TAG_compile_unit CHECK-ARCHIVE-NOT: TAG CHECK-ARCHIVE: AT_name {{.*}}basic3.c - +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _val 0000000000000004 => 0000000100001008 +CHECK-ARCHIVE-NEXT: DW_TAG_variable +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"val" +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _bar 0000000000000020 => 0000000100000f40 +CHECK-ARCHIVE-NEXT: DW_TAG_subprogram +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"bar" +CHECK-ARCHIVE-NOT: Found valid debug map entry +CHECK-ARCHIVE: Found valid debug map entry: _inc 0000000000000070 => 0000000100000f90 +CHECK-ARCHIVE-NEXT: DW_TAG_subprogram +CHECK-ARCHIVE-NEXT: DW_AT_name {{.*}}"inc") diff --git a/llvm/tools/dsymutil/DwarfLinker.cpp b/llvm/tools/dsymutil/DwarfLinker.cpp index e6c8ba550bea..3c0bc0b85afa 100644 --- a/llvm/tools/dsymutil/DwarfLinker.cpp +++ b/llvm/tools/dsymutil/DwarfLinker.cpp @@ -14,6 +14,8 @@ #include "llvm/DebugInfo/DWARF/DWARFDebugInfoEntry.h" #include "llvm/DebugInfo/DWARF/DWARFFormValue.h" #include "llvm/Object/MachO.h" +#include "llvm/Support/Dwarf.h" +#include "llvm/Support/LEB128.h" #include namespace llvm { @@ -28,7 +30,10 @@ class CompileUnit { public: /// \brief Information gathered about a DIE in the object file. struct DIEInfo { - uint32_t ParentIdx; + uint64_t Address; ///< Linked address of the described entity. + uint32_t ParentIdx; ///< The index of this DIE's parent. + bool Keep; ///< Is the DIE part of the linked output? + bool InDebugMap; ///< Was this DIE's entity found in the map? }; CompileUnit(DWARFUnit &OrigUnit) : OrigUnit(OrigUnit) { @@ -114,6 +119,47 @@ private: const DebugMapObject &DMO); /// @} + /// \defgroup FindRootDIEs Find DIEs corresponding to debug map entries. + /// + /// @{ + /// \brief Recursively walk the \p DIE tree and look for DIEs to + /// keep. Store that information in \p CU's DIEInfo. + void lookForDIEsToKeep(const DWARFDebugInfoEntryMinimal &DIE, + const DebugMapObject &DMO, CompileUnit &CU, + unsigned Flags); + + /// \brief Flags passed to DwarfLinker::lookForDIEsToKeep + enum TravesalFlags { + TF_Keep = 1 << 0, ///< Mark the traversed DIEs as kept. + TF_InFunctionScope = 1 << 1, ///< Current scope is a fucntion scope. + TF_DependencyWalk = 1 << 2, ///< Walking the dependencies of a kept DIE. + TF_ParentWalk = 1 << 3, ///< Walking up the parents of a kept DIE. + }; + + /// \brief Mark the passed DIE as well as all the ones it depends on + /// as kept. + void keepDIEAndDenpendencies(const DWARFDebugInfoEntryMinimal &DIE, + CompileUnit::DIEInfo &MyInfo, + const DebugMapObject &DMO, CompileUnit &CU, + unsigned Flags); + + unsigned shouldKeepDIE(const DWARFDebugInfoEntryMinimal &DIE, + CompileUnit &Unit, CompileUnit::DIEInfo &MyInfo, + unsigned Flags); + + unsigned shouldKeepVariableDIE(const DWARFDebugInfoEntryMinimal &DIE, + CompileUnit &Unit, + CompileUnit::DIEInfo &MyInfo, unsigned Flags); + + unsigned shouldKeepSubprogramDIE(const DWARFDebugInfoEntryMinimal &DIE, + CompileUnit &Unit, + CompileUnit::DIEInfo &MyInfo, + unsigned Flags); + + bool hasValidRelocation(uint32_t StartOffset, uint32_t EndOffset, + CompileUnit::DIEInfo &Info); + /// @} + /// \defgroup Helpers Various helper methods. /// /// @{ @@ -199,6 +245,21 @@ static void gatherDIEParents(const DWARFDebugInfoEntryMinimal *DIE, gatherDIEParents(Child, MyIdx, CU); } +static bool dieNeedsChildrenToBeMeaningful(uint32_t Tag) { + switch (Tag) { + default: + return false; + case dwarf::DW_TAG_subprogram: + case dwarf::DW_TAG_lexical_block: + case dwarf::DW_TAG_subroutine_type: + case dwarf::DW_TAG_structure_type: + case dwarf::DW_TAG_class_type: + case dwarf::DW_TAG_union_type: + return true; + } + llvm_unreachable("Invalid Tag"); +} + void DwarfLinker::startDebugObject(DWARFContext &Dwarf) { Units.reserve(Dwarf.getNumCompileUnits()); NextValidReloc = 0; @@ -291,6 +352,255 @@ bool DwarfLinker::findValidRelocsInDebugInfo(const object::ObjectFile &Obj, return false; } +/// \brief Checks that there is a relocation against an actual debug +/// map entry between \p StartOffset and \p NextOffset. +/// +/// This function must be called with offsets in strictly ascending +/// order because it never looks back at relocations it already 'went past'. +/// \returns true and sets Info.InDebugMap if it is the case. +bool DwarfLinker::hasValidRelocation(uint32_t StartOffset, uint32_t EndOffset, + CompileUnit::DIEInfo &Info) { + assert(NextValidReloc == 0 || + StartOffset > ValidRelocs[NextValidReloc - 1].Offset); + if (NextValidReloc >= ValidRelocs.size()) + return false; + + uint64_t RelocOffset = ValidRelocs[NextValidReloc].Offset; + + // We might need to skip some relocs that we didn't consider. For + // example the high_pc of a discarded DIE might contain a reloc that + // is in the list because it actually corresponds to the start of a + // function that is in the debug map. + while (RelocOffset < StartOffset && NextValidReloc < ValidRelocs.size() - 1) + RelocOffset = ValidRelocs[++NextValidReloc].Offset; + + if (RelocOffset < StartOffset || RelocOffset >= EndOffset) + return false; + + const auto &ValidReloc = ValidRelocs[NextValidReloc++]; + if (Verbose) + outs() << "Found valid debug map entry: " << ValidReloc.Mapping->getKey() + << " " << format("\t%016" PRIx64 " => %016" PRIx64, + ValidReloc.Mapping->getValue().ObjectAddress, + ValidReloc.Mapping->getValue().BinaryAddress); + + Info.Address = + ValidReloc.Mapping->getValue().BinaryAddress + ValidReloc.Addend; + Info.InDebugMap = true; + return true; +} + +/// \brief Get the starting and ending (exclusive) offset for the +/// attribute with index \p Idx descibed by \p Abbrev. \p Offset is +/// supposed to point to the position of the first attribute described +/// by \p Abbrev. +/// \return [StartOffset, EndOffset) as a pair. +static std::pair +getAttributeOffsets(const DWARFAbbreviationDeclaration *Abbrev, unsigned Idx, + unsigned Offset, const DWARFUnit &Unit) { + DataExtractor Data = Unit.getDebugInfoExtractor(); + + for (unsigned i = 0; i < Idx; ++i) + DWARFFormValue::skipValue(Abbrev->getFormByIndex(i), Data, &Offset, &Unit); + + uint32_t End = Offset; + DWARFFormValue::skipValue(Abbrev->getFormByIndex(Idx), Data, &End, &Unit); + + return std::make_pair(Offset, End); +} + +/// \brief Check if a variable describing DIE should be kept. +/// \returns updated TraversalFlags. +unsigned DwarfLinker::shouldKeepVariableDIE( + const DWARFDebugInfoEntryMinimal &DIE, CompileUnit &Unit, + CompileUnit::DIEInfo &MyInfo, unsigned Flags) { + const auto *Abbrev = DIE.getAbbreviationDeclarationPtr(); + + // Global variables with constant value can always be kept. + if (!(Flags & TF_InFunctionScope) && + Abbrev->findAttributeIndex(dwarf::DW_AT_const_value) != -1U) { + MyInfo.InDebugMap = true; + return Flags | TF_Keep; + } + + uint32_t LocationIdx = Abbrev->findAttributeIndex(dwarf::DW_AT_location); + if (LocationIdx == -1U) + return Flags; + + uint32_t Offset = DIE.getOffset() + getULEB128Size(Abbrev->getCode()); + const DWARFUnit &OrigUnit = Unit.getOrigUnit(); + uint32_t LocationOffset, LocationEndOffset; + std::tie(LocationOffset, LocationEndOffset) = + getAttributeOffsets(Abbrev, LocationIdx, Offset, OrigUnit); + + // See if there is a relocation to a valid debug map entry inside + // this variable's location. The order is important here. We want to + // always check in the variable has a valid relocation, so that the + // DIEInfo is filled. However, we don't want a static variable in a + // function to force us to keep the enclosing function. + if (!hasValidRelocation(LocationOffset, LocationEndOffset, MyInfo) || + (Flags & TF_InFunctionScope)) + return Flags; + + if (Verbose) + DIE.dump(outs(), const_cast(&OrigUnit), 0, 8 /* Indent */); + + return Flags | TF_Keep; +} + +/// \brief Check if a function describing DIE should be kept. +/// \returns updated TraversalFlags. +unsigned DwarfLinker::shouldKeepSubprogramDIE( + const DWARFDebugInfoEntryMinimal &DIE, CompileUnit &Unit, + CompileUnit::DIEInfo &MyInfo, unsigned Flags) { + const auto *Abbrev = DIE.getAbbreviationDeclarationPtr(); + + Flags |= TF_InFunctionScope; + + uint32_t LowPcIdx = Abbrev->findAttributeIndex(dwarf::DW_AT_low_pc); + if (LowPcIdx == -1U) + return Flags; + + uint32_t Offset = DIE.getOffset() + getULEB128Size(Abbrev->getCode()); + const DWARFUnit &OrigUnit = Unit.getOrigUnit(); + uint32_t LowPcOffset, LowPcEndOffset; + std::tie(LowPcOffset, LowPcEndOffset) = + getAttributeOffsets(Abbrev, LowPcIdx, Offset, OrigUnit); + + uint64_t LowPc = + DIE.getAttributeValueAsAddress(&OrigUnit, dwarf::DW_AT_low_pc, -1ULL); + assert(LowPc != -1ULL && "low_pc attribute is not an address."); + if (LowPc == -1ULL || + !hasValidRelocation(LowPcOffset, LowPcEndOffset, MyInfo)) + return Flags; + + if (Verbose) + DIE.dump(outs(), const_cast(&OrigUnit), 0, 8 /* Indent */); + + return Flags | TF_Keep; +} + +/// \brief Check if a DIE should be kept. +/// \returns updated TraversalFlags. +unsigned DwarfLinker::shouldKeepDIE(const DWARFDebugInfoEntryMinimal &DIE, + CompileUnit &Unit, + CompileUnit::DIEInfo &MyInfo, + unsigned Flags) { + switch (DIE.getTag()) { + case dwarf::DW_TAG_constant: + case dwarf::DW_TAG_variable: + return shouldKeepVariableDIE(DIE, Unit, MyInfo, Flags); + case dwarf::DW_TAG_subprogram: + return shouldKeepSubprogramDIE(DIE, Unit, MyInfo, Flags); + case dwarf::DW_TAG_module: + case dwarf::DW_TAG_imported_module: + case dwarf::DW_TAG_imported_declaration: + case dwarf::DW_TAG_imported_unit: + // We always want to keep these. + return Flags | TF_Keep; + } + + return Flags; +} + + +/// \brief Mark the passed DIE as well as all the ones it depends on +/// as kept. +/// +/// This function is called by lookForDIEsToKeep on DIEs that are +/// newly discovered to be needed in the link. It recursively calls +/// back to lookForDIEsToKeep while adding TF_DependencyWalk to the +/// TraversalFlags to inform it that it's not doing the primary DIE +/// tree walk. +void DwarfLinker::keepDIEAndDenpendencies(const DWARFDebugInfoEntryMinimal &DIE, + CompileUnit::DIEInfo &MyInfo, + const DebugMapObject &DMO, + CompileUnit &CU, unsigned Flags) { + const DWARFUnit &Unit = CU.getOrigUnit(); + MyInfo.Keep = true; + + // First mark all the parent chain as kept. + unsigned AncestorIdx = MyInfo.ParentIdx; + while (!CU.getInfo(AncestorIdx).Keep) { + lookForDIEsToKeep(*Unit.getDIEAtIndex(AncestorIdx), DMO, CU, + TF_ParentWalk | TF_Keep | TF_DependencyWalk); + AncestorIdx = CU.getInfo(AncestorIdx).ParentIdx; + } + + // Then we need to mark all the DIEs referenced by this DIE's + // attributes as kept. + DataExtractor Data = Unit.getDebugInfoExtractor(); + const auto *Abbrev = DIE.getAbbreviationDeclarationPtr(); + uint32_t Offset = DIE.getOffset() + getULEB128Size(Abbrev->getCode()); + + // Mark all DIEs referenced through atttributes as kept. + for (const auto &AttrSpec : Abbrev->attributes()) { + DWARFFormValue Val(AttrSpec.Form); + + if (!Val.isFormClass(DWARFFormValue::FC_Reference)) { + DWARFFormValue::skipValue(AttrSpec.Form, Data, &Offset, &Unit); + continue; + } + + Val.extractValue(Data, &Offset, &Unit); + CompileUnit *ReferencedCU; + if (const auto *RefDIE = resolveDIEReference(Val, Unit, DIE, ReferencedCU)) + lookForDIEsToKeep(*RefDIE, DMO, *ReferencedCU, + TF_Keep | TF_DependencyWalk); + } +} + +/// \brief Recursively walk the \p DIE tree and look for DIEs to +/// keep. Store that information in \p CU's DIEInfo. +/// +/// This function is the entry point of the DIE selection +/// algorithm. It is expected to walk the DIE tree in file order and +/// (though the mediation of its helper) call hasValidRelocation() on +/// each DIE that might be a 'root DIE' (See DwarfLinker class +/// comment). +/// While walking the dependencies of root DIEs, this function is +/// also called, but during these dependency walks the file order is +/// not respected. The TF_DependencyWalk flag tells us which kind of +/// traversal we are currently doing. +void DwarfLinker::lookForDIEsToKeep(const DWARFDebugInfoEntryMinimal &DIE, + const DebugMapObject &DMO, CompileUnit &CU, + unsigned Flags) { + unsigned Idx = CU.getOrigUnit().getDIEIndex(&DIE); + CompileUnit::DIEInfo &MyInfo = CU.getInfo(Idx); + bool AlreadyKept = MyInfo.Keep; + + // If the Keep flag is set, we are marking a required DIE's + // dependencies. If our target is already marked as kept, we're all + // set. + if ((Flags & TF_DependencyWalk) && AlreadyKept) + return; + + // We must not call shouldKeepDIE while called from keepDIEAndDenpendencies, + // because it would screw up the relocation finding logic. + if (!(Flags & TF_DependencyWalk)) + Flags = shouldKeepDIE(DIE, CU, MyInfo, Flags); + + // If it is a newly kept DIE mark it as well as all its dependencies as kept. + if (!AlreadyKept && (Flags & TF_Keep)) + keepDIEAndDenpendencies(DIE, MyInfo, DMO, CU, Flags); + + // The TF_ParentWalk flag tells us that we are currently walking up + // the parent chain of a required DIE, and we don't want to mark all + // the children of the parents as kept (consider for example a + // DW_TAG_namespace node in the parent chain). There are however a + // set of DIE types for which we want to ignore that directive and still + // walk their children. + if (dieNeedsChildrenToBeMeaningful(DIE.getTag())) + Flags &= ~TF_ParentWalk; + + if (!DIE.hasChildren() || (Flags & TF_ParentWalk)) + return; + + for (auto *Child = DIE.getFirstChild(); Child && !Child->isNULL(); + Child = Child->getSibling()) + lookForDIEsToKeep(*Child, DMO, CU, Flags); +} + bool DwarfLinker::link(const DebugMap &Map) { if (Map.begin() == Map.end()) { @@ -332,6 +642,15 @@ bool DwarfLinker::link(const DebugMap &Map) { gatherDIEParents(CUDie, 0, Units.back()); } + // Then mark all the DIEs that need to be present in the linked + // output and collect some information about them. Note that this + // loop can not be merged with the previous one becaue cross-cu + // references require the ParentIdx to be setup for every CU in + // the object file before calling this. + for (auto &CurrentUnit : Units) + lookForDIEsToKeep(*CurrentUnit.getOrigUnit().getCompileUnitDIE(), *Obj, + CurrentUnit, 0); + // Clean-up before starting working on the next object. endDebugObject(); }