| 1 | //===-- SymbolLocatorDebuginfod.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 "SymbolLocatorDebuginfod.h" |
| 10 | |
| 11 | #include "lldb/Core/PluginManager.h" |
| 12 | #include "lldb/Interpreter/OptionValueString.h" |
| 13 | #include "lldb/Utility/Args.h" |
| 14 | #include "lldb/Utility/LLDBLog.h" |
| 15 | #include "lldb/Utility/Log.h" |
| 16 | |
| 17 | #include "llvm/Debuginfod/Debuginfod.h" |
| 18 | #include "llvm/Debuginfod/HTTPClient.h" |
| 19 | |
| 20 | using namespace lldb; |
| 21 | using namespace lldb_private; |
| 22 | |
| 23 | LLDB_PLUGIN_DEFINE(SymbolLocatorDebuginfod) |
| 24 | |
| 25 | namespace { |
| 26 | |
| 27 | #define LLDB_PROPERTIES_symbollocatordebuginfod |
| 28 | #include "SymbolLocatorDebuginfodProperties.inc" |
| 29 | |
| 30 | enum { |
| 31 | #define LLDB_PROPERTIES_symbollocatordebuginfod |
| 32 | #include "SymbolLocatorDebuginfodPropertiesEnum.inc" |
| 33 | }; |
| 34 | |
| 35 | class PluginProperties : public Properties { |
| 36 | public: |
| 37 | static llvm::StringRef GetSettingName() { |
| 38 | return SymbolLocatorDebuginfod::GetPluginNameStatic(); |
| 39 | } |
| 40 | |
| 41 | PluginProperties() { |
| 42 | m_collection_sp = std::make_shared<OptionValueProperties>(GetSettingName()); |
| 43 | m_collection_sp->Initialize(g_symbollocatordebuginfod_properties); |
| 44 | |
| 45 | // We need to read the default value first to read the environment variable. |
| 46 | llvm::SmallVector<llvm::StringRef> urls = llvm::getDefaultDebuginfodUrls(); |
| 47 | Args arg_urls{urls}; |
| 48 | m_collection_sp->SetPropertyAtIndexFromArgs(ePropertyServerURLs, arg_urls); |
| 49 | |
| 50 | m_collection_sp->SetValueChangedCallback( |
| 51 | ePropertyServerURLs, [this] { ServerURLsChangedCallback(); }); |
| 52 | } |
| 53 | |
| 54 | Args GetDebugInfoDURLs() const { |
| 55 | Args urls; |
| 56 | m_collection_sp->GetPropertyAtIndexAsArgs(ePropertyServerURLs, urls); |
| 57 | return urls; |
| 58 | } |
| 59 | |
| 60 | llvm::Expected<std::string> GetCachePath() { |
| 61 | OptionValueString *s = |
| 62 | m_collection_sp->GetPropertyAtIndexAsOptionValueString( |
| 63 | ePropertySymbolCachePath); |
| 64 | // If we don't have a valid cache location, use the default one. |
| 65 | if (!s || !s->GetCurrentValueAsRef().size()) { |
| 66 | llvm::Expected<std::string> maybeCachePath = |
| 67 | llvm::getDefaultDebuginfodCacheDirectory(); |
| 68 | if (!maybeCachePath) |
| 69 | return maybeCachePath; |
| 70 | return *maybeCachePath; |
| 71 | } |
| 72 | return s->GetCurrentValue(); |
| 73 | } |
| 74 | |
| 75 | std::chrono::milliseconds GetTimeout() const { |
| 76 | std::optional<uint64_t> seconds = |
| 77 | m_collection_sp->GetPropertyAtIndexAs<uint64_t>(ePropertyTimeout); |
| 78 | if (seconds && *seconds != 0) { |
| 79 | return std::chrono::duration_cast<std::chrono::milliseconds>( |
| 80 | std::chrono::seconds(*seconds)); |
| 81 | } else { |
| 82 | return llvm::getDefaultDebuginfodTimeout(); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | private: |
| 87 | void ServerURLsChangedCallback() { |
| 88 | m_server_urls = GetDebugInfoDURLs(); |
| 89 | llvm::SmallVector<llvm::StringRef> dbginfod_urls; |
| 90 | llvm::for_each(m_server_urls, [&](const auto &obj) { |
| 91 | dbginfod_urls.push_back(obj.ref()); |
| 92 | }); |
| 93 | llvm::setDefaultDebuginfodUrls(dbginfod_urls); |
| 94 | } |
| 95 | // Storage for the StringRef's used within the Debuginfod library. |
| 96 | Args m_server_urls; |
| 97 | }; |
| 98 | |
| 99 | } // namespace |
| 100 | |
| 101 | static PluginProperties &GetGlobalPluginProperties() { |
| 102 | static PluginProperties g_settings; |
| 103 | return g_settings; |
| 104 | } |
| 105 | |
| 106 | SymbolLocatorDebuginfod::SymbolLocatorDebuginfod() : SymbolLocator() {} |
| 107 | |
| 108 | void SymbolLocatorDebuginfod::Initialize() { |
| 109 | static llvm::once_flag g_once_flag; |
| 110 | |
| 111 | llvm::call_once(flag&: g_once_flag, F: []() { |
| 112 | PluginManager::RegisterPlugin( |
| 113 | name: GetPluginNameStatic(), description: GetPluginDescriptionStatic(), create_callback: CreateInstance, |
| 114 | locate_executable_object_file: LocateExecutableObjectFile, locate_executable_symbol_file: LocateExecutableSymbolFile, download_object_symbol_file: nullptr, |
| 115 | find_symbol_file_in_bundle: nullptr, debugger_init_callback: SymbolLocatorDebuginfod::DebuggerInitialize); |
| 116 | llvm::HTTPClient::initialize(); |
| 117 | }); |
| 118 | } |
| 119 | |
| 120 | void SymbolLocatorDebuginfod::DebuggerInitialize(Debugger &debugger) { |
| 121 | if (!PluginManager::GetSettingForSymbolLocatorPlugin( |
| 122 | debugger, setting_name: PluginProperties::GetSettingName())) { |
| 123 | const bool is_global_setting = true; |
| 124 | PluginManager::CreateSettingForSymbolLocatorPlugin( |
| 125 | debugger, properties_sp: GetGlobalPluginProperties().GetValueProperties(), |
| 126 | description: "Properties for the Debuginfod Symbol Locator plug-in." , |
| 127 | is_global_property: is_global_setting); |
| 128 | } |
| 129 | } |
| 130 | |
| 131 | void SymbolLocatorDebuginfod::Terminate() { |
| 132 | PluginManager::UnregisterPlugin(create_callback: CreateInstance); |
| 133 | llvm::HTTPClient::cleanup(); |
| 134 | } |
| 135 | |
| 136 | llvm::StringRef SymbolLocatorDebuginfod::GetPluginDescriptionStatic() { |
| 137 | return "Debuginfod symbol locator." ; |
| 138 | } |
| 139 | |
| 140 | SymbolLocator *SymbolLocatorDebuginfod::CreateInstance() { |
| 141 | return new SymbolLocatorDebuginfod(); |
| 142 | } |
| 143 | |
| 144 | static llvm::StringRef getFileName(const ModuleSpec &module_spec, |
| 145 | std::string url_path) { |
| 146 | // Check if the URL path requests an executable file or a symbol file |
| 147 | bool is_executable = url_path.find(s: "debuginfo" ) == std::string::npos; |
| 148 | if (is_executable) |
| 149 | return module_spec.GetFileSpec().GetFilename().GetStringRef(); |
| 150 | llvm::StringRef symbol_file = |
| 151 | module_spec.GetSymbolFileSpec().GetFilename().GetStringRef(); |
| 152 | // Remove llvmcache- prefix and hash, keep origin file name |
| 153 | if (symbol_file.starts_with(Prefix: "llvmcache-" )) { |
| 154 | size_t pos = symbol_file.rfind(C: '-'); |
| 155 | if (pos != llvm::StringRef::npos) { |
| 156 | symbol_file = symbol_file.substr(Start: pos + 1); |
| 157 | } |
| 158 | } |
| 159 | return symbol_file; |
| 160 | } |
| 161 | |
| 162 | static std::optional<FileSpec> |
| 163 | GetFileForModule(const ModuleSpec &module_spec, |
| 164 | std::function<std::string(llvm::object::BuildID)> UrlBuilder) { |
| 165 | const UUID &module_uuid = module_spec.GetUUID(); |
| 166 | // Don't bother if we don't have a valid UUID, Debuginfod isn't available, |
| 167 | // or if the 'symbols.enable-external-lookup' setting is false. |
| 168 | if (!module_uuid.IsValid() || !llvm::canUseDebuginfod() || |
| 169 | !ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) |
| 170 | return {}; |
| 171 | |
| 172 | // Grab LLDB's Debuginfod overrides from the |
| 173 | // plugin.symbol-locator.debuginfod.* settings. |
| 174 | PluginProperties &plugin_props = GetGlobalPluginProperties(); |
| 175 | llvm::Expected<std::string> cache_path_or_err = plugin_props.GetCachePath(); |
| 176 | // A cache location is *required*. |
| 177 | if (!cache_path_or_err) |
| 178 | return {}; |
| 179 | std::string cache_path = *cache_path_or_err; |
| 180 | llvm::SmallVector<llvm::StringRef> debuginfod_urls = |
| 181 | llvm::getDefaultDebuginfodUrls(); |
| 182 | std::chrono::milliseconds timeout = plugin_props.GetTimeout(); |
| 183 | |
| 184 | // We're ready to ask the Debuginfod library to find our file. |
| 185 | llvm::object::BuildID build_id(module_uuid.GetBytes()); |
| 186 | std::string url_path = UrlBuilder(build_id); |
| 187 | llvm::StringRef file_name = getFileName(module_spec, url_path); |
| 188 | std::string cache_file_name = llvm::toHex(Input: build_id, LowerCase: true); |
| 189 | if (!file_name.empty()) |
| 190 | cache_file_name += "-" + file_name.str(); |
| 191 | llvm::Expected<std::string> result = llvm::getCachedOrDownloadArtifact( |
| 192 | UniqueKey: cache_file_name, UrlPath: url_path, CacheDirectoryPath: cache_path, DebuginfodUrls: debuginfod_urls, Timeout: timeout); |
| 193 | if (result) |
| 194 | return FileSpec(*result); |
| 195 | |
| 196 | Log *log = GetLog(mask: LLDBLog::Symbols); |
| 197 | auto err_message = llvm::toString(E: result.takeError()); |
| 198 | LLDB_LOGV(log, |
| 199 | "Debuginfod failed to download symbol artifact {0} with error {1}" , |
| 200 | url_path, err_message); |
| 201 | return {}; |
| 202 | } |
| 203 | |
| 204 | std::optional<ModuleSpec> SymbolLocatorDebuginfod::LocateExecutableObjectFile( |
| 205 | const ModuleSpec &module_spec) { |
| 206 | return GetFileForModule(module_spec, UrlBuilder: llvm::getDebuginfodExecutableUrlPath); |
| 207 | } |
| 208 | |
| 209 | std::optional<FileSpec> SymbolLocatorDebuginfod::LocateExecutableSymbolFile( |
| 210 | const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { |
| 211 | return GetFileForModule(module_spec, UrlBuilder: llvm::getDebuginfodDebuginfoUrlPath); |
| 212 | } |
| 213 | |