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