> On Mar 11, 2016, at 11:02 AM, Zachary Turner via lldb-dev 
> <lldb-dev@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
_______________________________________________
lldb-dev mailing list
lldb-dev@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-dev

Reply via email to