[lldb-dev] SymbolFile::FindGlobalVariables

Greg Clayton via lldb-dev lldb-dev at lists.llvm.org
Fri Mar 11 11:35:16 PST 2016


> On Mar 11, 2016, at 11:02 AM, Zachary Turner via lldb-dev <lldb-dev at lists.llvm.org> wrote:
> 
> I'm trying to implement this function for PDB.  There are two overloads:
> 
> uint32_t
> FindGlobalVariables (const ConstString &name, const CompilerDeclContext *parent_decl_ctx, bool append, uint32_t max_matches, VariableList& variables)
> 
> uint32_t
> FindGlobalVariables(const RegularExpression& regex, bool append, uint32_t max_matches, VariableList& variables)
> 
> I know how to implement the second overload, but not the first.  What is a CompilerDeclContext?  


It is a declaration context. A variable like "foo::g_int" that is declared like:

namespace foo
{
   int g_int;
}

Has a decl context of "namespace foo". Also a variable like:


namespace foo
{
    class bar
    {
         struct baz
         {
              void foo();
         };
    };
}

The function foo would have a decl context "struct baz". "struct baz" has a parent decl context "class bar". "class bar" has a parent decl context "namespace foo". "namespace foo" has a parent decl context of "compile unit"


> Some comments in the DWARF implementation of the function seem to imply it's related to namespaces, but there's a lot of strange code that I don't understand.  What is the relationship between a namespace and a symbol file?  And why does `DeclContextMatchesThisSymbolFile` contain no code at all that accesses any property of the symbol file?  It just checks if decl_ctx->GetTypeSystem()->GetMinimumLanguage(nullptr) == decl_ctx->GetTypeSystem(), which appears to have nothing to do with any symbol file.

When it comes down to creating types I am going to guess that you will be using ClangASTContext to create any types that you hand out. This already has all of the needed calls for you to create all of the stuff that you will need. You will need to take a look at DWARFASTParserClang and see how it creates types using the ClangASTContext.
> 
> What user command or debugger operation results in FindGlobalVariables getting called with this particular overload, and how does it build the CompilerDeclContext?

The SymbolFile subclasses will create decl contexts as needed. In DWARF it uses:

void
SymbolFileDWARF::ParseDeclsForContext (CompilerDeclContext decl_ctx)
{
    TypeSystem *type_system = decl_ctx.GetTypeSystem();
    DWARFASTParser *ast_parser = type_system->GetDWARFParser();
    std::vector<DWARFDIE> decl_ctx_die_list = ast_parser->GetDIEForDeclContext(decl_ctx);

    for (DWARFDIE decl_ctx_die : decl_ctx_die_list)
        for (DWARFDIE decl = decl_ctx_die.GetFirstChild(); decl; decl = decl.GetSibling())
            ast_parser->GetDeclForUIDFromDWARF(decl);
}

> 
> On another note, why is the decl context stored as void* instead of having an actual wrapper with an abstract interface such as ClangDeclContext / JavaDeclContext, etc that all inherit from LanguageDeclContext, and pass the LanguageDeclContext around instead of a void*?

So three classes: CompilerType, CompilerDecl and CompilerDeclContext all contain a "TypeSystem *" which points to a subclass of "TypeSystem". Then each different type system will store their native pointer to the thing that represents a type, decl and decl context. For ClangASTContext TypeSystem produced objects, CompilerType stores a QualType as an opaque pointer gotten from a call to "clang::QualType::getAsOpaquePtr()". CompilerDecl stores just the "clang::Decl *", and for CompilerDeclContext we store a "clang::DeclContext *". Each type system is different. 

SwiftASTContext stores "swift::Type*" in CompilerType, and it doesn't represent CompilerDecl or CompilerDeclContext because its expression parser doesn't need access to these things.

RenderScript and Go each have their own type systems and can back CompilerType, CompilerDecl and CompilerDeclContext as they need to backing them with whatever they need. 

Right now CompilerType is the important one since all variable viewing explores CompilerType to display the children of a struct/union/class. CompilerDecl and CompilerDeclContext will help with expressions and the only thing that needs this right now is the clang expression parser for C/C++/ObjC/ObjC++.

When you are asked to parse a type for a variable in your SymbolFilePDB, you will be required to make a CompilerType. I would suggest using a ClangASTContext as a type system to create your types. You can see how DWARF does this in DWARFASTParserClang. You will need to do something very similar. If you need to create a class for something like:

namespace A
{
    class B
    {
    };
}

Part of correctly doing so involves you creating a "namespace A" in the ClangASTContext (which is a clang::DeclContext). You will specify that the translation unit is the decl context for "namespace A" when you create the namespace. When you create the "class B", you will need to specify that the context that is is created in is the "namespace A" from the ClangASTContext. These are parameters that are needed when you create clang types in a ClangASTContext. So you will be creating the CompilerDecl and CompilerDeclContext classes already. Classes are clang::DeclContext objects, functions are as well. A variable is just a clang::Decl. Each of these items could be handed out through your SymbolFile if someone asks "get me the CompilerDecl for the variable with ID 123".

The type system stuff is the most complex thing you will be doing as you are implementing your SymbolFilePDB and I know you will have a lot of questions.

In other debuggers, the debugger makes up a very simple structures used to represent the types from your executable and then these debuggers create expression parsers that uses these simple structs. If the language changes, or a new language feature is added be compiler engineers, the debugger must go and add all sorts of functionalities to the expression parser and they are always playing catchup and can never do all of the things the compiler can do. In LLDB we took a different approach: create types using the native clang::ASTContext just as the compiler would when compiling source code, and then just use the compiler to evaluate expressions. We can do so many things no other debugger expression parser can:
- multi-line statements
- declare expression local variables
- use flow control (if/then/else, switch, etc)
- Use C++ lambdas
- declare types that you can use in your expressions
- much much more

So the price of entry is a bit high as you need to convert your types into clang types, but the payoff is huge.

Let me know what other questions you have.

Greg


More information about the lldb-dev mailing list