1//===-- SymbolLocatorDebugSymbols.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 "SymbolLocatorDebugSymbols.h"
10
11#include "Plugins/ObjectFile/wasm/ObjectFileWasm.h"
12#include "lldb/Core/Debugger.h"
13#include "lldb/Core/Module.h"
14#include "lldb/Core/ModuleList.h"
15#include "lldb/Core/ModuleSpec.h"
16#include "lldb/Core/PluginManager.h"
17#include "lldb/Core/Progress.h"
18#include "lldb/Core/Section.h"
19#include "lldb/Host/FileSystem.h"
20#include "lldb/Host/Host.h"
21#include "lldb/Host/HostInfo.h"
22#include "lldb/Symbol/ObjectFile.h"
23#include "lldb/Target/Target.h"
24#include "lldb/Utility/ArchSpec.h"
25#include "lldb/Utility/DataBuffer.h"
26#include "lldb/Utility/DataExtractor.h"
27#include "lldb/Utility/LLDBLog.h"
28#include "lldb/Utility/Log.h"
29#include "lldb/Utility/StreamString.h"
30#include "lldb/Utility/Timer.h"
31#include "lldb/Utility/UUID.h"
32
33#include "llvm/ADT/SmallSet.h"
34#include "llvm/Support/FileSystem.h"
35#include "llvm/Support/ThreadPool.h"
36
37#include "Host/macosx/cfcpp/CFCBundle.h"
38#include "Host/macosx/cfcpp/CFCData.h"
39#include "Host/macosx/cfcpp/CFCReleaser.h"
40#include "Host/macosx/cfcpp/CFCString.h"
41
42#include "mach/machine.h"
43
44#include <CoreFoundation/CoreFoundation.h>
45
46#include <cstring>
47#include <dirent.h>
48#include <dlfcn.h>
49#include <optional>
50#include <pwd.h>
51
52using namespace lldb;
53using namespace lldb_private;
54
55static CFURLRef (*g_dlsym_DBGCopyFullDSYMURLForUUID)(
56    CFUUIDRef uuid, CFURLRef exec_url) = nullptr;
57static CFDictionaryRef (*g_dlsym_DBGCopyDSYMPropertyLists)(CFURLRef dsym_url) =
58    nullptr;
59
60LLDB_PLUGIN_DEFINE(SymbolLocatorDebugSymbols)
61
62SymbolLocatorDebugSymbols::SymbolLocatorDebugSymbols() : SymbolLocator() {}
63
64void SymbolLocatorDebugSymbols::Initialize() {
65  PluginManager::RegisterPlugin(
66      GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
67      LocateExecutableObjectFile, LocateExecutableSymbolFile,
68      DownloadObjectAndSymbolFile, FindSymbolFileInBundle);
69}
70
71void SymbolLocatorDebugSymbols::Terminate() {
72  PluginManager::UnregisterPlugin(CreateInstance);
73}
74
75llvm::StringRef SymbolLocatorDebugSymbols::GetPluginDescriptionStatic() {
76  return "DebugSymbols symbol locator.";
77}
78
79SymbolLocator *SymbolLocatorDebugSymbols::CreateInstance() {
80  return new SymbolLocatorDebugSymbols();
81}
82
83std::optional<ModuleSpec> SymbolLocatorDebugSymbols::LocateExecutableObjectFile(
84    const ModuleSpec &module_spec) {
85  Log *log = GetLog(LLDBLog::Host);
86  if (!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) {
87    LLDB_LOGF(log, "Spotlight lookup for .dSYM bundles is disabled.");
88    return {};
89  }
90  ModuleSpec return_module_spec;
91  return_module_spec = module_spec;
92  return_module_spec.GetFileSpec().Clear();
93  return_module_spec.GetSymbolFileSpec().Clear();
94
95  const UUID *uuid = module_spec.GetUUIDPtr();
96  const ArchSpec *arch = module_spec.GetArchitecturePtr();
97
98  int items_found = 0;
99
100  if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
101      g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
102    void *handle = dlopen(
103        "/System/Library/PrivateFrameworks/DebugSymbols.framework/DebugSymbols",
104        RTLD_LAZY | RTLD_LOCAL);
105    if (handle) {
106      g_dlsym_DBGCopyFullDSYMURLForUUID =
107          (CFURLRef(*)(CFUUIDRef, CFURLRef))dlsym(handle,
108                                                  "DBGCopyFullDSYMURLForUUID");
109      g_dlsym_DBGCopyDSYMPropertyLists = (CFDictionaryRef(*)(CFURLRef))dlsym(
110          handle, "DBGCopyDSYMPropertyLists");
111    }
112  }
113
114  if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
115      g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
116    return {};
117  }
118
119  if (uuid && uuid->IsValid()) {
120    // Try and locate the dSYM file using DebugSymbols first
121    llvm::ArrayRef<uint8_t> module_uuid = uuid->GetBytes();
122    if (module_uuid.size() == 16) {
123      CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes(
124          NULL, module_uuid[0], module_uuid[1], module_uuid[2], module_uuid[3],
125          module_uuid[4], module_uuid[5], module_uuid[6], module_uuid[7],
126          module_uuid[8], module_uuid[9], module_uuid[10], module_uuid[11],
127          module_uuid[12], module_uuid[13], module_uuid[14], module_uuid[15]));
128
129      if (module_uuid_ref.get()) {
130        CFCReleaser<CFURLRef> exec_url;
131        const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
132        if (exec_fspec) {
133          char exec_cf_path[PATH_MAX];
134          if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path)))
135            exec_url.reset(::CFURLCreateFromFileSystemRepresentation(
136                NULL, (const UInt8 *)exec_cf_path, strlen(exec_cf_path),
137                FALSE));
138        }
139
140        CFCReleaser<CFURLRef> dsym_url(g_dlsym_DBGCopyFullDSYMURLForUUID(
141            module_uuid_ref.get(), exec_url.get()));
142        char path[PATH_MAX];
143
144        if (dsym_url.get()) {
145          if (::CFURLGetFileSystemRepresentation(
146                  dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
147            LLDB_LOGF(log,
148                      "DebugSymbols framework returned dSYM path of %s for "
149                      "UUID %s -- looking for the dSYM",
150                      path, uuid->GetAsString().c_str());
151            FileSpec dsym_filespec(path);
152            if (path[0] == '~')
153              FileSystem::Instance().Resolve(dsym_filespec);
154
155            if (FileSystem::Instance().IsDirectory(dsym_filespec)) {
156              dsym_filespec = PluginManager::FindSymbolFileInBundle(
157                  dsym_filespec, uuid, arch);
158              ++items_found;
159            } else {
160              ++items_found;
161            }
162            return_module_spec.GetSymbolFileSpec() = dsym_filespec;
163          }
164
165          bool success = false;
166          if (log) {
167            if (::CFURLGetFileSystemRepresentation(
168                    dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
169              LLDB_LOGF(log,
170                        "DebugSymbols framework returned dSYM path of %s for "
171                        "UUID %s -- looking for an exec file",
172                        path, uuid->GetAsString().c_str());
173            }
174          }
175
176          CFCReleaser<CFDictionaryRef> dict(
177              g_dlsym_DBGCopyDSYMPropertyLists(dsym_url.get()));
178          CFDictionaryRef uuid_dict = NULL;
179          if (dict.get()) {
180            CFCString uuid_cfstr(uuid->GetAsString().c_str());
181            uuid_dict = static_cast<CFDictionaryRef>(
182                ::CFDictionaryGetValue(dict.get(), uuid_cfstr.get()));
183          }
184
185          // Check to see if we have the file on the local filesystem.
186          if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
187            ModuleSpec exe_spec;
188            exe_spec.GetFileSpec() = module_spec.GetFileSpec();
189            exe_spec.GetUUID() = module_spec.GetUUID();
190            ModuleSP module_sp;
191            module_sp.reset(new Module(exe_spec));
192            if (module_sp && module_sp->GetObjectFile() &&
193                module_sp->MatchesModuleSpec(exe_spec)) {
194              success = true;
195              return_module_spec.GetFileSpec() = module_spec.GetFileSpec();
196              LLDB_LOGF(log, "using original binary filepath %s for UUID %s",
197                        module_spec.GetFileSpec().GetPath().c_str(),
198                        uuid->GetAsString().c_str());
199              ++items_found;
200            }
201          }
202
203          // Check if the requested image is in our shared cache.
204          if (!success) {
205            SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo(
206                module_spec.GetFileSpec().GetPath());
207
208            // If we found it and it has the correct UUID, let's proceed with
209            // creating a module from the memory contents.
210            if (image_info.uuid && (!module_spec.GetUUID() ||
211                                    module_spec.GetUUID() == image_info.uuid)) {
212              success = true;
213              return_module_spec.GetFileSpec() = module_spec.GetFileSpec();
214              LLDB_LOGF(log,
215                        "using binary from shared cache for filepath %s for "
216                        "UUID %s",
217                        module_spec.GetFileSpec().GetPath().c_str(),
218                        uuid->GetAsString().c_str());
219              ++items_found;
220            }
221          }
222
223          // Use the DBGSymbolRichExecutable filepath if present
224          if (!success && uuid_dict) {
225            CFStringRef exec_cf_path =
226                static_cast<CFStringRef>(::CFDictionaryGetValue(
227                    uuid_dict, CFSTR("DBGSymbolRichExecutable")));
228            if (exec_cf_path && ::CFStringGetFileSystemRepresentation(
229                                    exec_cf_path, path, sizeof(path))) {
230              LLDB_LOGF(log, "plist bundle has exec path of %s for UUID %s",
231                        path, uuid->GetAsString().c_str());
232              ++items_found;
233              FileSpec exec_filespec(path);
234              if (path[0] == '~')
235                FileSystem::Instance().Resolve(exec_filespec);
236              if (FileSystem::Instance().Exists(exec_filespec)) {
237                success = true;
238                return_module_spec.GetFileSpec() = exec_filespec;
239              }
240            }
241          }
242
243          // Look next to the dSYM for the binary file.
244          if (!success) {
245            if (::CFURLGetFileSystemRepresentation(
246                    dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
247              char *dsym_extension_pos = ::strstr(path, ".dSYM");
248              if (dsym_extension_pos) {
249                *dsym_extension_pos = '\0';
250                LLDB_LOGF(log,
251                          "Looking for executable binary next to dSYM "
252                          "bundle with name with name %s",
253                          path);
254                FileSpec file_spec(path);
255                FileSystem::Instance().Resolve(file_spec);
256                ModuleSpecList module_specs;
257                ModuleSpec matched_module_spec;
258                using namespace llvm::sys::fs;
259                switch (get_file_type(file_spec.GetPath())) {
260
261                case file_type::directory_file: // Bundle directory?
262                {
263                  CFCBundle bundle(path);
264                  CFCReleaser<CFURLRef> bundle_exe_url(
265                      bundle.CopyExecutableURL());
266                  if (bundle_exe_url.get()) {
267                    if (::CFURLGetFileSystemRepresentation(bundle_exe_url.get(),
268                                                           true, (UInt8 *)path,
269                                                           sizeof(path) - 1)) {
270                      FileSpec bundle_exe_file_spec(path);
271                      FileSystem::Instance().Resolve(bundle_exe_file_spec);
272                      if (ObjectFile::GetModuleSpecifications(
273                              bundle_exe_file_spec, 0, 0, module_specs) &&
274                          module_specs.FindMatchingModuleSpec(
275                              module_spec, matched_module_spec))
276
277                      {
278                        ++items_found;
279                        return_module_spec.GetFileSpec() = bundle_exe_file_spec;
280                        LLDB_LOGF(log,
281                                  "Executable binary %s next to dSYM is "
282                                  "compatible; using",
283                                  path);
284                      }
285                    }
286                  }
287                } break;
288
289                case file_type::fifo_file:      // Forget pipes
290                case file_type::socket_file:    // We can't process socket files
291                case file_type::file_not_found: // File doesn't exist...
292                case file_type::status_error:
293                  break;
294
295                case file_type::type_unknown:
296                case file_type::regular_file:
297                case file_type::symlink_file:
298                case file_type::block_file:
299                case file_type::character_file:
300                  if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0,
301                                                          module_specs) &&
302                      module_specs.FindMatchingModuleSpec(module_spec,
303                                                          matched_module_spec))
304
305                  {
306                    ++items_found;
307                    return_module_spec.GetFileSpec() = file_spec;
308                    LLDB_LOGF(log,
309                              "Executable binary %s next to dSYM is "
310                              "compatible; using",
311                              path);
312                  }
313                  break;
314                }
315              }
316            }
317          }
318        }
319      }
320    }
321  }
322
323  if (items_found)
324    return return_module_spec;
325
326  return {};
327}
328
329std::optional<FileSpec> SymbolLocatorDebugSymbols::FindSymbolFileInBundle(
330    const FileSpec &dsym_bundle_fspec, const UUID *uuid, const ArchSpec *arch) {
331  std::string dsym_bundle_path = dsym_bundle_fspec.GetPath();
332  llvm::SmallString<128> buffer(dsym_bundle_path);
333  llvm::sys::path::append(buffer, "Contents", "Resources", "DWARF");
334
335  std::error_code EC;
336  llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs =
337      FileSystem::Instance().GetVirtualFileSystem();
338  llvm::vfs::recursive_directory_iterator Iter(*vfs, buffer.str(), EC);
339  llvm::vfs::recursive_directory_iterator End;
340  for (; Iter != End && !EC; Iter.increment(EC)) {
341    llvm::ErrorOr<llvm::vfs::Status> Status = vfs->status(Iter->path());
342    if (Status->isDirectory())
343      continue;
344
345    FileSpec dsym_fspec(Iter->path());
346    ModuleSpecList module_specs;
347    if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs)) {
348      ModuleSpec spec;
349      for (size_t i = 0; i < module_specs.GetSize(); ++i) {
350        bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
351        assert(got_spec); // The call has side-effects so can't be inlined.
352        UNUSED_IF_ASSERT_DISABLED(got_spec);
353        if ((uuid == nullptr ||
354             (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
355            (arch == nullptr ||
356             (spec.GetArchitecturePtr() &&
357              spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
358          return dsym_fspec;
359        }
360      }
361    }
362  }
363
364  return {};
365}
366
367static bool FileAtPathContainsArchAndUUID(const FileSpec &file_fspec,
368                                          const ArchSpec *arch,
369                                          const lldb_private::UUID *uuid) {
370  ModuleSpecList module_specs;
371  if (ObjectFile::GetModuleSpecifications(file_fspec, 0, 0, module_specs)) {
372    ModuleSpec spec;
373    for (size_t i = 0; i < module_specs.GetSize(); ++i) {
374      bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
375      UNUSED_IF_ASSERT_DISABLED(got_spec);
376      assert(got_spec);
377      if ((uuid == nullptr || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
378          (arch == nullptr ||
379           (spec.GetArchitecturePtr() &&
380            spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
381        return true;
382      }
383    }
384  }
385  return false;
386}
387
388// Given a binary exec_fspec, and a ModuleSpec with an architecture/uuid,
389// return true if there is a matching dSYM bundle next to the exec_fspec,
390// and return that value in dsym_fspec.
391// If there is a .dSYM.yaa compressed archive next to the exec_fspec,
392// call through PluginManager::DownloadObjectAndSymbolFile to download the
393// expanded/uncompressed dSYM and return that filepath in dsym_fspec.
394static bool LookForDsymNextToExecutablePath(const ModuleSpec &mod_spec,
395                                            const FileSpec &exec_fspec,
396                                            FileSpec &dsym_fspec) {
397  ConstString filename = exec_fspec.GetFilename();
398  FileSpec dsym_directory = exec_fspec;
399  dsym_directory.RemoveLastPathComponent();
400
401  std::string dsym_filename = filename.AsCString();
402  dsym_filename += ".dSYM";
403  dsym_directory.AppendPathComponent(dsym_filename);
404  dsym_directory.AppendPathComponent("Contents");
405  dsym_directory.AppendPathComponent("Resources");
406  dsym_directory.AppendPathComponent("DWARF");
407
408  if (FileSystem::Instance().Exists(dsym_directory)) {
409
410    // See if the binary name exists in the dSYM DWARF
411    // subdir.
412    dsym_fspec = dsym_directory;
413    dsym_fspec.AppendPathComponent(filename.AsCString());
414    if (FileSystem::Instance().Exists(dsym_fspec) &&
415        FileAtPathContainsArchAndUUID(dsym_fspec, mod_spec.GetArchitecturePtr(),
416                                      mod_spec.GetUUIDPtr())) {
417      return true;
418    }
419
420    // See if we have "../CF.framework" - so we'll look for
421    // CF.framework.dSYM/Contents/Resources/DWARF/CF
422    // We need to drop the last suffix after '.' to match
423    // 'CF' in the DWARF subdir.
424    std::string binary_name(filename.AsCString());
425    auto last_dot = binary_name.find_last_of('.');
426    if (last_dot != std::string::npos) {
427      binary_name.erase(last_dot);
428      dsym_fspec = dsym_directory;
429      dsym_fspec.AppendPathComponent(binary_name);
430      if (FileSystem::Instance().Exists(dsym_fspec) &&
431          FileAtPathContainsArchAndUUID(dsym_fspec,
432                                        mod_spec.GetArchitecturePtr(),
433                                        mod_spec.GetUUIDPtr())) {
434        return true;
435      }
436    }
437  }
438
439  // See if we have a .dSYM.yaa next to this executable path.
440  FileSpec dsym_yaa_fspec = exec_fspec;
441  dsym_yaa_fspec.RemoveLastPathComponent();
442  std::string dsym_yaa_filename = filename.AsCString();
443  dsym_yaa_filename += ".dSYM.yaa";
444  dsym_yaa_fspec.AppendPathComponent(dsym_yaa_filename);
445
446  if (FileSystem::Instance().Exists(dsym_yaa_fspec)) {
447    ModuleSpec mutable_mod_spec = mod_spec;
448    Status error;
449    if (PluginManager::DownloadObjectAndSymbolFile(mutable_mod_spec, error,
450                                                   true) &&
451        FileSystem::Instance().Exists(mutable_mod_spec.GetSymbolFileSpec())) {
452      dsym_fspec = mutable_mod_spec.GetSymbolFileSpec();
453      return true;
454    }
455  }
456
457  return false;
458}
459
460// Given a ModuleSpec with a FileSpec and optionally uuid/architecture
461// filled in, look for a .dSYM bundle next to that binary.  Returns true
462// if a .dSYM bundle is found, and that path is returned in the dsym_fspec
463// FileSpec.
464//
465// This routine looks a few directory layers above the given exec_path -
466// exec_path might be /System/Library/Frameworks/CF.framework/CF and the
467// dSYM might be /System/Library/Frameworks/CF.framework.dSYM.
468//
469// If there is a .dSYM.yaa compressed archive found next to the binary,
470// we'll call DownloadObjectAndSymbolFile to expand it into a plain .dSYM
471static bool LocateDSYMInVincinityOfExecutable(const ModuleSpec &module_spec,
472                                              FileSpec &dsym_fspec) {
473  Log *log = GetLog(LLDBLog::Host);
474  const FileSpec &exec_fspec = module_spec.GetFileSpec();
475  if (exec_fspec) {
476    if (::LookForDsymNextToExecutablePath(module_spec, exec_fspec,
477                                          dsym_fspec)) {
478      if (log) {
479        LLDB_LOGF(log, "dSYM with matching UUID & arch found at %s",
480                  dsym_fspec.GetPath().c_str());
481      }
482      return true;
483    } else {
484      FileSpec parent_dirs = exec_fspec;
485
486      // Remove the binary name from the FileSpec
487      parent_dirs.RemoveLastPathComponent();
488
489      // Add a ".dSYM" name to each directory component of the path,
490      // stripping off components.  e.g. we may have a binary like
491      // /S/L/F/Foundation.framework/Versions/A/Foundation and
492      // /S/L/F/Foundation.framework.dSYM
493      //
494      // so we'll need to start with
495      // /S/L/F/Foundation.framework/Versions/A, add the .dSYM part to the
496      // "A", and if that doesn't exist, strip off the "A" and try it again
497      // with "Versions", etc., until we find a dSYM bundle or we've
498      // stripped off enough path components that there's no need to
499      // continue.
500
501      for (int i = 0; i < 4; i++) {
502        // Does this part of the path have a "." character - could it be a
503        // bundle's top level directory?
504        const char *fn = parent_dirs.GetFilename().AsCString();
505        if (fn == nullptr)
506          break;
507        if (::strchr(fn, '.') != nullptr) {
508          if (::LookForDsymNextToExecutablePath(module_spec, parent_dirs,
509                                                dsym_fspec)) {
510            if (log) {
511              LLDB_LOGF(log, "dSYM with matching UUID & arch found at %s",
512                        dsym_fspec.GetPath().c_str());
513            }
514            return true;
515          }
516        }
517        parent_dirs.RemoveLastPathComponent();
518      }
519    }
520  }
521  dsym_fspec.Clear();
522  return false;
523}
524
525static int LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec &module_spec,
526                                              ModuleSpec &return_module_spec) {
527  Log *log = GetLog(LLDBLog::Host);
528  if (!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) {
529    LLDB_LOGF(log, "Spotlight lookup for .dSYM bundles is disabled.");
530    return 0;
531  }
532
533  return_module_spec = module_spec;
534  return_module_spec.GetFileSpec().Clear();
535  return_module_spec.GetSymbolFileSpec().Clear();
536
537  const UUID *uuid = module_spec.GetUUIDPtr();
538  const ArchSpec *arch = module_spec.GetArchitecturePtr();
539
540  int items_found = 0;
541
542  if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
543      g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
544    void *handle = dlopen(
545        "/System/Library/PrivateFrameworks/DebugSymbols.framework/DebugSymbols",
546        RTLD_LAZY | RTLD_LOCAL);
547    if (handle) {
548      g_dlsym_DBGCopyFullDSYMURLForUUID =
549          (CFURLRef(*)(CFUUIDRef, CFURLRef))dlsym(handle,
550                                                  "DBGCopyFullDSYMURLForUUID");
551      g_dlsym_DBGCopyDSYMPropertyLists = (CFDictionaryRef(*)(CFURLRef))dlsym(
552          handle, "DBGCopyDSYMPropertyLists");
553    }
554  }
555
556  if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
557      g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
558    return items_found;
559  }
560
561  if (uuid && uuid->IsValid()) {
562    // Try and locate the dSYM file using DebugSymbols first
563    llvm::ArrayRef<uint8_t> module_uuid = uuid->GetBytes();
564    if (module_uuid.size() == 16) {
565      CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes(
566          NULL, module_uuid[0], module_uuid[1], module_uuid[2], module_uuid[3],
567          module_uuid[4], module_uuid[5], module_uuid[6], module_uuid[7],
568          module_uuid[8], module_uuid[9], module_uuid[10], module_uuid[11],
569          module_uuid[12], module_uuid[13], module_uuid[14], module_uuid[15]));
570
571      if (module_uuid_ref.get()) {
572        CFCReleaser<CFURLRef> exec_url;
573        const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
574        if (exec_fspec) {
575          char exec_cf_path[PATH_MAX];
576          if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path)))
577            exec_url.reset(::CFURLCreateFromFileSystemRepresentation(
578                NULL, (const UInt8 *)exec_cf_path, strlen(exec_cf_path),
579                FALSE));
580        }
581
582        CFCReleaser<CFURLRef> dsym_url(g_dlsym_DBGCopyFullDSYMURLForUUID(
583            module_uuid_ref.get(), exec_url.get()));
584        char path[PATH_MAX];
585
586        if (dsym_url.get()) {
587          if (::CFURLGetFileSystemRepresentation(
588                  dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
589            LLDB_LOGF(log,
590                      "DebugSymbols framework returned dSYM path of %s for "
591                      "UUID %s -- looking for the dSYM",
592                      path, uuid->GetAsString().c_str());
593            FileSpec dsym_filespec(path);
594            if (path[0] == '~')
595              FileSystem::Instance().Resolve(dsym_filespec);
596
597            if (FileSystem::Instance().IsDirectory(dsym_filespec)) {
598              dsym_filespec = PluginManager::FindSymbolFileInBundle(
599                  dsym_filespec, uuid, arch);
600              ++items_found;
601            } else {
602              ++items_found;
603            }
604            return_module_spec.GetSymbolFileSpec() = dsym_filespec;
605          }
606
607          bool success = false;
608          if (log) {
609            if (::CFURLGetFileSystemRepresentation(
610                    dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
611              LLDB_LOGF(log,
612                        "DebugSymbols framework returned dSYM path of %s for "
613                        "UUID %s -- looking for an exec file",
614                        path, uuid->GetAsString().c_str());
615            }
616          }
617
618          CFCReleaser<CFDictionaryRef> dict(
619              g_dlsym_DBGCopyDSYMPropertyLists(dsym_url.get()));
620          CFDictionaryRef uuid_dict = NULL;
621          if (dict.get()) {
622            CFCString uuid_cfstr(uuid->GetAsString().c_str());
623            uuid_dict = static_cast<CFDictionaryRef>(
624                ::CFDictionaryGetValue(dict.get(), uuid_cfstr.get()));
625          }
626
627          // Check to see if we have the file on the local filesystem.
628          if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
629            ModuleSpec exe_spec;
630            exe_spec.GetFileSpec() = module_spec.GetFileSpec();
631            exe_spec.GetUUID() = module_spec.GetUUID();
632            ModuleSP module_sp;
633            module_sp.reset(new Module(exe_spec));
634            if (module_sp && module_sp->GetObjectFile() &&
635                module_sp->MatchesModuleSpec(exe_spec)) {
636              success = true;
637              return_module_spec.GetFileSpec() = module_spec.GetFileSpec();
638              LLDB_LOGF(log, "using original binary filepath %s for UUID %s",
639                        module_spec.GetFileSpec().GetPath().c_str(),
640                        uuid->GetAsString().c_str());
641              ++items_found;
642            }
643          }
644
645          // Check if the requested image is in our shared cache.
646          if (!success) {
647            SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo(
648                module_spec.GetFileSpec().GetPath());
649
650            // If we found it and it has the correct UUID, let's proceed with
651            // creating a module from the memory contents.
652            if (image_info.uuid && (!module_spec.GetUUID() ||
653                                    module_spec.GetUUID() == image_info.uuid)) {
654              success = true;
655              return_module_spec.GetFileSpec() = module_spec.GetFileSpec();
656              LLDB_LOGF(log,
657                        "using binary from shared cache for filepath %s for "
658                        "UUID %s",
659                        module_spec.GetFileSpec().GetPath().c_str(),
660                        uuid->GetAsString().c_str());
661              ++items_found;
662            }
663          }
664
665          // Use the DBGSymbolRichExecutable filepath if present
666          if (!success && uuid_dict) {
667            CFStringRef exec_cf_path =
668                static_cast<CFStringRef>(::CFDictionaryGetValue(
669                    uuid_dict, CFSTR("DBGSymbolRichExecutable")));
670            if (exec_cf_path && ::CFStringGetFileSystemRepresentation(
671                                    exec_cf_path, path, sizeof(path))) {
672              LLDB_LOGF(log, "plist bundle has exec path of %s for UUID %s",
673                        path, uuid->GetAsString().c_str());
674              ++items_found;
675              FileSpec exec_filespec(path);
676              if (path[0] == '~')
677                FileSystem::Instance().Resolve(exec_filespec);
678              if (FileSystem::Instance().Exists(exec_filespec)) {
679                success = true;
680                return_module_spec.GetFileSpec() = exec_filespec;
681              }
682            }
683          }
684
685          // Look next to the dSYM for the binary file.
686          if (!success) {
687            if (::CFURLGetFileSystemRepresentation(
688                    dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
689              char *dsym_extension_pos = ::strstr(path, ".dSYM");
690              if (dsym_extension_pos) {
691                *dsym_extension_pos = '\0';
692                LLDB_LOGF(log,
693                          "Looking for executable binary next to dSYM "
694                          "bundle with name with name %s",
695                          path);
696                FileSpec file_spec(path);
697                FileSystem::Instance().Resolve(file_spec);
698                ModuleSpecList module_specs;
699                ModuleSpec matched_module_spec;
700                using namespace llvm::sys::fs;
701                switch (get_file_type(file_spec.GetPath())) {
702
703                case file_type::directory_file: // Bundle directory?
704                {
705                  CFCBundle bundle(path);
706                  CFCReleaser<CFURLRef> bundle_exe_url(
707                      bundle.CopyExecutableURL());
708                  if (bundle_exe_url.get()) {
709                    if (::CFURLGetFileSystemRepresentation(bundle_exe_url.get(),
710                                                           true, (UInt8 *)path,
711                                                           sizeof(path) - 1)) {
712                      FileSpec bundle_exe_file_spec(path);
713                      FileSystem::Instance().Resolve(bundle_exe_file_spec);
714                      if (ObjectFile::GetModuleSpecifications(
715                              bundle_exe_file_spec, 0, 0, module_specs) &&
716                          module_specs.FindMatchingModuleSpec(
717                              module_spec, matched_module_spec))
718
719                      {
720                        ++items_found;
721                        return_module_spec.GetFileSpec() = bundle_exe_file_spec;
722                        LLDB_LOGF(log,
723                                  "Executable binary %s next to dSYM is "
724                                  "compatible; using",
725                                  path);
726                      }
727                    }
728                  }
729                } break;
730
731                case file_type::fifo_file:      // Forget pipes
732                case file_type::socket_file:    // We can't process socket files
733                case file_type::file_not_found: // File doesn't exist...
734                case file_type::status_error:
735                  break;
736
737                case file_type::type_unknown:
738                case file_type::regular_file:
739                case file_type::symlink_file:
740                case file_type::block_file:
741                case file_type::character_file:
742                  if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0,
743                                                          module_specs) &&
744                      module_specs.FindMatchingModuleSpec(module_spec,
745                                                          matched_module_spec))
746
747                  {
748                    ++items_found;
749                    return_module_spec.GetFileSpec() = file_spec;
750                    LLDB_LOGF(log,
751                              "Executable binary %s next to dSYM is "
752                              "compatible; using",
753                              path);
754                  }
755                  break;
756                }
757              }
758            }
759          }
760        }
761      }
762    }
763  }
764
765  return items_found;
766}
767
768std::optional<FileSpec> SymbolLocatorDebugSymbols::LocateExecutableSymbolFile(
769    const ModuleSpec &module_spec, const FileSpecList &default_search_paths) {
770  const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
771  const ArchSpec *arch = module_spec.GetArchitecturePtr();
772  const UUID *uuid = module_spec.GetUUIDPtr();
773
774  LLDB_SCOPED_TIMERF(
775      "LocateExecutableSymbolFileDsym (file = %s, arch = %s, uuid = %p)",
776      exec_fspec ? exec_fspec->GetFilename().AsCString("<NULL>") : "<NULL>",
777      arch ? arch->GetArchitectureName() : "<NULL>", (const void *)uuid);
778
779  FileSpec symbol_fspec;
780  ModuleSpec dsym_module_spec;
781  // First try and find the dSYM in the same directory as the executable or in
782  // an appropriate parent directory
783  if (!LocateDSYMInVincinityOfExecutable(module_spec, symbol_fspec)) {
784    // We failed to easily find the dSYM above, so use DebugSymbols
785    LocateMacOSXFilesUsingDebugSymbols(module_spec, dsym_module_spec);
786  } else {
787    dsym_module_spec.GetSymbolFileSpec() = symbol_fspec;
788  }
789
790  return dsym_module_spec.GetSymbolFileSpec();
791}
792
793static bool GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict,
794                                                ModuleSpec &module_spec,
795                                                Status &error,
796                                                const std::string &command) {
797  Log *log = GetLog(LLDBLog::Host);
798  bool success = false;
799  if (uuid_dict != NULL && CFGetTypeID(uuid_dict) == CFDictionaryGetTypeID()) {
800    std::string str;
801    CFStringRef cf_str;
802    CFDictionaryRef cf_dict;
803
804    cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
805                                               CFSTR("DBGError"));
806    if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
807      if (CFCString::FileSystemRepresentation(cf_str, str)) {
808        std::string errorstr = command;
809        errorstr += ":\n";
810        errorstr += str;
811        error.SetErrorString(errorstr);
812      }
813    }
814
815    cf_str = (CFStringRef)CFDictionaryGetValue(
816        (CFDictionaryRef)uuid_dict, CFSTR("DBGSymbolRichExecutable"));
817    if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
818      if (CFCString::FileSystemRepresentation(cf_str, str)) {
819        module_spec.GetFileSpec().SetFile(str.c_str(), FileSpec::Style::native);
820        FileSystem::Instance().Resolve(module_spec.GetFileSpec());
821        LLDB_LOGF(log,
822                  "From dsymForUUID plist: Symbol rich executable is at '%s'",
823                  str.c_str());
824      }
825    }
826
827    cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
828                                               CFSTR("DBGDSYMPath"));
829    if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
830      if (CFCString::FileSystemRepresentation(cf_str, str)) {
831        module_spec.GetSymbolFileSpec().SetFile(str.c_str(),
832                                                FileSpec::Style::native);
833        FileSystem::Instance().Resolve(module_spec.GetFileSpec());
834        success = true;
835        LLDB_LOGF(log, "From dsymForUUID plist: dSYM is at '%s'", str.c_str());
836      }
837    }
838
839    std::string DBGBuildSourcePath;
840    std::string DBGSourcePath;
841
842    // If DBGVersion 1 or DBGVersion missing, ignore DBGSourcePathRemapping.
843    // If DBGVersion 2, strip last two components of path remappings from
844    //                  entries to fix an issue with a specific set of
845    //                  DBGSourcePathRemapping entries that lldb worked
846    //                  with.
847    // If DBGVersion 3, trust & use the source path remappings as-is.
848    //
849    cf_dict = (CFDictionaryRef)CFDictionaryGetValue(
850        (CFDictionaryRef)uuid_dict, CFSTR("DBGSourcePathRemapping"));
851    if (cf_dict && CFGetTypeID(cf_dict) == CFDictionaryGetTypeID()) {
852      // If we see DBGVersion with a value of 2 or higher, this is a new style
853      // DBGSourcePathRemapping dictionary
854      bool new_style_source_remapping_dictionary = false;
855      bool do_truncate_remapping_names = false;
856      std::string original_DBGSourcePath_value = DBGSourcePath;
857      cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
858                                                 CFSTR("DBGVersion"));
859      if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
860        std::string version;
861        CFCString::FileSystemRepresentation(cf_str, version);
862        if (!version.empty() && isdigit(version[0])) {
863          int version_number = atoi(version.c_str());
864          if (version_number > 1) {
865            new_style_source_remapping_dictionary = true;
866          }
867          if (version_number == 2) {
868            do_truncate_remapping_names = true;
869          }
870        }
871      }
872
873      CFIndex kv_pair_count = CFDictionaryGetCount((CFDictionaryRef)uuid_dict);
874      if (kv_pair_count > 0) {
875        CFStringRef *keys =
876            (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
877        CFStringRef *values =
878            (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
879        if (keys != nullptr && values != nullptr) {
880          CFDictionaryGetKeysAndValues((CFDictionaryRef)uuid_dict,
881                                       (const void **)keys,
882                                       (const void **)values);
883        }
884        for (CFIndex i = 0; i < kv_pair_count; i++) {
885          DBGBuildSourcePath.clear();
886          DBGSourcePath.clear();
887          if (keys[i] && CFGetTypeID(keys[i]) == CFStringGetTypeID()) {
888            CFCString::FileSystemRepresentation(keys[i], DBGBuildSourcePath);
889          }
890          if (values[i] && CFGetTypeID(values[i]) == CFStringGetTypeID()) {
891            CFCString::FileSystemRepresentation(values[i], DBGSourcePath);
892          }
893          if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
894            // In the "old style" DBGSourcePathRemapping dictionary, the
895            // DBGSourcePath values (the "values" half of key-value path pairs)
896            // were wrong.  Ignore them and use the universal DBGSourcePath
897            // string from earlier.
898            if (new_style_source_remapping_dictionary &&
899                !original_DBGSourcePath_value.empty()) {
900              DBGSourcePath = original_DBGSourcePath_value;
901            }
902            if (DBGSourcePath[0] == '~') {
903              FileSpec resolved_source_path(DBGSourcePath.c_str());
904              FileSystem::Instance().Resolve(resolved_source_path);
905              DBGSourcePath = resolved_source_path.GetPath();
906            }
907            // With version 2 of DBGSourcePathRemapping, we can chop off the
908            // last two filename parts from the source remapping and get a more
909            // general source remapping that still works. Add this as another
910            // option in addition to the full source path remap.
911            module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
912                                                      DBGSourcePath, true);
913            if (do_truncate_remapping_names) {
914              FileSpec build_path(DBGBuildSourcePath.c_str());
915              FileSpec source_path(DBGSourcePath.c_str());
916              build_path.RemoveLastPathComponent();
917              build_path.RemoveLastPathComponent();
918              source_path.RemoveLastPathComponent();
919              source_path.RemoveLastPathComponent();
920              module_spec.GetSourceMappingList().Append(
921                  build_path.GetPath(), source_path.GetPath(), true);
922            }
923          }
924        }
925        if (keys)
926          free(keys);
927        if (values)
928          free(values);
929      }
930    }
931
932    // If we have a DBGBuildSourcePath + DBGSourcePath pair, append them to the
933    // source remappings list.
934
935    cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
936                                               CFSTR("DBGBuildSourcePath"));
937    if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
938      CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath);
939    }
940
941    cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
942                                               CFSTR("DBGSourcePath"));
943    if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
944      CFCString::FileSystemRepresentation(cf_str, DBGSourcePath);
945    }
946
947    if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
948      if (DBGSourcePath[0] == '~') {
949        FileSpec resolved_source_path(DBGSourcePath.c_str());
950        FileSystem::Instance().Resolve(resolved_source_path);
951        DBGSourcePath = resolved_source_path.GetPath();
952      }
953      module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
954                                                DBGSourcePath, true);
955    }
956  }
957  return success;
958}
959
960/// It's expensive to check for the DBGShellCommands defaults setting. Only do
961/// it once per lldb run and cache the result.
962static llvm::StringRef GetDbgShellCommand() {
963  static std::once_flag g_once_flag;
964  static std::string g_dbgshell_command;
965  std::call_once(g_once_flag, [&]() {
966    CFTypeRef defaults_setting = CFPreferencesCopyAppValue(
967        CFSTR("DBGShellCommands"), CFSTR("com.apple.DebugSymbols"));
968    if (defaults_setting &&
969        CFGetTypeID(defaults_setting) == CFStringGetTypeID()) {
970      char buffer[PATH_MAX];
971      if (CFStringGetCString((CFStringRef)defaults_setting, buffer,
972                             sizeof(buffer), kCFStringEncodingUTF8)) {
973        g_dbgshell_command = buffer;
974      }
975    }
976    if (defaults_setting) {
977      CFRelease(defaults_setting);
978    }
979  });
980  return g_dbgshell_command;
981}
982
983/// Get the dsymForUUID executable and cache the result so we don't end up
984/// stat'ing the binary over and over.
985static FileSpec GetDsymForUUIDExecutable() {
986  // The LLDB_APPLE_DSYMFORUUID_EXECUTABLE environment variable is used by the
987  // test suite to override the dsymForUUID location. Because we must be able
988  // to change the value within a single test, don't bother caching it.
989  if (const char *dsymForUUID_env =
990          getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE")) {
991    FileSpec dsymForUUID_executable(dsymForUUID_env);
992    FileSystem::Instance().Resolve(dsymForUUID_executable);
993    if (FileSystem::Instance().Exists(dsymForUUID_executable))
994      return dsymForUUID_executable;
995  }
996
997  static std::once_flag g_once_flag;
998  static FileSpec g_dsymForUUID_executable;
999  std::call_once(g_once_flag, [&]() {
1000    // Try the DBGShellCommand.
1001    llvm::StringRef dbgshell_command = GetDbgShellCommand();
1002    if (!dbgshell_command.empty()) {
1003      g_dsymForUUID_executable = FileSpec(dbgshell_command);
1004      FileSystem::Instance().Resolve(g_dsymForUUID_executable);
1005      if (FileSystem::Instance().Exists(g_dsymForUUID_executable))
1006        return;
1007    }
1008
1009    // Try dsymForUUID in /usr/local/bin
1010    {
1011      g_dsymForUUID_executable = FileSpec("/usr/local/bin/dsymForUUID");
1012      if (FileSystem::Instance().Exists(g_dsymForUUID_executable))
1013        return;
1014    }
1015
1016    // We couldn't find the dsymForUUID binary.
1017    g_dsymForUUID_executable = {};
1018  });
1019  return g_dsymForUUID_executable;
1020}
1021
1022bool SymbolLocatorDebugSymbols::DownloadObjectAndSymbolFile(
1023    ModuleSpec &module_spec, Status &error, bool force_lookup,
1024    bool copy_executable) {
1025  const UUID *uuid_ptr = module_spec.GetUUIDPtr();
1026  const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr();
1027
1028  // If \a dbgshell_command is set, the user has specified
1029  // forced symbol lookup via that command.  We'll get the
1030  // path back from GetDsymForUUIDExecutable() later.
1031  llvm::StringRef dbgshell_command = GetDbgShellCommand();
1032
1033  // If forced lookup isn't set, by the user's \a dbgshell_command or
1034  // by the \a force_lookup argument, exit this method.
1035  if (!force_lookup && dbgshell_command.empty())
1036    return false;
1037
1038  // We need a UUID or valid existing FileSpec.
1039  if (!uuid_ptr &&
1040      (!file_spec_ptr || !FileSystem::Instance().Exists(*file_spec_ptr)))
1041    return false;
1042
1043  // We need a dsymForUUID binary or an equivalent executable/script.
1044  FileSpec dsymForUUID_exe_spec = GetDsymForUUIDExecutable();
1045  if (!dsymForUUID_exe_spec)
1046    return false;
1047
1048  const std::string dsymForUUID_exe_path = dsymForUUID_exe_spec.GetPath();
1049  const std::string uuid_str = uuid_ptr ? uuid_ptr->GetAsString() : "";
1050  const std::string file_path_str =
1051      file_spec_ptr ? file_spec_ptr->GetPath() : "";
1052
1053  Log *log = GetLog(LLDBLog::Host);
1054
1055  // Create the dsymForUUID command.
1056  StreamString command;
1057  const char *copy_executable_arg = copy_executable ? "--copyExecutable " : "";
1058  if (!uuid_str.empty()) {
1059    command.Printf("%s --ignoreNegativeCache %s%s",
1060                   dsymForUUID_exe_path.c_str(), copy_executable_arg,
1061                   uuid_str.c_str());
1062    LLDB_LOGF(log, "Calling %s with UUID %s to find dSYM: %s",
1063              dsymForUUID_exe_path.c_str(), uuid_str.c_str(),
1064              command.GetString().data());
1065  } else if (!file_path_str.empty()) {
1066    command.Printf("%s --ignoreNegativeCache %s%s",
1067                   dsymForUUID_exe_path.c_str(), copy_executable_arg,
1068                   file_path_str.c_str());
1069    LLDB_LOGF(log, "Calling %s with file %s to find dSYM: %s",
1070              dsymForUUID_exe_path.c_str(), file_path_str.c_str(),
1071              command.GetString().data());
1072  } else {
1073    return false;
1074  }
1075
1076  // Invoke dsymForUUID.
1077  int exit_status = -1;
1078  int signo = -1;
1079  std::string command_output;
1080  error = Host::RunShellCommand(
1081      command.GetData(),
1082      FileSpec(),      // current working directory
1083      &exit_status,    // Exit status
1084      &signo,          // Signal int *
1085      &command_output, // Command output
1086      std::chrono::seconds(
1087          640), // Large timeout to allow for long dsym download times
1088      false);   // Don't run in a shell (we don't need shell expansion)
1089
1090  if (error.Fail() || exit_status != 0 || command_output.empty()) {
1091    LLDB_LOGF(log, "'%s' failed (exit status: %d, error: '%s', output: '%s')",
1092              command.GetData(), exit_status, error.AsCString(),
1093              command_output.c_str());
1094    return false;
1095  }
1096
1097  CFCData data(
1098      CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)command_output.data(),
1099                                  command_output.size(), kCFAllocatorNull));
1100
1101  CFCReleaser<CFDictionaryRef> plist(
1102      (CFDictionaryRef)::CFPropertyListCreateWithData(
1103          NULL, data.get(), kCFPropertyListImmutable, NULL, NULL));
1104
1105  if (!plist.get()) {
1106    LLDB_LOGF(log, "'%s' failed: output is not a valid plist",
1107              command.GetData());
1108    return false;
1109  }
1110
1111  if (CFGetTypeID(plist.get()) != CFDictionaryGetTypeID()) {
1112    LLDB_LOGF(log, "'%s' failed: output plist is not a valid CFDictionary",
1113              command.GetData());
1114    return false;
1115  }
1116
1117  if (!uuid_str.empty()) {
1118    CFCString uuid_cfstr(uuid_str.c_str());
1119    CFDictionaryRef uuid_dict =
1120        (CFDictionaryRef)CFDictionaryGetValue(plist.get(), uuid_cfstr.get());
1121    return GetModuleSpecInfoFromUUIDDictionary(uuid_dict, module_spec, error,
1122                                               command.GetData());
1123  }
1124
1125  if (const CFIndex num_values = ::CFDictionaryGetCount(plist.get())) {
1126    std::vector<CFStringRef> keys(num_values, NULL);
1127    std::vector<CFDictionaryRef> values(num_values, NULL);
1128    ::CFDictionaryGetKeysAndValues(plist.get(), NULL,
1129                                   (const void **)&values[0]);
1130    if (num_values == 1) {
1131      return GetModuleSpecInfoFromUUIDDictionary(values[0], module_spec, error,
1132                                                 command.GetData());
1133    }
1134
1135    for (CFIndex i = 0; i < num_values; ++i) {
1136      ModuleSpec curr_module_spec;
1137      if (GetModuleSpecInfoFromUUIDDictionary(values[i], curr_module_spec,
1138                                              error, command.GetData())) {
1139        if (module_spec.GetArchitecture().IsCompatibleMatch(
1140                curr_module_spec.GetArchitecture())) {
1141          module_spec = curr_module_spec;
1142          return true;
1143        }
1144      }
1145    }
1146  }
1147
1148  return false;
1149}
1150