180814287SRaphael Isemann //===-- LocateSymbolFileMacOSX.cpp ----------------------------------------===//
280552918SZachary Turner //
380552918SZachary Turner // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
480552918SZachary Turner // See https://llvm.org/LICENSE.txt for license information.
580552918SZachary Turner // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
680552918SZachary Turner //
780552918SZachary Turner //===----------------------------------------------------------------------===//
880552918SZachary Turner 
980552918SZachary Turner #include "lldb/Symbol/LocateSymbolFile.h"
1080552918SZachary Turner 
1180552918SZachary Turner #include <dirent.h>
12868a394bSJason Molenda #include <dlfcn.h>
1380552918SZachary Turner #include <pwd.h>
1480552918SZachary Turner 
1580552918SZachary Turner #include <CoreFoundation/CoreFoundation.h>
1680552918SZachary Turner 
1780552918SZachary Turner #include "Host/macosx/cfcpp/CFCBundle.h"
1880552918SZachary Turner #include "Host/macosx/cfcpp/CFCData.h"
1980552918SZachary Turner #include "Host/macosx/cfcpp/CFCReleaser.h"
2080552918SZachary Turner #include "Host/macosx/cfcpp/CFCString.h"
21*d2f3b602SJason Molenda #include "lldb/Core/Module.h"
2280552918SZachary Turner #include "lldb/Core/ModuleList.h"
2380552918SZachary Turner #include "lldb/Core/ModuleSpec.h"
2480552918SZachary Turner #include "lldb/Host/Host.h"
25*d2f3b602SJason Molenda #include "lldb/Host/HostInfo.h"
2680552918SZachary Turner #include "lldb/Symbol/ObjectFile.h"
2780552918SZachary Turner #include "lldb/Utility/ArchSpec.h"
2880552918SZachary Turner #include "lldb/Utility/DataBuffer.h"
2980552918SZachary Turner #include "lldb/Utility/DataExtractor.h"
3080552918SZachary Turner #include "lldb/Utility/Endian.h"
31c34698a8SPavel Labath #include "lldb/Utility/LLDBLog.h"
3280552918SZachary Turner #include "lldb/Utility/Log.h"
3380552918SZachary Turner #include "lldb/Utility/StreamString.h"
3480552918SZachary Turner #include "lldb/Utility/Timer.h"
3580552918SZachary Turner #include "lldb/Utility/UUID.h"
3680552918SZachary Turner #include "mach/machine.h"
3780552918SZachary Turner 
38e0ea8d87SJonas Devlieghere #include "llvm/ADT/ScopeExit.h"
3980552918SZachary Turner #include "llvm/Support/FileSystem.h"
4080552918SZachary Turner 
4180552918SZachary Turner using namespace lldb;
4280552918SZachary Turner using namespace lldb_private;
4380552918SZachary Turner 
4480c600feSJonas Devlieghere static CFURLRef (*g_dlsym_DBGCopyFullDSYMURLForUUID)(
4580c600feSJonas Devlieghere     CFUUIDRef uuid, CFURLRef exec_url) = nullptr;
4680c600feSJonas Devlieghere static CFDictionaryRef (*g_dlsym_DBGCopyDSYMPropertyLists)(CFURLRef dsym_url) =
4780c600feSJonas Devlieghere     nullptr;
4880552918SZachary Turner 
LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec & module_spec,ModuleSpec & return_module_spec)4980552918SZachary Turner int LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec &module_spec,
5080552918SZachary Turner                                        ModuleSpec &return_module_spec) {
51a007a6d8SPavel Labath   Log *log = GetLog(LLDBLog::Host);
5280552918SZachary Turner   if (!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) {
5363e5fb76SJonas Devlieghere     LLDB_LOGF(log, "Spotlight lookup for .dSYM bundles is disabled.");
5480552918SZachary Turner     return 0;
5580552918SZachary Turner   }
5680552918SZachary Turner 
5780552918SZachary Turner   return_module_spec = module_spec;
5880552918SZachary Turner   return_module_spec.GetFileSpec().Clear();
5980552918SZachary Turner   return_module_spec.GetSymbolFileSpec().Clear();
6080552918SZachary Turner 
61a842950bSJonas Devlieghere   const UUID *uuid = module_spec.GetUUIDPtr();
62a842950bSJonas Devlieghere   const ArchSpec *arch = module_spec.GetArchitecturePtr();
63a842950bSJonas Devlieghere 
6480552918SZachary Turner   int items_found = 0;
6580552918SZachary Turner 
66868a394bSJason Molenda   if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
67868a394bSJason Molenda       g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
6880c600feSJonas Devlieghere     void *handle = dlopen(
6980c600feSJonas Devlieghere         "/System/Library/PrivateFrameworks/DebugSymbols.framework/DebugSymbols",
7080c600feSJonas Devlieghere         RTLD_LAZY | RTLD_LOCAL);
71868a394bSJason Molenda     if (handle) {
7280c600feSJonas Devlieghere       g_dlsym_DBGCopyFullDSYMURLForUUID =
7380c600feSJonas Devlieghere           (CFURLRef(*)(CFUUIDRef, CFURLRef))dlsym(handle,
7480c600feSJonas Devlieghere                                                   "DBGCopyFullDSYMURLForUUID");
7580c600feSJonas Devlieghere       g_dlsym_DBGCopyDSYMPropertyLists = (CFDictionaryRef(*)(CFURLRef))dlsym(
7680c600feSJonas Devlieghere           handle, "DBGCopyDSYMPropertyLists");
77868a394bSJason Molenda     }
78868a394bSJason Molenda   }
79868a394bSJason Molenda 
80868a394bSJason Molenda   if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr ||
81868a394bSJason Molenda       g_dlsym_DBGCopyDSYMPropertyLists == nullptr) {
82868a394bSJason Molenda     return items_found;
83868a394bSJason Molenda   }
8480552918SZachary Turner 
8580552918SZachary Turner   if (uuid && uuid->IsValid()) {
8680552918SZachary Turner     // Try and locate the dSYM file using DebugSymbols first
8780552918SZachary Turner     llvm::ArrayRef<uint8_t> module_uuid = uuid->GetBytes();
8880552918SZachary Turner     if (module_uuid.size() == 16) {
8980552918SZachary Turner       CFCReleaser<CFUUIDRef> module_uuid_ref(::CFUUIDCreateWithBytes(
9080552918SZachary Turner           NULL, module_uuid[0], module_uuid[1], module_uuid[2], module_uuid[3],
9180552918SZachary Turner           module_uuid[4], module_uuid[5], module_uuid[6], module_uuid[7],
9280552918SZachary Turner           module_uuid[8], module_uuid[9], module_uuid[10], module_uuid[11],
9380552918SZachary Turner           module_uuid[12], module_uuid[13], module_uuid[14], module_uuid[15]));
9480552918SZachary Turner 
9580552918SZachary Turner       if (module_uuid_ref.get()) {
9680552918SZachary Turner         CFCReleaser<CFURLRef> exec_url;
9780552918SZachary Turner         const FileSpec *exec_fspec = module_spec.GetFileSpecPtr();
9880552918SZachary Turner         if (exec_fspec) {
9980552918SZachary Turner           char exec_cf_path[PATH_MAX];
10080552918SZachary Turner           if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path)))
10180552918SZachary Turner             exec_url.reset(::CFURLCreateFromFileSystemRepresentation(
10280552918SZachary Turner                 NULL, (const UInt8 *)exec_cf_path, strlen(exec_cf_path),
10380552918SZachary Turner                 FALSE));
10480552918SZachary Turner         }
10580552918SZachary Turner 
10680c600feSJonas Devlieghere         CFCReleaser<CFURLRef> dsym_url(g_dlsym_DBGCopyFullDSYMURLForUUID(
10780c600feSJonas Devlieghere             module_uuid_ref.get(), exec_url.get()));
10880552918SZachary Turner         char path[PATH_MAX];
10980552918SZachary Turner 
11080552918SZachary Turner         if (dsym_url.get()) {
11180552918SZachary Turner           if (::CFURLGetFileSystemRepresentation(
11280552918SZachary Turner                   dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
11363e5fb76SJonas Devlieghere             LLDB_LOGF(log,
11463e5fb76SJonas Devlieghere                       "DebugSymbols framework returned dSYM path of %s for "
11580552918SZachary Turner                       "UUID %s -- looking for the dSYM",
11680552918SZachary Turner                       path, uuid->GetAsString().c_str());
11780552918SZachary Turner             FileSpec dsym_filespec(path);
11880552918SZachary Turner             if (path[0] == '~')
11980552918SZachary Turner               FileSystem::Instance().Resolve(dsym_filespec);
12080552918SZachary Turner 
12180552918SZachary Turner             if (FileSystem::Instance().IsDirectory(dsym_filespec)) {
12280552918SZachary Turner               dsym_filespec =
12380552918SZachary Turner                   Symbols::FindSymbolFileInBundle(dsym_filespec, uuid, arch);
12480552918SZachary Turner               ++items_found;
12580552918SZachary Turner             } else {
12680552918SZachary Turner               ++items_found;
12780552918SZachary Turner             }
12880552918SZachary Turner             return_module_spec.GetSymbolFileSpec() = dsym_filespec;
12980552918SZachary Turner           }
13080552918SZachary Turner 
13180552918SZachary Turner           bool success = false;
13280552918SZachary Turner           if (log) {
13380552918SZachary Turner             if (::CFURLGetFileSystemRepresentation(
13480552918SZachary Turner                     dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
13563e5fb76SJonas Devlieghere               LLDB_LOGF(log,
13663e5fb76SJonas Devlieghere                         "DebugSymbols framework returned dSYM path of %s for "
13780552918SZachary Turner                         "UUID %s -- looking for an exec file",
13880552918SZachary Turner                         path, uuid->GetAsString().c_str());
13980552918SZachary Turner             }
14080552918SZachary Turner           }
14180552918SZachary Turner 
14280552918SZachary Turner           CFCReleaser<CFDictionaryRef> dict(
143868a394bSJason Molenda               g_dlsym_DBGCopyDSYMPropertyLists(dsym_url.get()));
14480552918SZachary Turner           CFDictionaryRef uuid_dict = NULL;
14580552918SZachary Turner           if (dict.get()) {
14680552918SZachary Turner             CFCString uuid_cfstr(uuid->GetAsString().c_str());
14780552918SZachary Turner             uuid_dict = static_cast<CFDictionaryRef>(
14880552918SZachary Turner                 ::CFDictionaryGetValue(dict.get(), uuid_cfstr.get()));
14980552918SZachary Turner           }
150*d2f3b602SJason Molenda 
151*d2f3b602SJason Molenda           // Check to see if we have the file on the local filesystem.
152*d2f3b602SJason Molenda           if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) {
153*d2f3b602SJason Molenda             ModuleSpec exe_spec;
154*d2f3b602SJason Molenda             exe_spec.GetFileSpec() = module_spec.GetFileSpec();
155*d2f3b602SJason Molenda             exe_spec.GetUUID() = module_spec.GetUUID();
156*d2f3b602SJason Molenda             ModuleSP module_sp;
157*d2f3b602SJason Molenda             module_sp.reset(new Module(exe_spec));
158*d2f3b602SJason Molenda             if (module_sp && module_sp->GetObjectFile() &&
159*d2f3b602SJason Molenda                 module_sp->MatchesModuleSpec(exe_spec)) {
160*d2f3b602SJason Molenda               success = true;
161*d2f3b602SJason Molenda               return_module_spec.GetFileSpec() = module_spec.GetFileSpec();
162*d2f3b602SJason Molenda               LLDB_LOGF(log, "using original binary filepath %s for UUID %s",
163*d2f3b602SJason Molenda                         module_spec.GetFileSpec().GetPath().c_str(),
164*d2f3b602SJason Molenda                         uuid->GetAsString().c_str());
165*d2f3b602SJason Molenda               ++items_found;
166*d2f3b602SJason Molenda             }
167*d2f3b602SJason Molenda           }
168*d2f3b602SJason Molenda 
169*d2f3b602SJason Molenda           // Check if the requested image is in our shared cache.
170*d2f3b602SJason Molenda           if (!success) {
171*d2f3b602SJason Molenda             SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo(
172*d2f3b602SJason Molenda                 module_spec.GetFileSpec().GetPath());
173*d2f3b602SJason Molenda 
174*d2f3b602SJason Molenda             // If we found it and it has the correct UUID, let's proceed with
175*d2f3b602SJason Molenda             // creating a module from the memory contents.
176*d2f3b602SJason Molenda             if (image_info.uuid && (!module_spec.GetUUID() ||
177*d2f3b602SJason Molenda                                     module_spec.GetUUID() == image_info.uuid)) {
178*d2f3b602SJason Molenda               success = true;
179*d2f3b602SJason Molenda               return_module_spec.GetFileSpec() = module_spec.GetFileSpec();
180*d2f3b602SJason Molenda               LLDB_LOGF(log,
181*d2f3b602SJason Molenda                         "using binary from shared cache for filepath %s for "
182*d2f3b602SJason Molenda                         "UUID %s",
183*d2f3b602SJason Molenda                         module_spec.GetFileSpec().GetPath().c_str(),
184*d2f3b602SJason Molenda                         uuid->GetAsString().c_str());
185*d2f3b602SJason Molenda               ++items_found;
186*d2f3b602SJason Molenda             }
187*d2f3b602SJason Molenda           }
188*d2f3b602SJason Molenda 
189*d2f3b602SJason Molenda           // Use the DBGSymbolRichExecutable filepath if present
190*d2f3b602SJason Molenda           if (!success && uuid_dict) {
19180552918SZachary Turner             CFStringRef exec_cf_path =
19280552918SZachary Turner                 static_cast<CFStringRef>(::CFDictionaryGetValue(
19380552918SZachary Turner                     uuid_dict, CFSTR("DBGSymbolRichExecutable")));
19480552918SZachary Turner             if (exec_cf_path && ::CFStringGetFileSystemRepresentation(
19580552918SZachary Turner                                     exec_cf_path, path, sizeof(path))) {
19663e5fb76SJonas Devlieghere               LLDB_LOGF(log, "plist bundle has exec path of %s for UUID %s",
19780552918SZachary Turner                         path, uuid->GetAsString().c_str());
19880552918SZachary Turner               ++items_found;
19980552918SZachary Turner               FileSpec exec_filespec(path);
20080552918SZachary Turner               if (path[0] == '~')
20180552918SZachary Turner                 FileSystem::Instance().Resolve(exec_filespec);
20280552918SZachary Turner               if (FileSystem::Instance().Exists(exec_filespec)) {
20380552918SZachary Turner                 success = true;
20480552918SZachary Turner                 return_module_spec.GetFileSpec() = exec_filespec;
20580552918SZachary Turner               }
20680552918SZachary Turner             }
20780552918SZachary Turner           }
20880552918SZachary Turner 
209*d2f3b602SJason Molenda           // Look next to the dSYM for the binary file.
21080552918SZachary Turner           if (!success) {
21180552918SZachary Turner             if (::CFURLGetFileSystemRepresentation(
21280552918SZachary Turner                     dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) {
21380552918SZachary Turner               char *dsym_extension_pos = ::strstr(path, ".dSYM");
21480552918SZachary Turner               if (dsym_extension_pos) {
21580552918SZachary Turner                 *dsym_extension_pos = '\0';
21663e5fb76SJonas Devlieghere                 LLDB_LOGF(log,
21763e5fb76SJonas Devlieghere                           "Looking for executable binary next to dSYM "
21880552918SZachary Turner                           "bundle with name with name %s",
21980552918SZachary Turner                           path);
22080552918SZachary Turner                 FileSpec file_spec(path);
22180552918SZachary Turner                 FileSystem::Instance().Resolve(file_spec);
22280552918SZachary Turner                 ModuleSpecList module_specs;
22380552918SZachary Turner                 ModuleSpec matched_module_spec;
22480552918SZachary Turner                 using namespace llvm::sys::fs;
22580552918SZachary Turner                 switch (get_file_type(file_spec.GetPath())) {
22680552918SZachary Turner 
22780552918SZachary Turner                 case file_type::directory_file: // Bundle directory?
22880552918SZachary Turner                 {
22980552918SZachary Turner                   CFCBundle bundle(path);
23080552918SZachary Turner                   CFCReleaser<CFURLRef> bundle_exe_url(
23180552918SZachary Turner                       bundle.CopyExecutableURL());
23280552918SZachary Turner                   if (bundle_exe_url.get()) {
23380552918SZachary Turner                     if (::CFURLGetFileSystemRepresentation(bundle_exe_url.get(),
23480552918SZachary Turner                                                            true, (UInt8 *)path,
23580552918SZachary Turner                                                            sizeof(path) - 1)) {
23680552918SZachary Turner                       FileSpec bundle_exe_file_spec(path);
23780552918SZachary Turner                       FileSystem::Instance().Resolve(bundle_exe_file_spec);
23880552918SZachary Turner                       if (ObjectFile::GetModuleSpecifications(
23980552918SZachary Turner                               bundle_exe_file_spec, 0, 0, module_specs) &&
24080552918SZachary Turner                           module_specs.FindMatchingModuleSpec(
24180552918SZachary Turner                               module_spec, matched_module_spec))
24280552918SZachary Turner 
24380552918SZachary Turner                       {
24480552918SZachary Turner                         ++items_found;
24580552918SZachary Turner                         return_module_spec.GetFileSpec() = bundle_exe_file_spec;
24663e5fb76SJonas Devlieghere                         LLDB_LOGF(log,
24763e5fb76SJonas Devlieghere                                   "Executable binary %s next to dSYM is "
24880552918SZachary Turner                                   "compatible; using",
24980552918SZachary Turner                                   path);
25080552918SZachary Turner                       }
25180552918SZachary Turner                     }
25280552918SZachary Turner                   }
25380552918SZachary Turner                 } break;
25480552918SZachary Turner 
25580552918SZachary Turner                 case file_type::fifo_file:      // Forget pipes
25680552918SZachary Turner                 case file_type::socket_file:    // We can't process socket files
25780552918SZachary Turner                 case file_type::file_not_found: // File doesn't exist...
25880552918SZachary Turner                 case file_type::status_error:
25980552918SZachary Turner                   break;
26080552918SZachary Turner 
26180552918SZachary Turner                 case file_type::type_unknown:
26280552918SZachary Turner                 case file_type::regular_file:
26380552918SZachary Turner                 case file_type::symlink_file:
26480552918SZachary Turner                 case file_type::block_file:
26580552918SZachary Turner                 case file_type::character_file:
26680552918SZachary Turner                   if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0,
26780552918SZachary Turner                                                           module_specs) &&
26880552918SZachary Turner                       module_specs.FindMatchingModuleSpec(module_spec,
26980552918SZachary Turner                                                           matched_module_spec))
27080552918SZachary Turner 
27180552918SZachary Turner                   {
27280552918SZachary Turner                     ++items_found;
27380552918SZachary Turner                     return_module_spec.GetFileSpec() = file_spec;
27463e5fb76SJonas Devlieghere                     LLDB_LOGF(log,
27563e5fb76SJonas Devlieghere                               "Executable binary %s next to dSYM is "
27680552918SZachary Turner                               "compatible; using",
27780552918SZachary Turner                               path);
27880552918SZachary Turner                   }
27980552918SZachary Turner                   break;
28080552918SZachary Turner                 }
28180552918SZachary Turner               }
28280552918SZachary Turner             }
28380552918SZachary Turner           }
28480552918SZachary Turner         }
28580552918SZachary Turner       }
28680552918SZachary Turner     }
28780552918SZachary Turner   }
28880552918SZachary Turner 
28980552918SZachary Turner   return items_found;
29080552918SZachary Turner }
29180552918SZachary Turner 
FindSymbolFileInBundle(const FileSpec & dsym_bundle_fspec,const lldb_private::UUID * uuid,const ArchSpec * arch)29280552918SZachary Turner FileSpec Symbols::FindSymbolFileInBundle(const FileSpec &dsym_bundle_fspec,
29380552918SZachary Turner                                          const lldb_private::UUID *uuid,
29480552918SZachary Turner                                          const ArchSpec *arch) {
29563bfb3a8SJonas Devlieghere   std::string dsym_bundle_path = dsym_bundle_fspec.GetPath();
29663bfb3a8SJonas Devlieghere   llvm::SmallString<128> buffer(dsym_bundle_path);
29763bfb3a8SJonas Devlieghere   llvm::sys::path::append(buffer, "Contents", "Resources", "DWARF");
29880552918SZachary Turner 
29963bfb3a8SJonas Devlieghere   std::error_code EC;
30063bfb3a8SJonas Devlieghere   llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> vfs =
30163bfb3a8SJonas Devlieghere       FileSystem::Instance().GetVirtualFileSystem();
30263bfb3a8SJonas Devlieghere   llvm::vfs::recursive_directory_iterator Iter(*vfs, buffer.str(), EC);
30363bfb3a8SJonas Devlieghere   llvm::vfs::recursive_directory_iterator End;
30463bfb3a8SJonas Devlieghere   for (; Iter != End && !EC; Iter.increment(EC)) {
30563bfb3a8SJonas Devlieghere     llvm::ErrorOr<llvm::vfs::Status> Status = vfs->status(Iter->path());
30663bfb3a8SJonas Devlieghere     if (Status->isDirectory())
30780552918SZachary Turner       continue;
30880552918SZachary Turner 
30963bfb3a8SJonas Devlieghere     FileSpec dsym_fspec(Iter->path());
31080552918SZachary Turner     ModuleSpecList module_specs;
31180552918SZachary Turner     if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs)) {
31280552918SZachary Turner       ModuleSpec spec;
31380552918SZachary Turner       for (size_t i = 0; i < module_specs.GetSize(); ++i) {
31480552918SZachary Turner         bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec);
31563bfb3a8SJonas Devlieghere         assert(got_spec); // The call has side-effects so can't be inlined.
31680552918SZachary Turner         UNUSED_IF_ASSERT_DISABLED(got_spec);
31763bfb3a8SJonas Devlieghere         if ((uuid == nullptr ||
31880552918SZachary Turner              (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) &&
31963bfb3a8SJonas Devlieghere             (arch == nullptr ||
32080552918SZachary Turner              (spec.GetArchitecturePtr() &&
32180552918SZachary Turner               spec.GetArchitecture().IsCompatibleMatch(*arch)))) {
32280552918SZachary Turner           return dsym_fspec;
32380552918SZachary Turner         }
32480552918SZachary Turner       }
32580552918SZachary Turner     }
32680552918SZachary Turner   }
32780552918SZachary Turner 
32880552918SZachary Turner   return {};
32980552918SZachary Turner }
33080552918SZachary Turner 
GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict,ModuleSpec & module_spec,Status & error)33180552918SZachary Turner static bool GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict,
332af91446aSJonas Devlieghere                                                 ModuleSpec &module_spec,
333af91446aSJonas Devlieghere                                                 Status &error) {
334a007a6d8SPavel Labath   Log *log = GetLog(LLDBLog::Host);
33580552918SZachary Turner   bool success = false;
33680552918SZachary Turner   if (uuid_dict != NULL && CFGetTypeID(uuid_dict) == CFDictionaryGetTypeID()) {
33780552918SZachary Turner     std::string str;
33880552918SZachary Turner     CFStringRef cf_str;
33980552918SZachary Turner     CFDictionaryRef cf_dict;
34080552918SZachary Turner 
341af91446aSJonas Devlieghere     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
342af91446aSJonas Devlieghere                                                CFSTR("DBGError"));
343af91446aSJonas Devlieghere     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
344af91446aSJonas Devlieghere       if (CFCString::FileSystemRepresentation(cf_str, str)) {
345af91446aSJonas Devlieghere         error.SetErrorString(str);
346af91446aSJonas Devlieghere       }
347af91446aSJonas Devlieghere     }
348af91446aSJonas Devlieghere 
34980552918SZachary Turner     cf_str = (CFStringRef)CFDictionaryGetValue(
35080552918SZachary Turner         (CFDictionaryRef)uuid_dict, CFSTR("DBGSymbolRichExecutable"));
35180552918SZachary Turner     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
35280552918SZachary Turner       if (CFCString::FileSystemRepresentation(cf_str, str)) {
35380552918SZachary Turner         module_spec.GetFileSpec().SetFile(str.c_str(), FileSpec::Style::native);
35480552918SZachary Turner         FileSystem::Instance().Resolve(module_spec.GetFileSpec());
35563e5fb76SJonas Devlieghere         LLDB_LOGF(log,
35680552918SZachary Turner                   "From dsymForUUID plist: Symbol rich executable is at '%s'",
35780552918SZachary Turner                   str.c_str());
35880552918SZachary Turner       }
35980552918SZachary Turner     }
36080552918SZachary Turner 
36180552918SZachary Turner     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
36280552918SZachary Turner                                                CFSTR("DBGDSYMPath"));
36380552918SZachary Turner     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
36480552918SZachary Turner       if (CFCString::FileSystemRepresentation(cf_str, str)) {
36580552918SZachary Turner         module_spec.GetSymbolFileSpec().SetFile(str.c_str(),
36680552918SZachary Turner                                                 FileSpec::Style::native);
36780552918SZachary Turner         FileSystem::Instance().Resolve(module_spec.GetFileSpec());
36880552918SZachary Turner         success = true;
369*d2f3b602SJason Molenda         LLDB_LOGF(log, "From dsymForUUID plist: dSYM is at '%s'", str.c_str());
37080552918SZachary Turner       }
37180552918SZachary Turner     }
37280552918SZachary Turner 
37380552918SZachary Turner     std::string DBGBuildSourcePath;
37480552918SZachary Turner     std::string DBGSourcePath;
37580552918SZachary Turner 
37680552918SZachary Turner     // If DBGVersion 1 or DBGVersion missing, ignore DBGSourcePathRemapping.
37780552918SZachary Turner     // If DBGVersion 2, strip last two components of path remappings from
37880552918SZachary Turner     //                  entries to fix an issue with a specific set of
37980552918SZachary Turner     //                  DBGSourcePathRemapping entries that lldb worked
38080552918SZachary Turner     //                  with.
38180552918SZachary Turner     // If DBGVersion 3, trust & use the source path remappings as-is.
38280552918SZachary Turner     //
38380552918SZachary Turner     cf_dict = (CFDictionaryRef)CFDictionaryGetValue(
38480552918SZachary Turner         (CFDictionaryRef)uuid_dict, CFSTR("DBGSourcePathRemapping"));
38580552918SZachary Turner     if (cf_dict && CFGetTypeID(cf_dict) == CFDictionaryGetTypeID()) {
38680552918SZachary Turner       // If we see DBGVersion with a value of 2 or higher, this is a new style
38780552918SZachary Turner       // DBGSourcePathRemapping dictionary
38880552918SZachary Turner       bool new_style_source_remapping_dictionary = false;
38980552918SZachary Turner       bool do_truncate_remapping_names = false;
39080552918SZachary Turner       std::string original_DBGSourcePath_value = DBGSourcePath;
39180552918SZachary Turner       cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
39280552918SZachary Turner                                                  CFSTR("DBGVersion"));
39380552918SZachary Turner       if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
39480552918SZachary Turner         std::string version;
39580552918SZachary Turner         CFCString::FileSystemRepresentation(cf_str, version);
39680552918SZachary Turner         if (!version.empty() && isdigit(version[0])) {
39780552918SZachary Turner           int version_number = atoi(version.c_str());
39880552918SZachary Turner           if (version_number > 1) {
39980552918SZachary Turner             new_style_source_remapping_dictionary = true;
40080552918SZachary Turner           }
40180552918SZachary Turner           if (version_number == 2) {
40280552918SZachary Turner             do_truncate_remapping_names = true;
40380552918SZachary Turner           }
40480552918SZachary Turner         }
40580552918SZachary Turner       }
40680552918SZachary Turner 
40780552918SZachary Turner       CFIndex kv_pair_count = CFDictionaryGetCount((CFDictionaryRef)uuid_dict);
40880552918SZachary Turner       if (kv_pair_count > 0) {
40980552918SZachary Turner         CFStringRef *keys =
41080552918SZachary Turner             (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
41180552918SZachary Turner         CFStringRef *values =
41280552918SZachary Turner             (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef));
41380552918SZachary Turner         if (keys != nullptr && values != nullptr) {
41480552918SZachary Turner           CFDictionaryGetKeysAndValues((CFDictionaryRef)uuid_dict,
41580552918SZachary Turner                                        (const void **)keys,
41680552918SZachary Turner                                        (const void **)values);
41780552918SZachary Turner         }
41880552918SZachary Turner         for (CFIndex i = 0; i < kv_pair_count; i++) {
41980552918SZachary Turner           DBGBuildSourcePath.clear();
42080552918SZachary Turner           DBGSourcePath.clear();
42180552918SZachary Turner           if (keys[i] && CFGetTypeID(keys[i]) == CFStringGetTypeID()) {
42280552918SZachary Turner             CFCString::FileSystemRepresentation(keys[i], DBGBuildSourcePath);
42380552918SZachary Turner           }
42480552918SZachary Turner           if (values[i] && CFGetTypeID(values[i]) == CFStringGetTypeID()) {
42580552918SZachary Turner             CFCString::FileSystemRepresentation(values[i], DBGSourcePath);
42680552918SZachary Turner           }
42780552918SZachary Turner           if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
42880552918SZachary Turner             // In the "old style" DBGSourcePathRemapping dictionary, the
42980552918SZachary Turner             // DBGSourcePath values (the "values" half of key-value path pairs)
43080552918SZachary Turner             // were wrong.  Ignore them and use the universal DBGSourcePath
43180552918SZachary Turner             // string from earlier.
43280552918SZachary Turner             if (new_style_source_remapping_dictionary &&
43380552918SZachary Turner                 !original_DBGSourcePath_value.empty()) {
43480552918SZachary Turner               DBGSourcePath = original_DBGSourcePath_value;
43580552918SZachary Turner             }
43680552918SZachary Turner             if (DBGSourcePath[0] == '~') {
43780552918SZachary Turner               FileSpec resolved_source_path(DBGSourcePath.c_str());
43880552918SZachary Turner               FileSystem::Instance().Resolve(resolved_source_path);
43980552918SZachary Turner               DBGSourcePath = resolved_source_path.GetPath();
44080552918SZachary Turner             }
44180552918SZachary Turner             // With version 2 of DBGSourcePathRemapping, we can chop off the
44280552918SZachary Turner             // last two filename parts from the source remapping and get a more
44380552918SZachary Turner             // general source remapping that still works. Add this as another
44480552918SZachary Turner             // option in addition to the full source path remap.
44548677f58SBenjamin Kramer             module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
44648677f58SBenjamin Kramer                                                       DBGSourcePath, true);
44780552918SZachary Turner             if (do_truncate_remapping_names) {
44880552918SZachary Turner               FileSpec build_path(DBGBuildSourcePath.c_str());
44980552918SZachary Turner               FileSpec source_path(DBGSourcePath.c_str());
45080552918SZachary Turner               build_path.RemoveLastPathComponent();
45180552918SZachary Turner               build_path.RemoveLastPathComponent();
45280552918SZachary Turner               source_path.RemoveLastPathComponent();
45380552918SZachary Turner               source_path.RemoveLastPathComponent();
45480552918SZachary Turner               module_spec.GetSourceMappingList().Append(
45548677f58SBenjamin Kramer                   build_path.GetPath(), source_path.GetPath(), true);
45680552918SZachary Turner             }
45780552918SZachary Turner           }
45880552918SZachary Turner         }
45980552918SZachary Turner         if (keys)
46080552918SZachary Turner           free(keys);
46180552918SZachary Turner         if (values)
46280552918SZachary Turner           free(values);
46380552918SZachary Turner       }
46480552918SZachary Turner     }
46580552918SZachary Turner 
46680552918SZachary Turner     // If we have a DBGBuildSourcePath + DBGSourcePath pair, append them to the
46780552918SZachary Turner     // source remappings list.
46880552918SZachary Turner 
46980552918SZachary Turner     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
47080552918SZachary Turner                                                CFSTR("DBGBuildSourcePath"));
47180552918SZachary Turner     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
47280552918SZachary Turner       CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath);
47380552918SZachary Turner     }
47480552918SZachary Turner 
47580552918SZachary Turner     cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict,
47680552918SZachary Turner                                                CFSTR("DBGSourcePath"));
47780552918SZachary Turner     if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) {
47880552918SZachary Turner       CFCString::FileSystemRepresentation(cf_str, DBGSourcePath);
47980552918SZachary Turner     }
48080552918SZachary Turner 
48180552918SZachary Turner     if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) {
48280552918SZachary Turner       if (DBGSourcePath[0] == '~') {
48380552918SZachary Turner         FileSpec resolved_source_path(DBGSourcePath.c_str());
48480552918SZachary Turner         FileSystem::Instance().Resolve(resolved_source_path);
48580552918SZachary Turner         DBGSourcePath = resolved_source_path.GetPath();
48680552918SZachary Turner       }
48748677f58SBenjamin Kramer       module_spec.GetSourceMappingList().Append(DBGBuildSourcePath,
48848677f58SBenjamin Kramer                                                 DBGSourcePath, true);
48980552918SZachary Turner     }
49080552918SZachary Turner   }
49180552918SZachary Turner   return success;
49280552918SZachary Turner }
49380552918SZachary Turner 
DownloadObjectAndSymbolFile(ModuleSpec & module_spec,Status & error,bool force_lookup)49480552918SZachary Turner bool Symbols::DownloadObjectAndSymbolFile(ModuleSpec &module_spec,
495af91446aSJonas Devlieghere                                           Status &error, bool force_lookup) {
49680552918SZachary Turner   bool success = false;
49780552918SZachary Turner   const UUID *uuid_ptr = module_spec.GetUUIDPtr();
49880552918SZachary Turner   const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr();
49980552918SZachary Turner 
50080552918SZachary Turner   // It's expensive to check for the DBGShellCommands defaults setting, only do
50180552918SZachary Turner   // it once per lldb run and cache the result.
50280552918SZachary Turner   static bool g_have_checked_for_dbgshell_command = false;
50380552918SZachary Turner   static const char *g_dbgshell_command = NULL;
50480552918SZachary Turner   if (!g_have_checked_for_dbgshell_command) {
50580552918SZachary Turner     g_have_checked_for_dbgshell_command = true;
50680552918SZachary Turner     CFTypeRef defaults_setting = CFPreferencesCopyAppValue(
50780552918SZachary Turner         CFSTR("DBGShellCommands"), CFSTR("com.apple.DebugSymbols"));
50880552918SZachary Turner     if (defaults_setting &&
50980552918SZachary Turner         CFGetTypeID(defaults_setting) == CFStringGetTypeID()) {
51080552918SZachary Turner       char cstr_buf[PATH_MAX];
51180552918SZachary Turner       if (CFStringGetCString((CFStringRef)defaults_setting, cstr_buf,
51280552918SZachary Turner                              sizeof(cstr_buf), kCFStringEncodingUTF8)) {
51380552918SZachary Turner         g_dbgshell_command =
51480552918SZachary Turner             strdup(cstr_buf); // this malloc'ed memory will never be freed
51580552918SZachary Turner       }
51680552918SZachary Turner     }
51780552918SZachary Turner     if (defaults_setting) {
51880552918SZachary Turner       CFRelease(defaults_setting);
51980552918SZachary Turner     }
52080552918SZachary Turner   }
52180552918SZachary Turner 
52280552918SZachary Turner   // When g_dbgshell_command is NULL, the user has not enabled the use of an
52380552918SZachary Turner   // external program to find the symbols, don't run it for them.
52480552918SZachary Turner   if (!force_lookup && g_dbgshell_command == NULL) {
52580552918SZachary Turner     return false;
52680552918SZachary Turner   }
52780552918SZachary Turner 
52880552918SZachary Turner   if (uuid_ptr ||
52980552918SZachary Turner       (file_spec_ptr && FileSystem::Instance().Exists(*file_spec_ptr))) {
53080552918SZachary Turner     static bool g_located_dsym_for_uuid_exe = false;
53180552918SZachary Turner     static bool g_dsym_for_uuid_exe_exists = false;
53280552918SZachary Turner     static char g_dsym_for_uuid_exe_path[PATH_MAX];
53380552918SZachary Turner     if (!g_located_dsym_for_uuid_exe) {
53480552918SZachary Turner       g_located_dsym_for_uuid_exe = true;
53580552918SZachary Turner       const char *dsym_for_uuid_exe_path_cstr =
53680552918SZachary Turner           getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE");
53780552918SZachary Turner       FileSpec dsym_for_uuid_exe_spec;
53880552918SZachary Turner       if (dsym_for_uuid_exe_path_cstr) {
53980552918SZachary Turner         dsym_for_uuid_exe_spec.SetFile(dsym_for_uuid_exe_path_cstr,
54080552918SZachary Turner                                        FileSpec::Style::native);
54180552918SZachary Turner         FileSystem::Instance().Resolve(dsym_for_uuid_exe_spec);
54280552918SZachary Turner         g_dsym_for_uuid_exe_exists =
54380552918SZachary Turner             FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
54480552918SZachary Turner       }
54580552918SZachary Turner 
54680552918SZachary Turner       if (!g_dsym_for_uuid_exe_exists) {
54780552918SZachary Turner         dsym_for_uuid_exe_spec.SetFile("/usr/local/bin/dsymForUUID",
54880552918SZachary Turner                                        FileSpec::Style::native);
54980552918SZachary Turner         g_dsym_for_uuid_exe_exists =
55080552918SZachary Turner             FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
55180552918SZachary Turner         if (!g_dsym_for_uuid_exe_exists) {
55280552918SZachary Turner           long bufsize;
55380552918SZachary Turner           if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) != -1) {
55480552918SZachary Turner             char buffer[bufsize];
55580552918SZachary Turner             struct passwd pwd;
55680552918SZachary Turner             struct passwd *tilde_rc = NULL;
55780552918SZachary Turner             // we are a library so we need to use the reentrant version of
55880552918SZachary Turner             // getpwnam()
55980552918SZachary Turner             if (getpwnam_r("rc", &pwd, buffer, bufsize, &tilde_rc) == 0 &&
56080552918SZachary Turner                 tilde_rc && tilde_rc->pw_dir) {
56180552918SZachary Turner               std::string dsymforuuid_path(tilde_rc->pw_dir);
56280552918SZachary Turner               dsymforuuid_path += "/bin/dsymForUUID";
56380552918SZachary Turner               dsym_for_uuid_exe_spec.SetFile(dsymforuuid_path.c_str(),
56480552918SZachary Turner                                              FileSpec::Style::native);
56580552918SZachary Turner               g_dsym_for_uuid_exe_exists =
56680552918SZachary Turner                   FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
56780552918SZachary Turner             }
56880552918SZachary Turner           }
56980552918SZachary Turner         }
57080552918SZachary Turner       }
57180552918SZachary Turner       if (!g_dsym_for_uuid_exe_exists && g_dbgshell_command != NULL) {
57280552918SZachary Turner         dsym_for_uuid_exe_spec.SetFile(g_dbgshell_command,
57380552918SZachary Turner                                        FileSpec::Style::native);
57480552918SZachary Turner         FileSystem::Instance().Resolve(dsym_for_uuid_exe_spec);
57580552918SZachary Turner         g_dsym_for_uuid_exe_exists =
57680552918SZachary Turner             FileSystem::Instance().Exists(dsym_for_uuid_exe_spec);
57780552918SZachary Turner       }
57880552918SZachary Turner 
57980552918SZachary Turner       if (g_dsym_for_uuid_exe_exists)
58080552918SZachary Turner         dsym_for_uuid_exe_spec.GetPath(g_dsym_for_uuid_exe_path,
58180552918SZachary Turner                                        sizeof(g_dsym_for_uuid_exe_path));
58280552918SZachary Turner     }
58380552918SZachary Turner     if (g_dsym_for_uuid_exe_exists) {
58480552918SZachary Turner       std::string uuid_str;
58580552918SZachary Turner       char file_path[PATH_MAX];
58680552918SZachary Turner       file_path[0] = '\0';
58780552918SZachary Turner 
58880552918SZachary Turner       if (uuid_ptr)
58980552918SZachary Turner         uuid_str = uuid_ptr->GetAsString();
59080552918SZachary Turner 
59180552918SZachary Turner       if (file_spec_ptr)
59280552918SZachary Turner         file_spec_ptr->GetPath(file_path, sizeof(file_path));
59380552918SZachary Turner 
59480552918SZachary Turner       StreamString command;
59580552918SZachary Turner       if (!uuid_str.empty())
59680552918SZachary Turner         command.Printf("%s --ignoreNegativeCache --copyExecutable %s",
59780552918SZachary Turner                        g_dsym_for_uuid_exe_path, uuid_str.c_str());
59880552918SZachary Turner       else if (file_path[0] != '\0')
59980552918SZachary Turner         command.Printf("%s --ignoreNegativeCache --copyExecutable %s",
60080552918SZachary Turner                        g_dsym_for_uuid_exe_path, file_path);
60180552918SZachary Turner 
60280552918SZachary Turner       if (!command.GetString().empty()) {
603a007a6d8SPavel Labath         Log *log = GetLog(LLDBLog::Host);
60480552918SZachary Turner         int exit_status = -1;
60580552918SZachary Turner         int signo = -1;
60680552918SZachary Turner         std::string command_output;
60780552918SZachary Turner         if (log) {
60880552918SZachary Turner           if (!uuid_str.empty())
60963e5fb76SJonas Devlieghere             LLDB_LOGF(log, "Calling %s with UUID %s to find dSYM",
61080552918SZachary Turner                       g_dsym_for_uuid_exe_path, uuid_str.c_str());
61180552918SZachary Turner           else if (file_path[0] != '\0')
61263e5fb76SJonas Devlieghere             LLDB_LOGF(log, "Calling %s with file %s to find dSYM",
61380552918SZachary Turner                       g_dsym_for_uuid_exe_path, file_path);
61480552918SZachary Turner         }
615af91446aSJonas Devlieghere         error = Host::RunShellCommand(
61680552918SZachary Turner             command.GetData(),
6175e713563SRaphael Isemann             FileSpec(),      // current working directory
61880552918SZachary Turner             &exit_status,    // Exit status
61980552918SZachary Turner             &signo,          // Signal int *
62080552918SZachary Turner             &command_output, // Command output
62180552918SZachary Turner             std::chrono::seconds(
622af5504edSJason Molenda                 640), // Large timeout to allow for long dsym download times
62380552918SZachary Turner             false);   // Don't run in a shell (we don't need shell expansion)
62480552918SZachary Turner         if (error.Success() && exit_status == 0 && !command_output.empty()) {
62580552918SZachary Turner           CFCData data(CFDataCreateWithBytesNoCopy(
62680552918SZachary Turner               NULL, (const UInt8 *)command_output.data(), command_output.size(),
62780552918SZachary Turner               kCFAllocatorNull));
62880552918SZachary Turner 
62980552918SZachary Turner           CFCReleaser<CFDictionaryRef> plist(
63080552918SZachary Turner               (CFDictionaryRef)::CFPropertyListCreateFromXMLData(
63180552918SZachary Turner                   NULL, data.get(), kCFPropertyListImmutable, NULL));
63280552918SZachary Turner 
63380552918SZachary Turner           if (plist.get() &&
63480552918SZachary Turner               CFGetTypeID(plist.get()) == CFDictionaryGetTypeID()) {
63580552918SZachary Turner             if (!uuid_str.empty()) {
63680552918SZachary Turner               CFCString uuid_cfstr(uuid_str.c_str());
63780552918SZachary Turner               CFDictionaryRef uuid_dict = (CFDictionaryRef)CFDictionaryGetValue(
63880552918SZachary Turner                   plist.get(), uuid_cfstr.get());
639af91446aSJonas Devlieghere               success = GetModuleSpecInfoFromUUIDDictionary(uuid_dict,
640af91446aSJonas Devlieghere                                                             module_spec, error);
64180552918SZachary Turner             } else {
64280552918SZachary Turner               const CFIndex num_values = ::CFDictionaryGetCount(plist.get());
64380552918SZachary Turner               if (num_values > 0) {
64480552918SZachary Turner                 std::vector<CFStringRef> keys(num_values, NULL);
64580552918SZachary Turner                 std::vector<CFDictionaryRef> values(num_values, NULL);
64680552918SZachary Turner                 ::CFDictionaryGetKeysAndValues(plist.get(), NULL,
64780552918SZachary Turner                                                (const void **)&values[0]);
64880552918SZachary Turner                 if (num_values == 1) {
649af91446aSJonas Devlieghere                   success = GetModuleSpecInfoFromUUIDDictionary(
650af91446aSJonas Devlieghere                       values[0], module_spec, error);
651a842950bSJonas Devlieghere                   return success;
65280552918SZachary Turner                 } else {
65380552918SZachary Turner                   for (CFIndex i = 0; i < num_values; ++i) {
65480552918SZachary Turner                     ModuleSpec curr_module_spec;
655af91446aSJonas Devlieghere                     if (GetModuleSpecInfoFromUUIDDictionary(
656af91446aSJonas Devlieghere                             values[i], curr_module_spec, error)) {
65780552918SZachary Turner                       if (module_spec.GetArchitecture().IsCompatibleMatch(
65880552918SZachary Turner                               curr_module_spec.GetArchitecture())) {
65980552918SZachary Turner                         module_spec = curr_module_spec;
66080552918SZachary Turner                         return true;
66180552918SZachary Turner                       }
66280552918SZachary Turner                     }
66380552918SZachary Turner                   }
66480552918SZachary Turner                 }
66580552918SZachary Turner               }
66680552918SZachary Turner             }
66780552918SZachary Turner           }
66880552918SZachary Turner         } else {
66980552918SZachary Turner           if (!uuid_str.empty())
67063e5fb76SJonas Devlieghere             LLDB_LOGF(log, "Called %s on %s, no matches",
67180552918SZachary Turner                       g_dsym_for_uuid_exe_path, uuid_str.c_str());
67280552918SZachary Turner           else if (file_path[0] != '\0')
67363e5fb76SJonas Devlieghere             LLDB_LOGF(log, "Called %s on %s, no matches",
67480552918SZachary Turner                       g_dsym_for_uuid_exe_path, file_path);
67580552918SZachary Turner         }
67680552918SZachary Turner       }
67780552918SZachary Turner     }
67880552918SZachary Turner   }
67980552918SZachary Turner   return success;
68080552918SZachary Turner }
681