diff --git a/lldb/packages/Python/lldbsuite/test/decorators.py b/lldb/packages/Python/lldbsuite/test/decorators.py index 8010d743b3a34..2757d0934f48f 100644 --- a/lldb/packages/Python/lldbsuite/test/decorators.py +++ b/lldb/packages/Python/lldbsuite/test/decorators.py @@ -1411,3 +1411,14 @@ def skipIfBuildType(types: list[str]): and configuration.cmake_build_type.lower() in types, "skip on {} build type(s)".format(", ".join(types)), ) + + +def skipUnlessPackageAvailable(name): + """Skip the test case if the named package is not available on the system.""" + available = True + try: + __import__(name) + except ImportError: + available = False + + return unittest.skipUnless(available, f"requires the '{name}' package") diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py b/lldb/packages/Python/lldbsuite/test/lldbtest.py index 460a04eb7444c..c027c669ae5d4 100644 --- a/lldb/packages/Python/lldbsuite/test/lldbtest.py +++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -2049,6 +2049,10 @@ def test_method(self, attrvalue=attrvalue): else: newattrs[attrname] = attrvalue + + if original_testcase.TEST_WITH_PDB_DEBUG_INFO: + newattrs["SHARED_BUILD_TESTCASE"] = False + return super(LLDBTestCaseFactory, cls).__new__(cls, name, bases, newattrs) diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp index da2149a2f1f51..eeb8db4446e1e 100644 --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp @@ -1108,6 +1108,27 @@ std::optional ObjectFilePECOFF::GetDebugLink() { return std::nullopt; } +std::optional ObjectFilePECOFF::GetPDBPath() { + llvm::StringRef pdb_file; + const llvm::codeview::DebugInfo *pdb_info = nullptr; + if (llvm::Error Err = m_binary->getDebugPDBInfo(pdb_info, pdb_file)) { + // DebugInfo section is corrupt. + Log *log = GetLog(LLDBLog::Object); + llvm::StringRef file = m_binary->getFileName(); + LLDB_LOG_ERROR( + log, std::move(Err), + "Failed to read Codeview record for PDB debug info file ({1}): {0}", + file); + return std::nullopt; + } + if (pdb_file.empty()) { + // No DebugInfo section present. + return std::nullopt; + } + return FileSpec(pdb_file, FileSpec::GuessPathStyle(pdb_file).value_or( + FileSpec::Style::native)); +} + uint32_t ObjectFilePECOFF::ParseDependentModules() { ModuleSP module_sp(GetModule()); if (!module_sp) diff --git a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h index 8002e70e604bb..30bd672dc68f8 100644 --- a/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h +++ b/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.h @@ -130,6 +130,8 @@ class ObjectFilePECOFF : public lldb_private::ObjectFile { /// contains it. std::optional GetDebugLink(); + std::optional GetPDBPath(); + uint32_t GetDependentModules(lldb_private::FileSpecList &files) override; lldb_private::Address GetEntryPointAddress() override; diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index 441bae3de4039..ecaa689884dc4 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -39,6 +39,7 @@ #include "llvm/DebugInfo/PDB/Native/ModuleDebugStream.h" #include "llvm/DebugInfo/PDB/Native/NativeSession.h" #include "llvm/DebugInfo/PDB/Native/PDBFile.h" +#include "llvm/DebugInfo/PDB/Native/PublicsStream.h" #include "llvm/DebugInfo/PDB/Native/SymbolStream.h" #include "llvm/DebugInfo/PDB/Native/TpiStream.h" #include "llvm/DebugInfo/PDB/PDB.h" @@ -356,6 +357,11 @@ uint32_t SymbolFileNativePDB::CalculateAbilities() { if (!pdb_file) return 0; + LLDB_LOG( + GetLog(LLDBLog::Symbols), "Loading {0} for {1}", + pdb_file->getFilePath(), + m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath()); + auto expected_index = PdbIndex::create(pdb_file); if (!expected_index) { llvm::consumeError(expected_index.takeError()); @@ -1152,7 +1158,80 @@ lldb::LanguageType SymbolFileNativePDB::ParseLanguage(CompileUnit &comp_unit) { return TranslateLanguage(item->m_compile_opts->getLanguage()); } -void SymbolFileNativePDB::AddSymbols(Symtab &symtab) {} +void SymbolFileNativePDB::AddSymbols(Symtab &symtab) { + auto *section_list = + m_objfile_sp->GetModule()->GetObjectFile()->GetSectionList(); + if (!section_list) + return; + + PublicSym32 last_sym; + size_t last_sym_idx = 0; + lldb::SectionSP section_sp; + + // To estimate the size of a symbol, we use the difference to the next symbol. + // If there's no next symbol or the section/segment changed, the symbol will + // take the remaining space. The estimate can be too high in case there's + // padding between symbols. This similar to the algorithm used by the DIA + // SDK. + auto finish_last_symbol = [&](const PublicSym32 *next) { + if (!section_sp) + return; + Symbol *last = symtab.SymbolAtIndex(last_sym_idx); + if (!last) + return; + + if (next && last_sym.Segment == next->Segment) { + assert(last_sym.Offset <= next->Offset); + last->SetByteSize(next->Offset - last_sym.Offset); + } else { + // the last symbol was the last in its section + assert(section_sp->GetByteSize() >= last_sym.Offset); + assert(!next || next->Segment > last_sym.Segment); + last->SetByteSize(section_sp->GetByteSize() - last_sym.Offset); + } + }; + + // The address map is sorted by the address of a symbol. + for (auto pid : m_index->publics().getAddressMap()) { + PdbGlobalSymId global{pid, true}; + CVSymbol sym = m_index->ReadSymbolRecord(global); + auto kind = sym.kind(); + if (kind != S_PUB32) + continue; + PublicSym32 pub = + llvm::cantFail(SymbolDeserializer::deserializeAs(sym)); + finish_last_symbol(&pub); + + if (!section_sp || last_sym.Segment != pub.Segment) + section_sp = section_list->FindSectionByID(pub.Segment); + + if (!section_sp) + continue; + + lldb::SymbolType type = eSymbolTypeData; + if ((pub.Flags & PublicSymFlags::Function) != PublicSymFlags::None || + (pub.Flags & PublicSymFlags::Code) != PublicSymFlags::None) + type = eSymbolTypeCode; + + last_sym_idx = + symtab.AddSymbol(Symbol(/*symID=*/pid, + /*name=*/pub.Name, + /*type=*/type, + /*external=*/true, + /*is_debug=*/true, + /*is_trampoline=*/false, + /*is_artificial=*/false, + /*section_sp=*/section_sp, + /*value=*/pub.Offset, + /*size=*/0, + /*size_is_valid=*/false, + /*contains_linker_annotations=*/false, + /*flags=*/0)); + last_sym = pub; + } + + finish_last_symbol(nullptr); +} size_t SymbolFileNativePDB::ParseFunctions(CompileUnit &comp_unit) { std::lock_guard guard(GetModuleMutex()); @@ -1772,6 +1851,94 @@ void SymbolFileNativePDB::DumpClangAST(Stream &s, llvm::StringRef filter) { ast_builder->Dump(s, filter); } +void SymbolFileNativePDB::CacheFunctionNames() { + if (!m_func_full_names.IsEmpty()) + return; + + // (segment, code offset) -> gid + std::map, uint32_t> addr_ids; + + // First, find all function references in the globals table. + for (const uint32_t gid : m_index->globals().getGlobalsTable()) { + CVSymbol ref_sym = m_index->symrecords().readRecord(gid); + auto kind = ref_sym.kind(); + if (kind != S_PROCREF && kind != S_LPROCREF) + continue; + + ProcRefSym ref = + llvm::cantFail(SymbolDeserializer::deserializeAs(ref_sym)); + if (ref.Name.empty()) + continue; + + // Find the function this is referencing. + CompilandIndexItem &cci = + m_index->compilands().GetOrCreateCompiland(ref.modi()); + auto iter = cci.m_debug_stream.getSymbolArray().at(ref.SymOffset); + if (iter == cci.m_debug_stream.getSymbolArray().end()) + continue; + kind = iter->kind(); + if (kind != S_GPROC32 && kind != S_LPROC32) + continue; + + ProcSym proc = + llvm::cantFail(SymbolDeserializer::deserializeAs(*iter)); + if ((proc.Flags & ProcSymFlags::IsUnreachable) != ProcSymFlags::None) + continue; + if (proc.Name.empty() || proc.FunctionType.isSimple()) + continue; + + // The function/procedure symbol only contains the demangled name. + // The mangled names are in the publics table. Save the address of this + // function to lookup the mangled name later. + addr_ids.emplace(std::make_pair(proc.Segment, proc.CodeOffset), gid); + + llvm::StringRef basename = MSVCUndecoratedNameParser::DropScope(proc.Name); + if (basename.empty()) + basename = proc.Name; + + m_func_base_names.Append(ConstString(basename), gid); + m_func_full_names.Append(ConstString(proc.Name), gid); + + // To see if this is a member function, check the type. + auto type = m_index->tpi().getType(proc.FunctionType); + if (type.kind() == LF_MFUNCTION) { + MemberFunctionRecord mfr; + llvm::cantFail( + TypeDeserializer::deserializeAs(type, mfr)); + if (!mfr.getThisType().isNoneType()) + m_func_method_names.Append(ConstString(basename), gid); + } + } + + // The publics stream contains all mangled function names and their address. + for (auto pid : m_index->publics().getPublicsTable()) { + PdbGlobalSymId global{pid, true}; + CVSymbol sym = m_index->ReadSymbolRecord(global); + auto kind = sym.kind(); + if (kind != S_PUB32) + continue; + PublicSym32 pub = + llvm::cantFail(SymbolDeserializer::deserializeAs(sym)); + // We only care about mangled names - if the name isn't mangled, it's + // already in the full name map. + if (!Mangled::IsMangledName(pub.Name)) + continue; + + // Check if this symbol is for one of our functions. + auto it = addr_ids.find({pub.Segment, pub.Offset}); + if (it != addr_ids.end()) + m_func_full_names.Append(ConstString(pub.Name), it->second); + } + + // Sort them before value searching is working properly. + m_func_full_names.Sort(); + m_func_full_names.SizeToFit(); + m_func_method_names.Sort(); + m_func_method_names.SizeToFit(); + m_func_base_names.Sort(); + m_func_base_names.SizeToFit(); +} + void SymbolFileNativePDB::FindGlobalVariables( ConstString name, const CompilerDeclContext &parent_decl_ctx, uint32_t max_matches, VariableList &variables) { @@ -1808,34 +1975,60 @@ void SymbolFileNativePDB::FindFunctions( if (name_type_mask & eFunctionNameTypeFull) name = lookup_info.GetName(); - // For now we only support lookup by method name or full name. if (!(name_type_mask & eFunctionNameTypeFull || + name_type_mask & eFunctionNameTypeBase || name_type_mask & eFunctionNameTypeMethod)) return; + CacheFunctionNames(); - using SymbolAndOffset = std::pair; + std::set resolved_ids; // avoid duplicate lookups + auto resolve_from = [&](UniqueCStringMap &Names) { + std::vector ids; + if (!Names.GetValues(name, ids)) + return; - std::vector matches = m_index->globals().findRecordsByName( - name.GetStringRef(), m_index->symrecords()); - for (const SymbolAndOffset &match : matches) { - if (match.second.kind() != S_PROCREF && match.second.kind() != S_LPROCREF) - continue; - ProcRefSym proc(match.second.kind()); - cantFail(SymbolDeserializer::deserializeAs(match.second, proc)); + for (uint32_t id : ids) { + if (!resolved_ids.insert(id).second) + continue; - if (!IsValidRecord(proc)) - continue; + PdbGlobalSymId global{id, false}; + if (parent_decl_ctx.IsValid() && + GetDeclContextContainingUID(toOpaqueUid(global)) != parent_decl_ctx) + continue; - CompilandIndexItem &cci = - m_index->compilands().GetOrCreateCompiland(proc.modi()); - SymbolContext sc; + CVSymbol sym = m_index->ReadSymbolRecord(global); + auto kind = sym.kind(); + lldbassert(kind == S_PROCREF || kind == S_LPROCREF); - sc.comp_unit = GetOrCreateCompileUnit(cci).get(); - PdbCompilandSymId func_id(proc.modi(), proc.SymOffset); - sc.function = GetOrCreateFunction(func_id, *sc.comp_unit).get(); + ProcRefSym proc = + cantFail(SymbolDeserializer::deserializeAs(sym)); - sc_list.Append(sc); - } + if (!IsValidRecord(proc)) + continue; + + CompilandIndexItem &cci = + m_index->compilands().GetOrCreateCompiland(proc.modi()); + SymbolContext sc; + + sc.comp_unit = GetOrCreateCompileUnit(cci).get(); + if (!sc.comp_unit) + continue; + + PdbCompilandSymId func_id(proc.modi(), proc.SymOffset); + sc.function = GetOrCreateFunction(func_id, *sc.comp_unit).get(); + if (!sc.function) + continue; + + sc_list.Append(sc); + } + }; + + if (name_type_mask & eFunctionNameTypeFull) + resolve_from(m_func_full_names); + if (name_type_mask & eFunctionNameTypeBase) + resolve_from(m_func_base_names); + if (name_type_mask & eFunctionNameTypeMethod) + resolve_from(m_func_method_names); } void SymbolFileNativePDB::FindFunctions(const RegularExpression ®ex, diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h index bd672bc959ed9..f33bfba2f9465 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.h @@ -259,6 +259,8 @@ class SymbolFileNativePDB : public SymbolFileCommon { fn); void ParseInlineSite(PdbCompilandSymId inline_site_id, Address func_addr); + + void CacheFunctionNames(); llvm::BumpPtrAllocator m_allocator; @@ -280,6 +282,13 @@ class SymbolFileNativePDB : public SymbolFileCommon { llvm::DenseMap> m_inline_sites; llvm::DenseMap m_parent_types; + + /// mangled name/full function name -> Global ID(s) + lldb_private::UniqueCStringMap m_func_full_names; + /// basename -> Global ID(s) + lldb_private::UniqueCStringMap m_func_base_names; + /// method basename -> Global ID(s) + lldb_private::UniqueCStringMap m_func_method_names; }; } // namespace npdb diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp index e24a4249a731f..17a49b851e415 100644 --- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp @@ -153,7 +153,8 @@ uint32_t SymbolFilePDB::CalculateAbilities() { if (!m_session_up) { // Lazily load and match the PDB file, but only do this once. - std::string exePath = m_objfile_sp->GetFileSpec().GetPath(); + std::string exePath = + m_objfile_sp->GetModule()->GetObjectFile()->GetFileSpec().GetPath(); auto error = loadDataForEXE(PDB_ReaderType::DIA, llvm::StringRef(exePath), m_session_up); if (error) { @@ -163,6 +164,10 @@ uint32_t SymbolFilePDB::CalculateAbilities() { return 0; // See if any symbol file is specified through `--symfile` option. FileSpec symfile = module_sp->GetSymbolFileFileSpec(); + // If m_objfile_sp is a supplemental ObjectFilePDB (e.g. located via + // SymStore by SymbolVendorPECOFF), its file spec is the PDB path. + if (!symfile && m_objfile_sp.get() != module_sp->GetObjectFile()) + symfile = m_objfile_sp->GetFileSpec(); if (!symfile) return 0; error = loadDataForPDB(PDB_ReaderType::DIA, @@ -201,8 +206,10 @@ uint32_t SymbolFilePDB::CalculateAbilities() { } void SymbolFilePDB::InitializeObject() { - lldb::addr_t obj_load_address = - m_objfile_sp->GetBaseAddress().GetFileAddress(); + lldb::addr_t obj_load_address = m_objfile_sp->GetModule() + ->GetObjectFile() + ->GetBaseAddress() + .GetFileAddress(); lldbassert(obj_load_address && obj_load_address != LLDB_INVALID_ADDRESS); m_session_up->setLoadAddress(obj_load_address); if (!m_global_scope_up) @@ -1392,7 +1399,8 @@ void SymbolFilePDB::AddSymbols(lldb_private::Symtab &symtab) { if (!results) return; - auto section_list = m_objfile_sp->GetSectionList(); + auto section_list = + m_objfile_sp->GetModule()->GetObjectFile()->GetSectionList(); if (!section_list) return; diff --git a/lldb/source/Plugins/SymbolLocator/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/CMakeLists.txt index 3b466f71dca58..9b9ec470b86a9 100644 --- a/lldb/source/Plugins/SymbolLocator/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/CMakeLists.txt @@ -6,6 +6,7 @@ set_property(DIRECTORY PROPERTY LLDB_PLUGIN_KIND SymbolLocator) # prevents an unstripped binary from being requested from the Debuginfod # provider. add_subdirectory(Debuginfod) +add_subdirectory(SymStore) add_subdirectory(Default) if (CMAKE_SYSTEM_NAME MATCHES "Darwin") add_subdirectory(DebugSymbols) diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt index f07e93e131376..29ab65bcac142 100644 --- a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt @@ -14,6 +14,7 @@ add_lldb_library(lldbPluginSymbolLocatorDebuginfod PLUGIN lldbHost lldbSymbol LLVMDebuginfod + LLVMHTTP ) add_dependencies(lldbPluginSymbolLocatorDebuginfod diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp b/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp index f9aa6b1a98765..0938c39b7f92e 100644 --- a/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp +++ b/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp @@ -15,7 +15,7 @@ #include "lldb/Utility/Log.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" using namespace lldb; using namespace lldb_private; diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt new file mode 100644 index 0000000000000..66a3d8640816f --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt @@ -0,0 +1,21 @@ +lldb_tablegen(SymbolLocatorSymStoreProperties.inc -gen-lldb-property-defs + SOURCE SymbolLocatorSymStoreProperties.td + TARGET LLDBPluginSymbolLocatorSymStorePropertiesGen) + +lldb_tablegen(SymbolLocatorSymStorePropertiesEnum.inc -gen-lldb-property-enum-defs + SOURCE SymbolLocatorSymStoreProperties.td + TARGET LLDBPluginSymbolLocatorSymStorePropertiesEnumGen) + +add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN + SymbolLocatorSymStore.cpp + + LINK_LIBS + lldbCore + lldbHost + lldbSymbol + LLVMHTTP + ) + +add_dependencies(lldbPluginSymbolLocatorSymStore + LLDBPluginSymbolLocatorSymStorePropertiesGen + LLDBPluginSymbolLocatorSymStorePropertiesEnumGen) diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp new file mode 100644 index 0000000000000..4dda1f763b453 --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -0,0 +1,531 @@ +//===----------------------------------------------------------------------===// +// +// 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 "SymbolLocatorSymStore.h" + +#include "lldb/Core/ModuleList.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/FileSystem.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Utility/Args.h" +#include "lldb/Utility/LLDBLog.h" +#include "lldb/Utility/Log.h" +#include "lldb/Utility/UUID.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/HTTP/HTTPClient.h" +#include "llvm/HTTP/StreamedHTTPResponseHandler.h" +#include "llvm/Support/Caching.h" +#include "llvm/Support/Endian.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb; +using namespace lldb_private; + +LLDB_PLUGIN_DEFINE(SymbolLocatorSymStore) + +namespace { + +#define LLDB_PROPERTIES_symbollocatorsymstore +#include "SymbolLocatorSymStoreProperties.inc" + +enum { +#define LLDB_PROPERTIES_symbollocatorsymstore +#include "SymbolLocatorSymStorePropertiesEnum.inc" +}; + +class PluginProperties : public Properties { +public: + static llvm::StringRef GetSettingName() { + return SymbolLocatorSymStore::GetPluginNameStatic(); + } + + PluginProperties() { + m_collection_sp = std::make_shared(GetSettingName()); + m_collection_sp->Initialize(g_symbollocatorsymstore_properties); + } + + Args GetURLs() const { + Args urls; + m_collection_sp->GetPropertyAtIndexAsArgs(ePropertySymStoreURLs, urls); + return urls; + } + + std::string GetCachePath() const { + OptionValueString *s = + m_collection_sp->GetPropertyAtIndexAsOptionValueString( + ePropertyCachePath); + if (s && !s->GetCurrentValueAsRef().empty()) + return s->GetCurrentValue(); + return SymbolLocatorSymStore::GetSystemDefaultCachePath(); + } + + std::optional GetTLSCertFingerprint() const { + OptionValueString *s = + m_collection_sp->GetPropertyAtIndexAsOptionValueString( + ePropertyTLSCertFingerprint); + if (!s) + return {}; + llvm::StringRef val = s->GetCurrentValueAsRef(); + if (val.empty()) + return {}; + if (val.size() != 64 || !llvm::all_of(val, llvm::isHexDigit)) { + Debugger::ReportWarning(llvm::formatv( + "plugin.symbol-locator.symstore.tls-cert-fingerprint: expected a " + "64-character hex string (SHA-256), but got '{0}', ignoring", + val)); + return {}; + } + return val.lower(); + } +}; + +} // namespace + +static PluginProperties &GetGlobalPluginProperties() { + static PluginProperties g_settings; + return g_settings; +} + +SymbolLocatorSymStore::SymbolLocatorSymStore() : SymbolLocator() {} + +void SymbolLocatorSymStore::Initialize() { + PluginManager::RegisterPlugin( + GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, + nullptr, LocateExecutableSymbolFile, nullptr, nullptr, + SymbolLocatorSymStore::DebuggerInitialize); + llvm::HTTPClient::initialize(); + + std::string default_cache = GetSystemDefaultCachePath(); + if (std::error_code ec = llvm::sys::fs::create_directories(default_cache)) { + Debugger::ReportWarning(llvm::formatv( + "default SymStore cache directory '{0}' is not accessible: {1}", + default_cache, ec.message())); + } +} + +void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) { + if (!PluginManager::GetSettingForSymbolLocatorPlugin( + debugger, PluginProperties::GetSettingName())) { + constexpr bool is_global_setting = true; + PluginManager::CreateSettingForSymbolLocatorPlugin( + debugger, GetGlobalPluginProperties().GetValueProperties(), + "Properties for the SymStore Symbol Locator plug-in.", + is_global_setting); + } +} + +void SymbolLocatorSymStore::Terminate() { + PluginManager::UnregisterPlugin(CreateInstance); + llvm::HTTPClient::cleanup(); +} + +llvm::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() { + return "Symbol locator for PDB in SymStore"; +} + +SymbolLocator *SymbolLocatorSymStore::CreateInstance() { + return new SymbolLocatorSymStore(); +} + +namespace { + +SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source) { + SymbolLocatorSymStore::LookupEntry entry; + entry.source = source.str(); + entry.cache = std::nullopt; + return entry; +} + +SymbolLocatorSymStore::LookupEntry MakeLookupEntry(llvm::StringRef source, + llvm::StringRef cache) { + SymbolLocatorSymStore::LookupEntry entry; + entry.source = source.str(); + entry.cache = cache.str(); + return entry; +} + +std::vector GetGlobalLookupOrder() { + std::vector result; + + const char *sym_path = std::getenv("_NT_SYMBOL_PATH"); + for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(sym_path)) + result.push_back(std::move(entry)); + + const char *alt_path = std::getenv("_NT_ALT_SYMBOL_PATH"); + for (auto entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(alt_path)) + result.push_back(std::move(entry)); + + for (const auto &url : GetGlobalPluginProperties().GetURLs()) + result.push_back(MakeLookupEntry(url.ref())); + + return result; +} + +std::optional +ParseSrvEntry(llvm::StringRef entry) { + llvm::SmallVector parts; + entry.trim().split(parts, '*'); + + // Format is: srv*[LocalCache*]SymbolStore + switch (parts.size()) { + case 2: + return MakeLookupEntry(parts[1]); + case 3: { + // Fall back to the configured default cache for empty values. + if (parts[1].empty()) + return MakeLookupEntry(parts[2], + GetGlobalPluginProperties().GetCachePath()); + return MakeLookupEntry(parts[2], parts[1]); + } + default: + return {}; // Ignore entries with invalid number of parts. + } +} + +std::optional ParseCacheEntry(llvm::StringRef entry) { + llvm::SmallVector parts; + entry.trim().split(parts, '*'); + + // Ignore entries with invalid number of parts. + if (parts.size() > 2) + return {}; + + // Empty cache* deliberatly specifies the default cache path. + llvm::StringRef value; + if (parts.size() == 2) + value = parts.back(); + + // Fall back to LLDB's default cache for empty values. + if (value.empty()) + return GetGlobalPluginProperties().GetCachePath(); + + return value.str(); +} + +// RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and +// 4-byte age: +// 12345678-1234-5678-9ABC-DEF012345678-00000001 +// +// SymStore key is a string with no separators and age as decimal: +// 12345678123456789ABCDEF0123456781 +// +std::string FormatSymStoreKey(const UUID &uuid) { + llvm::ArrayRef bytes = uuid.GetBytes(); + uint32_t age = llvm::support::endian::read32be(bytes.data() + 16); + constexpr bool lower_case = false; + return llvm::toHex(bytes.slice(0, 16), lower_case) + std::to_string(age); +} + +bool HasUnsafeCharacters(llvm::StringRef s) { + for (unsigned char c : s) { + // RFC 3986 unreserved characters are safe for file names and URLs. + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || + c == '~') { + continue; + } + + return true; + } + + // Avoid path semantics issues. + return s == "." || s == ".."; +} + +std::optional +RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, + llvm::StringRef pdb_name) { + using namespace llvm::sys; + + // Make sure URL will be valid, portable, and compatible with symbol servers. + if (HasUnsafeCharacters(pdb_name)) { + Debugger::ReportWarning(llvm::formatv( + "rejecting HTTP lookup for PDB file due to unsafe characters in " + "name: {0}", + pdb_name)); + return {}; + } + + // Download into a temporary file. + llvm::SmallString<128> tmp_file; + constexpr bool erase_on_reboot = true; + path::system_temp_directory(erase_on_reboot, tmp_file); + path::append(tmp_file, llvm::formatv("lldb_symstore_{0}_{1}", key, pdb_name)); + + // Server has SymStore directory structure with forward slashes as separators. + std::string source_url = + llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key); + + if (!llvm::HTTPClient::isAvailable()) { + Debugger::ReportWarning( + "HTTP client is not available for SymStore download"); + return {}; + } + + llvm::HTTPClient client; + // TODO: Since PDBs can be huge, we should distinguish between resolve, + // connect, send and receive. + client.setTimeout(std::chrono::seconds(60)); + + llvm::StreamedHTTPResponseHandler Handler( + [dest = tmp_file.str().str()]() + -> llvm::Expected> { + std::error_code ec; + auto os = std::make_unique(dest, ec); + if (ec) + return llvm::createStringError(ec, "Failed to open file for writing"); + return std::make_unique(std::move(os), dest); + }, + client); + + llvm::HTTPRequest request(source_url); + request.PinnedCertFingerprint = + GetGlobalPluginProperties().GetTLSCertFingerprint(); + if (llvm::Error Err = client.perform(request, Handler)) { + Debugger::ReportWarning( + llvm::formatv("failed to download from SymStore '{0}': {1}", source_url, + llvm::toString(std::move(Err)))); + return {}; + } + if (llvm::Error Err = Handler.commit()) { + Debugger::ReportWarning( + llvm::formatv("failed to download from SymStore '{0}': {1}", source_url, + llvm::toString(std::move(Err)))); + return {}; + } + + unsigned responseCode = client.responseCode(); + switch (responseCode) { + case 404: + return {}; // file not found + case 200: + return FileSpec(tmp_file.str()); // success + default: + Debugger::ReportWarning(llvm::formatv( + "failed to download from SymStore '{0}': response code {1}", source_url, + responseCode)); + return {}; + } +} + +std::optional FindFileInLocalSymStore(llvm::StringRef root_dir, + llvm::StringRef key, + llvm::StringRef pdb_name) { + llvm::SmallString<256> path; + llvm::sys::path::append(path, root_dir, pdb_name, key, pdb_name); + FileSpec spec(path); + if (!FileSystem::Instance().Exists(spec)) + return {}; + + return spec; +} + +std::optional MoveToLocalSymStore(llvm::StringRef cache, + llvm::StringRef key, + llvm::StringRef pdb_name, + FileSpec tmp_file) { + // Caches have SymStore directory structure: cache/pdb_name/key/pdb_name + llvm::SmallString<256> dest_dir; + llvm::sys::path::append(dest_dir, cache, pdb_name, key); + if (std::error_code ec = llvm::sys::fs::create_directories(dest_dir)) { + Debugger::ReportWarning( + llvm::formatv("failed to create SymStore cache directory '{0}': {1}", + dest_dir, ec.message())); + return {}; + } + + llvm::SmallString<256> dest; + llvm::sys::path::append(dest, dest_dir, pdb_name); + std::error_code ec = llvm::sys::fs::rename(tmp_file.GetPath(), dest); + + // Fall back to copy+delete if we move to a different volume. + if (ec == std::errc::cross_device_link) { + ec = llvm::sys::fs::copy_file(tmp_file.GetPath(), dest); + if (!ec) + llvm::sys::fs::remove(tmp_file.GetPath()); + } + if (ec) { + Debugger::ReportWarning( + llvm::formatv("failed to move '{0}' to SymStore cache '{1}': {2}", + tmp_file.GetPath(), dest, ec.message())); + return {}; + } + + return FileSpec(dest.str()); +} + +std::string SelectSymStoreCache(std::optional sympath_cache) { + llvm::SmallVector candidates; + + // Prefer user cache from symbol path. + if (sympath_cache) { + assert(!sympath_cache->empty() && "Empty entries resolve to default cache"); + candidates.push_back(*sympath_cache); + } + + // Fallback to configured cache from settings. + candidates.push_back(GetGlobalPluginProperties().GetCachePath()); + + Log *log = GetLog(LLDBLog::Symbols); + for (const auto &path : candidates) { + if (llvm::sys::fs::is_directory(path)) + return path; + if (std::error_code ec = llvm::sys::fs::create_directories(path)) { + LLDB_LOG(log, "Ignoring invalid SymStore cache directory '{0}': {1}", + path, ec.message()); + continue; + } + return path; + } + + // Last resort is the system default location. + return SymbolLocatorSymStore::GetSystemDefaultCachePath(); +} + +std::optional +LocateSymStoreEntry(const SymbolLocatorSymStore::LookupEntry &entry, + llvm::StringRef key, llvm::StringRef pdb_name) { + Log *log = GetLog(LLDBLog::Symbols); + llvm::StringRef url = entry.source; + if (url.starts_with("http://") || url.starts_with("https://")) { + // Check cache first. + std::string cache_path = SelectSymStoreCache(entry.cache); + if (auto spec = FindFileInLocalSymStore(cache_path, key, pdb_name)) { + LLDB_LOG(log, "Found {0} in SymStore cache {1}", pdb_name, cache_path); + return *spec; + } + + // Download and move to cache. + if (auto tmp_file = RequestFileFromSymStoreServerHTTP(url, key, pdb_name)) { + LLDB_LOG(log, "Downloaded {0} from SymStore {1}", pdb_name, url); + auto spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file); + if (!spec) { + // Try the fallback and eventually rather cancel than loading the tmp + // file, since it might disappear or get overwritten. + cache_path = SymbolLocatorSymStore::GetSystemDefaultCachePath(); + spec = MoveToLocalSymStore(cache_path, key, pdb_name, *tmp_file); + if (!spec) + return {}; + } + LLDB_LOG(log, "Added {0} to SymStore cache {1}", pdb_name, cache_path); + return *spec; + } + + return {}; + } + + llvm::StringRef file = entry.source; + if (file.starts_with("file://")) + file = file.drop_front(7); + if (auto spec = FindFileInLocalSymStore(file, key, pdb_name)) { + LLDB_LOG(log, "Found {0} in local SymStore {1}", pdb_name, file); + return *spec; + } + + return {}; +} + +} // namespace + +std::optional SymbolLocatorSymStore::LocateExecutableSymbolFile( + const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { + const UUID &uuid = module_spec.GetUUID(); + if (!uuid.IsValid() || + !ModuleList::GetGlobalModuleListProperties().GetEnableExternalLookup()) + return {}; + + Log *log = GetLog(LLDBLog::Symbols); + std::string pdb_name = + module_spec.GetSymbolFileSpec().GetFilename().GetStringRef().str(); + if (pdb_name.empty()) { + LLDB_LOG(log, "Failed to resolve symbol PDB module: PDB name empty"); + return {}; + } + + LLDB_LOG(log, "LocateExecutableSymbolFile {0} with UUID {1}", pdb_name, + uuid.GetAsString()); + if (uuid.GetBytes().size() != 20) { + LLDB_LOG(log, "Failed to resolve symbol PDB module: UUID invalid"); + return {}; + } + + std::string key = FormatSymStoreKey(uuid); + for (const LookupEntry &entry : GetGlobalLookupOrder()) { + if (auto spec = LocateSymStoreEntry(entry, key, pdb_name)) + return *spec; + } + + return {}; +} + +std::vector +SymbolLocatorSymStore::ParseEnvSymbolPaths(llvm::StringRef val) { + if (val.empty()) + return {}; + + std::vector result; + std::optional implicit_cache; + llvm::SmallVector entries; + val.split(entries, ';'); + + for (llvm::StringRef raw : entries) { + llvm::StringRef entry = raw.trim(); + if (entry.empty()) + continue; + + // Explicit cache directives apply to all subsequent srv* entries that don't + // set their own explicit cache. + if (entry.starts_with_insensitive("cache*")) { + if (auto cache = ParseCacheEntry(entry)) + implicit_cache = *cache; + continue; + } + + // SymStore directives with explicit interpreters are unsupported + // explicitly. + if (entry.starts_with_insensitive("symsrv*")) { + Debugger::ReportWarning( + llvm::formatv("ignoring unsupported entry in env: {0}", entry)); + continue; + } + + // SymStore server directives may include an explicit cache. + // Format is: srv*[LocalCache*]SymbolStore + if (entry.starts_with_insensitive("srv*")) { + if (auto lookup_entry = ParseSrvEntry(entry)) { + if (!lookup_entry->cache && implicit_cache) + lookup_entry->cache = implicit_cache; + result.push_back(*lookup_entry); + } + continue; + } + + // Plain local paths aren't cached. + result.push_back(MakeLookupEntry(entry)); + } + + return result; +} + +std::string SymbolLocatorSymStore::GetSystemDefaultCachePath() { + // Fall back to the platform cache directory. + llvm::SmallString<128> cache_dir; + if (llvm::sys::path::cache_directory(cache_dir)) { + llvm::sys::path::append(cache_dir, "lldb", "symstore"); + return cache_dir.str().str(); + } + // Last resort: use a subdirectory of the system temp directory. + constexpr bool erase_on_reboot = false; + llvm::sys::path::system_temp_directory(erase_on_reboot, cache_dir); + llvm::sys::path::append(cache_dir, "lldb", "symstore"); + return cache_dir.str().str(); +} diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h new file mode 100644 index 0000000000000..80651c6ec5f5c --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SYMSTORE_SYMBOLLOCATORSYMSTORE_H +#define LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SYMSTORE_SYMBOLLOCATORSYMSTORE_H + +#include "lldb/Core/Debugger.h" +#include "lldb/Symbol/SymbolLocator.h" +#include "lldb/lldb-private.h" + +namespace lldb_private { + +/// This plugin implements lookup in Microsoft SymStore instances. This can work +/// cross-platform and for arbitrary debug info formats, but the focus is on PDB +/// with PE/COFF binaries on Windows. +class SymbolLocatorSymStore : public SymbolLocator { +public: + SymbolLocatorSymStore(); + + static void Initialize(); + static void Terminate(); + static void DebuggerInitialize(Debugger &debugger); + + static llvm::StringRef GetPluginNameStatic() { return "symstore"; } + static llvm::StringRef GetPluginDescriptionStatic(); + + static lldb_private::SymbolLocator *CreateInstance(); + + /// PluginInterface protocol. + /// \{ + llvm::StringRef GetPluginName() override { return GetPluginNameStatic(); } + /// \} + + // Locate the symbol file given a module specification. + // + // Locating the file should happen only on the local computer or using the + // current computers global settings. + static std::optional + LocateExecutableSymbolFile(const ModuleSpec &module_spec, + const FileSpecList &default_search_paths); + + struct LookupEntry { + std::string source; + std::optional cache; + }; + + static std::vector ParseEnvSymbolPaths(llvm::StringRef val); + static std::string GetSystemDefaultCachePath(); +}; + +} // namespace lldb_private + +#endif // LLDB_SOURCE_PLUGINS_SYMBOLLOCATOR_SYMSTORE_SYMBOLLOCATORSYMSTORE_H diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td new file mode 100644 index 0000000000000..6da63565d500c --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td @@ -0,0 +1,14 @@ +include "../../../../include/lldb/Core/PropertiesBase.td" + +let Definition = "symbollocatorsymstore" in { + def SymStoreURLs : Property<"urls", "Array">, + ElementType<"String">, + Desc<"List of local symstore directories to query after " + "_NT_SYMBOL_PATH">; + def CachePath : Property<"cache", "String">, + DefaultStringValue<"">, + Desc<"Default cache directory for downloaded symbol files. Used when no cache is specified in _NT_SYMBOL_PATH.">; + def TLSCertFingerprint : Property<"tls-cert-fingerprint", "String">, + DefaultStringValue<"">, + Desc<"SHA-256 fingerprint (lowercase hex, no separators) of the symbol server's TLS certificate. When set, LLDB will accept an HTTPS server that presents this self-signed certificate even if it is not trusted by the system certificate store (Windows only).">; +} diff --git a/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp b/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp index 20ccfa54a106c..87436da443d91 100644 --- a/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp +++ b/lldb/source/Plugins/SymbolVendor/PECOFF/SymbolVendorPECOFF.cpp @@ -69,8 +69,20 @@ SymbolVendorPECOFF::CreateInstance(const lldb::ModuleSP &module_sp, lldb::eSectionTypeDWARFDebugInfo, true)) return nullptr; + // Otherwise, we try to locate it. + FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths(); + // If the module specified a filespec, use that. FileSpec fspec = module_sp->GetSymbolFileFileSpec(); + // Otherwise, if this is CodeView, use the PDB path and set the module + // directory as the first fallback lookup location. + if (!fspec) { + if (auto pdb_spec = obj_file->GetPDBPath()) { + fspec = *pdb_spec; + if (ConstString dir = obj_file->GetFileSpec().GetDirectory()) + search_paths.Insert(0, FileSpec(dir)); + } + } // Otherwise, try gnu_debuglink, if one exists. if (!fspec) fspec = obj_file->GetDebugLink().value_or(FileSpec()); @@ -84,7 +96,6 @@ SymbolVendorPECOFF::CreateInstance(const lldb::ModuleSP &module_sp, FileSystem::Instance().Resolve(module_spec.GetFileSpec()); module_spec.GetSymbolFileSpec() = fspec; module_spec.GetUUID() = uuid; - FileSpecList search_paths = Target::GetDefaultDebugFileSearchPaths(); FileSpec dsym_fspec = PluginManager::LocateExecutableSymbolFile( module_spec, search_paths, module_sp->GetSymbolLocatorStatistics()); if (!dsym_fspec) @@ -101,31 +112,30 @@ SymbolVendorPECOFF::CreateInstance(const lldb::ModuleSP &module_sp, // This objfile is for debugging purposes. dsym_objfile_sp->SetType(ObjectFile::eTypeDebugInfo); - // Get the module unified section list and add our debug sections to - // that. + // For DWARF get the module unified section list and add our debug sections + // to that. SectionList *module_section_list = module_sp->GetSectionList(); SectionList *objfile_section_list = dsym_objfile_sp->GetSectionList(); - if (!objfile_section_list || !module_section_list) - return nullptr; - - static const SectionType g_sections[] = { - eSectionTypeDWARFDebugAbbrev, eSectionTypeDWARFDebugAranges, - eSectionTypeDWARFDebugFrame, eSectionTypeDWARFDebugInfo, - eSectionTypeDWARFDebugLine, eSectionTypeDWARFDebugLoc, - eSectionTypeDWARFDebugLocLists, eSectionTypeDWARFDebugMacInfo, - eSectionTypeDWARFDebugNames, eSectionTypeDWARFDebugPubNames, - eSectionTypeDWARFDebugPubTypes, eSectionTypeDWARFDebugRanges, - eSectionTypeDWARFDebugStr, eSectionTypeDWARFDebugTypes, - }; - for (SectionType section_type : g_sections) { - if (SectionSP section_sp = - objfile_section_list->FindSectionByType(section_type, true)) { - if (SectionSP module_section_sp = - module_section_list->FindSectionByType(section_type, true)) - module_section_list->ReplaceSection(module_section_sp->GetID(), - section_sp); - else - module_section_list->AddSection(section_sp); + if (objfile_section_list && module_section_list) { + static const SectionType g_sections[] = { + eSectionTypeDWARFDebugAbbrev, eSectionTypeDWARFDebugAranges, + eSectionTypeDWARFDebugFrame, eSectionTypeDWARFDebugInfo, + eSectionTypeDWARFDebugLine, eSectionTypeDWARFDebugLoc, + eSectionTypeDWARFDebugLocLists, eSectionTypeDWARFDebugMacInfo, + eSectionTypeDWARFDebugNames, eSectionTypeDWARFDebugPubNames, + eSectionTypeDWARFDebugPubTypes, eSectionTypeDWARFDebugRanges, + eSectionTypeDWARFDebugStr, eSectionTypeDWARFDebugTypes, + }; + for (SectionType section_type : g_sections) { + if (SectionSP section_sp = + objfile_section_list->FindSectionByType(section_type, true)) { + if (SectionSP module_section_sp = + module_section_list->FindSectionByType(section_type, true)) + module_section_list->ReplaceSection(module_section_sp->GetID(), + section_sp); + else + module_section_list->AddSection(section_sp); + } } } diff --git a/lldb/test/API/functionalities/multiple-slides/TestMultipleSlides.py b/lldb/test/API/functionalities/multiple-slides/TestMultipleSlides.py index 3d6b27fe68a1b..5fd2b767a6237 100644 --- a/lldb/test/API/functionalities/multiple-slides/TestMultipleSlides.py +++ b/lldb/test/API/functionalities/multiple-slides/TestMultipleSlides.py @@ -29,10 +29,13 @@ def test_mulitple_slides(self): first_sym.GetEndAddress().GetOffset() - first_sym.GetStartAddress().GetOffset() ) + int_size = target.FindFirstType("int").GetByteSize() + self.assertGreaterEqual(first_size, 2048 * int_size) second_size = ( second_sym.GetEndAddress().GetOffset() - second_sym.GetStartAddress().GetOffset() ) + self.assertGreaterEqual(second_size, 2048 * int_size) # View the first element of `first` and `second` while # they have no load address set. diff --git a/lldb/test/API/symstore/Makefile b/lldb/test/API/symstore/Makefile new file mode 100644 index 0000000000000..c9319d6e6888a --- /dev/null +++ b/lldb/test/API/symstore/Makefile @@ -0,0 +1,2 @@ +C_SOURCES := main.c +include Makefile.rules diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py new file mode 100644 index 0000000000000..83a3b71b0adfb --- /dev/null +++ b/lldb/test/API/symstore/TestSymStore.py @@ -0,0 +1,485 @@ +import datetime +import http.server +import ipaddress +import os +import shutil +import socketserver +import ssl +import sys +import threading +from functools import partial + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +""" +Test debug symbol acquisition from a local SymStore repository. This can work +cross-platform and for arbitrary debug info formats. We only support PDB +currently. +""" + + +class MockedSymStore: + """ + Context Manager to populate a file structure equivalent to SymStore.exe + """ + + def __init__(self, test, exe, pdb): + self._test = test + self._exe = exe + self._pdb = pdb + self.cache_dir = test.getBuildArtifact("cache") + + def get_key_pdb(self, exe): + """ + Module UUID: 12345678-1234-5678-9ABC-DEF012345678-00000001 + To SymStore key: 12345678123456789ABCDEF0123456781 + """ + spec = lldb.SBModuleSpec() + spec.SetFileSpec(lldb.SBFileSpec(self._test.getBuildArtifact(exe))) + module = lldb.SBModule(spec) + raw = module.GetUUIDString().replace("-", "").upper() + if len(raw) != 40: + raise RuntimeError("Unexpected number of bytes in embedded UUID") + guid_hex = raw[:32] + age = int(raw[32:], 16) + return guid_hex + str(age) + + def __enter__(self): + """ + Mock local symstore directory tree, move PDB there and report path. + """ + key = None + if self._test.getDebugInfo() == "pdb": + key = self.get_key_pdb(self._exe) + self._test.assertIsNotNone(key) + symstore_dir = self._test.getBuildArtifact("symstore") + pdb_dir = os.path.join(symstore_dir, self._pdb, key) + os.makedirs(pdb_dir, exist_ok=True) + shutil.move( + self._test.getBuildArtifact(self._pdb), + os.path.join(pdb_dir, self._pdb), + ) + # We always configure a valid fallback cache, because we might not have + # permission to write outside the test directory. + self._test.runCmd( + f"settings set plugin.symbol-locator.symstore.cache {self.cache_dir}" + ) + return symstore_dir + + def __exit__(self, *exc_info): + """ + Reset settings + """ + self._test.runCmd("settings clear plugin.symbol-locator.symstore") + + +class NtSymbolPath: + """ + Context Manager to temporarily set the _NT_SYMBOL_PATH environment variable. + """ + + def __init__(self, value): + self._value = value + self._saved = None + + def __enter__(self): + self._saved = os.environ.get("_NT_SYMBOL_PATH") + os.environ["_NT_SYMBOL_PATH"] = self._value + + def __exit__(self, *exc_info): + if self._saved is None: + os.environ.pop("_NT_SYMBOL_PATH", None) + else: + os.environ["_NT_SYMBOL_PATH"] = self._saved + + +class HTTPServer: + """ + Context Manager to serve a local directory tree via HTTP. + """ + + def __init__(self, dir, handler=None): + address = ("localhost", 0) # auto-select free port + if handler is None: + handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir) + self._server = socketserver.ThreadingTCPServer(address, handler) + self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) + + def __enter__(self): + self._thread.start() + host, port = self._server.server_address + return f"http://{host}:{port}" + + def __exit__(self, *exc_info): + if self._server: + self._server.shutdown() + self._server.server_close() + if self._thread: + self._thread.join() + + +class HTTPSServer: + """ + Context Manager to serve a local directory tree via HTTPS. + """ + + class ErrorAwareTCPServer(socketserver.ThreadingTCPServer): + """TCP layer that will suppress errors of the given type.""" + + def __init__(self, address, handler, err): + self.err = err + super().__init__(address, handler) + + def handle_error(self, request, client_address): + if isinstance(sys.exc_info()[1], self.err): + return + super().handle_error(request, client_address) + + def __init__(self, dir=None, handler=None, cert=None): + if handler is None: + handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir) + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + ctx.load_cert_chain(cert.file, cert.key_file) + address = ("localhost", 0) # auto-select free port + self._server = self.ErrorAwareTCPServer(address, handler, ssl.SSLError) + self._server.socket = ctx.wrap_socket(self._server.socket, server_side=True) + self._thread = threading.Thread(target=self._server.serve_forever, daemon=True) + + def __enter__(self): + self._thread.start() + host, port = self._server.server_address + return f"https://{host}:{port}" + + def __exit__(self, *exc_info): + if self._server: + self._server.shutdown() + self._server.server_close() + if self._thread: + self._thread.join() + + +class SelfSignedCert: + """ + Self-signed cert/key pair for localhost. + """ + + def __init__(self, tmpdir): + from cryptography import x509 + from cryptography.x509.oid import NameOID + from cryptography.hazmat.primitives import hashes, serialization + from cryptography.hazmat.primitives.asymmetric import rsa + + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) + name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "localhost")]) + now = datetime.datetime.now(datetime.timezone.utc) + cert = ( + x509.CertificateBuilder() + .subject_name(name) + .issuer_name(name) + .public_key(key.public_key()) + .serial_number(x509.random_serial_number()) + .not_valid_before(now) + .not_valid_after(now + datetime.timedelta(days=1)) + .add_extension( + x509.SubjectAlternativeName( + [ + x509.DNSName("localhost"), + x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")), + ] + ), + critical=False, + ) + .sign(key, hashes.SHA256()) + ) + os.makedirs(tmpdir, exist_ok=False) + self.key_file = os.path.join(tmpdir, "server.key") + self.file = os.path.join(tmpdir, "server.crt") + with open(self.key_file, "wb") as f: + f.write( + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption(), + ) + ) + with open(self.file, "wb") as f: + f.write(cert.public_bytes(serialization.Encoding.PEM)) + self.fingerprint = cert.fingerprint(hashes.SHA256()).hex() + + +class RedirectHandler(http.server.BaseHTTPRequestHandler): + base_url = None + + def do_GET(self): + self.send_response(301) + self.send_header("Location", self.base_url + self.path) + self.end_headers() + + def log_message(self, *args): + pass # suppress request logs + + +class RequestCounter(http.server.SimpleHTTPRequestHandler): + requests = 0 # class-level so all instances share one counter + + def __init__(self, *args, directory=None, **kwargs): + super().__init__(*args, directory=directory, **kwargs) + + def do_GET(self): + RequestCounter.requests += 1 + super().do_GET() + + +class SymStoreTests(TestBase): + TEST_WITH_PDB_DEBUG_INFO = True + + def build_inferior(self): + if self.getDebugInfo() != "pdb": + self.skipTest("Non-PDB debug info variants not yet supported") + self.build() + exe_file = "a.out" + sym_file = "a.pdb" + self.assertTrue(os.path.isfile(self.getBuildArtifact(exe_file))) + self.assertTrue(os.path.isfile(self.getBuildArtifact(sym_file))) + return exe_file, sym_file + + def assertFiles(self, dir, expected): + actual = sum(len(f) for _, _, f in os.walk(dir)) + self.assertEqual(actual, expected) + + def try_breakpoint(self, exe, should_have_loc, ext_lookup=True): + enable = "true" if ext_lookup else "false" + self.runCmd(f"settings set symbols.enable-external-lookup {enable}") + target = self.dbg.CreateTarget(self.getBuildArtifact(exe)) + self.assertTrue(target and target.IsValid(), "Target is valid") + bp = target.BreakpointCreateByName("func") + self.assertTrue(bp and bp.IsValid(), "Breakpoint is valid") + self.assertEqual(bp.GetNumLocations(), 1 if should_have_loc else 0) + self.dbg.DeleteTarget(target) + + def test_no_symstore_url(self): + """ + Check that breakpoint doesn't resolve without SymStore. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym): + self.try_breakpoint(exe, should_have_loc=False) + + def test_external_lookup_off(self): + """ + Check that breakpoint doesn't resolve with external lookup disabled. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as dir: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {dir}") + self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False) + + def test_local_dir(self): + """ + Check that breakpoint resolves with local SymStore. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as dir: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {dir}") + self.try_breakpoint(exe, should_have_loc=True) + + def test_http_not_found(self): + """ + Check that we don't issue a warning for a 404 response from a symbol server. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as symstore_dir: + os.makedirs(f"{symstore_dir}_empty", exist_ok=False) + with HTTPServer(f"{symstore_dir}_empty") as url: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {url}") + warnings = "" + with open(self.getBuildArtifact("stderr.txt"), "w+b") as err_file: + self.dbg.SetErrorFileHandle(err_file, False) + self.try_breakpoint(exe, should_have_loc=False) + self.dbg.SetErrorFileHandle(sys.stderr, False) + err_file.seek(0) + warnings = err_file.read().decode() + self.assertEqual(warnings, "") + + def test_http(self): + """ + Check that breakpoint resolves with remote SymStore. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as dir: + with HTTPServer(dir) as url: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {url}") + self.try_breakpoint(exe, should_have_loc=True) + + def test_sympath_local_dir(self): + """ + Check that breakpoint resolves with plain directory in _NT_SYMBOL_PATH. + The PDB is not copied to the configured cache. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + with NtSymbolPath(dir): + self.try_breakpoint(exe, should_have_loc=True) + self.assertFiles(symstore.cache_dir, 0) + + def test_sympath_local_srv(self): + """ + Check that breakpoint resolves with local directory in server notation + in _NT_SYMBOL_PATH. The PDB is not copied to the configured cache. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + with NtSymbolPath(f"srv*{dir}"): + self.try_breakpoint(exe, should_have_loc=True) + self.assertFiles(symstore.cache_dir, 0) + + def test_sympath_srv(self): + """ + Check that breakpoint resolves with an HTTP symbol server in _NT_SYMBOL_PATH + using the srv* notation. The PDB is stored in the configured cache. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + self.assertFiles(symstore.cache_dir, 0) + with HTTPServer(dir) as url: + with NtSymbolPath(f"srv*{url}"): + self.try_breakpoint(exe, should_have_loc=True) + key = symstore.get_key_pdb(exe) + cache_file = os.path.join(symstore.cache_dir, sym, key, sym) + self.assertTrue(os.path.isfile(cache_file)) + self.assertFiles(symstore.cache_dir, 1) + + def test_sympath_cache_explicit(self): + """ + Check PDB storage with explicit cache in _NT_SYMBOL_PATH. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + with HTTPServer(dir) as url: + explicit_cache = self.getBuildArtifact("explicit_cache") + with NtSymbolPath(f"srv*{explicit_cache}*{url}"): + self.try_breakpoint(exe, should_have_loc=True) + self.assertFiles(symstore.cache_dir, 0) + self.assertFiles(explicit_cache, 1) + + def test_sympath_cache_implicit(self): + """ + Check PDB storage with implicit cache in _NT_SYMBOL_PATH. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + with HTTPServer(dir) as url: + implicit_cache = self.getBuildArtifact("implicit_cache") + with NtSymbolPath(f"cache*{implicit_cache};srv*{url}"): + self.try_breakpoint(exe, should_have_loc=True) + self.assertFiles(symstore.cache_dir, 0) + self.assertFiles(implicit_cache, 1) + + def test_sympath_cache_invalid(self): + """ + Check that PDB is stored in configured default cache + if path in _NT_SYMBOL_PATH is invalid. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + with HTTPServer(dir) as url: + invalid_cache = ":\\" + self.assertFiles(symstore.cache_dir, 0) + with NtSymbolPath(f"cache*{invalid_cache};srv*{url}"): + self.try_breakpoint(exe, should_have_loc=True) + self.assertFiles(symstore.cache_dir, 1) + + def test_sympath_cache_empty(self): + """ + Check that PDB is stored in configured default cache + if path in _NT_SYMBOL_PATH is empty. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + with symstore as dir: + with HTTPServer(dir) as url: + self.assertFiles(symstore.cache_dir, 0) + with NtSymbolPath(f"cache*;srv*{url}"): + self.try_breakpoint(exe, should_have_loc=True) + self.assertFiles(symstore.cache_dir, 1) + + def test_lookup_order(self): + """ + Check that _NT_SYMBOL_PATH takes precedence over symstore.urls setting. + """ + exe, sym = self.build_inferior() + symstore = MockedSymStore(self, exe, sym) + RequestCounter.requests = 0 + with symstore as dir: + with HTTPServer(dir, RequestCounter) as url: + self.runCmd(f"settings set plugin.symbol-locator.symstore.urls {url}") + with NtSymbolPath(dir): + self.try_breakpoint(exe, should_have_loc=True) + self.assertEqual(RequestCounter.requests, 0) + + @skipUnlessPackageAvailable("cryptography") + def test_https(self): + """ + Check that breakpoint resolves with remote SymStore via HTTPS. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as symstore_dir: + cert = SelfSignedCert(self.getBuildArtifact("cert")) + with HTTPSServer(dir=symstore_dir, cert=cert) as https_url: + # We accept only the self-signed certificate with this fingerprint + self.runCmd( + f"settings set plugin.symbol-locator.symstore.tls-cert-fingerprint {cert.fingerprint}" + ) + self.runCmd( + f"settings set plugin.symbol-locator.symstore.urls {https_url}" + ) + self.try_breakpoint(exe, should_have_loc=True) + + @skipUnlessPackageAvailable("cryptography") + def test_https_reject_selfsigned_cert(self): + """ + Check that LLDB rejects an HTTPS server with an untrusted self-signed cert. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as symstore_dir: + cert = SelfSignedCert(self.getBuildArtifact("cert")) + with HTTPSServer(dir=symstore_dir, cert=cert) as https_url: + # No fingerprint set + self.runCmd( + f"settings set plugin.symbol-locator.symstore.urls {https_url}" + ) + self.try_breakpoint(exe, should_have_loc=False) + # Incorrect fingerprint set + bogus = "DEADBEEFCAFEBABE" + self.runCmd( + f"settings set plugin.symbol-locator.symstore.tls-cert-fingerprint {bogus}{bogus}{bogus}{bogus}" + ) + self.try_breakpoint(exe, should_have_loc=False) + + @skipUnlessPackageAvailable("cryptography") + def test_https_reject_redirect_http(self): + """ + Check that LLDB does not retrieve symbols from servers that redirect with security downgrades. + """ + exe, sym = self.build_inferior() + with MockedSymStore(self, exe, sym) as symstore_dir: + with HTTPServer(symstore_dir) as http_url: + RedirectHandler.base_url = http_url + cert = SelfSignedCert(self.getBuildArtifact("cert")) + with HTTPSServer(handler=RedirectHandler, cert=cert) as https_url: + self.runCmd( + f"settings set plugin.symbol-locator.symstore.urls {https_url}" + ) + self.runCmd( + f"settings set plugin.symbol-locator.symstore.tls-cert-fingerprint {cert.fingerprint}" + ) + self.try_breakpoint(exe, should_have_loc=False) diff --git a/lldb/test/API/symstore/main.c b/lldb/test/API/symstore/main.c new file mode 100644 index 0000000000000..a95762e80ea44 --- /dev/null +++ b/lldb/test/API/symstore/main.c @@ -0,0 +1,5 @@ +int func(int argc, const char *argv[]) { + return (argc + 1) * (argv[argc][0] + 2); +} + +int main(int argc, const char *argv[]) { return func(0, argv); } diff --git a/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test index 2692c3d9c3e70..55487235ae8cb 100644 --- a/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test +++ b/lldb/test/Shell/Settings/TestFrameFormatFunctionReturnObjC.test @@ -2,8 +2,11 @@ # ${function.return-right} in languages that don't implement this frame # format variable (in this case Objective-C). # +# link.exe will discard DWARF information. +# REQUIRES: (system-windows && lld) || !system-windows +# # RUN: split-file %s %t -# RUN: %clang_host -g -gdwarf %t/main.m -o %t.objc.out +# RUN: %clang_host -g -gdwarf %t/main.m -o %t.objc.out %if system-windows %{-fuse-ld=lld%} # RUN: %lldb -x -b -s %t/commands.input %t.objc.out -o exit 2>&1 \ # RUN: | FileCheck %s diff --git a/lldb/test/Shell/SymbolFile/DWARF/dwo-static-data-member-access.test b/lldb/test/Shell/SymbolFile/DWARF/dwo-static-data-member-access.test index 6e4deae7b9a0d..40d5e90097eb6 100644 --- a/lldb/test/Shell/SymbolFile/DWARF/dwo-static-data-member-access.test +++ b/lldb/test/Shell/SymbolFile/DWARF/dwo-static-data-member-access.test @@ -4,6 +4,9 @@ # a DW_TAG_variable DIE, whose parent DIE is only # a forward declaration. +# UNSUPPORTED: system-darwin +# UNSUPPORTED: system-windows + # RUN: %clangxx_host %S/Inputs/dwo-static-data-member.cpp \ # RUN: -g -gdwarf-5 -gsplit-dwarf -flimit-debug-info -o %t # RUN: %lldb %t -s %s -o exit 2>&1 | FileCheck %s diff --git a/lldb/test/Shell/SymbolFile/NativePDB/disassembly.cpp b/lldb/test/Shell/SymbolFile/NativePDB/disassembly.cpp index db3b85fa7e59f..b3f7b098a95d9 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/disassembly.cpp +++ b/lldb/test/Shell/SymbolFile/NativePDB/disassembly.cpp @@ -18,9 +18,7 @@ int main(int argc, char **argv) { // CHECK: (lldb) disassemble --flavor=intel -m -n main -// CHECK: 12 int foo() { return 42; } -// CHECK-NEXT: 13 -// CHECK-NEXT: ** 14 int main(int argc, char **argv) { +// CHECK: ** 14 int main(int argc, char **argv) { // CHECK: disassembly.cpp.tmp.exe`main: // CHECK-NEXT: disassembly.cpp.tmp.exe[{{.*}}] <+0>: sub rsp, 0x38 // CHECK-NEXT: disassembly.cpp.tmp.exe[{{.*}}] <+4>: mov dword ptr [rsp + 0x34], 0x0 diff --git a/lldb/test/Shell/SymbolFile/NativePDB/find-functions.cpp b/lldb/test/Shell/SymbolFile/NativePDB/find-functions.cpp index 5ebef61bdbfef..3ef7a4c94c287 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/find-functions.cpp +++ b/lldb/test/Shell/SymbolFile/NativePDB/find-functions.cpp @@ -6,24 +6,52 @@ // RUN: lldb-test symbols --find=function --name=main --function-flags=full %t.exe \ // RUN: | FileCheck %s --check-prefix=FIND-MAIN +// RUN: lldb-test symbols --find=function --name=main --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-NO-FUNCTION +// RUN: lldb-test symbols --find=function --name=main --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-MAIN // RUN: lldb-test symbols --find=function --name=static_fn --function-flags=full %t.exe \ // RUN: | FileCheck %s --check-prefix=FIND-STATIC +// RUN: lldb-test symbols --find=function --name=static_fn --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-NO-FUNCTION +// RUN: lldb-test symbols --find=function --name=static_fn --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-STATIC // RUN: lldb-test symbols --find=function --name=varargs_fn --function-flags=full %t.exe \ // RUN: | FileCheck %s --check-prefix=FIND-VAR +// RUN: lldb-test symbols --find=function --name=varargs_fn --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-NO-FUNCTION +// RUN: lldb-test symbols --find=function --name=varargs_fn --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-VAR // RUN: lldb-test symbols --find=function --name=Struct::simple_method --function-flags=full %t.exe \ // RUN: | FileCheck %s --check-prefix=FIND-SIMPLE +// RUN: lldb-test symbols --find=function --name=Struct::simple_method --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-SIMPLE +// RUN: lldb-test symbols --find=function --name=Struct::simple_method --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-SIMPLE // RUN: lldb-test symbols --find=function --name=Struct::virtual_method --function-flags=full %t.exe \ // RUN: | FileCheck %s --check-prefix=FIND-VIRTUAL +// RUN: lldb-test symbols --find=function --name=Struct::virtual_method --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-VIRTUAL +// RUN: lldb-test symbols --find=function --name=Struct::virtual_method --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-VIRTUAL // RUN: lldb-test symbols --find=function --name=Struct::static_method --function-flags=full %t.exe \ // RUN: | FileCheck %s --check-prefix=FIND-STATIC-METHOD +// RUN: lldb-test symbols --find=function --name=Struct::static_method --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-NO-FUNCTION +// RUN: lldb-test symbols --find=function --name=Struct::static_method --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-STATIC-METHOD // RUN: lldb-test symbols --find=function --name=Struct::overloaded_method --function-flags=full %t.exe \ -// RUN: | FileCheck %s --check-prefix=FIND-OVERLOAD +// RUN: | FileCheck %s --check-prefix=FIND-OVERLOAD-FULL +// RUN: lldb-test symbols --find=function --name=Struct::overloaded_method --function-flags=method %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-OVERLOAD-METHOD +// RUN: lldb-test symbols --find=function --name=Struct::overloaded_method --function-flags=base %t.exe \ +// RUN: | FileCheck %s --check-prefix=FIND-OVERLOAD-BASE struct Struct { int simple_method() { @@ -51,7 +79,28 @@ struct Struct { } }; +class Class { +public: + bool overloaded_method() { + return false; + } + bool overloaded_method(int i) { + return i > 0; + } + static int overloaded_method(bool b) { + return b ? 1 : 2; + } +}; + +char overloaded_method() { + return 0; +} +char overloaded_method(int i) { + return 0; +} + Struct s; +Class c; static int static_fn() { return 42; @@ -63,12 +112,16 @@ int varargs_fn(int x, int y, ...) { int main(int argc, char **argv) { return static_fn() + varargs_fn(argc, argc) + s.simple_method() + - Struct::static_method() + s.virtual_method() + s.overloaded_method(); + Struct::static_method() + s.virtual_method() + s.overloaded_method() + + Class::overloaded_method(false) + c.overloaded_method(1) + c.overloaded_method() + + overloaded_method() + overloaded_method(1); } // FIND-MAIN: Function: id = {{.*}}, name = "main" // FIND-MAIN-NEXT: FuncType: id = {{.*}}, compiler_type = "int (int, char **)" +// FIND-NO-FUNCTION: Found 0 functions + // FIND-STATIC: Function: id = {{.*}}, name = "{{.*}}static_fn{{.*}}" // FIND-STATIC-NEXT: FuncType: id = {{.*}}, compiler_type = "int (void)" @@ -84,7 +137,29 @@ int main(int argc, char **argv) { // FIND-STATIC-METHOD: Function: id = {{.*}}, name = "{{.*}}Struct::static_method{{.*}}" // FIND-STATIC-METHOD-NEXT: FuncType: id = {{.*}}, compiler_type = "int (void)" -// FIND-OVERLOAD: Function: id = {{.*}}, name = "{{.*}}Struct::overloaded_method{{.*}}" -// FIND-OVERLOAD: FuncType: id = {{.*}}, compiler_type = "int (void)" -// FIND-OVERLOAD: FuncType: id = {{.*}}, compiler_type = "int (char)" -// FIND-OVERLOAD: FuncType: id = {{.*}}, compiler_type = "int (char, int, ...)" +// FIND-OVERLOAD-FULL-NOT: "Class::overloaded_method" +// FIND-OVERLOAD-FULL-NOT: "overloaded_method" +// FIND-OVERLOAD-FULL-DAG: Function: id = {{.*}}, name = "{{.*}}Struct::overloaded_method{{.*}}" +// FIND-OVERLOAD-FULL-DAG: FuncType: id = {{.*}}, compiler_type = "int (void)" +// FIND-OVERLOAD-FULL-DAG: FuncType: id = {{.*}}, compiler_type = "int (char)" +// FIND-OVERLOAD-FULL-DAG: FuncType: id = {{.*}}, compiler_type = "int (char, int, ...)" + +// FIND-OVERLOAD-BASE-DAG: Function: id = {{.*}}, name = "{{.*}}Struct::overloaded_method{{.*}}" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "int (void)" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "int (char)" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "int (char, int, ...)" +// FIND-OVERLOAD-BASE-DAG: Function: id = {{.*}}, name = "Class::overloaded_method" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "_Bool (void)" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "_Bool (int)" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "int (_Bool)" +// FIND-OVERLOAD-BASE-DAG: Function: id = {{.*}}, name = "overloaded_method" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "char (void)" +// FIND-OVERLOAD-BASE-DAG: FuncType: id = {{.*}}, compiler_type = "char (int)" + +// FIND-OVERLOAD-METHOD-NOT: "overloaded_method" +// FIND-OVERLOAD-METHOD-DAG: Function: id = {{.*}}, name = "{{.*}}Struct::overloaded_method{{.*}}" +// FIND-OVERLOAD-METHOD-DAG: FuncType: id = {{.*}}, compiler_type = "int (void)" +// FIND-OVERLOAD-METHOD-DAG: FuncType: id = {{.*}}, compiler_type = "int (char)" +// FIND-OVERLOAD-METHOD-DAG: Function: id = {{.*}}, name = "Class::overloaded_method" +// FIND-OVERLOAD-METHOD-DAG: FuncType: id = {{.*}}, compiler_type = "_Bool (void)" +// FIND-OVERLOAD-METHOD-DAG: FuncType: id = {{.*}}, compiler_type = "_Bool (int)" diff --git a/lldb/test/Shell/SymbolFile/NativePDB/inline_sites.test b/lldb/test/Shell/SymbolFile/NativePDB/inline_sites.test index 6293148d90ce4..769f18de51472 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/inline_sites.test +++ b/lldb/test/Shell/SymbolFile/NativePDB/inline_sites.test @@ -61,6 +61,7 @@ # CHECK: Function: id = {{.*}}, name = "main", range = [0x0000000140001000-0x0000000140001046) # CHECK: Blocks: id = {{.*}}, range = [0x140001000-0x140001046) # CHECK: LineEntry: [0x0000000140001000-0x0000000140001004): /tmp/a.cpp:2 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "argc", type = "int", valid ranges = , location = [0x0000000140001000, 0x000000014000102d) -> DW_OP_reg26 XMM9 # CHECK-NEXT: Variable: id = {{.*}}, name = "argv", type = "char **", valid ranges = , location = [0x0000000140001000, 0x0000000140001045) -> DW_OP_reg3 RBX @@ -71,6 +72,7 @@ # CHECK: Blocks: id = {{.*}}, range = [0x140001000-0x140001046) # CHECK-NEXT: id = {{.*}}, ranges = [0x140001004-0x140001039)[0x14000103f-0x140001046), name = "Namespace1::foo", decl = a.h:4 # CHECK: LineEntry: [0x0000000140001004-0x000000014000100c): /tmp/a.h:5 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "x", type = "int", valid ranges = , location = , decl = # CHECK-NEXT: Variable: id = {{.*}}, name = "foo_local", type = "int", valid ranges = , location = [0x0000000140001004, 0x0000000140001039) -> DW_OP_breg7 RSP+44 # CHECK-NEXT: Variable: id = {{.*}}, name = "argc", type = "int", valid ranges = , location = [0x0000000140001000, 0x000000014000102d) -> DW_OP_reg26 XMM9 @@ -84,6 +86,7 @@ # CHECK: Blocks: id = {{.*}}, range = [0x140001000-0x140001046) # CHECK-NEXT: id = {{.*}}, ranges = [0x140001004-0x140001039)[0x14000103f-0x140001046), name = "Namespace1::foo", decl = a.h:4 # CHECK: LineEntry: [0x0000000140001010-0x0000000140001018): /tmp/a.h:7 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "x", type = "int", valid ranges = , location = , decl = # CHECK-NEXT: Variable: id = {{.*}}, name = "foo_local", type = "int", valid ranges = , location = [0x0000000140001004, 0x0000000140001039) -> DW_OP_breg7 RSP+44 # CHECK-NEXT: Variable: id = {{.*}}, name = "argc", type = "int", valid ranges = , location = [0x0000000140001000, 0x000000014000102d) -> DW_OP_reg26 XMM9 @@ -99,6 +102,7 @@ # CHECK-NEXT: id = {{.*}}, ranges = [0x140001004-0x140001039)[0x14000103f-0x140001046), name = "Namespace1::foo", decl = a.h:4 # CHECK-NEXT: id = {{.*}}, range = [0x14000101c-0x140001039), name = "Class1::bar", decl = b.h:4 # CHECK: LineEntry: [0x000000014000101c-0x0000000140001022): /tmp/b.h:5 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "x", type = "int", valid ranges = , location = [0x000000014000101c, 0x000000014000101e) -> DW_OP_reg24 XMM7 # CHECK-NEXT: Variable: id = {{.*}}, name = "bar_local", type = "int", valid ranges = , location = [0x000000014000101c, 0x0000000140001039) -> DW_OP_breg7 RSP+52 # CHECK-NEXT: Variable: id = {{.*}}, name = "x", type = "int", valid ranges = , location = , decl = @@ -118,6 +122,7 @@ # CHECK-NEXT: id = {{.*}}, range = [0x14000101c-0x140001039), name = "Class1::bar", decl = b.h:4 # CHECK-NEXT: id = {{.*}}, range = [0x14000102a-0x140001039), name = "Namespace2::Class2::func", decl = c.h:4 # CHECK: LineEntry: [0x000000014000102a-0x0000000140001031): /tmp/c.h:5 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "x", type = "int", valid ranges = , location = [0x000000014000102a, 0x0000000140001039) -> DW_OP_reg24 XMM7 # CHECK-NEXT: Variable: id = {{.*}}, name = "func_local", type = "int", valid ranges = , location = [0x000000014000102a, 0x0000000140001039) -> DW_OP_breg7 RSP+48 # CHECK-NEXT: Variable: id = {{.*}}, name = "bar_local", type = "int", valid ranges = , location = [0x000000014000101c, 0x0000000140001039) -> DW_OP_breg7 RSP+52 @@ -132,6 +137,7 @@ # CHECK: Function: id = {{.*}}, name = "main", range = [0x0000000140001000-0x0000000140001046) # CHECK: Blocks: id = {{.*}}, range = [0x140001000-0x140001046) # CHECK: LineEntry: [0x0000000140001039-0x000000014000103d): /tmp/a.cpp:3 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "argv", type = "char **", valid ranges = , location = [0x0000000140001000, 0x0000000140001045) -> DW_OP_reg3 RBX # CHECK-NEXT: Variable: id = {{.*}}, name = "main_local", type = "int", valid ranges = , location = [0x0000000140001004, 0x0000000140001046) -> DW_OP_breg7 RSP+48 @@ -142,6 +148,7 @@ # CHECK: Blocks: id = {{.*}}, range = [0x140001000-0x140001046) # CHECK-NEXT: id = {{.*}}, ranges = [0x140001004-0x140001039)[0x14000103f-0x140001046), name = "Namespace1::foo", decl = a.h:4 # CHECK: LineEntry: [0x0000000140001044-0x0000000140001046): /tmp/a.h:8 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001046), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "x", type = "int", valid ranges = , location = , decl = # CHECK-NEXT: Variable: id = {{.*}}, name = "foo_local", type = "int", valid ranges = , location = [0x0000000140001044, 0x0000000140001046) -> DW_OP_breg7 RSP+44 # CHECK-NEXT: Variable: id = {{.*}}, name = "argc", type = "int", valid ranges = , location = [0x0000000140001044, 0x0000000140001045) -> DW_OP_reg26 XMM9 diff --git a/lldb/test/Shell/SymbolFile/NativePDB/local-variables-registers.s b/lldb/test/Shell/SymbolFile/NativePDB/local-variables-registers.s index 85d92a2447939..fe2f397d60c01 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/local-variables-registers.s +++ b/lldb/test/Shell/SymbolFile/NativePDB/local-variables-registers.s @@ -34,38 +34,46 @@ # CHECK: (lldb) image lookup -a 0x140001000 -v # CHECK: LineEntry: [0x0000000140001000-0x0000000140001003): C:\src\test\a.cpp:10 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001011), name="struct S CreateS(int, char)", mangled="?CreateS@@YA?AUS@@HD@Z" # CHECK-NEXT: Variable: id = {{.*}}, name = "p1", type = "int", valid ranges = , location = [0x0000000140001000, 0x0000000140001003) -> DW_OP_reg26 XMM9 # CHECK-NEXT: Variable: id = {{.*}}, name = "p2", type = "char", valid ranges = , location = [0x0000000140001000, 0x0000000140001006) -> DW_OP_regx 0x3f # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001003 -v # CHECK: LineEntry: [0x0000000140001003-0x0000000140001006): C:\src\test\a.cpp:11 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001011), name="struct S CreateS(int, char)", mangled="?CreateS@@YA?AUS@@HD@Z" # CHECK-NEXT: Variable: id = {{.*}}, name = "p2", type = "char", valid ranges = , location = [0x0000000140001000, 0x0000000140001006) -> DW_OP_regx 0x3f # CHECK-NEXT: Variable: id = {{.*}}, name = "s", type = "S", valid ranges = , location = [0x0000000140001003, 0x0000000140001006) -> DW_OP_piece 0x4, DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x3 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001006 -v # CHECK: LineEntry: [0x0000000140001006-0x0000000140001011): C:\src\test\a.cpp:12 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001000-0x0000000140001011), name="struct S CreateS(int, char)", mangled="?CreateS@@YA?AUS@@HD@Z" # CHECK-NEXT: Variable: id = {{.*}}, name = "s", type = "S", valid ranges = , location = [0x0000000140001006, 0x0000000140001011) -> DW_OP_reg26 XMM9, DW_OP_piece 0x4, DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x3 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001011 -v # CHECK: LineEntry: [0x0000000140001011-0x0000000140001015): C:\src\test\a.cpp:15 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "argc", type = "int", valid ranges = , location = [0x0000000140001011, 0x0000000140001017) -> DW_OP_reg26 XMM9 # CHECK-NEXT: Variable: id = {{.*}}, name = "argv", type = "char **", valid ranges = , location = [0x0000000140001011, 0x0000000140001019) -> DW_OP_reg3 RBX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001017 -v # CHECK: LineEntry: [0x0000000140001017-0x000000014000101e): C:\src\test\a.cpp:17 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "argv", type = "char **", valid ranges = , location = [0x0000000140001011, 0x0000000140001019) -> DW_OP_reg3 RBX # CHECK-NEXT: Variable: id = {{.*}}, name = "local", type = "int", valid ranges = , location = [0x0000000140001017, 0x000000014000101e) -> DW_OP_reg26 XMM9 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001019 -v # CHECK: LineEntry: [0x0000000140001017-0x000000014000101e): C:\src\test\a.cpp:17 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "local", type = "int", valid ranges = , location = [0x0000000140001017, 0x000000014000101e) -> DW_OP_reg26 XMM9 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000101e -v # CHECK: LineEntry: [0x000000014000101e-0x0000000140001031): C:\src\test\a.cpp:18 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "s", type = "S", valid ranges = , location = [0x000000014000101e, 0x000000014000102c) -> DW_OP_reg24 XMM7, DW_OP_piece 0x4, DW_OP_piece 0x4 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000102c -v # CHECK: LineEntry: [0x000000014000101e-0x0000000140001031): C:\src\test\a.cpp:18 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" .text .def @feat.00; @@ -406,14 +414,17 @@ main: # @main .short .Ltmp103-.Ltmp102 # CHECK: (lldb) image lookup -a 0x140001031 -v # CHECK: LineEntry: [0x0000000140001031-0x0000000140001034): C:\src\test\a.cpp:1000 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "non_overlapped_ranges", type = "S1", valid ranges = , location = [0x0000000140001031, 0x0000000140001032) -> DW_OP_reg3 RBX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001032 -v # CHECK: LineEntry: [0x0000000140001031-0x0000000140001034): C:\src\test\a.cpp:1000 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "non_overlapped_ranges", type = "S1", valid ranges = , location = [0x0000000140001032, 0x0000000140001033) -> DW_OP_reg2 RCX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001033 -v # CHECK: LineEntry: [0x0000000140001031-0x0000000140001034): C:\src\test\a.cpp:1000 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "non_overlapped_ranges", type = "S1", valid ranges = , location = [0x0000000140001033, 0x0000000140001034) -> DW_OP_reg8 R8 # CHECK-EMPTY: @@ -431,18 +442,22 @@ main: # @main .short .Ltmp105-.Ltmp104 # CHECK: (lldb) image lookup -a 0x140001034 -v # CHECK: LineEntry: [0x0000000140001034-0x000000014000103b): C:\src\test\a.cpp:1001 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_subfield_ranges", type = "S1", valid ranges = , location = [0x0000000140001034, 0x0000000140001035) -> DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x7 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001035 -v # CHECK: LineEntry: [0x0000000140001034-0x000000014000103b): C:\src\test\a.cpp:1001 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_subfield_ranges", type = "S1", valid ranges = , location = [0x0000000140001035, 0x0000000140001036) -> DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x3, DW_OP_reg24 XMM7, DW_OP_piece 0x4 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001036 -v # CHECK: LineEntry: [0x0000000140001034-0x000000014000103b): C:\src\test\a.cpp:1001 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_subfield_ranges", type = "S1", valid ranges = , location = [0x0000000140001036, 0x0000000140001037) -> DW_OP_piece 0x4, DW_OP_reg24 XMM7, DW_OP_piece 0x4 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001037 -v # CHECK: LineEntry: [0x0000000140001034-0x000000014000103b): C:\src\test\a.cpp:1001 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_subfield_ranges", type = "S1", valid ranges = , location = [0x0000000140001037, 0x0000000140001039) -> DW_OP_piece 0x4, DW_OP_reg26 XMM9, DW_OP_piece 0x4 # CHECK-EMPTY: @@ -461,22 +476,27 @@ main: # @main .short .Ltmp107-.Ltmp106 # CHECK: (lldb) image lookup -a 0x14000103b -v # CHECK: LineEntry: [0x000000014000103b-0x0000000140001045): C:\src\test\a.cpp:1002 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_2", type = "S1", valid ranges = , location = [0x000000014000103b, 0x000000014000103c) -> DW_OP_reg3 RBX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000103d -v # CHECK: LineEntry: [0x000000014000103b-0x0000000140001045): C:\src\test\a.cpp:1002 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_2", type = "S1", valid ranges = , location = [0x000000014000103c, 0x000000014000103e) -> DW_OP_reg2 RCX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000103f -v # CHECK: LineEntry: [0x000000014000103b-0x0000000140001045): C:\src\test\a.cpp:1002 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_2", type = "S1", valid ranges = , location = [0x000000014000103f, 0x0000000140001041) -> DW_OP_reg11 R11 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001041 -v # CHECK: LineEntry: [0x000000014000103b-0x0000000140001045): C:\src\test\a.cpp:1002 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_2", type = "S1", valid ranges = , location = [0x0000000140001041, 0x0000000140001043) -> DW_OP_reg0 RAX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001043 -v # CHECK: LineEntry: [0x000000014000103b-0x0000000140001045): C:\src\test\a.cpp:1002 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_2", type = "S1", valid ranges = , location = [0x0000000140001043, 0x0000000140001044) -> DW_OP_reg11 R11 # CHECK-EMPTY: @@ -505,33 +525,41 @@ main: # @main .short .Ltmp109-.Ltmp108 # CHECK: (lldb) image lookup -a 0x140001045 -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x0000000140001045, 0x0000000140001046) -> DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x7 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001046 -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x0000000140001046, 0x0000000140001047) -> DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x3, DW_OP_reg24 XMM7, DW_OP_piece 0x4 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001047 -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x0000000140001047, 0x0000000140001048) -> DW_OP_reg3 RBX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001048 -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x0000000140001048, 0x0000000140001049) -> DW_OP_regx 0x3f, DW_OP_piece 0x1, DW_OP_piece 0x3, DW_OP_reg24 XMM7, DW_OP_piece 0x4 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x140001049 -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x0000000140001049, 0x000000014000104a) -> DW_OP_reg0 RAX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000104a -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000104b -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x000000014000104b, 0x000000014000104e) -> DW_OP_reg2 RCX # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000104c -v # CHECK: LineEntry: [0x0000000140001045-0x000000014000104e): C:\src\test\a.cpp:1003 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "overlapped_ranges_3", type = "S1", valid ranges = , location = [0x000000014000104b, 0x000000014000104e) -> DW_OP_reg2 RCX # CHECK-EMPTY: @@ -549,10 +577,12 @@ main: # @main .short 4431 # Record kind: S_PROC_ID_END # CHECK: (lldb) image lookup -a 0x14000104e -v # CHECK: LineEntry: [0x000000014000104e-0x0000000140001050): C:\src\test\a.cpp:1004 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "simple_type1", type = "int64_t", valid ranges = , location = [0x000000014000104e, 0x000000014000104f) -> DW_OP_reg26 XMM9, DW_OP_piece 0x4, DW_OP_reg24 XMM7, DW_OP_piece 0x4 # CHECK-EMPTY: # CHECK: (lldb) image lookup -a 0x14000104f -v # CHECK: LineEntry: [0x000000014000104e-0x0000000140001050): C:\src\test\a.cpp:1004 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001011-0x0000000140001050), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "simple_type1", type = "int64_t", valid ranges = , location = [0x000000014000104f, 0x0000000140001050) -> DW_OP_reg26 XMM9, DW_OP_piece 0x4, DW_OP_piece 0x4 # CHECK-EMPTY: diff --git a/lldb/test/Shell/SymbolFile/NativePDB/nested-blocks-same-address.s b/lldb/test/Shell/SymbolFile/NativePDB/nested-blocks-same-address.s index dc3ee844fe364..e51b280d4213e 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/nested-blocks-same-address.s +++ b/lldb/test/Shell/SymbolFile/NativePDB/nested-blocks-same-address.s @@ -30,6 +30,7 @@ # CHECK-NEXT: id = {{.*}}, range = [0x140001025-0x140001046) # CHECK-NEXT: id = {{.*}}, range = [0x140001025-0x140001046) # CHECK-NEXT: LineEntry: [0x0000000140001035-0x0000000140001046): /tmp/test.cpp:10 +# CHECK-NEXT: Symbol: id = {{.*}}, range = [0x0000000140001020-0x000000014000104d), name="main" # CHECK-NEXT: Variable: id = {{.*}}, name = "path", type = "volatile char[10]", valid ranges = , location = [0x0000000140001025, 0x0000000140001046) -> DW_OP_breg7 RSP+40, decl = # CHECK-NEXT: Variable: id = {{.*}}, name = "kMfDLL", type = "const char *", valid ranges = , location = [0x000000014000103c, 0x0000000140001046) -> DW_OP_reg2 RCX, decl = # CHECK-NEXT: Variable: id = {{.*}}, name = "__range1", type = "const char *const (&)[1]", valid ranges = , location = , decl = diff --git a/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp b/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp new file mode 100644 index 0000000000000..5a63ec2361151 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp @@ -0,0 +1,59 @@ +// REQUIRES: x86 + +// Test symtab reading +// RUN: %build --compiler=clang-cl --arch=64 --nodefaultlib -o %t.exe -- %s +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 lldb-test symtab %t.exe --find-symbols-by-regex=".*" | FileCheck %s +// RUN: env LLDB_USE_NATIVE_PDB_READER=0 lldb-test symtab %t.exe --find-symbols-by-regex=".*" | FileCheck %s + +struct A { + void something() {} +}; + +namespace ns { +template struct B { + struct C { + static int static_fn() { return 1; } + }; + + int b_func() const { return 3; } +}; + +struct Dyn { + virtual ~Dyn() = default; +}; + +int a_function() { return 1; } +} // namespace ns + +void *operator new(unsigned long long n) { return nullptr; } +void operator delete(void *p, unsigned long long i) {} + +A global_a; +ns::B::C global_c; +int global_int; + +int main(int argc, char **argv) { + A a; + a.something(); + ns::B::C::static_fn(); + ns::B::C::static_fn(); + ns::B b; + ns::Dyn dyn; + return ns::a_function() + b.b_func(); +} + +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 main +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?b_func@?$B@F@ns@@QEBAHXZ +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?something@A@@QEAAXXZ +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ??_GDyn@ns@@UEAAPEAXI@Z +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ??2@YAPEAX_K@Z +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ??3@YAXPEAX_K@Z +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?static_fn@C@?$B@H@ns@@SAHXZ +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?a_function@ns@@YAHXZ +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?static_fn@C@?$B@_N@ns@@SAHXZ +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ??1Dyn@ns@@UEAA@XZ +// CHECK-DAG: Code 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ??0Dyn@ns@@QEAA@XZ +// CHECK-DAG: Data 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?global_int@@3HA +// CHECK-DAG: Data 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ??_7Dyn@ns@@6B@ +// CHECK-DAG: Data 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?global_a@@3UA@@A +// CHECK-DAG: Data 0x{{[0-9a-f]+}} 0x{{0*[1-9a-f][0-9a-f]*}} 0x00000000 ?global_c@@3UC@?$B@_J@ns@@A diff --git a/lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp b/lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp new file mode 100644 index 0000000000000..ef7690b1720a6 --- /dev/null +++ b/lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp @@ -0,0 +1,39 @@ +// REQUIRES: lld, target-windows + +// Test that `target symbols add ` works. +// RUN: %build --compiler=clang-cl --nodefaultlib --output=%t.exe %s +// RUN: mv %t.pdb %t-renamed.pdb + +// RUN: env LLDB_USE_NATIVE_PDB_READER=0 %lldb \ +// RUN: -o "b main" \ +// RUN: -o "target symbols add %t-renamed.pdb" \ +// RUN: -o r \ +// RUN: -o "target variable a" \ +// RUN: -o "target modules dump symtab" \ +// RUN: -b %t.exe | FileCheck %s + +// RUN: env LLDB_USE_NATIVE_PDB_READER=1 %lldb \ +// RUN: -o "b main" \ +// RUN: -o "target symbols add %t-renamed.pdb" \ +// RUN: -o r \ +// RUN: -o "target variable a" \ +// RUN: -o "target modules dump symtab" \ +// RUN: -b %t.exe | FileCheck %s + +// CHECK: target create +// CHECK: (lldb) b main +// CHECK-NEXT: Breakpoint 1: no locations (pending). +// CHECK: (lldb) target symbols add +// CHECK: 1 location added to breakpoint 1 +// CHECK: (lldb) r +// CHECK: * thread #1, stop reason = breakpoint 1.1 +// CHECK: (lldb) target variable a +// CHECK-NEXT: (A) a = (x = 47) +// CHECK: (lldb) target modules dump symtab +// CHECK: [{{.*}} main + +struct A { + int x = 47; +}; +A a; +int main() {} diff --git a/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test b/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test index 78d7f7469b9f8..9c507dc9079b7 100644 --- a/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test +++ b/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test @@ -4,9 +4,10 @@ # process actually loads the DLL. # RUN: %clang_host -g0 -O0 -shared %S/Inputs/shlib.c -o %t.shlib.dll \ -# RUN: %if windows-msvc %{-Wl,-implib:%t.shlib.lib%} \ +# RUN: %if windows-msvc %{-Wl,-implib:%t.shlib.lib -Wl,-debug:none%} \ # RUN: %else %{-Wl,--out-implib=%t.shlib.lib%} -# RUN: %clang_host -g0 -O0 %S/Inputs/main.c %t.shlib.lib -o %t.main.exe +# RUN: %clang_host -g0 -O0 %S/Inputs/main.c %t.shlib.lib -o %t.main.exe \ +# RUN: %if windows-msvc %{-Wl,-debug:none%} # RUN: %lldb -b -o "#before" -o "target modules list" -o "b main" -o run \ # RUN: -o "#after" -o "target modules list" %t.main.exe | FileCheck %s diff --git a/lldb/test/Shell/Unwind/windows-unaligned-x86_64.test b/lldb/test/Shell/Unwind/windows-unaligned-x86_64.test index 0356960424328..9f0a97527de4e 100644 --- a/lldb/test/Shell/Unwind/windows-unaligned-x86_64.test +++ b/lldb/test/Shell/Unwind/windows-unaligned-x86_64.test @@ -5,7 +5,8 @@ # REQUIRES: target-x86_64, native, system-windows # RUN: %build %p/Inputs/windows-unaligned-x86_64.cpp %p/Inputs/windows-unaligned-x86_64-asm.s -o %t -# RUN: %lldb %t -s %s -o exit | FileCheck %s +# RUN: env LLDB_USE_NATIVE_PDB_READER=0 %lldb %t -s %s -o exit | FileCheck %s +# RUN: env LLDB_USE_NATIVE_PDB_READER=1 %lldb %t -s %s -o exit | FileCheck %s # Future TODO: If %build could compile the source file in C mode, the symbol # name handling would be easier across msvc and mingw build configurations. diff --git a/lldb/unittests/Symbol/CMakeLists.txt b/lldb/unittests/Symbol/CMakeLists.txt index 88ea76ada2015..cc3de05dd0881 100644 --- a/lldb/unittests/Symbol/CMakeLists.txt +++ b/lldb/unittests/Symbol/CMakeLists.txt @@ -20,6 +20,7 @@ add_lldb_unittest(SymbolTests PostfixExpressionTest.cpp SymbolTest.cpp SymtabTest.cpp + SymStoreTest.cpp TestTypeSystem.cpp TestTypeSystemClang.cpp TestClangASTImporter.cpp @@ -40,6 +41,7 @@ add_lldb_unittest(SymbolTests lldbPluginSymbolFileDWARF lldbPluginSymbolFileSymtab lldbPluginTypeSystemClang + lldbPluginSymbolLocatorSymStore ${SWIFT_LIBS} LLVMTestingSupport ) diff --git a/lldb/unittests/Symbol/SymStoreTest.cpp b/lldb/unittests/Symbol/SymStoreTest.cpp new file mode 100644 index 0000000000000..aada565e6d3b7 --- /dev/null +++ b/lldb/unittests/Symbol/SymStoreTest.cpp @@ -0,0 +1,113 @@ +//===----------------------------------------------------------------------===// +// +// 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 "gtest/gtest.h" + +#include "Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h" + +using namespace lldb_private; +using LookupEntry = SymbolLocatorSymStore::LookupEntry; + +TEST(SymStoreTest, ParseEnvSymbolPaths_Srv) { + auto check = [](const char *str) { + std::vector sources; + for (LookupEntry entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(str)) + sources.push_back(std::move(entry.source)); + return sources; + }; + auto returns = [](auto... strs) { return std::vector{strs...}; }; + + // Local paths. + EXPECT_EQ(check(""), returns()); + EXPECT_EQ(check("C:\\ProgramData\\Symbols"), + returns("C:\\ProgramData\\Symbols")); + EXPECT_EQ(check("C:\\symbols;\\\\buildserver\\syms;file://D:/pdb"), + returns("C:\\symbols", "\\\\buildserver\\syms", "file://D:/pdb")); + + // Symbol servers. + EXPECT_EQ(check("srv*https://msdl.microsoft.com/download/symbols"), + returns("https://msdl.microsoft.com/download/symbols")); + EXPECT_EQ(check("Srv*https://msdl.microsoft.com/download/symbols"), + returns("https://msdl.microsoft.com/download/symbols")); + EXPECT_EQ(check("SRV*http://localhost"), returns("http://localhost")); + + // Symbol servers and local paths with caches. + EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"), + returns("\\\\corp\\symbols")); + EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"), + returns("D:\\sym", "D:\\sym")); + EXPECT_EQ(check("srv**https://symbols.mozilla.org"), + returns("https://symbols.mozilla.org")); + + // Symbol server with custom implementation (unsupported). + EXPECT_EQ(check("symsrv*symsrv.dll*https://symbols.mozilla.org"), returns()); + EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"), + returns()); + EXPECT_EQ(check("symsrv*https://symbols.mozilla.org;D:\\sym"), + returns("D:\\sym")); + + // Partially invalid specs. + EXPECT_EQ(check("srv*;;D:\\sym;SRV*"), returns("", "D:\\sym", "")); + EXPECT_EQ(check("srv*D:\\1*D:\\2*D:\\3;D:\\sym"), returns("D:\\sym")); + EXPECT_EQ(check("symsrv*D:\\1;D:\\sym"), returns("D:\\sym")); +} + +TEST(SymStoreTest, ParseEnvSymbolPaths_Cache) { + auto check = [](const char *str) { + std::vector caches; + for (LookupEntry entry : SymbolLocatorSymStore::ParseEnvSymbolPaths(str)) + if (entry.cache) + caches.push_back(std::move(*entry.cache)); + return caches; + }; + auto returns = [](auto... strs) { return std::vector{strs...}; }; + + // No caches. + EXPECT_EQ(check(""), returns()); + EXPECT_EQ(check("C:\\ProgramData\\Symbols"), returns()); + EXPECT_EQ(check("C:\\symbols;\\\\buildserver\\syms;file://D:/pdb"), + returns()); + EXPECT_EQ(check("SRV*http://localhost"), returns()); + + // No cache without a server. + EXPECT_EQ(check("cache*"), returns()); + EXPECT_EQ(check("cache*C:\\symcache"), returns()); + EXPECT_EQ(check("cache*C:\\symcache;D:\\sym"), returns()); + + // Explicit caches for symbol servers. + EXPECT_EQ(check("SRV*C:\\symcache*\\\\corp\\symbols"), + returns("C:\\symcache")); + EXPECT_EQ(check("D:\\sym;srv*C:\\symcache*D:\\sym"), returns("C:\\symcache")); + + // Implicit caches for following symbol servers. + EXPECT_EQ(check("cache*D:\\s;srv*\\\\corp"), returns("D:\\s")); + EXPECT_EQ(check("CACHE*D:\\s;srv*\\\\corp;SRV*http://localhost"), + returns("D:\\s", "D:\\s")); + EXPECT_EQ(check("Cache*D:\\s;srv*\\\\corp;SRV*C:\\X*http://localhost"), + returns("D:\\s", "C:\\X")); + EXPECT_EQ(check("srv*\\\\corp;cache*D:\\s;SRV*C:\\X*http://localhost"), + returns("C:\\X")); + EXPECT_EQ(check("srv*\\\\corp;SRV*C:\\X*http://localhost;cache*D:\\s"), + returns("C:\\X")); + + // Fall back to default cache. + auto default_cache = SymbolLocatorSymStore::GetSystemDefaultCachePath(); + EXPECT_EQ(check("cache*;srv*\\\\corp"), returns(default_cache)); + EXPECT_EQ(check("srv**https://symbols.mozilla.org"), returns(default_cache)); + + // Symbol server with custom implementation (unsupported). + EXPECT_EQ(check("symsrv*symsrv.dll*https://symbols.mozilla.org"), returns()); + EXPECT_EQ(check("symsrv*symsrv.dll*C:\\symbols*https://symbols.mozilla.org"), + returns()); + + // Partially invalid specs. + EXPECT_EQ(check("cache*C:\\1;;D:\\sym;SRV*"), returns("C:\\1")); + EXPECT_EQ(check("cache*C:\\1;srv*D:\\1*D:\\2*D:\\3;srv*D:\\sym"), + returns("C:\\1")); + EXPECT_EQ(check("cache*C:\\1;symsrv*D:\\1;srv*D:\\sym"), returns("C:\\1")); +} diff --git a/llvm/include/llvm/Debuginfod/Debuginfod.h b/llvm/include/llvm/Debuginfod/Debuginfod.h index 99fe15ad85979..005e5c7d3a83f 100644 --- a/llvm/include/llvm/Debuginfod/Debuginfod.h +++ b/llvm/include/llvm/Debuginfod/Debuginfod.h @@ -20,10 +20,9 @@ #ifndef LLVM_DEBUGINFOD_DEBUGINFOD_H #define LLVM_DEBUGINFOD_DEBUGINFOD_H -#include "HTTPServer.h" - #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" +#include "llvm/HTTP/HTTPServer.h" #include "llvm/Object/BuildID.h" #include "llvm/Support/Error.h" #include "llvm/Support/MemoryBuffer.h" diff --git a/llvm/include/llvm/Debuginfod/HTTPClient.h b/llvm/include/llvm/HTTP/HTTPClient.h similarity index 84% rename from llvm/include/llvm/Debuginfod/HTTPClient.h rename to llvm/include/llvm/HTTP/HTTPClient.h index 6ded55502f055..a3db41c1a9034 100644 --- a/llvm/include/llvm/Debuginfod/HTTPClient.h +++ b/llvm/include/llvm/HTTP/HTTPClient.h @@ -1,4 +1,4 @@ -//===-- llvm/Support/HTTPClient.h - HTTP client library ---------*- C++ -*-===// +//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -12,8 +12,8 @@ /// //===----------------------------------------------------------------------===// -#ifndef LLVM_DEBUGINFOD_HTTPCLIENT_H -#define LLVM_DEBUGINFOD_HTTPCLIENT_H +#ifndef LLVM_HTTP_HTTPCLIENT_H +#define LLVM_HTTP_HTTPCLIENT_H #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -21,6 +21,7 @@ #include "llvm/Support/MemoryBuffer.h" #include +#include namespace llvm { @@ -31,7 +32,10 @@ struct HTTPRequest { SmallString<128> Url; SmallVector Headers; HTTPMethod Method = HTTPMethod::GET; + // Follow redirects without security downgrades. bool FollowRedirects = true; + // Allow self-signed TLS certificates with this SHA-256 (WinHTTP only). + std::optional PinnedCertFingerprint; HTTPRequest(StringRef Url); }; @@ -51,8 +55,8 @@ class HTTPResponseHandler { /// A reusable client that can perform HTTPRequests through a network socket. class HTTPClient { -#ifdef LLVM_ENABLE_CURL - void *Curl = nullptr; +#if defined(LLVM_ENABLE_CURL) || defined(_WIN32) + void *Handle = nullptr; #endif public: @@ -85,4 +89,4 @@ class HTTPClient { } // end namespace llvm -#endif // LLVM_DEBUGINFOD_HTTPCLIENT_H +#endif // LLVM_HTTP_HTTPCLIENT_H diff --git a/llvm/include/llvm/Debuginfod/HTTPServer.h b/llvm/include/llvm/HTTP/HTTPServer.h similarity index 95% rename from llvm/include/llvm/Debuginfod/HTTPServer.h rename to llvm/include/llvm/HTTP/HTTPServer.h index c200089200ab7..1d218002789c3 100644 --- a/llvm/include/llvm/Debuginfod/HTTPServer.h +++ b/llvm/include/llvm/HTTP/HTTPServer.h @@ -1,4 +1,4 @@ -//===-- llvm/Debuginfod/HTTPServer.h - HTTP server library ------*- C++ -*-===// +//===--- HTTPServer.h - HTTP server library ---------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -13,8 +13,8 @@ /// //===----------------------------------------------------------------------===// -#ifndef LLVM_DEBUGINFOD_HTTPSERVER_H -#define LLVM_DEBUGINFOD_HTTPSERVER_H +#ifndef LLVM_HTTP_HTTPSERVER_H +#define LLVM_HTTP_HTTPSERVER_H #include "llvm/ADT/StringRef.h" #include "llvm/Support/Error.h" @@ -130,4 +130,4 @@ class HTTPServer { }; } // end namespace llvm -#endif // LLVM_DEBUGINFOD_HTTPSERVER_H +#endif // LLVM_HTTP_HTTPSERVER_H diff --git a/llvm/include/llvm/HTTP/StreamedHTTPResponseHandler.h b/llvm/include/llvm/HTTP/StreamedHTTPResponseHandler.h new file mode 100644 index 0000000000000..ce9bf28655186 --- /dev/null +++ b/llvm/include/llvm/HTTP/StreamedHTTPResponseHandler.h @@ -0,0 +1,49 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// An HTTPResponseHandler that streams the response body to a CachedFileStream. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_HTTP_STREAMEDHTTPRESPONSEHANDLER_H +#define LLVM_HTTP_STREAMEDHTTPRESPONSEHANDLER_H + +#include "llvm/HTTP/HTTPClient.h" +#include "llvm/Support/Caching.h" +#include "llvm/Support/Error.h" +#include +#include + +namespace llvm { + +/// A handler which streams the returned data to a CachedFileStream. The cache +/// file is only created if a 200 OK status is observed. +class StreamedHTTPResponseHandler : public HTTPResponseHandler { + using CreateStreamFn = + std::function>()>; + CreateStreamFn CreateStream; + HTTPClient &Client; + std::unique_ptr FileStream; + +public: + StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client) + : CreateStream(std::move(CreateStream)), Client(Client) {} + + /// Must be called exactly once after the writes have been completed + /// but before the StreamedHTTPResponseHandler object is destroyed. + Error commit(); + + virtual ~StreamedHTTPResponseHandler() = default; + + Error handleBodyChunk(StringRef BodyChunk) override; +}; + +} // end namespace llvm + +#endif // LLVM_HTTP_STREAMEDHTTPRESPONSEHANDLER_H diff --git a/llvm/lib/CMakeLists.txt b/llvm/lib/CMakeLists.txt index 080c435e98cf9..6cfe72f3635b5 100644 --- a/llvm/lib/CMakeLists.txt +++ b/llvm/lib/CMakeLists.txt @@ -33,6 +33,7 @@ add_subdirectory(ObjectYAML) add_subdirectory(Option) add_subdirectory(Remarks) add_subdirectory(RemoteCachingService) +add_subdirectory(HTTP) add_subdirectory(Debuginfod) add_subdirectory(DebugInfo) add_subdirectory(DWARFCFIChecker) diff --git a/llvm/lib/Debuginfod/CMakeLists.txt b/llvm/lib/Debuginfod/CMakeLists.txt index b1329bd2d077e..5b3403b961a4c 100644 --- a/llvm/lib/Debuginfod/CMakeLists.txt +++ b/llvm/lib/Debuginfod/CMakeLists.txt @@ -1,13 +1,3 @@ -# Link LibCURL if the user wants it -if (LLVM_ENABLE_CURL) - set(imported_libs CURL::libcurl) -endif() - -# Link cpp-httplib if the user wants it -if (LLVM_ENABLE_HTTPLIB) - set(imported_libs ${imported_libs} httplib::httplib) -endif() - # Make sure pthread is linked if this is a unix host if (CMAKE_HOST_UNIX) set(imported_libs ${imported_libs} ${LLVM_PTHREAD_LIB}) @@ -18,14 +8,13 @@ endif() add_llvm_library(LLVMDebuginfod BuildIDFetcher.cpp Debuginfod.cpp - HTTPClient.cpp - HTTPServer.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Debuginfod LINK_LIBS ${imported_libs} + LLVMHTTP LINK_COMPONENTS Support diff --git a/llvm/lib/Debuginfod/Debuginfod.cpp b/llvm/lib/Debuginfod/Debuginfod.cpp index 12f817c9e4bf0..84fe7ac06336a 100644 --- a/llvm/lib/Debuginfod/Debuginfod.cpp +++ b/llvm/lib/Debuginfod/Debuginfod.cpp @@ -27,7 +27,8 @@ #include "llvm/BinaryFormat/Magic.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" +#include "llvm/HTTP/StreamedHTTPResponseHandler.h" #include "llvm/Object/BuildID.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/CachePruning.h" @@ -174,53 +175,6 @@ Expected getCachedOrDownloadArtifact(StringRef UniqueKey, getDefaultDebuginfodTimeout()); } -namespace { - -/// A simple handler which streams the returned data to a cache file. The cache -/// file is only created if a 200 OK status is observed. -class StreamedHTTPResponseHandler : public HTTPResponseHandler { - using CreateStreamFn = - std::function>()>; - CreateStreamFn CreateStream; - HTTPClient &Client; - std::unique_ptr FileStream; - -public: - StreamedHTTPResponseHandler(CreateStreamFn CreateStream, HTTPClient &Client) - : CreateStream(CreateStream), Client(Client) {} - - /// Must be called exactly once after the writes have been completed - /// but before the StreamedHTTPResponseHandler object is destroyed. - Error commit(); - - virtual ~StreamedHTTPResponseHandler() = default; - - Error handleBodyChunk(StringRef BodyChunk) override; -}; - -} // namespace - -Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) { - if (!FileStream) { - unsigned Code = Client.responseCode(); - if (Code && Code != 200) - return Error::success(); - Expected> FileStreamOrError = - CreateStream(); - if (!FileStreamOrError) - return FileStreamOrError.takeError(); - FileStream = std::move(*FileStreamOrError); - } - *FileStream->OS << BodyChunk; - return Error::success(); -} - -Error StreamedHTTPResponseHandler::commit() { - if (FileStream) - return FileStream->commit(); - return Error::success(); -} - // An over-accepting simplification of the HTTP RFC 7230 spec. static bool isHeader(StringRef S) { StringRef Name; diff --git a/llvm/lib/Debuginfod/HTTPClient.cpp b/llvm/lib/Debuginfod/HTTPClient.cpp deleted file mode 100644 index 4cca250746a59..0000000000000 --- a/llvm/lib/Debuginfod/HTTPClient.cpp +++ /dev/null @@ -1,162 +0,0 @@ -//===-- llvm/Debuginfod/HTTPClient.cpp - HTTP client library ----*- C++ -*-===// -// -// 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 -// -//===----------------------------------------------------------------------===// -/// -/// \file -/// This file defines the implementation of the HTTPClient library for issuing -/// HTTP requests and handling the responses. -/// -//===----------------------------------------------------------------------===// - -#include "llvm/Debuginfod/HTTPClient.h" -#include "llvm/ADT/APInt.h" -#include "llvm/ADT/StringRef.h" -#include "llvm/Support/Errc.h" -#include "llvm/Support/Error.h" -#include "llvm/Support/MemoryBuffer.h" -#ifdef LLVM_ENABLE_CURL -#include -#endif - -using namespace llvm; - -HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); } - -bool operator==(const HTTPRequest &A, const HTTPRequest &B) { - return A.Url == B.Url && A.Method == B.Method && - A.FollowRedirects == B.FollowRedirects; -} - -HTTPResponseHandler::~HTTPResponseHandler() = default; - -bool HTTPClient::IsInitialized = false; - -class HTTPClientCleanup { -public: - ~HTTPClientCleanup() { HTTPClient::cleanup(); } -}; -static const HTTPClientCleanup Cleanup; - -#ifdef LLVM_ENABLE_CURL - -bool HTTPClient::isAvailable() { return true; } - -void HTTPClient::initialize() { - if (!IsInitialized) { - curl_global_init(CURL_GLOBAL_ALL); - IsInitialized = true; - } -} - -void HTTPClient::cleanup() { - if (IsInitialized) { - curl_global_cleanup(); - IsInitialized = false; - } -} - -void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { - if (Timeout < std::chrono::milliseconds(0)) - Timeout = std::chrono::milliseconds(0); - curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count()); -} - -/// CurlHTTPRequest and the curl{Header,Write}Function are implementation -/// details used to work with Curl. Curl makes callbacks with a single -/// customizable pointer parameter. -struct CurlHTTPRequest { - CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {} - void storeError(Error Err) { - ErrorState = joinErrors(std::move(Err), std::move(ErrorState)); - } - HTTPResponseHandler &Handler; - llvm::Error ErrorState = Error::success(); -}; - -static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, - CurlHTTPRequest *CurlRequest) { - Size *= NMemb; - if (Error Err = - CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) { - CurlRequest->storeError(std::move(Err)); - return 0; - } - return Size; -} - -HTTPClient::HTTPClient() { - assert(IsInitialized && - "Must call HTTPClient::initialize() at the beginning of main()."); - if (Curl) - return; - Curl = curl_easy_init(); - assert(Curl && "Curl could not be initialized"); - // Set the callback hooks. - curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); - // Detect supported compressed encodings and accept all. - curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, ""); -} - -HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } - -Error HTTPClient::perform(const HTTPRequest &Request, - HTTPResponseHandler &Handler) { - if (Request.Method != HTTPMethod::GET) - return createStringError(errc::invalid_argument, - "Unsupported CURL request method."); - - SmallString<128> Url = Request.Url; - curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str()); - curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); - - curl_slist *Headers = nullptr; - for (const std::string &Header : Request.Headers) - Headers = curl_slist_append(Headers, Header.c_str()); - curl_easy_setopt(Curl, CURLOPT_HTTPHEADER, Headers); - - CurlHTTPRequest CurlRequest(Handler); - curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); - CURLcode CurlRes = curl_easy_perform(Curl); - curl_slist_free_all(Headers); - if (CurlRes != CURLE_OK) - return joinErrors(std::move(CurlRequest.ErrorState), - createStringError(errc::io_error, - "curl_easy_perform() failed: %s\n", - curl_easy_strerror(CurlRes))); - return std::move(CurlRequest.ErrorState); -} - -unsigned HTTPClient::responseCode() { - long Code = 0; - curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); - return Code; -} - -#else - -HTTPClient::HTTPClient() = default; - -HTTPClient::~HTTPClient() = default; - -bool HTTPClient::isAvailable() { return false; } - -void HTTPClient::initialize() {} - -void HTTPClient::cleanup() {} - -void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} - -Error HTTPClient::perform(const HTTPRequest &Request, - HTTPResponseHandler &Handler) { - llvm_unreachable("No HTTP Client implementation available."); -} - -unsigned HTTPClient::responseCode() { - llvm_unreachable("No HTTP Client implementation available."); -} - -#endif diff --git a/llvm/lib/HTTP/CMakeLists.txt b/llvm/lib/HTTP/CMakeLists.txt new file mode 100644 index 0000000000000..9dab6ad200066 --- /dev/null +++ b/llvm/lib/HTTP/CMakeLists.txt @@ -0,0 +1,27 @@ +# Link LibCURL if the user wants it +if (LLVM_ENABLE_CURL) + set(imported_libs CURL::libcurl) +endif() + +# Link cpp-httplib if the user wants it +if (LLVM_ENABLE_HTTPLIB) + set(imported_libs ${imported_libs} httplib::httplib) +endif() + +# Use WinHTTP on Windows +if (WIN32) + set(imported_libs ${imported_libs} winhttp.lib crypt32.lib) +endif() + +# This is no component library so the LLVM dylib does not gain a dependency on curl +add_llvm_library(LLVMHTTP + HTTPClient.cpp + HTTPServer.cpp + StreamedHTTPResponseHandler.cpp + + LINK_LIBS + ${imported_libs} + + LINK_COMPONENTS + Support +) diff --git a/llvm/lib/HTTP/HTTPClient.cpp b/llvm/lib/HTTP/HTTPClient.cpp new file mode 100644 index 0000000000000..c69aecfa7d619 --- /dev/null +++ b/llvm/lib/HTTP/HTTPClient.cpp @@ -0,0 +1,456 @@ +//===--- HTTPClient.cpp - HTTP client library -----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// This file defines the implementation of the HTTPClient library for issuing +/// HTTP requests and handling the responses. +/// +//===----------------------------------------------------------------------===// + +#include "llvm/HTTP/HTTPClient.h" + +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/MemoryBuffer.h" +#ifdef LLVM_ENABLE_CURL +#include +#endif +#ifdef _WIN32 +#include "llvm/Support/ConvertUTF.h" +#endif + +using namespace llvm; + +HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); } + +bool operator==(const HTTPRequest &A, const HTTPRequest &B) { + return A.Url == B.Url && A.Method == B.Method && + A.FollowRedirects == B.FollowRedirects && + A.PinnedCertFingerprint == B.PinnedCertFingerprint; +} + +HTTPResponseHandler::~HTTPResponseHandler() = default; + +bool HTTPClient::IsInitialized = false; + +class HTTPClientCleanup { +public: + ~HTTPClientCleanup() { HTTPClient::cleanup(); } +}; +ManagedStatic Cleanup; + +#ifdef LLVM_ENABLE_CURL + +bool HTTPClient::isAvailable() { return true; } + +void HTTPClient::initialize() { + if (!IsInitialized) { + curl_global_init(CURL_GLOBAL_ALL); + IsInitialized = true; + } +} + +void HTTPClient::cleanup() { + if (IsInitialized) { + curl_global_cleanup(); + IsInitialized = false; + } +} + +void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { + if (Timeout < std::chrono::milliseconds(0)) + Timeout = std::chrono::milliseconds(0); + curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, Timeout.count()); +} + +/// CurlHTTPRequest and the curl{Header,Write}Function are implementation +/// details used to work with Curl. Curl makes callbacks with a single +/// customizable pointer parameter. +struct CurlHTTPRequest { + CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {} + void storeError(Error Err) { + ErrorState = joinErrors(std::move(Err), std::move(ErrorState)); + } + HTTPResponseHandler &Handler; + llvm::Error ErrorState = Error::success(); +}; + +static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, + CurlHTTPRequest *CurlRequest) { + Size *= NMemb; + if (Error Err = + CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) { + CurlRequest->storeError(std::move(Err)); + return 0; + } + return Size; +} + +HTTPClient::HTTPClient() { + assert(IsInitialized && + "Must call HTTPClient::initialize() at the beginning of main()."); + if (Handle) + return; + Handle = curl_easy_init(); + assert(Handle && "Curl could not be initialized"); + // Set the callback hooks. + curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, curlWriteFunction); + // Detect supported compressed encodings and accept all. + curl_easy_setopt(Handle, CURLOPT_ACCEPT_ENCODING, ""); +} + +HTTPClient::~HTTPClient() { curl_easy_cleanup(Handle); } + +Error HTTPClient::perform(const HTTPRequest &Request, + HTTPResponseHandler &Handler) { + if (Request.Method != HTTPMethod::GET) + return createStringError(errc::invalid_argument, + "Unsupported CURL request method."); + + SmallString<128> Url = Request.Url; + curl_easy_setopt(Handle, CURLOPT_URL, Url.c_str()); + curl_easy_setopt(Handle, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects); + + curl_slist *Headers = nullptr; + for (const std::string &Header : Request.Headers) + Headers = curl_slist_append(Headers, Header.c_str()); + curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, Headers); + + CurlHTTPRequest CurlRequest(Handler); + curl_easy_setopt(Handle, CURLOPT_WRITEDATA, &CurlRequest); + CURLcode CurlRes = curl_easy_perform(Handle); + curl_slist_free_all(Headers); + if (CurlRes != CURLE_OK) + return joinErrors(std::move(CurlRequest.ErrorState), + createStringError(errc::io_error, + "curl_easy_perform() failed: %s\n", + curl_easy_strerror(CurlRes))); + return std::move(CurlRequest.ErrorState); +} + +unsigned HTTPClient::responseCode() { + long Code = 0; + curl_easy_getinfo(Handle, CURLINFO_RESPONSE_CODE, &Code); + return Code; +} + +#else + +#ifdef _WIN32 + +// We cannot sort these headers alphabetically. +// clang-format off +#include +#include +#include +// clang-format on + +namespace { + +struct WinHTTPSession { + HINTERNET SessionHandle = nullptr; + HINTERNET ConnectHandle = nullptr; + HINTERNET RequestHandle = nullptr; + DWORD ResponseCode = 0; + + ~WinHTTPSession() { + if (RequestHandle) + WinHttpCloseHandle(RequestHandle); + if (ConnectHandle) + WinHttpCloseHandle(ConnectHandle); + if (SessionHandle) + WinHttpCloseHandle(SessionHandle); + } +}; + +bool parseURL(StringRef Url, std::wstring &Host, std::wstring &Path, + INTERNET_PORT &Port, bool &Secure) { + // Parse URL: http://host:port/path + if (Url.starts_with("https://")) { + Secure = true; + Url = Url.drop_front(8); + } else if (Url.starts_with("http://")) { + Secure = false; + Url = Url.drop_front(7); + } else { + return false; + } + + size_t SlashPos = Url.find('/'); + StringRef HostPort = + (SlashPos != StringRef::npos) ? Url.substr(0, SlashPos) : Url; + StringRef PathPart = + (SlashPos != StringRef::npos) ? Url.substr(SlashPos) : StringRef("/"); + + size_t ColonPos = HostPort.find(':'); + StringRef HostStr = + (ColonPos != StringRef::npos) ? HostPort.substr(0, ColonPos) : HostPort; + + if (!llvm::ConvertUTF8toWide(HostStr, Host)) + return false; + if (!llvm::ConvertUTF8toWide(PathPart, Path)) + return false; + + if (ColonPos != StringRef::npos) { + StringRef PortStr = HostPort.substr(ColonPos + 1); + Port = static_cast(std::stoi(PortStr.str())); + } else { + Port = Secure ? INTERNET_DEFAULT_HTTPS_PORT : INTERNET_DEFAULT_HTTP_PORT; + } + + return true; +} + +} // namespace + +HTTPClient::HTTPClient() : Handle(new WinHTTPSession()) {} + +HTTPClient::~HTTPClient() { delete static_cast(Handle); } + +bool HTTPClient::isAvailable() { return true; } + +void HTTPClient::initialize() { + if (!IsInitialized) { + IsInitialized = true; + } +} + +void HTTPClient::cleanup() { + if (IsInitialized) { + IsInitialized = false; + } +} + +void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { + WinHTTPSession *Session = static_cast(Handle); + if (Session && Session->SessionHandle) { + DWORD TimeoutMs = static_cast(Timeout.count()); + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_CONNECT_TIMEOUT, + &TimeoutMs, sizeof(TimeoutMs)); + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_RECEIVE_TIMEOUT, + &TimeoutMs, sizeof(TimeoutMs)); + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SEND_TIMEOUT, + &TimeoutMs, sizeof(TimeoutMs)); + } +} + +static Error VerifyTLSCertWinHTTP(HINTERNET RequestHandle, + const std::string &PinnedFingerprint) { + // Decode the expected fingerprint from hex into binary. + BYTE Expected[32]; + DWORD ExpectedSize = sizeof(Expected); + if (!CryptStringToBinaryA( + PinnedFingerprint.c_str(), (DWORD)PinnedFingerprint.size(), + CRYPT_STRING_HEXRAW, Expected, &ExpectedSize, nullptr, nullptr)) + return createStringError(errc::invalid_argument, + "Invalid certificate fingerprint format"); + + // Retrieve the server certificate and compute its SHA-256 hash. + PCCERT_CONTEXT CertCtx = nullptr; + DWORD CertCtxSize = sizeof(CertCtx); + if (!WinHttpQueryOption(RequestHandle, WINHTTP_OPTION_SERVER_CERT_CONTEXT, + &CertCtx, &CertCtxSize)) + return createStringError(errc::io_error, + "Failed to retrieve server certificate"); + + std::array Actual; + DWORD ActualSize = Actual.size(); + bool GotHash = CertGetCertificateContextProperty( + CertCtx, CERT_SHA256_HASH_PROP_ID, Actual.data(), &ActualSize); + CertFreeCertificateContext(CertCtx); + if (!GotHash) + return createStringError(errc::io_error, + "Failed to compute certificate fingerprint"); + + if (memcmp(Actual.data(), Expected, Actual.size()) != 0) + return createStringError(errc::permission_denied, + "Certificate fingerprint mismatch"); + + return Error::success(); +} + +Error HTTPClient::perform(const HTTPRequest &Request, + HTTPResponseHandler &Handler) { + if (Request.Method != HTTPMethod::GET) + return createStringError(errc::invalid_argument, + "Only GET requests are supported."); + for (const std::string &Header : Request.Headers) + if (Header.find("\r") != std::string::npos || + Header.find("\n") != std::string::npos) { + return createStringError(errc::invalid_argument, + "Unsafe request can lead to header injection."); + } + + WinHTTPSession *Session = static_cast(Handle); + + // Parse URL + std::wstring Host, Path; + INTERNET_PORT Port = 0; + bool Secure = false; + if (!parseURL(Request.Url, Host, Path, Port, Secure)) + return createStringError(errc::invalid_argument, + "Invalid URL: " + Request.Url); + + // Create session + Session->SessionHandle = + WinHttpOpen(L"LLVM-HTTPClient/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!Session->SessionHandle) + return createStringError(errc::io_error, "Failed to open WinHTTP session"); + + // Prevent fallback to TLS 1.0/1.1 + DWORD SecureProtocols = + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_3; + if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_SECURE_PROTOCOLS, + &SecureProtocols, sizeof(SecureProtocols))) + return createStringError(errc::io_error, "Failed to set secure protocols"); + + // Disallow redirects in general or HTTPS to HTTP only. + DWORD RedirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_DISALLOW_HTTPS_TO_HTTP; + if (!Request.FollowRedirects) + RedirectPolicy = WINHTTP_OPTION_REDIRECT_POLICY_NEVER; + if (!WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_REDIRECT_POLICY, + &RedirectPolicy, sizeof(RedirectPolicy))) + return createStringError(errc::io_error, "Failed to set redirect policy"); + + // Use HTTP/2 if available + DWORD EnableHttp2 = WINHTTP_PROTOCOL_FLAG_HTTP2; + WinHttpSetOption(Session->SessionHandle, WINHTTP_OPTION_ENABLE_HTTP_PROTOCOL, + &EnableHttp2, sizeof(EnableHttp2)); + + // Create connection + Session->ConnectHandle = + WinHttpConnect(Session->SessionHandle, Host.c_str(), Port, 0); + if (!Session->ConnectHandle) { + return createStringError(errc::io_error, + "Failed to connect to host: " + Request.Url); + } + + // Open request + DWORD Flags = WINHTTP_FLAG_REFRESH; + if (Secure) + Flags |= WINHTTP_FLAG_SECURE; + + Session->RequestHandle = WinHttpOpenRequest( + Session->ConnectHandle, L"GET", Path.c_str(), nullptr, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, Flags); + if (!Session->RequestHandle) + return createStringError(errc::io_error, "Failed to open HTTP request"); + + DWORD SecurityFlags = 0; + if (Secure) { + // Enforce checks that certificate wasn't revoked. + DWORD EnableRevocationChecks = WINHTTP_ENABLE_SSL_REVOCATION; + if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_ENABLE_FEATURE, + &EnableRevocationChecks, + sizeof(EnableRevocationChecks))) + return createStringError( + errc::io_error, "Failed to enable certificate revocation checks"); + + // Bypass certificate chain validation with pinned certificates so + // that self-signed certificates are accepted at the WinHTTP level. Manual + // verification happens right after receiving the response. + if (Request.PinnedCertFingerprint) + SecurityFlags = (SecurityFlags | SECURITY_FLAG_IGNORE_UNKNOWN_CA); + if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_SECURITY_FLAGS, + &SecurityFlags, sizeof(SecurityFlags))) + return createStringError(errc::io_error, + "Failed to enforce security flags"); + } + + // Add headers + for (const std::string &Header : Request.Headers) { + std::wstring WideHeader; + if (!llvm::ConvertUTF8toWide(Header, WideHeader)) + continue; + WinHttpAddRequestHeaders(Session->RequestHandle, WideHeader.c_str(), + static_cast(WideHeader.length()), + WINHTTP_ADDREQ_FLAG_ADD); + } + + // Send request + if (!WinHttpSendRequest(Session->RequestHandle, WINHTTP_NO_ADDITIONAL_HEADERS, + 0, nullptr, 0, 0, 0)) + return createStringError(errc::io_error, "Failed to send HTTP request"); + + // Receive response + if (!WinHttpReceiveResponse(Session->RequestHandle, nullptr)) + return createStringError(errc::io_error, "Failed to receive HTTP response"); + + // Verify the server certificate fingerprint if one was pinned. + if ((SecurityFlags & SECURITY_FLAG_IGNORE_UNKNOWN_CA) != 0) + if (Error Err = VerifyTLSCertWinHTTP(Session->RequestHandle, + *Request.PinnedCertFingerprint)) + return Err; + + // Get response code + DWORD CodeSize = sizeof(Session->ResponseCode); + if (!WinHttpQueryHeaders(Session->RequestHandle, + WINHTTP_QUERY_STATUS_CODE | + WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &Session->ResponseCode, + &CodeSize, nullptr)) + Session->ResponseCode = 0; + + // Read response body + DWORD BytesAvailable = 0; + while (WinHttpQueryDataAvailable(Session->RequestHandle, &BytesAvailable)) { + if (BytesAvailable == 0) + break; + + std::vector Buffer(BytesAvailable); + DWORD BytesRead = 0; + if (!WinHttpReadData(Session->RequestHandle, Buffer.data(), BytesAvailable, + &BytesRead)) + return createStringError(errc::io_error, "Failed to read HTTP response"); + + if (BytesRead > 0) { + if (Error Err = + Handler.handleBodyChunk(StringRef(Buffer.data(), BytesRead))) + return Err; + } + } + + return Error::success(); +} + +unsigned HTTPClient::responseCode() { + WinHTTPSession *Session = static_cast(Handle); + return Session ? Session->ResponseCode : 0; +} + +#else // _WIN32 + +// Non-Windows, non-libcurl stub implementations +HTTPClient::HTTPClient() = default; + +HTTPClient::~HTTPClient() = default; + +bool HTTPClient::isAvailable() { return false; } + +void HTTPClient::initialize() {} + +void HTTPClient::cleanup() {} + +void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {} + +Error HTTPClient::perform(const HTTPRequest &Request, + HTTPResponseHandler &Handler) { + llvm_unreachable("No HTTP Client implementation available."); +} + +unsigned HTTPClient::responseCode() { + llvm_unreachable("No HTTP Client implementation available."); +} + +#endif // _WIN32 + +#endif diff --git a/llvm/lib/Debuginfod/HTTPServer.cpp b/llvm/lib/HTTP/HTTPServer.cpp similarity index 95% rename from llvm/lib/Debuginfod/HTTPServer.cpp rename to llvm/lib/HTTP/HTTPServer.cpp index 1264353ce4b33..219b2d0ab9ae1 100644 --- a/llvm/lib/Debuginfod/HTTPServer.cpp +++ b/llvm/lib/HTTP/HTTPServer.cpp @@ -1,4 +1,4 @@ -//===-- llvm/Debuginfod/HTTPServer.cpp - HTTP server library -----*- C++-*-===// +//===--- HTTPServer.cpp - HTTP server library -----------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -13,7 +13,8 @@ /// //===----------------------------------------------------------------------===// -#include "llvm/Debuginfod/HTTPServer.h" +#include "llvm/HTTP/HTTPServer.h" + #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" @@ -187,12 +188,8 @@ Expected HTTPServer::bind(const char *HostInterface) { return make_error("no httplib"); } -Error HTTPServer::listen() { - return make_error("no httplib"); -} +Error HTTPServer::listen() { return make_error("no httplib"); } -void HTTPServer::stop() { - llvm_unreachable("no httplib"); -} +void HTTPServer::stop() { llvm_unreachable("no httplib"); } #endif // LLVM_ENABLE_HTTPLIB diff --git a/llvm/lib/HTTP/StreamedHTTPResponseHandler.cpp b/llvm/lib/HTTP/StreamedHTTPResponseHandler.cpp new file mode 100644 index 0000000000000..2ebe418cde089 --- /dev/null +++ b/llvm/lib/HTTP/StreamedHTTPResponseHandler.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 "llvm/HTTP/StreamedHTTPResponseHandler.h" + +namespace llvm { + +Error StreamedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) { + if (!FileStream) { + unsigned Code = Client.responseCode(); + if (Code && Code != 200) + return Error::success(); + Expected> FileStreamOrError = + CreateStream(); + if (!FileStreamOrError) + return FileStreamOrError.takeError(); + FileStream = std::move(*FileStreamOrError); + } + *FileStream->OS << BodyChunk; + return Error::success(); +} + +Error StreamedHTTPResponseHandler::commit() { + if (FileStream) + return FileStream->commit(); + return Error::success(); +} + +} // namespace llvm diff --git a/llvm/test/tools/llvm-debuginfod-find/Inputs/capture_req.py b/llvm/test/tools/llvm-debuginfod-find/Inputs/capture_req.py index fa823e61f9397..db9e3e14584e4 100644 --- a/llvm/test/tools/llvm-debuginfod-find/Inputs/capture_req.py +++ b/llvm/test/tools/llvm-debuginfod-find/Inputs/capture_req.py @@ -14,7 +14,7 @@ def log_request(self, *args, **kwargs): print(self.headers) -httpd = http.server.HTTPServer(("", 0), TrivialHandler) +httpd = http.server.HTTPServer(("localhost", 0), TrivialHandler) port = httpd.socket.getsockname()[1] try: diff --git a/llvm/test/tools/llvm-debuginfod-find/headers.test b/llvm/test/tools/llvm-debuginfod-find/headers-curl.test similarity index 98% rename from llvm/test/tools/llvm-debuginfod-find/headers.test rename to llvm/test/tools/llvm-debuginfod-find/headers-curl.test index 86517f8fc5a12..9990d8b80a7d0 100644 --- a/llvm/test/tools/llvm-debuginfod-find/headers.test +++ b/llvm/test/tools/llvm-debuginfod-find/headers-curl.test @@ -1,4 +1,4 @@ -REQUIRES: curl +REQUIRES: curl, http-server RUN: rm -rf %t RUN: mkdir -p %t/debuginfod-cache diff --git a/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test new file mode 100644 index 0000000000000..9b614790694e8 --- /dev/null +++ b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test @@ -0,0 +1,34 @@ +REQUIRES: system-windows, http-server, disabled-temp + +RUN: not llvm-debuginfod-find --debuginfo 0 2>&1 | FileCheck %s --check-prefix CHECK-STDOUT +CHECK-STDOUT: Build ID 00 could not be found + +RUN: rm -rf %t +RUN: mkdir -p %t/debuginfod-cache +RUN: %python %S/Inputs/capture_req.py llvm-debuginfod-find --debuginfo 0 \ +RUN: | FileCheck --check-prefix NO-HEADERS %s +RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=bad %python %S/Inputs/capture_req.py \ +RUN: llvm-debuginfod-find --debuginfo 0 \ +RUN: | FileCheck --check-prefix NO-HEADERS %s +RUN: rm -rf %t/debuginfod-cache/* +RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=%S/Inputs/headers %python %S/Inputs/capture_req.py \ +RUN: llvm-debuginfod-find --debuginfo 0 \ +RUN: | FileCheck --check-prefix HEADERS %s +RUN: rm -rf %t/debuginfod-cache/* +RUN: env DEBUGINFOD_CACHE=%t/debuginfod-cache DEBUGINFOD_HEADERS_FILE=%S/Inputs/headers DEBUGINFOD_URLS=fake not llvm-debuginfod-find --debuginfo 0 2>&1 \ +RUN: | FileCheck --check-prefix ERR -DHEADER_FILE=%S/Inputs/headers %s + +NO-HEADERS: User-Agent: LLVM-HTTPClient/1.0 +NO-HEADERS-NEXT: Host: localhost:{{[0-9]+}} + +HEADERS: User-Agent: LLVM-HTTPClient/1.0 +HEADERS-NEXT: A: B +HEADERS-NEXT: C: D +HEADERS-NEXT: E: F +HEADERS-NEXT: hi!$: j k +HEADERS-NEXT: Host: localhost:{{[0-9]+}} + +ERR: warning: could not parse debuginfod header: [[HEADER_FILE]]:3 +ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:4 +ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:5 +ERR-NEXT: warning: could not parse debuginfod header: [[HEADER_FILE]]:6 diff --git a/llvm/test/tools/llvm-debuginfod-find/lit.local.cfg.py b/llvm/test/tools/llvm-debuginfod-find/lit.local.cfg.py new file mode 100644 index 0000000000000..964591114f26a --- /dev/null +++ b/llvm/test/tools/llvm-debuginfod-find/lit.local.cfg.py @@ -0,0 +1,46 @@ +import http.server +import threading +import urllib.request +import time +import sys + + +captured_req = False + + +class TrivialHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.end_headers() + + def log_request(self, *args, **kwargs): + if len(self.requestline) > 0: + if len(self.headers) > 0: + global captured_req + captured_req = True + + +# Start server on random free port, query it and check response +def can_capture_req_to_http_server(): + if sys.platform != "win32": + return True + try: + httpd = http.server.HTTPServer(("127.0.0.1", 0), TrivialHandler) + port = httpd.server_address[1] + thread = threading.Thread(target=httpd.serve_forever, daemon=True) + thread.start() + time.sleep(0.2) + url = f"http://127.0.0.1:{port}/" + with urllib.request.urlopen(url) as response: + if response.status == 200: + return captured_req + except Exception as e: + print("Failed to start and query HTTP server: ", repr(e)) + return False + finally: + httpd.shutdown() + thread.join() + + +if can_capture_req_to_http_server(): + config.available_features.add("http-server") diff --git a/llvm/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt index 7acc87e08a9ef..6602a2319cb57 100644 --- a/llvm/tools/llvm-cov/CMakeLists.txt +++ b/llvm/tools/llvm-cov/CMakeLists.txt @@ -22,4 +22,4 @@ add_llvm_tool(llvm-cov TestingSupport.cpp ) -target_link_libraries(llvm-cov PRIVATE LLVMDebuginfod) +target_link_libraries(llvm-cov PRIVATE LLVMHTTP LLVMDebuginfod) diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp index 6c66858c4de8c..c8cf0885fc07d 100644 --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -24,7 +24,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/InstrProfReader.h" diff --git a/llvm/tools/llvm-debuginfod-find/CMakeLists.txt b/llvm/tools/llvm-debuginfod-find/CMakeLists.txt index 39da11fcd9599..fcc706d6df452 100644 --- a/llvm/tools/llvm-debuginfod-find/CMakeLists.txt +++ b/llvm/tools/llvm-debuginfod-find/CMakeLists.txt @@ -13,8 +13,9 @@ add_llvm_tool(llvm-debuginfod-find DebugInfodFindOptsTableGen GENERATE_DRIVER ) + if(NOT LLVM_TOOL_LLVM_DRIVER_BUILD) - target_link_libraries(llvm-debuginfod-find PRIVATE LLVMDebuginfod) + target_link_libraries(llvm-debuginfod-find PRIVATE LLVMDebuginfod LLVMHTTP) endif() if(LLVM_INSTALL_BINUTILS_SYMLINKS) add_llvm_tool_symlink(debuginfod-find llvm-debuginfod-find) diff --git a/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp b/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp index 934833bf6fe42..b6a5efd3e162c 100644 --- a/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp +++ b/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp @@ -19,7 +19,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" diff --git a/llvm/tools/llvm-debuginfod/CMakeLists.txt b/llvm/tools/llvm-debuginfod/CMakeLists.txt index 6b0a1193d1b22..194784b7a7eee 100644 --- a/llvm/tools/llvm-debuginfod/CMakeLists.txt +++ b/llvm/tools/llvm-debuginfod/CMakeLists.txt @@ -15,7 +15,7 @@ add_llvm_tool(llvm-debuginfod ) if(NOT LLVM_TOOL_LLVM_DRIVER_BUILD) - target_link_libraries(llvm-debuginfod PRIVATE LLVMDebuginfod) + target_link_libraries(llvm-debuginfod PRIVATE LLVMDebuginfod LLVMHTTP) endif() if(LLVM_INSTALL_BINUTILS_SYMLINKS) diff --git a/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp b/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp index 7b85166c1b4ae..29b320ca5c5b9 100644 --- a/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp +++ b/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp @@ -18,7 +18,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" diff --git a/llvm/tools/llvm-objdump/CMakeLists.txt b/llvm/tools/llvm-objdump/CMakeLists.txt index 7e3197f0a0bd3..ff3a61e8d832e 100644 --- a/llvm/tools/llvm-objdump/CMakeLists.txt +++ b/llvm/tools/llvm-objdump/CMakeLists.txt @@ -40,7 +40,7 @@ add_llvm_tool(llvm-objdump ) if(NOT LLVM_TOOL_LLVM_DRIVER_BUILD) - target_link_libraries(llvm-objdump PRIVATE LLVMDebuginfod) + target_link_libraries(llvm-objdump PRIVATE LLVMDebuginfod LLVMHTTP) endif() add_llvm_tool_symlink(llvm-otool llvm-objdump) diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp index 221b884e0c06c..1a9644e3226c3 100644 --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -34,8 +34,8 @@ #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Demangle/Demangle.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" #include "llvm/MC/MCDisassembler/MCRelocationInfo.h" diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp index 45eac90aef935..49f841dd1e78f 100644 --- a/llvm/tools/llvm-profdata/llvm-profdata.cpp +++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp @@ -13,7 +13,7 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Object/Binary.h" #include "llvm/ProfileData/DataAccessProf.h" diff --git a/llvm/tools/llvm-symbolizer/CMakeLists.txt b/llvm/tools/llvm-symbolizer/CMakeLists.txt index 6f8de1f1f4943..15ef8337f63c4 100644 --- a/llvm/tools/llvm-symbolizer/CMakeLists.txt +++ b/llvm/tools/llvm-symbolizer/CMakeLists.txt @@ -24,7 +24,7 @@ add_llvm_tool(llvm-symbolizer ) if(NOT LLVM_TOOL_LLVM_DRIVER_BUILD) - target_link_libraries(llvm-symbolizer PRIVATE LLVMDebuginfod) + target_link_libraries(llvm-symbolizer PRIVATE LLVMDebuginfod LLVMHTTP) endif() add_llvm_tool_symlink(llvm-addr2line llvm-symbolizer) diff --git a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp index 4784dafeb2948..7daea8d18e363 100644 --- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -25,7 +25,7 @@ #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt index d22613d12d6dc..d2932520bbb80 100644 --- a/llvm/unittests/CMakeLists.txt +++ b/llvm/unittests/CMakeLists.txt @@ -45,6 +45,7 @@ add_subdirectory(ExecutionEngine) add_subdirectory(FileCheck) add_subdirectory(Frontend) add_subdirectory(FuzzMutate) +add_subdirectory(HTTP) add_subdirectory(InterfaceStub) add_subdirectory(IR) add_subdirectory(LineEditor) diff --git a/llvm/unittests/Debuginfod/CMakeLists.txt b/llvm/unittests/Debuginfod/CMakeLists.txt index 9b084ff33f3b7..89d2c53fabac7 100644 --- a/llvm/unittests/Debuginfod/CMakeLists.txt +++ b/llvm/unittests/Debuginfod/CMakeLists.txt @@ -1,9 +1,9 @@ add_llvm_unittest(DebuginfodTests - HTTPServerTests.cpp DebuginfodTests.cpp ) target_link_libraries(DebuginfodTests PRIVATE LLVMDebuginfod + LLVMHTTP LLVMTestingSupport ) diff --git a/llvm/unittests/Debuginfod/DebuginfodTests.cpp b/llvm/unittests/Debuginfod/DebuginfodTests.cpp index 5312912599e93..00d3017638f38 100644 --- a/llvm/unittests/Debuginfod/DebuginfodTests.cpp +++ b/llvm/unittests/Debuginfod/DebuginfodTests.cpp @@ -1,4 +1,4 @@ -//===-- llvm/unittest/Support/DebuginfodTests.cpp - unit tests ------------===// +//===--- DebuginfodTests.cpp - unit tests ---------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,7 +7,7 @@ //===----------------------------------------------------------------------===// #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Testing/Support/Error.h" diff --git a/llvm/unittests/HTTP/CMakeLists.txt b/llvm/unittests/HTTP/CMakeLists.txt new file mode 100644 index 0000000000000..411a7280998b9 --- /dev/null +++ b/llvm/unittests/HTTP/CMakeLists.txt @@ -0,0 +1,8 @@ +add_llvm_unittest(HTTPTests + HTTPServerTests.cpp + ) + +target_link_libraries(HTTPTests PRIVATE + LLVMHTTP + LLVMTestingSupport + ) diff --git a/llvm/unittests/Debuginfod/HTTPServerTests.cpp b/llvm/unittests/HTTP/HTTPServerTests.cpp similarity index 94% rename from llvm/unittests/Debuginfod/HTTPServerTests.cpp rename to llvm/unittests/HTTP/HTTPServerTests.cpp index cd1d5f2d9fc70..cc48434149437 100644 --- a/llvm/unittests/Debuginfod/HTTPServerTests.cpp +++ b/llvm/unittests/HTTP/HTTPServerTests.cpp @@ -1,4 +1,4 @@ -//===-- llvm/unittest/Support/HTTPServer.cpp - unit tests -------*- C++ -*-===// +//===-- HTTPServerTests.cpp - unit tests ----------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,8 +7,8 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/StringExtras.h" -#include "llvm/Debuginfod/HTTPClient.h" -#include "llvm/Debuginfod/HTTPServer.h" +#include "llvm/HTTP/HTTPClient.h" +#include "llvm/HTTP/HTTPServer.h" #include "llvm/Support/Error.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Testing/Support/Error.h" @@ -237,15 +237,15 @@ TEST_F(HTTPClientServerTest, ClientTimeout) { TEST_F(HTTPClientServerTest, PathMatching) { HTTPServer Server; - EXPECT_THAT_ERROR( - Server.get(R"(/abc/(.*)/(.*))", - [&](HTTPServerRequest &Request) { - EXPECT_EQ(Request.UrlPath, "/abc/1/2"); - ASSERT_THAT(Request.UrlPathMatches, - testing::ElementsAre("1", "2")); - Request.setResponse({200u, "text/plain", Request.UrlPath}); - }), - Succeeded()); + EXPECT_THAT_ERROR(Server.get(R"(/abc/(.*)/(.*))", + [&](HTTPServerRequest &Request) { + EXPECT_EQ(Request.UrlPath, "/abc/1/2"); + ASSERT_THAT(Request.UrlPathMatches, + testing::ElementsAre("1", "2")); + Request.setResponse( + {200u, "text/plain", Request.UrlPath}); + }), + Succeeded()); EXPECT_THAT_ERROR(Server.get(UrlPathPattern, [&](HTTPServerRequest &Request) { llvm_unreachable(