set_trace_func is available in JRuby, but you have to turn on a flag (--debug) for it to receive all events.
The main problem I have with set_trace_func is that it passes a binding along. The *VAST* majority of overhead from using set_trace_func is because of that binding, and it's because of the binding (and to a lesser extent the constant pinging for an installed event hook) that we require a flag to enable full tracing.
It would probably be possible to reduce static-only tracing to the point where it could be on all the time, but being able to pull off arbitrary bindings for set_trace_func is almost a nonstarter.
The JVM allows debugging a previously "non-debug" JVM instance. When a debugger attaches to a running process, loaded code is rewritten to include debugging logic throughout. Detaching returns code to the original non-debug footprint.
The rewriting generally happens at a JVM bytecode level, allowing for the code to still JIT compile, and so you can still run "optimized" code, albeit hindered by debugging overhead.
JRuby is a mixed-mode environment, like Rubinius, which means we have an interpreted phase before we "JIT" code into JVM bytecode. Code that goes to JVM bytecode can in theory be debugged exactly like any other JVM code, since it will get rewritten and be step-debuggable using the same mechanisms. Code that is still interpreted, however, can't really enlist in that process because the JVM knows nothing about our execution model.
Interpreted code is where set_trace_func (and the lower-overhead native "EventHook" logic which we duplicated in JRuby) makes more sense. When tracing is enabled, we emit trace function calls for both interpreted and JIT'ed code, allowing you to debug an application that's still JIT'ing and optimizing as it did before. The ruby-debug we ship is actually a "native" (i.e. Java-based) JRuby extension that mimics what the C version does, event hooks and all.
There may also be a possible marriage of the two worlds, where we present a Java debugging API that knows about both interpreted and compiled Ruby code.
The debugging support for JVM better than on most native runtimes, and other runtimes might do well to copy it.
Using standard JVM debuggers, I'm sure it would be possible to debug JITed JRuby code, since it's all just JVM bytecode then. Getting lower than that would require native code debuggers, and then you have the same problems we have in JRuby: combining debug logic for both
unoptimized and optimized code.
When we compile JRuby, we don't keep track of optimizations performed in the current compiler. But in general we don't throw away the AST, since that's our master blueprint and we may want to deoptimize and return to interpreting for a while. There's obviously even more challenges
for debugging optimized code if it may suddenly branch back intointerpreted code. The event hook handles this case nightly, since it just requires that executing code periodically send pings, which it can do whether optimized or interpreted.
That's largely how the internal "event hook" stuff in MRI works today.
set_trace_func passes your function a binding, but that's done only
for set_trace_func's API; if you implement a "native" hook, as in
ruby-debug and ruby-prof, your hook function only gets the mostly
static bits. At that point, you can make additional calls to retrieve
a full binding (if one is available).
My primary concern in any debugging API going forward is that it
remains mindful of different implementations in-memory representation,
code lifecycle, and optimization goals. It's not possible to do all
optimizations all the time AND have full debugging support, which is
why JRuby requires passing a start up flag in most cases to get full
"event hook" and set_trace_func behaviors (since constantly pinging
for a hook adds overhead).
There's nothing that any other impl does we can't do in JRuby, as far
as debugging support goes; but if it's too invasive, we won't support
it during normal execution.
> If you look at the examples in the Pickaxe book, I think you'll find that it
> contains no use of binding. For static POSIX set -x -like tracing you don't
> need a binding. There are lots of profiling and tracing use cases where you
> don't need a binding created. In light of this, it is a pity that right now
> they should have to take a hit for information they don't want or need. And
> even though it may not be prohibitive to get static information like
> position information, why bother if it's not wanted?
JRuby recently added built-in profiling support that simply wraps the
method lookup chain with "profiling" method lookup logic. Basically,
all method lookups now return that method wrapped with a profiling
aware wrapper. This makes it theoretically possible for us to turn on
profiling at runtime (if we flush all call sites in the system), but
more interestingly it points out the perils of expecting profiling or
debugging to work against optimized code. Any optimizing runtime will
produce different optimized code when profiling or debugging is
present, since it necessarily becomes part of the application's
execution profile.
- Charlie
On Wed, Dec 22, 2010 at 8:57 AM, Rocky BernsteinThat's largely how the internal "event hook" stuff in MRI works today.
<rocky.b...@gmail.com> wrote:
> Me: How about a kind of set_trace_func that passes no parameters, not even
> the static ones like the class of the method that the frame is in? If
> someone wants information, they issue a call to get it. If no binding is
> needed, none is created. This also, to some extent, pushes the burden of
> the slowdown back where it belongs: on the programmer/program that is
> requesting such stuff.
set_trace_func passes your function a binding, but that's done only
for set_trace_func's API; if you implement a "native" hook, as in
ruby-debug and ruby-prof, your hook function only gets the mostly
static bits. At that point, you can make additional calls to retrieve
a full binding (if one is available).
- Charlie