From e8f982e50ad85d2ca028139dd4ec0e026bc64488 Mon Sep 17 00:00:00 2001 From: David Spickett Date: Mon, 1 Sep 2025 13:20:30 +0100 Subject: [PATCH 01/30] [llldb][test] Mark a DWO test unsupported on Darwin and Windows (#156306) This uses split DWARF and from looking at other tests, it should not be running on Darwin or Windows. It does pass using the DIA PDB plugin but I think this is misleading because it's not actually testing the intended feature. When the native PDB plugin is used it fails because it cannot set a breakpoint. I don't see a point to running this test on Windows at all. Native PDB plugin test failures are being tracked in #114906. --- .../Shell/SymbolFile/DWARF/dwo-static-data-member-access.test | 3 +++ 1 file changed, 3 insertions(+) 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 From e0dada5fdcc430db38b12dd5317b24fd53ff3d6b Mon Sep 17 00:00:00 2001 From: David Spickett Date: Mon, 1 Sep 2025 14:31:09 +0100 Subject: [PATCH 02/30] [lldb][test] Use lld on Windows in frame format test (#156320) link.exe discards DWARF information. Other linkers on non-Windows do not. Uses the same solution as TestFrameFunctionInlined.test. This test was failing with the native PDB plugin but shouldn't have been using PDB anyway (see #114906). Passes with DWARF and lld. --- .../Shell/Settings/TestFrameFormatFunctionReturnObjC.test | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From 0a15ba1fac1655eea7729f4a9461eb1ae73e7dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 6 May 2026 14:51:55 +0200 Subject: [PATCH 03/30] [lldb] Consider FileSpec from ObjectFilePDB in SymbolFilePDB (not upstream) --- lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp index e24a4249a731f..3333013a0f8ee 100644 --- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp @@ -163,6 +163,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, From 8d4c9dfa2e5e7bee8fbac42b5c72b6e618b7c466 Mon Sep 17 00:00:00 2001 From: nerix Date: Tue, 12 Aug 2025 19:49:47 +0200 Subject: [PATCH 04/30] Reland "[LLDB][NativePDB] Find functions by basename" ( #152295) (#153160) Relands #152295. Checking for the overloads did not account for them being out of order. For example, [the failed output](https://github.com/llvm/llvm-project/pull/152295#issuecomment-3177563247) contained the overloads, but out of order. The last commit here fixes that by using `-DAG`. --------- Co-authored-by: Jonas Devlieghere --- .../NativePDB/SymbolFileNativePDB.cpp | 153 +++++++++++++++--- .../NativePDB/SymbolFileNativePDB.h | 9 ++ .../SymbolFile/NativePDB/find-functions.cpp | 87 +++++++++- 3 files changed, 224 insertions(+), 25 deletions(-) diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index 441bae3de4039..d82da5cdf553d 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" @@ -1772,6 +1773,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()) + 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 +1897,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/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)" From 07948be7ff3301a2022e4106150b9f14094e7e46 Mon Sep 17 00:00:00 2001 From: nerix Date: Thu, 14 Aug 2025 15:23:39 +0200 Subject: [PATCH 05/30] [LLDB][NativePDB] Ignore functions with no type in name lookup (#153382) Some functions don't have the `FunctionType` set. We can't look these up and won't be able to call them, so ignore them when caching the function names. This does fix the failures caused by https://github.com/llvm/llvm-project/pull/153160 mentioned in https://github.com/llvm/llvm-project/pull/153160#issuecomment-3183062431. However, in `lldb-shell::msstl_smoke.cpp` there's another failure not introduced by #153160 (fixed with #153386). --- .../source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index d82da5cdf553d..13caa8f9acf2e 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -1806,7 +1806,7 @@ void SymbolFileNativePDB::CacheFunctionNames() { llvm::cantFail(SymbolDeserializer::deserializeAs(*iter)); if ((proc.Flags & ProcSymFlags::IsUnreachable) != ProcSymFlags::None) continue; - if (proc.Name.empty()) + if (proc.Name.empty() || proc.FunctionType.isSimple()) continue; // The function/procedure symbol only contains the demangled name. From 93bf224e7900538c1dfed21beac4292e2f07981d Mon Sep 17 00:00:00 2001 From: nerix Date: Thu, 11 Sep 2025 22:35:19 +0200 Subject: [PATCH 06/30] [LLDB][NativePDB] Implement `AddSymbols` (#154121) This PR implements `SymbolFileNativePDB::AddSymbols` which adds public symbols to the symbol table. These symbols are found in the publics stream. It contains mangled names coupled with addresses. Addresses are a pair of (segment, offset). If I understood correctly, then the segment is the section ID from the COFF header. Sections are already [constructed](https://github.com/llvm/llvm-project/blob/c48ec7fb60b5e0b4100731d75f82ea63c0ec7b45/lldb/source/Plugins/ObjectFile/PECOFF/ObjectFilePECOFF.cpp#L1048) using this 1-based index ([MS docs](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers)). This allows us to use `section_list->FindSectionByID`. --- .../NativePDB/SymbolFileNativePDB.cpp | 39 +++++++++++- .../SymbolFile/NativePDB/disassembly.cpp | 4 +- .../SymbolFile/NativePDB/inline_sites.test | 7 +++ .../NativePDB/local-variables-registers.s | 30 ++++++++++ .../NativePDB/nested-blocks-same-address.s | 1 + .../Shell/SymbolFile/NativePDB/symtab.cpp | 59 +++++++++++++++++++ .../Unwind/windows-unaligned-x86_64.test | 3 +- 7 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index 13caa8f9acf2e..8727e6a17afa8 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -1153,7 +1153,44 @@ 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->GetSectionList(); + if (!section_list) + return; + + 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)); + + auto 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; + + 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)); + } +} size_t SymbolFileNativePDB::ParseFunctions(CompileUnit &comp_unit) { std::lock_guard guard(GetModuleMutex()); 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/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..81d643d9572d8 --- /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 {{.*}} main +// CHECK-DAG: Code {{.*}} ?b_func@?$B@F@ns@@QEBAHXZ +// CHECK-DAG: Code {{.*}} ?something@A@@QEAAXXZ +// CHECK-DAG: Code {{.*}} ??_GDyn@ns@@UEAAPEAXI@Z +// CHECK-DAG: Code {{.*}} ??2@YAPEAX_K@Z +// CHECK-DAG: Code {{.*}} ??3@YAXPEAX_K@Z +// CHECK-DAG: Code {{.*}} ?static_fn@C@?$B@H@ns@@SAHXZ +// CHECK-DAG: Code {{.*}} ?a_function@ns@@YAHXZ +// CHECK-DAG: Code {{.*}} ?static_fn@C@?$B@_N@ns@@SAHXZ +// CHECK-DAG: Code {{.*}} ??1Dyn@ns@@UEAA@XZ +// CHECK-DAG: Code {{.*}} ??0Dyn@ns@@QEAA@XZ +// CHECK-DAG: Data {{.*}} ?global_int@@3HA +// CHECK-DAG: Data {{.*}} ??_7Dyn@ns@@6B@ +// CHECK-DAG: Data {{.*}} ?global_a@@3UA@@A +// CHECK-DAG: Data {{.*}} ?global_c@@3UC@?$B@_J@ns@@A 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. From 42d1fc2316bb6977ae0e20becd26a7359f153f6b Mon Sep 17 00:00:00 2001 From: nerix Date: Fri, 31 Oct 2025 10:33:37 +0100 Subject: [PATCH 07/30] [LLDB][NativePDB] Estimate symbol sizes (#165727) In #165604, a test was skipped on Windows, because the native PDB plugin didn't set sizes on symbols. While the test isn't compiled with debug info, it's linked with `-gdwarf`, causing a PDB to be created on Windows. This PDB will only contain the public symbols (written by the linker) and section information. The symbols themselves don't have a size, however the DIA SDK sets a size for them. It seems like, for these data symbols, the size given from DIA is the distance to the next symbol (or the section end). This PR implements the naive approach for the native plugin. The main difference is in function/code symbols. There, DIA searches for a corresponding `S_GPROC32` which have a "code size" that is sometimes slightly smaller than the difference to the next symbol. --- .../NativePDB/SymbolFileNativePDB.cpp | 67 ++++++++++++++----- .../multiple-slides/TestMultipleSlides.py | 3 + .../Shell/SymbolFile/NativePDB/symtab.cpp | 30 ++++----- 3 files changed, 69 insertions(+), 31 deletions(-) diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index 8727e6a17afa8..cc7fabce94d01 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -1158,7 +1158,35 @@ void SymbolFileNativePDB::AddSymbols(Symtab &symtab) { if (!section_list) return; - for (auto pid : m_index->publics().getPublicsTable()) { + 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(); @@ -1166,8 +1194,11 @@ void SymbolFileNativePDB::AddSymbols(Symtab &symtab) { 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); - auto section_sp = section_list->FindSectionByID(pub.Segment); if (!section_sp) continue; @@ -1176,20 +1207,24 @@ void SymbolFileNativePDB::AddSymbols(Symtab &symtab) { (pub.Flags & PublicSymFlags::Code) != PublicSymFlags::None) type = eSymbolTypeCode; - 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_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) { 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/Shell/SymbolFile/NativePDB/symtab.cpp b/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp index 81d643d9572d8..5a63ec2361151 100644 --- a/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp +++ b/lldb/test/Shell/SymbolFile/NativePDB/symtab.cpp @@ -42,18 +42,18 @@ int main(int argc, char **argv) { return ns::a_function() + b.b_func(); } -// CHECK-DAG: Code {{.*}} main -// CHECK-DAG: Code {{.*}} ?b_func@?$B@F@ns@@QEBAHXZ -// CHECK-DAG: Code {{.*}} ?something@A@@QEAAXXZ -// CHECK-DAG: Code {{.*}} ??_GDyn@ns@@UEAAPEAXI@Z -// CHECK-DAG: Code {{.*}} ??2@YAPEAX_K@Z -// CHECK-DAG: Code {{.*}} ??3@YAXPEAX_K@Z -// CHECK-DAG: Code {{.*}} ?static_fn@C@?$B@H@ns@@SAHXZ -// CHECK-DAG: Code {{.*}} ?a_function@ns@@YAHXZ -// CHECK-DAG: Code {{.*}} ?static_fn@C@?$B@_N@ns@@SAHXZ -// CHECK-DAG: Code {{.*}} ??1Dyn@ns@@UEAA@XZ -// CHECK-DAG: Code {{.*}} ??0Dyn@ns@@QEAA@XZ -// CHECK-DAG: Data {{.*}} ?global_int@@3HA -// CHECK-DAG: Data {{.*}} ??_7Dyn@ns@@6B@ -// CHECK-DAG: Data {{.*}} ?global_a@@3UA@@A -// CHECK-DAG: Data {{.*}} ?global_c@@3UC@?$B@_J@ns@@A +// 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 From a0e5b377aec6892312a198b862cffb30648d6f78 Mon Sep 17 00:00:00 2001 From: nerix Date: Fri, 28 Nov 2025 14:58:32 +0100 Subject: [PATCH 08/30] [LLDB][PDB] Access object file through module (#169728) When a PDB is loaded through `target symbols add `, its `m_objectfile_sp` is an `ObjectFilePDB` instead of `ObjectFilePECOFF` (the debugged module). In both the native and DIA plugin, some paths assumed that `m_objectfile_sp` is the debugged module. With this PR, they go through `m_objfile_sp->GetModule()->GetObjectFile()`. For the DIA plugin, this lead to an assertion failure (https://github.com/llvm/llvm-project/issues/169628#issuecomment-3582555277) and for both plugins, it meant that the symbol table wasn't loaded. --- .../NativePDB/SymbolFileNativePDB.cpp | 3 +- .../Plugins/SymbolFile/PDB/SymbolFilePDB.cpp | 9 +++-- .../test/Shell/SymbolFile/PDB/add-symbols.cpp | 39 +++++++++++++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 lldb/test/Shell/SymbolFile/PDB/add-symbols.cpp diff --git a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp index cc7fabce94d01..c955fd87333b8 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -1154,7 +1154,8 @@ lldb::LanguageType SymbolFileNativePDB::ParseLanguage(CompileUnit &comp_unit) { } void SymbolFileNativePDB::AddSymbols(Symtab &symtab) { - 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/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp index 3333013a0f8ee..3dc0be1836e94 100644 --- a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp @@ -205,8 +205,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) @@ -1396,7 +1398,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/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() {} From 4755c278c86c99e01cfa95abd218c822af30a1fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Tue, 17 Mar 2026 09:30:25 +0100 Subject: [PATCH 09/30] Reland "[lldb] Initial plugin and test for SymbolLocatorSymStore" (#185658) Minimal infrastructure for a the SymbolLocator plugin that fetches debug info from Microsoft SymStore repositories. This can work cross-platform and for various debug info formats in principle, but the current plan is focussed on PE/COFF on Windows with debug info in PDB files. Once we have a stable first version, we'd like to add features like download, environment variables, caching and progress feedback for users. SymbolVendorPECOFF was tailored towards DWARF debug info so far. I added code to load the PDB path from the executable (it only checked gnu_debuglink so far) and not bail out if DWARF sections are missing, so that in the PDB case we still call AddSymbolFileRepresentation() in the very end of CreateInstance(). The API test in this patch mocks the directory layout from SymStore, so it doesn't depend on SymStore.exe from the Windows SDK. It runs on all platforms that link debug info in a PDB file, which is still just Windows, but it could be cross-platform in principle. ----- Relands with minor fixes: API tests create mocked SymStore in the test's build directory. One log instruction was moved. One more object access goes through module in SymbolFile. --- .../ObjectFile/PECOFF/ObjectFilePECOFF.cpp | 21 +++ .../ObjectFile/PECOFF/ObjectFilePECOFF.h | 2 + .../NativePDB/SymbolFileNativePDB.cpp | 5 + .../Plugins/SymbolFile/PDB/SymbolFilePDB.cpp | 3 +- .../Plugins/SymbolLocator/CMakeLists.txt | 1 + .../SymbolLocator/SymStore/CMakeLists.txt | 20 +++ .../SymStore/SymbolLocatorSymStore.cpp | 147 ++++++++++++++++++ .../SymStore/SymbolLocatorSymStore.h | 50 ++++++ .../SymbolLocatorSymStoreProperties.td | 7 + .../PECOFF/SymbolVendorPECOFF.cpp | 58 ++++--- lldb/test/API/symstore/Makefile | 2 + lldb/test/API/symstore/TestSymStoreLocal.py | 121 ++++++++++++++ lldb/test/API/symstore/main.c | 5 + .../dependent-modules-nodupe-windows.test | 5 +- 14 files changed, 420 insertions(+), 27 deletions(-) create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h create mode 100644 lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td create mode 100644 lldb/test/API/symstore/Makefile create mode 100644 lldb/test/API/symstore/TestSymStoreLocal.py create mode 100644 lldb/test/API/symstore/main.c 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 c955fd87333b8..ecaa689884dc4 100644 --- a/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp +++ b/lldb/source/Plugins/SymbolFile/NativePDB/SymbolFileNativePDB.cpp @@ -357,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()); diff --git a/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp b/lldb/source/Plugins/SymbolFile/PDB/SymbolFilePDB.cpp index 3dc0be1836e94..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) { 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/SymStore/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt new file mode 100644 index 0000000000000..b0da27f26c6a8 --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt @@ -0,0 +1,20 @@ +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 + ) + +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..64779609398b9 --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -0,0 +1,147 @@ +//===----------------------------------------------------------------------===// +// +// 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/Support/Endian.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/Path.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; + } +}; + +} // namespace + +static PluginProperties &GetGlobalPluginProperties() { + static PluginProperties g_settings; + return g_settings; +} + +SymbolLocatorSymStore::SymbolLocatorSymStore() : SymbolLocator() {} + +void SymbolLocatorSymStore::Initialize() { + // First version can only locate PDB in local SymStore (no download yet). + PluginManager::RegisterPlugin( + GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, + nullptr, LocateExecutableSymbolFile, nullptr, nullptr, + SymbolLocatorSymStore::DebuggerInitialize); +} + +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::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() { + return "Symbol locator for PDB in SymStore"; +} + +SymbolLocator *SymbolLocatorSymStore::CreateInstance() { + return new SymbolLocatorSymStore(); +} + +// 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 +// +static std::string formatSymStoreKey(const UUID &uuid) { + llvm::ArrayRef bytes = uuid.GetBytes(); + uint32_t age = llvm::support::endian::read32be(bytes.data() + 16); + constexpr bool LowerCase = false; + return llvm::toHex(bytes.slice(0, 16), LowerCase) + std::to_string(age); +} + +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_LOGV(log, "Failed to resolve symbol PDB module: PDB name empty"); + return {}; + } + + LLDB_LOGV(log, "LocateExecutableSymbolFile {0} with UUID {1}", pdb_name, + uuid.GetAsString()); + if (uuid.GetBytes().size() != 20) { + LLDB_LOGV(log, "Failed to resolve symbol PDB module: UUID invalid"); + return {}; + } + + std::string key = formatSymStoreKey(uuid); + Args sym_store_urls = GetGlobalPluginProperties().GetURLs(); + for (const Args::ArgEntry &url : sym_store_urls) { + llvm::SmallString<256> path; + llvm::sys::path::append(path, url.ref(), pdb_name, key, pdb_name); + FileSpec spec(path); + if (FileSystem::Instance().Exists(spec)) { + LLDB_LOGV(log, "Found {0} in SymStore {1}", pdb_name, url.ref()); + return spec; + } + } + + return {}; +} diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h new file mode 100644 index 0000000000000..52ec04cae387b --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h @@ -0,0 +1,50 @@ +//===----------------------------------------------------------------------===// +// +// 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); +}; + +} // 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..bd5fb6a06f6c7 --- /dev/null +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td @@ -0,0 +1,7 @@ +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 for symbols">; +} 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/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/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStoreLocal.py new file mode 100644 index 0000000000000..cedc76ac4354c --- /dev/null +++ b/lldb/test/API/symstore/TestSymStoreLocal.py @@ -0,0 +1,121 @@ +import os +import shutil +import tempfile + +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 in a + temporary directory. + """ + + def __init__(self, test, exe, pdb): + self._test = test + self._exe = exe + self._pdb = pdb + self._tmp = None + + 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) + self._tmp = self._test.getBuildArtifact("tmp") + pdb_dir = os.path.join(self._tmp, self._pdb, key) + os.makedirs(pdb_dir) + shutil.move( + self._test.getBuildArtifact(self._pdb), + os.path.join(pdb_dir, self._pdb), + ) + return self._tmp + + def __exit__(self, *exc_info): + """ + Clean up and delete original exe so next make won't skip link command. + """ + shutil.rmtree(self._tmp) + os.remove(self._test.getBuildArtifact(self._exe)) + self._test.runCmd("settings clear plugin.symbol-locator.symstore") + + +class SymStoreLocalTests(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 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 symstore_dir: + self.runCmd( + f"settings set plugin.symbol-locator.symstore.urls {symstore_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 symstore_dir: + self.runCmd( + f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" + ) + self.try_breakpoint(exe, should_have_loc=True) 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/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 From 87d5335e93e665d40688238a5b1b674ee2e94723 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Thu, 12 Mar 2026 15:20:48 +0100 Subject: [PATCH 10/30] Re-reland "[Support] Move HTTP client/server to new LLVMSupportHTTP lib (NFC)" (#186074) Relocate HTTPClient and HTTPServer from the Debuginfod library to llvm/Support/HTTP so they can be reused by other components. --------- Relanding with fixes in CMakeLists.txt to account for dependency to new LLVMSupportHTTP in tools. Relanding with one more fix in libSupportHTTP that adds it as a component in libLLVM.so --------- Co-authored-by: Alexandre Ganea Co-authored-by: Jonas Devlieghere --- .../SymbolLocator/Debuginfod/CMakeLists.txt | 1 + .../Debuginfod/SymbolLocatorDebuginfod.cpp | 2 +- llvm/include/llvm/Debuginfod/Debuginfod.h | 3 +-- .../{Debuginfod => Support/HTTP}/HTTPClient.h | 8 +++---- .../{Debuginfod => Support/HTTP}/HTTPServer.h | 8 +++---- llvm/lib/Debuginfod/CMakeLists.txt | 13 +--------- llvm/lib/Debuginfod/Debuginfod.cpp | 2 +- llvm/lib/Support/CMakeLists.txt | 1 + llvm/lib/Support/HTTP/CMakeLists.txt | 20 ++++++++++++++++ .../HTTP}/HTTPClient.cpp | 8 ++++--- .../HTTP}/HTTPServer.cpp | 13 ++++------ llvm/tools/llvm-cov/CMakeLists.txt | 1 + llvm/tools/llvm-cov/CodeCoverage.cpp | 2 +- .../tools/llvm-debuginfod-find/CMakeLists.txt | 1 + .../llvm-debuginfod-find.cpp | 2 +- llvm/tools/llvm-debuginfod/CMakeLists.txt | 1 + .../tools/llvm-debuginfod/llvm-debuginfod.cpp | 2 +- llvm/tools/llvm-objdump/CMakeLists.txt | 1 + llvm/tools/llvm-objdump/llvm-objdump.cpp | 2 +- llvm/tools/llvm-profdata/CMakeLists.txt | 1 + llvm/tools/llvm-profdata/llvm-profdata.cpp | 2 +- llvm/tools/llvm-symbolizer/CMakeLists.txt | 1 + .../tools/llvm-symbolizer/llvm-symbolizer.cpp | 2 +- llvm/unittests/Debuginfod/CMakeLists.txt | 2 +- llvm/unittests/Debuginfod/DebuginfodTests.cpp | 4 ++-- llvm/unittests/Support/HTTP/CMakeLists.txt | 8 +++++++ .../HTTP}/HTTPServerTests.cpp | 24 +++++++++---------- 27 files changed, 79 insertions(+), 56 deletions(-) rename llvm/include/llvm/{Debuginfod => Support/HTTP}/HTTPClient.h (92%) rename llvm/include/llvm/{Debuginfod => Support/HTTP}/HTTPServer.h (95%) create mode 100644 llvm/lib/Support/HTTP/CMakeLists.txt rename llvm/lib/{Debuginfod => Support/HTTP}/HTTPClient.cpp (95%) rename llvm/lib/{Debuginfod => Support/HTTP}/HTTPServer.cpp (95%) create mode 100644 llvm/unittests/Support/HTTP/CMakeLists.txt rename llvm/unittests/{Debuginfod => Support/HTTP}/HTTPServerTests.cpp (94%) diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt index f07e93e131376..4fd27e4f4b49f 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 + LLVMSupportHTTP ) add_dependencies(lldbPluginSymbolLocatorDebuginfod diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp b/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp index f9aa6b1a98765..03d51ed8a3eea 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/Support/HTTP/HTTPClient.h" using namespace lldb; using namespace lldb_private; diff --git a/llvm/include/llvm/Debuginfod/Debuginfod.h b/llvm/include/llvm/Debuginfod/Debuginfod.h index 99fe15ad85979..0bdb5d3ce3882 100644 --- a/llvm/include/llvm/Debuginfod/Debuginfod.h +++ b/llvm/include/llvm/Debuginfod/Debuginfod.h @@ -20,12 +20,11 @@ #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/Object/BuildID.h" #include "llvm/Support/Error.h" +#include "llvm/Support/HTTP/HTTPServer.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mutex.h" #include "llvm/Support/RWMutex.h" diff --git a/llvm/include/llvm/Debuginfod/HTTPClient.h b/llvm/include/llvm/Support/HTTP/HTTPClient.h similarity index 92% rename from llvm/include/llvm/Debuginfod/HTTPClient.h rename to llvm/include/llvm/Support/HTTP/HTTPClient.h index 6ded55502f055..aa4727ad33024 100644 --- a/llvm/include/llvm/Debuginfod/HTTPClient.h +++ b/llvm/include/llvm/Support/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_SUPPORT_HTTP_HTTPCLIENT_H +#define LLVM_SUPPORT_HTTP_HTTPCLIENT_H #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -85,4 +85,4 @@ class HTTPClient { } // end namespace llvm -#endif // LLVM_DEBUGINFOD_HTTPCLIENT_H +#endif // LLVM_SUPPORT_HTTP_HTTPCLIENT_H diff --git a/llvm/include/llvm/Debuginfod/HTTPServer.h b/llvm/include/llvm/Support/HTTP/HTTPServer.h similarity index 95% rename from llvm/include/llvm/Debuginfod/HTTPServer.h rename to llvm/include/llvm/Support/HTTP/HTTPServer.h index c200089200ab7..101ca0e6fbd6b 100644 --- a/llvm/include/llvm/Debuginfod/HTTPServer.h +++ b/llvm/include/llvm/Support/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_SUPPORT_HTTP_HTTPSERVER_H +#define LLVM_SUPPORT_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_SUPPORT_HTTP_HTTPSERVER_H diff --git a/llvm/lib/Debuginfod/CMakeLists.txt b/llvm/lib/Debuginfod/CMakeLists.txt index b1329bd2d077e..c31481cc97f08 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,8 +8,6 @@ endif() add_llvm_library(LLVMDebuginfod BuildIDFetcher.cpp Debuginfod.cpp - HTTPClient.cpp - HTTPServer.cpp ADDITIONAL_HEADER_DIRS ${LLVM_MAIN_INCLUDE_DIR}/llvm/Debuginfod @@ -29,6 +17,7 @@ add_llvm_library(LLVMDebuginfod LINK_COMPONENTS Support + SupportHTTP Symbolize DebugInfoDWARF BinaryFormat diff --git a/llvm/lib/Debuginfod/Debuginfod.cpp b/llvm/lib/Debuginfod/Debuginfod.cpp index 12f817c9e4bf0..6847dc092dfdb 100644 --- a/llvm/lib/Debuginfod/Debuginfod.cpp +++ b/llvm/lib/Debuginfod/Debuginfod.cpp @@ -27,7 +27,6 @@ #include "llvm/BinaryFormat/Magic.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Object/BuildID.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Support/CachePruning.h" @@ -35,6 +34,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileUtilities.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/ThreadPool.h" diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 1b4c9e4fea5a9..9f708541eaa27 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -135,6 +135,7 @@ if (UNIX AND ${CMAKE_SYSTEM_NAME} MATCHES "AIX") endif() add_subdirectory(BLAKE3) +add_subdirectory(HTTP) add_llvm_component_library(LLVMSupport ABIBreak.cpp diff --git a/llvm/lib/Support/HTTP/CMakeLists.txt b/llvm/lib/Support/HTTP/CMakeLists.txt new file mode 100644 index 0000000000000..9bf1da8c60c88 --- /dev/null +++ b/llvm/lib/Support/HTTP/CMakeLists.txt @@ -0,0 +1,20 @@ +# 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() + +add_llvm_component_library(LLVMSupportHTTP + HTTPClient.cpp + HTTPServer.cpp + + LINK_LIBS + ${imported_libs} + + LINK_COMPONENTS + Support +) diff --git a/llvm/lib/Debuginfod/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp similarity index 95% rename from llvm/lib/Debuginfod/HTTPClient.cpp rename to llvm/lib/Support/HTTP/HTTPClient.cpp index 4cca250746a59..6301f86da4086 100644 --- a/llvm/lib/Debuginfod/HTTPClient.cpp +++ b/llvm/lib/Support/HTTP/HTTPClient.cpp @@ -1,4 +1,4 @@ -//===-- llvm/Debuginfod/HTTPClient.cpp - HTTP client library ----*- C++ -*-===// +//===--- 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. @@ -12,11 +12,13 @@ /// //===----------------------------------------------------------------------===// -#include "llvm/Debuginfod/HTTPClient.h" +#include "llvm/Support/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 @@ -39,7 +41,7 @@ class HTTPClientCleanup { public: ~HTTPClientCleanup() { HTTPClient::cleanup(); } }; -static const HTTPClientCleanup Cleanup; +ManagedStatic Cleanup; #ifdef LLVM_ENABLE_CURL diff --git a/llvm/lib/Debuginfod/HTTPServer.cpp b/llvm/lib/Support/HTTP/HTTPServer.cpp similarity index 95% rename from llvm/lib/Debuginfod/HTTPServer.cpp rename to llvm/lib/Support/HTTP/HTTPServer.cpp index 1264353ce4b33..c6fd49f7ee623 100644 --- a/llvm/lib/Debuginfod/HTTPServer.cpp +++ b/llvm/lib/Support/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/Support/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/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt index 7acc87e08a9ef..f902469689ccd 100644 --- a/llvm/tools/llvm-cov/CMakeLists.txt +++ b/llvm/tools/llvm-cov/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS Core Support + SupportHTTP Object Coverage ProfileData diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp index 6c66858c4de8c..375ef74818f5c 100644 --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -24,13 +24,13 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" diff --git a/llvm/tools/llvm-debuginfod-find/CMakeLists.txt b/llvm/tools/llvm-debuginfod-find/CMakeLists.txt index 39da11fcd9599..e23357008fe77 100644 --- a/llvm/tools/llvm-debuginfod-find/CMakeLists.txt +++ b/llvm/tools/llvm-debuginfod-find/CMakeLists.txt @@ -2,6 +2,7 @@ set(LLVM_LINK_COMPONENTS Option Object Support + SupportHTTP ) set(LLVM_TARGET_DEFINITIONS Opts.td) tablegen(LLVM Opts.inc -gen-opt-parser-defs) diff --git a/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp b/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp index 934833bf6fe42..8f1b9d0f4659d 100644 --- a/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp +++ b/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp @@ -19,10 +19,10 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/LLVMDriver.h" diff --git a/llvm/tools/llvm-debuginfod/CMakeLists.txt b/llvm/tools/llvm-debuginfod/CMakeLists.txt index 6b0a1193d1b22..1bca529dcd25e 100644 --- a/llvm/tools/llvm-debuginfod/CMakeLists.txt +++ b/llvm/tools/llvm-debuginfod/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS Option Support + SupportHTTP ) set(LLVM_TARGET_DEFINITIONS Opts.td) tablegen(LLVM Opts.inc -gen-opt-parser-defs) diff --git a/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp b/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp index 7b85166c1b4ae..16d95532a9edc 100644 --- a/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp +++ b/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp @@ -18,10 +18,10 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/ThreadPool.h" diff --git a/llvm/tools/llvm-objdump/CMakeLists.txt b/llvm/tools/llvm-objdump/CMakeLists.txt index 7e3197f0a0bd3..40e93762a41f9 100644 --- a/llvm/tools/llvm-objdump/CMakeLists.txt +++ b/llvm/tools/llvm-objdump/CMakeLists.txt @@ -12,6 +12,7 @@ set(LLVM_LINK_COMPONENTS Object Option Support + SupportHTTP Symbolize TargetParser ) diff --git a/llvm/tools/llvm-objdump/llvm-objdump.cpp b/llvm/tools/llvm-objdump/llvm-objdump.cpp index 221b884e0c06c..26906cb54075e 100644 --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -34,7 +34,6 @@ #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/MC/MCAsmInfo.h" #include "llvm/MC/MCContext.h" @@ -65,6 +64,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" diff --git a/llvm/tools/llvm-profdata/CMakeLists.txt b/llvm/tools/llvm-profdata/CMakeLists.txt index 165be9a2ea31b..f5f31df5f80ae 100644 --- a/llvm/tools/llvm-profdata/CMakeLists.txt +++ b/llvm/tools/llvm-profdata/CMakeLists.txt @@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS Object ProfileData Support + SupportHTTP ) add_llvm_tool(llvm-profdata diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp index 45eac90aef935..8c48d043cc212 100644 --- a/llvm/tools/llvm-profdata/llvm-profdata.cpp +++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp @@ -13,7 +13,6 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Object/Binary.h" #include "llvm/ProfileData/DataAccessProf.h" @@ -34,6 +33,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormattedStream.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/MD5.h" #include "llvm/Support/MemoryBuffer.h" diff --git a/llvm/tools/llvm-symbolizer/CMakeLists.txt b/llvm/tools/llvm-symbolizer/CMakeLists.txt index 6f8de1f1f4943..39937e08b7884 100644 --- a/llvm/tools/llvm-symbolizer/CMakeLists.txt +++ b/llvm/tools/llvm-symbolizer/CMakeLists.txt @@ -12,6 +12,7 @@ set(LLVM_LINK_COMPONENTS Object Option Support + SupportHTTP Symbolize ) diff --git a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp index 4784dafeb2948..7f7f17ef94750 100644 --- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -25,7 +25,6 @@ #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" @@ -34,6 +33,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/Path.h" #include "llvm/Support/StringSaver.h" diff --git a/llvm/unittests/Debuginfod/CMakeLists.txt b/llvm/unittests/Debuginfod/CMakeLists.txt index 9b084ff33f3b7..4ac75c0720e89 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 + LLVMSupportHTTP LLVMTestingSupport ) diff --git a/llvm/unittests/Debuginfod/DebuginfodTests.cpp b/llvm/unittests/Debuginfod/DebuginfodTests.cpp index 5312912599e93..2e39484e7559f 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,8 +7,8 @@ //===----------------------------------------------------------------------===// #include "llvm/Debuginfod/Debuginfod.h" -#include "llvm/Debuginfod/HTTPClient.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/Path.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" diff --git a/llvm/unittests/Support/HTTP/CMakeLists.txt b/llvm/unittests/Support/HTTP/CMakeLists.txt new file mode 100644 index 0000000000000..59e2393bfae06 --- /dev/null +++ b/llvm/unittests/Support/HTTP/CMakeLists.txt @@ -0,0 +1,8 @@ +add_llvm_unittest(SupportHTTPTests + HTTPServerTests.cpp + ) + +target_link_libraries(SupportHTTPTests PRIVATE + LLVMSupportHTTP + LLVMTestingSupport + ) diff --git a/llvm/unittests/Debuginfod/HTTPServerTests.cpp b/llvm/unittests/Support/HTTP/HTTPServerTests.cpp similarity index 94% rename from llvm/unittests/Debuginfod/HTTPServerTests.cpp rename to llvm/unittests/Support/HTTP/HTTPServerTests.cpp index cd1d5f2d9fc70..1066fdd2f7735 100644 --- a/llvm/unittests/Debuginfod/HTTPServerTests.cpp +++ b/llvm/unittests/Support/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,9 +7,9 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/StringExtras.h" -#include "llvm/Debuginfod/HTTPClient.h" -#include "llvm/Debuginfod/HTTPServer.h" #include "llvm/Support/Error.h" +#include "llvm/Support/HTTP/HTTPClient.h" +#include "llvm/Support/HTTP/HTTPServer.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.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( From 1e1c0339cb810509736886b91da6dad37d154300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 13 Mar 2026 22:27:22 +0100 Subject: [PATCH 11/30] [lldb] Fix liblldb linkage in libllvm build after 5eaf19a15129 (#186515) Referencing libSupportHTTP under LINK_LIBS of add_lldb_library() pulls in the archive even in a build configuration with LLVM_LINK_LLVM_DYLIB=On, where libSupportHTTP is part of libLLVM. This patch moves it to LINK_COMPONENTS to fix the issue. --- lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt index 4fd27e4f4b49f..25d3ff4b683b0 100644 --- a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt @@ -14,7 +14,9 @@ add_lldb_library(lldbPluginSymbolLocatorDebuginfod PLUGIN lldbHost lldbSymbol LLVMDebuginfod - LLVMSupportHTTP + + LINK_COMPONENTS + SupportHTTP ) add_dependencies(lldbPluginSymbolLocatorDebuginfod From b8a3e1500cfe31e174c403ccb5925bdb74b29bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Tue, 17 Mar 2026 15:50:59 +0100 Subject: [PATCH 12/30] [lldb] Fix permission issue in API test on lldb-x86_64-win (#187021) Deleting the executable at the end of this API test-case fails with a permission error, likely because lldb still holds a reference to the EXE. Exit explicitly to avoid that. --- lldb/test/API/symstore/TestSymStoreLocal.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStoreLocal.py index cedc76ac4354c..48e7361aab09c 100644 --- a/lldb/test/API/symstore/TestSymStoreLocal.py +++ b/lldb/test/API/symstore/TestSymStoreLocal.py @@ -1,6 +1,5 @@ import os import shutil -import tempfile import lldb from lldbsuite.test.decorators import * @@ -51,7 +50,7 @@ def __enter__(self): self._test.assertIsNotNone(key) self._tmp = self._test.getBuildArtifact("tmp") pdb_dir = os.path.join(self._tmp, self._pdb, key) - os.makedirs(pdb_dir) + os.makedirs(pdb_dir, exist_ok=True) shutil.move( self._test.getBuildArtifact(self._pdb), os.path.join(pdb_dir, self._pdb), @@ -97,6 +96,7 @@ def test_no_symstore_url(self): exe, sym = self.build_inferior() with MockedSymStore(self, exe, sym): self.try_breakpoint(exe, should_have_loc=False) + self.runCmd("quit", check=False) def test_external_lookup_off(self): """ @@ -108,6 +108,7 @@ def test_external_lookup_off(self): f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" ) self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False) + self.runCmd("quit", check=False) def test_local_dir(self): """ @@ -119,3 +120,4 @@ def test_local_dir(self): f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" ) self.try_breakpoint(exe, should_have_loc=True) + self.runCmd("quit", check=False) From cedcfd4f0651ded25abb272c03e4d93d6420c9de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 18 Mar 2026 09:20:25 +0100 Subject: [PATCH 13/30] [lldb] Avoid permission issue in API test with SHARED_BUILD_TESTCASE (#187072) Deleting the inferior binary after an API test-case causes issues on one of the Windows bots. The previous the fix attempt in ca15db1cd509c236 didn't succeed. We have to use isolated subfolders for each test-case. This is achieved easily by disabling SHARED_BUILD_TESTCASE. --- lldb/test/API/symstore/TestSymStoreLocal.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStoreLocal.py index 48e7361aab09c..c1abd80b56f3d 100644 --- a/lldb/test/API/symstore/TestSymStoreLocal.py +++ b/lldb/test/API/symstore/TestSymStoreLocal.py @@ -59,14 +59,14 @@ def __enter__(self): def __exit__(self, *exc_info): """ - Clean up and delete original exe so next make won't skip link command. + Remove symstore and reset settings """ shutil.rmtree(self._tmp) - os.remove(self._test.getBuildArtifact(self._exe)) self._test.runCmd("settings clear plugin.symbol-locator.symstore") class SymStoreLocalTests(TestBase): + SHARED_BUILD_TESTCASE = False TEST_WITH_PDB_DEBUG_INFO = True def build_inferior(self): @@ -96,7 +96,6 @@ def test_no_symstore_url(self): exe, sym = self.build_inferior() with MockedSymStore(self, exe, sym): self.try_breakpoint(exe, should_have_loc=False) - self.runCmd("quit", check=False) def test_external_lookup_off(self): """ @@ -108,7 +107,6 @@ def test_external_lookup_off(self): f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" ) self.try_breakpoint(exe, ext_lookup=False, should_have_loc=False) - self.runCmd("quit", check=False) def test_local_dir(self): """ @@ -120,4 +118,3 @@ def test_local_dir(self): f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" ) self.try_breakpoint(exe, should_have_loc=True) - self.runCmd("quit", check=False) From d5029e2834d422a9fa38718c02429d9694879778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 18 Mar 2026 11:03:10 +0100 Subject: [PATCH 14/30] [lldb] Skip file cleanup to avoid permission issue in API test (#187227) Deleting anything in the build directory of a test-case is causing an issue on one of the Windows bots. After the previous attempts in ca15db1cd509c236cd8138bcd098117d0106db56 and fdd2437af3cdc6d5fe199fcc9d991ccf503b55bd didn't help, we now skip the file cleanup altogether. --- lldb/test/API/symstore/TestSymStoreLocal.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStoreLocal.py index c1abd80b56f3d..98569d2b8c66f 100644 --- a/lldb/test/API/symstore/TestSymStoreLocal.py +++ b/lldb/test/API/symstore/TestSymStoreLocal.py @@ -15,15 +15,13 @@ class MockedSymStore: """ - Context Manager to populate a file structure equivalent to SymStore.exe in a - temporary directory. + 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._tmp = None def get_key_pdb(self, exe): """ @@ -48,20 +46,19 @@ def __enter__(self): if self._test.getDebugInfo() == "pdb": key = self.get_key_pdb(self._exe) self._test.assertIsNotNone(key) - self._tmp = self._test.getBuildArtifact("tmp") - pdb_dir = os.path.join(self._tmp, self._pdb, 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), ) - return self._tmp + return symstore_dir def __exit__(self, *exc_info): """ - Remove symstore and reset settings + Reset settings """ - shutil.rmtree(self._tmp) self._test.runCmd("settings clear plugin.symbol-locator.symstore") From 9217c1150314e5b834af6930488bd6fee7211f4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 20 Mar 2026 12:47:45 +0100 Subject: [PATCH 15/30] [lldb] Add HTTP support in SymbolLocatorSymStore (#186986) The initial version of SymbolLocatorSymStore supported servers only on local paths. This patch extends it to HTTP/HTTPS end-points. For that to work on Windows, we add a WinHTTP-based HTTP client backend in LLVM next to the existing CURL-based implementation. We don't add a HTTP server implementation, because there is no use right now. Test coverage for the LLVM part is built on llvm-debuginfod-find and works server-less, since it checks textual output of request headers. The existing CURL-based implementation uses the same approach. The LLDB API test for the specific SymbolLocatorSymStore feature spawns a HTTP server from Python. To keep the size of this patch within reasonable limits, the initial implementation of the SymbolLocatorSymStore feature is dump: There is no caching, no verification of downloaded files and no protection against file corruptions. We use a local implementation of LLVM's HTTPResponseHandler, but should think about extracting and reusing Debuginfod's StreamedHTTPResponseHandler in the future. The goal of this patch is a basic working implementation that is testable in isolation. We will iterate on it to improve it further. This should be fine since downloading from SymStores is not default-enabled yet. Users have to set their server URLs explicitly. --------- Co-authored-by: Alexandre Ganea --- .../SymbolLocator/SymStore/CMakeLists.txt | 1 + .../SymStore/SymbolLocatorSymStore.cpp | 155 ++++++++++- .../{TestSymStoreLocal.py => TestSymStore.py} | 54 +++- llvm/include/llvm/Support/HTTP/HTTPClient.h | 4 +- llvm/lib/Support/HTTP/CMakeLists.txt | 5 + llvm/lib/Support/HTTP/HTTPClient.cpp | 259 +++++++++++++++++- .../{headers.test => headers-curl.test} | 0 .../llvm-debuginfod-find/headers-winhttp.test | 31 +++ 8 files changed, 477 insertions(+), 32 deletions(-) rename lldb/test/API/symstore/{TestSymStoreLocal.py => TestSymStore.py} (68%) rename llvm/test/tools/llvm-debuginfod-find/{headers.test => headers-curl.test} (100%) create mode 100644 llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt index b0da27f26c6a8..775e0284cd8af 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt @@ -13,6 +13,7 @@ add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN lldbCore lldbHost lldbSymbol + LLVMSupportHTTP ) add_dependencies(lldbPluginSymbolLocatorSymStore diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index 64779609398b9..fe5f5af45b18d 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -20,6 +20,8 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Endian.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/Path.h" using namespace lldb; @@ -65,11 +67,11 @@ static PluginProperties &GetGlobalPluginProperties() { SymbolLocatorSymStore::SymbolLocatorSymStore() : SymbolLocator() {} void SymbolLocatorSymStore::Initialize() { - // First version can only locate PDB in local SymStore (no download yet). PluginManager::RegisterPlugin( GetPluginNameStatic(), GetPluginDescriptionStatic(), CreateInstance, nullptr, LocateExecutableSymbolFile, nullptr, nullptr, SymbolLocatorSymStore::DebuggerInitialize); + llvm::HTTPClient::initialize(); } void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) { @@ -85,6 +87,7 @@ void SymbolLocatorSymStore::DebuggerInitialize(Debugger &debugger) { void SymbolLocatorSymStore::Terminate() { PluginManager::UnregisterPlugin(CreateInstance); + llvm::HTTPClient::cleanup(); } llvm::StringRef SymbolLocatorSymStore::GetPluginDescriptionStatic() { @@ -95,6 +98,8 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() { return new SymbolLocatorSymStore(); } +namespace { + // RSDS entries store identity as a 20-byte UUID composed of 16-byte GUID and // 4-byte age: // 12345678-1234-5678-9ABC-DEF012345678-00000001 @@ -102,13 +107,150 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() { // SymStore key is a string with no separators and age as decimal: // 12345678123456789ABCDEF0123456781 // -static std::string formatSymStoreKey(const UUID &uuid) { +std::string formatSymStoreKey(const UUID &uuid) { llvm::ArrayRef bytes = uuid.GetBytes(); uint32_t age = llvm::support::endian::read32be(bytes.data() + 16); constexpr bool LowerCase = false; return llvm::toHex(bytes.slice(0, 16), LowerCase) + std::to_string(age); } +// This is a simple version of Debuginfod's StreamedHTTPResponseHandler. We +// should consider reusing that once we introduce caching. +class FileDownloadHandler : public llvm::HTTPResponseHandler { +private: + std::error_code m_ec; + llvm::raw_fd_ostream m_stream; + +public: + FileDownloadHandler(llvm::StringRef file) : m_stream(file.str(), m_ec) {} + virtual ~FileDownloadHandler() = default; + + llvm::Error handleBodyChunk(llvm::StringRef data) override { + // Propagate error from ctor. + if (m_ec) + return llvm::createStringError(m_ec, "Failed to open file for writing"); + m_stream.write(data.data(), data.size()); + if (std::error_code ec = m_stream.error()) + return llvm::createStringError(ec, "Error writing to file"); + + return llvm::Error::success(); + } +}; + +llvm::Error downloadFileHTTP(llvm::StringRef url, FileDownloadHandler dest) { + if (!llvm::HTTPClient::isAvailable()) + return llvm::createStringError( + std::make_error_code(std::errc::not_supported), + "HTTP client is not available"); + llvm::HTTPRequest Request(url); + Request.FollowRedirects = true; + + llvm::HTTPClient Client; + + // TODO: Since PDBs can be huge, we should distinguish between resolve, + // connect, send and receive. + Client.setTimeout(std::chrono::seconds(60)); + + if (llvm::Error Err = Client.perform(Request, dest)) + return Err; + + unsigned ResponseCode = Client.responseCode(); + if (ResponseCode != 200) { + return llvm::createStringError(std::make_error_code(std::errc::io_error), + "HTTP request failed with status code " + + std::to_string(ResponseCode)); + } + + return llvm::Error::success(); +} + +bool has_unsafe_characters(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 == ".."; +} + +// TODO: This is a dump initial implementation: It always downloads the file, it +// doesn't validate the result, it doesn't employ proper buffering for large +// files. +std::optional +requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, + llvm::StringRef pdb_name) { + using namespace llvm::sys; + Log *log = GetLog(LLDBLog::Symbols); + + // Make sure URL will be valid, portable, and compatible with symbol servers. + if (has_unsafe_characters(pdb_name)) { + Debugger::ReportWarning(llvm::formatv( + "Rejecting HTTP lookup for PDB file due to unsafe characters in " + "name: {0}", + pdb_name)); + return {}; + } + + // Construct the path for local storage. Configurable cache coming soon. + llvm::SmallString<128> cache_file; + if (!path::cache_directory(cache_file)) { + Debugger::ReportWarning("Failed to determine cache directory for SymStore"); + return {}; + } + path::append(cache_file, "lldb", "SymStore", pdb_name, key); + if (std::error_code ec = fs::create_directories(cache_file)) { + Debugger::ReportWarning( + llvm::formatv("Failed to create cache directory '{0}': {1}", cache_file, + ec.message())); + return {}; + } + path::append(cache_file, pdb_name); + + // Server has same directory structure with forward slashes as separators. + std::string source_url = + llvm::formatv("{0}/{1}/{2}/{1}", base_url, pdb_name, key); + if (llvm::Error err = downloadFileHTTP(source_url, cache_file.str())) { + LLDB_LOG_ERROR(log, std::move(err), + "Failed to download from SymStore '{1}': {0}", source_url); + return {}; + } + + return FileSpec(cache_file.str()); +} + +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 locateSymStoreEntry(llvm::StringRef base_url, + llvm::StringRef key, + llvm::StringRef pdb_name) { + if (base_url.starts_with("http://") || base_url.starts_with("https://")) + return requestFileFromSymStoreServerHTTP(base_url, key, pdb_name); + + if (base_url.starts_with("file://")) + base_url = base_url.drop_front(7); + + return findFileInLocalSymStore(base_url, key, pdb_name); +} + +} // namespace + std::optional SymbolLocatorSymStore::LocateExecutableSymbolFile( const ModuleSpec &module_spec, const FileSpecList &default_search_paths) { const UUID &uuid = module_spec.GetUUID(); @@ -134,12 +276,9 @@ std::optional SymbolLocatorSymStore::LocateExecutableSymbolFile( std::string key = formatSymStoreKey(uuid); Args sym_store_urls = GetGlobalPluginProperties().GetURLs(); for (const Args::ArgEntry &url : sym_store_urls) { - llvm::SmallString<256> path; - llvm::sys::path::append(path, url.ref(), pdb_name, key, pdb_name); - FileSpec spec(path); - if (FileSystem::Instance().Exists(spec)) { - LLDB_LOGV(log, "Found {0} in SymStore {1}", pdb_name, url.ref()); - return spec; + if (auto spec = locateSymStoreEntry(url.ref(), key, pdb_name)) { + LLDB_LOG_VERBOSE(log, "Found {0} in SymStore {1}", pdb_name, url.ref()); + return *spec; } } diff --git a/lldb/test/API/symstore/TestSymStoreLocal.py b/lldb/test/API/symstore/TestSymStore.py similarity index 68% rename from lldb/test/API/symstore/TestSymStoreLocal.py rename to lldb/test/API/symstore/TestSymStore.py index 98569d2b8c66f..13d0cc1666c84 100644 --- a/lldb/test/API/symstore/TestSymStoreLocal.py +++ b/lldb/test/API/symstore/TestSymStore.py @@ -1,5 +1,9 @@ +import http.server import os import shutil +import socketserver +import threading +from functools import partial import lldb from lldbsuite.test.decorators import * @@ -62,7 +66,31 @@ def __exit__(self, *exc_info): self._test.runCmd("settings clear plugin.symbol-locator.symstore") -class SymStoreLocalTests(TestBase): +class HTTPServer: + """ + Context Manager to serve a local directory tree via HTTP. + """ + + def __init__(self, dir): + address = ("localhost", 0) # auto-select free port + 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 SymStoreTests(TestBase): SHARED_BUILD_TESTCASE = False TEST_WITH_PDB_DEBUG_INFO = True @@ -99,10 +127,8 @@ 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 symstore_dir: - self.runCmd( - f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" - ) + 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): @@ -110,8 +136,18 @@ def test_local_dir(self): Check that breakpoint resolves with local SymStore. """ exe, sym = self.build_inferior() - with MockedSymStore(self, exe, sym) as symstore_dir: - self.runCmd( - f"settings set plugin.symbol-locator.symstore.urls {symstore_dir}" - ) + 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) + + # TODO: Add test coverage for common HTTPS security scenarios, e.g. self-signed + # certs, non-HTTPS redirects, etc. + def test_http(self): + """ + Check that breakpoint hits 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) diff --git a/llvm/include/llvm/Support/HTTP/HTTPClient.h b/llvm/include/llvm/Support/HTTP/HTTPClient.h index aa4727ad33024..17b706ff3e083 100644 --- a/llvm/include/llvm/Support/HTTP/HTTPClient.h +++ b/llvm/include/llvm/Support/HTTP/HTTPClient.h @@ -51,8 +51,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: diff --git a/llvm/lib/Support/HTTP/CMakeLists.txt b/llvm/lib/Support/HTTP/CMakeLists.txt index 9bf1da8c60c88..e7a0e6fe34110 100644 --- a/llvm/lib/Support/HTTP/CMakeLists.txt +++ b/llvm/lib/Support/HTTP/CMakeLists.txt @@ -8,6 +8,11 @@ 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) +endif() + add_llvm_component_library(LLVMSupportHTTP HTTPClient.cpp HTTPServer.cpp diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp index 6301f86da4086..69780b32d1cf0 100644 --- a/llvm/lib/Support/HTTP/HTTPClient.cpp +++ b/llvm/lib/Support/HTTP/HTTPClient.cpp @@ -23,6 +23,9 @@ #ifdef LLVM_ENABLE_CURL #include #endif +#ifdef _WIN32 +#include "llvm/Support/ConvertUTF.h" +#endif using namespace llvm; @@ -64,7 +67,7 @@ void HTTPClient::cleanup() { 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()); + curl_easy_setopt(Handle, CURLOPT_TIMEOUT_MS, Timeout.count()); } /// CurlHTTPRequest and the curl{Header,Write}Function are implementation @@ -93,17 +96,17 @@ static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb, HTTPClient::HTTPClient() { assert(IsInitialized && "Must call HTTPClient::initialize() at the beginning of main()."); - if (Curl) + if (Handle) return; - Curl = curl_easy_init(); - assert(Curl && "Curl could not be initialized"); + Handle = curl_easy_init(); + assert(Handle && "Curl could not be initialized"); // Set the callback hooks. - curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction); + curl_easy_setopt(Handle, CURLOPT_WRITEFUNCTION, curlWriteFunction); // Detect supported compressed encodings and accept all. - curl_easy_setopt(Curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(Handle, CURLOPT_ACCEPT_ENCODING, ""); } -HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); } +HTTPClient::~HTTPClient() { curl_easy_cleanup(Handle); } Error HTTPClient::perform(const HTTPRequest &Request, HTTPResponseHandler &Handler) { @@ -112,17 +115,17 @@ Error HTTPClient::perform(const HTTPRequest &Request, "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_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(Curl, CURLOPT_HTTPHEADER, Headers); + curl_easy_setopt(Handle, CURLOPT_HTTPHEADER, Headers); CurlHTTPRequest CurlRequest(Handler); - curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest); - CURLcode CurlRes = curl_easy_perform(Curl); + 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), @@ -134,12 +137,240 @@ Error HTTPClient::perform(const HTTPRequest &Request, unsigned HTTPClient::responseCode() { long Code = 0; - curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code); + curl_easy_getinfo(Handle, CURLINFO_RESPONSE_CODE, &Code); return Code; } #else +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "winhttp.lib") + +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)); + } +} + +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"); + + // 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"); + + // 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"); + + // Explicitly enforce default validation. This protects against insecure + // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA. + DWORD SecurityFlags = 0; + 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"); + + // 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; @@ -161,4 +392,6 @@ unsigned HTTPClient::responseCode() { llvm_unreachable("No HTTP Client implementation available."); } +#endif // _WIN32 + #endif diff --git a/llvm/test/tools/llvm-debuginfod-find/headers.test b/llvm/test/tools/llvm-debuginfod-find/headers-curl.test similarity index 100% rename from llvm/test/tools/llvm-debuginfod-find/headers.test rename to llvm/test/tools/llvm-debuginfod-find/headers-curl.test 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..96a73c6bf373a --- /dev/null +++ b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test @@ -0,0 +1,31 @@ +REQUIRES: system-windows + +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 From f67025d6b21555b6bb91bd7a002c3d60d00c2b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 20 Mar 2026 15:51:12 +0100 Subject: [PATCH 16/30] [llvm] Restrict llvm-debginfod-find test to localhost to fix winhttp case (#187705) Listening on all interfaces is probably not permitted on the bots and causes failures of llvm-debuginfod-find/headers-winhttp.test after 39d6bb21804d21abe2fa0ec019919d72104827ac. Restricting them to localhost should fix that. --- llvm/test/tools/llvm-debuginfod-find/Inputs/capture_req.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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: From 63e619f12b1f94ba3e4e26ba93942f5e2b3b081d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 20 Mar 2026 18:03:39 +0100 Subject: [PATCH 17/30] [llvm] Run headers-winhttp.test only if the Python side of it works (#187727) a3db68a97b2c321ef0af73f39a3725490c131468 seemed t be the obvious fix for the winhttp issue from 39d6bb21804d21abe2fa0ec019919d72104827ac in llvm-debuginfod-find, but there are still bots failing. This patch disables the test on all bots that cannot spawn an HTTP server in Python and record request headers. Ideally it turns all affected bots back to green and gives us an error message to investigate. --- .../llvm-debuginfod-find/headers-curl.test | 2 +- .../llvm-debuginfod-find/headers-winhttp.test | 5 +- .../llvm-debuginfod-find/lit.local.cfg.py | 46 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 llvm/test/tools/llvm-debuginfod-find/lit.local.cfg.py diff --git a/llvm/test/tools/llvm-debuginfod-find/headers-curl.test b/llvm/test/tools/llvm-debuginfod-find/headers-curl.test index 86517f8fc5a12..9990d8b80a7d0 100644 --- a/llvm/test/tools/llvm-debuginfod-find/headers-curl.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 index 96a73c6bf373a..f5a59518cfccd 100644 --- a/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test +++ b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test @@ -1,4 +1,7 @@ -REQUIRES: system-windows +REQUIRES: system-windows, http-server + +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 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") From 9221455cdc2939f9112fe8dc3f17a1d99fcde4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 20 Mar 2026 19:12:25 +0100 Subject: [PATCH 18/30] [llvm] Silence llvm-debuginfod-find/headers-winhttp.test on Windows bots temporarily (#187753) Windows bots are still failing after a3db68a97b2c321e and d7dbba55bff52f342. This test is new, let's take it off while we investigate. --- llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test index f5a59518cfccd..9b614790694e8 100644 --- a/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test +++ b/llvm/test/tools/llvm-debuginfod-find/headers-winhttp.test @@ -1,4 +1,4 @@ -REQUIRES: system-windows, http-server +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 From 03ecf21ad09d871a3969d0954a811801e8a690da Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Fri, 20 Mar 2026 22:52:56 +0100 Subject: [PATCH 19/30] [lldb] Fix warning style for SymStore symbol locator (#187776) Missed this when reviewing #186986. This fixes the warnings to follow the [LLVM Coding Standards](https://llvm.org/docs/CodingStandards.html#error-and-warning-messages). --- .../SymbolLocator/SymStore/SymbolLocatorSymStore.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index fe5f5af45b18d..ce48d74be8eb1 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -192,7 +192,7 @@ requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, // Make sure URL will be valid, portable, and compatible with symbol servers. if (has_unsafe_characters(pdb_name)) { Debugger::ReportWarning(llvm::formatv( - "Rejecting HTTP lookup for PDB file due to unsafe characters in " + "rejecting HTTP lookup for PDB file due to unsafe characters in " "name: {0}", pdb_name)); return {}; @@ -201,13 +201,13 @@ requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, // Construct the path for local storage. Configurable cache coming soon. llvm::SmallString<128> cache_file; if (!path::cache_directory(cache_file)) { - Debugger::ReportWarning("Failed to determine cache directory for SymStore"); + Debugger::ReportWarning("failed to determine cache directory for SymStore"); return {}; } path::append(cache_file, "lldb", "SymStore", pdb_name, key); if (std::error_code ec = fs::create_directories(cache_file)) { Debugger::ReportWarning( - llvm::formatv("Failed to create cache directory '{0}': {1}", cache_file, + llvm::formatv("failed to create cache directory '{0}': {1}", cache_file, ec.message())); return {}; } From eedd6c7bb50621042970aa8a1c6f003d792184f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 27 Mar 2026 09:39:39 +0100 Subject: [PATCH 20/30] [lldb] Let SymStore locator download PDBs to a temp file (#188798) The cache path we get from the `llvm::sys::path::cache_directory()` library function is not writeable on some Windows bots as they point us to the Default user's AppData: C:\Users\Default\AppData\Local\lldb. This patch avoids the problem and downloads into the temporary directory that we query from `llvm::sys::path::system_temp_directory()`. Once we introduce real caching, this would have changed anyway. --- .../SymStore/SymbolLocatorSymStore.cpp | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index ce48d74be8eb1..d556c277a4253 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -198,31 +198,24 @@ requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, return {}; } - // Construct the path for local storage. Configurable cache coming soon. - llvm::SmallString<128> cache_file; - if (!path::cache_directory(cache_file)) { - Debugger::ReportWarning("failed to determine cache directory for SymStore"); - return {}; - } - path::append(cache_file, "lldb", "SymStore", pdb_name, key); - if (std::error_code ec = fs::create_directories(cache_file)) { - Debugger::ReportWarning( - llvm::formatv("failed to create cache directory '{0}': {1}", cache_file, - ec.message())); - return {}; - } - path::append(cache_file, pdb_name); - - // Server has same directory structure with forward slashes as separators. + // Download into a temporary file. Cache coming soon. + llvm::SmallString<128> tmp_file; + std::string tmp_file_name = + llvm::formatv("lldb_symstore_{0}_{1}", key, pdb_name); + constexpr bool erase_on_reboot = true; + path::system_temp_directory(erase_on_reboot, tmp_file); + path::append(tmp_file, tmp_file_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::Error err = downloadFileHTTP(source_url, cache_file.str())) { + if (llvm::Error err = downloadFileHTTP(source_url, tmp_file.str())) { LLDB_LOG_ERROR(log, std::move(err), "Failed to download from SymStore '{1}': {0}", source_url); return {}; } - return FileSpec(cache_file.str()); + return FileSpec(tmp_file.str()); } std::optional findFileInLocalSymStore(llvm::StringRef root_dir, From c3af12cc6d7b6ff2262f6cd6a0f16440fd847698 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Fri, 27 Mar 2026 13:15:51 +0100 Subject: [PATCH 21/30] [llvm][SupportHTTP] Guard SSL settings by Secure flag to avoid failing on plain HTTP (#188970) This patch only adds the condition, so the flags are applied only for HTTPS URLs. No change in implementation. --- llvm/lib/Support/HTTP/HTTPClient.cpp | 32 +++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp index 69780b32d1cf0..1a3d4b8be9e0e 100644 --- a/llvm/lib/Support/HTTP/HTTPClient.cpp +++ b/llvm/lib/Support/HTTP/HTTPClient.cpp @@ -297,21 +297,23 @@ Error HTTPClient::perform(const HTTPRequest &Request, if (!Session->RequestHandle) return createStringError(errc::io_error, "Failed to open HTTP request"); - // 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"); - - // Explicitly enforce default validation. This protects against insecure - // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA. - DWORD SecurityFlags = 0; - if (!WinHttpSetOption(Session->RequestHandle, WINHTTP_OPTION_SECURITY_FLAGS, - &SecurityFlags, sizeof(SecurityFlags))) - return createStringError(errc::io_error, - "Failed to enforce security flags"); + 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"); + + // Explicitly enforce default validation. This protects against insecure + // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA. + DWORD SecurityFlags = 0; + 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) { From 2e1f865526424e6379731836f5ca98ea4310c410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Thu, 2 Apr 2026 15:37:08 +0200 Subject: [PATCH 22/30] [llvm] Fix SupportHTTP linkage with libLLVM in unit-tests (#190097) Since libSupportHTTP is part of the LLVM dylib, we must link it as a component now. Fixes https://github.com/llvm/llvm-project/issues/189978 --- llvm/unittests/Debuginfod/CMakeLists.txt | 5 ++++- llvm/unittests/Support/HTTP/CMakeLists.txt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/llvm/unittests/Debuginfod/CMakeLists.txt b/llvm/unittests/Debuginfod/CMakeLists.txt index 4ac75c0720e89..c8b8e24fc34e0 100644 --- a/llvm/unittests/Debuginfod/CMakeLists.txt +++ b/llvm/unittests/Debuginfod/CMakeLists.txt @@ -1,9 +1,12 @@ +set(LLVM_LINK_COMPONENTS + SupportHTTP + ) + add_llvm_unittest(DebuginfodTests DebuginfodTests.cpp ) target_link_libraries(DebuginfodTests PRIVATE LLVMDebuginfod - LLVMSupportHTTP LLVMTestingSupport ) diff --git a/llvm/unittests/Support/HTTP/CMakeLists.txt b/llvm/unittests/Support/HTTP/CMakeLists.txt index 59e2393bfae06..a6a1e72dcf7dc 100644 --- a/llvm/unittests/Support/HTTP/CMakeLists.txt +++ b/llvm/unittests/Support/HTTP/CMakeLists.txt @@ -1,8 +1,11 @@ +set(LLVM_LINK_COMPONENTS + SupportHTTP + ) + add_llvm_unittest(SupportHTTPTests HTTPServerTests.cpp ) target_link_libraries(SupportHTTPTests PRIVATE - LLVMSupportHTTP LLVMTestingSupport ) From d9f886a89636b7565d055030b9d95b4b9c841416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 8 Apr 2026 08:36:32 -0400 Subject: [PATCH 23/30] [lldb] Bring Debuginfod's StreamedHTTPResponseHandler to SymbolLocatorSymStore (#187687) SymbolLocatorSymStore used a simple local implementation of HTTPResponseHandler so far. That was fine for basic usage, but it would cause issues down the line. This patch hoists the StreamedHTTPResponseHandler class from libDebuginfod to SupportHTTP and integrates it in SymbolLocatorSymStore. PDB file downloads will now be buffered on disk, which is necessary since they can be huge. We use the opportunity an stop logging 404 responses (file not found on server) and print warnings for all other erroneous HTTP responses. It was more complicated before, because the old response handler created the underlying file in any case. The new one does that only once the first content package comes in. --- .../SymStore/SymbolLocatorSymStore.cpp | 133 +++++++++--------- lldb/test/API/symstore/TestSymStore.py | 18 +++ .../HTTP/StreamedHTTPResponseHandler.h | 49 +++++++ llvm/lib/Debuginfod/Debuginfod.cpp | 48 +------ llvm/lib/Support/HTTP/CMakeLists.txt | 1 + .../HTTP/StreamedHTTPResponseHandler.cpp | 34 +++++ 6 files changed, 166 insertions(+), 117 deletions(-) create mode 100644 llvm/include/llvm/Support/HTTP/StreamedHTTPResponseHandler.h create mode 100644 llvm/lib/Support/HTTP/StreamedHTTPResponseHandler.cpp diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index d556c277a4253..36114660e6192 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -18,11 +18,14 @@ #include "lldb/Utility/UUID.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Caching.h" #include "llvm/Support/Endian.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/HTTP/HTTPClient.h" +#include "llvm/Support/HTTP/StreamedHTTPResponseHandler.h" #include "llvm/Support/Path.h" +#include "llvm/Support/raw_ostream.h" using namespace lldb; using namespace lldb_private; @@ -107,64 +110,14 @@ namespace { // SymStore key is a string with no separators and age as decimal: // 12345678123456789ABCDEF0123456781 // -std::string formatSymStoreKey(const UUID &uuid) { +std::string FormatSymStoreKey(const UUID &uuid) { llvm::ArrayRef bytes = uuid.GetBytes(); uint32_t age = llvm::support::endian::read32be(bytes.data() + 16); - constexpr bool LowerCase = false; - return llvm::toHex(bytes.slice(0, 16), LowerCase) + std::to_string(age); + constexpr bool lower_case = false; + return llvm::toHex(bytes.slice(0, 16), lower_case) + std::to_string(age); } -// This is a simple version of Debuginfod's StreamedHTTPResponseHandler. We -// should consider reusing that once we introduce caching. -class FileDownloadHandler : public llvm::HTTPResponseHandler { -private: - std::error_code m_ec; - llvm::raw_fd_ostream m_stream; - -public: - FileDownloadHandler(llvm::StringRef file) : m_stream(file.str(), m_ec) {} - virtual ~FileDownloadHandler() = default; - - llvm::Error handleBodyChunk(llvm::StringRef data) override { - // Propagate error from ctor. - if (m_ec) - return llvm::createStringError(m_ec, "Failed to open file for writing"); - m_stream.write(data.data(), data.size()); - if (std::error_code ec = m_stream.error()) - return llvm::createStringError(ec, "Error writing to file"); - - return llvm::Error::success(); - } -}; - -llvm::Error downloadFileHTTP(llvm::StringRef url, FileDownloadHandler dest) { - if (!llvm::HTTPClient::isAvailable()) - return llvm::createStringError( - std::make_error_code(std::errc::not_supported), - "HTTP client is not available"); - llvm::HTTPRequest Request(url); - Request.FollowRedirects = true; - - llvm::HTTPClient Client; - - // TODO: Since PDBs can be huge, we should distinguish between resolve, - // connect, send and receive. - Client.setTimeout(std::chrono::seconds(60)); - - if (llvm::Error Err = Client.perform(Request, dest)) - return Err; - - unsigned ResponseCode = Client.responseCode(); - if (ResponseCode != 200) { - return llvm::createStringError(std::make_error_code(std::errc::io_error), - "HTTP request failed with status code " + - std::to_string(ResponseCode)); - } - - return llvm::Error::success(); -} - -bool has_unsafe_characters(llvm::StringRef s) { +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') || @@ -180,17 +133,15 @@ bool has_unsafe_characters(llvm::StringRef s) { return s == "." || s == ".."; } -// TODO: This is a dump initial implementation: It always downloads the file, it -// doesn't validate the result, it doesn't employ proper buffering for large -// files. +// TODO: This is a dumb initial implementation: It always downloads the file and +// doesn't validate the result. std::optional -requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, +RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, llvm::StringRef pdb_name) { using namespace llvm::sys; - Log *log = GetLog(LLDBLog::Symbols); // Make sure URL will be valid, portable, and compatible with symbol servers. - if (has_unsafe_characters(pdb_name)) { + if (HasUnsafeCharacters(pdb_name)) { Debugger::ReportWarning(llvm::formatv( "rejecting HTTP lookup for PDB file due to unsafe characters in " "name: {0}", @@ -209,16 +160,58 @@ requestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, // 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::Error err = downloadFileHTTP(source_url, tmp_file.str())) { - LLDB_LOG_ERROR(log, std::move(err), - "Failed to download from SymStore '{1}': {0}", source_url); + + if (!llvm::HTTPClient::isAvailable()) { + Debugger::ReportWarning( + "HTTP client is not available for SymStore download"); return {}; } - return FileSpec(tmp_file.str()); + 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); + 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, +std::optional FindFileInLocalSymStore(llvm::StringRef root_dir, llvm::StringRef key, llvm::StringRef pdb_name) { llvm::SmallString<256> path; @@ -230,16 +223,16 @@ std::optional findFileInLocalSymStore(llvm::StringRef root_dir, return spec; } -std::optional locateSymStoreEntry(llvm::StringRef base_url, +std::optional LocateSymStoreEntry(llvm::StringRef base_url, llvm::StringRef key, llvm::StringRef pdb_name) { if (base_url.starts_with("http://") || base_url.starts_with("https://")) - return requestFileFromSymStoreServerHTTP(base_url, key, pdb_name); + return RequestFileFromSymStoreServerHTTP(base_url, key, pdb_name); if (base_url.starts_with("file://")) base_url = base_url.drop_front(7); - return findFileInLocalSymStore(base_url, key, pdb_name); + return FindFileInLocalSymStore(base_url, key, pdb_name); } } // namespace @@ -266,10 +259,10 @@ std::optional SymbolLocatorSymStore::LocateExecutableSymbolFile( return {}; } - std::string key = formatSymStoreKey(uuid); + std::string key = FormatSymStoreKey(uuid); Args sym_store_urls = GetGlobalPluginProperties().GetURLs(); for (const Args::ArgEntry &url : sym_store_urls) { - if (auto spec = locateSymStoreEntry(url.ref(), key, pdb_name)) { + if (auto spec = LocateSymStoreEntry(url.ref(), key, pdb_name)) { LLDB_LOG_VERBOSE(log, "Found {0} in SymStore {1}", pdb_name, url.ref()); return *spec; } diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py index 13d0cc1666c84..5baa5fa4d0da8 100644 --- a/lldb/test/API/symstore/TestSymStore.py +++ b/lldb/test/API/symstore/TestSymStore.py @@ -140,6 +140,24 @@ def test_local_dir(self): 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, "") + # TODO: Add test coverage for common HTTPS security scenarios, e.g. self-signed # certs, non-HTTPS redirects, etc. def test_http(self): diff --git a/llvm/include/llvm/Support/HTTP/StreamedHTTPResponseHandler.h b/llvm/include/llvm/Support/HTTP/StreamedHTTPResponseHandler.h new file mode 100644 index 0000000000000..14ffab8aa1be3 --- /dev/null +++ b/llvm/include/llvm/Support/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_SUPPORT_HTTP_STREAMEDHTTPRESPONSEHANDLER_H +#define LLVM_SUPPORT_HTTP_STREAMEDHTTPRESPONSEHANDLER_H + +#include "llvm/Support/Caching.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/HTTP/HTTPClient.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_SUPPORT_HTTP_STREAMEDHTTPRESPONSEHANDLER_H diff --git a/llvm/lib/Debuginfod/Debuginfod.cpp b/llvm/lib/Debuginfod/Debuginfod.cpp index 6847dc092dfdb..77de51d90a4eb 100644 --- a/llvm/lib/Debuginfod/Debuginfod.cpp +++ b/llvm/lib/Debuginfod/Debuginfod.cpp @@ -35,6 +35,7 @@ #include "llvm/Support/Error.h" #include "llvm/Support/FileUtilities.h" #include "llvm/Support/HTTP/HTTPClient.h" +#include "llvm/Support/HTTP/StreamedHTTPResponseHandler.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/ThreadPool.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/Support/HTTP/CMakeLists.txt b/llvm/lib/Support/HTTP/CMakeLists.txt index e7a0e6fe34110..f8e4b87539931 100644 --- a/llvm/lib/Support/HTTP/CMakeLists.txt +++ b/llvm/lib/Support/HTTP/CMakeLists.txt @@ -16,6 +16,7 @@ endif() add_llvm_component_library(LLVMSupportHTTP HTTPClient.cpp HTTPServer.cpp + StreamedHTTPResponseHandler.cpp LINK_LIBS ${imported_libs} diff --git a/llvm/lib/Support/HTTP/StreamedHTTPResponseHandler.cpp b/llvm/lib/Support/HTTP/StreamedHTTPResponseHandler.cpp new file mode 100644 index 0000000000000..5a0e0e6355684 --- /dev/null +++ b/llvm/lib/Support/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/Support/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 From 963710c2c9b6b9a71c54d350dbb2be983573941c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Tue, 14 Apr 2026 13:54:19 +0200 Subject: [PATCH 24/30] [lldb] Fix: Disable shared build dir when testing with PDB (#190991) The mechanism to disable `SHARED_BUILD_TESTCASE` for tests that set `TEST_WITH_PDB_DEBUG_INFO` doesn't work. The property was set on the wrong object. This patch fixes it and moves the assignment after the for-loop, since the respective dict only exists there. --- lldb/packages/Python/lldbsuite/test/lldbtest.py | 4 ++++ lldb/test/API/symstore/TestSymStore.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) 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/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py index 5baa5fa4d0da8..b5c8fbb58324f 100644 --- a/lldb/test/API/symstore/TestSymStore.py +++ b/lldb/test/API/symstore/TestSymStore.py @@ -91,7 +91,6 @@ def __exit__(self, *exc_info): class SymStoreTests(TestBase): - SHARED_BUILD_TESTCASE = False TEST_WITH_PDB_DEBUG_INFO = True def build_inferior(self): From f1b285c23674f692bffc9573a87660d047f83120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Thu, 26 Mar 2026 14:48:27 +0200 Subject: [PATCH 25/30] [SupportHTTP] Remove a redundant pragma (#188533) We explicitly link against the winhttp library by declaring it in CMake, in llvm/lib/Support/HTTP/CMakeLists.txt. We therefore don't need to specify linking against it through a pragma (which embeds a directive in the object file). This fixes warnings when building in mingw mode, where this kind of pragma isn't supported: llvm-project/llvm/lib/Support/HTTP/HTTPClient.cpp:149:9: warning: unknown pragma ignored [-Wunknown-pragmas] 149 | #pragma comment(lib, "winhttp.lib") | ^ --- llvm/lib/Support/HTTP/HTTPClient.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/llvm/lib/Support/HTTP/HTTPClient.cpp b/llvm/lib/Support/HTTP/HTTPClient.cpp index 1a3d4b8be9e0e..e5ff6e6bcacd3 100644 --- a/llvm/lib/Support/HTTP/HTTPClient.cpp +++ b/llvm/lib/Support/HTTP/HTTPClient.cpp @@ -146,7 +146,6 @@ unsigned HTTPClient::responseCode() { #ifdef _WIN32 #include #include -#pragma comment(lib, "winhttp.lib") namespace { From 21d286935a43d73c3ee82ae159f2fb2f2b920259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 15 Apr 2026 10:38:53 +0200 Subject: [PATCH 26/30] [llvm] Move libSupportHTTP to top-level libHTTP (NFC) (#191202) The HTTP implementation depends on CURL and would preferably not be part of the LLVM dylib. This was not possible as a nested library under libSupport, because libSupport itself is part of the LLVM dylib. This patch moves the HTTP code into a separate top-level library that is independent from libSupport and excluded from the LLVM dylib. --- .../SymbolLocator/Debuginfod/CMakeLists.txt | 4 +- .../Debuginfod/SymbolLocatorDebuginfod.cpp | 2 +- .../SymbolLocator/SymStore/CMakeLists.txt | 2 +- .../SymStore/SymbolLocatorSymStore.cpp | 4 +- llvm/include/llvm/Debuginfod/Debuginfod.h | 2 +- .../llvm/{Support => }/HTTP/HTTPClient.h | 6 +-- .../llvm/{Support => }/HTTP/HTTPServer.h | 6 +-- .../HTTP/StreamedHTTPResponseHandler.h | 8 +-- llvm/lib/CMakeLists.txt | 1 + llvm/lib/Debuginfod/CMakeLists.txt | 2 +- llvm/lib/Debuginfod/Debuginfod.cpp | 4 +- llvm/lib/{Support => }/HTTP/CMakeLists.txt | 53 ++++++++++--------- llvm/lib/{Support => }/HTTP/HTTPClient.cpp | 2 +- llvm/lib/{Support => }/HTTP/HTTPServer.cpp | 2 +- .../HTTP/StreamedHTTPResponseHandler.cpp | 2 +- llvm/lib/Support/CMakeLists.txt | 1 - llvm/tools/llvm-cov/CMakeLists.txt | 3 +- llvm/tools/llvm-cov/CodeCoverage.cpp | 2 +- .../tools/llvm-debuginfod-find/CMakeLists.txt | 4 +- .../llvm-debuginfod-find.cpp | 2 +- llvm/tools/llvm-debuginfod/CMakeLists.txt | 3 +- .../tools/llvm-debuginfod/llvm-debuginfod.cpp | 2 +- llvm/tools/llvm-objdump/CMakeLists.txt | 3 +- llvm/tools/llvm-objdump/llvm-objdump.cpp | 2 +- llvm/tools/llvm-profdata/CMakeLists.txt | 1 - llvm/tools/llvm-profdata/llvm-profdata.cpp | 2 +- llvm/tools/llvm-symbolizer/CMakeLists.txt | 3 +- .../tools/llvm-symbolizer/llvm-symbolizer.cpp | 2 +- llvm/unittests/CMakeLists.txt | 1 + llvm/unittests/Debuginfod/CMakeLists.txt | 4 -- llvm/unittests/Debuginfod/DebuginfodTests.cpp | 2 +- llvm/unittests/HTTP/CMakeLists.txt | 8 +++ .../{Support => }/HTTP/HTTPServerTests.cpp | 4 +- llvm/unittests/Support/HTTP/CMakeLists.txt | 11 ---- 34 files changed, 74 insertions(+), 86 deletions(-) rename llvm/include/llvm/{Support => }/HTTP/HTTPClient.h (95%) rename llvm/include/llvm/{Support => }/HTTP/HTTPServer.h (97%) rename llvm/include/llvm/{Support => }/HTTP/StreamedHTTPResponseHandler.h (87%) rename llvm/lib/{Support => }/HTTP/CMakeLists.txt (80%) rename llvm/lib/{Support => }/HTTP/HTTPClient.cpp (99%) rename llvm/lib/{Support => }/HTTP/HTTPServer.cpp (99%) rename llvm/lib/{Support => }/HTTP/StreamedHTTPResponseHandler.cpp (94%) create mode 100644 llvm/unittests/HTTP/CMakeLists.txt rename llvm/unittests/{Support => }/HTTP/HTTPServerTests.cpp (99%) delete mode 100644 llvm/unittests/Support/HTTP/CMakeLists.txt diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt index 25d3ff4b683b0..29ab65bcac142 100644 --- a/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/Debuginfod/CMakeLists.txt @@ -14,9 +14,7 @@ add_lldb_library(lldbPluginSymbolLocatorDebuginfod PLUGIN lldbHost lldbSymbol LLVMDebuginfod - - LINK_COMPONENTS - SupportHTTP + LLVMHTTP ) add_dependencies(lldbPluginSymbolLocatorDebuginfod diff --git a/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp b/lldb/source/Plugins/SymbolLocator/Debuginfod/SymbolLocatorDebuginfod.cpp index 03d51ed8a3eea..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/Support/HTTP/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 index 775e0284cd8af..66a3d8640816f 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt +++ b/lldb/source/Plugins/SymbolLocator/SymStore/CMakeLists.txt @@ -13,7 +13,7 @@ add_lldb_library(lldbPluginSymbolLocatorSymStore PLUGIN lldbCore lldbHost lldbSymbol - LLVMSupportHTTP + LLVMHTTP ) add_dependencies(lldbPluginSymbolLocatorSymStore diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index 36114660e6192..7a668f9220e32 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -18,12 +18,12 @@ #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/HTTP/HTTPClient.h" -#include "llvm/Support/HTTP/StreamedHTTPResponseHandler.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" diff --git a/llvm/include/llvm/Debuginfod/Debuginfod.h b/llvm/include/llvm/Debuginfod/Debuginfod.h index 0bdb5d3ce3882..005e5c7d3a83f 100644 --- a/llvm/include/llvm/Debuginfod/Debuginfod.h +++ b/llvm/include/llvm/Debuginfod/Debuginfod.h @@ -22,9 +22,9 @@ #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/HTTP/HTTPServer.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Mutex.h" #include "llvm/Support/RWMutex.h" diff --git a/llvm/include/llvm/Support/HTTP/HTTPClient.h b/llvm/include/llvm/HTTP/HTTPClient.h similarity index 95% rename from llvm/include/llvm/Support/HTTP/HTTPClient.h rename to llvm/include/llvm/HTTP/HTTPClient.h index 17b706ff3e083..a2ba4251d35ce 100644 --- a/llvm/include/llvm/Support/HTTP/HTTPClient.h +++ b/llvm/include/llvm/HTTP/HTTPClient.h @@ -12,8 +12,8 @@ /// //===----------------------------------------------------------------------===// -#ifndef LLVM_SUPPORT_HTTP_HTTPCLIENT_H -#define LLVM_SUPPORT_HTTP_HTTPCLIENT_H +#ifndef LLVM_HTTP_HTTPCLIENT_H +#define LLVM_HTTP_HTTPCLIENT_H #include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallVector.h" @@ -85,4 +85,4 @@ class HTTPClient { } // end namespace llvm -#endif // LLVM_SUPPORT_HTTP_HTTPCLIENT_H +#endif // LLVM_HTTP_HTTPCLIENT_H diff --git a/llvm/include/llvm/Support/HTTP/HTTPServer.h b/llvm/include/llvm/HTTP/HTTPServer.h similarity index 97% rename from llvm/include/llvm/Support/HTTP/HTTPServer.h rename to llvm/include/llvm/HTTP/HTTPServer.h index 101ca0e6fbd6b..1d218002789c3 100644 --- a/llvm/include/llvm/Support/HTTP/HTTPServer.h +++ b/llvm/include/llvm/HTTP/HTTPServer.h @@ -13,8 +13,8 @@ /// //===----------------------------------------------------------------------===// -#ifndef LLVM_SUPPORT_HTTP_HTTPSERVER_H -#define LLVM_SUPPORT_HTTP_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_SUPPORT_HTTP_HTTPSERVER_H +#endif // LLVM_HTTP_HTTPSERVER_H diff --git a/llvm/include/llvm/Support/HTTP/StreamedHTTPResponseHandler.h b/llvm/include/llvm/HTTP/StreamedHTTPResponseHandler.h similarity index 87% rename from llvm/include/llvm/Support/HTTP/StreamedHTTPResponseHandler.h rename to llvm/include/llvm/HTTP/StreamedHTTPResponseHandler.h index 14ffab8aa1be3..ce9bf28655186 100644 --- a/llvm/include/llvm/Support/HTTP/StreamedHTTPResponseHandler.h +++ b/llvm/include/llvm/HTTP/StreamedHTTPResponseHandler.h @@ -11,12 +11,12 @@ /// //===----------------------------------------------------------------------===// -#ifndef LLVM_SUPPORT_HTTP_STREAMEDHTTPRESPONSEHANDLER_H -#define LLVM_SUPPORT_HTTP_STREAMEDHTTPRESPONSEHANDLER_H +#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 "llvm/Support/HTTP/HTTPClient.h" #include #include @@ -46,4 +46,4 @@ class StreamedHTTPResponseHandler : public HTTPResponseHandler { } // end namespace llvm -#endif // LLVM_SUPPORT_HTTP_STREAMEDHTTPRESPONSEHANDLER_H +#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 c31481cc97f08..5b3403b961a4c 100644 --- a/llvm/lib/Debuginfod/CMakeLists.txt +++ b/llvm/lib/Debuginfod/CMakeLists.txt @@ -14,10 +14,10 @@ add_llvm_library(LLVMDebuginfod LINK_LIBS ${imported_libs} + LLVMHTTP LINK_COMPONENTS Support - SupportHTTP Symbolize DebugInfoDWARF BinaryFormat diff --git a/llvm/lib/Debuginfod/Debuginfod.cpp b/llvm/lib/Debuginfod/Debuginfod.cpp index 77de51d90a4eb..84fe7ac06336a 100644 --- a/llvm/lib/Debuginfod/Debuginfod.cpp +++ b/llvm/lib/Debuginfod/Debuginfod.cpp @@ -27,6 +27,8 @@ #include "llvm/BinaryFormat/Magic.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/Symbolize/Symbolize.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" @@ -34,8 +36,6 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileUtilities.h" -#include "llvm/Support/HTTP/HTTPClient.h" -#include "llvm/Support/HTTP/StreamedHTTPResponseHandler.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/ThreadPool.h" diff --git a/llvm/lib/Support/HTTP/CMakeLists.txt b/llvm/lib/HTTP/CMakeLists.txt similarity index 80% rename from llvm/lib/Support/HTTP/CMakeLists.txt rename to llvm/lib/HTTP/CMakeLists.txt index f8e4b87539931..e3b71434333a1 100644 --- a/llvm/lib/Support/HTTP/CMakeLists.txt +++ b/llvm/lib/HTTP/CMakeLists.txt @@ -1,26 +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) -endif() - -add_llvm_component_library(LLVMSupportHTTP - HTTPClient.cpp - HTTPServer.cpp - StreamedHTTPResponseHandler.cpp - - LINK_LIBS - ${imported_libs} - - LINK_COMPONENTS - Support -) +# 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) +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/Support/HTTP/HTTPClient.cpp b/llvm/lib/HTTP/HTTPClient.cpp similarity index 99% rename from llvm/lib/Support/HTTP/HTTPClient.cpp rename to llvm/lib/HTTP/HTTPClient.cpp index e5ff6e6bcacd3..8517c8cc7c7a0 100644 --- a/llvm/lib/Support/HTTP/HTTPClient.cpp +++ b/llvm/lib/HTTP/HTTPClient.cpp @@ -12,7 +12,7 @@ /// //===----------------------------------------------------------------------===// -#include "llvm/Support/HTTP/HTTPClient.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/ADT/APInt.h" #include "llvm/ADT/StringRef.h" diff --git a/llvm/lib/Support/HTTP/HTTPServer.cpp b/llvm/lib/HTTP/HTTPServer.cpp similarity index 99% rename from llvm/lib/Support/HTTP/HTTPServer.cpp rename to llvm/lib/HTTP/HTTPServer.cpp index c6fd49f7ee623..219b2d0ab9ae1 100644 --- a/llvm/lib/Support/HTTP/HTTPServer.cpp +++ b/llvm/lib/HTTP/HTTPServer.cpp @@ -13,7 +13,7 @@ /// //===----------------------------------------------------------------------===// -#include "llvm/Support/HTTP/HTTPServer.h" +#include "llvm/HTTP/HTTPServer.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" diff --git a/llvm/lib/Support/HTTP/StreamedHTTPResponseHandler.cpp b/llvm/lib/HTTP/StreamedHTTPResponseHandler.cpp similarity index 94% rename from llvm/lib/Support/HTTP/StreamedHTTPResponseHandler.cpp rename to llvm/lib/HTTP/StreamedHTTPResponseHandler.cpp index 5a0e0e6355684..2ebe418cde089 100644 --- a/llvm/lib/Support/HTTP/StreamedHTTPResponseHandler.cpp +++ b/llvm/lib/HTTP/StreamedHTTPResponseHandler.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -#include "llvm/Support/HTTP/StreamedHTTPResponseHandler.h" +#include "llvm/HTTP/StreamedHTTPResponseHandler.h" namespace llvm { diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 9f708541eaa27..1b4c9e4fea5a9 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -135,7 +135,6 @@ if (UNIX AND ${CMAKE_SYSTEM_NAME} MATCHES "AIX") endif() add_subdirectory(BLAKE3) -add_subdirectory(HTTP) add_llvm_component_library(LLVMSupport ABIBreak.cpp diff --git a/llvm/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt index f902469689ccd..6602a2319cb57 100644 --- a/llvm/tools/llvm-cov/CMakeLists.txt +++ b/llvm/tools/llvm-cov/CMakeLists.txt @@ -1,7 +1,6 @@ set(LLVM_LINK_COMPONENTS Core Support - SupportHTTP Object Coverage ProfileData @@ -23,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 375ef74818f5c..c8cf0885fc07d 100644 --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -24,13 +24,13 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Object/BuildID.h" #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/InstrProfReader.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/Process.h" diff --git a/llvm/tools/llvm-debuginfod-find/CMakeLists.txt b/llvm/tools/llvm-debuginfod-find/CMakeLists.txt index e23357008fe77..fcc706d6df452 100644 --- a/llvm/tools/llvm-debuginfod-find/CMakeLists.txt +++ b/llvm/tools/llvm-debuginfod-find/CMakeLists.txt @@ -2,7 +2,6 @@ set(LLVM_LINK_COMPONENTS Option Object Support - SupportHTTP ) set(LLVM_TARGET_DEFINITIONS Opts.td) tablegen(LLVM Opts.inc -gen-opt-parser-defs) @@ -14,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 8f1b9d0f4659d..b6a5efd3e162c 100644 --- a/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp +++ b/llvm/tools/llvm-debuginfod-find/llvm-debuginfod-find.cpp @@ -19,10 +19,10 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/LLVMDriver.h" diff --git a/llvm/tools/llvm-debuginfod/CMakeLists.txt b/llvm/tools/llvm-debuginfod/CMakeLists.txt index 1bca529dcd25e..194784b7a7eee 100644 --- a/llvm/tools/llvm-debuginfod/CMakeLists.txt +++ b/llvm/tools/llvm-debuginfod/CMakeLists.txt @@ -1,7 +1,6 @@ set(LLVM_LINK_COMPONENTS Option Support - SupportHTTP ) set(LLVM_TARGET_DEFINITIONS Opts.td) tablegen(LLVM Opts.inc -gen-opt-parser-defs) @@ -16,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 16d95532a9edc..29b320ca5c5b9 100644 --- a/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp +++ b/llvm/tools/llvm-debuginfod/llvm-debuginfod.cpp @@ -18,10 +18,10 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/CommandLine.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/ThreadPool.h" diff --git a/llvm/tools/llvm-objdump/CMakeLists.txt b/llvm/tools/llvm-objdump/CMakeLists.txt index 40e93762a41f9..ff3a61e8d832e 100644 --- a/llvm/tools/llvm-objdump/CMakeLists.txt +++ b/llvm/tools/llvm-objdump/CMakeLists.txt @@ -12,7 +12,6 @@ set(LLVM_LINK_COMPONENTS Object Option Support - SupportHTTP Symbolize TargetParser ) @@ -41,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 26906cb54075e..1a9644e3226c3 100644 --- a/llvm/tools/llvm-objdump/llvm-objdump.cpp +++ b/llvm/tools/llvm-objdump/llvm-objdump.cpp @@ -35,6 +35,7 @@ #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.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" @@ -64,7 +65,6 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/SourceMgr.h" diff --git a/llvm/tools/llvm-profdata/CMakeLists.txt b/llvm/tools/llvm-profdata/CMakeLists.txt index f5f31df5f80ae..165be9a2ea31b 100644 --- a/llvm/tools/llvm-profdata/CMakeLists.txt +++ b/llvm/tools/llvm-profdata/CMakeLists.txt @@ -3,7 +3,6 @@ set(LLVM_LINK_COMPONENTS Object ProfileData Support - SupportHTTP ) add_llvm_tool(llvm-profdata diff --git a/llvm/tools/llvm-profdata/llvm-profdata.cpp b/llvm/tools/llvm-profdata/llvm-profdata.cpp index 8c48d043cc212..49f841dd1e78f 100644 --- a/llvm/tools/llvm-profdata/llvm-profdata.cpp +++ b/llvm/tools/llvm-profdata/llvm-profdata.cpp @@ -13,6 +13,7 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/IR/LLVMContext.h" #include "llvm/Object/Binary.h" #include "llvm/ProfileData/DataAccessProf.h" @@ -33,7 +34,6 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormattedStream.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/MD5.h" #include "llvm/Support/MemoryBuffer.h" diff --git a/llvm/tools/llvm-symbolizer/CMakeLists.txt b/llvm/tools/llvm-symbolizer/CMakeLists.txt index 39937e08b7884..15ef8337f63c4 100644 --- a/llvm/tools/llvm-symbolizer/CMakeLists.txt +++ b/llvm/tools/llvm-symbolizer/CMakeLists.txt @@ -12,7 +12,6 @@ set(LLVM_LINK_COMPONENTS Object Option Support - SupportHTTP Symbolize ) @@ -25,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 7f7f17ef94750..7daea8d18e363 100644 --- a/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/llvm/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -25,6 +25,7 @@ #include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/Debuginfod/BuildIDFetcher.h" #include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" @@ -33,7 +34,6 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/LLVMDriver.h" #include "llvm/Support/Path.h" #include "llvm/Support/StringSaver.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 c8b8e24fc34e0..cb800872e10e8 100644 --- a/llvm/unittests/Debuginfod/CMakeLists.txt +++ b/llvm/unittests/Debuginfod/CMakeLists.txt @@ -1,7 +1,3 @@ -set(LLVM_LINK_COMPONENTS - SupportHTTP - ) - add_llvm_unittest(DebuginfodTests DebuginfodTests.cpp ) diff --git a/llvm/unittests/Debuginfod/DebuginfodTests.cpp b/llvm/unittests/Debuginfod/DebuginfodTests.cpp index 2e39484e7559f..00d3017638f38 100644 --- a/llvm/unittests/Debuginfod/DebuginfodTests.cpp +++ b/llvm/unittests/Debuginfod/DebuginfodTests.cpp @@ -7,8 +7,8 @@ //===----------------------------------------------------------------------===// #include "llvm/Debuginfod/Debuginfod.h" +#include "llvm/HTTP/HTTPClient.h" #include "llvm/Support/FileSystem.h" -#include "llvm/Support/HTTP/HTTPClient.h" #include "llvm/Support/Path.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.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/Support/HTTP/HTTPServerTests.cpp b/llvm/unittests/HTTP/HTTPServerTests.cpp similarity index 99% rename from llvm/unittests/Support/HTTP/HTTPServerTests.cpp rename to llvm/unittests/HTTP/HTTPServerTests.cpp index 1066fdd2f7735..cc48434149437 100644 --- a/llvm/unittests/Support/HTTP/HTTPServerTests.cpp +++ b/llvm/unittests/HTTP/HTTPServerTests.cpp @@ -7,9 +7,9 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/StringExtras.h" +#include "llvm/HTTP/HTTPClient.h" +#include "llvm/HTTP/HTTPServer.h" #include "llvm/Support/Error.h" -#include "llvm/Support/HTTP/HTTPClient.h" -#include "llvm/Support/HTTP/HTTPServer.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Testing/Support/Error.h" #include "gmock/gmock.h" diff --git a/llvm/unittests/Support/HTTP/CMakeLists.txt b/llvm/unittests/Support/HTTP/CMakeLists.txt deleted file mode 100644 index a6a1e72dcf7dc..0000000000000 --- a/llvm/unittests/Support/HTTP/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -set(LLVM_LINK_COMPONENTS - SupportHTTP - ) - -add_llvm_unittest(SupportHTTPTests - HTTPServerTests.cpp - ) - -target_link_libraries(SupportHTTPTests PRIVATE - LLVMTestingSupport - ) From b9b65ac4194487d2a729a94afa45542dec3f4172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 15 Apr 2026 12:22:31 +0200 Subject: [PATCH 27/30] [llvm] Fix: DebuginfodTests must link libHTTP after 789f30c73ef4 (#192225) --- llvm/unittests/Debuginfod/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/llvm/unittests/Debuginfod/CMakeLists.txt b/llvm/unittests/Debuginfod/CMakeLists.txt index cb800872e10e8..89d2c53fabac7 100644 --- a/llvm/unittests/Debuginfod/CMakeLists.txt +++ b/llvm/unittests/Debuginfod/CMakeLists.txt @@ -4,5 +4,6 @@ add_llvm_unittest(DebuginfodTests target_link_libraries(DebuginfodTests PRIVATE LLVMDebuginfod + LLVMHTTP LLVMTestingSupport ) From f6ac9277fa12a739c109655daaa32d24675e9c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Mon, 20 Apr 2026 11:23:11 +0200 Subject: [PATCH 28/30] [lldb] Add caching and _NT_SYMBOL_PATH parsing in SymbolLocatorSymStore (#191782) The _NT_SYMBOL_PATH environment variable is the idiomatic way to set a system-wide lookup order of symbol servers and a local cache for SymStore. It holds a semicolon-separated list of entries in the following notations: * srv*[*] sets a source and an optional explicit cache * cache* sets an implicit cache for all subsequent entries * all other entries are bare local directories Since symbol paths are closely intertwined with the caching of symbol files, this patch proposes support in LLDB for both features at once. ParseEnvSymbolPaths() implements the parsing logic, which processes entries of the symbol path string from left to right to create a series of LookupEntry objects that each store a source and a cache location. The source of a LookupEntry can be a local directory or an HTTP server address. The cache is a local directory or empty. This representation unifies the implicit vs. explicit caching options from the SymStore protocol. The lookup remains in LocateSymStoreEntry() which we now invoke for each LookupEntry. Here we distinguish sources between HTTP servers and local directories. The latter doesn't change. We just moved the logging to clearly express the new cases. For HTTP downloads we now check the cache first and add files to it after download. The download itself keeps targeting a temporary file to avoid corrupt cache entries in case of interruptions. After all, the SymStore protocol has no check-sums! We define a default cache path that is used as a fallback, if the symbol path doesn't specify any. It can be overridden through the plugin.symbol-locator.symstore.cache property. This is analog to Debuginfod and very handy since we want to move files out from temp after download. The default cache path is what we use if there are no other options. The symbol path notation in the SymStore protocol carries quite some legacy. The SymStoreTest unittest checks that we can parse all the obscure combinations of possible server and cache entries. --------- Co-authored-by: Nerixyz --- .../SymStore/SymbolLocatorSymStore.cpp | 274 ++++++++++++++++-- .../SymStore/SymbolLocatorSymStore.h | 8 + .../SymbolLocatorSymStoreProperties.td | 8 +- lldb/test/API/symstore/TestSymStore.py | 161 +++++++++- lldb/unittests/Symbol/CMakeLists.txt | 2 + lldb/unittests/Symbol/SymStoreTest.cpp | 113 ++++++++ 6 files changed, 543 insertions(+), 23 deletions(-) create mode 100644 lldb/unittests/Symbol/SymStoreTest.cpp diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index 7a668f9220e32..4cff9210db818 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -58,6 +58,15 @@ class PluginProperties : public Properties { 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(); + } }; } // namespace @@ -75,6 +84,13 @@ void SymbolLocatorSymStore::Initialize() { 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) { @@ -103,6 +119,79 @@ SymbolLocator *SymbolLocatorSymStore::CreateInstance() { 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 @@ -133,8 +222,6 @@ bool HasUnsafeCharacters(llvm::StringRef s) { return s == "." || s == ".."; } -// TODO: This is a dumb initial implementation: It always downloads the file and -// doesn't validate the result. std::optional RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, llvm::StringRef pdb_name) { @@ -149,13 +236,11 @@ RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, return {}; } - // Download into a temporary file. Cache coming soon. + // Download into a temporary file. llvm::SmallString<128> tmp_file; - std::string tmp_file_name = - llvm::formatv("lldb_symstore_{0}_{1}", key, pdb_name); constexpr bool erase_on_reboot = true; path::system_temp_directory(erase_on_reboot, tmp_file); - path::append(tmp_file, tmp_file_name); + 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 = @@ -223,16 +308,109 @@ std::optional FindFileInLocalSymStore(llvm::StringRef root_dir, return spec; } -std::optional LocateSymStoreEntry(llvm::StringRef base_url, +std::optional MoveToLocalSymStore(llvm::StringRef cache, llvm::StringRef key, - llvm::StringRef pdb_name) { - if (base_url.starts_with("http://") || base_url.starts_with("https://")) - return RequestFileFromSymStoreServerHTTP(base_url, key, pdb_name); + 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()); - if (base_url.starts_with("file://")) - base_url = base_url.drop_front(7); + 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; + } - return FindFileInLocalSymStore(base_url, key, pdb_name); + // 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_LOGV(log, "Found {0} in local SymStore {1}", pdb_name, file); + return *spec; + } + + return {}; } } // namespace @@ -260,13 +438,73 @@ std::optional SymbolLocatorSymStore::LocateExecutableSymbolFile( } std::string key = FormatSymStoreKey(uuid); - Args sym_store_urls = GetGlobalPluginProperties().GetURLs(); - for (const Args::ArgEntry &url : sym_store_urls) { - if (auto spec = LocateSymStoreEntry(url.ref(), key, pdb_name)) { - LLDB_LOG_VERBOSE(log, "Found {0} in SymStore {1}", pdb_name, url.ref()); + 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 index 52ec04cae387b..80651c6ec5f5c 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.h @@ -43,6 +43,14 @@ class SymbolLocatorSymStore : public SymbolLocator { 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 diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td index bd5fb6a06f6c7..bf4b8568f7c24 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td @@ -2,6 +2,10 @@ 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 for symbols">; + 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.">; } diff --git a/lldb/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py index b5c8fbb58324f..91c74302b144b 100644 --- a/lldb/test/API/symstore/TestSymStore.py +++ b/lldb/test/API/symstore/TestSymStore.py @@ -2,6 +2,7 @@ import os import shutil import socketserver +import sys import threading from functools import partial @@ -26,6 +27,7 @@ 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): """ @@ -57,6 +59,11 @@ def __enter__(self): 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): @@ -66,14 +73,35 @@ def __exit__(self, *exc_info): 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): + def __init__(self, dir, handler=None): address = ("localhost", 0) # auto-select free port - handler = partial(http.server.SimpleHTTPRequestHandler, directory=dir) + 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) @@ -90,6 +118,17 @@ def __exit__(self, *exc_info): self._thread.join() +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 @@ -103,6 +142,10 @@ def build_inferior(self): 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}") @@ -161,10 +204,122 @@ def test_http_not_found(self): # certs, non-HTTPS redirects, etc. def test_http(self): """ - Check that breakpoint hits with remote SymStore. + 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) 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")); +} From ed30d8e27b676e87c90e82a8d0e6864ece9d461c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Wed, 22 Apr 2026 10:11:10 +0200 Subject: [PATCH 29/30] [lldb] Add HTTPS tests for SymbolLocatorSymStore (#192274) Using self-signed certificates is the only way forward for testing security features on the HTTPS path. As we don't want to allow any arbitrary certificate, we add a new property that pins a fingerprint and any self-signed certificate is only accepted if it matches this fingerprint. --- .../Python/lldbsuite/test/decorators.py | 11 ++ .../SymStore/SymbolLocatorSymStore.cpp | 21 +++ .../SymbolLocatorSymStoreProperties.td | 3 + lldb/test/API/symstore/TestSymStore.py | 164 +++++++++++++++++- llvm/include/llvm/HTTP/HTTPClient.h | 4 + llvm/lib/HTTP/CMakeLists.txt | 2 +- llvm/lib/HTTP/HTTPClient.cpp | 66 ++++++- 7 files changed, 264 insertions(+), 7 deletions(-) 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/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index 4cff9210db818..9efc99c1e9e45 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -67,6 +67,25 @@ class PluginProperties : public Properties { 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 @@ -269,6 +288,8 @@ RequestFileFromSymStoreServerHTTP(llvm::StringRef base_url, llvm::StringRef key, 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, diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td index bf4b8568f7c24..6da63565d500c 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStoreProperties.td @@ -8,4 +8,7 @@ let Definition = "symbollocatorsymstore" in { 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/test/API/symstore/TestSymStore.py b/lldb/test/API/symstore/TestSymStore.py index 91c74302b144b..83a3b71b0adfb 100644 --- a/lldb/test/API/symstore/TestSymStore.py +++ b/lldb/test/API/symstore/TestSymStore.py @@ -1,7 +1,10 @@ +import datetime import http.server +import ipaddress import os import shutil import socketserver +import ssl import sys import threading from functools import partial @@ -118,6 +121,107 @@ def __exit__(self, *exc_info): 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 @@ -200,8 +304,6 @@ def test_http_not_found(self): warnings = err_file.read().decode() self.assertEqual(warnings, "") - # TODO: Add test coverage for common HTTPS security scenarios, e.g. self-signed - # certs, non-HTTPS redirects, etc. def test_http(self): """ Check that breakpoint resolves with remote SymStore. @@ -323,3 +425,61 @@ def test_lookup_order(self): 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/llvm/include/llvm/HTTP/HTTPClient.h b/llvm/include/llvm/HTTP/HTTPClient.h index a2ba4251d35ce..a3db41c1a9034 100644 --- a/llvm/include/llvm/HTTP/HTTPClient.h +++ b/llvm/include/llvm/HTTP/HTTPClient.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); }; diff --git a/llvm/lib/HTTP/CMakeLists.txt b/llvm/lib/HTTP/CMakeLists.txt index e3b71434333a1..9dab6ad200066 100644 --- a/llvm/lib/HTTP/CMakeLists.txt +++ b/llvm/lib/HTTP/CMakeLists.txt @@ -10,7 +10,7 @@ endif() # Use WinHTTP on Windows if (WIN32) - set(imported_libs ${imported_libs} winhttp.lib) + 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 diff --git a/llvm/lib/HTTP/HTTPClient.cpp b/llvm/lib/HTTP/HTTPClient.cpp index 8517c8cc7c7a0..c69aecfa7d619 100644 --- a/llvm/lib/HTTP/HTTPClient.cpp +++ b/llvm/lib/HTTP/HTTPClient.cpp @@ -33,7 +33,8 @@ 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.FollowRedirects == B.FollowRedirects && + A.PinnedCertFingerprint == B.PinnedCertFingerprint; } HTTPResponseHandler::~HTTPResponseHandler() = default; @@ -144,8 +145,13 @@ unsigned HTTPClient::responseCode() { #else #ifdef _WIN32 + +// We cannot sort these headers alphabetically. +// clang-format off #include +#include #include +// clang-format on namespace { @@ -236,6 +242,41 @@ void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) { } } +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) @@ -272,6 +313,14 @@ Error HTTPClient::perform(const HTTPRequest &Request, &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, @@ -296,6 +345,7 @@ Error HTTPClient::perform(const HTTPRequest &Request, 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; @@ -305,9 +355,11 @@ Error HTTPClient::perform(const HTTPRequest &Request, return createStringError( errc::io_error, "Failed to enable certificate revocation checks"); - // Explicitly enforce default validation. This protects against insecure - // overrides like SECURITY_FLAG_IGNORE_UNKNOWN_CA. - DWORD SecurityFlags = 0; + // 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, @@ -333,6 +385,12 @@ Error HTTPClient::perform(const HTTPRequest &Request, 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, From 01cead3a6550b8b4e60ceae6136759f9e422b836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=A4nitz?= Date: Thu, 30 Apr 2026 16:58:56 +0200 Subject: [PATCH 30/30] [lldb] Change verbose logs into regular ones in SymbolLocatorSymStore (#195095) --- .../SymbolLocator/SymStore/SymbolLocatorSymStore.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp index 9efc99c1e9e45..4dda1f763b453 100644 --- a/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp +++ b/lldb/source/Plugins/SymbolLocator/SymStore/SymbolLocatorSymStore.cpp @@ -427,7 +427,7 @@ LocateSymStoreEntry(const SymbolLocatorSymStore::LookupEntry &entry, if (file.starts_with("file://")) file = file.drop_front(7); if (auto spec = FindFileInLocalSymStore(file, key, pdb_name)) { - LLDB_LOGV(log, "Found {0} in local SymStore {1}", pdb_name, file); + LLDB_LOG(log, "Found {0} in local SymStore {1}", pdb_name, file); return *spec; } @@ -447,14 +447,14 @@ std::optional SymbolLocatorSymStore::LocateExecutableSymbolFile( std::string pdb_name = module_spec.GetSymbolFileSpec().GetFilename().GetStringRef().str(); if (pdb_name.empty()) { - LLDB_LOGV(log, "Failed to resolve symbol PDB module: PDB name empty"); + LLDB_LOG(log, "Failed to resolve symbol PDB module: PDB name empty"); return {}; } - LLDB_LOGV(log, "LocateExecutableSymbolFile {0} with UUID {1}", pdb_name, - uuid.GetAsString()); + LLDB_LOG(log, "LocateExecutableSymbolFile {0} with UUID {1}", pdb_name, + uuid.GetAsString()); if (uuid.GetBytes().size() != 20) { - LLDB_LOGV(log, "Failed to resolve symbol PDB module: UUID invalid"); + LLDB_LOG(log, "Failed to resolve symbol PDB module: UUID invalid"); return {}; }