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/ADT/StringExtras.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/Debuginfod/Debuginfod.h"
21#include "llvm/Debuginfod/HTTPClient.h"
22#include "llvm/Option/ArgList.h"
23#include "llvm/Option/Option.h"
24#include "llvm/Support/CommandLine.h"
25#include "llvm/Support/LLVMDriver.h"
26#include "llvm/Support/ThreadPool.h"
27
28using namespace llvm;
29
30// Command-line option boilerplate.
31namespace {
32enum ID {
33 OPT_INVALID = 0, // This is not an option ID.
34#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
35#include "Opts.inc"
36#undef OPTION
37};
38
39#define PREFIX(NAME, VALUE) \
40 static constexpr StringLiteral NAME##_init[] = VALUE; \
41 static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \
42 std::size(NAME##_init) - 1);
43#include "Opts.inc"
44#undef PREFIX
45
46using namespace llvm::opt;
47static constexpr opt::OptTable::Info InfoTable[] = {
48#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
49#include "Opts.inc"
50#undef OPTION
51};
52
53class DebuginfodOptTable : public opt::GenericOptTable {
54public:
55 DebuginfodOptTable() : GenericOptTable(InfoTable) {}
56};
57} // end anonymous namespace
58
59// Options
60static unsigned Port;
61static std::string HostInterface;
62static int ScanInterval;
63static double MinInterval;
64static size_t MaxConcurrency;
65static bool VerboseLogging;
66static std::vector<std::string> ScanPaths;
67
68ExitOnError ExitOnErr;
69
70template <typename T>
71static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value,
72 T Default) {
73 if (const opt::Arg *A = Args.getLastArg(Ids: ID)) {
74 StringRef V(A->getValue());
75 if (!llvm::to_integer(V, Value, 0)) {
76 errs() << A->getSpelling() + ": expected an integer, but got '" + V + "'";
77 exit(status: 1);
78 }
79 } else {
80 Value = Default;
81 }
82}
83
84static void parseArgs(int argc, char **argv) {
85 DebuginfodOptTable Tbl;
86 llvm::StringRef ToolName = argv[0];
87 llvm::BumpPtrAllocator A;
88 llvm::StringSaver Saver{A};
89 opt::InputArgList Args =
90 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
91 llvm::errs() << Msg << '\n';
92 std::exit(status: 1);
93 });
94
95 if (Args.hasArg(Ids: OPT_help)) {
96 Tbl.printHelp(OS&: llvm::outs(),
97 Usage: "llvm-debuginfod [options] <Directories to scan>",
98 Title: ToolName.str().c_str());
99 std::exit(status: 0);
100 }
101
102 VerboseLogging = Args.hasArg(Ids: OPT_verbose_logging);
103 ScanPaths = Args.getAllArgValues(Id: OPT_INPUT);
104
105 parseIntArg(Args, ID: OPT_port, Value&: Port, Default: 0u);
106 parseIntArg(Args, ID: OPT_scan_interval, Value&: ScanInterval, Default: 300);
107 parseIntArg(Args, ID: OPT_max_concurrency, Value&: MaxConcurrency, Default: size_t(0));
108
109 if (const opt::Arg *A = Args.getLastArg(Ids: OPT_min_interval)) {
110 StringRef V(A->getValue());
111 if (!llvm::to_float(T: V, Num&: MinInterval)) {
112 errs() << A->getSpelling() + ": expected a number, but got '" + V + "'";
113 exit(status: 1);
114 }
115 } else {
116 MinInterval = 10.0;
117 }
118
119 HostInterface = Args.getLastArgValue(Id: OPT_host_interface, Default: "0.0.0.0");
120}
121
122int llvm_debuginfod_main(int argc, char **argv, const llvm::ToolContext &) {
123 HTTPClient::initialize();
124 parseArgs(argc, argv);
125
126 SmallVector<StringRef, 1> Paths;
127 for (const std::string &Path : ScanPaths)
128 Paths.push_back(Elt: Path);
129
130 DefaultThreadPool Pool(hardware_concurrency(ThreadCount: MaxConcurrency));
131 DebuginfodLog Log;
132 DebuginfodCollection Collection(Paths, Log, Pool, MinInterval);
133 DebuginfodServer Server(Log, Collection);
134
135 if (!Port)
136 Port = ExitOnErr(Server.Server.bind(HostInterface: HostInterface.c_str()));
137 else
138 ExitOnErr(Server.Server.bind(Port, HostInterface: HostInterface.c_str()));
139
140 Log.push(Message: "Listening on port " + Twine(Port).str());
141
142 Pool.async(F: [&]() { ExitOnErr(Server.Server.listen()); });
143 Pool.async(F: [&]() {
144 while (true) {
145 DebuginfodLogEntry Entry = Log.pop();
146 if (VerboseLogging) {
147 outs() << Entry.Message << "\n";
148 outs().flush();
149 }
150 }
151 });
152 if (Paths.size())
153 ExitOnErr(Collection.updateForever(Interval: std::chrono::seconds(ScanInterval)));
154 Pool.wait();
155 llvm_unreachable("The ThreadPool should never finish running its tasks.");
156}
157