[llvm-symbolizer] Introduce the -dsym-hint option.
authorAlexander Potapenko <glider@google.com>
Fri, 17 Oct 2014 00:50:19 +0000 (00:50 +0000)
committerAlexander Potapenko <glider@google.com>
Fri, 17 Oct 2014 00:50:19 +0000 (00:50 +0000)
llvm-symbolizer will consult one of the .dSYM paths passed via -dsym-hint
if it fails to find the .dSYM bundle at the default location.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@220004 91177308-0d34-0410-b5e6-96231b3b80d8

12 files changed:
docs/CommandGuide/llvm-symbolizer.rst
test/tools/llvm-symbolizer/Inputs/dsym-test-exe [new file with mode: 0755]
test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Info.plist [new file with mode: 0644]
test/tools/llvm-symbolizer/Inputs/dsym-test-exe-differentname.dSYM/Contents/Resources/DWARF/dsym-test-exe-second [new file with mode: 0644]
test/tools/llvm-symbolizer/Inputs/dsym-test-exe-second [new file with mode: 0755]
test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Info.plist [new file with mode: 0644]
test/tools/llvm-symbolizer/Inputs/dsym-test-exe.dSYM/Contents/Resources/DWARF/dsym-test-exe [new file with mode: 0644]
test/tools/llvm-symbolizer/Inputs/dsym-test.c [new file with mode: 0644]
test/tools/llvm-symbolizer/dsym.test [new file with mode: 0644]
tools/llvm-symbolizer/LLVMSymbolize.cpp
tools/llvm-symbolizer/LLVMSymbolize.h
tools/llvm-symbolizer/llvm-symbolizer.cpp

index ce2d9c00435302a70afa30cd23d24a1de9c96fae..96720e633f2fafb9c53a03e1c5d983c4d40b9d25 100644 (file)
@@ -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.
 
  input (see example above). If architecture is not specified in either way,
  address will not be symbolized. Defaults to empty string.
 
+.. option:: -dsym-hint=<path/to/file.dSYM>
+
+ (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
 -----------
 
 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 (executable)
index 0000000..ba3154c
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 (file)
index 0000000..4e84ad0
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+       <dict>
+               <key>CFBundleDevelopmentRegion</key>
+               <string>English</string>
+               <key>CFBundleIdentifier</key>
+               <string>com.apple.xcode.dsym.dsym-test-exe-differentname</string>
+               <key>CFBundleInfoDictionaryVersion</key>
+               <string>6.0</string>
+               <key>CFBundlePackageType</key>
+               <string>dSYM</string>
+               <key>CFBundleSignature</key>
+               <string>????</string>
+               <key>CFBundleShortVersionString</key>
+               <string>1.0</string>
+               <key>CFBundleVersion</key>
+               <string>1</string>
+       </dict>
+</plist>
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 (file)
index 0000000..c30dba3
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 (executable)
index 0000000..ba3154c
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 (file)
index 0000000..35b1c11
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+       <dict>
+               <key>CFBundleDevelopmentRegion</key>
+               <string>English</string>
+               <key>CFBundleIdentifier</key>
+               <string>com.apple.xcode.dsym.dsym-test-exe</string>
+               <key>CFBundleInfoDictionaryVersion</key>
+               <string>6.0</string>
+               <key>CFBundlePackageType</key>
+               <string>dSYM</string>
+               <key>CFBundleSignature</key>
+               <string>????</string>
+               <key>CFBundleShortVersionString</key>
+               <string>1.0</string>
+               <key>CFBundleVersion</key>
+               <string>1</string>
+       </dict>
+</plist>
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 (file)
index 0000000..c30dba3
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 (file)
index 0000000..84d5ad9
--- /dev/null
@@ -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 (file)
index 0000000..326602d
--- /dev/null
@@ -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 f1dccfdc40a4495308aaa5974cde19a1acc215a0..31bbedf8f032b58e1d4ef34a8a99739b975c6e2e 100644 (file)
@@ -207,14 +207,21 @@ std::string LLVMSymbolizer::symbolizeData(const std::string &ModuleName,
 
 void LLVMSymbolizer::flush() {
   DeleteContainerSeconds(Modules);
 
 void LLVMSymbolizer::flush() {
   DeleteContainerSeconds(Modules);
-  BinaryForPath.clear();
+  ObjectPairForPathArch.clear();
   ObjectFileForArch.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();
   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;
 }
 
   return false;
 }
 
-static bool getGNUDebuglinkContents(const Binary *Bin, std::string &DebugName,
+static bool getGNUDebuglinkContents(const ObjectFile *Obj, std::string &DebugName,
                                     uint32_t &CRCHash) {
                                     uint32_t &CRCHash) {
-  const ObjectFile *Obj = dyn_cast<ObjectFile>(Bin);
   if (!Obj)
     return false;
   for (const SectionRef &Section : Obj->sections()) {
   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;
 }
 
   return false;
 }
 
-LLVMSymbolizer::BinaryPair
-LLVMSymbolizer::getOrCreateBinary(const std::string &Path) {
-  const auto &I = BinaryForPath.find(Path);
-  if (I != BinaryForPath.end())
+static
+bool darwinDsymMatchesBinary(const MachOObjectFile *DbgObj,
+                             const MachOObjectFile *Obj) {
+  ArrayRef<uint8_t> dbg_uuid = DbgObj->getUuid();
+  ArrayRef<uint8_t> 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<std::string> 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<OwningBinary<Binary>> BinaryOrErr = createBinary(path);
+    std::error_code EC = BinaryOrErr.getError();
+    if (EC != errc::no_such_file_or_directory && !error(EC)) {
+      OwningBinary<Binary> B = std::move(BinaryOrErr.get());
+      ObjectFile *DbgObj =
+          getObjectFileFromBinary(B.getBinary().get(), ArchName);
+      const MachOObjectFile *MachDbgObj =
+          dyn_cast<const MachOObjectFile>(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;
     return I->second;
-  Binary *Bin = nullptr;
-  Binary *DbgBin = nullptr;
+  ObjectFile *Obj = nullptr;
+  ObjectFile *DbgObj = nullptr;
   ErrorOr<OwningBinary<Binary>> BinaryOrErr = createBinary(Path);
   if (!error(BinaryOrErr.getError())) {
   ErrorOr<OwningBinary<Binary>> BinaryOrErr = createBinary(Path);
   if (!error(BinaryOrErr.getError())) {
-    OwningBinary<Binary> &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<Binary> B = std::move(BinaryOrErr.get());
-        DbgBin = B.getBinary().get();
-        addOwningBinary(std::move(B));
-      }
+    OwningBinary<Binary> &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<const MachOObjectFile>(Obj))
+      DbgObj = lookUpDsymFile(Path, MachObj, ArchName);
     // Try to locate the debug binary using .gnu_debuglink section.
     // Try to locate the debug binary using .gnu_debuglink section.
-    if (!DbgBin) {
+    if (!DbgObj) {
       std::string DebuglinkName;
       uint32_t CRCHash;
       std::string DebugBinaryPath;
       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<Binary> B = std::move(BinaryOrErr.get());
           findDebugBinary(Path, DebuglinkName, CRCHash, DebugBinaryPath)) {
         BinaryOrErr = createBinary(DebugBinaryPath);
         if (!error(BinaryOrErr.getError())) {
           OwningBinary<Binary> B = std::move(BinaryOrErr.get());
-          DbgBin = B.getBinary().get();
+          DbgObj = getObjectFileFromBinary(B.getBinary().get(), ArchName);
           addOwningBinary(std::move(B));
         }
       }
     }
   }
           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 *
   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 (!Bin)
     return nullptr;
   ObjectFile *Res = nullptr;
@@ -382,18 +422,16 @@ LLVMSymbolizer::getOrCreateModuleInfo(const std::string &ModuleName) {
       ArchName = ArchStr;
     }
   }
       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;
   }
     // 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);
   assert(Context);
-  ModuleInfo *Info = new ModuleInfo(Obj, Context);
+  ModuleInfo *Info = new ModuleInfo(Objects.first, Context);
   Modules.insert(make_pair(ModuleName, Info));
   return Info;
 }
   Modules.insert(make_pair(ModuleName, Info));
   return Info;
 }
index 17b78dc52046d143bda7028b9e6b129401bd342a..52f1fc99171569e81689c1225d2f370cea19d618 100644 (file)
@@ -39,13 +39,14 @@ public:
     bool PrintInlining : 1;
     bool Demangle : 1;
     std::string DefaultArch;
     bool PrintInlining : 1;
     bool Demangle : 1;
     std::string DefaultArch;
+    std::vector<std::string> DsymHints;
     Options(bool UseSymbolTable = true,
             FunctionNameKind PrintFunctions = FunctionNameKind::LinkageName,
             bool PrintInlining = true, bool Demangle = true,
             std::string DefaultArch = "")
     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) {}
   };
 
   LLVMSymbolizer(const Options &Opts = Options()) : Opts(Opts) {}
@@ -62,11 +63,15 @@ public:
   void flush();
   static std::string DemangleName(const std::string &Name);
 private:
   void flush();
   static std::string DemangleName(const std::string &Name);
 private:
-  typedef std::pair<Binary*, Binary*> BinaryPair;
+  typedef std::pair<ObjectFile*, ObjectFile*> ObjectPair;
 
   ModuleInfo *getOrCreateModuleInfo(const std::string &ModuleName);
 
   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);
   /// \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<std::string, ModuleInfo *> Modules;
 
   // Owns module info objects.
   std::map<std::string, ModuleInfo *> Modules;
-  std::map<std::string, BinaryPair> BinaryForPath;
   std::map<std::pair<MachOUniversalBinary *, std::string>, ObjectFile *>
       ObjectFileForArch;
   std::map<std::pair<MachOUniversalBinary *, std::string>, ObjectFile *>
       ObjectFileForArch;
+  std::map<std::pair<std::string, std::string>, ObjectPair>
+      ObjectPairForPathArch;
 
   Options Opts;
   static const char kBadString[];
 
   Options Opts;
   static const char kBadString[];
index 29db172531bab9fb58e3c3297bd2917fcb5e7ce4..d554022e450c746b8a6b778dae4906d5c2599a74 100644 (file)
@@ -19,6 +19,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Debug.h"
 #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"
 #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)"));
 
              cl::desc("Path to object file to be symbolized (if not provided, "
                       "object file should be specified for each input line)"));
 
+static cl::list<std::string>
+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 ";
 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);
   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;
   LLVMSymbolizer Symbolizer(Opts);
 
   bool IsData = false;