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 | name: GetPluginNameStatic(), description: GetPluginDescriptionStatic(), create_callback: CreateInstance, |
67 | locate_executable_object_file: LocateExecutableObjectFile, locate_executable_symbol_file: LocateExecutableSymbolFile, |
68 | download_object_symbol_file: DownloadObjectAndSymbolFile, find_symbol_file_in_bundle: FindSymbolFileInBundle); |
69 | } |
70 | |
71 | void SymbolLocatorDebugSymbols::Terminate() { |
72 | PluginManager::UnregisterPlugin(create_callback: 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(mask: 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 | file: "/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(file_spec&: dsym_filespec); |
154 | |
155 | if (FileSystem::Instance().IsDirectory(file_spec: dsym_filespec)) { |
156 | dsym_filespec = PluginManager::FindSymbolFileInBundle( |
157 | dsym_bundle_fspec: 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(file_spec: 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(p: new Module(exe_spec)); |
192 | if (module_sp && module_sp->GetObjectFile() && |
193 | module_sp->MatchesModuleSpec(module_ref: 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 | image_name: 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(file_spec&: exec_filespec); |
236 | if (FileSystem::Instance().Exists(file_spec: 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(haystack: path, needle: ".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(Path: 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(file_spec&: bundle_exe_file_spec); |
272 | if (ObjectFile::GetModuleSpecifications( |
273 | file: bundle_exe_file_spec, file_offset: 0, file_size: 0, specs&: module_specs) && |
274 | module_specs.FindMatchingModuleSpec( |
275 | module_spec, match_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: file_spec, file_offset: 0, file_size: 0, |
301 | specs&: module_specs) && |
302 | module_specs.FindMatchingModuleSpec(module_spec, |
303 | match_module_spec&: 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(path&: buffer, a: "Contents" , b: "Resources" , c: "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(Path: Iter->path()); |
342 | if (Status->isDirectory()) |
343 | continue; |
344 | |
345 | FileSpec dsym_fspec(Iter->path()); |
346 | ModuleSpecList module_specs; |
347 | if (ObjectFile::GetModuleSpecifications(file: dsym_fspec, file_offset: 0, file_size: 0, specs&: module_specs)) { |
348 | ModuleSpec spec; |
349 | for (size_t i = 0; i < module_specs.GetSize(); ++i) { |
350 | bool got_spec = module_specs.GetModuleSpecAtIndex(i, module_spec&: 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(rhs: *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: file_fspec, file_offset: 0, file_size: 0, specs&: module_specs)) { |
372 | ModuleSpec spec; |
373 | for (size_t i = 0; i < module_specs.GetSize(); ++i) { |
374 | bool got_spec = module_specs.GetModuleSpecAtIndex(i, module_spec&: 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(rhs: *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(component: dsym_filename); |
404 | dsym_directory.AppendPathComponent(component: "Contents" ); |
405 | dsym_directory.AppendPathComponent(component: "Resources" ); |
406 | dsym_directory.AppendPathComponent(component: "DWARF" ); |
407 | |
408 | if (FileSystem::Instance().Exists(file_spec: 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(component: filename.AsCString()); |
414 | if (FileSystem::Instance().Exists(file_spec: dsym_fspec) && |
415 | FileAtPathContainsArchAndUUID(file_fspec: dsym_fspec, arch: mod_spec.GetArchitecturePtr(), |
416 | uuid: 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(c: '.'); |
426 | if (last_dot != std::string::npos) { |
427 | binary_name.erase(pos: last_dot); |
428 | dsym_fspec = dsym_directory; |
429 | dsym_fspec.AppendPathComponent(component: binary_name); |
430 | if (FileSystem::Instance().Exists(file_spec: dsym_fspec) && |
431 | FileAtPathContainsArchAndUUID(file_fspec: dsym_fspec, |
432 | arch: mod_spec.GetArchitecturePtr(), |
433 | uuid: 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(component: dsym_yaa_filename); |
445 | |
446 | if (FileSystem::Instance().Exists(file_spec: dsym_yaa_fspec)) { |
447 | ModuleSpec mutable_mod_spec = mod_spec; |
448 | Status error; |
449 | if (PluginManager::DownloadObjectAndSymbolFile(module_spec&: mutable_mod_spec, error, |
450 | force_lookup: true) && |
451 | FileSystem::Instance().Exists(file_spec: 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(mask: LLDBLog::Host); |
474 | const FileSpec &exec_fspec = module_spec.GetFileSpec(); |
475 | if (exec_fspec) { |
476 | if (::LookForDsymNextToExecutablePath(mod_spec: 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(s: fn, c: '.') != nullptr) { |
508 | if (::LookForDsymNextToExecutablePath(mod_spec: module_spec, exec_fspec: 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(mask: 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 | file: "/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(file_spec&: dsym_filespec); |
596 | |
597 | if (FileSystem::Instance().IsDirectory(file_spec: dsym_filespec)) { |
598 | dsym_filespec = PluginManager::FindSymbolFileInBundle( |
599 | dsym_bundle_fspec: 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(file_spec: 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(p: new Module(exe_spec)); |
634 | if (module_sp && module_sp->GetObjectFile() && |
635 | module_sp->MatchesModuleSpec(module_ref: 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 | image_name: 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(file_spec&: exec_filespec); |
678 | if (FileSystem::Instance().Exists(file_spec: 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(haystack: path, needle: ".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(Path: 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(file_spec&: bundle_exe_file_spec); |
714 | if (ObjectFile::GetModuleSpecifications( |
715 | file: bundle_exe_file_spec, file_offset: 0, file_size: 0, specs&: module_specs) && |
716 | module_specs.FindMatchingModuleSpec( |
717 | module_spec, match_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: file_spec, file_offset: 0, file_size: 0, |
743 | specs&: module_specs) && |
744 | module_specs.FindMatchingModuleSpec(module_spec, |
745 | match_module_spec&: 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(value_if_empty: "<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, dsym_fspec&: symbol_fspec)) { |
788 | // We failed to easily find the dSYM above, so use DebugSymbols |
789 | LocateMacOSXFilesUsingDebugSymbols(module_spec, return_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(mask: 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: 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: cf_str, str)) { |
823 | module_spec.GetFileSpec().SetFile(path: str.c_str(), style: FileSpec::Style::native); |
824 | FileSystem::Instance().Resolve(file_spec&: 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: cf_str, str)) { |
835 | module_spec.GetSymbolFileSpec().SetFile(path: str.c_str(), |
836 | style: FileSpec::Style::native); |
837 | FileSystem::Instance().Resolve(file_spec&: 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: cf_str, str&: version); |
866 | if (!version.empty() && isdigit(version[0])) { |
867 | int version_number = atoi(nptr: 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(cf_str: keys[i], str&: DBGBuildSourcePath); |
893 | } |
894 | if (values[i] && CFGetTypeID(values[i]) == CFStringGetTypeID()) { |
895 | CFCString::FileSystemRepresentation(cf_str: values[i], str&: 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(file_spec&: 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(path: DBGBuildSourcePath, |
916 | replacement: DBGSourcePath, notify: 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 | path: build_path.GetPath(), replacement: source_path.GetPath(), notify: 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: cf_str, 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: cf_str, 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(file_spec&: resolved_source_path); |
955 | DBGSourcePath = resolved_source_path.GetPath(); |
956 | } |
957 | module_spec.GetSourceMappingList().Append(path: DBGBuildSourcePath, |
958 | replacement: DBGSourcePath, notify: 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(once&: g_once_flag, f: [&]() { |
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(name: "LLDB_APPLE_DSYMFORUUID_EXECUTABLE" )) { |
995 | FileSpec dsymForUUID_executable(dsymForUUID_env); |
996 | FileSystem::Instance().Resolve(file_spec&: dsymForUUID_executable); |
997 | if (FileSystem::Instance().Exists(file_spec: 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(once&: g_once_flag, f: [&]() { |
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(file_spec&: g_dsymForUUID_executable); |
1009 | if (FileSystem::Instance().Exists(file_spec: 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(file_spec: 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: *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(Fmt: "{0} ({1})" , Vals: file_spec_ptr->GetFilename().GetString(), |
1073 | Vals: 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(mask: 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: command.GetData(), |
1091 | working_dir: FileSpec(), // current working directory |
1092 | status_ptr: &exit_status, // Exit status |
1093 | signo_ptr: &signo, // Signal int * |
1094 | command_output: &command_output, // Command output |
1095 | timeout: std::chrono::seconds( |
1096 | 640), // Large timeout to allow for long dsym download times |
1097 | run_in_shell: 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 | rhs: curr_module_spec.GetArchitecture())) { |
1150 | module_spec = curr_module_spec; |
1151 | return true; |
1152 | } |
1153 | } |
1154 | } |
1155 | } |
1156 | |
1157 | return false; |
1158 | } |
1159 | |