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