[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/<Arch>/<ModuleName>.swiftinterface

<rdar://problem/49751748>

llvm-svn: 358921
This commit is contained in:
Adrian Prantl 2019-04-22 21:33:22 +00:00
parent d748689c7f
commit 0d809aa218
9 changed files with 245 additions and 49 deletions

View File

@ -159,6 +159,19 @@ inline Optional<const char *> toString(const Optional<DWARFFormValue> &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<DWARFFormValue> &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.

View File

@ -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: "<compiler-generated>", directory: "")

View File

@ -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 }
...

View File

@ -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;

View File

@ -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;
};

View File

@ -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<char> &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<std::string, std::string> &ParseableSwiftInterfaces,
std::function<void(const Twine &, const DWARFDie &)> 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<DWARFFormValue> Val = DIE.find(dwarf::DW_AT_name))
if (Optional<const char *> 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<std::string, std::string> &ParseableSwiftInterfaces,
std::function<void(const Twine &, const DWARFDie &)> 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<std::string, std::string> &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<StringError>(
"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,

View File

@ -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<uint64_t> 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<std::string, std::string> ParseableSwiftInterfaces;
bool ModuleCacheHintDisplayed = false;
bool ArchiveHintDisplayed = false;
};

View File

@ -62,6 +62,9 @@ struct LinkOptions {
/// -oso-prepend-path
std::string PrependPath;
/// The Resources directory in the .dSYM bundle.
Optional<std::string> ResourceDir;
/// Symbol map translator.
SymbolMapTranslator Translator;

View File

@ -278,23 +278,30 @@ static bool verify(llvm::StringRef OutputFile, llvm::StringRef Arch) {
return false;
}
static Expected<std::string> getOutputFileName(llvm::StringRef InputFile) {
namespace {
struct OutputLocation {
std::string DWARFFile;
Optional<std::string> ResourceDir;
};
}
static Expected<OutputLocation> 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<std::string> getOutputFileName(llvm::StringRef InputFile) {
// <DWARF file(s)>
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<raw_fd_ostream> OS;
Expected<std::string> OutputFileOrErr = getOutputFileName(InputFile);
if (!OutputFileOrErr) {
WithColor::error() << toString(OutputFileOrErr.takeError());
Expected<OutputLocation> 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<std::string> OutputFileOrErr = getOutputFileName(InputFile);
if (!OutputFileOrErr) {
WithColor::error() << toString(OutputFileOrErr.takeError());
Expected<OutputLocation> 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;
}