Index: docs/CommandGuide/llvm-symbolizer.rst =================================================================== --- docs/CommandGuide/llvm-symbolizer.rst +++ docs/CommandGuide/llvm-symbolizer.rst @@ -92,6 +92,13 @@ 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 ----------- Index: test/tools/llvm-symbolizer/Inputs/dsym-test.c =================================================================== --- test/tools/llvm-symbolizer/Inputs/dsym-test.c +++ test/tools/llvm-symbolizer/Inputs/dsym-test.c @@ -0,0 +1,3 @@ +int main() { + return 0; +} Index: test/tools/llvm-symbolizer/dsym.test =================================================================== --- test/tools/llvm-symbolizer/dsym.test +++ 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 Index: tools/llvm-symbolizer/LLVMSymbolize.h =================================================================== --- tools/llvm-symbolizer/LLVMSymbolize.h +++ tools/llvm-symbolizer/LLVMSymbolize.h @@ -39,13 +39,14 @@ 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,17 @@ 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); + + void populateDsymPaths(const std::string &ExePath, + std::vector &OutPaths); + /// \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); @@ -82,13 +89,11 @@ } // Owns module info objects. - typedef std::map ModuleMapTy; - ModuleMapTy Modules; - typedef std::map BinaryMapTy; - BinaryMapTy BinaryForPath; - typedef std::map, ObjectFile *> - ObjectFileForArchMapTy; - ObjectFileForArchMapTy ObjectFileForArch; + std::map Modules; + std::map, ObjectFile *> + ObjectFileForArch; + std::map, ObjectPair> + ObjectPairForPathArch; Options Opts; static const char kBadString[]; @@ -122,9 +127,7 @@ return s1.Addr < s2.Addr; } }; - typedef std::map SymbolMapTy; - SymbolMapTy Functions; - SymbolMapTy Objects; + std::map Functions, Objects; }; } // namespace symbolize Index: tools/llvm-symbolizer/LLVMSymbolize.cpp =================================================================== --- tools/llvm-symbolizer/LLVMSymbolize.cpp +++ tools/llvm-symbolizer/LLVMSymbolize.cpp @@ -85,27 +85,29 @@ SymbolName = SymbolName.drop_front(); // FIXME: If a function has alias, there are two entries in symbol table // with same address size. Make sure we choose the correct one. - SymbolMapTy &M = SymbolType == SymbolRef::ST_Function ? Functions : Objects; + auto &SymbolMap = + SymbolType == SymbolRef::ST_Function ? Functions : Objects; SymbolDesc SD = { SymbolAddress, SymbolSize }; - M.insert(std::make_pair(SD, SymbolName)); + SymbolMap.insert(std::make_pair(SD, SymbolName)); } bool ModuleInfo::getNameFromSymbolTable(SymbolRef::Type Type, uint64_t Address, std::string &Name, uint64_t &Addr, uint64_t &Size) const { - const SymbolMapTy &M = Type == SymbolRef::ST_Function ? Functions : Objects; - if (M.empty()) + const auto &SymbolMap = Type == SymbolRef::ST_Function ? Functions : Objects; + if (SymbolMap.empty()) return false; SymbolDesc SD = { Address, Address }; - SymbolMapTy::const_iterator it = M.upper_bound(SD); - if (it == M.begin()) + auto symbol_iterator = SymbolMap.upper_bound(SD); + if (symbol_iterator == SymbolMap.begin()) return false; - --it; - if (it->first.Size != 0 && it->first.Addr + it->first.Size <= Address) + --symbol_iterator; + if (symbol_iterator->first.Size != 0 && + symbol_iterator->first.Addr + symbol_iterator->first.Size <= Address) return false; - Name = it->second.str(); - Addr = it->first.Addr; - Size = it->first.Size; + Name = symbol_iterator->second.str(); + Addr = symbol_iterator->first.Addr; + Size = symbol_iterator->first.Size; return true; } @@ -206,14 +208,21 @@ 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(); @@ -264,9 +273,8 @@ 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()) { @@ -293,63 +301,123 @@ return false; } -LLVMSymbolizer::BinaryPair -LLVMSymbolizer::getOrCreateBinary(const std::string &Path) { - BinaryMapTy::iterator I = BinaryForPath.find(Path); - if (I != BinaryForPath.end()) +#if 0 +ErrorOr getUuidFromObjectFile(const ObjectFile *Obj) { + const MachOObjectFile *file = dyn_cast(Obj); + MachOObjectFile::LoadCommandInfo Command = file->getFirstLoadCommandInfo(); + MachO::mach_header Header = file->getHeader(); + unsigned ncmds = Header.ncmds; + for (unsigned i = 0;; ++i) { + if (Command.C.cmd == MachO::LC_UUID) + return file->getUuidCommand(Command); + if (i == ncmds - 1) + break; + else + Command = file->getNextLoadCommandInfo(Command); + } + return object_error::parse_failed; +} +#endif + +static +bool darwinDsymMatchesBinary(const MachOObjectFile *DbgObj, + const MachOObjectFile *Obj) { + ErrorOr dbg_uuid_or_error = DbgObj->findAndGetUuidCommand(); + ErrorOr bin_uuid_or_error = Obj->findAndGetUuidCommand(); + if (error(dbg_uuid_or_error.getError()) || + error(bin_uuid_or_error.getError())) + return false; + return !memcmp(&(dbg_uuid_or_error.get()), &(bin_uuid_or_error.get()), + sizeof(MachO::uuid_command)); +} + +void LLVMSymbolizer::populateDsymPaths(const std::string &ExePath, + std::vector &OutPaths) { + StringRef Filename = sys::path::filename(ExePath); + const std::string &resource_path = + getDarwinDWARFResourceForPath(ExePath, Filename); + OutPaths.push_back(resource_path); + for (const auto &path : Opts.DsymHints) { + OutPaths.push_back(getDarwinDWARFResourceForPath(path, Filename)); + } +} + +ObjectFile *LLVMSymbolizer::lookUpDsymFile(const std::string &ExePath, + const MachOObjectFile *MachExeObj, const std::string &ArchName) { + ObjectFile *result = nullptr; + // On Darwin we may find DWARF in separate object file in + // resource directory. + std::vector DsymPaths; + populateDsymPaths(ExePath, DsymPaths); + 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 result; +} + +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; - Binary *Bin = nullptr; - Binary *DbgBin = nullptr; + ObjectFile *Obj = nullptr; + ObjectFile *DbgObj = 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)); + OwningBinary &B = BinaryOrErr.get(); + Binary *Bin = B.getBinary().get(); + Obj = getObjectFileFromBinary(Bin, ArchName); + addOwningBinary(std::move(B)); 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(); - addOwningBinary(std::move(B)); - } + const MachOObjectFile *MachObj = dyn_cast(Obj); + if (MachObj) + 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; if (MachOUniversalBinary *UB = dyn_cast(Bin)) { - ObjectFileForArchMapTy::iterator I = ObjectFileForArch.find( - std::make_pair(UB, ArchName)); + const auto &I = ObjectFileForArch.find(std::make_pair(UB, ArchName)); if (I != ObjectFileForArch.end()) return I->second; ErrorOr> ParsedObj = @@ -367,7 +435,7 @@ ModuleInfo * LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) { - ModuleMapTy::iterator I = Modules.find(ModuleName); + const auto &I = Modules.find(ModuleName); if (I != Modules.end()) return I->second; std::string BinaryName = ModuleName; @@ -381,18 +449,16 @@ 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; } Index: tools/llvm-symbolizer/llvm-symbolizer.cpp =================================================================== --- tools/llvm-symbolizer/llvm-symbolizer.cpp +++ 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 @@ 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,13 @@ 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: dSYM hint must have the '.dSYM' extension.\n"; + } + } LLVMSymbolizer Symbolizer(Opts); bool IsData = false;