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