From f6b39f50d13379503df4de87a79a8c1b63d6e721 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Tue, 4 Aug 2020 08:51:24 -0700 Subject: [PATCH] [llvm-symbolizer] Switch command line parsing from llvm::cl to OptTable for the advantage outlined by D83639 ([OptTable] Support grouped short options) Some behavior changes: * -i={0,false} is removed. Use --no-inlines instead. * --demangle={0,false} is removed. Use --no-demangle instead * -untag-addresses={0,false} is removed. Use --no-untag-addresses instead Added a higher level API OptTable::parseArgs which handles optional initial options populated from an environment variable, expands response files recursively, and parses options. Reviewed By: jhenderson Differential Revision: https://reviews.llvm.org/D83530 --- docs/CommandGuide/llvm-symbolizer.rst | 16 +- include/llvm/Option/OptTable.h | 18 + include/llvm/Support/CommandLine.h | 8 + lib/Option/OptTable.cpp | 32 +- lib/Support/CommandLine.cpp | 16 + test/DebugInfo/debuglineinfo-path.ll | 6 +- test/tools/llvm-symbolizer/basic.s | 1 + test/tools/llvm-symbolizer/help.test | 4 +- .../llvm-symbolizer/output-style-inlined.test | 8 +- test/tools/llvm-symbolizer/split-dwarf.test | 6 +- .../llvm-symbolizer/unknown-argument.test | 12 + .../llvm-symbolizer/untag-addresses.test | 2 +- tools/llvm-symbolizer/CMakeLists.txt | 7 + tools/llvm-symbolizer/Opts.td | 60 +++ tools/llvm-symbolizer/llvm-symbolizer.cpp | 358 ++++++++---------- 15 files changed, 338 insertions(+), 216 deletions(-) create mode 100644 test/tools/llvm-symbolizer/unknown-argument.test create mode 100644 tools/llvm-symbolizer/Opts.td diff --git a/docs/CommandGuide/llvm-symbolizer.rst b/docs/CommandGuide/llvm-symbolizer.rst index 5c8465af04a..5c6a9511353 100644 --- a/docs/CommandGuide/llvm-symbolizer.rst +++ b/docs/CommandGuide/llvm-symbolizer.rst @@ -220,16 +220,16 @@ OPTIONS Show help and usage for this command. -.. option:: --help-list - - Show help and usage for this command without grouping the options into categories. - .. _llvm-symbolizer-opt-i: .. option:: --inlining, --inlines, -i If a source code location is in an inlined function, prints all the inlined - frames. Defaults to true. + frames. This is the default. + +.. option:: --no-inlines + + Don't print inlined frames. .. option:: --no-demangle @@ -267,17 +267,17 @@ OPTIONS foo() at /tmp/test.cpp:6:3 - $ llvm-symbolizer --output-style=LLVM --obj=inlined.elf 0x4004be 0x400486 -p -i=0 + $ llvm-symbolizer --output-style=LLVM --obj=inlined.elf 0x4004be 0x400486 -p --no-inlines main at /tmp/test.cpp:11:18 foo() at /tmp/test.cpp:6:3 - $ llvm-symbolizer --output-style=GNU --obj=inlined.elf 0x4004be 0x400486 -p -i=0 + $ llvm-symbolizer --output-style=GNU --obj=inlined.elf 0x4004be 0x400486 -p --no-inlines baz() at /tmp/test.cpp:11 foo() at /tmp/test.cpp:6 $ clang -g -fdebug-info-for-profiling test.cpp -o profiling.elf - $ llvm-symbolizer --output-style=GNU --obj=profiling.elf 0x401167 -p -i=0 + $ llvm-symbolizer --output-style=GNU --obj=profiling.elf 0x401167 -p --no-inlines main at /tmp/test.cpp:15 (discriminator 2) .. option:: --pretty-print, -p diff --git a/include/llvm/Option/OptTable.h b/include/llvm/Option/OptTable.h index b9984bed55a..1aabff0fd65 100644 --- a/include/llvm/Option/OptTable.h +++ b/include/llvm/Option/OptTable.h @@ -13,6 +13,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Option/OptSpecifier.h" +#include "llvm/Support/StringSaver.h" #include #include #include @@ -20,6 +21,7 @@ namespace llvm { class raw_ostream; +template class function_ref; namespace opt { @@ -60,6 +62,7 @@ private: std::vector OptionInfos; bool IgnoreCase; bool GroupedShortOptions = false; + const char *EnvVar = nullptr; unsigned TheInputOptionID = 0; unsigned TheUnknownOptionID = 0; @@ -123,6 +126,9 @@ public: return getInfo(id).MetaVar; } + /// Specify the environment variable where initial options should be read. + void setInitialOptionsFromEnvironment(const char *E) { EnvVar = E; } + /// Support grouped short options. e.g. -ab represents -a -b. void setGroupedShortOptions(bool Value) { GroupedShortOptions = Value; } @@ -219,6 +225,18 @@ public: unsigned &MissingArgCount, unsigned FlagsToInclude = 0, unsigned FlagsToExclude = 0) const; + /// A convenience helper which handles optional initial options populated from + /// an environment variable, expands response files recursively and parses + /// options. + /// + /// \param ErrorFn - Called on a formatted error message for missing arguments + /// or unknown options. + /// \return An InputArgList; on error this will contain all the options which + /// could be parsed. + InputArgList parseArgs(int Argc, char *const *Argv, OptSpecifier Unknown, + StringSaver &Saver, + function_ref ErrorFn) const; + /// Render the help text for an option table. /// /// \param OS - The stream to write the help text to. diff --git a/include/llvm/Support/CommandLine.h b/include/llvm/Support/CommandLine.h index 62e44aeefe9..38c58808006 100644 --- a/include/llvm/Support/CommandLine.h +++ b/include/llvm/Support/CommandLine.h @@ -2085,6 +2085,14 @@ bool ExpandResponseFiles( llvm::vfs::FileSystem &FS = *llvm::vfs::getRealFileSystem(), llvm::Optional CurrentDir = llvm::None); +/// A convenience helper which concatenates the options specified by the +/// environment variable EnvVar and command line options, then expands response +/// files recursively. The tokenizer is a predefined GNU or Windows one. +/// \return true if all @files were expanded successfully or there were none. +bool expandResponseFiles(int Argc, const char *const *Argv, const char *EnvVar, + StringSaver &Saver, + SmallVectorImpl &NewArgv); + /// Mark all options not part of this category as cl::ReallyHidden. /// /// \param Category the category of options to keep displaying diff --git a/lib/Option/OptTable.cpp b/lib/Option/OptTable.cpp index 16404d3d810..2b7fcf55a57 100644 --- a/lib/Option/OptTable.cpp +++ b/lib/Option/OptTable.cpp @@ -6,14 +6,15 @@ // //===----------------------------------------------------------------------===// +#include "llvm/Option/OptTable.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Option/Arg.h" #include "llvm/Option/ArgList.h" -#include "llvm/Option/Option.h" #include "llvm/Option/OptSpecifier.h" -#include "llvm/Option/OptTable.h" +#include "llvm/Option/Option.h" +#include "llvm/Support/CommandLine.h" // for expandResponseFiles #include "llvm/Support/Compiler.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/raw_ostream.h" @@ -490,6 +491,33 @@ InputArgList OptTable::ParseArgs(ArrayRef ArgArr, return Args; } +InputArgList OptTable::parseArgs(int Argc, char *const *Argv, + OptSpecifier Unknown, StringSaver &Saver, + function_ref ErrorFn) const { + SmallVector NewArgv; + // The environment variable specifies initial options which can be overridden + // by commnad line options. + cl::expandResponseFiles(Argc, Argv, EnvVar, Saver, NewArgv); + + unsigned MAI, MAC; + opt::InputArgList Args = ParseArgs(makeArrayRef(NewArgv), MAI, MAC); + if (MAC) + ErrorFn((Twine(Args.getArgString(MAI)) + ": missing argument").str()); + + // For each unknwon option, call ErrorFn with a formatted error message. The + // message includes a suggested alternative option spelling if available. + std::string Nearest; + for (const opt::Arg *A : Args.filtered(Unknown)) { + std::string Spelling = A->getAsString(Args); + if (findNearest(Spelling, Nearest) > 1) + ErrorFn("unknown argument '" + A->getAsString(Args) + "'"); + else + ErrorFn("unknown argument '" + A->getAsString(Args) + + "', did you mean '" + Nearest + "'?"); + } + return Args; +} + static std::string getOptionHelpName(const OptTable &Opts, OptSpecifier Id) { const Option O = Opts.getOption(Id); std::string Name = O.getPrefixedName(); diff --git a/lib/Support/CommandLine.cpp b/lib/Support/CommandLine.cpp index 4fba6a9ada2..e53421a277f 100644 --- a/lib/Support/CommandLine.cpp +++ b/lib/Support/CommandLine.cpp @@ -1251,6 +1251,22 @@ bool cl::ExpandResponseFiles(StringSaver &Saver, TokenizerCallback Tokenizer, return AllExpanded; } +bool cl::expandResponseFiles(int Argc, const char *const *Argv, + const char *EnvVar, StringSaver &Saver, + SmallVectorImpl &NewArgv) { + auto Tokenize = Triple(sys::getProcessTriple()).isOSWindows() + ? cl::TokenizeWindowsCommandLine + : cl::TokenizeGNUCommandLine; + // The environment variable specifies initial options. + if (EnvVar) + if (llvm::Optional EnvValue = sys::Process::GetEnv(EnvVar)) + Tokenize(*EnvValue, Saver, NewArgv, /*MarkEOLs=*/false); + + // Command line options can override the environment variable. + NewArgv.append(Argv + 1, Argv + Argc); + return ExpandResponseFiles(Saver, Tokenize, NewArgv); +} + bool cl::readConfigFile(StringRef CfgFile, StringSaver &Saver, SmallVectorImpl &Argv) { SmallString<128> AbsPath; diff --git a/test/DebugInfo/debuglineinfo-path.ll b/test/DebugInfo/debuglineinfo-path.ll index 4c5f43aa03f..ea32aecf5d8 100644 --- a/test/DebugInfo/debuglineinfo-path.ll +++ b/test/DebugInfo/debuglineinfo-path.ll @@ -8,9 +8,9 @@ ; RUN: llvm-nm --radix=o %t | grep posix_absolute_func > %t.posix_absolute_func ; RUN: llvm-nm --radix=o %t | grep posix_relative_func > %t.posix_relative_func ; RUN: llvm-nm --radix=o %t | grep win_func > %t.win_func -; RUN: llvm-symbolizer --functions=linkage --inlining --demangle=false --obj %t < %t.posix_absolute_func | FileCheck %s --check-prefix=POSIX_A -; RUN: llvm-symbolizer --functions=linkage --inlining --demangle=false --obj %t < %t.posix_relative_func | FileCheck %s --check-prefix=POSIX_R -; RUN: llvm-symbolizer --functions=linkage --inlining --demangle=false --obj %t < %t.win_func | FileCheck %s --check-prefix=WIN +; RUN: llvm-symbolizer --functions=linkage --inlining --no-demangle --obj %t < %t.posix_absolute_func | FileCheck %s --check-prefix=POSIX_A +; RUN: llvm-symbolizer --functions=linkage --inlining --no-demangle --obj %t < %t.posix_relative_func | FileCheck %s --check-prefix=POSIX_R +; RUN: llvm-symbolizer --functions=linkage --inlining --no-demangle --obj %t < %t.win_func | FileCheck %s --check-prefix=WIN ;POSIX_A: posix_absolute_func ;POSIX_A: /absolute/posix/path{{[\/]}}posix.c diff --git a/test/tools/llvm-symbolizer/basic.s b/test/tools/llvm-symbolizer/basic.s index b9d5c814024..1a28f14f3eb 100644 --- a/test/tools/llvm-symbolizer/basic.s +++ b/test/tools/llvm-symbolizer/basic.s @@ -17,6 +17,7 @@ foo: # Check --obj aliases --exe, -e # RUN: llvm-symbolizer 0xa 0xb --exe=%t.o | FileCheck %s +# RUN: llvm-symbolizer 0xa 0xb --exe %t.o | FileCheck %s # RUN: llvm-symbolizer 0xa 0xb -e %t.o | FileCheck %s # RUN: llvm-symbolizer 0xa 0xb -e=%t.o | FileCheck %s # RUN: llvm-symbolizer 0xa 0xb -e%t.o | FileCheck %s diff --git a/test/tools/llvm-symbolizer/help.test b/test/tools/llvm-symbolizer/help.test index 12339463631..c05760f6186 100644 --- a/test/tools/llvm-symbolizer/help.test +++ b/test/tools/llvm-symbolizer/help.test @@ -4,9 +4,9 @@ RUN: llvm-addr2line -h | FileCheck %s --check-prefix=ADDR2LINE RUN: llvm-addr2line --help | FileCheck %s --check-prefix=ADDR2LINE SYMBOLIZER: OVERVIEW: llvm-symbolizer -SYMBOLIZER: USAGE: llvm-symbolizer{{(.exe)?}} [options] ... +SYMBOLIZER: USAGE: llvm-symbolizer{{(.exe)?}} [options] addresses... SYMBOLIZER: @FILE ADDR2LINE: OVERVIEW: llvm-addr2line -ADDR2LINE: USAGE: llvm-addr2line{{(.exe)?}} [options] ... +ADDR2LINE: USAGE: llvm-addr2line{{(.exe)?}} [options] addresses... ADDR2LINE: @FILE diff --git a/test/tools/llvm-symbolizer/output-style-inlined.test b/test/tools/llvm-symbolizer/output-style-inlined.test index 7aa9c6b4059..daa9584a3f4 100644 --- a/test/tools/llvm-symbolizer/output-style-inlined.test +++ b/test/tools/llvm-symbolizer/output-style-inlined.test @@ -1,16 +1,16 @@ -This test checks that when inlined frames are not shown (-i=0) and the output +This test checks that when inlined frames are not shown (--no-inlines) and the output style is set to GNU (--output-style=GNU) the name of an inlined function is not replaced with the name of the top caller function. At the same time, the current behavior of llvm-symbolizer is preserved with --output-style=LLVM or when the option is not specified. -RUN: llvm-symbolizer -i=0 -e %p/Inputs/addr.exe 0x40054d \ +RUN: llvm-symbolizer --no-inlines -e %p/Inputs/addr.exe 0x40054d \ RUN: | FileCheck %s --check-prefix=LLVM --implicit-check-not=inctwo -RUN: llvm-symbolizer --output-style=LLVM -i=0 -e %p/Inputs/addr.exe 0x40054d \ +RUN: llvm-symbolizer --output-style=LLVM --no-inlines -e %p/Inputs/addr.exe 0x40054d \ RUN: | FileCheck %s --check-prefix=LLVM --implicit-check-not=inctwo -RUN: llvm-symbolizer --output-style=GNU -i=0 -e %p/Inputs/addr.exe 0x40054d \ +RUN: llvm-symbolizer --output-style=GNU --no-inlines -e %p/Inputs/addr.exe 0x40054d \ RUN: | FileCheck %s --check-prefix=GNU --implicit-check-not=main RUN: llvm-addr2line -f -e %p/Inputs/addr.exe 0x40054d \ diff --git a/test/tools/llvm-symbolizer/split-dwarf.test b/test/tools/llvm-symbolizer/split-dwarf.test index af758acd7e0..e129d2ede3a 100644 --- a/test/tools/llvm-symbolizer/split-dwarf.test +++ b/test/tools/llvm-symbolizer/split-dwarf.test @@ -4,14 +4,14 @@ RUN: mkdir -p %t RUN: cp %p/Inputs/split-dwarf-test.dwo %t RUN: cd %t -RUN: llvm-symbolizer --functions=linkage --inlining --demangle=false \ +RUN: llvm-symbolizer --functions=linkage --inlining --no-demangle \ RUN: --obj=%p/Inputs/split-dwarf-test 0x400504 0x4004f4 | FileCheck --check-prefixes=SPLIT,DWO %s Ensure we get the same results in the absence of gmlt-like data in the executable but the presence of a .dwo file RUN: echo "%p/Inputs/split-dwarf-test-nogmlt 0x400504" >> %t.input RUN: echo "%p/Inputs/split-dwarf-test-nogmlt 0x4004f4" >> %t.input -RUN: llvm-symbolizer --functions=linkage --inlining --demangle=false \ +RUN: llvm-symbolizer --functions=linkage --inlining --no-demangle \ RUN: --default-arch=i386 --obj=%p/Inputs/split-dwarf-test-nogmlt 0x400504 0x4004f4 | FileCheck --check-prefixes=SPLIT,DWO %s Ensure we get gmlt like results in the absence of a .dwo file but the presence of gmlt-like data in the executable @@ -19,7 +19,7 @@ Ensure we get gmlt like results in the absence of a .dwo file but the presence o RUN: rm %t/split-dwarf-test.dwo RUN: echo "%p/Inputs/split-dwarf-test 0x400504" >> %t.input RUN: echo "%p/Inputs/split-dwarf-test 0x4004f4" >> %t.input -RUN: llvm-symbolizer --functions=linkage --inlining --demangle=false \ +RUN: llvm-symbolizer --functions=linkage --inlining --no-demangle \ RUN: --default-arch=i386 --obj=%p/Inputs/split-dwarf-test 0x400504 0x4004f4 | FileCheck --check-prefixes=SPLIT,NODWO %s DWO: _Z2f2v diff --git a/test/tools/llvm-symbolizer/unknown-argument.test b/test/tools/llvm-symbolizer/unknown-argument.test new file mode 100644 index 00000000000..a697f1a2762 --- /dev/null +++ b/test/tools/llvm-symbolizer/unknown-argument.test @@ -0,0 +1,12 @@ +# RUN: not llvm-symbolizer -x --flag 2>&1 | FileCheck %s + +# CHECK: error: unknown argument '-x'{{$}} +# CHECK-NEXT: error: unknown argument '--flag'{{$}} + +# RUN: not llvm-symbolizer --inline 2>&1 | FileCheck %s --check-prefix=SUGGEST + +# SUGGEST: error: unknown argument '--inline', did you mean '--inlines'? + +# RUN: not llvm-symbolizer -e 2>&1 | FileCheck %s --check-prefix=MISSING + +# MISSING: error: -e: missing argument diff --git a/test/tools/llvm-symbolizer/untag-addresses.test b/test/tools/llvm-symbolizer/untag-addresses.test index 3799f306cab..f37f257d2d2 100644 --- a/test/tools/llvm-symbolizer/untag-addresses.test +++ b/test/tools/llvm-symbolizer/untag-addresses.test @@ -2,7 +2,7 @@ # RUN: llvm-mc -filetype=obj -triple=x86_64-pc-linux %s -o %t.o # RUN: echo DATA %t.o 0 | llvm-symbolizer | FileCheck --check-prefix=UNTAG %s -# RUN: echo DATA %t.o 0 | llvm-symbolizer -untag-addresses=0 | FileCheck --check-prefix=NOUNTAG %s +# RUN: echo DATA %t.o 0 | llvm-symbolizer --no-untag-addresses | FileCheck --check-prefix=NOUNTAG %s # RUN: echo DATA %t.o 0 | llvm-addr2line | FileCheck --check-prefix=NOUNTAG %s # UNTAG: foo diff --git a/tools/llvm-symbolizer/CMakeLists.txt b/tools/llvm-symbolizer/CMakeLists.txt index 13da12fba7b..c112e344da7 100644 --- a/tools/llvm-symbolizer/CMakeLists.txt +++ b/tools/llvm-symbolizer/CMakeLists.txt @@ -3,17 +3,24 @@ # This means that we need LLVM libraries to be compiled for these # targets as well. Currently, there is no support for such a build strategy. +set(LLVM_TARGET_DEFINITIONS Opts.td) +tablegen(LLVM Opts.inc -gen-opt-parser-defs) +add_public_tablegen_target(SymbolizerOptsTableGen) + set(LLVM_LINK_COMPONENTS DebugInfoDWARF DebugInfoPDB Demangle Object + Option Support Symbolize ) add_llvm_tool(llvm-symbolizer llvm-symbolizer.cpp + DEPENDS + SymbolizerOptsTableGen ) add_llvm_tool_symlink(llvm-addr2line llvm-symbolizer) diff --git a/tools/llvm-symbolizer/Opts.td b/tools/llvm-symbolizer/Opts.td new file mode 100644 index 00000000000..d83b796635b --- /dev/null +++ b/tools/llvm-symbolizer/Opts.td @@ -0,0 +1,60 @@ +include "llvm/Option/OptParser.td" + +multiclass B { + def NAME: Flag<["--", "-"], name>, HelpText; + def no_ # NAME: Flag<["--", "-"], "no-" # name>, HelpText; +} + +multiclass Eq { + def NAME #_EQ : Joined<["--", "-"], name #"=">, + HelpText; + def : Separate<["--", "-"], name>, Alias(NAME #_EQ)>; +} + +class F: Flag<["--", "-"], name>, HelpText; + +def addresses : F<"addresses", "Show address before line information">; +defm adjust_vma + : Eq<"adjust-vma", "Add specified offset to object file addresses">, + MetaVarName<"">; +def basenames : Flag<["--"], "basenames">, HelpText<"Strip directory names from paths">; +defm debug_file_directory : Eq<"debug-file-directory", "Path to directory where to look for debug files">, MetaVarName<"">; +defm default_arch : Eq<"default-arch", "Default architecture (for multi-arch objects)">; +defm demangle : B<"demangle", "Demangle function names", "Don't demangle function names">; +def functions : F<"functions", "Print function name for a given address">; +def functions_EQ : Joined<["--"], "functions=">, HelpText<"Print function name for a given address">, Values<"none,short,linkage">; +def help : F<"help", "Display this help">; +defm dwp : Eq<"dwp", "Path to DWP file to be use for any split CUs">, MetaVarName<"">; +defm dsym_hint : Eq<"dsym-hint", "Path to .dSYM bundles to search for debug info for the object files">, MetaVarName<"">; +defm fallback_debug_path : Eq<"fallback-debug-path", "Fallback path for debug binaries">, MetaVarName<"">; +defm inlines : B<"inlines", "Print all inlined frames for a given address", + "Do not print inlined frames">; +defm obj + : Eq<"obj", "Path to object file to be symbolized (if not provided, " + "object file should be specified for each input line)">, MetaVarName<"">; +defm output_style + : Eq<"output-style", "Specify print style. Supported styles: LLVM, GNU">, + MetaVarName<"style">, + Values<"LLVM,GNU">; +def pretty_print : F<"pretty-print", "Make the output more human friendly">; +defm print_source_context_lines : Eq<"print-source-context-lines", "Print N lines of source file context">; +def relative_address : F<"relative-address", "Interpret addresses as addresses relative to the image base">; +def relativenames : F<"relativenames", "Strip the compilation directory from paths">; +defm untag_addresses : B<"untag-addresses", "", "Remove memory tags from addresses before symbolization">; +def use_native_pdb_reader : F<"use-native-pdb-reader", "Use native PDB functionality">; +def verbose : F<"verbose", "Print verbose line info">; + +def : Flag<["-"], "a">, Alias, HelpText<"Alias for --addresses">; +def : F<"print-address", "Alias for --addresses">, Alias; +def : Flag<["-"], "C">, Alias, HelpText<"Alias for --demangle">; +def : Joined<["--"], "exe=">, Alias, HelpText<"Alias for --obj">, MetaVarName<"">; +def : Separate<["--"], "exe">, Alias, HelpText<"Alias for --obj">, MetaVarName<"">; +def : JoinedOrSeparate<["-"], "e">, Alias, HelpText<"Alias for --obj">, MetaVarName<"">; +def : Joined<["-"], "e=">, Alias, HelpText<"Alias for --obj">, MetaVarName<"">; +def : Flag<["-"], "f">, Alias, HelpText<"Alias for --functions">; +def : Joined<["-"], "f=">, Alias, HelpText<"Alias for --functions=">; +def : Flag<["-"], "h">, Alias; +def : Flag<["-"], "i">, Alias, HelpText<"Alias for --inlines">; +def : F<"inlining", "Alias for --inlines">, Alias; +def : Flag<["-"], "p">, Alias, HelpText<"Alias for --pretty-print">; +def : Flag<["-"], "s">, Alias, HelpText<"Alias for --basenames">; diff --git a/tools/llvm-symbolizer/llvm-symbolizer.cpp b/tools/llvm-symbolizer/llvm-symbolizer.cpp index 6a702c64a10..2101d645dff 100644 --- a/tools/llvm-symbolizer/llvm-symbolizer.cpp +++ b/tools/llvm-symbolizer/llvm-symbolizer.cpp @@ -14,15 +14,20 @@ // //===----------------------------------------------------------------------===// +#include "Opts.inc" #include "llvm/ADT/StringRef.h" #include "llvm/DebugInfo/Symbolize/DIPrinter.h" #include "llvm/DebugInfo/Symbolize/Symbolize.h" +#include "llvm/Option/Arg.h" +#include "llvm/Option/ArgList.h" +#include "llvm/Option/Option.h" #include "llvm/Support/COM.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" +#include "llvm/Support/StringSaver.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -32,144 +37,42 @@ using namespace llvm; using namespace symbolize; -static cl::opt -ClUseSymbolTable("use-symbol-table", cl::init(true), - cl::desc("Prefer names in symbol table to names " - "in debug info")); +namespace { +enum ID { + OPT_INVALID = 0, // This is not an option ID. +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + OPT_##ID, +#include "Opts.inc" +#undef OPTION +}; -static cl::opt ClPrintFunctions( - "functions", cl::init(FunctionNameKind::LinkageName), - cl::desc("Print function name for a given address"), cl::ValueOptional, - cl::values(clEnumValN(FunctionNameKind::None, "none", "omit function name"), - clEnumValN(FunctionNameKind::ShortName, "short", - "print short function name"), - clEnumValN(FunctionNameKind::LinkageName, "linkage", - "print function linkage name"), - // Sentinel value for unspecified value. - clEnumValN(FunctionNameKind::LinkageName, "", ""))); -static cl::alias ClPrintFunctionsShort("f", cl::desc("Alias for -functions"), - cl::NotHidden, cl::Grouping, - cl::aliasopt(ClPrintFunctions)); +#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; +#include "Opts.inc" +#undef PREFIX -static cl::opt - ClUseRelativeAddress("relative-address", cl::init(false), - cl::desc("Interpret addresses as relative addresses"), - cl::ReallyHidden); +static const opt::OptTable::Info InfoTable[] = { +#define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ + HELPTEXT, METAVAR, VALUES) \ + { \ + PREFIX, NAME, HELPTEXT, \ + METAVAR, OPT_##ID, opt::Option::KIND##Class, \ + PARAM, FLAGS, OPT_##GROUP, \ + OPT_##ALIAS, ALIASARGS, VALUES}, +#include "Opts.inc" +#undef OPTION +}; -static cl::opt ClUntagAddresses( - "untag-addresses", cl::init(true), - cl::desc("Remove memory tags from addresses before symbolization")); - -static cl::opt - ClPrintInlining("inlining", cl::init(true), - cl::desc("Print all inlined frames for a given address")); -static cl::alias - ClPrintInliningAliasI("i", cl::desc("Alias for -inlining"), - cl::NotHidden, cl::aliasopt(ClPrintInlining), - cl::Grouping); -static cl::alias - ClPrintInliningAliasInlines("inlines", cl::desc("Alias for -inlining"), - cl::NotHidden, cl::aliasopt(ClPrintInlining)); - -static cl::opt ClBasenames("basenames", cl::init(false), - cl::desc("Strip directory names from paths")); -static cl::alias ClBasenamesShort("s", cl::desc("Alias for -basenames"), - cl::NotHidden, cl::aliasopt(ClBasenames)); - -static cl::opt - ClRelativenames("relativenames", cl::init(false), - cl::desc("Strip the compilation directory from paths")); - -static cl::opt -ClDemangle("demangle", cl::init(true), cl::desc("Demangle function names")); -static cl::alias -ClDemangleShort("C", cl::desc("Alias for -demangle"), - cl::NotHidden, cl::aliasopt(ClDemangle), cl::Grouping); -static cl::opt -ClNoDemangle("no-demangle", cl::init(false), - cl::desc("Don't demangle function names")); - -static cl::opt ClDefaultArch("default-arch", cl::init(""), - cl::desc("Default architecture " - "(for multi-arch objects)")); - -static cl::opt -ClBinaryName("obj", cl::init(""), - cl::desc("Path to object file to be symbolized (if not provided, " - "object file should be specified for each input line)")); -static cl::alias -ClBinaryNameAliasExe("exe", cl::desc("Alias for -obj"), - cl::NotHidden, cl::aliasopt(ClBinaryName)); -static cl::alias ClBinaryNameAliasE("e", cl::desc("Alias for -obj"), - cl::NotHidden, cl::Grouping, cl::Prefix, - cl::aliasopt(ClBinaryName)); - -static cl::opt - ClDwpName("dwp", cl::init(""), - cl::desc("Path to DWP file to be use for any split CUs")); - -static cl::list -ClDsymHint("dsym-hint", cl::ZeroOrMore, - cl::desc("Path to .dSYM bundles to search for debug info for the " - "object files")); - -static cl::opt -ClPrintAddress("print-address", cl::init(false), - cl::desc("Show address before line information")); -static cl::alias -ClPrintAddressAliasAddresses("addresses", cl::desc("Alias for -print-address"), - cl::NotHidden, cl::aliasopt(ClPrintAddress)); -static cl::alias -ClPrintAddressAliasA("a", cl::desc("Alias for -print-address"), - cl::NotHidden, cl::aliasopt(ClPrintAddress), cl::Grouping); - -static cl::opt - ClPrettyPrint("pretty-print", cl::init(false), - cl::desc("Make the output more human friendly")); -static cl::alias ClPrettyPrintShort("p", cl::desc("Alias for -pretty-print"), - cl::NotHidden, - cl::aliasopt(ClPrettyPrint), cl::Grouping); - -static cl::opt ClPrintSourceContextLines( - "print-source-context-lines", cl::init(0), - cl::desc("Print N number of source file context")); - -static cl::opt ClVerbose("verbose", cl::init(false), - cl::desc("Print verbose line info")); - -static cl::opt - ClAdjustVMA("adjust-vma", cl::init(0), cl::value_desc("offset"), - cl::desc("Add specified offset to object file addresses")); +class SymbolizerOptTable : public opt::OptTable { +public: + SymbolizerOptTable() : OptTable(InfoTable, true) {} +}; +} // namespace static cl::list ClInputAddresses(cl::Positional, cl::desc("..."), cl::ZeroOrMore); -static cl::opt - ClFallbackDebugPath("fallback-debug-path", cl::init(""), - cl::desc("Fallback path for debug binaries.")); - -static cl::list - ClDebugFileDirectory("debug-file-directory", cl::ZeroOrMore, - cl::value_desc("dir"), - cl::desc("Path to directory where to look for debug " - "files.")); - -static cl::opt - ClOutputStyle("output-style", cl::init(DIPrinter::OutputStyle::LLVM), - cl::desc("Specify print style"), - cl::values(clEnumValN(DIPrinter::OutputStyle::LLVM, "LLVM", - "LLVM default style"), - clEnumValN(DIPrinter::OutputStyle::GNU, "GNU", - "GNU addr2line style"))); - -static cl::opt - ClUseNativePDBReader("use-native-pdb-reader", cl::init(0), - cl::desc("Use native PDB functionality")); - -static cl::extrahelp - HelpResponse("\nPass @FILE as argument to read options from FILE.\n"); - template static bool error(Expected &ResOrErr) { if (ResOrErr) @@ -185,7 +88,8 @@ enum class Command { Frame, }; -static bool parseCommand(bool IsAddr2Line, StringRef InputString, Command &Cmd, +static bool parseCommand(StringRef BinaryName, bool IsAddr2Line, + StringRef InputString, Command &Cmd, std::string &ModuleName, uint64_t &ModuleOffset) { const char kDelimiters[] = " \n\r"; ModuleName = ""; @@ -201,7 +105,7 @@ static bool parseCommand(bool IsAddr2Line, StringRef InputString, Command &Cmd, } const char *Pos = InputString.data(); // Skip delimiters and parse input filename (if needed). - if (ClBinaryName.empty()) { + if (BinaryName.empty()) { Pos += strspn(Pos, kDelimiters); if (*Pos == '"' || *Pos == '\'') { char Quote = *Pos; @@ -217,7 +121,7 @@ static bool parseCommand(bool IsAddr2Line, StringRef InputString, Command &Cmd, Pos += NameLength; } } else { - ModuleName = ClBinaryName; + ModuleName = BinaryName.str(); } // Skip delimiters and parse module offset. Pos += strspn(Pos, kDelimiters); @@ -230,24 +134,26 @@ static bool parseCommand(bool IsAddr2Line, StringRef InputString, Command &Cmd, return !Offset.getAsInteger(IsAddr2Line ? 16 : 0, ModuleOffset); } -static void symbolizeInput(bool IsAddr2Line, StringRef InputString, - LLVMSymbolizer &Symbolizer, DIPrinter &Printer) { +static void symbolizeInput(const opt::InputArgList &Args, uint64_t AdjustVMA, + bool IsAddr2Line, DIPrinter::OutputStyle OutputStyle, + StringRef InputString, LLVMSymbolizer &Symbolizer, + DIPrinter &Printer) { Command Cmd; std::string ModuleName; uint64_t Offset = 0; - if (!parseCommand(IsAddr2Line, StringRef(InputString), Cmd, ModuleName, - Offset)) { + if (!parseCommand(Args.getLastArgValue(OPT_obj_EQ), IsAddr2Line, + StringRef(InputString), Cmd, ModuleName, Offset)) { outs() << InputString << "\n"; return; } - if (ClPrintAddress) { + if (Args.hasArg(OPT_addresses)) { outs() << "0x"; outs().write_hex(Offset); - StringRef Delimiter = ClPrettyPrint ? ": " : "\n"; + StringRef Delimiter = Args.hasArg(OPT_pretty_print) ? ": " : "\n"; outs() << Delimiter; } - Offset -= ClAdjustVMA; + Offset -= AdjustVMA; if (Cmd == Command::Data) { auto ResOrErr = Symbolizer.symbolizeData( ModuleName, {Offset, object::SectionedAddress::UndefSection}); @@ -261,13 +167,13 @@ static void symbolizeInput(bool IsAddr2Line, StringRef InputString, if (ResOrErr->empty()) outs() << "??\n"; } - } else if (ClPrintInlining) { + } else if (Args.hasFlag(OPT_inlines, OPT_no_inlines, !IsAddr2Line)) { auto ResOrErr = Symbolizer.symbolizeInlinedCode( ModuleName, {Offset, object::SectionedAddress::UndefSection}); Printer << (error(ResOrErr) ? DIInliningInfo() : ResOrErr.get()); - } else if (ClOutputStyle == DIPrinter::OutputStyle::GNU) { - // With ClPrintFunctions == FunctionNameKind::LinkageName (default) - // and ClUseSymbolTable == true (also default), Symbolizer.symbolizeCode() + } else if (OutputStyle == DIPrinter::OutputStyle::GNU) { + // With PrintFunctions == FunctionNameKind::LinkageName (default) + // and UseSymbolTable == true (also default), Symbolizer.symbolizeCode() // may override the name of an inlined function with the name of the topmost // caller function in the inlining chain. This contradicts the existing // behavior of addr2line. Symbolizer.symbolizeInlinedCode() overrides only @@ -280,67 +186,131 @@ static void symbolizeInput(bool IsAddr2Line, StringRef InputString, ModuleName, {Offset, object::SectionedAddress::UndefSection}); Printer << (error(ResOrErr) ? DILineInfo() : ResOrErr.get()); } - if (ClOutputStyle == DIPrinter::OutputStyle::LLVM) + if (OutputStyle == DIPrinter::OutputStyle::LLVM) outs() << "\n"; } +static void printHelp(bool IsAddr2Line, const SymbolizerOptTable &Tbl, + raw_ostream &OS) { + StringRef ToolName = IsAddr2Line ? "llvm-addr2line" : "llvm-symbolizer"; + const char HelpText[] = " [options] addresses..."; + Tbl.PrintHelp(OS, (ToolName + HelpText).str().c_str(), + ToolName.str().c_str()); + // TODO Replace this with OptTable API once it adds extrahelp support. + OS << "\nPass @FILE as argument to read options from FILE.\n"; +} + +static opt::InputArgList parseOptions(int Argc, char *Argv[], bool IsAddr2Line, + StringSaver &Saver, + SymbolizerOptTable &Tbl) { + Tbl.setGroupedShortOptions(true); + // The environment variable specifies initial options which can be overridden + // by commnad line options. + Tbl.setInitialOptionsFromEnvironment(IsAddr2Line ? "LLVM_ADDR2LINE_OPTS" + : "LLVM_SYMBOLIZER_OPTS"); + bool HasError = false; + opt::InputArgList Args = + Tbl.parseArgs(Argc, Argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { + errs() << ("error: " + Msg + "\n"); + HasError = true; + }); + if (HasError) + exit(1); + if (Args.hasArg(OPT_help)) { + printHelp(IsAddr2Line, Tbl, outs()); + exit(0); + } + + return Args; +} + +template +static void parseIntArg(const opt::InputArgList &Args, int ID, T &Value) { + if (const opt::Arg *A = Args.getLastArg(ID)) { + StringRef V(A->getValue()); + if (!llvm::to_integer(V, Value, 0)) { + errs() << A->getSpelling() + + ": expected a non-negative integer, but got '" + V + "'"; + exit(1); + } + } else { + Value = 0; + } +} + +static FunctionNameKind decideHowToPrintFunctions(const opt::InputArgList &Args, + bool IsAddr2Line) { + if (Args.hasArg(OPT_functions)) + return FunctionNameKind::LinkageName; + if (const opt::Arg *A = Args.getLastArg(OPT_functions_EQ)) + return StringSwitch(A->getValue()) + .Case("none", FunctionNameKind::None) + .Case("short", FunctionNameKind::ShortName) + .Default(FunctionNameKind::LinkageName); + return IsAddr2Line ? FunctionNameKind::None : FunctionNameKind::LinkageName; +} + int main(int argc, char **argv) { InitLLVM X(argc, argv); + sys::InitializeCOMRAII COM(sys::COMThreadingMode::MultiThreaded); bool IsAddr2Line = sys::path::stem(argv[0]).contains("addr2line"); - - if (IsAddr2Line) { - ClDemangle.setInitialValue(false); - ClPrintFunctions.setInitialValue(FunctionNameKind::None); - ClPrintInlining.setInitialValue(false); - ClUntagAddresses.setInitialValue(false); - ClOutputStyle.setInitialValue(DIPrinter::OutputStyle::GNU); - } - - llvm::sys::InitializeCOMRAII COM(llvm::sys::COMThreadingMode::MultiThreaded); - cl::ParseCommandLineOptions( - argc, argv, IsAddr2Line ? "llvm-addr2line\n" : "llvm-symbolizer\n", - /*Errs=*/nullptr, - IsAddr2Line ? "LLVM_ADDR2LINE_OPTS" : "LLVM_SYMBOLIZER_OPTS"); - - // If both --demangle and --no-demangle are specified then pick the last one. - if (ClNoDemangle.getPosition() > ClDemangle.getPosition()) - ClDemangle = !ClNoDemangle; + BumpPtrAllocator A; + StringSaver Saver(A); + SymbolizerOptTable Tbl; + opt::InputArgList Args = parseOptions(argc, argv, IsAddr2Line, Saver, Tbl); LLVMSymbolizer::Options Opts; - Opts.PrintFunctions = ClPrintFunctions; - Opts.UseSymbolTable = ClUseSymbolTable; - Opts.Demangle = ClDemangle; - Opts.RelativeAddresses = ClUseRelativeAddress; - Opts.UntagAddresses = ClUntagAddresses; - Opts.DefaultArch = ClDefaultArch; - Opts.FallbackDebugPath = ClFallbackDebugPath; - Opts.DWPName = ClDwpName; - Opts.DebugFileDirectory = ClDebugFileDirectory; - Opts.UseNativePDBReader = ClUseNativePDBReader; - Opts.PathStyle = DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath; - // If both --basenames and --relativenames are specified then pick the last - // one. - if (ClBasenames.getPosition() > ClRelativenames.getPosition()) - Opts.PathStyle = DILineInfoSpecifier::FileLineInfoKind::BaseNameOnly; - else if (ClRelativenames) - Opts.PathStyle = DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath; + uint64_t AdjustVMA; + unsigned SourceContextLines; + parseIntArg(Args, OPT_adjust_vma_EQ, AdjustVMA); + if (const opt::Arg *A = Args.getLastArg(OPT_basenames, OPT_relativenames)) { + Opts.PathStyle = + A->getOption().matches(OPT_basenames) + ? DILineInfoSpecifier::FileLineInfoKind::BaseNameOnly + : DILineInfoSpecifier::FileLineInfoKind::RelativeFilePath; + } else { + Opts.PathStyle = DILineInfoSpecifier::FileLineInfoKind::AbsoluteFilePath; + } + Opts.DebugFileDirectory = Args.getAllArgValues(OPT_debug_file_directory_EQ); + Opts.DefaultArch = Args.getLastArgValue(OPT_default_arch_EQ).str(); + Opts.Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, !IsAddr2Line); + Opts.DWPName = Args.getLastArgValue(OPT_dwp_EQ).str(); + Opts.FallbackDebugPath = + Args.getLastArgValue(OPT_fallback_debug_path_EQ).str(); + Opts.PrintFunctions = decideHowToPrintFunctions(Args, IsAddr2Line); + parseIntArg(Args, OPT_print_source_context_lines_EQ, SourceContextLines); + Opts.RelativeAddresses = Args.hasArg(OPT_relative_address); + Opts.UntagAddresses = + Args.hasFlag(OPT_untag_addresses, OPT_no_untag_addresses, !IsAddr2Line); + Opts.UseNativePDBReader = Args.hasArg(OPT_use_native_pdb_reader); + Opts.UseSymbolTable = true; - for (const auto &hint : ClDsymHint) { - if (sys::path::extension(hint) == ".dSYM") { - Opts.DsymHints.push_back(hint); + for (const opt::Arg *A : Args.filtered(OPT_dsym_hint_EQ)) { + StringRef Hint(A->getValue()); + if (sys::path::extension(Hint) == ".dSYM") { + Opts.DsymHints.emplace_back(Hint); } else { - errs() << "Warning: invalid dSYM hint: \"" << hint << - "\" (must have the '.dSYM' extension).\n"; + errs() << "Warning: invalid dSYM hint: \"" << Hint + << "\" (must have the '.dSYM' extension).\n"; } } + + auto OutputStyle = + IsAddr2Line ? DIPrinter::OutputStyle::GNU : DIPrinter::OutputStyle::LLVM; + if (const opt::Arg *A = Args.getLastArg(OPT_output_style_EQ)) { + OutputStyle = strcmp(A->getValue(), "GNU") == 0 + ? DIPrinter::OutputStyle::GNU + : DIPrinter::OutputStyle::LLVM; + } + LLVMSymbolizer Symbolizer(Opts); + DIPrinter Printer(outs(), Opts.PrintFunctions != FunctionNameKind::None, + Args.hasArg(OPT_pretty_print), SourceContextLines, + Args.hasArg(OPT_verbose), OutputStyle); - DIPrinter Printer(outs(), ClPrintFunctions != FunctionNameKind::None, - ClPrettyPrint, ClPrintSourceContextLines, ClVerbose, - ClOutputStyle); - - if (ClInputAddresses.empty()) { + std::vector InputAddresses = Args.getAllArgValues(OPT_INPUT); + if (InputAddresses.empty()) { const int kMaxInputStringLength = 1024; char InputString[kMaxInputStringLength]; @@ -351,12 +321,14 @@ int main(int argc, char **argv) { std::remove_if(StrippedInputString.begin(), StrippedInputString.end(), [](char c) { return c == '\r' || c == '\n'; }), StrippedInputString.end()); - symbolizeInput(IsAddr2Line, StrippedInputString, Symbolizer, Printer); + symbolizeInput(Args, AdjustVMA, IsAddr2Line, OutputStyle, + StrippedInputString, Symbolizer, Printer); outs().flush(); } } else { - for (StringRef Address : ClInputAddresses) - symbolizeInput(IsAddr2Line, Address, Symbolizer, Printer); + for (StringRef Address : InputAddresses) + symbolizeInput(Args, AdjustVMA, IsAddr2Line, OutputStyle, Address, + Symbolizer, Printer); } return 0;