[lldb-dev] SymbolFile::FindGlobalVariables

Greg Clayton via lldb-dev lldb-dev at lists.llvm.org
Thu Mar 17 10:40:19 PDT 2016


> On Mar 16, 2016, at 3:34 PM, Zachary Turner <zturner at google.com> wrote:
> 
> Hi Greg,
> 
> could you clarify the difference between the functions ParseTypes,

SymbolFile::ParseTypes() is only used for debugging, don't worry about this one unless you need a way to say "parse all types in the supplied context". It can help you debug things. It is only called by Module::ParseAllDebugSymbols() and this is only used for debugging and it isn't exposed anywhere or used by anyone. Just used for initial implementation. Again, see Module::ParseAllDebugSymbols() for all the details, but not one calls Module::ParseAllDebugSymbols().

> FindTypes

FindType is for finding types by name:

virtual uint32_t
FindTypes (const SymbolContext& sc, 
           const ConstString &name, 
           const CompilerDeclContext *parent_decl_ctx, 
           bool append, 
           uint32_t max_matches, 
           llvm::DenseSet<lldb_private::SymbolFile *> &searched_symbol_files, 
           TypeMap& types);

The symbol context can limit the scope of your search if needed. Why? Because you might be stopped inside a "test" module that has 100 compile units, and you are stopped in compile unit "foo.cpp" in function "bar()". The symbol context can specify a scope in which to search. If a function or block is specified, you should find the any types in the the block, if you don't find one, proceed to the parent block. Keep going up the blocks, then search the class if "bar()" is a method, then search the compile unit for any types that match, and then fall back to the module. It is usually easiest to just lookup the type and come up with N results, and pare down the results after you find them as most times you will only find one type so there is no need.

If "parent_decl_ctx" is not NULL, you take all results that made it through the "sc" filter and then filter out any results that are not in this "parent_decl_ctx". The expression parser often says "I am looking for "basic_string" in the parent_decl_ctx that represents "namespace std".

You always check to see if "this" is in the searched_symbol_files set and only search if you aren't. This is for module debugging where DWARF files refer to types from other DWARF files.


And there is one that is specific to module debugging:

    virtual size_t          FindTypes (const std::vector<CompilerContext> &context, bool append, TypeMap& types);

You don't need to implement this one unless you have one PDB file that refers to a type in another...




> ResolveTypeUID

Often types you might have a variable whose type id "typedef FooType ...;". Because of this, you can give your lldb_private::Variable a lldb_private::Type that says "I am a typedef named 'FooType' to the type with UID 0x1234. We don't need to resolve the type just yet and if no one needs to explore the type, we don't need to resolve the type right away. If anyone does ask for the CompilerType from a lldb_private::Type, we can then do a "m_symfile->ResolveTypeUID(m_encoding_uid);". So it allows us to just say "your type is a type from a symbol file with a user ID of 0x1234" and we can resolve that lazily if any only if we ever need to. Another instance where this is useful is when you create a lldb_private::Function:

Function (CompileUnit *comp_unit,
          lldb::user_id_t func_uid,
          lldb::user_id_t func_type_uid,
          const Mangled &mangled,
          Type * func_type,
          const AddressRange& range);

Note that you don't need to parse the function type up front, you can just make a function with:

SymbolFilePDB::ParseFunction(...)
{
    lldb::user_id_t func_uid = 0x1234;
    Mangled mangled("_Z3foo", true);
    AddressRange func_range = ...;
    Function *func = new Function(m_cu, func_uid, func_uid, mangled, nullptr, func_range);
}

Later if anyone calls:

    Type* Function::GetType();

You can see if can lazily resolve its function type using ResolveTypeUID:

Type*
Function::GetType()
{
    if (m_type == nullptr)
    {
        SymbolContext sc;
        
        CalculateSymbolContext (&sc);
        
        if (!sc.module_sp)
            return nullptr;
        
        SymbolVendor *sym_vendor = sc.module_sp->GetSymbolVendor();
        
        if (sym_vendor == nullptr)
            return nullptr;
        
        SymbolFile *sym_file = sym_vendor->GetSymbolFile();
        
        if (sym_file == nullptr)
            return nullptr;
        
        m_type = sym_file->ResolveTypeUID(m_type_uid);
    }
    return m_type;
}

> , and CompleteType

    virtual bool SymbolFile::CompleteType (CompilerType &compiler_type);

This allows you to say "I have a class A type that is very complex and until someone needs to know the details about this class I will have the type represented as 'class A;'. Once someone needs to know the details they can lazily complete the type. This is very useful when creating types in clang::ASTContext objects because we can make a forward declaration that knows how to complete itself when you hook into the clang::ExternalASTSource. This is some of the factoring you have already been doing. If you always just want make all of your types completely when you hand them out, then you don't need to do this. But since clang types already have all the hooks needed to lazily complete types, we take advantage of this. This way you can hand an expression a "class A;" as the type for a variable, and if clang ever needs to know about the details inside of A, the clang::ExternalASTSource hooks will allow the type to lazily complete itself _only_ if ever needed. So if you have an expression like "A* a = GetA(); printf("%p\n", a)", clang never need to know what is inside of A so it never asks us to complete it. But if you do "A* a = ...; a->DoSomething()", clang will ask us to expand the type. Another example is imagine you have a local variable in your frame whose type is "A* a;". If we display the type in the debugger's variable view, we know this is a pointer and unless someone clicks on the disclosure triangle to expand the type, we don't need to know what is inside of "A". Variable display is done by using CompilerType methods like:

CompilerType t;

const bool omit_empty_base_classes = true;
uint32_t num_children = t.GetNumChildren (omit_empty_base_classes);
for (i in num_children)
{
    CompilerType child_type = t.GetChildCompilerTypeAtIndex (exe_ctx, i, ...);
}

When we call CompilerType::GetNumChildren(), we call into ClangASTContext::GetNumChildren(...), which knows we have clang types and it also knows that if the type isn't complete and it can complete itself, that we can complete the type:

uint32_t
ClangASTContext::GetNumChildren (lldb::opaque_compiler_type_t type, bool omit_empty_base_classes)
{
    uint32_t num_children = 0;
    clang::QualType qual_type(GetQualType(type));
    const clang::Type::TypeClass type_class = qual_type->getTypeClass();
    switch (type_class)
    {
        case clang::Type::Record:
            if (GetCompleteQualType (getASTContext(), qual_type))
            {


Note that we are smart and only try to complete types that we know can be complete, like Record types for clang. See the function GetCompleteQualType(). So the key is here: both LLDB code and the actual clang compiler itself as it is compiling expressions has the ability to deal with incomplete types and complete them only when necessary.

> from the SymbolFile plugin?

Let me know if you have any questions.

Greg


More information about the lldb-dev mailing list