diff options
Diffstat (limited to 'contrib/llvm-project/lldb/source/Plugins/SymbolLocator/DebugSymbols/SymbolLocatorDebugSymbols.cpp')
-rw-r--r-- | contrib/llvm-project/lldb/source/Plugins/SymbolLocator/DebugSymbols/SymbolLocatorDebugSymbols.cpp | 1158 |
1 files changed, 1158 insertions, 0 deletions
diff --git a/contrib/llvm-project/lldb/source/Plugins/SymbolLocator/DebugSymbols/SymbolLocatorDebugSymbols.cpp b/contrib/llvm-project/lldb/source/Plugins/SymbolLocator/DebugSymbols/SymbolLocatorDebugSymbols.cpp new file mode 100644 index 000000000000..69595c6c14e4 --- /dev/null +++ b/contrib/llvm-project/lldb/source/Plugins/SymbolLocator/DebugSymbols/SymbolLocatorDebugSymbols.cpp @@ -0,0 +1,1158 @@ +//===-- 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 <CoreFoundation/CoreFoundation.h> + +#include <cstring> +#include <dirent.h> +#include <dlfcn.h> +#include <optional> +#include <pwd.h> + +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<ModuleSpec> 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<uint8_t> module_uuid = uuid->GetBytes(); + if (module_uuid.size() == 16) { + CFCReleaser<CFUUIDRef> 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<CFURLRef> 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<CFURLRef> 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<CFDictionaryRef> 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<CFDictionaryRef>( + ::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<CFStringRef>(::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<CFURLRef> 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<FileSpec> 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<llvm::vfs::FileSystem> 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<llvm::vfs::Status> 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<uint8_t> module_uuid = uuid->GetBytes(); + if (module_uuid.size() == 16) { + CFCReleaser<CFUUIDRef> 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<CFURLRef> 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<CFURLRef> 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<CFDictionaryRef> 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<CFDictionaryRef>( + ::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<CFStringRef>(::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<CFURLRef> 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<FileSpec> 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("<NULL>") : "<NULL>", + arch ? arch->GetArchitectureName() : "<NULL>", (const void *)uuid); + + Progress progress( + "Locating external symbol file", + module_spec.GetFileSpec().GetFilename().AsCString("<Unknown>")); + + 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<CFDictionaryRef> 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<CFStringRef> keys(num_values, NULL); + std::vector<CFDictionaryRef> 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; +} |