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
41using namespace lldb;
42using namespace lldb_private;
43
44static CFURLRef (*g_dlsym_DBGCopyFullDSYMURLForUUID)(
45    CFUUIDRef uuid, CFURLRef exec_url) = nullptr;
46static CFDictionaryRef (*g_dlsym_DBGCopyDSYMPropertyLists)(CFURLRef dsym_url) =
47    nullptr;
48
49int 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
292FileSpec 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
331static 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.
496static 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.
519static 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
556bool 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