From: Adrian Prantl Date: Mon, 22 Apr 2019 21:33:22 +0000 (+0000) Subject: [dsymutil] Collect parseable Swift interfaces in the .dSYM bundle. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=80d580cd44ae6367b2c462d8e2254df3c11ce086;p=llvm [dsymutil] Collect parseable Swift interfaces in the .dSYM bundle. When a Swift module built with debug info imports a library without debug info from a textual interface, the textual interface is necessary to reconstruct types defined in the library's interface. By recording the Swift interface files in DWARF dsymutil can collect them and LLDB can find them. This patch teaches dsymutil to look for DW_TAG_imported_modules and records all references to parseable Swift ingterfrace files and copies them to a.out.dSYM/Contents/Resources//.swiftinterface git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@358921 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/llvm/DebugInfo/DWARF/DWARFFormValue.h b/include/llvm/DebugInfo/DWARF/DWARFFormValue.h index 8107624f8a8..279a2e7e352 100644 --- a/include/llvm/DebugInfo/DWARF/DWARFFormValue.h +++ b/include/llvm/DebugInfo/DWARF/DWARFFormValue.h @@ -159,6 +159,19 @@ inline Optional toString(const Optional &V) { return None; } +/// Take an optional DWARFFormValue and try to extract a string value from it. +/// +/// \param V and optional DWARFFormValue to attempt to extract the value from. +/// \returns an optional value that contains a value if the form value +/// was valid and was a string. +inline StringRef toStringRef(const Optional &V, + StringRef Default = {}) { + if (V) + if (auto S = V->getAsCString()) + return *S; + return Default; +} + /// Take an optional DWARFFormValue and extract a string value from it. /// /// \param V and optional DWARFFormValue to attempt to extract the value from. diff --git a/test/tools/dsymutil/Inputs/swift-interface.ll b/test/tools/dsymutil/Inputs/swift-interface.ll new file mode 100644 index 00000000000..715800e6d61 --- /dev/null +++ b/test/tools/dsymutil/Inputs/swift-interface.ll @@ -0,0 +1,34 @@ +; This is a manually stripped empty Swift program with one import. +source_filename = "/swift-interface.ll" +target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-apple-macosx10.9.0" + +@__swift_reflection_version = linkonce_odr hidden constant i16 3 +@llvm.used = appending global [1 x i8*] [i8* bitcast (i16* @__swift_reflection_version to i8*)], section "llvm.metadata", align 8 + +define i32 @main(i32, i8**) !dbg !29 { +entry: + %2 = bitcast i8** %1 to i8* + ret i32 0, !dbg !35 +} + +!llvm.dbg.cu = !{!0} +!swift.module.flags = !{!14} +!llvm.module.flags = !{!20, !21, !24} + +!0 = distinct !DICompileUnit(language: DW_LANG_Swift, file: !1, isOptimized: false, runtimeVersion: 5, emissionKind: FullDebug, enums: !2, imports: !3) +!1 = !DIFile(filename: "ParseableInterfaceImports.swift", directory: "/") +!2 = !{} +!3 = !{!4} +!4 = !DIImportedEntity(tag: DW_TAG_imported_module, scope: !1, entity: !5, file: !1) +!5 = !DIModule(scope: null, name: "Foo", includePath: "/Foo/x86_64.swiftinterface") +!14 = !{!"standard-library", i1 false} +!20 = !{i32 2, !"Dwarf Version", i32 4} +!21 = !{i32 2, !"Debug Info Version", i32 3} +!24 = !{i32 1, !"Swift Version", i32 7} +!29 = distinct !DISubprogram(name: "main", linkageName: "main", scope: !5, file: !1, line: 1, type: !30, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2) +!30 = !DISubroutineType(types: !31) +!31 = !{} +!35 = !DILocation(line: 0, scope: !36) +!36 = !DILexicalBlockFile(scope: !29, file: !37, discriminator: 0) +!37 = !DIFile(filename: "", directory: "") diff --git a/test/tools/dsymutil/X86/swift-interface.test b/test/tools/dsymutil/X86/swift-interface.test new file mode 100644 index 00000000000..4bfd2b89e7c --- /dev/null +++ b/test/tools/dsymutil/X86/swift-interface.test @@ -0,0 +1,23 @@ +# RUN: rm -rf %t.dir +# RUN: mkdir -p %t.dir/obj +# RUN: mkdir -p %t.dir/Foo/x86_64 +# RUN: llc %p/../Inputs/swift-interface.ll -filetype=obj -o %t.dir/obj/1.o +# RUN: dsymutil -oso-prepend-path %t.dir -y %s \ +# RUN: -o %t.dir/swift-interface.dSYM 2>&1 \ +# RUN: | FileCheck %s --check-prefix=WARNINGS +# RUN: echo "// module Foo" >%t.dir/Foo/x86_64.swiftinterface +# RUN: dsymutil -oso-prepend-path %t.dir -y %s \ +# RUN: -o %t.dir/swift-interface.dSYM +# RUN: cat %t.dir/swift-interface.dSYM/Contents/Resources/Swift/Foo.swiftinterface \ +# RUN: | FileCheck %s --check-prefix=INTERFACE + +# WARNINGS: cannot copy parseable Swift interface +# INTERFACE: module Foo + +--- +triple: 'x86_64-apple-darwin' +objects: + - filename: obj/1.o + symbols: + - { sym: _main, objAddr: 0x0, binAddr: 0x10000, size: 0x10 } +... diff --git a/tools/dsymutil/CompileUnit.cpp b/tools/dsymutil/CompileUnit.cpp index 670591a9436..036c61a6b92 100644 --- a/tools/dsymutil/CompileUnit.cpp +++ b/tools/dsymutil/CompileUnit.cpp @@ -22,6 +22,14 @@ static bool inFunctionScope(CompileUnit &U, unsigned Idx) { return false; } +uint16_t CompileUnit::getLanguage() { + if (!Language) { + DWARFDie CU = getOrigUnit().getUnitDIE(); + Language = dwarf::toUnsigned(CU.find(dwarf::DW_AT_language), 0); + } + return Language; +} + void CompileUnit::markEverythingAsKept() { unsigned Idx = 0; diff --git a/tools/dsymutil/CompileUnit.h b/tools/dsymutil/CompileUnit.h index 6e3c2c669e0..e0f5d3bc65b 100644 --- a/tools/dsymutil/CompileUnit.h +++ b/tools/dsymutil/CompileUnit.h @@ -114,6 +114,8 @@ public: bool hasODR() const { return HasODR; } bool isClangModule() const { return !ClangModuleName.empty(); } + uint16_t getLanguage(); + const std::string &getClangModuleName() const { return ClangModuleName; } DIEInfo &getInfo(unsigned Idx) { return Info[Idx]; } @@ -316,6 +318,9 @@ private: /// Did a DIE actually contain a valid reloc? bool HasInterestingContent; + /// The DW_AT_language of this unit. + uint16_t Language = 0; + /// If this is a Clang module, this holds the module's name. std::string ClangModuleName; }; diff --git a/tools/dsymutil/DwarfLinker.cpp b/tools/dsymutil/DwarfLinker.cpp index fa408d7cfe7..e97d5026174 100644 --- a/tools/dsymutil/DwarfLinker.cpp +++ b/tools/dsymutil/DwarfLinker.cpp @@ -243,18 +243,55 @@ bool DwarfLinker::createStreamer(const Triple &TheTriple, return Streamer->init(TheTriple); } +/// Resolve the relative path to a build artifact referenced by DWARF by +/// applying DW_AT_comp_dir. +static void resolveRelativeObjectPath(SmallVectorImpl &Buf, DWARFDie CU) { + sys::path::append(Buf, dwarf::toString(CU.find(dwarf::DW_AT_comp_dir), "")); +} + +/// Collect references to parseable Swift interfaces in imported +/// DW_TAG_module blocks. +static void analyzeImportedModule( + const DWARFDie &DIE, CompileUnit &CU, + std::map &ParseableSwiftInterfaces, + std::function ReportWarning) { + if (CU.getLanguage() != dwarf::DW_LANG_Swift) + return; + + StringRef Path = dwarf::toStringRef(DIE.find(dwarf::DW_AT_LLVM_include_path)); + if (!Path.endswith(".swiftinterface")) + return; + if (Optional Val = DIE.find(dwarf::DW_AT_name)) + if (Optional Name = Val->getAsCString()) { + auto &Entry = ParseableSwiftInterfaces[*Name]; + // The prepend path is applied later when copying. + DWARFDie CUDie = CU.getOrigUnit().getUnitDIE(); + SmallString<128> ResolvedPath; + if (sys::path::is_relative(Path)) + resolveRelativeObjectPath(ResolvedPath, CUDie); + sys::path::append(ResolvedPath, Path); + if (!Entry.empty() && Entry != ResolvedPath) + ReportWarning( + Twine("Conflicting parseable interfaces for Swift Module ") + + *Name + ": " + Entry + " and " + Path, + DIE); + Entry = ResolvedPath.str(); + } +} + /// Recursive helper to build the global DeclContext information and /// gather the child->parent relationships in the original compile unit. /// /// \return true when this DIE and all of its children are only /// forward declarations to types defined in external clang modules /// (i.e., forward declarations that are children of a DW_TAG_module). -static bool analyzeContextInfo(const DWARFDie &DIE, unsigned ParentIdx, - CompileUnit &CU, DeclContext *CurrentDeclContext, - UniquingStringPool &StringPool, - DeclContextTree &Contexts, - uint64_t ModulesEndOffset, - bool InImportedModule = false) { +static bool analyzeContextInfo( + const DWARFDie &DIE, unsigned ParentIdx, CompileUnit &CU, + DeclContext *CurrentDeclContext, UniquingStringPool &StringPool, + DeclContextTree &Contexts, uint64_t ModulesEndOffset, + std::map &ParseableSwiftInterfaces, + std::function ReportWarning, + bool InImportedModule = false) { unsigned MyIdx = CU.getOrigUnit().getDIEIndex(DIE); CompileUnit::DIEInfo &Info = CU.getInfo(MyIdx); @@ -274,6 +311,7 @@ static bool analyzeContextInfo(const DWARFDie &DIE, unsigned ParentIdx, dwarf::toString(DIE.find(dwarf::DW_AT_name), "") != CU.getClangModuleName()) { InImportedModule = true; + analyzeImportedModule(DIE, CU, ParseableSwiftInterfaces, ReportWarning); } Info.ParentIdx = ParentIdx; @@ -294,9 +332,10 @@ static bool analyzeContextInfo(const DWARFDie &DIE, unsigned ParentIdx, Info.Prune = InImportedModule; if (DIE.hasChildren()) for (auto Child : DIE.children()) - Info.Prune &= - analyzeContextInfo(Child, MyIdx, CU, CurrentDeclContext, StringPool, - Contexts, ModulesEndOffset, InImportedModule); + Info.Prune &= analyzeContextInfo(Child, MyIdx, CU, CurrentDeclContext, + StringPool, Contexts, ModulesEndOffset, + ParseableSwiftInterfaces, ReportWarning, + InImportedModule); // Prune this DIE if it is either a forward declaration inside a // DW_TAG_module or a DW_TAG_module that contains nothing but @@ -2106,7 +2145,7 @@ static uint64_t getDwoId(const DWARFDie &CUDie, const DWARFUnit &Unit) { } bool DwarfLinker::registerModuleReference( - const DWARFDie &CUDie, const DWARFUnit &Unit, DebugMap &ModuleMap, + DWARFDie CUDie, const DWARFUnit &Unit, DebugMap &ModuleMap, const DebugMapObject &DMO, RangesTy &Ranges, OffsetsStringPool &StringPool, UniquingStringPool &UniquingStringPool, DeclContextTree &ODRContexts, uint64_t ModulesEndOffset, unsigned &UnitID, bool IsLittleEndian, @@ -2117,7 +2156,6 @@ bool DwarfLinker::registerModuleReference( return false; // Clang module DWARF skeleton CUs abuse this for the path to the module. - std::string PCMpath = dwarf::toString(CUDie.find(dwarf::DW_AT_comp_dir), ""); uint64_t DwoId = getDwoId(CUDie, Unit); std::string Name = dwarf::toString(CUDie.find(dwarf::DW_AT_name), ""); @@ -2152,7 +2190,8 @@ bool DwarfLinker::registerModuleReference( // Cyclic dependencies are disallowed by Clang, but we still // shouldn't run into an infinite loop, so mark it as processed now. ClangModules.insert({PCMfile, DwoId}); - if (Error E = loadClangModule(PCMfile, PCMpath, Name, DwoId, ModuleMap, DMO, + + if (Error E = loadClangModule(CUDie, PCMfile, Name, DwoId, ModuleMap, DMO, Ranges, StringPool, UniquingStringPool, ODRContexts, ModulesEndOffset, UnitID, IsLittleEndian, Indent + 2, Quiet)) { @@ -2185,17 +2224,16 @@ DwarfLinker::loadObject(const DebugMapObject &Obj, const DebugMap &Map) { } Error DwarfLinker::loadClangModule( - StringRef Filename, StringRef ModulePath, StringRef ModuleName, - uint64_t DwoId, DebugMap &ModuleMap, const DebugMapObject &DMO, - RangesTy &Ranges, OffsetsStringPool &StringPool, - UniquingStringPool &UniquingStringPool, DeclContextTree &ODRContexts, - uint64_t ModulesEndOffset, unsigned &UnitID, bool IsLittleEndian, - unsigned Indent, bool Quiet) { - SmallString<80> Path(Options.PrependPath); + DWARFDie CUDie, StringRef Filename, StringRef ModuleName, uint64_t DwoId, + DebugMap &ModuleMap, const DebugMapObject &DMO, RangesTy &Ranges, + OffsetsStringPool &StringPool, UniquingStringPool &UniquingStringPool, + DeclContextTree &ODRContexts, uint64_t ModulesEndOffset, unsigned &UnitID, + bool IsLittleEndian, unsigned Indent, bool Quiet) { + /// Using a SmallString<0> because loadClangModule() is recursive. + SmallString<0> Path(Options.PrependPath); if (sys::path::is_relative(Filename)) - sys::path::append(Path, ModulePath, Filename); - else - sys::path::append(Path, Filename); + resolveRelativeObjectPath(Path, CUDie); + sys::path::append(Path, Filename); // Don't use the cached binary holder because we have no thread-safety // guarantee and the lifetime is limited. auto &Obj = ModuleMap.addDebugMapObject( @@ -2282,7 +2320,11 @@ Error DwarfLinker::loadClangModule( ModuleName); Unit->setHasInterestingContent(); analyzeContextInfo(CUDie, 0, *Unit, &ODRContexts.getRoot(), - UniquingStringPool, ODRContexts, ModulesEndOffset); + UniquingStringPool, ODRContexts, ModulesEndOffset, + ParseableSwiftInterfaces, + [&](const Twine &Warning, const DWARFDie &DIE) { + reportWarning(Warning, DMO, &DIE); + }); // Keep everything. Unit->markEverythingAsKept(); } @@ -2449,6 +2491,43 @@ bool DwarfLinker::emitPaperTrailWarnings(const DebugMapObject &DMO, return true; } +static Error copySwiftInterfaces( + const std::map &ParseableSwiftInterfaces, + StringRef Architecture, const LinkOptions &Options) { + std::error_code EC; + SmallString<128> InputPath; + SmallString<128> Path; + sys::path::append(Path, *Options.ResourceDir, "Swift"); + if ((EC = sys::fs::create_directories(Path.str(), true, + sys::fs::perms::all_all))) + return make_error( + "cannot create directory: " + toString(errorCodeToError(EC)), EC); + unsigned BaseLength = Path.size(); + + for (auto &I : ParseableSwiftInterfaces) { + StringRef ModuleName = I.first; + StringRef InterfaceFile = I.second; + if (!Options.PrependPath.empty()) { + InputPath.clear(); + sys::path::append(InputPath, Options.PrependPath, InterfaceFile); + InterfaceFile = InputPath; + } + sys::path::append(Path, ModuleName); + Path.append(".swiftinterface"); + if (Options.Verbose) + outs() << "copy parseable Swift interface " << InterfaceFile << " -> " + << Path.str() << '\n'; + + // copy_file attempts an APFS clone first, so this should be cheap. + if ((EC = sys::fs::copy_file(InterfaceFile, Path.str()))) + warn(Twine("cannot copy parseable Swift interface ") + + InterfaceFile + ": " + + toString(errorCodeToError(EC))); + Path.resize(BaseLength); + } + return Error::success(); +} + bool DwarfLinker::link(const DebugMap &Map) { if (!createStreamer(Map.getTriple(), OutFile)) return false; @@ -2636,7 +2715,11 @@ bool DwarfLinker::link(const DebugMap &Map) { continue; analyzeContextInfo(CurrentUnit->getOrigUnit().getUnitDIE(), 0, *CurrentUnit, &ODRContexts.getRoot(), - UniquingStringPool, ODRContexts, ModulesEndOffset); + UniquingStringPool, ODRContexts, ModulesEndOffset, + ParseableSwiftInterfaces, + [&](const Twine &Warning, const DWARFDie &DIE) { + reportWarning(Warning, LinkContext.DMO, &DIE); + }); } }; @@ -2749,7 +2832,17 @@ bool DwarfLinker::link(const DebugMap &Map) { pool.wait(); } - return Options.NoOutput ? true : Streamer->finish(Map, Options.Translator); + if (Options.NoOutput) + return true; + + if (Options.ResourceDir && !ParseableSwiftInterfaces.empty()) { + StringRef ArchName = Triple::getArchTypeName(Map.getTriple().getArch()); + if (auto E = + copySwiftInterfaces(ParseableSwiftInterfaces, ArchName, Options)) + return error(toString(std::move(E))); + } + + return Streamer->finish(Map, Options.Translator); } // namespace dsymutil bool linkDwarf(raw_fd_ostream &OutFile, BinaryHolder &BinHolder, diff --git a/tools/dsymutil/DwarfLinker.h b/tools/dsymutil/DwarfLinker.h index fdc86cf442a..482c607d22c 100644 --- a/tools/dsymutil/DwarfLinker.h +++ b/tools/dsymutil/DwarfLinker.h @@ -193,7 +193,7 @@ private: /// A skeleton CU is a CU without children, a DW_AT_gnu_dwo_name /// pointing to the module, and a DW_AT_gnu_dwo_id with the module /// hash. - bool registerModuleReference(const DWARFDie &CUDie, const DWARFUnit &Unit, + bool registerModuleReference(DWARFDie CUDie, const DWARFUnit &Unit, DebugMap &ModuleMap, const DebugMapObject &DMO, RangesTy &Ranges, OffsetsStringPool &OffsetsStringPool, @@ -206,7 +206,7 @@ private: /// Recursively add the debug info in this clang module .pcm /// file (and all the modules imported by it in a bottom-up fashion) /// to Units. - Error loadClangModule(StringRef Filename, StringRef ModulePath, + Error loadClangModule(DWARFDie CUDie, StringRef FilePath, StringRef ModuleName, uint64_t DwoId, DebugMap &ModuleMap, const DebugMapObject &DMO, RangesTy &Ranges, OffsetsStringPool &OffsetsStringPool, @@ -494,6 +494,12 @@ private: /// Mapping the PCM filename to the DwoId. StringMap ClangModules; + /// A list of all .swiftinterface files referenced by the debug + /// info, mapping Module name to path on disk. The entries need to + /// be uniqued and sorted and there are only few entries expected + /// per compile unit, which is why this is a std::map. + std::map ParseableSwiftInterfaces; + bool ModuleCacheHintDisplayed = false; bool ArchiveHintDisplayed = false; }; diff --git a/tools/dsymutil/LinkUtils.h b/tools/dsymutil/LinkUtils.h index 2b3ccb29088..0cd5600b65d 100644 --- a/tools/dsymutil/LinkUtils.h +++ b/tools/dsymutil/LinkUtils.h @@ -62,6 +62,9 @@ struct LinkOptions { /// -oso-prepend-path std::string PrependPath; + /// The Resources directory in the .dSYM bundle. + Optional ResourceDir; + /// Symbol map translator. SymbolMapTranslator Translator; diff --git a/tools/dsymutil/dsymutil.cpp b/tools/dsymutil/dsymutil.cpp index 14f9a0e5137..1651fa86f79 100644 --- a/tools/dsymutil/dsymutil.cpp +++ b/tools/dsymutil/dsymutil.cpp @@ -278,23 +278,30 @@ static bool verify(llvm::StringRef OutputFile, llvm::StringRef Arch) { return false; } -static Expected getOutputFileName(llvm::StringRef InputFile) { +namespace { +struct OutputLocation { + std::string DWARFFile; + Optional ResourceDir; +}; +} + +static Expected getOutputFileName(llvm::StringRef InputFile) { if (OutputFileOpt == "-") - return OutputFileOpt; + return OutputLocation{OutputFileOpt, {}}; // When updating, do in place replacement. if (OutputFileOpt.empty() && (Update || !SymbolMap.empty())) - return InputFile; + return OutputLocation{InputFile, {}}; // If a flat dSYM has been requested, things are pretty simple. if (FlatOut) { if (OutputFileOpt.empty()) { if (InputFile == "-") - return "a.out.dwarf"; - return (InputFile + ".dwarf").str(); + return OutputLocation{"a.out.dwarf", {}}; + return OutputLocation{(InputFile + ".dwarf").str(), {}}; } - return OutputFileOpt; + return OutputLocation{OutputFileOpt, {}}; } // We need to create/update a dSYM bundle. @@ -307,17 +314,18 @@ static Expected getOutputFileName(llvm::StringRef InputFile) { // std::string DwarfFile = InputFile == "-" ? llvm::StringRef("a.out") : InputFile; - llvm::SmallString<128> BundleDir(OutputFileOpt); - if (BundleDir.empty()) - BundleDir = DwarfFile + ".dSYM"; - if (auto E = createBundleDir(BundleDir)) + llvm::SmallString<128> Path(OutputFileOpt); + if (Path.empty()) + Path = DwarfFile + ".dSYM"; + if (auto E = createBundleDir(Path)) return std::move(E); - if (auto E = createPlistFile(DwarfFile, BundleDir)) + if (auto E = createPlistFile(DwarfFile, Path)) return std::move(E); - llvm::sys::path::append(BundleDir, "Contents", "Resources", "DWARF", - llvm::sys::path::filename(DwarfFile)); - return BundleDir.str(); + llvm::sys::path::append(Path, "Contents", "Resources"); + StringRef ResourceDir = Path; + llvm::sys::path::append(Path, "DWARF", llvm::sys::path::filename(DwarfFile)); + return OutputLocation{Path.str(), ResourceDir.str()}; } /// Parses the command line options into the LinkOptions struct and performs @@ -544,13 +552,15 @@ int main(int argc, char **argv) { // types don't work with std::bind in the ThreadPool implementation. std::shared_ptr OS; - Expected OutputFileOrErr = getOutputFileName(InputFile); - if (!OutputFileOrErr) { - WithColor::error() << toString(OutputFileOrErr.takeError()); + Expected OutputLocationOrErr = + getOutputFileName(InputFile); + if (!OutputLocationOrErr) { + WithColor::error() << toString(OutputLocationOrErr.takeError()); return 1; } + OptionsOrErr->ResourceDir = OutputLocationOrErr->ResourceDir; - std::string OutputFile = *OutputFileOrErr; + std::string OutputFile = OutputLocationOrErr->DWARFFile; if (NeedsTempFiles) { TempFiles.emplace_back(Map->getTriple().getArchName().str()); @@ -597,12 +607,13 @@ int main(int argc, char **argv) { return 1; if (NeedsTempFiles) { - Expected OutputFileOrErr = getOutputFileName(InputFile); - if (!OutputFileOrErr) { - WithColor::error() << toString(OutputFileOrErr.takeError()); + Expected OutputLocationOrErr = getOutputFileName(InputFile); + if (!OutputLocationOrErr) { + WithColor::error() << toString(OutputLocationOrErr.takeError()); return 1; } - if (!MachOUtils::generateUniversalBinary(TempFiles, *OutputFileOrErr, + if (!MachOUtils::generateUniversalBinary(TempFiles, + OutputLocationOrErr->DWARFFile, *OptionsOrErr, SDKPath)) return 1; }