diff --git a/docs/CommandGuide/llvm-symbolizer.rst b/docs/CommandGuide/llvm-symbolizer.rst index ce2d9c00435..96720e633f2 100644 --- a/docs/CommandGuide/llvm-symbolizer.rst +++ b/docs/CommandGuide/llvm-symbolizer.rst @@ -92,6 +92,13 @@ OPTIONS input (see example above). If architecture is not specified in either way, address will not be symbolized. Defaults to empty string. +.. option:: -dsym-hint= + + (Darwin-only flag). If the debug info for a binary isn't present in the default + location, look for the debug info at the .dSYM path provided via the + ``-dsym-hint`` flag. This flag can be used multiple times. + + EXIT STATUS ----------- diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test-exe b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe new file mode 100755 index 00000000000..ba3154cd516 Binary files /dev/null and b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe differ diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Info.plist b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Info.plist new file mode 100644 index 00000000000..4e84ad0cc59 --- /dev/null +++ b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.dsym-test-exe-differentname + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Resources/DWARF/dsym-test-exe-second b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Resources/DWARF/dsym-test-exe-second new file mode 100644 index 00000000000..c30dba3ff98 Binary files /dev/null and b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Resources/DWARF/dsym-test-exe-second differ diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-second b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-second new file mode 100755 index 00000000000..ba3154cd516 Binary files /dev/null and b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe-second differ diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Info.plist b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Info.plist new file mode 100644 index 00000000000..35b1c113da4 --- /dev/null +++ b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIdentifier + com.apple.xcode.dsym.dsym-test-exe + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + dSYM + CFBundleSignature + ???? + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Resources/DWARF/dsym-test-exe b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Resources/DWARF/dsym-test-exe new file mode 100644 index 00000000000..c30dba3ff98 Binary files /dev/null and b/test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Resources/DWARF/dsym-test-exe differ diff --git a/test/tools/llvm-symbolizer/Inputs/dsym-test.c b/test/tools/llvm-symbolizer/Inputs/dsym-test.c new file mode 100644 index 00000000000..84d5ad9e914 --- /dev/null +++ b/test/tools/llvm-symbolizer/Inputs/dsym-test.c @@ -0,0 +1,8 @@ +// clang -c dsym-test.c -g +// clang dsym-test.o -g -o dsym-test-exe +// dsymutil dsym-test-exe +// clang dsym-test.o -g -o dsym-test-exe-second +// dsymutil dsym-test-exe-second -o dsym-test-exe-differentname.dSYM +int main() { + return 0; +} diff --git a/test/tools/llvm-symbolizer/dsym.test b/test/tools/llvm-symbolizer/dsym.test new file mode 100644 index 00000000000..326602d3f73 --- /dev/null +++ b/test/tools/llvm-symbolizer/dsym.test @@ -0,0 +1,14 @@ +RUN: echo "%p/Inputs/dsym-test-exe 0x0000000100000f90" > %t.input +RUN: echo "%p/Inputs/dsym-test-exe-second 0x0000000100000f90" >> %t.input +RUN: llvm-symbolizer < %t.input | FileCheck %s --check-prefix=CHECK-NOHINT +RUN: llvm-symbolizer -dsym-hint=%p/Inputs/dsym-test-exe-differentname.dSYM < %t.input | FileCheck %s --check-prefix=CHECK-HINT + +CHECK-NOHINT: main +CHECK-NOHINT: dsym-test.c +CHECK-NOHINT: main +CHECK-NOHINT: ??:0:0 + +CHECK-HINT: main +CHECK-HINT: dsym-test.c +CHECK-HINT: main +CHECK-HINT: dsym-test.c diff --git a/tools/llvm-symbolizer/LLVMSymbolize.cpp b/tools/llvm-symbolizer/LLVMSymbolize.cpp index f1dccfdc40a..31bbedf8f03 100644 --- a/tools/llvm-symbolizer/LLVMSymbolize.cpp +++ b/tools/llvm-symbolizer/LLVMSymbolize.cpp @@ -207,14 +207,21 @@ std::string LLVMSymbolizer::symbolizeData(const std::string &ModuleName, void LLVMSymbolizer::flush() { DeleteContainerSeconds(Modules); - BinaryForPath.clear(); + ObjectPairForPathArch.clear(); ObjectFileForArch.clear(); } -static std::string getDarwinDWARFResourceForPath(const std::string &Path) { - StringRef Basename = sys::path::filename(Path); - const std::string &DSymDirectory = Path + ".dSYM"; - SmallString<16> ResourceName = StringRef(DSymDirectory); +// For Path="/path/to/foo" and Basename="foo" assume that debug info is in +// /path/to/foo.dSYM/Contents/Resources/DWARF/foo. +// For Path="/path/to/bar.dSYM" and Basename="foo" assume that debug info is in +// /path/to/bar.dSYM/Contents/Resources/DWARF/foo. +static +std::string getDarwinDWARFResourceForPath( + const std::string &Path, const std::string &Basename) { + SmallString<16> ResourceName = StringRef(Path); + if (sys::path::extension(Path) != ".dSYM") { + ResourceName += ".dSYM"; + } sys::path::append(ResourceName, "Contents", "Resources", "DWARF"); sys::path::append(ResourceName, Basename); return ResourceName.str(); @@ -265,9 +272,8 @@ static bool findDebugBinary(const std::string &OrigPath, return false; } -static bool getGNUDebuglinkContents(const Binary *Bin, std::string &DebugName, +static bool getGNUDebuglinkContents(const ObjectFile *Obj, std::string &DebugName, uint32_t &CRCHash) { - const ObjectFile *Obj = dyn_cast(Bin); if (!Obj) return false; for (const SectionRef &Section : Obj->sections()) { @@ -294,57 +300,91 @@ static bool getGNUDebuglinkContents(const Binary *Bin, std::string &DebugName, return false; } -LLVMSymbolizer::BinaryPair -LLVMSymbolizer::getOrCreateBinary(const std::string &Path) { - const auto &I = BinaryForPath.find(Path); - if (I != BinaryForPath.end()) - return I->second; - Binary *Bin = nullptr; - Binary *DbgBin = nullptr; - ErrorOr> BinaryOrErr = createBinary(Path); - if (!error(BinaryOrErr.getError())) { - OwningBinary &ParsedBinary = BinaryOrErr.get(); - // Check if it's a universal binary. - Bin = ParsedBinary.getBinary().get(); - addOwningBinary(std::move(ParsedBinary)); - if (Bin->isMachO() || Bin->isMachOUniversalBinary()) { - // On Darwin we may find DWARF in separate object file in - // resource directory. - const std::string &ResourcePath = - getDarwinDWARFResourceForPath(Path); - BinaryOrErr = createBinary(ResourcePath); - std::error_code EC = BinaryOrErr.getError(); - if (EC != errc::no_such_file_or_directory && !error(EC)) { - OwningBinary B = std::move(BinaryOrErr.get()); - DbgBin = B.getBinary().get(); +static +bool darwinDsymMatchesBinary(const MachOObjectFile *DbgObj, + const MachOObjectFile *Obj) { + ArrayRef dbg_uuid = DbgObj->getUuid(); + ArrayRef bin_uuid = Obj->getUuid(); + if (dbg_uuid.empty() || bin_uuid.empty()) + return false; + return !memcmp(dbg_uuid.data(), bin_uuid.data(), dbg_uuid.size()); +} + +ObjectFile *LLVMSymbolizer::lookUpDsymFile(const std::string &ExePath, + const MachOObjectFile *MachExeObj, const std::string &ArchName) { + // On Darwin we may find DWARF in separate object file in + // resource directory. + std::vector DsymPaths; + StringRef Filename = sys::path::filename(ExePath); + DsymPaths.push_back(getDarwinDWARFResourceForPath(ExePath, Filename)); + for (const auto &Path : Opts.DsymHints) { + DsymPaths.push_back(getDarwinDWARFResourceForPath(Path, Filename)); + } + for (const auto &path : DsymPaths) { + ErrorOr> BinaryOrErr = createBinary(path); + std::error_code EC = BinaryOrErr.getError(); + if (EC != errc::no_such_file_or_directory && !error(EC)) { + OwningBinary B = std::move(BinaryOrErr.get()); + ObjectFile *DbgObj = + getObjectFileFromBinary(B.getBinary().get(), ArchName); + const MachOObjectFile *MachDbgObj = + dyn_cast(DbgObj); + if (!MachDbgObj) continue; + if (darwinDsymMatchesBinary(MachDbgObj, MachExeObj)) { addOwningBinary(std::move(B)); + return DbgObj; } } + } + return nullptr; +} + +LLVMSymbolizer::ObjectPair +LLVMSymbolizer::getOrCreateObjects(const std::string &Path, + const std::string &ArchName) { + const auto &I = ObjectPairForPathArch.find(std::make_pair(Path, ArchName)); + if (I != ObjectPairForPathArch.end()) + return I->second; + ObjectFile *Obj = nullptr; + ObjectFile *DbgObj = nullptr; + ErrorOr> BinaryOrErr = createBinary(Path); + if (!error(BinaryOrErr.getError())) { + OwningBinary &B = BinaryOrErr.get(); + Obj = getObjectFileFromBinary(B.getBinary().get(), ArchName); + if (!Obj) { + ObjectPair Res = std::make_pair(nullptr, nullptr); + ObjectPairForPathArch[std::make_pair(Path, ArchName)] = Res; + return Res; + } + addOwningBinary(std::move(B)); + if (auto MachObj = dyn_cast(Obj)) + DbgObj = lookUpDsymFile(Path, MachObj, ArchName); // Try to locate the debug binary using .gnu_debuglink section. - if (!DbgBin) { + if (!DbgObj) { std::string DebuglinkName; uint32_t CRCHash; std::string DebugBinaryPath; - if (getGNUDebuglinkContents(Bin, DebuglinkName, CRCHash) && + if (getGNUDebuglinkContents(Obj, DebuglinkName, CRCHash) && findDebugBinary(Path, DebuglinkName, CRCHash, DebugBinaryPath)) { BinaryOrErr = createBinary(DebugBinaryPath); if (!error(BinaryOrErr.getError())) { OwningBinary B = std::move(BinaryOrErr.get()); - DbgBin = B.getBinary().get(); + DbgObj = getObjectFileFromBinary(B.getBinary().get(), ArchName); addOwningBinary(std::move(B)); } } } } - if (!DbgBin) - DbgBin = Bin; - BinaryPair Res = std::make_pair(Bin, DbgBin); - BinaryForPath[Path] = Res; + if (!DbgObj) + DbgObj = Obj; + ObjectPair Res = std::make_pair(Obj, DbgObj); + ObjectPairForPathArch[std::make_pair(Path, ArchName)] = Res; return Res; } ObjectFile * -LLVMSymbolizer::getObjectFileFromBinary(Binary *Bin, const std::string &ArchName) { +LLVMSymbolizer::getObjectFileFromBinary(Binary *Bin, + const std::string &ArchName) { if (!Bin) return nullptr; ObjectFile *Res = nullptr; @@ -382,18 +422,16 @@ LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) { ArchName = ArchStr; } } - BinaryPair Binaries = getOrCreateBinary(BinaryName); - ObjectFile *Obj = getObjectFileFromBinary(Binaries.first, ArchName); - ObjectFile *DbgObj = getObjectFileFromBinary(Binaries.second, ArchName); + ObjectPair Objects = getOrCreateObjects(BinaryName, ArchName); - if (!Obj) { + if (!Objects.first) { // Failed to find valid object file. Modules.insert(make_pair(ModuleName, (ModuleInfo *)nullptr)); return nullptr; } - DIContext *Context = DIContext::getDWARFContext(*DbgObj); + DIContext *Context = DIContext::getDWARFContext(*Objects.second); assert(Context); - ModuleInfo *Info = new ModuleInfo(Obj, Context); + ModuleInfo *Info = new ModuleInfo(Objects.first, Context); Modules.insert(make_pair(ModuleName, Info)); return Info; } diff --git a/tools/llvm-symbolizer/LLVMSymbolize.h b/tools/llvm-symbolizer/LLVMSymbolize.h index 17b78dc5204..52f1fc99171 100644 --- a/tools/llvm-symbolizer/LLVMSymbolize.h +++ b/tools/llvm-symbolizer/LLVMSymbolize.h @@ -39,13 +39,14 @@ public: bool PrintInlining : 1; bool Demangle : 1; std::string DefaultArch; + std::vector DsymHints; Options(bool UseSymbolTable = true, FunctionNameKind PrintFunctions = FunctionNameKind::LinkageName, bool PrintInlining = true, bool Demangle = true, std::string DefaultArch = "") - : UseSymbolTable(UseSymbolTable), PrintFunctions(PrintFunctions), - PrintInlining(PrintInlining), Demangle(Demangle), - DefaultArch(DefaultArch) {} + : UseSymbolTable(UseSymbolTable), + PrintFunctions(PrintFunctions), PrintInlining(PrintInlining), + Demangle(Demangle), DefaultArch(DefaultArch) {} }; LLVMSymbolizer(const Options &Opts = Options()) : Opts(Opts) {} @@ -62,11 +63,15 @@ public: void flush(); static std::string DemangleName(const std::string &Name); private: - typedef std::pair BinaryPair; + typedef std::pair ObjectPair; ModuleInfo *getOrCreateModuleInfo(const std::string &ModuleName); - /// \brief Returns pair of pointers to binary and debug binary. - BinaryPair getOrCreateBinary(const std::string &Path); + ObjectFile *lookUpDsymFile(const std::string &Path, const MachOObjectFile *ExeObj, + const std::string &ArchName); + + /// \brief Returns pair of pointers to object and debug object. + ObjectPair getOrCreateObjects(const std::string &Path, + const std::string &ArchName); /// \brief Returns a parsed object file for a given architecture in a /// universal binary (or the binary itself if it is an object file). ObjectFile *getObjectFileFromBinary(Binary *Bin, const std::string &ArchName); @@ -83,9 +88,10 @@ private: // Owns module info objects. std::map Modules; - std::map BinaryForPath; std::map, ObjectFile *> ObjectFileForArch; + std::map, ObjectPair> + ObjectPairForPathArch; Options Opts; static const char kBadString[]; diff --git a/tools/llvm-symbolizer/llvm-symbolizer.cpp b/tools/llvm-symbolizer/llvm-symbolizer.cpp index 29db172531b..d554022e450 100644 --- a/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -19,6 +19,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/FileSystem.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/PrettyStackTrace.h" #include "llvm/Support/Signals.h" @@ -61,6 +62,11 @@ ClBinaryName("obj", cl::init(""), cl::desc("Path to object file to be symbolized (if not provided, " "object file should be specified for each input line)")); +static cl::list +ClDsymHint("dsym-hint", cl::ZeroOrMore, + cl::desc("Path to .dSYM bundles to search for debug info for the " + "object files")); + static bool parseCommand(bool &IsData, std::string &ModuleName, uint64_t &ModuleOffset) { const char *kDataCmd = "DATA "; @@ -119,6 +125,14 @@ int main(int argc, char **argv) { cl::ParseCommandLineOptions(argc, argv, "llvm-symbolizer\n"); LLVMSymbolizer::Options Opts(ClUseSymbolTable, ClPrintFunctions, ClPrintInlining, ClDemangle, ClDefaultArch); + for (const auto &hint : ClDsymHint) { + if (sys::path::extension(hint) == ".dSYM") { + Opts.DsymHints.push_back(hint); + } else { + errs() << "Warning: invalid dSYM hint: \"" << hint << + "\" (must have the '.dSYM' extension).\n"; + } + } LLVMSymbolizer Symbolizer(Opts); bool IsData = false;