blob: 523c7123daef26b1a24f1e48e82049f09d8e06e5 [file] [log] [blame]
<html>
<head>
<title>Dalvik Debugger Support</title>
</head>
<body>
<h1>Dalvik Debugger Support</h1>
<p>
The Dalvik virtual machine supports source-level debugging with many popular
development environments. Any tool that allows remote debugging over JDWP
(the
<a href="http://java.sun.com/javase/6/docs/technotes/guides/jpda/jdwp-spec.html">
Java Debug Wire Protocol</a>) is expected work. Supported debuggers
include jdb, Eclipse, IntelliJ, and JSwat.
</p><p>
The VM does not support tools based on JVMTI (Java Virtual
Machine Tool Interface). This is a relatively intrusive approach that
relies on bytecode insertion, something the Dalvik VM does not currently
support.
</p><p>
Dalvik's implementation of JDWP also includes hooks for supporting
DDM (Dalvik Debug Monitor) features, notably as implemented by DDMS
(Dalvik Debug Monitor Server) and the Eclipse ADT plugin. The protocol
and VM interaction is described in some detail
<a href="debugmon.html">here</a>.
</p><p>
All of the debugger support in the VM lives in the <code>dalvik/vm/jdwp</code>
directory, and is almost entirely isolated from the rest of the VM sources.
<code>dalvik/vm/Debugger.c</code> bridges the gap. The goal in doing so
was to make it easier to re-use the JDWP code in other projects.
</p><p>
<h2>Implementation</h2>
<p>
Every VM that has debugging enabled starts a "JDWP" thread. The thread
typically sits idle until DDMS or a debugger connects. The thread is
only responsible for handling requests from the debugger; VM-initated
communication, such as notifying the debugger when the VM has stopped at
a breakpoint, are sent from the affected thread.
</p><p>
When the VM is started from the Android app framework, debugging is enabled
for all applications when the system property <code>ro.debuggable</code>
is set to </code>1</code> (use <code>adb shell getprop ro.debuggable</code>
to check it). If it's zero, debugging can be enabled via the application's
manifest, which must include <code>android:debuggable="true"</code> in the
<code>&lt;application&gt;</code> element.
</p><p>
The VM recognizes the difference between a connection from DDMS and a
connection from a debugger (either directly or in concert with DDMS).
A connection from DDMS alone doesn't result in a change in VM behavior,
but when the VM sees debugger packets it allocates additional data
structures and may switch to a different implementation of the interpreter.
</p><p>
Because Dalvik maps bytecode into memory read-only, some common
techniques are difficult to implement without allocating additional memory.
For example, suppose the debugger sets a breakpoint in a method. The
quick way to handle this is to insert a breakpoint instruction directly
into the code. When the instruction is reached, the breakpoint handler
engages. Without this, it's necessary to perform an "is there a breakpoint
here" scan. Even with some optimizations, the debug-enabled interpreter
is much slower than the regular interpreter (perhaps 5x).
</p><p>
The JDWP protocol is stateless, so the VM handles individual debugger
requests as they arrive, and posts events to the debugger as they happen.
</p><p>
<h2>Debug Data</h2>
<p> Source code debug data, which includes mappings of source code to
bytecode and lists describing which registers are used to hold method
arguments and local variables, are optionally emitted by the Java compiler.
When <code>dx</code> converts Java bytecode to Dalvik bytecode, it must
also convert this debug data.
</p><p>
<code>dx</code> must also ensure that it doesn't perform operations
that confuse the debugger. For example, re-using registers that hold
method arguments and the "<code>this</code>" pointer is allowed in
Dalvik bytecode if the values are never used or no longer needed.
This can be very confusing for the debugger (and the programmer)
since the values have method scope and aren't expected to disappear. For
this reason, <code>dx</code> generates sub-optimal code in some situations
when debugging support is enabled.
</p><p>
Some of the debug data is used for other purposes; in particular, having
filename and line number data is necessary for generating useful exception
stack traces. This data can be omitted by <code>dx</code> to make the DEX
file smaller.
</p><p>
<h2>Usage</h2>
<p>
The Dalvik VM supports many of the same command-line flags that other popular
desktop VMs do. To start a VM with debugging enabled, you add a command-line
flag with some basic options. The basic incantation looks something
like this:
<pre>-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y</pre>
or
<pre>-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=y</pre>
</p><p>
After the initial prefix, options are provided as name=value pairs. The
options currently supported by the Dalvik VM are:
<dl>
<dt>transport (no default)</dt>
<dd>Communication transport mechanism to use. Dalvik supports
TCP/IP sockets (<code>dt_socket</code>) and connection over USB
through ADB (<code>dt_android_adb</code>).
</dd>
<dt>server (default='n')</dt>
<dd>Determines whether the VM acts as a client or a server. When
acting as a server, the VM waits for a debugger to connect to it.
When acting as a client, the VM attempts to connect to a waiting
debugger.
</dd>
<dt>suspend (default='n')</dt>
<dd>If set to 'y', the VM will wait for a debugger connection
before executing application code. When the debugger connects (or
when the VM finishes connecting to the debugger), the VM tells the
debugger that it has suspended, and will not proceed until told
to resume. If set to 'n', the VM just plows ahead.
</dd>
<dt>address (default="")</dt>
<dd>This must be <code>hostname:port</code> when <code>server=n</code>,
but can be just <code>port</code> when <code>server=y</code>. This
specifies the IP address and port number to connect or listen to.
<br>
Listening on port 0 has a special meaning: try to
listen on port 8000; if that fails, try 8001, 8002, and so on. (This
behavior is non-standard and may be removed from a future release.)
<br>This option has no meaning for <code>transport=dt_android_adb</code>.
</dd>
<dt>help (no arguments)</dt>
<dd>If this is the only option, a brief usage message is displayed.
</dd>
<dt>launch, onthrow, oncaught, timeout</dt>
<dd>These options are accepted but ignored.
</dd>
</dl>
</p><p>
To debug a program on an Android device using DDMS over USB, you could
use a command like this:
<pre>% dalvikvm -agentlib:jdwp=transport=dt_android_adb,suspend=y,server=y -cp /data/foo.jar Foo</pre>
This tells the Dalvik VM to run the program with debugging enabled, listening
for a connection from DDMS, and waiting for a debugger. The program will show
up with an app name of "?" in the process list, because it wasn't started
from the Android application framework. From here you would connect your
debugger to the appropriate DDMS listen port (e.g.
<code>jdb -attach localhost:8700</code> after selecting it in the app list).
</p><p>
To debug a program on an Android device using TCP/IP bridged across ADB,
you would first need to set up forwarding:
<pre>% adb forward tcp:8000 tcp:8000
% adb shell dalvikvm -agentlib:jdwp=transport=dt_socket,address=8000,suspend=y,server=y -cp /data/foo.jar Foo</pre>
and then <code>jdb -attach localhost:8000</code>.
</p><p>
(In the above examples, the VM will be suspended when you attach. In jdb,
type <code>cont</code> to continue.)
</p><p>
The DDMS integration makes the <code>dt_android_adb</code> transport much
more convenient when debugging on an Android device, but when working with
Dalvik on the desktop it makes sense to use the TCP/IP transport.
</p><p>
<h2>Known Issues and Limitations</h2>
</p><p>
Most of the optional features JDWP allows are not implemented. These
include field access watchpoints and better tracking of monitors.
</p><p>
Not all JDWP requests are implemented. In particular, anything that
never gets emitted by the debuggers we've used is not supported and will
result in error messages being logged. Support will be added when a
use case is uncovered.
</p><p>
&nbsp;
</p><p>
The debugger and garbage collector are somewhat loosely
integrated at present. The VM currently guarantees that any object the
debugger is aware of will not be garbage collected until after the
debugger disconnects. This can result in a build-up over time while the
debugger is connected. For example, if the debugger sees a running
thread, the associated Thread object will not be collected, even after
the thread terminates.
</p><p>
The situation is exacerbated by a flaw in the exception processing code,
which results in nearly all exceptions being added to the "do not discard"
list, even if the debugger never sees them. Having a debugger attached
to a program that throws lots of exceptions can result in out-of-memory
errors. This will be fixed in a future release.
</p><p>
The only way to "unlock" the references is to detach and reattach the
debugger.
</p><p>
&nbsp;
</p><p>
The translation from Java bytecode to Dalvik bytecode may result in
identical sequences of instructions being combined. This can make it
look like the wrong bit of code is being executed. For example:
<pre> int test(int i) {
if (i == 1) {
return 0;
}
return 1;
}</pre>
The Dalvik bytecode uses a common <code>return</code> instruction for both
<code>return</code> statements, so when <code>i</code> is 1 the debugger
will single-step through <code>return 0</code> and then <code>return 1</code>.
</p><p>
&nbsp;
</p><p>
Dalvik handles synchronized methods differently from other VMs.
Instead of marking a method as <code>synchronized</code> and expecting
the VM to handle the locks, <code>dx</code> inserts a "lock"
instruction at the top of the method and an "unlock" instruction in a
synthetic <code>finally</code> block. As a result, when single-stepping
a <code>return</code> statement, the "current line" cursor may jump to
the last line in the method.
</p><p>
This can also affect the way the debugger processes exceptions. The
debugger may decide to break on an
exception based on whether that exception is "caught" or "uncaught". To
be considered uncaught, there must be no matching <code>catch</code> block
or <code>finally</code> clause between the current point of execution and
the top of the thread. An exception thrown within or below a synchronized
method will always be considered "caught", so the debugger won't stop
until the exception is re-thrown from the synthetic <code>finally</code> block.
</p><p>
<address>Copyright &copy; 2009 The Android Open Source Project</address>
</p>
</body>
</html>