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