1//===-- llvm-symbolizer.cpp - Simple addr2line-like symbolizer ------------===//
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// This utility works much like "addr2line". It is able of transforming
10// tuples (module name, module offset) to code locations (function name,
11// file, line number, column number). It is targeted for compiler-rt tools
12// (especially AddressSanitizer and ThreadSanitizer) that can use it
13// to symbolize stack traces in their error reports.
14//
15//===----------------------------------------------------------------------===//
16
17#include "Opts.inc"
18#include "llvm/ADT/StringExtras.h"
19#include "llvm/ADT/StringRef.h"
20#include "llvm/Config/config.h"
21#include "llvm/DebugInfo/Symbolize/DIPrinter.h"
22#include "llvm/DebugInfo/Symbolize/Markup.h"
23#include "llvm/DebugInfo/Symbolize/MarkupFilter.h"
24#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
25#include "llvm/DebugInfo/Symbolize/Symbolize.h"
26#include "llvm/Debuginfod/BuildIDFetcher.h"
27#include "llvm/Debuginfod/Debuginfod.h"
28#include "llvm/Debuginfod/HTTPClient.h"
29#include "llvm/Option/Arg.h"
30#include "llvm/Option/ArgList.h"
31#include "llvm/Option/Option.h"
32#include "llvm/Support/COM.h"
33#include "llvm/Support/CommandLine.h"
34#include "llvm/Support/Debug.h"
35#include "llvm/Support/Errc.h"
36#include "llvm/Support/FileSystem.h"
37#include "llvm/Support/LLVMDriver.h"
38#include "llvm/Support/Path.h"
39#include "llvm/Support/StringSaver.h"
40#include "llvm/Support/WithColor.h"
41#include "llvm/Support/raw_ostream.h"
42#include <algorithm>
43#include <cstdio>
44#include <cstring>
45#include <iostream>
46#include <string>
47
48using namespace llvm;
49using namespace symbolize;
50
51namespace {
52enum ID {
53 OPT_INVALID = 0, // This is not an option ID.
54#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
55#include "Opts.inc"
56#undef OPTION
57};
58
59#define PREFIX(NAME, VALUE) \
60 static constexpr StringLiteral NAME##_init[] = VALUE; \
61 static constexpr ArrayRef<StringLiteral> NAME(NAME##_init, \
62 std::size(NAME##_init) - 1);
63#include "Opts.inc"
64#undef PREFIX
65
66using namespace llvm::opt;
67static constexpr opt::OptTable::Info InfoTable[] = {
68#define OPTION(...) LLVM_CONSTRUCT_OPT_INFO(__VA_ARGS__),
69#include "Opts.inc"
70#undef OPTION
71};
72
73class SymbolizerOptTable : public opt::GenericOptTable {
74public:
75 SymbolizerOptTable() : GenericOptTable(InfoTable) {
76 setGroupedShortOptions(true);
77 }
78};
79} // namespace
80
81static std::string ToolName;
82
83static void printError(const ErrorInfoBase &EI, StringRef AuxInfo) {
84 WithColor::error(OS&: errs(), Prefix: ToolName);
85 if (!AuxInfo.empty())
86 errs() << "'" << AuxInfo << "': ";
87 EI.log(OS&: errs());
88 errs() << '\n';
89}
90
91template <typename T>
92static void print(const Request &Request, Expected<T> &ResOrErr,
93 DIPrinter &Printer) {
94 if (ResOrErr) {
95 // No error, print the result.
96 Printer.print(Request, *ResOrErr);
97 return;
98 }
99
100 // Handle the error.
101 bool PrintEmpty = true;
102 handleAllErrors(std::move(ResOrErr.takeError()),
103 [&](const ErrorInfoBase &EI) {
104 PrintEmpty = Printer.printError(Request, ErrorInfo: EI);
105 });
106
107 if (PrintEmpty)
108 Printer.print(Request, T());
109}
110
111enum class OutputStyle { LLVM, GNU, JSON };
112
113enum class Command {
114 Code,
115 Data,
116 Frame,
117};
118
119static void enableDebuginfod(LLVMSymbolizer &Symbolizer,
120 const opt::ArgList &Args) {
121 static bool IsEnabled = false;
122 if (IsEnabled)
123 return;
124 IsEnabled = true;
125 // Look up symbols using the debuginfod client.
126 Symbolizer.setBuildIDFetcher(std::make_unique<DebuginfodFetcher>(
127 args: Args.getAllArgValues(Id: OPT_debug_file_directory_EQ)));
128 // The HTTPClient must be initialized for use by the debuginfod client.
129 HTTPClient::initialize();
130}
131
132static StringRef getSpaceDelimitedWord(StringRef &Source) {
133 const char kDelimiters[] = " \n\r";
134 const char *Pos = Source.data();
135 StringRef Result;
136 Pos += strspn(s: Pos, accept: kDelimiters);
137 if (*Pos == '"' || *Pos == '\'') {
138 char Quote = *Pos;
139 Pos++;
140 const char *End = strchr(s: Pos, c: Quote);
141 if (!End)
142 return StringRef();
143 Result = StringRef(Pos, End - Pos);
144 Pos = End + 1;
145 } else {
146 int NameLength = strcspn(s: Pos, reject: kDelimiters);
147 Result = StringRef(Pos, NameLength);
148 Pos += NameLength;
149 }
150 Source = StringRef(Pos, Source.end() - Pos);
151 return Result;
152}
153
154static Error makeStringError(StringRef Msg) {
155 return make_error<StringError>(Args&: Msg, Args: inconvertibleErrorCode());
156}
157
158static Error parseCommand(StringRef BinaryName, bool IsAddr2Line,
159 StringRef InputString, Command &Cmd,
160 std::string &ModuleName, object::BuildID &BuildID,
161 StringRef &Symbol, uint64_t &Offset) {
162 ModuleName = BinaryName;
163 if (InputString.consume_front(Prefix: "CODE ")) {
164 Cmd = Command::Code;
165 } else if (InputString.consume_front(Prefix: "DATA ")) {
166 Cmd = Command::Data;
167 } else if (InputString.consume_front(Prefix: "FRAME ")) {
168 Cmd = Command::Frame;
169 } else {
170 // If no cmd, assume it's CODE.
171 Cmd = Command::Code;
172 }
173
174 // Parse optional input file specification.
175 bool HasFilePrefix = false;
176 bool HasBuildIDPrefix = false;
177 while (!InputString.empty()) {
178 InputString = InputString.ltrim();
179 if (InputString.consume_front(Prefix: "FILE:")) {
180 if (HasFilePrefix || HasBuildIDPrefix)
181 return makeStringError(Msg: "duplicate input file specification prefix");
182 HasFilePrefix = true;
183 continue;
184 }
185 if (InputString.consume_front(Prefix: "BUILDID:")) {
186 if (HasBuildIDPrefix || HasFilePrefix)
187 return makeStringError(Msg: "duplicate input file specification prefix");
188 HasBuildIDPrefix = true;
189 continue;
190 }
191 break;
192 }
193
194 // If an input file is not specified on the command line, try to extract it
195 // from the command.
196 if (HasBuildIDPrefix || HasFilePrefix) {
197 InputString = InputString.ltrim();
198 if (InputString.empty()) {
199 if (HasFilePrefix)
200 return makeStringError(Msg: "must be followed by an input file");
201 else
202 return makeStringError(Msg: "must be followed by a hash");
203 }
204
205 if (!BinaryName.empty() || !BuildID.empty())
206 return makeStringError(Msg: "input file has already been specified");
207
208 StringRef Name = getSpaceDelimitedWord(Source&: InputString);
209 if (Name.empty())
210 return makeStringError(Msg: "unbalanced quotes in input file name");
211 if (HasBuildIDPrefix) {
212 BuildID = parseBuildID(Str: Name);
213 if (BuildID.empty())
214 return makeStringError(Msg: "wrong format of build-id");
215 } else {
216 ModuleName = Name;
217 }
218 } else if (BinaryName.empty() && BuildID.empty()) {
219 // No input file has been specified. If the input string contains at least
220 // two items, assume that the first item is a file name.
221 ModuleName = getSpaceDelimitedWord(Source&: InputString);
222 if (ModuleName.empty())
223 return makeStringError(Msg: "no input filename has been specified");
224 }
225
226 // Parse address specification, which can be an offset in module or a
227 // symbol with optional offset.
228 InputString = InputString.trim();
229 if (InputString.empty())
230 return makeStringError(Msg: "no module offset has been specified");
231
232 // If input string contains a space, ignore everything after it. This behavior
233 // is consistent with GNU addr2line.
234 int AddrSpecLength = InputString.find_first_of(Chars: " \n\r");
235 StringRef AddrSpec = InputString.substr(Start: 0, N: AddrSpecLength);
236 bool StartsWithDigit = std::isdigit(AddrSpec.front());
237
238 // GNU addr2line assumes the address is hexadecimal and allows a redundant
239 // "0x" or "0X" prefix; do the same for compatibility.
240 if (IsAddr2Line)
241 AddrSpec.consume_front(Prefix: "0x") || AddrSpec.consume_front(Prefix: "0X");
242
243 // If address specification is a number, treat it as a module offset.
244 if (!AddrSpec.getAsInteger(Radix: IsAddr2Line ? 16 : 0, Result&: Offset)) {
245 // Module offset is an address.
246 Symbol = StringRef();
247 return Error::success();
248 }
249
250 // If address specification starts with a digit, but is not a number, consider
251 // it as invalid.
252 if (StartsWithDigit || AddrSpec.empty())
253 return makeStringError(Msg: "expected a number as module offset");
254
255 // Otherwise it is a symbol name, potentially with an offset.
256 Symbol = AddrSpec;
257 Offset = 0;
258
259 // If the address specification contains '+', try treating it as
260 // "symbol + offset".
261 size_t Plus = AddrSpec.rfind(C: '+');
262 if (Plus != StringRef::npos) {
263 StringRef SymbolStr = AddrSpec.take_front(N: Plus);
264 StringRef OffsetStr = AddrSpec.substr(Start: Plus + 1);
265 if (!SymbolStr.empty() && !OffsetStr.empty() &&
266 !OffsetStr.getAsInteger(Radix: 0, Result&: Offset)) {
267 Symbol = SymbolStr;
268 return Error::success();
269 }
270 // The found '+' is not an offset delimiter.
271 }
272
273 return Error::success();
274}
275
276template <typename T>
277void executeCommand(StringRef ModuleName, const T &ModuleSpec, Command Cmd,
278 StringRef Symbol, uint64_t Offset, uint64_t AdjustVMA,
279 bool ShouldInline, OutputStyle Style,
280 LLVMSymbolizer &Symbolizer, DIPrinter &Printer) {
281 uint64_t AdjustedOffset = Offset - AdjustVMA;
282 object::SectionedAddress Address = {.Address: AdjustedOffset,
283 .SectionIndex: object::SectionedAddress::UndefSection};
284 Request SymRequest = {
285 .ModuleName: ModuleName, .Address: Symbol.empty() ? std::make_optional(t&: Offset) : std::nullopt,
286 .Symbol: Symbol};
287 if (Cmd == Command::Data) {
288 Expected<DIGlobal> ResOrErr = Symbolizer.symbolizeData(ModuleSpec, Address);
289 print(Request: SymRequest, ResOrErr, Printer);
290 } else if (Cmd == Command::Frame) {
291 Expected<std::vector<DILocal>> ResOrErr =
292 Symbolizer.symbolizeFrame(ModuleSpec, Address);
293 print(Request: SymRequest, ResOrErr, Printer);
294 } else if (!Symbol.empty()) {
295 Expected<std::vector<DILineInfo>> ResOrErr =
296 Symbolizer.findSymbol(ModuleSpec, Symbol, Offset);
297 print(Request: SymRequest, ResOrErr, Printer);
298 } else if (ShouldInline) {
299 Expected<DIInliningInfo> ResOrErr =
300 Symbolizer.symbolizeInlinedCode(ModuleSpec, Address);
301 print(Request: SymRequest, ResOrErr, Printer);
302 } else if (Style == OutputStyle::GNU) {
303 // With PrintFunctions == FunctionNameKind::LinkageName (default)
304 // and UseSymbolTable == true (also default), Symbolizer.symbolizeCode()
305 // may override the name of an inlined function with the name of the topmost
306 // caller function in the inlining chain. This contradicts the existing
307 // behavior of addr2line. Symbolizer.symbolizeInlinedCode() overrides only
308 // the topmost function, which suits our needs better.
309 Expected<DIInliningInfo> ResOrErr =
310 Symbolizer.symbolizeInlinedCode(ModuleSpec, Address);
311 Expected<DILineInfo> Res0OrErr =
312 !ResOrErr
313 ? Expected<DILineInfo>(ResOrErr.takeError())
314 : ((ResOrErr->getNumberOfFrames() == 0) ? DILineInfo()
315 : ResOrErr->getFrame(Index: 0));
316 print(Request: SymRequest, ResOrErr&: Res0OrErr, Printer);
317 } else {
318 Expected<DILineInfo> ResOrErr =
319 Symbolizer.symbolizeCode(ModuleSpec, Address);
320 print(Request: SymRequest, ResOrErr, Printer);
321 }
322 Symbolizer.pruneCache();
323}
324
325static void printUnknownLineInfo(std::string ModuleName, DIPrinter &Printer) {
326 Request SymRequest = {.ModuleName: ModuleName, .Address: std::nullopt, .Symbol: StringRef()};
327 Printer.print(Request: SymRequest, Info: DILineInfo());
328}
329
330static void symbolizeInput(const opt::InputArgList &Args,
331 object::BuildIDRef IncomingBuildID,
332 uint64_t AdjustVMA, bool IsAddr2Line,
333 OutputStyle Style, StringRef InputString,
334 LLVMSymbolizer &Symbolizer, DIPrinter &Printer) {
335 Command Cmd;
336 std::string ModuleName;
337 object::BuildID BuildID(IncomingBuildID.begin(), IncomingBuildID.end());
338 uint64_t Offset = 0;
339 StringRef Symbol;
340
341 // An empty input string may be used to check if the process is alive and
342 // responding to input. Do not emit a message on stderr in this case but
343 // respond on stdout.
344 if (InputString.empty()) {
345 printUnknownLineInfo(ModuleName, Printer);
346 return;
347 }
348 if (Error E = parseCommand(BinaryName: Args.getLastArgValue(Id: OPT_obj_EQ), IsAddr2Line,
349 InputString: StringRef(InputString), Cmd, ModuleName, BuildID,
350 Symbol, Offset)) {
351 handleAllErrors(E: std::move(E), Handlers: [&](const StringError &EI) {
352 printError(EI, AuxInfo: InputString);
353 printUnknownLineInfo(ModuleName, Printer);
354 });
355 return;
356 }
357 bool ShouldInline = Args.hasFlag(Pos: OPT_inlines, Neg: OPT_no_inlines, Default: !IsAddr2Line);
358 if (!BuildID.empty()) {
359 assert(ModuleName.empty());
360 if (!Args.hasArg(Ids: OPT_no_debuginfod))
361 enableDebuginfod(Symbolizer, Args);
362 std::string BuildIDStr = toHex(Input: BuildID);
363 executeCommand(ModuleName: BuildIDStr, ModuleSpec: BuildID, Cmd, Symbol, Offset, AdjustVMA,
364 ShouldInline, Style, Symbolizer, Printer);
365 } else {
366 executeCommand(ModuleName, ModuleSpec: ModuleName, Cmd, Symbol, Offset, AdjustVMA,
367 ShouldInline, Style, Symbolizer, Printer);
368 }
369}
370
371static void printHelp(StringRef ToolName, const SymbolizerOptTable &Tbl,
372 raw_ostream &OS) {
373 const char HelpText[] = " [options] addresses...";
374 Tbl.printHelp(OS, Usage: (ToolName + HelpText).str().c_str(),
375 Title: ToolName.str().c_str());
376 // TODO Replace this with OptTable API once it adds extrahelp support.
377 OS << "\nPass @FILE as argument to read options from FILE.\n";
378}
379
380static opt::InputArgList parseOptions(int Argc, char *Argv[], bool IsAddr2Line,
381 StringSaver &Saver,
382 SymbolizerOptTable &Tbl) {
383 StringRef ToolName = IsAddr2Line ? "llvm-addr2line" : "llvm-symbolizer";
384 // The environment variable specifies initial options which can be overridden
385 // by commnad line options.
386 Tbl.setInitialOptionsFromEnvironment(IsAddr2Line ? "LLVM_ADDR2LINE_OPTS"
387 : "LLVM_SYMBOLIZER_OPTS");
388 bool HasError = false;
389 opt::InputArgList Args =
390 Tbl.parseArgs(Argc, Argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
391 errs() << ("error: " + Msg + "\n");
392 HasError = true;
393 });
394 if (HasError)
395 exit(status: 1);
396 if (Args.hasArg(Ids: OPT_help)) {
397 printHelp(ToolName, Tbl, OS&: outs());
398 exit(status: 0);
399 }
400 if (Args.hasArg(Ids: OPT_version)) {
401 outs() << ToolName << '\n';
402 cl::PrintVersionMessage();
403 exit(status: 0);
404 }
405
406 return Args;
407}
408
409template <typename T>
410static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value) {
411 if (const opt::Arg *A = Args.getLastArg(Ids: ID)) {
412 StringRef V(A->getValue());
413 if (!llvm::to_integer(V, Value, 0)) {
414 errs() << A->getSpelling() +
415 ": expected a non-negative integer, but got '" + V + "'";
416 exit(status: 1);
417 }
418 } else {
419 Value = 0;
420 }
421}
422
423static FunctionNameKind decideHowToPrintFunctions(const opt::InputArgList &Args,
424 bool IsAddr2Line) {
425 if (Args.hasArg(Ids: OPT_functions))
426 return FunctionNameKind::LinkageName;
427 if (const opt::Arg *A = Args.getLastArg(Ids: OPT_functions_EQ))
428 return StringSwitch<FunctionNameKind>(A->getValue())
429 .Case(S: "none", Value: FunctionNameKind::None)
430 .Case(S: "short", Value: FunctionNameKind::ShortName)
431 .Default(Value: FunctionNameKind::LinkageName);
432 return IsAddr2Line ? FunctionNameKind::None : FunctionNameKind::LinkageName;
433}
434
435static std::optional<bool> parseColorArg(const opt::InputArgList &Args) {
436 if (Args.hasArg(Ids: OPT_color))
437 return true;
438 if (const opt::Arg *A = Args.getLastArg(Ids: OPT_color_EQ))
439 return StringSwitch<std::optional<bool>>(A->getValue())
440 .Case(S: "always", Value: true)
441 .Case(S: "never", Value: false)
442 .Case(S: "auto", Value: std::nullopt);
443 return std::nullopt;
444}
445
446static object::BuildID parseBuildIDArg(const opt::InputArgList &Args, int ID) {
447 const opt::Arg *A = Args.getLastArg(Ids: ID);
448 if (!A)
449 return {};
450
451 StringRef V(A->getValue());
452 object::BuildID BuildID = parseBuildID(Str: V);
453 if (BuildID.empty()) {
454 errs() << A->getSpelling() + ": expected a build ID, but got '" + V + "'\n";
455 exit(status: 1);
456 }
457 return BuildID;
458}
459
460// Symbolize markup from stdin and write the result to stdout.
461static void filterMarkup(const opt::InputArgList &Args, LLVMSymbolizer &Symbolizer) {
462 MarkupFilter Filter(outs(), Symbolizer, parseColorArg(Args));
463 std::string InputString;
464 while (std::getline(is&: std::cin, str&: InputString)) {
465 InputString += '\n';
466 Filter.filter(InputLine: std::move(InputString));
467 }
468 Filter.finish();
469}
470
471int llvm_symbolizer_main(int argc, char **argv, const llvm::ToolContext &) {
472 sys::InitializeCOMRAII COM(sys::COMThreadingMode::MultiThreaded);
473
474 ToolName = argv[0];
475 bool IsAddr2Line = sys::path::stem(path: ToolName).contains(Other: "addr2line");
476 BumpPtrAllocator A;
477 StringSaver Saver(A);
478 SymbolizerOptTable Tbl;
479 opt::InputArgList Args = parseOptions(Argc: argc, Argv: argv, IsAddr2Line, Saver, Tbl);
480
481 LLVMSymbolizer::Options Opts;
482 uint64_t AdjustVMA;
483 PrinterConfig Config;
484 parseIntArg(Args, ID: OPT_adjust_vma_EQ, Value&: AdjustVMA);
485 if (const opt::Arg *A = Args.getLastArg(Ids: OPT_basenames, Ids: OPT_relativenames)) {
486 Opts.PathStyle =
487 A->getOption().matches(ID: OPT_basenames)
488 ? DILineInfoSpecifier::FileLineInfoKind::BaseNameOnly
489 : DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath;
490 } else {
491 Opts.PathStyle = DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath;
492 }
493 Opts.DebugFileDirectory = Args.getAllArgValues(Id: OPT_debug_file_directory_EQ);
494 Opts.DefaultArch = Args.getLastArgValue(Id: OPT_default_arch_EQ).str();
495 Opts.Demangle = Args.hasFlag(Pos: OPT_demangle, Neg: OPT_no_demangle, Default: !IsAddr2Line);
496 Opts.DWPName = Args.getLastArgValue(Id: OPT_dwp_EQ).str();
497 Opts.FallbackDebugPath =
498 Args.getLastArgValue(Id: OPT_fallback_debug_path_EQ).str();
499 Opts.PrintFunctions = decideHowToPrintFunctions(Args, IsAddr2Line);
500 parseIntArg(Args, ID: OPT_print_source_context_lines_EQ,
501 Value&: Config.SourceContextLines);
502 Opts.RelativeAddresses = Args.hasArg(Ids: OPT_relative_address);
503 Opts.UntagAddresses =
504 Args.hasFlag(Pos: OPT_untag_addresses, Neg: OPT_no_untag_addresses, Default: !IsAddr2Line);
505 Opts.UseDIA = Args.hasArg(Ids: OPT_use_dia);
506#if !defined(LLVM_ENABLE_DIA_SDK)
507 if (Opts.UseDIA) {
508 WithColor::warning() << "DIA not available; using native PDB reader\n";
509 Opts.UseDIA = false;
510 }
511#endif
512 Opts.UseSymbolTable = true;
513 if (Args.hasArg(Ids: OPT_cache_size_EQ))
514 parseIntArg(Args, ID: OPT_cache_size_EQ, Value&: Opts.MaxCacheSize);
515 Config.PrintAddress = Args.hasArg(Ids: OPT_addresses);
516 Config.PrintFunctions = Opts.PrintFunctions != FunctionNameKind::None;
517 Config.Pretty = Args.hasArg(Ids: OPT_pretty_print);
518 Config.Verbose = Args.hasArg(Ids: OPT_verbose);
519
520 for (const opt::Arg *A : Args.filtered(Ids: OPT_dsym_hint_EQ)) {
521 StringRef Hint(A->getValue());
522 if (sys::path::extension(path: Hint) == ".dSYM") {
523 Opts.DsymHints.emplace_back(args&: Hint);
524 } else {
525 errs() << "Warning: invalid dSYM hint: \"" << Hint
526 << "\" (must have the '.dSYM' extension).\n";
527 }
528 }
529
530 LLVMSymbolizer Symbolizer(Opts);
531
532 if (Args.hasFlag(Pos: OPT_debuginfod, Neg: OPT_no_debuginfod, Default: canUseDebuginfod()))
533 enableDebuginfod(Symbolizer, Args);
534
535 if (Args.hasArg(Ids: OPT_filter_markup)) {
536 filterMarkup(Args, Symbolizer);
537 return 0;
538 }
539
540 auto Style = IsAddr2Line ? OutputStyle::GNU : OutputStyle::LLVM;
541 if (const opt::Arg *A = Args.getLastArg(Ids: OPT_output_style_EQ)) {
542 if (strcmp(s1: A->getValue(), s2: "GNU") == 0)
543 Style = OutputStyle::GNU;
544 else if (strcmp(s1: A->getValue(), s2: "JSON") == 0)
545 Style = OutputStyle::JSON;
546 else
547 Style = OutputStyle::LLVM;
548 }
549
550 if (Args.hasArg(Ids: OPT_build_id_EQ) && Args.hasArg(Ids: OPT_obj_EQ)) {
551 errs() << "error: cannot specify both --build-id and --obj\n";
552 return EXIT_FAILURE;
553 }
554 object::BuildID BuildID = parseBuildIDArg(Args, ID: OPT_build_id_EQ);
555
556 std::unique_ptr<DIPrinter> Printer;
557 if (Style == OutputStyle::GNU)
558 Printer = std::make_unique<GNUPrinter>(args&: outs(), args&: printError, args&: Config);
559 else if (Style == OutputStyle::JSON)
560 Printer = std::make_unique<JSONPrinter>(args&: outs(), args&: Config);
561 else
562 Printer = std::make_unique<LLVMPrinter>(args&: outs(), args&: printError, args&: Config);
563
564 // When an input file is specified, exit immediately if the file cannot be
565 // read. If getOrCreateModuleInfo succeeds, symbolizeInput will reuse the
566 // cached file handle.
567 if (auto *Arg = Args.getLastArg(Ids: OPT_obj_EQ); Arg) {
568 auto Status = Symbolizer.getOrCreateModuleInfo(ModuleName: Arg->getValue());
569 if (!Status) {
570 Request SymRequest = {.ModuleName: Arg->getValue(), .Address: 0, .Symbol: StringRef()};
571 handleAllErrors(E: Status.takeError(), Handlers: [&](const ErrorInfoBase &EI) {
572 Printer->printError(Request: SymRequest, ErrorInfo: EI);
573 });
574 return EXIT_FAILURE;
575 }
576 }
577
578 std::vector<std::string> InputAddresses = Args.getAllArgValues(Id: OPT_INPUT);
579 if (InputAddresses.empty()) {
580 const int kMaxInputStringLength = 1024;
581 char InputString[kMaxInputStringLength];
582
583 while (fgets(s: InputString, n: sizeof(InputString), stdin)) {
584 // Strip newline characters.
585 std::string StrippedInputString(InputString);
586 llvm::erase_if(C&: StrippedInputString,
587 P: [](char c) { return c == '\r' || c == '\n'; });
588 symbolizeInput(Args, IncomingBuildID: BuildID, AdjustVMA, IsAddr2Line, Style,
589 InputString: StrippedInputString, Symbolizer, Printer&: *Printer);
590 outs().flush();
591 }
592 } else {
593 Printer->listBegin();
594 for (StringRef Address : InputAddresses)
595 symbolizeInput(Args, IncomingBuildID: BuildID, AdjustVMA, IsAddr2Line, Style, InputString: Address,
596 Symbolizer, Printer&: *Printer);
597 Printer->listEnd();
598 }
599
600 return 0;
601}
602