Skip to content

Commit 36f0190

Browse files
committed
[llvm] [Debuginfod] LLVM debuginfod server.
This implements a debuginfod server in llvm using the `DebuginfodCollection` and `DebuginfodServer` classes. This is tested with lit tests against the debuginfod-find client. The server scans 0 or more local directories for artifacts. It serves the debuginfod protocol over HTTP. Only the `executable` and `debuginfo` endpoints are supported (no `/source` endpoint). The server also uses the debuginfod client as a fallback, so it can hit the local debuginfod cache or federate to other known debuginfod servers. The client behavior is controllable through the standard environment variables (`DEBUGINFOD_URLS`, `DEBUGINFOD_CACHE_PATH`, `DEBUGINFOD_TIMEOUT`) The server implements on-demand collection updates as follows: If the build-id is not found by a local lookup, rescan immediately and look up the build-id again before returning 404. To protect against DoS attacks, do not rescan more frequently than once per N seconds (specified by `-m`). Lit tests are provided which test the `llvm-debuginfod-find` client against the `llvm-debuginfod` server. Reviewed By: mysterymath Differential Revision: https://reviews.llvm.org/D114846
1 parent 472aa7e commit 36f0190

File tree

8 files changed

+224
-2
lines changed

8 files changed

+224
-2
lines changed

llvm/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ llvm_canonicalize_cmake_booleans(
66
LLVM_ENABLE_FFI
77
LLVM_ENABLE_THREADS
88
LLVM_ENABLE_CURL
9+
LLVM_ENABLE_HTTPLIB
910
LLVM_ENABLE_ZLIB
1011
LLVM_ENABLE_LIBXML2
1112
LLVM_INCLUDE_GO_TESTS

llvm/test/lit.cfg.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def get_asan_rtlib():
159159
tools.extend([
160160
'dsymutil', 'lli', 'lli-child-target', 'llvm-ar', 'llvm-as',
161161
'llvm-addr2line', 'llvm-bcanalyzer', 'llvm-bitcode-strip', 'llvm-config',
162-
'llvm-cov', 'llvm-cxxdump', 'llvm-cvtres', 'llvm-debuginfod-find',
162+
'llvm-cov', 'llvm-cxxdump', 'llvm-cvtres', 'llvm-debuginfod-find', 'llvm-debuginfod',
163163
'llvm-diff', 'llvm-dis', 'llvm-dwarfdump', 'llvm-dlltool', 'llvm-exegesis',
164164
'llvm-extract', 'llvm-isel-fuzzer', 'llvm-ifs',
165165
'llvm-install-name-tool', 'llvm-jitlink', 'llvm-opt-fuzzer', 'llvm-lib',
@@ -478,4 +478,3 @@ def exclude_unsupported_files_for_aix(dirname):
478478
if 'aix' in config.target_triple:
479479
for directory in ('/CodeGen/X86', '/DebugInfo', '/DebugInfo/X86', '/DebugInfo/Generic', '/LTO/X86', '/Linker'):
480480
exclude_unsupported_files_for_aix(config.test_source_root + directory)
481-

llvm/test/lit.site.cfg.py.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ config.have_zlib = @LLVM_ENABLE_ZLIB@
4040
config.have_libxar = @LLVM_HAVE_LIBXAR@
4141
config.have_libxml2 = @LLVM_ENABLE_LIBXML2@
4242
config.have_curl = @LLVM_ENABLE_CURL@
43+
config.have_httplib = @LLVM_ENABLE_HTTPLIB@
4344
config.have_dia_sdk = @LLVM_ENABLE_DIA_SDK@
4445
config.enable_ffi = @LLVM_ENABLE_FFI@
4546
config.build_examples = @LLVM_BUILD_EXAMPLES@
Binary file not shown.
13.9 KB
Binary file not shown.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# REQUIRES: curl, httplib, thread_support
2+
3+
#int main () {
4+
# int x = 1;
5+
# return x;
6+
#}
7+
#
8+
#Build as : clang -g main.c -o main-debug.exe
9+
#Then run : cp main-debug.exe main.exe && strip main.exe
10+
#resulting buildid: 2c39b7557c50162aaeb5a3148c9f76e6e46012e3
11+
12+
# RUN: rm -rf %t
13+
# RUN: mkdir %t
14+
# # Query the debuginfod server for artifacts
15+
# RUN: DEBUGINFOD_CACHE_PATH=%t %python %s --server-cmd 'llvm-debuginfod -v -c 3 %S/Inputs' \
16+
# RUN: --tool-cmd 'llvm-debuginfod-find --dump --executable 2c39b7557c50162aaeb5a3148c9f76e6e46012e3' | \
17+
# RUN: diff - %S/Inputs/main.exe
18+
# RUN: DEBUGINFOD_CACHE_PATH=%t %python %s --server-cmd 'llvm-debuginfod -v -c 3 %S/Inputs' \
19+
# RUN: --tool-cmd 'llvm-debuginfod-find --dump --debuginfo 2c39b7557c50162aaeb5a3148c9f76e6e46012e3' | \
20+
# RUN: diff - %S/Inputs/main-debug.exe
21+
# Debuginfod server does not yet support source files
22+
23+
# # The artifacts should still be present in the cache without needing to query
24+
# # the server.
25+
# RUN: DEBUGINFOD_CACHE_PATH=%t llvm-debuginfod-find --dump \
26+
# RUN: --executable 2c39b7557c50162aaeb5a3148c9f76e6e46012e3 | \
27+
# RUN: diff - %S/Inputs/main.exe
28+
# RUN: DEBUGINFOD_CACHE_PATH=%t llvm-debuginfod-find --dump \
29+
# RUN: --debuginfo 2c39b7557c50162aaeb5a3148c9f76e6e46012e3 | \
30+
# RUN: diff - %S/Inputs/main-debug.exe
31+
32+
33+
34+
# This script is used to test the debuginfod client within a host tool against
35+
# the debuginfod server.
36+
# It first stands up the debuginfod server and then executes the tool.
37+
# This way the tool can make debuginfod HTTP requests to the debuginfod server.
38+
import argparse
39+
import threading
40+
import subprocess
41+
import sys
42+
import os
43+
import io
44+
45+
# Starts the server and obtains the port number from the first line of stdout.
46+
# Waits until the server has completed one full directory scan before returning.
47+
def start_debuginfod_server(server_args):
48+
process = subprocess.Popen(
49+
server_args,
50+
env=os.environ,
51+
stdout=subprocess.PIPE)
52+
port = -1
53+
# Obtain the port.
54+
stdout_reader = io.TextIOWrapper(process.stdout, encoding='ascii')
55+
stdout_line = stdout_reader.readline()
56+
port = int(stdout_line.split()[-1])
57+
# Wait until a directory scan is completed.
58+
while True:
59+
stdout_line = stdout_reader.readline().strip()
60+
print(stdout_line, file=sys.stderr)
61+
if stdout_line == 'Updated collection':
62+
break
63+
return (process, port)
64+
65+
# Starts the server with the specified args (if nonempty), then runs the tool
66+
# with specified args.
67+
# Sets the DEBUGINFOD_CACHE_PATH env var to point at the given cache_directory.
68+
# Sets the DEBUGINFOD_URLS env var to point at the local server.
69+
def test_tool(server_args, tool_args):
70+
server_process = None
71+
client_process = None
72+
port = None
73+
server_process, port = start_debuginfod_server(server_args)
74+
try:
75+
env = os.environ
76+
if port is not None:
77+
env['DEBUGINFOD_URLS'] = 'http://localhost:%s' % port
78+
client_process = subprocess.Popen(
79+
tool_args, env=os.environ)
80+
client_code = client_process.wait()
81+
if client_code != 0:
82+
print('nontrivial client return code %s' % client_code, file=sys.stderr)
83+
return 1
84+
if server_process is not None:
85+
server_process.terminate()
86+
server_code = server_process.wait()
87+
if server_code != -15:
88+
print('nontrivial server return code %s' % server_code, file=sys.stderr)
89+
return 1
90+
91+
finally:
92+
if server_process is not None:
93+
server_process.terminate()
94+
if client_process is not None:
95+
client_process.terminate()
96+
return 0
97+
98+
def main():
99+
parser = argparse.ArgumentParser()
100+
parser.add_argument('--server-cmd', default='', help='Command to start the server. If not present, no server is started.', type=str)
101+
parser.add_argument('--tool-cmd', required=True, type=str)
102+
args = parser.parse_args()
103+
result = test_tool(args.server_cmd.split(),
104+
args.tool_cmd.split())
105+
sys.exit(result)
106+
107+
if __name__ == '__main__':
108+
main()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
set(LLVM_LINK_COMPONENTS
2+
Debuginfod
3+
Support
4+
)
5+
add_llvm_tool(llvm-debuginfod
6+
llvm-debuginfod.cpp
7+
)
8+
if(LLVM_INSTALL_BINUTILS_SYMLINKS)
9+
add_llvm_tool_symlink(debuginfod llvm-debuginfod)
10+
endif()
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//===-- llvm-debuginfod.cpp - federating debuginfod server ----------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// \file
10+
/// This file contains the llvm-debuginfod tool, which serves the debuginfod
11+
/// protocol over HTTP. The tool periodically scans zero or more filesystem
12+
/// directories for ELF binaries to serve, and federates requests for unknown
13+
/// build IDs to the debuginfod servers set in the DEBUGINFOD_URLS environment
14+
/// variable.
15+
///
16+
//===----------------------------------------------------------------------===//
17+
18+
#include "llvm/Debuginfod/Debuginfod.h"
19+
#include "llvm/Debuginfod/HTTPClient.h"
20+
#include "llvm/Support/CommandLine.h"
21+
#include "llvm/Support/InitLLVM.h"
22+
#include "llvm/Support/ThreadPool.h"
23+
24+
using namespace llvm;
25+
26+
cl::OptionCategory DebuginfodCategory("llvm-debuginfod Options");
27+
28+
static cl::list<std::string> ScanPaths(cl::Positional,
29+
cl::desc("<Directories to scan>"),
30+
cl::cat(DebuginfodCategory));
31+
32+
static cl::opt<unsigned>
33+
Port("p", cl::init(0),
34+
cl::desc("Port to listen on. Set to 0 to bind to any available port."),
35+
cl::cat(DebuginfodCategory));
36+
37+
static cl::opt<std::string>
38+
HostInterface("i", cl::init("0.0.0.0"),
39+
cl::desc("Host interface to bind to."),
40+
cl::cat(DebuginfodCategory));
41+
42+
static cl::opt<int>
43+
ScanInterval("t", cl::init(300),
44+
cl::desc("Number of seconds to wait between subsequent "
45+
"automated scans of the filesystem."),
46+
cl::cat(DebuginfodCategory));
47+
48+
static cl::opt<double> MinInterval(
49+
"m", cl::init(10),
50+
cl::desc(
51+
"Minimum number of seconds to wait before an on-demand update can be "
52+
"triggered by a request for a buildid which is not in the collection."),
53+
cl::cat(DebuginfodCategory));
54+
55+
static cl::opt<size_t>
56+
MaxConcurrency("c", cl::init(0),
57+
cl::desc("Maximum number of files to scan concurrently. If "
58+
"0, use the hardware concurrency."),
59+
cl::cat(DebuginfodCategory));
60+
61+
static cl::opt<bool> VerboseLogging("v", cl::init(false),
62+
cl::desc("Enable verbose logging."),
63+
cl::cat(DebuginfodCategory));
64+
65+
ExitOnError ExitOnErr;
66+
67+
int main(int argc, char **argv) {
68+
InitLLVM X(argc, argv);
69+
HTTPClient::initialize();
70+
cl::HideUnrelatedOptions({&DebuginfodCategory});
71+
cl::ParseCommandLineOptions(argc, argv);
72+
73+
SmallVector<StringRef, 1> Paths;
74+
for (const std::string &Path : ScanPaths)
75+
Paths.push_back(Path);
76+
77+
ThreadPool Pool(hardware_concurrency(MaxConcurrency));
78+
DebuginfodLog Log;
79+
DebuginfodCollection Collection(Paths, Log, Pool, MinInterval);
80+
DebuginfodServer Server(Log, Collection);
81+
82+
if (!Port)
83+
Port = ExitOnErr(Server.Server.bind(HostInterface.c_str()));
84+
else
85+
ExitOnErr(Server.Server.bind(Port, HostInterface.c_str()));
86+
87+
Log.push("Listening on port " + Twine(Port).str());
88+
89+
Pool.async([&]() { ExitOnErr(Server.Server.listen()); });
90+
Pool.async([&]() {
91+
while (1) {
92+
DebuginfodLogEntry Entry = Log.pop();
93+
if (VerboseLogging) {
94+
outs() << Entry.Message << "\n";
95+
outs().flush();
96+
}
97+
}
98+
});
99+
if (Paths.size())
100+
ExitOnErr(Collection.updateForever(std::chrono::seconds(ScanInterval)));
101+
Pool.wait();
102+
llvm_unreachable("The ThreadPool should never finish running its tasks.");
103+
}

0 commit comments

Comments
 (0)