//===-- SymbolLocatorDebugSymbols.cpp -------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "SymbolLocatorDebugSymbols.h" #include "Plugins/ObjectFile/wasm/ObjectFileWasm.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" #include "lldb/Core/ModuleList.h" #include "lldb/Core/ModuleSpec.h" #include "lldb/Core/PluginManager.h" #include "lldb/Core/Progress.h" #include "lldb/Core/Section.h" #include "lldb/Host/FileSystem.h" #include "lldb/Host/Host.h" #include "lldb/Host/HostInfo.h" #include "lldb/Symbol/ObjectFile.h" #include "lldb/Target/Target.h" #include "lldb/Utility/ArchSpec.h" #include "lldb/Utility/DataBuffer.h" #include "lldb/Utility/DataExtractor.h" #include "lldb/Utility/LLDBLog.h" #include "lldb/Utility/Log.h" #include "lldb/Utility/StreamString.h" #include "lldb/Utility/Timer.h" #include "lldb/Utility/UUID.h" #include "llvm/ADT/SmallSet.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ThreadPool.h" #include "Host/macosx/cfcpp/CFCBundle.h" #include "Host/macosx/cfcpp/CFCData.h" #include "Host/macosx/cfcpp/CFCReleaser.h" #include "Host/macosx/cfcpp/CFCString.h" #include "mach/machine.h" #include #include #include #include #include #include using namespace lldb; using namespace lldb_private; static CFURLRef (*g_dlsym_DBGCopyFullDSYMURLForUUID)( CFUUIDRef uuid, CFURLRef exec_url) = nullptr; static CFDictionaryRef (*g_dlsym_DBGCopyDSYMPropertyLists)(CFURLRef dsym_url) = nullptr; LLDB_PLUGIN_DEFINE(SymbolLocatorDebugSymbols) SymbolLocatorDebugSymbols::SymbolLocatorDebugSymbols() : SymbolLocator() {} void SymbolLocatorDebugSymbols::Initialize() { PluginManager::RegisterPlugin( GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, LocateExecutableObjectFile, LocateExecutableSymbolFile, DownloadObjectAndSymbolFile, FindSymbolFileInBundle); } void SymbolLocatorDebugSymbols::Terminate() { PluginManager::UnregisterPlugin(CreateInstance); } llvm::StringRef SymbolLocatorDebugSymbols::GetPluginDescriptionStatic() { return "DebugSymbols symbol locator."; } SymbolLocator *SymbolLocatorDebugSymbols::CreateInstance() { return new SymbolLocatorDebugSymbols(); } std::optional SymbolLocatorDebugSymbols::LocateExecutableObjectFile( const ModuleSpec &module_spec) { Log *log = GetLog(LLDBLog::Host); if (!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) { LLDB_LOGF(log, "Spotlight lookup for .dSYM bundles is disabled."); return {}; } ModuleSpec return_module_spec; return_module_spec = module_spec; return_module_spec.GetFileSpec().Clear(); return_module_spec.GetSymbolFileSpec().Clear(); const UUID *uuid = module_spec.GetUUIDPtr(); const ArchSpec *arch = module_spec.GetArchitecturePtr(); int items_found = 0; if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr || g_dlsym_DBGCopyDSYMPropertyLists == nullptr) { void *handle = dlopen( "/System/Library/PrivateFrameworks/DebugSymbols.framework/DebugSymbols", RTLD_LAZY | RTLD_LOCAL); if (handle) { g_dlsym_DBGCopyFullDSYMURLForUUID = (CFURLRef(*)(CFUUIDRef, CFURLRef))dlsym(handle, "DBGCopyFullDSYMURLForUUID"); g_dlsym_DBGCopyDSYMPropertyLists = (CFDictionaryRef(*)(CFURLRef))dlsym( handle, "DBGCopyDSYMPropertyLists"); } } if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr || g_dlsym_DBGCopyDSYMPropertyLists == nullptr) { return {}; } if (uuid && uuid->IsValid()) { // Try and locate the dSYM file using DebugSymbols first llvm::ArrayRef module_uuid = uuid->GetBytes(); if (module_uuid.size() == 16) { CFCReleaser module_uuid_ref(::CFUUIDCreateWithBytes( NULL, module_uuid[0], module_uuid[1], module_uuid[2], module_uuid[3], module_uuid[4], module_uuid[5], module_uuid[6], module_uuid[7], module_uuid[8], module_uuid[9], module_uuid[10], module_uuid[11], module_uuid[12], module_uuid[13], module_uuid[14], module_uuid[15])); if (module_uuid_ref.get()) { CFCReleaser exec_url; const FileSpec *exec_fspec = module_spec.GetFileSpecPtr(); if (exec_fspec) { char exec_cf_path[PATH_MAX]; if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path))) exec_url.reset(::CFURLCreateFromFileSystemRepresentation( NULL, (const UInt8 *)exec_cf_path, strlen(exec_cf_path), FALSE)); } CFCReleaser dsym_url(g_dlsym_DBGCopyFullDSYMURLForUUID( module_uuid_ref.get(), exec_url.get())); char path[PATH_MAX]; if (dsym_url.get()) { if (::CFURLGetFileSystemRepresentation( dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { LLDB_LOGF(log, "DebugSymbols framework returned dSYM path of %s for " "UUID %s -- looking for the dSYM", path, uuid->GetAsString().c_str()); FileSpec dsym_filespec(path); if (path[0] == '~') FileSystem::Instance().Resolve(dsym_filespec); if (FileSystem::Instance().IsDirectory(dsym_filespec)) { dsym_filespec = PluginManager::FindSymbolFileInBundle( dsym_filespec, uuid, arch); ++items_found; } else { ++items_found; } return_module_spec.GetSymbolFileSpec() = dsym_filespec; } bool success = false; if (log) { if (::CFURLGetFileSystemRepresentation( dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { LLDB_LOGF(log, "DebugSymbols framework returned dSYM path of %s for " "UUID %s -- looking for an exec file", path, uuid->GetAsString().c_str()); } } CFCReleaser dict( g_dlsym_DBGCopyDSYMPropertyLists(dsym_url.get())); CFDictionaryRef uuid_dict = NULL; if (dict.get()) { CFCString uuid_cfstr(uuid->GetAsString().c_str()); uuid_dict = static_cast( ::CFDictionaryGetValue(dict.get(), uuid_cfstr.get())); } // Check to see if we have the file on the local filesystem. if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) { ModuleSpec exe_spec; exe_spec.GetFileSpec() = module_spec.GetFileSpec(); exe_spec.GetUUID() = module_spec.GetUUID(); ModuleSP module_sp; module_sp.reset(new Module(exe_spec)); if (module_sp && module_sp->GetObjectFile() && module_sp->MatchesModuleSpec(exe_spec)) { success = true; return_module_spec.GetFileSpec() = module_spec.GetFileSpec(); LLDB_LOGF(log, "using original binary filepath %s for UUID %s", module_spec.GetFileSpec().GetPath().c_str(), uuid->GetAsString().c_str()); ++items_found; } } // Check if the requested image is in our shared cache. if (!success) { SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo( module_spec.GetFileSpec().GetPath()); // If we found it and it has the correct UUID, let's proceed with // creating a module from the memory contents. if (image_info.uuid && (!module_spec.GetUUID() || module_spec.GetUUID() == image_info.uuid)) { success = true; return_module_spec.GetFileSpec() = module_spec.GetFileSpec(); LLDB_LOGF(log, "using binary from shared cache for filepath %s for " "UUID %s", module_spec.GetFileSpec().GetPath().c_str(), uuid->GetAsString().c_str()); ++items_found; } } // Use the DBGSymbolRichExecutable filepath if present if (!success && uuid_dict) { CFStringRef exec_cf_path = static_cast(::CFDictionaryGetValue( uuid_dict, CFSTR("DBGSymbolRichExecutable"))); if (exec_cf_path && ::CFStringGetFileSystemRepresentation( exec_cf_path, path, sizeof(path))) { LLDB_LOGF(log, "plist bundle has exec path of %s for UUID %s", path, uuid->GetAsString().c_str()); ++items_found; FileSpec exec_filespec(path); if (path[0] == '~') FileSystem::Instance().Resolve(exec_filespec); if (FileSystem::Instance().Exists(exec_filespec)) { success = true; return_module_spec.GetFileSpec() = exec_filespec; } } } // Look next to the dSYM for the binary file. if (!success) { if (::CFURLGetFileSystemRepresentation( dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { char *dsym_extension_pos = ::strstr(path, ".dSYM"); if (dsym_extension_pos) { *dsym_extension_pos = '\0'; LLDB_LOGF(log, "Looking for executable binary next to dSYM " "bundle with name with name %s", path); FileSpec file_spec(path); FileSystem::Instance().Resolve(file_spec); ModuleSpecList module_specs; ModuleSpec matched_module_spec; using namespace llvm::sys::fs; switch (get_file_type(file_spec.GetPath())) { case file_type::directory_file: // Bundle directory? { CFCBundle bundle(path); CFCReleaser bundle_exe_url( bundle.CopyExecutableURL()); if (bundle_exe_url.get()) { if (::CFURLGetFileSystemRepresentation(bundle_exe_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { FileSpec bundle_exe_file_spec(path); FileSystem::Instance().Resolve(bundle_exe_file_spec); if (ObjectFile::GetModuleSpecifications( bundle_exe_file_spec, 0, 0, module_specs) && module_specs.FindMatchingModuleSpec( module_spec, matched_module_spec)) { ++items_found; return_module_spec.GetFileSpec() = bundle_exe_file_spec; LLDB_LOGF(log, "Executable binary %s next to dSYM is " "compatible; using", path); } } } } break; case file_type::fifo_file: // Forget pipes case file_type::socket_file: // We can't process socket files case file_type::file_not_found: // File doesn't exist... case file_type::status_error: break; case file_type::type_unknown: case file_type::regular_file: case file_type::symlink_file: case file_type::block_file: case file_type::character_file: if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0, module_specs) && module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec)) { ++items_found; return_module_spec.GetFileSpec() = file_spec; LLDB_LOGF(log, "Executable binary %s next to dSYM is " "compatible; using", path); } break; } } } } } } } } if (items_found) return return_module_spec; return {}; } std::optional SymbolLocatorDebugSymbols::FindSymbolFileInBundle( const FileSpec &dsym_bundle_fspec, const UUID *uuid, const ArchSpec *arch) { std::string dsym_bundle_path = dsym_bundle_fspec.GetPath(); llvm::SmallString<128> buffer(dsym_bundle_path); llvm::sys::path::append(buffer, "Contents", "Resources", "DWARF"); std::error_code EC; llvm::IntrusiveRefCntPtr vfs = FileSystem::Instance().GetVirtualFileSystem(); llvm::vfs::recursive_directory_iterator Iter(*vfs, buffer.str(), EC); llvm::vfs::recursive_directory_iterator End; for (; Iter != End && !EC; Iter.increment(EC)) { llvm::ErrorOr Status = vfs->status(Iter->path()); if (Status->isDirectory()) continue; FileSpec dsym_fspec(Iter->path()); ModuleSpecList module_specs; if (ObjectFile::GetModuleSpecifications(dsym_fspec, 0, 0, module_specs)) { ModuleSpec spec; for (size_t i = 0; i < module_specs.GetSize(); ++i) { bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec); assert(got_spec); // The call has side-effects so can't be inlined. UNUSED_IF_ASSERT_DISABLED(got_spec); if ((uuid == nullptr || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) && (arch == nullptr || (spec.GetArchitecturePtr() && spec.GetArchitecture().IsCompatibleMatch(*arch)))) { return dsym_fspec; } } } } return {}; } static bool FileAtPathContainsArchAndUUID(const FileSpec &file_fspec, const ArchSpec *arch, const lldb_private::UUID *uuid) { ModuleSpecList module_specs; if (ObjectFile::GetModuleSpecifications(file_fspec, 0, 0, module_specs)) { ModuleSpec spec; for (size_t i = 0; i < module_specs.GetSize(); ++i) { bool got_spec = module_specs.GetModuleSpecAtIndex(i, spec); UNUSED_IF_ASSERT_DISABLED(got_spec); assert(got_spec); if ((uuid == nullptr || (spec.GetUUIDPtr() && spec.GetUUID() == *uuid)) && (arch == nullptr || (spec.GetArchitecturePtr() && spec.GetArchitecture().IsCompatibleMatch(*arch)))) { return true; } } } return false; } // Given a binary exec_fspec, and a ModuleSpec with an architecture/uuid, // return true if there is a matching dSYM bundle next to the exec_fspec, // and return that value in dsym_fspec. // If there is a .dSYM.yaa compressed archive next to the exec_fspec, // call through PluginManager::DownloadObjectAndSymbolFile to download the // expanded/uncompressed dSYM and return that filepath in dsym_fspec. static bool LookForDsymNextToExecutablePath(const ModuleSpec &mod_spec, const FileSpec &exec_fspec, FileSpec &dsym_fspec) { ConstString filename = exec_fspec.GetFilename(); FileSpec dsym_directory = exec_fspec; dsym_directory.RemoveLastPathComponent(); std::string dsym_filename = filename.AsCString(); dsym_filename += ".dSYM"; dsym_directory.AppendPathComponent(dsym_filename); dsym_directory.AppendPathComponent("Contents"); dsym_directory.AppendPathComponent("Resources"); dsym_directory.AppendPathComponent("DWARF"); if (FileSystem::Instance().Exists(dsym_directory)) { // See if the binary name exists in the dSYM DWARF // subdir. dsym_fspec = dsym_directory; dsym_fspec.AppendPathComponent(filename.AsCString()); if (FileSystem::Instance().Exists(dsym_fspec) && FileAtPathContainsArchAndUUID(dsym_fspec, mod_spec.GetArchitecturePtr(), mod_spec.GetUUIDPtr())) { return true; } // See if we have "../CF.framework" - so we'll look for // CF.framework.dSYM/Contents/Resources/DWARF/CF // We need to drop the last suffix after '.' to match // 'CF' in the DWARF subdir. std::string binary_name(filename.AsCString()); auto last_dot = binary_name.find_last_of('.'); if (last_dot != std::string::npos) { binary_name.erase(last_dot); dsym_fspec = dsym_directory; dsym_fspec.AppendPathComponent(binary_name); if (FileSystem::Instance().Exists(dsym_fspec) && FileAtPathContainsArchAndUUID(dsym_fspec, mod_spec.GetArchitecturePtr(), mod_spec.GetUUIDPtr())) { return true; } } } // See if we have a .dSYM.yaa next to this executable path. FileSpec dsym_yaa_fspec = exec_fspec; dsym_yaa_fspec.RemoveLastPathComponent(); std::string dsym_yaa_filename = filename.AsCString(); dsym_yaa_filename += ".dSYM.yaa"; dsym_yaa_fspec.AppendPathComponent(dsym_yaa_filename); if (FileSystem::Instance().Exists(dsym_yaa_fspec)) { ModuleSpec mutable_mod_spec = mod_spec; Status error; if (PluginManager::DownloadObjectAndSymbolFile(mutable_mod_spec, error, true) && FileSystem::Instance().Exists(mutable_mod_spec.GetSymbolFileSpec())) { dsym_fspec = mutable_mod_spec.GetSymbolFileSpec(); return true; } } return false; } // Given a ModuleSpec with a FileSpec and optionally uuid/architecture // filled in, look for a .dSYM bundle next to that binary. Returns true // if a .dSYM bundle is found, and that path is returned in the dsym_fspec // FileSpec. // // This routine looks a few directory layers above the given exec_path - // exec_path might be /System/Library/Frameworks/CF.framework/CF and the // dSYM might be /System/Library/Frameworks/CF.framework.dSYM. // // If there is a .dSYM.yaa compressed archive found next to the binary, // we'll call DownloadObjectAndSymbolFile to expand it into a plain .dSYM static bool LocateDSYMInVincinityOfExecutable(const ModuleSpec &module_spec, FileSpec &dsym_fspec) { Log *log = GetLog(LLDBLog::Host); const FileSpec &exec_fspec = module_spec.GetFileSpec(); if (exec_fspec) { if (::LookForDsymNextToExecutablePath(module_spec, exec_fspec, dsym_fspec)) { if (log) { LLDB_LOGF(log, "dSYM with matching UUID & arch found at %s", dsym_fspec.GetPath().c_str()); } return true; } else { FileSpec parent_dirs = exec_fspec; // Remove the binary name from the FileSpec parent_dirs.RemoveLastPathComponent(); // Add a ".dSYM" name to each directory component of the path, // stripping off components. e.g. we may have a binary like // /S/L/F/Foundation.framework/Versions/A/Foundation and // /S/L/F/Foundation.framework.dSYM // // so we'll need to start with // /S/L/F/Foundation.framework/Versions/A, add the .dSYM part to the // "A", and if that doesn't exist, strip off the "A" and try it again // with "Versions", etc., until we find a dSYM bundle or we've // stripped off enough path components that there's no need to // continue. for (int i = 0; i < 4; i++) { // Does this part of the path have a "." character - could it be a // bundle's top level directory? const char *fn = parent_dirs.GetFilename().AsCString(); if (fn == nullptr) break; if (::strchr(fn, '.') != nullptr) { if (::LookForDsymNextToExecutablePath(module_spec, parent_dirs, dsym_fspec)) { if (log) { LLDB_LOGF(log, "dSYM with matching UUID & arch found at %s", dsym_fspec.GetPath().c_str()); } return true; } } parent_dirs.RemoveLastPathComponent(); } } } dsym_fspec.Clear(); return false; } static int LocateMacOSXFilesUsingDebugSymbols(const ModuleSpec &module_spec, ModuleSpec &return_module_spec) { Log *log = GetLog(LLDBLog::Host); if (!ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) { LLDB_LOGF(log, "Spotlight lookup for .dSYM bundles is disabled."); return 0; } return_module_spec = module_spec; return_module_spec.GetFileSpec().Clear(); return_module_spec.GetSymbolFileSpec().Clear(); const UUID *uuid = module_spec.GetUUIDPtr(); const ArchSpec *arch = module_spec.GetArchitecturePtr(); int items_found = 0; if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr || g_dlsym_DBGCopyDSYMPropertyLists == nullptr) { void *handle = dlopen( "/System/Library/PrivateFrameworks/DebugSymbols.framework/DebugSymbols", RTLD_LAZY | RTLD_LOCAL); if (handle) { g_dlsym_DBGCopyFullDSYMURLForUUID = (CFURLRef(*)(CFUUIDRef, CFURLRef))dlsym(handle, "DBGCopyFullDSYMURLForUUID"); g_dlsym_DBGCopyDSYMPropertyLists = (CFDictionaryRef(*)(CFURLRef))dlsym( handle, "DBGCopyDSYMPropertyLists"); } } if (g_dlsym_DBGCopyFullDSYMURLForUUID == nullptr || g_dlsym_DBGCopyDSYMPropertyLists == nullptr) { return items_found; } if (uuid && uuid->IsValid()) { // Try and locate the dSYM file using DebugSymbols first llvm::ArrayRef module_uuid = uuid->GetBytes(); if (module_uuid.size() == 16) { CFCReleaser module_uuid_ref(::CFUUIDCreateWithBytes( NULL, module_uuid[0], module_uuid[1], module_uuid[2], module_uuid[3], module_uuid[4], module_uuid[5], module_uuid[6], module_uuid[7], module_uuid[8], module_uuid[9], module_uuid[10], module_uuid[11], module_uuid[12], module_uuid[13], module_uuid[14], module_uuid[15])); if (module_uuid_ref.get()) { CFCReleaser exec_url; const FileSpec *exec_fspec = module_spec.GetFileSpecPtr(); if (exec_fspec) { char exec_cf_path[PATH_MAX]; if (exec_fspec->GetPath(exec_cf_path, sizeof(exec_cf_path))) exec_url.reset(::CFURLCreateFromFileSystemRepresentation( NULL, (const UInt8 *)exec_cf_path, strlen(exec_cf_path), FALSE)); } CFCReleaser dsym_url(g_dlsym_DBGCopyFullDSYMURLForUUID( module_uuid_ref.get(), exec_url.get())); char path[PATH_MAX]; if (dsym_url.get()) { if (::CFURLGetFileSystemRepresentation( dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { LLDB_LOGF(log, "DebugSymbols framework returned dSYM path of %s for " "UUID %s -- looking for the dSYM", path, uuid->GetAsString().c_str()); FileSpec dsym_filespec(path); if (path[0] == '~') FileSystem::Instance().Resolve(dsym_filespec); if (FileSystem::Instance().IsDirectory(dsym_filespec)) { dsym_filespec = PluginManager::FindSymbolFileInBundle( dsym_filespec, uuid, arch); ++items_found; } else { ++items_found; } return_module_spec.GetSymbolFileSpec() = dsym_filespec; } bool success = false; if (log) { if (::CFURLGetFileSystemRepresentation( dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { LLDB_LOGF(log, "DebugSymbols framework returned dSYM path of %s for " "UUID %s -- looking for an exec file", path, uuid->GetAsString().c_str()); } } CFCReleaser dict( g_dlsym_DBGCopyDSYMPropertyLists(dsym_url.get())); CFDictionaryRef uuid_dict = NULL; if (dict.get()) { CFCString uuid_cfstr(uuid->GetAsString().c_str()); uuid_dict = static_cast( ::CFDictionaryGetValue(dict.get(), uuid_cfstr.get())); } // Check to see if we have the file on the local filesystem. if (FileSystem::Instance().Exists(module_spec.GetFileSpec())) { ModuleSpec exe_spec; exe_spec.GetFileSpec() = module_spec.GetFileSpec(); exe_spec.GetUUID() = module_spec.GetUUID(); ModuleSP module_sp; module_sp.reset(new Module(exe_spec)); if (module_sp && module_sp->GetObjectFile() && module_sp->MatchesModuleSpec(exe_spec)) { success = true; return_module_spec.GetFileSpec() = module_spec.GetFileSpec(); LLDB_LOGF(log, "using original binary filepath %s for UUID %s", module_spec.GetFileSpec().GetPath().c_str(), uuid->GetAsString().c_str()); ++items_found; } } // Check if the requested image is in our shared cache. if (!success) { SharedCacheImageInfo image_info = HostInfo::GetSharedCacheImageInfo( module_spec.GetFileSpec().GetPath()); // If we found it and it has the correct UUID, let's proceed with // creating a module from the memory contents. if (image_info.uuid && (!module_spec.GetUUID() || module_spec.GetUUID() == image_info.uuid)) { success = true; return_module_spec.GetFileSpec() = module_spec.GetFileSpec(); LLDB_LOGF(log, "using binary from shared cache for filepath %s for " "UUID %s", module_spec.GetFileSpec().GetPath().c_str(), uuid->GetAsString().c_str()); ++items_found; } } // Use the DBGSymbolRichExecutable filepath if present if (!success && uuid_dict) { CFStringRef exec_cf_path = static_cast(::CFDictionaryGetValue( uuid_dict, CFSTR("DBGSymbolRichExecutable"))); if (exec_cf_path && ::CFStringGetFileSystemRepresentation( exec_cf_path, path, sizeof(path))) { LLDB_LOGF(log, "plist bundle has exec path of %s for UUID %s", path, uuid->GetAsString().c_str()); ++items_found; FileSpec exec_filespec(path); if (path[0] == '~') FileSystem::Instance().Resolve(exec_filespec); if (FileSystem::Instance().Exists(exec_filespec)) { success = true; return_module_spec.GetFileSpec() = exec_filespec; } } } // Look next to the dSYM for the binary file. if (!success) { if (::CFURLGetFileSystemRepresentation( dsym_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { char *dsym_extension_pos = ::strstr(path, ".dSYM"); if (dsym_extension_pos) { *dsym_extension_pos = '\0'; LLDB_LOGF(log, "Looking for executable binary next to dSYM " "bundle with name with name %s", path); FileSpec file_spec(path); FileSystem::Instance().Resolve(file_spec); ModuleSpecList module_specs; ModuleSpec matched_module_spec; using namespace llvm::sys::fs; switch (get_file_type(file_spec.GetPath())) { case file_type::directory_file: // Bundle directory? { CFCBundle bundle(path); CFCReleaser bundle_exe_url( bundle.CopyExecutableURL()); if (bundle_exe_url.get()) { if (::CFURLGetFileSystemRepresentation(bundle_exe_url.get(), true, (UInt8 *)path, sizeof(path) - 1)) { FileSpec bundle_exe_file_spec(path); FileSystem::Instance().Resolve(bundle_exe_file_spec); if (ObjectFile::GetModuleSpecifications( bundle_exe_file_spec, 0, 0, module_specs) && module_specs.FindMatchingModuleSpec( module_spec, matched_module_spec)) { ++items_found; return_module_spec.GetFileSpec() = bundle_exe_file_spec; LLDB_LOGF(log, "Executable binary %s next to dSYM is " "compatible; using", path); } } } } break; case file_type::fifo_file: // Forget pipes case file_type::socket_file: // We can't process socket files case file_type::file_not_found: // File doesn't exist... case file_type::status_error: break; case file_type::type_unknown: case file_type::regular_file: case file_type::symlink_file: case file_type::block_file: case file_type::character_file: if (ObjectFile::GetModuleSpecifications(file_spec, 0, 0, module_specs) && module_specs.FindMatchingModuleSpec(module_spec, matched_module_spec)) { ++items_found; return_module_spec.GetFileSpec() = file_spec; LLDB_LOGF(log, "Executable binary %s next to dSYM is " "compatible; using", path); } break; } } } } } } } } return items_found; } std::optional SymbolLocatorDebugSymbols::LocateExecutableSymbolFile( const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { const FileSpec *exec_fspec = module_spec.GetFileSpecPtr(); const ArchSpec *arch = module_spec.GetArchitecturePtr(); const UUID *uuid = module_spec.GetUUIDPtr(); LLDB_SCOPED_TIMERF( "LocateExecutableSymbolFileDsym (file = %s, arch = %s, uuid = %p)", exec_fspec ? exec_fspec->GetFilename().AsCString("") : "", arch ? arch->GetArchitectureName() : "", (const void *)uuid); Progress progress( "Locating external symbol file", module_spec.GetFileSpec().GetFilename().AsCString("")); FileSpec symbol_fspec; ModuleSpec dsym_module_spec; // First try and find the dSYM in the same directory as the executable or in // an appropriate parent directory if (!LocateDSYMInVincinityOfExecutable(module_spec, symbol_fspec)) { // We failed to easily find the dSYM above, so use DebugSymbols LocateMacOSXFilesUsingDebugSymbols(module_spec, dsym_module_spec); } else { dsym_module_spec.GetSymbolFileSpec() = symbol_fspec; } return dsym_module_spec.GetSymbolFileSpec(); } static bool GetModuleSpecInfoFromUUIDDictionary(CFDictionaryRef uuid_dict, ModuleSpec &module_spec, Status &error, const std::string &command) { Log *log = GetLog(LLDBLog::Host); bool success = false; if (uuid_dict != NULL && CFGetTypeID(uuid_dict) == CFDictionaryGetTypeID()) { std::string str; CFStringRef cf_str; CFDictionaryRef cf_dict; cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict, CFSTR("DBGError")); if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) { if (CFCString::FileSystemRepresentation(cf_str, str)) { std::string errorstr = command; errorstr += ":\n"; errorstr += str; error.SetErrorString(errorstr); } } cf_str = (CFStringRef)CFDictionaryGetValue( (CFDictionaryRef)uuid_dict, CFSTR("DBGSymbolRichExecutable")); if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) { if (CFCString::FileSystemRepresentation(cf_str, str)) { module_spec.GetFileSpec().SetFile(str.c_str(), FileSpec::Style::native); FileSystem::Instance().Resolve(module_spec.GetFileSpec()); LLDB_LOGF(log, "From dsymForUUID plist: Symbol rich executable is at '%s'", str.c_str()); } } cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict, CFSTR("DBGDSYMPath")); if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) { if (CFCString::FileSystemRepresentation(cf_str, str)) { module_spec.GetSymbolFileSpec().SetFile(str.c_str(), FileSpec::Style::native); FileSystem::Instance().Resolve(module_spec.GetFileSpec()); success = true; LLDB_LOGF(log, "From dsymForUUID plist: dSYM is at '%s'", str.c_str()); } } std::string DBGBuildSourcePath; std::string DBGSourcePath; // If DBGVersion 1 or DBGVersion missing, ignore DBGSourcePathRemapping. // If DBGVersion 2, strip last two components of path remappings from // entries to fix an issue with a specific set of // DBGSourcePathRemapping entries that lldb worked // with. // If DBGVersion 3, trust & use the source path remappings as-is. // cf_dict = (CFDictionaryRef)CFDictionaryGetValue( (CFDictionaryRef)uuid_dict, CFSTR("DBGSourcePathRemapping")); if (cf_dict && CFGetTypeID(cf_dict) == CFDictionaryGetTypeID()) { // If we see DBGVersion with a value of 2 or higher, this is a new style // DBGSourcePathRemapping dictionary bool new_style_source_remapping_dictionary = false; bool do_truncate_remapping_names = false; std::string original_DBGSourcePath_value = DBGSourcePath; cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict, CFSTR("DBGVersion")); if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) { std::string version; CFCString::FileSystemRepresentation(cf_str, version); if (!version.empty() && isdigit(version[0])) { int version_number = atoi(version.c_str()); if (version_number > 1) { new_style_source_remapping_dictionary = true; } if (version_number == 2) { do_truncate_remapping_names = true; } } } CFIndex kv_pair_count = CFDictionaryGetCount((CFDictionaryRef)uuid_dict); if (kv_pair_count > 0) { CFStringRef *keys = (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef)); CFStringRef *values = (CFStringRef *)malloc(kv_pair_count * sizeof(CFStringRef)); if (keys != nullptr && values != nullptr) { CFDictionaryGetKeysAndValues((CFDictionaryRef)uuid_dict, (const void **)keys, (const void **)values); } for (CFIndex i = 0; i < kv_pair_count; i++) { DBGBuildSourcePath.clear(); DBGSourcePath.clear(); if (keys[i] && CFGetTypeID(keys[i]) == CFStringGetTypeID()) { CFCString::FileSystemRepresentation(keys[i], DBGBuildSourcePath); } if (values[i] && CFGetTypeID(values[i]) == CFStringGetTypeID()) { CFCString::FileSystemRepresentation(values[i], DBGSourcePath); } if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) { // In the "old style" DBGSourcePathRemapping dictionary, the // DBGSourcePath values (the "values" half of key-value path pairs) // were wrong. Ignore them and use the universal DBGSourcePath // string from earlier. if (new_style_source_remapping_dictionary && !original_DBGSourcePath_value.empty()) { DBGSourcePath = original_DBGSourcePath_value; } if (DBGSourcePath[0] == '~') { FileSpec resolved_source_path(DBGSourcePath.c_str()); FileSystem::Instance().Resolve(resolved_source_path); DBGSourcePath = resolved_source_path.GetPath(); } // With version 2 of DBGSourcePathRemapping, we can chop off the // last two filename parts from the source remapping and get a more // general source remapping that still works. Add this as another // option in addition to the full source path remap. module_spec.GetSourceMappingList().Append(DBGBuildSourcePath, DBGSourcePath, true); if (do_truncate_remapping_names) { FileSpec build_path(DBGBuildSourcePath.c_str()); FileSpec source_path(DBGSourcePath.c_str()); build_path.RemoveLastPathComponent(); build_path.RemoveLastPathComponent(); source_path.RemoveLastPathComponent(); source_path.RemoveLastPathComponent(); module_spec.GetSourceMappingList().Append( build_path.GetPath(), source_path.GetPath(), true); } } } if (keys) free(keys); if (values) free(values); } } // If we have a DBGBuildSourcePath + DBGSourcePath pair, append them to the // source remappings list. cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict, CFSTR("DBGBuildSourcePath")); if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) { CFCString::FileSystemRepresentation(cf_str, DBGBuildSourcePath); } cf_str = (CFStringRef)CFDictionaryGetValue((CFDictionaryRef)uuid_dict, CFSTR("DBGSourcePath")); if (cf_str && CFGetTypeID(cf_str) == CFStringGetTypeID()) { CFCString::FileSystemRepresentation(cf_str, DBGSourcePath); } if (!DBGBuildSourcePath.empty() && !DBGSourcePath.empty()) { if (DBGSourcePath[0] == '~') { FileSpec resolved_source_path(DBGSourcePath.c_str()); FileSystem::Instance().Resolve(resolved_source_path); DBGSourcePath = resolved_source_path.GetPath(); } module_spec.GetSourceMappingList().Append(DBGBuildSourcePath, DBGSourcePath, true); } } return success; } /// It's expensive to check for the DBGShellCommands defaults setting. Only do /// it once per lldb run and cache the result. static llvm::StringRef GetDbgShellCommand() { static std::once_flag g_once_flag; static std::string g_dbgshell_command; std::call_once(g_once_flag, [&]() { CFTypeRef defaults_setting = CFPreferencesCopyAppValue( CFSTR("DBGShellCommands"), CFSTR("com.apple.DebugSymbols")); if (defaults_setting && CFGetTypeID(defaults_setting) == CFStringGetTypeID()) { char buffer[PATH_MAX]; if (CFStringGetCString((CFStringRef)defaults_setting, buffer, sizeof(buffer), kCFStringEncodingUTF8)) { g_dbgshell_command = buffer; } } if (defaults_setting) { CFRelease(defaults_setting); } }); return g_dbgshell_command; } /// Get the dsymForUUID executable and cache the result so we don't end up /// stat'ing the binary over and over. static FileSpec GetDsymForUUIDExecutable() { // The LLDB_APPLE_DSYMFORUUID_EXECUTABLE environment variable is used by the // test suite to override the dsymForUUID location. Because we must be able // to change the value within a single test, don't bother caching it. if (const char *dsymForUUID_env = getenv("LLDB_APPLE_DSYMFORUUID_EXECUTABLE")) { FileSpec dsymForUUID_executable(dsymForUUID_env); FileSystem::Instance().Resolve(dsymForUUID_executable); if (FileSystem::Instance().Exists(dsymForUUID_executable)) return dsymForUUID_executable; } static std::once_flag g_once_flag; static FileSpec g_dsymForUUID_executable; std::call_once(g_once_flag, [&]() { // Try the DBGShellCommand. llvm::StringRef dbgshell_command = GetDbgShellCommand(); if (!dbgshell_command.empty()) { g_dsymForUUID_executable = FileSpec(dbgshell_command); FileSystem::Instance().Resolve(g_dsymForUUID_executable); if (FileSystem::Instance().Exists(g_dsymForUUID_executable)) return; } // Try dsymForUUID in /usr/local/bin { g_dsymForUUID_executable = FileSpec("/usr/local/bin/dsymForUUID"); if (FileSystem::Instance().Exists(g_dsymForUUID_executable)) return; } // We couldn't find the dsymForUUID binary. g_dsymForUUID_executable = {}; }); return g_dsymForUUID_executable; } bool SymbolLocatorDebugSymbols::DownloadObjectAndSymbolFile( ModuleSpec &module_spec, Status &error, bool force_lookup, bool copy_executable) { const UUID *uuid_ptr = module_spec.GetUUIDPtr(); const FileSpec *file_spec_ptr = module_spec.GetFileSpecPtr(); // If \a dbgshell_command is set, the user has specified // forced symbol lookup via that command. We'll get the // path back from GetDsymForUUIDExecutable() later. llvm::StringRef dbgshell_command = GetDbgShellCommand(); // If forced lookup isn't set, by the user's \a dbgshell_command or // by the \a force_lookup argument, exit this method. if (!force_lookup && dbgshell_command.empty()) return false; // We need a UUID or valid existing FileSpec. if (!uuid_ptr && (!file_spec_ptr || !FileSystem::Instance().Exists(*file_spec_ptr))) return false; // We need a dsymForUUID binary or an equivalent executable/script. FileSpec dsymForUUID_exe_spec = GetDsymForUUIDExecutable(); if (!dsymForUUID_exe_spec) return false; // Create the dsymForUUID command. const std::string dsymForUUID_exe_path = dsymForUUID_exe_spec.GetPath(); const std::string uuid_str = uuid_ptr ? uuid_ptr->GetAsString() : ""; std::string lookup_arg = uuid_str; if (lookup_arg.empty()) lookup_arg = file_spec_ptr ? file_spec_ptr->GetPath() : ""; if (lookup_arg.empty()) return false; StreamString command; command << dsymForUUID_exe_path << " --ignoreNegativeCache "; if (copy_executable) command << "--copyExecutable "; command << lookup_arg; // Log and report progress. std::string lookup_desc; if (uuid_ptr && file_spec_ptr) lookup_desc = llvm::formatv("{0} ({1})", file_spec_ptr->GetFilename().GetString(), uuid_ptr->GetAsString()); else if (uuid_ptr) lookup_desc = uuid_ptr->GetAsString(); else if (file_spec_ptr) lookup_desc = file_spec_ptr->GetFilename().GetString(); Log *log = GetLog(LLDBLog::Host); LLDB_LOG(log, "Calling {0} for {1} to find dSYM: {2}", dsymForUUID_exe_path, lookup_desc, command.GetString()); Progress progress("Downloading symbol file for", lookup_desc); // Invoke dsymForUUID. int exit_status = -1; int signo = -1; std::string command_output; error = Host::RunShellCommand( command.GetData(), FileSpec(), // current working directory &exit_status, // Exit status &signo, // Signal int * &command_output, // Command output std::chrono::seconds( 640), // Large timeout to allow for long dsym download times false); // Don't run in a shell (we don't need shell expansion) if (error.Fail() || exit_status != 0 || command_output.empty()) { LLDB_LOGF(log, "'%s' failed (exit status: %d, error: '%s', output: '%s')", command.GetData(), exit_status, error.AsCString(), command_output.c_str()); return false; } CFCData data( CFDataCreateWithBytesNoCopy(NULL, (const UInt8 *)command_output.data(), command_output.size(), kCFAllocatorNull)); CFCReleaser plist( (CFDictionaryRef)::CFPropertyListCreateWithData( NULL, data.get(), kCFPropertyListImmutable, NULL, NULL)); if (!plist.get()) { LLDB_LOGF(log, "'%s' failed: output is not a valid plist", command.GetData()); return false; } if (CFGetTypeID(plist.get()) != CFDictionaryGetTypeID()) { LLDB_LOGF(log, "'%s' failed: output plist is not a valid CFDictionary", command.GetData()); return false; } if (!uuid_str.empty()) { CFCString uuid_cfstr(uuid_str.c_str()); CFDictionaryRef uuid_dict = (CFDictionaryRef)CFDictionaryGetValue(plist.get(), uuid_cfstr.get()); return GetModuleSpecInfoFromUUIDDictionary(uuid_dict, module_spec, error, command.GetData()); } if (const CFIndex num_values = ::CFDictionaryGetCount(plist.get())) { std::vector keys(num_values, NULL); std::vector values(num_values, NULL); ::CFDictionaryGetKeysAndValues(plist.get(), NULL, (const void **)&values[0]); if (num_values == 1) { return GetModuleSpecInfoFromUUIDDictionary(values[0], module_spec, error, command.GetData()); } for (CFIndex i = 0; i < num_values; ++i) { ModuleSpec curr_module_spec; if (GetModuleSpecInfoFromUUIDDictionary(values[i], curr_module_spec, error, command.GetData())) { if (module_spec.GetArchitecture().IsCompatibleMatch( curr_module_spec.GetArchitecture())) { module_spec = curr_module_spec; return true; } } } } return false; }