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/StringRef.h" 19 #include "llvm/Config/config.h" 20 #include "llvm/DebugInfo/Symbolize/DIPrinter.h" 21 #include "llvm/DebugInfo/Symbolize/Symbolize.h" 22 #include "llvm/Option/Arg.h" 23 #include "llvm/Option/ArgList.h" 24 #include "llvm/Option/Option.h" 25 #include "llvm/Support/COM.h" 26 #include "llvm/Support/CommandLine.h" 27 #include "llvm/Support/Debug.h" 28 #include "llvm/Support/FileSystem.h" 29 #include "llvm/Support/InitLLVM.h" 30 #include "llvm/Support/Path.h" 31 #include "llvm/Support/StringSaver.h" 32 #include "llvm/Support/raw_ostream.h" 33 #include <algorithm> 34 #include <cstdio> 35 #include <cstring> 36 #include <string> 37 38 using namespace llvm; 39 using namespace symbolize; 40 41 namespace { 42 enum ID { 43 OPT_INVALID = 0, // This is not an option ID. 44 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 45 HELPTEXT, METAVAR, VALUES) \ 46 OPT_##ID, 47 #include "Opts.inc" 48 #undef OPTION 49 }; 50 51 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; 52 #include "Opts.inc" 53 #undef PREFIX 54 55 static const opt::OptTable::Info InfoTable[] = { 56 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ 57 HELPTEXT, METAVAR, VALUES) \ 58 { \ 59 PREFIX, NAME, HELPTEXT, \ 60 METAVAR, OPT_##ID, opt::Option::KIND##Class, \ 61 PARAM, FLAGS, OPT_##GROUP, \ 62 OPT_##ALIAS, ALIASARGS, VALUES}, 63 #include "Opts.inc" 64 #undef OPTION 65 }; 66 67 class SymbolizerOptTable : public opt::OptTable { 68 public: 69 SymbolizerOptTable() : OptTable(InfoTable, true) {} 70 }; 71 } // namespace 72 73 static cl::list<std::string> ClInputAddresses(cl::Positional, 74 cl::desc("<input addresses>..."), 75 cl::ZeroOrMore); 76 77 template<typename T> 78 static bool error(Expected<T> &ResOrErr) { 79 if (ResOrErr) 80 return false; 81 logAllUnhandledErrors(ResOrErr.takeError(), errs(), 82 "LLVMSymbolizer: error reading file: "); 83 return true; 84 } 85 86 enum class Command { 87 Code, 88 Data, 89 Frame, 90 }; 91 92 static bool parseCommand(StringRef BinaryName, bool IsAddr2Line, 93 StringRef InputString, Command &Cmd, 94 std::string &ModuleName, uint64_t &ModuleOffset) { 95 const char kDelimiters[] = " \n\r"; 96 ModuleName = ""; 97 if (InputString.consume_front("CODE ")) { 98 Cmd = Command::Code; 99 } else if (InputString.consume_front("DATA ")) { 100 Cmd = Command::Data; 101 } else if (InputString.consume_front("FRAME ")) { 102 Cmd = Command::Frame; 103 } else { 104 // If no cmd, assume it's CODE. 105 Cmd = Command::Code; 106 } 107 const char *Pos = InputString.data(); 108 // Skip delimiters and parse input filename (if needed). 109 if (BinaryName.empty()) { 110 Pos += strspn(Pos, kDelimiters); 111 if (*Pos == '"' || *Pos == '\'') { 112 char Quote = *Pos; 113 Pos++; 114 const char *End = strchr(Pos, Quote); 115 if (!End) 116 return false; 117 ModuleName = std::string(Pos, End - Pos); 118 Pos = End + 1; 119 } else { 120 int NameLength = strcspn(Pos, kDelimiters); 121 ModuleName = std::string(Pos, NameLength); 122 Pos += NameLength; 123 } 124 } else { 125 ModuleName = BinaryName.str(); 126 } 127 // Skip delimiters and parse module offset. 128 Pos += strspn(Pos, kDelimiters); 129 int OffsetLength = strcspn(Pos, kDelimiters); 130 StringRef Offset(Pos, OffsetLength); 131 // GNU addr2line assumes the offset is hexadecimal and allows a redundant 132 // "0x" or "0X" prefix; do the same for compatibility. 133 if (IsAddr2Line) 134 Offset.consume_front("0x") || Offset.consume_front("0X"); 135 return !Offset.getAsInteger(IsAddr2Line ? 16 : 0, ModuleOffset); 136 } 137 138 static void symbolizeInput(const opt::InputArgList &Args, uint64_t AdjustVMA, 139 bool IsAddr2Line, DIPrinter::OutputStyle OutputStyle, 140 StringRef InputString, LLVMSymbolizer &Symbolizer, 141 DIPrinter &Printer) { 142 Command Cmd; 143 std::string ModuleName; 144 uint64_t Offset = 0; 145 if (!parseCommand(Args.getLastArgValue(OPT_obj_EQ), IsAddr2Line, 146 StringRef(InputString), Cmd, ModuleName, Offset)) { 147 outs() << InputString << "\n"; 148 return; 149 } 150 151 if (Args.hasArg(OPT_addresses)) { 152 outs() << "0x"; 153 outs().write_hex(Offset); 154 StringRef Delimiter = Args.hasArg(OPT_pretty_print) ? ": " : "\n"; 155 outs() << Delimiter; 156 } 157 Offset -= AdjustVMA; 158 if (Cmd == Command::Data) { 159 auto ResOrErr = Symbolizer.symbolizeData( 160 ModuleName, {Offset, object::SectionedAddress::UndefSection}); 161 Printer << (error(ResOrErr) ? DIGlobal() : ResOrErr.get()); 162 } else if (Cmd == Command::Frame) { 163 auto ResOrErr = Symbolizer.symbolizeFrame( 164 ModuleName, {Offset, object::SectionedAddress::UndefSection}); 165 if (!error(ResOrErr)) { 166 for (DILocal Local : *ResOrErr) 167 Printer << Local; 168 if (ResOrErr->empty()) 169 outs() << "??\n"; 170 } 171 } else if (Args.hasFlag(OPT_inlines, OPT_no_inlines, !IsAddr2Line)) { 172 auto ResOrErr = Symbolizer.symbolizeInlinedCode( 173 ModuleName, {Offset, object::SectionedAddress::UndefSection}); 174 Printer << (error(ResOrErr) ? DIInliningInfo() : ResOrErr.get()); 175 } else if (OutputStyle == DIPrinter::OutputStyle::GNU) { 176 // With PrintFunctions == FunctionNameKind::LinkageName (default) 177 // and UseSymbolTable == true (also default), Symbolizer.symbolizeCode() 178 // may override the name of an inlined function with the name of the topmost 179 // caller function in the inlining chain. This contradicts the existing 180 // behavior of addr2line. Symbolizer.symbolizeInlinedCode() overrides only 181 // the topmost function, which suits our needs better. 182 auto ResOrErr = Symbolizer.symbolizeInlinedCode( 183 ModuleName, {Offset, object::SectionedAddress::UndefSection}); 184 if (!ResOrErr || ResOrErr->getNumberOfFrames() == 0) { 185 error(ResOrErr); 186 Printer << DILineInfo(); 187 } else { 188 Printer << ResOrErr->getFrame(0); 189 } 190 } else { 191 auto ResOrErr = Symbolizer.symbolizeCode( 192 ModuleName, {Offset, object::SectionedAddress::UndefSection}); 193 Printer << (error(ResOrErr) ? DILineInfo() : ResOrErr.get()); 194 } 195 if (OutputStyle == DIPrinter::OutputStyle::LLVM) 196 outs() << "\n"; 197 } 198 199 static void printHelp(StringRef ToolName, const SymbolizerOptTable &Tbl, 200 raw_ostream &OS) { 201 const char HelpText[] = " [options] addresses..."; 202 Tbl.PrintHelp(OS, (ToolName + HelpText).str().c_str(), 203 ToolName.str().c_str()); 204 // TODO Replace this with OptTable API once it adds extrahelp support. 205 OS << "\nPass @FILE as argument to read options from FILE.\n"; 206 } 207 208 static opt::InputArgList parseOptions(int Argc, char *Argv[], bool IsAddr2Line, 209 StringSaver &Saver, 210 SymbolizerOptTable &Tbl) { 211 StringRef ToolName = IsAddr2Line ? "llvm-addr2line" : "llvm-symbolizer"; 212 Tbl.setGroupedShortOptions(true); 213 // The environment variable specifies initial options which can be overridden 214 // by commnad line options. 215 Tbl.setInitialOptionsFromEnvironment(IsAddr2Line ? "LLVM_ADDR2LINE_OPTS" 216 : "LLVM_SYMBOLIZER_OPTS"); 217 bool HasError = false; 218 opt::InputArgList Args = 219 Tbl.parseArgs(Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { 220 errs() << ("error: " + Msg + "\n"); 221 HasError = true; 222 }); 223 if (HasError) 224 exit(1); 225 if (Args.hasArg(OPT_help)) { 226 printHelp(ToolName, Tbl, outs()); 227 exit(0); 228 } 229 if (Args.hasArg(OPT_version)) { 230 outs() << ToolName << '\n'; 231 cl::PrintVersionMessage(); 232 exit(0); 233 } 234 235 return Args; 236 } 237 238 template <typename T> 239 static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value) { 240 if (const opt::Arg *A = Args.getLastArg(ID)) { 241 StringRef V(A->getValue()); 242 if (!llvm::to_integer(V, Value, 0)) { 243 errs() << A->getSpelling() + 244 ": expected a non-negative integer, but got '" + V + "'"; 245 exit(1); 246 } 247 } else { 248 Value = 0; 249 } 250 } 251 252 static FunctionNameKind decideHowToPrintFunctions(const opt::InputArgList &Args, 253 bool IsAddr2Line) { 254 if (Args.hasArg(OPT_functions)) 255 return FunctionNameKind::LinkageName; 256 if (const opt::Arg *A = Args.getLastArg(OPT_functions_EQ)) 257 return StringSwitch<FunctionNameKind>(A->getValue()) 258 .Case("none", FunctionNameKind::None) 259 .Case("short", FunctionNameKind::ShortName) 260 .Default(FunctionNameKind::LinkageName); 261 return IsAddr2Line ? FunctionNameKind::None : FunctionNameKind::LinkageName; 262 } 263 264 int main(int argc, char **argv) { 265 InitLLVM X(argc, argv); 266 sys::InitializeCOMRAII COM(sys::COMThreadingMode::MultiThreaded); 267 268 bool IsAddr2Line = sys::path::stem(argv[0]).contains("addr2line"); 269 BumpPtrAllocator A; 270 StringSaver Saver(A); 271 SymbolizerOptTable Tbl; 272 opt::InputArgList Args = parseOptions(argc, argv, IsAddr2Line, Saver, Tbl); 273 274 LLVMSymbolizer::Options Opts; 275 uint64_t AdjustVMA; 276 unsigned SourceContextLines; 277 parseIntArg(Args, OPT_adjust_vma_EQ, AdjustVMA); 278 if (const opt::Arg *A = Args.getLastArg(OPT_basenames, OPT_relativenames)) { 279 Opts.PathStyle = 280 A->getOption().matches(OPT_basenames) 281 ? DILineInfoSpecifier::FileLineInfoKind::BaseNameOnly 282 : DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath; 283 } else { 284 Opts.PathStyle = DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath; 285 } 286 Opts.DebugFileDirectory = Args.getAllArgValues(OPT_debug_file_directory_EQ); 287 Opts.DefaultArch = Args.getLastArgValue(OPT_default_arch_EQ).str(); 288 Opts.Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, !IsAddr2Line); 289 Opts.DWPName = Args.getLastArgValue(OPT_dwp_EQ).str(); 290 Opts.FallbackDebugPath = 291 Args.getLastArgValue(OPT_fallback_debug_path_EQ).str(); 292 Opts.PrintFunctions = decideHowToPrintFunctions(Args, IsAddr2Line); 293 parseIntArg(Args, OPT_print_source_context_lines_EQ, SourceContextLines); 294 Opts.RelativeAddresses = Args.hasArg(OPT_relative_address); 295 Opts.UntagAddresses = 296 Args.hasFlag(OPT_untag_addresses, OPT_no_untag_addresses, !IsAddr2Line); 297 Opts.UseDIA = Args.hasArg(OPT_use_dia); 298 #if !defined(LLVM_ENABLE_DIA_SDK) 299 if (Opts.UseDIA) { 300 WithColor::warning() << "DIA not available; using native PDB reader\n"; 301 Opts.UseDIA = false; 302 } 303 #endif 304 Opts.UseSymbolTable = true; 305 306 for (const opt::Arg *A : Args.filtered(OPT_dsym_hint_EQ)) { 307 StringRef Hint(A->getValue()); 308 if (sys::path::extension(Hint) == ".dSYM") { 309 Opts.DsymHints.emplace_back(Hint); 310 } else { 311 errs() << "Warning: invalid dSYM hint: \"" << Hint 312 << "\" (must have the '.dSYM' extension).\n"; 313 } 314 } 315 316 auto OutputStyle = 317 IsAddr2Line ? DIPrinter::OutputStyle::GNU : DIPrinter::OutputStyle::LLVM; 318 if (const opt::Arg *A = Args.getLastArg(OPT_output_style_EQ)) { 319 OutputStyle = strcmp(A->getValue(), "GNU") == 0 320 ? DIPrinter::OutputStyle::GNU 321 : DIPrinter::OutputStyle::LLVM; 322 } 323 324 LLVMSymbolizer Symbolizer(Opts); 325 DIPrinter Printer(outs(), Opts.PrintFunctions != FunctionNameKind::None, 326 Args.hasArg(OPT_pretty_print), SourceContextLines, 327 Args.hasArg(OPT_verbose), OutputStyle); 328 329 std::vector<std::string> InputAddresses = Args.getAllArgValues(OPT_INPUT); 330 if (InputAddresses.empty()) { 331 const int kMaxInputStringLength = 1024; 332 char InputString[kMaxInputStringLength]; 333 334 while (fgets(InputString, sizeof(InputString), stdin)) { 335 // Strip newline characters. 336 std::string StrippedInputString(InputString); 337 llvm::erase_if(StrippedInputString, 338 [](char c) { return c == '\r' || c == '\n'; }); 339 symbolizeInput(Args, AdjustVMA, IsAddr2Line, OutputStyle, 340 StrippedInputString, Symbolizer, Printer); 341 outs().flush(); 342 } 343 } else { 344 for (StringRef Address : InputAddresses) 345 symbolizeInput(Args, AdjustVMA, IsAddr2Line, OutputStyle, Address, 346 Symbolizer, Printer); 347 } 348 349 return 0; 350 } 351