On Nov 17, 2010, at 11:35 PM, Simon Ask Ulsnes wrote:

> 2010/11/18 Greg Clayton <[email protected]>:
>> LLDB currently doesn't yet have any support for JIT'ed code, though I would 
>> be happy to work with you if you wanted to get that working in LLDB.
> 
> This was my fear. But I'll need some kind of debugger for my own
> language anyway, so I'd be happy to implement this in LLDB.

Great!

> Could you briefly outline the general steps necessary for adding
> support for JIT'ed code? As you mention, I would expect the procedure
> to look similar to how dlopen()ed dylibs are registered, but I might
> be wrong.

A few questions on how this would be debugged (not worrying about the JIT yet):
1 - When you are debugging this, are you going to want to step through your new 
source code files or generated C/C++ sources? 
2 - If you want to debug sources that you produce, will this be like debugging 
lex/yacc code where a bunch of #line and #file directives are used to map C/C++ 
code to your proprietary source code?

If you are going to be debugging standard i386/x86_64 code, then you won't need 
to subclass lldb_private::Process. 

In order to support JIT'ed code, we just need a way to communicate between a 
running program and the debugger. Setting a breakpoint, like is done with the 
JIT support in GDB, is quite ok for this as this is how the dynamic loader 
plug-in for macosx currently works. We can probably get away with being able to 
register additional dynamic loader plug-ins with the current Process. To 
elaborate a bit lets look at how the dynamic loaders work for shared libraries. 
Currently each process has a pluggable dynamic loader plug-in that gets loaded 
prior to launch by the Process subclasses in "Process::WillLaunch()", or prior 
to attaching in "Process::WillAttachToProcessWithID (lldb::pid_t pid)" and 
"Process::WillAttachToProcessWithName (const char *process_name, bool 
wait_for_launch)". So any process can re-use an abstract dynamic loader plugin. 
The pseudo code looks like:

class Process
{
...
        std::auto_ptr<DynamicLoader> m_dynamic_loader_ap;
};

When the WillLaunch, or WillAttach functions are overridden in the Process 
subclasses (see ProcessGDBRemote for an example), it will find a dynamic loader 
by the plug-in name:

    m_dynamic_loader_ap.reset(DynamicLoader::FindPlugin(this, 
"dynamic-loader.macosx-dyld"));

Since the ProcessGDBRemote plug-in is currently for MacOSX debugging, we know 
to lookup the dynamic loader using a specific name.

After a dynamic loader plug-in is installed, it will get a callback after 
attaching or launching:


void
ProcessGDBRemote::DidLaunch ()
{
    DidLaunchOrAttach ();
    if (m_dynamic_loader_ap.get())
        m_dynamic_loader_ap->DidLaunch();
}

This gives the dynamic loader plug-in a chance to install its breakpoint and 
assign a callback to that breakpoint. When breakpoints have callbacks 
associated with them, the callbacks get called synchronously when the 
breakpoint is hit and this allows you to load/unload shared libaries (See 
DynamicLoaderMacOSXDYLD for example code).

We could allow the Process class to have more than one dynamic loader plug-in 
since loading JIT code is very similar to loading shared libraries:

class Process
{
...
        std::vector<DynamicLoaderSP> m_dynamic_loaders;
};

where DynamicLoaderSP is a shared pointer typedef...

This would allow us to have a standard system dynamic loader, and one or more 
JIT dynamic loader plug-ins.

The JIT'ed dynamic loader plug-in would do the same kind of thing the macosx 
one does: it will set a breakpoint, install a callback and react to that 
breakpoint callback as needed.

Inside LLDB we will need to think about how we want to represent JIT'ed code. 
There are a few options, but first lets look at how shared libraries are 
represented. Any executable or shared library is represented by a Module. 
Module objects have ObjectFile objects (abstracted object file readers (ELF and 
mach-o)), and a SymbolFile for reading debug symbols. We will want to repesent 
JIT'ed code by making a new Module that might be a special module that might 
own all of the JIT'ed code in a process from a specific JIT. So the clang 
JIT'ed code might require us to make a DynamicLoaderClangJIT DynamicLoader 
subclass, which would create a new module named with a fake name "<ClangJIT>" 
that we could add any information to. As new JIT'ed code gets added, new 
functions and data would get added to the "<ClangJIT>" object file (symbol 
table symbols and new sections) and symbol file (if we have debug info for the 
JIT'ed code). Another way would be let the JIT define logical modules in case 
you want to organize your JIT'ed code a bit more so that you can create many 
different Clang JIT modules. Either way, all of this work will be done by the 
DynamicLoaderClangJIT class.

> 
> GDB has a hook for JIT'ed code, but you are right that it only works
> for ELF binaries. The approach there is that GDB sets a breakpoint in
> an extern function, which LLVM calls when emitting code, giving GDB a
> chance to load the symbols. Would a different approach in LLDB be
> desirable, or does that seem OK to you?

That should work, see above comments.
> 
> According to the LLVMdev list, LLVM did not emit DWARF data for JIT'ed
> code as of March 2009 — I'm not sure if this has changed, though I
> suspect it hasn't, so to get this to work I guess there is also a bit
> of work to be done on the LLVM side of things.

Agreed, it would be great to be able to get DWARF for JIT'ed code.

> - Simon

Let me know if you need any explanation on anything mentioned above.

Greg

_______________________________________________
lldb-dev mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/lldb-dev

Reply via email to