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 
52 using namespace lldb;
53 using namespace lldb_private;
54 
55 static CFURLRef (*g_dlsym_DBGCopyFullDSYMURLForUUID)(
56     CFUUIDRef uuid, CFURLRef exec_url) = nullptr;
57 static CFDictionaryRef (*g_dlsym_DBGCopyDSYMPropertyLists)(CFURLRef dsym_url) =
58     nullptr;
59 
60 LLDB_PLUGIN_DEFINE(SymbolLocatorDebugSymbols)
61 
62 SymbolLocatorDebugSymbols::SymbolLocatorDebugSymbols() : SymbolLocator() {}
63 
64 void SymbolLocatorDebugSymbols::Initialize() {
65   PluginManager::RegisterPlugin(
66       GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance,
67       LocateExecutableObjectFile, LocateExecutableSymbolFile,
68       DownloadObjectAndSymbolFile, FindSymbolFileInBundle);
69 }
70 
71 void SymbolLocatorDebugSymbols::Terminate() {
72   PluginManager::UnregisterPlugin(CreateInstance);
73 }
74 
75 llvm::StringRef SymbolLocatorDebugSymbols::GetPluginDescriptionStatic() {
76   return "DebugSymbols symbol locator.";
77 }
78 
79 SymbolLocator *SymbolLocatorDebugSymbols::CreateInstance() {
80   return new SymbolLocatorDebugSymbols();
81 }
82 
83 std::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 
329 std::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 
367 static 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.
394 static 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
471 static 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 
525 static 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 
768 std::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   Progress progress(
780       "Locating external symbol file",
781       module_spec.GetFileSpec().GetFilename().AsCString("<Unknown>"));
782 
783   FileSpec symbol_fspec;
784   ModuleSpec dsym_module_spec;
785   // First try and find the dSYM in the same directory as the executable or in
786   // an appropriate parent directory
787   if (!LocateDSYMInVincinityOfExecutable(module_spec, symbol_fspec)) {
788     // We failed to easily find the dSYM above, so use DebugSymbols
789     LocateMacOSXFilesUsingDebugSymbols(module_spec, dsym_module_spec);
790   } else {
791     dsym_module_spec.GetSymbolFileSpec() = symbol_fspec;
792   }
793 
794   return dsym_module_spec.GetSymbolFileSpec();
795 }
796 
797 static bool GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict,
798                                                 ModuleSpec &module_spec,
799                                                 Status &error,
800                                                 const std::string &command) {
801   Log *log = GetLog(LLDBLog::Host);
802   bool success = false;
803   if (uuid_dict != NULL && CFGetTypeID(uuid_dict) == CFDictionaryGetTypeID()) {
804     std::string str;
805     CFStringRef cf_str;
806     CFDictionaryRef cf_dict;
807 
808     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
809                                                CFSTR("DBGError"));
810     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
811       if (CFCString::FileSystemRepresentation(cf_str, str)) {
812         std::string errorstr = command;
813         errorstr += ":\n";
814         errorstr += str;
815         error.SetErrorString(errorstr);
816       }
817     }
818 
819     cf_str = (CFStringRef)CFDictionaryGetValue(
820         (CFDictionaryRef)uuid_dict, CFSTR("DBGSymbolRichExecutable"));
821     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
822       if (CFCString::FileSystemRepresentation(cf_str, str)) {
823         module_spec.GetFileSpec().SetFile(str.c_str(), FileSpec::Style::native);
824         FileSystem::Instance().Resolve(module_spec.GetFileSpec());
825         LLDB_LOGF(log,
826                   "From dsymForUUID plist: Symbol rich executable is at '%s'",
827                   str.c_str());
828       }
829     }
830 
831     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
832                                                CFSTR("DBGDSYMPath"));
833     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
834       if (CFCString::FileSystemRepresentation(cf_str, str)) {
835         module_spec.GetSymbolFileSpec().SetFile(str.c_str(),
836                                                 FileSpec::Style::native);
837         FileSystem::Instance().Resolve(module_spec.GetFileSpec());
838         success = true;
839         LLDB_LOGF(log, "From dsymForUUID plist: dSYM is at '%s'", str.c_str());
840       }
841     }
842 
843     std::string DBGBuildSourcePath;
844     std::string DBGSourcePath;
845 
846     // If DBGVersion 1 or DBGVersion missing, ignore DBGSourcePathRemapping.
847     // If DBGVersion 2, strip last two components of path remappings from
848     //                  entries to fix an issue with a specific set of
849     //                  DBGSourcePathRemapping entries that lldb worked
850     //                  with.
851     // If DBGVersion 3, trust & use the source path remappings as-is.
852     //
853     cf_dict = (CFDictionaryRef)CFDictionaryGetValue(
854         (CFDictionaryRef)uuid_dict, CFSTR("DBGSourcePathRemapping"));
855     if (cf_dict && CFGetTypeID(cf_dict) == CFDictionaryGetTypeID()) {
856       // If we see DBGVersion with a value of 2 or higher, this is a new style
857       // DBGSourcePathRemapping dictionary
858       bool new_style_source_remapping_dictionary = false;
859       bool do_truncate_remapping_names = false;
860       std::string original_DBGSourcePath_value = DBGSourcePath;
861       cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
862                                                  CFSTR("DBGVersion"));
863       if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
864         std::string version;
865         CFCString::FileSystemRepresentation(cf_str, version);
866         if (!version.empty() && isdigit(version[0])) {
867           int version_number = atoi(version.c_str());
868           if (version_number > 1) {
869             new_style_source_remapping_dictionary = true;
870           }
871           if (version_number == 2) {
872             do_truncate_remapping_names = true;
873           }
874         }
875       }
876 
877       CFIndex kv_pair_count = CFDictionaryGetCount((CFDictionaryRef)uuid_dict);
878       if (kv_pair_count > 0) {
879         CFStringRef *keys =
880             (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
881         CFStringRef *values =
882             (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
883         if (keys != nullptr && values != nullptr) {
884           CFDictionaryGetKeysAndValues((CFDictionaryRef)uuid_dict,
885                                        (const void **)keys,
886                                        (const void **)values);
887         }
888         for (CFIndex i = 0; i < kv_pair_count; i++) {
889           DBGBuildSourcePath.clear();
890           DBGSourcePath.clear();
891           if (keys[i] && CFGetTypeID(keys[i]) == CFStringGetTypeID()) {
892             CFCString::FileSystemRepresentation(keys[i], DBGBuildSourcePath);
893           }
894           if (values[i] && CFGetTypeID(values[i]) == CFStringGetTypeID()) {
895             CFCString::FileSystemRepresentation(values[i], DBGSourcePath);
896           }
897           if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
898             // In the "old style" DBGSourcePathRemapping dictionary, the
899             // DBGSourcePath values (the "values" half of key-value path pairs)
900             // were wrong.  Ignore them and use the universal DBGSourcePath
901             // string from earlier.
902             if (new_style_source_remapping_dictionary &&
903                 !original_DBGSourcePath_value.empty()) {
904               DBGSourcePath = original_DBGSourcePath_value;
905             }
906             if (DBGSourcePath[0] == '~') {
907               FileSpec resolved_source_path(DBGSourcePath.c_str());
908               FileSystem::Instance().Resolve(resolved_source_path);
909               DBGSourcePath = resolved_source_path.GetPath();
910             }
911             // With version 2 of DBGSourcePathRemapping, we can chop off the
912             // last two filename parts from the source remapping and get a more
913             // general source remapping that still works. Add this as another
914             // option in addition to the full source path remap.
915             module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
916                                                       DBGSourcePath, true);
917             if (do_truncate_remapping_names) {
918               FileSpec build_path(DBGBuildSourcePath.c_str());
919               FileSpec source_path(DBGSourcePath.c_str());
920               build_path.RemoveLastPathComponent();
921               build_path.RemoveLastPathComponent();
922               source_path.RemoveLastPathComponent();
923               source_path.RemoveLastPathComponent();
924               module_spec.GetSourceMappingList().Append(
925                   build_path.GetPath(), source_path.GetPath(), true);
926             }
927           }
928         }
929         if (keys)
930           free(keys);
931         if (values)
932           free(values);
933       }
934     }
935 
936     // If we have a DBGBuildSourcePath + DBGSourcePath pair, append them to the
937     // source remappings list.
938 
939     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
940                                                CFSTR("DBGBuildSourcePath"));
941     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
942       CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath);
943     }
944 
945     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
946                                                CFSTR("DBGSourcePath"));
947     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
948       CFCString::FileSystemRepresentation(cf_str, DBGSourcePath);
949     }
950 
951     if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
952       if (DBGSourcePath[0] == '~') {
953         FileSpec resolved_source_path(DBGSourcePath.c_str());
954         FileSystem::Instance().Resolve(resolved_source_path);
955         DBGSourcePath = resolved_source_path.GetPath();
956       }
957       module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
958                                                 DBGSourcePath, true);
959     }
960   }
961   return success;
962 }
963 
964 /// It's expensive to check for the DBGShellCommands defaults setting. Only do
965 /// it once per lldb run and cache the result.
966 static llvm::StringRef GetDbgShellCommand() {
967   static std::once_flag g_once_flag;
968   static std::string g_dbgshell_command;
969   std::call_once(g_once_flag, [&]() {
970     CFTypeRef defaults_setting = CFPreferencesCopyAppValue(
971         CFSTR("DBGShellCommands"), CFSTR("com.apple.DebugSymbols"));
972     if (defaults_setting &&
973         CFGetTypeID(defaults_setting) == CFStringGetTypeID()) {
974       char buffer[PATH_MAX];
975       if (CFStringGetCString((CFStringRef)defaults_setting, buffer,
976                              sizeof(buffer), kCFStringEncodingUTF8)) {
977         g_dbgshell_command = buffer;
978       }
979     }
980     if (defaults_setting) {
981       CFRelease(defaults_setting);
982     }
983   });
984   return g_dbgshell_command;
985 }
986 
987 /// Get the dsymForUUID executable and cache the result so we don't end up
988 /// stat'ing the binary over and over.
989 static FileSpec GetDsymForUUIDExecutable() {
990   // The LLDB_APPLE_DSYMFORUUID_EXECUTABLE environment variable is used by the
991   // test suite to override the dsymForUUID location. Because we must be able
992   // to change the value within a single test, don't bother caching it.
993   if (const char *dsymForUUID_env =
994           getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE")) {
995     FileSpec dsymForUUID_executable(dsymForUUID_env);
996     FileSystem::Instance().Resolve(dsymForUUID_executable);
997     if (FileSystem::Instance().Exists(dsymForUUID_executable))
998       return dsymForUUID_executable;
999   }
1000 
1001   static std::once_flag g_once_flag;
1002   static FileSpec g_dsymForUUID_executable;
1003   std::call_once(g_once_flag, [&]() {
1004     // Try the DBGShellCommand.
1005     llvm::StringRef dbgshell_command = GetDbgShellCommand();
1006     if (!dbgshell_command.empty()) {
1007       g_dsymForUUID_executable = FileSpec(dbgshell_command);
1008       FileSystem::Instance().Resolve(g_dsymForUUID_executable);
1009       if (FileSystem::Instance().Exists(g_dsymForUUID_executable))
1010         return;
1011     }
1012 
1013     // Try dsymForUUID in /usr/local/bin
1014     {
1015       g_dsymForUUID_executable = FileSpec("/usr/local/bin/dsymForUUID");
1016       if (FileSystem::Instance().Exists(g_dsymForUUID_executable))
1017         return;
1018     }
1019 
1020     // We couldn't find the dsymForUUID binary.
1021     g_dsymForUUID_executable = {};
1022   });
1023   return g_dsymForUUID_executable;
1024 }
1025 
1026 bool SymbolLocatorDebugSymbols::DownloadObjectAndSymbolFile(
1027     ModuleSpec &module_spec, Status &error, bool force_lookup,
1028     bool copy_executable) {
1029   const UUID *uuid_ptr = module_spec.GetUUIDPtr();
1030   const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr();
1031 
1032   // If \a dbgshell_command is set, the user has specified
1033   // forced symbol lookup via that command.  We'll get the
1034   // path back from GetDsymForUUIDExecutable() later.
1035   llvm::StringRef dbgshell_command = GetDbgShellCommand();
1036 
1037   // If forced lookup isn't set, by the user's \a dbgshell_command or
1038   // by the \a force_lookup argument, exit this method.
1039   if (!force_lookup && dbgshell_command.empty())
1040     return false;
1041 
1042   // We need a UUID or valid existing FileSpec.
1043   if (!uuid_ptr &&
1044       (!file_spec_ptr || !FileSystem::Instance().Exists(*file_spec_ptr)))
1045     return false;
1046 
1047   // We need a dsymForUUID binary or an equivalent executable/script.
1048   FileSpec dsymForUUID_exe_spec = GetDsymForUUIDExecutable();
1049   if (!dsymForUUID_exe_spec)
1050     return false;
1051 
1052   // Create the dsymForUUID command.
1053   const std::string dsymForUUID_exe_path = dsymForUUID_exe_spec.GetPath();
1054   const std::string uuid_str = uuid_ptr ? uuid_ptr->GetAsString() : "";
1055 
1056   std::string lookup_arg = uuid_str;
1057   if (lookup_arg.empty())
1058     lookup_arg = file_spec_ptr ? file_spec_ptr->GetPath() : "";
1059   if (lookup_arg.empty())
1060     return false;
1061 
1062   StreamString command;
1063   command << dsymForUUID_exe_path << " --ignoreNegativeCache ";
1064   if (copy_executable)
1065     command << "--copyExecutable ";
1066   command << lookup_arg;
1067 
1068   // Log and report progress.
1069   std::string lookup_desc;
1070   if (uuid_ptr && file_spec_ptr)
1071     lookup_desc =
1072         llvm::formatv("{0} ({1})", file_spec_ptr->GetFilename().GetString(),
1073                       uuid_ptr->GetAsString());
1074   else if (uuid_ptr)
1075     lookup_desc = uuid_ptr->GetAsString();
1076   else if (file_spec_ptr)
1077     lookup_desc = file_spec_ptr->GetFilename().GetString();
1078 
1079   Log *log = GetLog(LLDBLog::Host);
1080   LLDB_LOG(log, "Calling {0} for {1} to find dSYM: {2}", dsymForUUID_exe_path,
1081            lookup_desc, command.GetString());
1082 
1083   Progress progress("Downloading symbol file for", lookup_desc);
1084 
1085   // Invoke dsymForUUID.
1086   int exit_status = -1;
1087   int signo = -1;
1088   std::string command_output;
1089   error = Host::RunShellCommand(
1090       command.GetData(),
1091       FileSpec(),      // current working directory
1092       &exit_status,    // Exit status
1093       &signo,          // Signal int *
1094       &command_output, // Command output
1095       std::chrono::seconds(
1096           640), // Large timeout to allow for long dsym download times
1097       false);   // Don't run in a shell (we don't need shell expansion)
1098 
1099   if (error.Fail() || exit_status != 0 || command_output.empty()) {
1100     LLDB_LOGF(log, "'%s' failed (exit status: %d, error: '%s', output: '%s')",
1101               command.GetData(), exit_status, error.AsCString(),
1102               command_output.c_str());
1103     return false;
1104   }
1105 
1106   CFCData data(
1107       CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)command_output.data(),
1108                                   command_output.size(), kCFAllocatorNull));
1109 
1110   CFCReleaser<CFDictionaryRef> plist(
1111       (CFDictionaryRef)::CFPropertyListCreateWithData(
1112           NULL, data.get(), kCFPropertyListImmutable, NULL, NULL));
1113 
1114   if (!plist.get()) {
1115     LLDB_LOGF(log, "'%s' failed: output is not a valid plist",
1116               command.GetData());
1117     return false;
1118   }
1119 
1120   if (CFGetTypeID(plist.get()) != CFDictionaryGetTypeID()) {
1121     LLDB_LOGF(log, "'%s' failed: output plist is not a valid CFDictionary",
1122               command.GetData());
1123     return false;
1124   }
1125 
1126   if (!uuid_str.empty()) {
1127     CFCString uuid_cfstr(uuid_str.c_str());
1128     CFDictionaryRef uuid_dict =
1129         (CFDictionaryRef)CFDictionaryGetValue(plist.get(), uuid_cfstr.get());
1130     return GetModuleSpecInfoFromUUIDDictionary(uuid_dict, module_spec, error,
1131                                                command.GetData());
1132   }
1133 
1134   if (const CFIndex num_values = ::CFDictionaryGetCount(plist.get())) {
1135     std::vector<CFStringRef> keys(num_values, NULL);
1136     std::vector<CFDictionaryRef> values(num_values, NULL);
1137     ::CFDictionaryGetKeysAndValues(plist.get(), NULL,
1138                                    (const void **)&values[0]);
1139     if (num_values == 1) {
1140       return GetModuleSpecInfoFromUUIDDictionary(values[0], module_spec, error,
1141                                                  command.GetData());
1142     }
1143 
1144     for (CFIndex i = 0; i < num_values; ++i) {
1145       ModuleSpec curr_module_spec;
1146       if (GetModuleSpecInfoFromUUIDDictionary(values[i], curr_module_spec,
1147                                               error, command.GetData())) {
1148         if (module_spec.GetArchitecture().IsCompatibleMatch(
1149                 curr_module_spec.GetArchitecture())) {
1150           module_spec = curr_module_spec;
1151           return true;
1152         }
1153       }
1154     }
1155   }
1156 
1157   return false;
1158 }
1159