labath created this revision. labath added reviewers: davide, zturner, asmith, JDevlieghere, clayborg. Herald added subscribers: aprantl, mgorny. Herald added a reviewer: alexshap.
lldb-test already had the ability to dump all symbol information in a module. This is interesting, but it can be too verbose, and it also does not use the same APIs that lldb uses to query symbol information. The last part is interesting to me now, because I am about to add DWARF v5 debug_names support, which needs to implement these APIs. This patch adds a set of arguments to lldb-test, which modify it's behavior from dumping all symbols to dumping only the requested information: - --lookup={function,namespace,type,variable} - search for the given kind of objects. - --name - the name to search for. - --regex - whether to treat the "name" as a regular expression. This is not available for all lookup types (we do not have the required APIs for namespaces and types). - --context - specifies the context, which can be used to restrict the search. This argument takes a variable name (which must be defined and be unique), and we then use the context that this variable is defined in as the search context. - --function-flags={auto,full,base,method,selector} - a set of flags to further restrict the search for function symbols. Together, these flags and their combinations cover the main SymbolFile entry points which I will need to modify for the accelerator table support, and so I plan to do most of the regression testing this way. (I've also found this a useful tool for exploration of what the given APIs are supposed to do.) I add a couple of tests to demonstrate the usage of the usage of the various options, and also an xfailed test which demonstrates a bug I found while playing with this. The only requirement for these tests is the presence of lld -- the should run on any platform which is able to build lldb. These tests use c++ code as input, but this isn't a requirement. It is also possible to use IR, assembly or json to create the test module. https://reviews.llvm.org/D46318 Files: lit/CMakeLists.txt lit/SymbolFile/DWARF/basic-function-lookup.cpp lit/SymbolFile/DWARF/basic-namespace-lookup.cpp lit/SymbolFile/DWARF/basic-type-lookup.cpp lit/SymbolFile/DWARF/basic-variable-lookup.cpp lit/SymbolFile/DWARF/type-in-function-lookup.cpp lit/lit.cfg lit/lit.site.cfg.in tools/lldb-test/lldb-test.cpp
Index: tools/lldb-test/lldb-test.cpp =================================================================== --- tools/lldb-test/lldb-test.cpp +++ tools/lldb-test/lldb-test.cpp @@ -20,6 +20,9 @@ #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Symbol/ClangASTContext.h" #include "lldb/Symbol/ClangASTImporter.h" +#include "lldb/Symbol/SymbolVendor.h" +#include "lldb/Symbol/TypeList.h" +#include "lldb/Symbol/VariableList.h" #include "lldb/Utility/DataExtractor.h" #include "lldb/Utility/StreamString.h" @@ -69,8 +72,65 @@ } // namespace module namespace symbols { -cl::list<std::string> InputFilenames(cl::Positional, cl::desc("<input files>"), - cl::OneOrMore, cl::sub(SymbolsSubcommand)); +static cl::list<std::string> InputFilenames(cl::Positional, + cl::desc("<input files>"), + cl::OneOrMore, + cl::sub(SymbolsSubcommand)); +enum class LookupType { + None, + Function, + Namespace, + Type, + Variable, +}; +static cl::opt<LookupType> Lookup( + "lookup", cl::desc("Choose lookup type:"), + cl::values( + clEnumValN(LookupType::None, "none", + "No lookup, just dump the module."), + clEnumValN(LookupType::Function, "function", "Find functions."), + clEnumValN(LookupType::Namespace, "namespace", "Find namespaces."), + clEnumValN(LookupType::Type, "type", "Find types."), + clEnumValN(LookupType::Variable, "variable", "Find global variables.")), + cl::sub(SymbolsSubcommand)); + +static cl::opt<std::string> Name("name", cl::desc("Name to lookup."), + cl::sub(SymbolsSubcommand)); +static cl::opt<bool> + Regex("regex", + cl::desc("Lookup using regular expressions (avaliable for variables " + "and functions only)."), + cl::sub(SymbolsSubcommand)); +static cl::opt<std::string> + Context("context", + cl::desc("Restrict search to the context of the given variable."), + cl::value_desc("variable"), cl::sub(SymbolsSubcommand)); + +static cl::list<FunctionNameType> FunctionNameFlags( + "function-flags", cl::desc("Function lookup flags:"), + cl::values(clEnumValN(eFunctionNameTypeAuto, "auto", + "Automatically deduce flags based on name."), + clEnumValN(eFunctionNameTypeFull, "full", "Full function name."), + clEnumValN(eFunctionNameTypeBase, "base", "Base name."), + clEnumValN(eFunctionNameTypeMethod, "method", "Method name."), + clEnumValN(eFunctionNameTypeSelector, "selector", + "Selector name.")), + cl::sub(SymbolsSubcommand)); +static FunctionNameType getFunctionNameFlags() { + FunctionNameType Result = FunctionNameType(0); + for (FunctionNameType Flag : FunctionNameFlags) + Result = FunctionNameType(Result | Flag); + return Result; +} + +static Expected<CompilerDeclContext> getDeclContext(SymbolVendor &Vendor); + +static void lookupFunctions(SymbolVendor &Vendor); +static void lookupNamespaces(SymbolVendor &Vendor); +static void lookupTypes(SymbolVendor &Vendor); +static void lookupVariables(SymbolVendor &Vendor); + +static void dumpSymbols(Debugger &Dbg); } } // namespace opts @@ -174,18 +234,167 @@ } } -static void dumpSymbols(Debugger &Dbg) { - for (const auto &File : opts::symbols::InputFilenames) { +Expected<CompilerDeclContext> +opts::symbols::getDeclContext(SymbolVendor &Vendor) { + if (Context.empty()) + return CompilerDeclContext(); + VariableList List; + Vendor.FindGlobalVariables(ConstString(Context), nullptr, false, UINT32_MAX, + List); + if (List.GetSize() == 0) { + return make_error<StringError>("Context search didn't find a match.", + inconvertibleErrorCode()); + } + if (List.GetSize() > 1) { + return make_error<StringError>("Context search found multiple matches.", + inconvertibleErrorCode()); + } + return List.GetVariableAtIndex(0)->GetDeclContext(); +} + +void opts::symbols::lookupFunctions(SymbolVendor &Vendor) { + SymbolContextList List; + if (Regex) { + RegularExpression RE(Name); + if (!RE.IsValid()) { + errs() << "ERROR: `" << Name << "` is not a valid regular expression.\n"; + return; + } + Vendor.FindFunctions(RE, true, false, List); + } else { + Expected<CompilerDeclContext> ContextOr = getDeclContext(Vendor); + if (!ContextOr) { + errs() << "ERROR: " << toString(ContextOr.takeError()) << "\n"; + return; + } + CompilerDeclContext *ContextPtr = + ContextOr->IsValid() ? &*ContextOr : nullptr; + + Vendor.FindFunctions(ConstString(Name), ContextPtr, getFunctionNameFlags(), + true, false, List); + } + outs() << formatv("Found {0} functions:\n", List.GetSize()); + StreamString Stream; + List.Dump(&Stream, nullptr); + outs() << Stream.GetData() << "\n"; +} + +void opts::symbols::lookupNamespaces(SymbolVendor &Vendor) { + Expected<CompilerDeclContext> ContextOr = getDeclContext(Vendor); + if (!ContextOr) { + errs() << "ERROR: " << toString(ContextOr.takeError()) << "\n"; + return; + } + CompilerDeclContext *ContextPtr = + ContextOr->IsValid() ? &*ContextOr : nullptr; + + SymbolContext SC; + CompilerDeclContext Result = + Vendor.FindNamespace(SC, ConstString(Name), ContextPtr); + if (Result) + outs() << "Found namespace: " + << Result.GetScopeQualifiedName().GetStringRef() << "\n"; + else + outs() << "Namespace not found.\n"; +} + +void opts::symbols::lookupTypes(SymbolVendor &Vendor) { + SymbolContext SC; + DenseSet<SymbolFile *> SearchedFiles; + TypeMap Map; + Expected<CompilerDeclContext> ContextOr = getDeclContext(Vendor); + if (!ContextOr) { + errs() << "ERROR: " << toString(ContextOr.takeError()) << "\n"; + return; + } + CompilerDeclContext *ContextPtr = + ContextOr->IsValid() ? &*ContextOr : nullptr; + + Vendor.FindTypes(SC, ConstString(Name), ContextPtr, true, UINT32_MAX, + SearchedFiles, Map); + + outs() << formatv("Found {0} types:\n", Map.GetSize()); + StreamString Stream; + Map.Dump(&Stream, false); + outs() << Stream.GetData() << "\n"; +} + +void opts::symbols::lookupVariables(SymbolVendor &Vendor) { + VariableList List; + if (Regex) { + RegularExpression RE(Name); + if (!RE.IsValid()) { + errs() << "ERROR: `" << Name << "` is not a valid regular expression.\n"; + return; + } + Vendor.FindGlobalVariables(RE, false, UINT32_MAX, List); + } else { + Expected<CompilerDeclContext> ContextOr = getDeclContext(Vendor); + if (!ContextOr) { + errs() << "ERROR: " << toString(ContextOr.takeError()) << "\n"; + return; + } + CompilerDeclContext *ContextPtr = + ContextOr->IsValid() ? &*ContextOr : nullptr; + + Vendor.FindGlobalVariables(ConstString(Name), ContextPtr, false, UINT32_MAX, + List); + } + outs() << formatv("Found {0} variables:\n", List.GetSize()); + StreamString Stream; + List.Dump(&Stream, false); + outs() << Stream.GetData() << "\n"; +} + +void opts::symbols::dumpSymbols(Debugger &Dbg) { + if (Lookup != LookupType::None && Regex && !Context.empty()) { + errs() + << "ERROR: Cannot lookup using both regular expressions and context.\n"; + return; + } + if ((Lookup == LookupType::Type || Lookup == LookupType::Namespace) && Regex) { + errs() << "ERROR: Cannot lookup types and namespaces using regular " + "expressions.\n"; + return; + } + if (Lookup == LookupType::Function && Regex && getFunctionNameFlags() != 0) { + errs() << "ERROR: Cannot lookup types using both regular expressions and " + "function-flags.\n"; + return; + } + for (const auto &File : InputFilenames) { + outs() << "Module: " << File << "\n"; ModuleSpec Spec{FileSpec(File, false)}; Spec.GetSymbolFileSpec().SetFile(File, false); auto ModulePtr = std::make_shared<lldb_private::Module>(Spec); + SymbolVendor *Vendor = ModulePtr->GetSymbolVendor(); + if (!Vendor) { + errs() << "ERROR: Module has no symbol vendor.\n"; + continue; + } - StreamString Stream; - ModulePtr->ParseAllDebugSymbols(); - ModulePtr->Dump(&Stream); - llvm::outs() << Stream.GetData() << "\n"; - llvm::outs().flush(); + switch(Lookup) { + case LookupType::Function: + lookupFunctions(*Vendor); + break; + case LookupType::Namespace: + lookupNamespaces(*Vendor); + break; + case LookupType::Type: + lookupTypes(*Vendor); + break; + case LookupType::Variable: + lookupVariables(*Vendor); + break; + case LookupType::None: + StreamString Stream; + ModulePtr->ParseAllDebugSymbols(); + ModulePtr->Dump(&Stream); + outs() << Stream.GetData() << "\n"; + break; + } + outs().flush(); } } @@ -246,7 +455,7 @@ if (opts::ModuleSubcommand) dumpModules(*Dbg); else if (opts::SymbolsSubcommand) - dumpSymbols(*Dbg); + opts::symbols::dumpSymbols(*Dbg); DebuggerLifetime->Terminate(); return 0; Index: lit/lit.site.cfg.in =================================================================== --- lit/lit.site.cfg.in +++ lit/lit.site.cfg.in @@ -13,6 +13,7 @@ config.cc = "@LLDB_TEST_C_COMPILER@" config.cxx = "@LLDB_TEST_CXX_COMPILER@" config.have_zlib = @LLVM_ENABLE_ZLIB@ +config.have_lld = @LLDB_HAVE_LLD@ # Support substitution of the tools and libs dirs with user parameters. This is # used when we can't determine the tool dir at configuration time. Index: lit/lit.cfg =================================================================== --- lit/lit.cfg +++ lit/lit.cfg @@ -132,6 +132,8 @@ config.available_features.add("compiler-msvc") config.available_features.add(binary_feature(config.have_zlib, "zlib", "no")) +if config.have_lld: + config.available_features.add("lld") # llvm-config knows whether it is compiled with asserts (and) # whether we are operating in release/debug mode. Index: lit/SymbolFile/DWARF/type-in-function-lookup.cpp =================================================================== --- /dev/null +++ lit/SymbolFile/DWARF/type-in-function-lookup.cpp @@ -0,0 +1,24 @@ +// REQUIRES: lld + +// XFAIL: * + +// RUN: clang %s -g -c -o %t.o --target=x86_64-pc-linux +// RUN: ld.lld %t.o -o %t +// RUN: lldb-test symbols --name=foo --lookup=type %t | \ +// RUN: FileCheck --check-prefix=NAME %s + +// Lookup for "foo" should find either both "struct foo" types or just the +// global one. Right now, it finds the definition inside bar(), which is +// definitely wrong. + +// NAME: Found 2 types: +struct foo {}; +// NAME-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-1]] + +void bar() { + struct foo {}; +// NAME-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-1]] + foo a; +} + +extern "C" void _start(foo) {} Index: lit/SymbolFile/DWARF/basic-variable-lookup.cpp =================================================================== --- /dev/null +++ lit/SymbolFile/DWARF/basic-variable-lookup.cpp @@ -0,0 +1,55 @@ +// REQUIRES: lld + +// RUN: clang %s -g -c -o %t.o --target=x86_64-pc-linux +// RUN: ld.lld %t.o -o %t +// RUN: lldb-test symbols --name=foo --lookup=variable --context=context %t | \ +// RUN: FileCheck --check-prefix=CONTEXT %s +// RUN: lldb-test symbols --name=foo --lookup=variable %t | \ +// RUN: FileCheck --check-prefix=NAME %s +// RUN: lldb-test symbols --regex --name=foo --lookup=variable %t | \ +// RUN: FileCheck --check-prefix=REGEX %s +// RUN: lldb-test symbols --name=not_there --lookup=variable %t | \ +// RUN: FileCheck --check-prefix=EMPTY %s + +// EMPTY: Found 0 variables: +// NAME: Found 4 variables: +// CONTEXT: Found 1 variables: +// REGEX: Found 5 variables: +int foo; +// NAME-DAG: name = "foo", type = {{.*}} (int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-1]] +// REGEX-DAG: name = "foo", type = {{.*}} (int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-2]] +namespace bar { +int context; +long foo; +// NAME-DAG: name = "foo", type = {{.*}} (long int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-1]] +// CONTEXT-DAG: name = "foo", type = {{.*}} (long int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-2]] +// REGEX-DAG: name = "foo", type = {{.*}} (long int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-3]] +namespace baz { +static short foo; +// NAME-DAG: name = "foo", type = {{.*}} (short), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-1]] +// REGEX-DAG: name = "foo", type = {{.*}} (short), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-2]] +} +} + +struct sbar { + static int foo; +// NAME-DAG: name = "foo", type = {{.*}} (int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-1]] +// REGEX-DAG: name = "foo", type = {{.*}} (int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-2]] +}; +int sbar::foo; + +int foobar; +// REGEX-DAG: name = "foobar", type = {{.*}} (int), {{.*}} decl = basic-variable-lookup.cpp:[[@LINE-1]] + +int fbar() { + static int foo; + return foo + bar::baz::foo; +} + +int Foo; + +struct ssbar { + int foo; +}; + +extern "C" void _start(sbar, ssbar) {} Index: lit/SymbolFile/DWARF/basic-type-lookup.cpp =================================================================== --- /dev/null +++ lit/SymbolFile/DWARF/basic-type-lookup.cpp @@ -0,0 +1,38 @@ +// REQUIRES: lld + +// RUN: clang %s -g -c -o %t.o --target=x86_64-pc-linux +// RUN: ld.lld %t.o -o %t +// RUN: lldb-test symbols --name=foo --lookup=type %t | \ +// RUN: FileCheck --check-prefix=NAME %s +// RUN: lldb-test symbols --name=foo --context=context --lookup=type %t | \ +// RUN: FileCheck --check-prefix=CONTEXT %s +// RUN: lldb-test symbols --name=not_there --lookup=type %t | \ +// RUN: FileCheck --check-prefix=EMPTY %s + +// EMPTY: Found 0 types: +// NAME: Found 4 types: +// CONTEXT: Found 1 types: +struct foo { }; +// NAME-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-1]] + +namespace bar { +int context; +struct foo {}; +// NAME-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-1]] +// CONTEXT-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-2]] +namespace baz { +struct foo {}; +// NAME-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-1]] +} +} + +struct sbar { + struct foo {}; +// NAME-DAG: name = "foo", {{.*}} decl = basic-type-lookup.cpp:[[@LINE-1]] +}; + +struct foobar {}; + +struct Foo {}; + +extern "C" void _start(foo, bar::foo, bar::baz::foo, sbar::foo, foobar, Foo) {} Index: lit/SymbolFile/DWARF/basic-namespace-lookup.cpp =================================================================== --- /dev/null +++ lit/SymbolFile/DWARF/basic-namespace-lookup.cpp @@ -0,0 +1,27 @@ +// REQUIRES: lld + +// RUN: clang %s -g -c -o %t.o --target=x86_64-pc-linux +// RUN: ld.lld %t.o -o %t +// RUN: lldb-test symbols --name=foo --lookup=namespace %t | \ +// RUN: FileCheck --check-prefix=FOO %s +// RUN: lldb-test symbols --name=foo --lookup=namespace --context=context %t | \ +// RUN: FileCheck --check-prefix=CONTEXT %s +// RUN: lldb-test symbols --name=not_there --lookup=namespace %t | \ +// RUN: FileCheck --check-prefix=EMPTY %s + +namespace foo { +int X; +} +// FOO: Found namespace: foo + +namespace bar { +int context; +namespace foo { +// CONTEXT: Found namespace: bar::foo +int X; +} +} // namespace bar + +// EMPTY: Namespace not found. + +extern "C" void _start() {} Index: lit/SymbolFile/DWARF/basic-function-lookup.cpp =================================================================== --- /dev/null +++ lit/SymbolFile/DWARF/basic-function-lookup.cpp @@ -0,0 +1,63 @@ +// REQUIRES: lld + +// RUN: clang %s -g -c -o %t.o --target=x86_64-pc-linux +// RUN: ld.lld %t.o -o %t +// RUN: lldb-test symbols --name=foo --lookup=function --function-flags=base %t | \ +// RUN: FileCheck --check-prefix=BASE %s +// RUN: lldb-test symbols --name=foo --lookup=function --function-flags=method %t | \ +// RUN: FileCheck --check-prefix=METHOD %s +// RUN: lldb-test symbols --name=foo --lookup=function --function-flags=full %t | \ +// RUN: FileCheck --check-prefix=FULL %s +// RUN: lldb-test symbols --name=foo --context=context --lookup=function --function-flags=base %t | \ +// RUN: FileCheck --check-prefix=CONTEXT %s +// RUN: lldb-test symbols --name=not_there --lookup=function %t | \ +// RUN: FileCheck --check-prefix=EMPTY %s + +// EMPTY: Found 0 functions: +// BASE: Found 4 functions: +// METHOD: Found 3 functions: +// FULL: Found 2 functions: +// CONTEXT: Found 1 functions: + +void foo() {} +// BASE-DAG: name = "foo()", mangled = "_Z3foov" +// FULL-DAG: name = "foo()", mangled = "_Z3foov" +void foo(int) {} +// BASE-DAG: name = "foo(int)", mangled = "_Z3fooi" +// FULL-DAG: name = "foo(int)", mangled = "_Z3fooi" + +namespace bar { +int context; +void foo() {} +// BASE-DAG: name = "bar::foo()", mangled = "_ZN3bar3fooEv" +// CONTEXT-DAG: name = "bar::foo()", mangled = "_ZN3bar3fooEv" +namespace baz { +void foo() {} +// BASE-DAG: name = "bar::baz::foo()", mangled = "_ZN3bar3baz3fooEv" +} // namespace baz +} // namespace bar + +struct foo {}; +void fbar(struct foo) {} + +void Foo() {} + +struct sbar { + void foo(); +// METHOD-DAG: name = "sbar::foo()", mangled = "_ZN4sbar3fooEv" + static void foo(int); +// METHOD-DAG: name = "sbar::foo(int)", mangled = "_ZN4sbar3fooEi" +}; +void sbar::foo() {} +void sbar::foo(int) {} + +void ffbar() { + struct sbar { + void foo() {} +// METHOD-DAG: name = "ffbar()::sbar::foo()", mangled = "_ZZ5ffbarvEN4sbar3fooEv" + }; + sbar a; + a.foo(); +} + +extern "C" void _start() {} Index: lit/CMakeLists.txt =================================================================== --- lit/CMakeLists.txt +++ lit/CMakeLists.txt @@ -18,6 +18,24 @@ string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} LLDB_LIBS_DIR ${LLVM_LIBRARY_OUTPUT_INTDIR}) string(REPLACE ${CMAKE_CFG_INTDIR} ${LLVM_BUILD_MODE} LLDB_TOOLS_DIR ${LLVM_RUNTIME_OUTPUT_INTDIR}) +set(LLDB_TEST_DEPS + LLDBUnitTests + dsymutil + lldb + lldb-test + llvm-config + llvm-mc + llvm-objcopy + ) + +if(TARGET lld) + list(APPEND LLDB_TEST_DEPS lld) + set(LLDB_HAVE_LLD 1) +else() + set(LLDB_HAVE_LLD 0) +endif() + + if(BUILD_SHARED_LIBS) set(ENABLE_SHARED 1) else() @@ -36,16 +54,6 @@ ${CMAKE_CURRENT_BINARY_DIR}/Unit/lit.site.cfg ) -set(LLDB_TEST_DEPS - LLDBUnitTests - dsymutil - lldb - lldb-test - llvm-config - llvm-mc - llvm-objcopy - ) - if(NOT LLDB_BUILT_STANDALONE) list(APPEND LLDB_TEST_DEPS FileCheck not yaml2obj) endif()
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits