1 //===-- LocateSymbolFileMacOSX.cpp ----------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "lldb/Symbol/LocateSymbolFile.h"
10 
11 #include <dirent.h>
12 #include <dlfcn.h>
13 #include <pwd.h>
14 
15 #include <CoreFoundation/CoreFoundation.h>
16 
17 #include "Host/macosx/cfcpp/CFCBundle.h"
18 #include "Host/macosx/cfcpp/CFCData.h"
19 #include "Host/macosx/cfcpp/CFCReleaser.h"
20 #include "Host/macosx/cfcpp/CFCString.h"
21 #include "lldb/Core/ModuleList.h"
22 #include "lldb/Core/ModuleSpec.h"
23 #include "lldb/Host/Host.h"
24 #include "lldb/Symbol/ObjectFile.h"
25 #include "lldb/Utility/ArchSpec.h"
26 #include "lldb/Utility/DataBuffer.h"
27 #include "lldb/Utility/DataExtractor.h"
28 #include "lldb/Utility/Endian.h"
29 #include "lldb/Utility/LLDBLog.h"
30 #include "lldb/Utility/Log.h"
31 #include "lldb/Utility/ReproducerProvider.h"
32 #include "lldb/Utility/StreamString.h"
33 #include "lldb/Utility/Timer.h"
34 #include "lldb/Utility/UUID.h"
35 #include "mach/machine.h"
36 
37 #include "llvm/ADT/ScopeExit.h"
38 #include "llvm/Support/FileSystem.h"
39 
40 using namespace lldb;
41 using namespace lldb_private;
42 
43 static CFURLRef (*g_dlsym_DBGCopyFullDSYMURLForUUID)(
44     CFUUIDRef uuid, CFURLRef exec_url) = nullptr;
45 static CFDictionaryRef (*g_dlsym_DBGCopyDSYMPropertyLists)(CFURLRef dsym_url) =
46     nullptr;
47 
48 int LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec &module_spec,
49                                        ModuleSpec &return_module_spec) {
50   Log *log = GetLog(LLDBLog::Host);
51   if (!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) {
52     LLDB_LOGF(log, "Spotlight lookup for .dSYM bundles is disabled.");
53     return 0;
54   }
55 
56   return_module_spec = module_spec;
57   return_module_spec.GetFileSpec().Clear();
58   return_module_spec.GetSymbolFileSpec().Clear();
59 
60   const UUID *uuid = module_spec.GetUUIDPtr();
61   const ArchSpec *arch = module_spec.GetArchitecturePtr();
62 
63   if (repro::Loader *l = repro::Reproducer::Instance().GetLoader()) {
64     static repro::SymbolFileLoader symbol_file_loader(l);
65     std::pair<FileSpec, FileSpec> paths = symbol_file_loader.GetPaths(uuid);
66     return_module_spec.GetFileSpec() = paths.first;
67     return_module_spec.GetSymbolFileSpec() = paths.second;
68     return 1;
69   }
70 
71   int items_found = 0;
72 
73   if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
74       g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
75     void *handle = dlopen(
76         "/System/Library/PrivateFrameworks/DebugSymbols.framework/DebugSymbols",
77         RTLD_LAZY | RTLD_LOCAL);
78     if (handle) {
79       g_dlsym_DBGCopyFullDSYMURLForUUID =
80           (CFURLRef(*)(CFUUIDRef, CFURLRef))dlsym(handle,
81                                                   "DBGCopyFullDSYMURLForUUID");
82       g_dlsym_DBGCopyDSYMPropertyLists = (CFDictionaryRef(*)(CFURLRef))dlsym(
83           handle, "DBGCopyDSYMPropertyLists");
84     }
85   }
86 
87   if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
88       g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
89     return items_found;
90   }
91 
92   if (uuid && uuid->IsValid()) {
93     // Try and locate the dSYM file using DebugSymbols first
94     llvm::ArrayRef<uint8_t> module_uuid = uuid->GetBytes();
95     if (module_uuid.size() == 16) {
96       CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes(
97           NULL, module_uuid[0], module_uuid[1], module_uuid[2], module_uuid[3],
98           module_uuid[4], module_uuid[5], module_uuid[6], module_uuid[7],
99           module_uuid[8], module_uuid[9], module_uuid[10], module_uuid[11],
100           module_uuid[12], module_uuid[13], module_uuid[14], module_uuid[15]));
101 
102       if (module_uuid_ref.get()) {
103         CFCReleaser<CFURLRef> exec_url;
104         const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
105         if (exec_fspec) {
106           char exec_cf_path[PATH_MAX];
107           if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path)))
108             exec_url.reset(::CFURLCreateFromFileSystemRepresentation(
109                 NULL, (const UInt8 *)exec_cf_path, strlen(exec_cf_path),
110                 FALSE));
111         }
112 
113         CFCReleaser<CFURLRef> dsym_url(g_dlsym_DBGCopyFullDSYMURLForUUID(
114             module_uuid_ref.get(), exec_url.get()));
115         char path[PATH_MAX];
116 
117         if (dsym_url.get()) {
118           if (::CFURLGetFileSystemRepresentation(
119                   dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
120             if (log) {
121               LLDB_LOGF(log,
122                         "DebugSymbols framework returned dSYM path of %s for "
123                         "UUID %s -- looking for the dSYM",
124                         path, uuid->GetAsString().c_str());
125             }
126             FileSpec dsym_filespec(path);
127             if (path[0] == '~')
128               FileSystem::Instance().Resolve(dsym_filespec);
129 
130             if (FileSystem::Instance().IsDirectory(dsym_filespec)) {
131               dsym_filespec =
132                   Symbols::FindSymbolFileInBundle(dsym_filespec, uuid, arch);
133               ++items_found;
134             } else {
135               ++items_found;
136             }
137             return_module_spec.GetSymbolFileSpec() = dsym_filespec;
138           }
139 
140           bool success = false;
141           if (log) {
142             if (::CFURLGetFileSystemRepresentation(
143                     dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
144               LLDB_LOGF(log,
145                         "DebugSymbols framework returned dSYM path of %s for "
146                         "UUID %s -- looking for an exec file",
147                         path, uuid->GetAsString().c_str());
148             }
149           }
150 
151           CFCReleaser<CFDictionaryRef> dict(
152               g_dlsym_DBGCopyDSYMPropertyLists(dsym_url.get()));
153           CFDictionaryRef uuid_dict = NULL;
154           if (dict.get()) {
155             CFCString uuid_cfstr(uuid->GetAsString().c_str());
156             uuid_dict = static_cast<CFDictionaryRef>(
157                 ::CFDictionaryGetValue(dict.get(), uuid_cfstr.get()));
158           }
159           if (uuid_dict) {
160             CFStringRef exec_cf_path =
161                 static_cast<CFStringRef>(::CFDictionaryGetValue(
162                     uuid_dict, CFSTR("DBGSymbolRichExecutable")));
163             if (exec_cf_path && ::CFStringGetFileSystemRepresentation(
164                                     exec_cf_path, path, sizeof(path))) {
165               if (log) {
166                 LLDB_LOGF(log, "plist bundle has exec path of %s for UUID %s",
167                           path, uuid->GetAsString().c_str());
168               }
169               ++items_found;
170               FileSpec exec_filespec(path);
171               if (path[0] == '~')
172                 FileSystem::Instance().Resolve(exec_filespec);
173               if (FileSystem::Instance().Exists(exec_filespec)) {
174                 success = true;
175                 return_module_spec.GetFileSpec() = exec_filespec;
176               }
177             }
178           }
179 
180           if (!success) {
181             // No dictionary, check near the dSYM bundle for an executable that
182             // matches...
183             if (::CFURLGetFileSystemRepresentation(
184                     dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
185               char *dsym_extension_pos = ::strstr(path, ".dSYM");
186               if (dsym_extension_pos) {
187                 *dsym_extension_pos = '\0';
188                 if (log) {
189                   LLDB_LOGF(log,
190                             "Looking for executable binary next to dSYM "
191                             "bundle with name with name %s",
192                             path);
193                 }
194                 FileSpec file_spec(path);
195                 FileSystem::Instance().Resolve(file_spec);
196                 ModuleSpecList module_specs;
197                 ModuleSpec matched_module_spec;
198                 using namespace llvm::sys::fs;
199                 switch (get_file_type(file_spec.GetPath())) {
200 
201                 case file_type::directory_file: // Bundle directory?
202                 {
203                   CFCBundle bundle(path);
204                   CFCReleaser<CFURLRef> bundle_exe_url(
205                       bundle.CopyExecutableURL());
206                   if (bundle_exe_url.get()) {
207                     if (::CFURLGetFileSystemRepresentation(bundle_exe_url.get(),
208                                                            true, (UInt8 *)path,
209                                                            sizeof(path) - 1)) {
210                       FileSpec bundle_exe_file_spec(path);
211                       FileSystem::Instance().Resolve(bundle_exe_file_spec);
212                       if (ObjectFile::GetModuleSpecifications(
213                               bundle_exe_file_spec, 0, 0, module_specs) &&
214                           module_specs.FindMatchingModuleSpec(
215                               module_spec, matched_module_spec))
216 
217                       {
218                         ++items_found;
219                         return_module_spec.GetFileSpec() = bundle_exe_file_spec;
220                         if (log) {
221                           LLDB_LOGF(log,
222                                     "Executable binary %s next to dSYM is "
223                                     "compatible; using",
224                                     path);
225                         }
226                       }
227                     }
228                   }
229                 } break;
230 
231                 case file_type::fifo_file:      // Forget pipes
232                 case file_type::socket_file:    // We can't process socket files
233                 case file_type::file_not_found: // File doesn't exist...
234                 case file_type::status_error:
235                   break;
236 
237                 case file_type::type_unknown:
238                 case file_type::regular_file:
239                 case file_type::symlink_file:
240                 case file_type::block_file:
241                 case file_type::character_file:
242                   if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0,
243                                                           module_specs) &&
244                       module_specs.FindMatchingModuleSpec(module_spec,
245                                                           matched_module_spec))
246 
247                   {
248                     ++items_found;
249                     return_module_spec.GetFileSpec() = file_spec;
250                     if (log) {
251                       LLDB_LOGF(log,
252                                 "Executable binary %s next to dSYM is "
253                                 "compatible; using",
254                                 path);
255                     }
256                   }
257                   break;
258                 }
259               }
260             }
261           }
262         }
263       }
264     }
265   }
266 
267   if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) {
268     g->GetOrCreate<repro::SymbolFileProvider>().AddSymbolFile(
269         uuid, return_module_spec.GetFileSpec(),
270         return_module_spec.GetSymbolFileSpec());
271   }
272 
273   return items_found;
274 }
275 
276 FileSpec Symbols::FindSymbolFileInBundle(const FileSpec &dsym_bundle_fspec,
277                                          const lldb_private::UUID *uuid,
278                                          const ArchSpec *arch) {
279   std::string dsym_bundle_path = dsym_bundle_fspec.GetPath();
280   llvm::SmallString<128> buffer(dsym_bundle_path);
281   llvm::sys::path::append(buffer, "Contents", "Resources", "DWARF");
282 
283   std::error_code EC;
284   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs =
285       FileSystem::Instance().GetVirtualFileSystem();
286   llvm::vfs::recursive_directory_iterator Iter(*vfs, buffer.str(), EC);
287   llvm::vfs::recursive_directory_iterator End;
288   for (; Iter != End && !EC; Iter.increment(EC)) {
289     llvm::ErrorOr<llvm::vfs::Status> Status = vfs->status(Iter->path());
290     if (Status->isDirectory())
291       continue;
292 
293     FileSpec dsym_fspec(Iter->path());
294     ModuleSpecList module_specs;
295     if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs)) {
296       ModuleSpec spec;
297       for (size_t i = 0; i < module_specs.GetSize(); ++i) {
298         bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
299         assert(got_spec); // The call has side-effects so can't be inlined.
300         UNUSED_IF_ASSERT_DISABLED(got_spec);
301         if ((uuid == nullptr ||
302              (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
303             (arch == nullptr ||
304              (spec.GetArchitecturePtr() &&
305               spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
306           return dsym_fspec;
307         }
308       }
309     }
310   }
311 
312   return {};
313 }
314 
315 static bool GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict,
316                                                 ModuleSpec &module_spec) {
317   Log *log = GetLog(LLDBLog::Host);
318   bool success = false;
319   if (uuid_dict != NULL && CFGetTypeID(uuid_dict) == CFDictionaryGetTypeID()) {
320     std::string str;
321     CFStringRef cf_str;
322     CFDictionaryRef cf_dict;
323 
324     cf_str = (CFStringRef)CFDictionaryGetValue(
325         (CFDictionaryRef)uuid_dict, CFSTR("DBGSymbolRichExecutable"));
326     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
327       if (CFCString::FileSystemRepresentation(cf_str, str)) {
328         module_spec.GetFileSpec().SetFile(str.c_str(), FileSpec::Style::native);
329         FileSystem::Instance().Resolve(module_spec.GetFileSpec());
330         if (log) {
331           LLDB_LOGF(log,
332                     "From dsymForUUID plist: Symbol rich executable is at '%s'",
333                     str.c_str());
334         }
335       }
336     }
337 
338     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
339                                                CFSTR("DBGDSYMPath"));
340     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
341       if (CFCString::FileSystemRepresentation(cf_str, str)) {
342         module_spec.GetSymbolFileSpec().SetFile(str.c_str(),
343                                                 FileSpec::Style::native);
344         FileSystem::Instance().Resolve(module_spec.GetFileSpec());
345         success = true;
346         if (log) {
347           LLDB_LOGF(log, "From dsymForUUID plist: dSYM is at '%s'",
348                     str.c_str());
349         }
350       }
351     }
352 
353     std::string DBGBuildSourcePath;
354     std::string DBGSourcePath;
355 
356     // If DBGVersion 1 or DBGVersion missing, ignore DBGSourcePathRemapping.
357     // If DBGVersion 2, strip last two components of path remappings from
358     //                  entries to fix an issue with a specific set of
359     //                  DBGSourcePathRemapping entries that lldb worked
360     //                  with.
361     // If DBGVersion 3, trust & use the source path remappings as-is.
362     //
363     cf_dict = (CFDictionaryRef)CFDictionaryGetValue(
364         (CFDictionaryRef)uuid_dict, CFSTR("DBGSourcePathRemapping"));
365     if (cf_dict && CFGetTypeID(cf_dict) == CFDictionaryGetTypeID()) {
366       // If we see DBGVersion with a value of 2 or higher, this is a new style
367       // DBGSourcePathRemapping dictionary
368       bool new_style_source_remapping_dictionary = false;
369       bool do_truncate_remapping_names = false;
370       std::string original_DBGSourcePath_value = DBGSourcePath;
371       cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
372                                                  CFSTR("DBGVersion"));
373       if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
374         std::string version;
375         CFCString::FileSystemRepresentation(cf_str, version);
376         if (!version.empty() && isdigit(version[0])) {
377           int version_number = atoi(version.c_str());
378           if (version_number > 1) {
379             new_style_source_remapping_dictionary = true;
380           }
381           if (version_number == 2) {
382             do_truncate_remapping_names = true;
383           }
384         }
385       }
386 
387       CFIndex kv_pair_count = CFDictionaryGetCount((CFDictionaryRef)uuid_dict);
388       if (kv_pair_count > 0) {
389         CFStringRef *keys =
390             (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
391         CFStringRef *values =
392             (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
393         if (keys != nullptr && values != nullptr) {
394           CFDictionaryGetKeysAndValues((CFDictionaryRef)uuid_dict,
395                                        (const void **)keys,
396                                        (const void **)values);
397         }
398         for (CFIndex i = 0; i < kv_pair_count; i++) {
399           DBGBuildSourcePath.clear();
400           DBGSourcePath.clear();
401           if (keys[i] && CFGetTypeID(keys[i]) == CFStringGetTypeID()) {
402             CFCString::FileSystemRepresentation(keys[i], DBGBuildSourcePath);
403           }
404           if (values[i] && CFGetTypeID(values[i]) == CFStringGetTypeID()) {
405             CFCString::FileSystemRepresentation(values[i], DBGSourcePath);
406           }
407           if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
408             // In the "old style" DBGSourcePathRemapping dictionary, the
409             // DBGSourcePath values (the "values" half of key-value path pairs)
410             // were wrong.  Ignore them and use the universal DBGSourcePath
411             // string from earlier.
412             if (new_style_source_remapping_dictionary &&
413                 !original_DBGSourcePath_value.empty()) {
414               DBGSourcePath = original_DBGSourcePath_value;
415             }
416             if (DBGSourcePath[0] == '~') {
417               FileSpec resolved_source_path(DBGSourcePath.c_str());
418               FileSystem::Instance().Resolve(resolved_source_path);
419               DBGSourcePath = resolved_source_path.GetPath();
420             }
421             // With version 2 of DBGSourcePathRemapping, we can chop off the
422             // last two filename parts from the source remapping and get a more
423             // general source remapping that still works. Add this as another
424             // option in addition to the full source path remap.
425             module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
426                                                       DBGSourcePath, true);
427             if (do_truncate_remapping_names) {
428               FileSpec build_path(DBGBuildSourcePath.c_str());
429               FileSpec source_path(DBGSourcePath.c_str());
430               build_path.RemoveLastPathComponent();
431               build_path.RemoveLastPathComponent();
432               source_path.RemoveLastPathComponent();
433               source_path.RemoveLastPathComponent();
434               module_spec.GetSourceMappingList().Append(
435                   build_path.GetPath(), source_path.GetPath(), true);
436             }
437           }
438         }
439         if (keys)
440           free(keys);
441         if (values)
442           free(values);
443       }
444     }
445 
446     // If we have a DBGBuildSourcePath + DBGSourcePath pair, append them to the
447     // source remappings list.
448 
449     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
450                                                CFSTR("DBGBuildSourcePath"));
451     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
452       CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath);
453     }
454 
455     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
456                                                CFSTR("DBGSourcePath"));
457     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
458       CFCString::FileSystemRepresentation(cf_str, DBGSourcePath);
459     }
460 
461     if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
462       if (DBGSourcePath[0] == '~') {
463         FileSpec resolved_source_path(DBGSourcePath.c_str());
464         FileSystem::Instance().Resolve(resolved_source_path);
465         DBGSourcePath = resolved_source_path.GetPath();
466       }
467       module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
468                                                 DBGSourcePath, true);
469     }
470   }
471   return success;
472 }
473 
474 bool Symbols::DownloadObjectAndSymbolFile(ModuleSpec &module_spec,
475                                           bool force_lookup) {
476   bool success = false;
477   const UUID *uuid_ptr = module_spec.GetUUIDPtr();
478   const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr();
479 
480   if (repro::Loader *l = repro::Reproducer::Instance().GetLoader()) {
481     static repro::SymbolFileLoader symbol_file_loader(l);
482     std::pair<FileSpec, FileSpec> paths = symbol_file_loader.GetPaths(uuid_ptr);
483     if (paths.first)
484       module_spec.GetFileSpec() = paths.first;
485     if (paths.second)
486       module_spec.GetSymbolFileSpec() = paths.second;
487     return true;
488   }
489 
490   // Lambda to capture the state of module_spec before returning from this
491   // function.
492   auto RecordResult = [&]() {
493     if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) {
494       g->GetOrCreate<repro::SymbolFileProvider>().AddSymbolFile(
495           uuid_ptr, module_spec.GetFileSpec(), module_spec.GetSymbolFileSpec());
496     }
497   };
498 
499   // It's expensive to check for the DBGShellCommands defaults setting, only do
500   // it once per lldb run and cache the result.
501   static bool g_have_checked_for_dbgshell_command = false;
502   static const char *g_dbgshell_command = NULL;
503   if (!g_have_checked_for_dbgshell_command) {
504     g_have_checked_for_dbgshell_command = true;
505     CFTypeRef defaults_setting = CFPreferencesCopyAppValue(
506         CFSTR("DBGShellCommands"), CFSTR("com.apple.DebugSymbols"));
507     if (defaults_setting &&
508         CFGetTypeID(defaults_setting) == CFStringGetTypeID()) {
509       char cstr_buf[PATH_MAX];
510       if (CFStringGetCString((CFStringRef)defaults_setting, cstr_buf,
511                              sizeof(cstr_buf), kCFStringEncodingUTF8)) {
512         g_dbgshell_command =
513             strdup(cstr_buf); // this malloc'ed memory will never be freed
514       }
515     }
516     if (defaults_setting) {
517       CFRelease(defaults_setting);
518     }
519   }
520 
521   // When g_dbgshell_command is NULL, the user has not enabled the use of an
522   // external program to find the symbols, don't run it for them.
523   if (!force_lookup && g_dbgshell_command == NULL) {
524     RecordResult();
525     return false;
526   }
527 
528   if (uuid_ptr ||
529       (file_spec_ptr && FileSystem::Instance().Exists(*file_spec_ptr))) {
530     static bool g_located_dsym_for_uuid_exe = false;
531     static bool g_dsym_for_uuid_exe_exists = false;
532     static char g_dsym_for_uuid_exe_path[PATH_MAX];
533     if (!g_located_dsym_for_uuid_exe) {
534       g_located_dsym_for_uuid_exe = true;
535       const char *dsym_for_uuid_exe_path_cstr =
536           getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE");
537       FileSpec dsym_for_uuid_exe_spec;
538       if (dsym_for_uuid_exe_path_cstr) {
539         dsym_for_uuid_exe_spec.SetFile(dsym_for_uuid_exe_path_cstr,
540                                        FileSpec::Style::native);
541         FileSystem::Instance().Resolve(dsym_for_uuid_exe_spec);
542         g_dsym_for_uuid_exe_exists =
543             FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
544       }
545 
546       if (!g_dsym_for_uuid_exe_exists) {
547         dsym_for_uuid_exe_spec.SetFile("/usr/local/bin/dsymForUUID",
548                                        FileSpec::Style::native);
549         g_dsym_for_uuid_exe_exists =
550             FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
551         if (!g_dsym_for_uuid_exe_exists) {
552           long bufsize;
553           if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) != -1) {
554             char buffer[bufsize];
555             struct passwd pwd;
556             struct passwd *tilde_rc = NULL;
557             // we are a library so we need to use the reentrant version of
558             // getpwnam()
559             if (getpwnam_r("rc", &pwd, buffer, bufsize, &tilde_rc) == 0 &&
560                 tilde_rc && tilde_rc->pw_dir) {
561               std::string dsymforuuid_path(tilde_rc->pw_dir);
562               dsymforuuid_path += "/bin/dsymForUUID";
563               dsym_for_uuid_exe_spec.SetFile(dsymforuuid_path.c_str(),
564                                              FileSpec::Style::native);
565               g_dsym_for_uuid_exe_exists =
566                   FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
567             }
568           }
569         }
570       }
571       if (!g_dsym_for_uuid_exe_exists && g_dbgshell_command != NULL) {
572         dsym_for_uuid_exe_spec.SetFile(g_dbgshell_command,
573                                        FileSpec::Style::native);
574         FileSystem::Instance().Resolve(dsym_for_uuid_exe_spec);
575         g_dsym_for_uuid_exe_exists =
576             FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
577       }
578 
579       if (g_dsym_for_uuid_exe_exists)
580         dsym_for_uuid_exe_spec.GetPath(g_dsym_for_uuid_exe_path,
581                                        sizeof(g_dsym_for_uuid_exe_path));
582     }
583     if (g_dsym_for_uuid_exe_exists) {
584       std::string uuid_str;
585       char file_path[PATH_MAX];
586       file_path[0] = '\0';
587 
588       if (uuid_ptr)
589         uuid_str = uuid_ptr->GetAsString();
590 
591       if (file_spec_ptr)
592         file_spec_ptr->GetPath(file_path, sizeof(file_path));
593 
594       StreamString command;
595       if (!uuid_str.empty())
596         command.Printf("%s --ignoreNegativeCache --copyExecutable %s",
597                        g_dsym_for_uuid_exe_path, uuid_str.c_str());
598       else if (file_path[0] != '\0')
599         command.Printf("%s --ignoreNegativeCache --copyExecutable %s",
600                        g_dsym_for_uuid_exe_path, file_path);
601 
602       if (!command.GetString().empty()) {
603         Log *log = GetLog(LLDBLog::Host);
604         int exit_status = -1;
605         int signo = -1;
606         std::string command_output;
607         if (log) {
608           if (!uuid_str.empty())
609             LLDB_LOGF(log, "Calling %s with UUID %s to find dSYM",
610                       g_dsym_for_uuid_exe_path, uuid_str.c_str());
611           else if (file_path[0] != '\0')
612             LLDB_LOGF(log, "Calling %s with file %s to find dSYM",
613                       g_dsym_for_uuid_exe_path, file_path);
614         }
615         Status error = Host::RunShellCommand(
616             command.GetData(),
617             FileSpec(),      // current working directory
618             &exit_status,    // Exit status
619             &signo,          // Signal int *
620             &command_output, // Command output
621             std::chrono::seconds(
622                 640), // Large timeout to allow for long dsym download times
623             false);   // Don't run in a shell (we don't need shell expansion)
624         if (error.Success() && exit_status == 0 && !command_output.empty()) {
625           CFCData data(CFDataCreateWithBytesNoCopy(
626               NULL, (const UInt8 *)command_output.data(), command_output.size(),
627               kCFAllocatorNull));
628 
629           CFCReleaser<CFDictionaryRef> plist(
630               (CFDictionaryRef)::CFPropertyListCreateFromXMLData(
631                   NULL, data.get(), kCFPropertyListImmutable, NULL));
632 
633           if (plist.get() &&
634               CFGetTypeID(plist.get()) == CFDictionaryGetTypeID()) {
635             if (!uuid_str.empty()) {
636               CFCString uuid_cfstr(uuid_str.c_str());
637               CFDictionaryRef uuid_dict = (CFDictionaryRef)CFDictionaryGetValue(
638                   plist.get(), uuid_cfstr.get());
639               success =
640                   GetModuleSpecInfoFromUUIDDictionary(uuid_dict, module_spec);
641             } else {
642               const CFIndex num_values = ::CFDictionaryGetCount(plist.get());
643               if (num_values > 0) {
644                 std::vector<CFStringRef> keys(num_values, NULL);
645                 std::vector<CFDictionaryRef> values(num_values, NULL);
646                 ::CFDictionaryGetKeysAndValues(plist.get(), NULL,
647                                                (const void **)&values[0]);
648                 if (num_values == 1) {
649                   success = GetModuleSpecInfoFromUUIDDictionary(values[0],
650                                                                 module_spec);
651                   RecordResult();
652                   return success;
653                 } else {
654                   for (CFIndex i = 0; i < num_values; ++i) {
655                     ModuleSpec curr_module_spec;
656                     if (GetModuleSpecInfoFromUUIDDictionary(values[i],
657                                                             curr_module_spec)) {
658                       if (module_spec.GetArchitecture().IsCompatibleMatch(
659                               curr_module_spec.GetArchitecture())) {
660                         module_spec = curr_module_spec;
661                         RecordResult();
662                         return true;
663                       }
664                     }
665                   }
666                 }
667               }
668             }
669           }
670         } else {
671           if (log) {
672             if (!uuid_str.empty())
673               LLDB_LOGF(log, "Called %s on %s, no matches",
674                         g_dsym_for_uuid_exe_path, uuid_str.c_str());
675             else if (file_path[0] != '\0')
676               LLDB_LOGF(log, "Called %s on %s, no matches",
677                         g_dsym_for_uuid_exe_path, file_path);
678           }
679         }
680       }
681     }
682   }
683   RecordResult();
684   return success;
685 }
686