Dalvik——Dalvik调试器指南


Dalvik vm支持许多常见开发环境下的代码级调试,任何允许JDWP下远程调试的工具都可以使用,其支持的调试器包括jdb、Eclipse、InterliJ和JSwat。
    vm不支持基于JVMTI(java virtual machine toolinterface)的工具,这相对而言是插入式接触(CR:不懂,relatively intrusive approach),依赖于字节码插入,这些vm暂时不支持。
    dalvik对于JDWP的实现也包括对DDM特性的支持,为人们所知的实现是DDMS(Dalvik Debug Monitor Server)和Eclipse ADT plugin。该协议和vm的交互见上篇《Dalvik调试监视器》。
    vm中对所有调试器的支持都在dalvik/vm/jdwp目录,它基本上和其他的vm源码都独立,而dalvik/vm/Debugger.c搭建了两者之间的桥梁。这么做的目的是便于在其他项目中复用JDWP代码。

1、实现
    任何调试状态的vm都启动一个jdwp线程,该线程处于空闲状态直到DDMS或者调试器连接它。该线程只负责处理调试器来的请求,而vm发起的通信(例如,当vm在断点停下来的时候告知调试器)都由相应的线程发出。
    当vm从Android应用framework中启动时,在系统属性ro.debuggable置为1(用adb shell getprop ro.debuggable 来检查它)时可以对所有应用进行调试;若为0,调试会根据应用的manifest来进行,如果<application>项中包含了android:debuggable="true"则可以调试。
    vm可识别ddms和调试器连接的不同(也就是直接连ddms和通过ddms连接)。单独从ddms的连接并不会导致vm行为的变化,但是当vm看到调试器发来的包时,它就会分配额外的数据结构,甚至切换到一个不同的解释器。
    由于dalvik将字节码映射到只读内存空间,一些常用的技术不通过分配额外的内存就难以实现。例如,假设调试器在一个方法中设置了断点,简单的处理方法是在代码中直接插入一个断点指令,当指令送达vm的时候,断点处理函数进行处理。如果不这样,就需要进行“这里有断点吗?”式的扫描。即使有优化,可调试的解释器也比通常的解释器要慢很多(可能到5倍)。
    JDWP协议是无状态的,因此vm在调试器请求到来的时候就处理,在发生事件的时候就发送给vm。

2、调试数据
    源代码调试数据通过java编译器发出,它包含了从源代码到字节码的映射、描述寄存器存储方法参数和本地变量的列表,可指定参数不发出它。当dx转换java字节码到dalvik字节码的时候,它也必须转换该调试数据。
    dx必须保证它不执行妨碍调试器的指令。例如,多次使用保存方法参数的寄存器和this指针在dalvik字节码是被允许的,如果该值从未被用或不再需要,但是这对于调试器来说会很不解,因为这些值在方法中,调试器不愿意他们丢失。因此,dx在调试器使能的时候有时产生非理想的代码。
    一些调试数据被用作其他作用,例如,包含文件名和行号数据在产生异常堆栈追踪时很必要。这些数据可以扔掉,为了保证dex文件更小。

3、使用
    正如其他流行的桌面虚拟机所做的,dalvik vm支持许多命令行flag。启动调试状态的vm,你需要通过基本的选项增加命令行flag。基本的命令如下:
-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y
    或
-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=y
    在前缀后面,选项以“名称=值”的形式提供,dalvik vm支持的选项如下:
transport (无默认)
    传输机器码用,dalvik支持tcp/ip socket(dt_socket)和通过adb连接到usb(dt_android_adb)。
server (默认='n')
    决定vm是作为客户端还是服务器,当作为服务器的时候,vm等待调试器连接它,当作为客户端的时候,vm尝试连接在等待状态的调试器
suspend (默认='n')
    如果设为y,vm会等待调试器连接再执行应用程序代码,当调试器连接后(或当vm完成和调试器连接后),vm告诉调试器它刮起了,然后不会做任何事除非被告知resume。如果设为n,vm会率先执行。
address (默认="")
    在server=n时,这必须是“hostname:port”形式,但是在server=y的时候只要指定port。这指定了要连接或要监听的ip地址和端口号。
    监听端口0有特殊含义:试图监听端口8000,如果失败试图连接8001、8002...以此类推。
    该参数对于transport=dt_android_adb无意义。
help (无参数)
    帮助信息
launch, onthrow, oncaught, timeout
    这些参数会被接受,然后被忽略。

    要通过usb使用DDMS调试android设备上的程序,你要使用命令:
% dalvikvm -agentlib:jdwp=transport=dt_android_adb,suspend=y,server=y -cp /data/foo.jar Foo
    这告诉dalvik vm在调试状态下运行程序,监听来自DDMS的连接,等待调试器。该程序会在进程列表上产生“?”名字的应用,因为它不是来自android应用列表中,从这儿你可以连接你的调试器到对应的DDMS监听端口(例如:在应用列表中选定“?”应用,输入jdb -attach localhost:8700)
  
    要通过adb使用tcp/ip调试android设备上的程序,你必须先使用命令:
% 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
    然后jdb -attach localhost:8000。
(上面的例子中,vm会在你连接时挂起,在jdb中,类型cont会继续)
   
    DDMS的集成使得dt_android_adb传输在调试android设备时更加方便,但是当使用桌面dalvik的时候,使用tcp/ip更好。

4、须知和缺陷
    大多数JDWP的可选项都没有实现,包括filed access watchpoint(CR:不太了解)和更号的追踪监视器。
    不是所有的JDWP请求都实现,任何我们使用的调试器不会发出的信息都没有实现,这会导致log一个error信息,当使用到的时候我们才会是实现它。
    调试器和GC当前没有高度集成,当前vm只保证在调试器中断连接之前,任何调试器识别的对象都不会被GC,这会在调试器连接时随着时间导致累积。例如,当调试器监视一个运行的线程,相关的线程对象就不会被收集,即使线程终结。
    这种情形在异常处理代码中会加重,导致几乎所有的异常都加到“do not discard”列表中,即使调试器没有监视他们。在调试会触发一大堆异常的程序时,会导致内存溢出错误。这些会在未来版本中修正。
    唯一“解锁”reference的方式是拆开重装调试器。(CR:不懂)
    从java字节码到dalvik字节码的转换回导致相同的指令序列被合并,这在执行中会看上去像错误代码。例如:
    int test(int i) {
        if (i == 1) {
            return 0;
        }
        return 1;
    }
    dalvik字节码对两次return都使用常见的return指令,因此当i=1的时候调试器单步执行时会先return 0然后return 1。
    dalvik处理同步的方法时和其他vm不同,其他vm都是把方法标记为synchronized然后等待vm来处理锁,而dx在方法顶层加了“lock”指令同时在“finally”程序块中增加了“unlock”指令。因此,当单步执行return语句时,“当前行”光标可能导致跳到方法的最后一行。
    这会影响调试器处理异常的方法。调试器可能会基于异常“捕捉”或“未捕捉”来发出异常。如果认为是“未捕捉”,应该没有catch程序块或者finally语句在当前执行行和线程顶端之间。在同步方法中或下面抛出的异常会被认为是“捕捉”,因而调试器不会停下来,直到异常被finally程序块重新抛出。

==================================CUT============================================

source:http://www.netmite.com/android/mydroid/2.0/dalvik/docs/debugger.html

Dalvik Debugger Support

The Dalvik virtual machine supports source-level debugging with many popular development environments. Any tool that allows remote debugging over JDWP (the Java Debug Wire Protocol) is expected work. Supported debuggers include jdb, Eclipse, IntelliJ, and JSwat.

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.

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 here.

All of the debugger support in the VM lives in the dalvik/vm/jdwp directory, and is almost entirely isolated from the rest of the VM sources. dalvik/vm/Debugger.c bridges the gap. The goal in doing so was to make it easier to re-use the JDWP code in other projects.

Implementation

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.

When the VM is started from the Android app framework, debugging is enabled for all applications when the system property ro.debuggable is set to 1 (use adb shell getprop ro.debuggable to check it). If it's zero, debugging can be enabled via the application's manifest, which must include android:debuggable="true" in the <application> element.

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.

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).

The JDWP protocol is stateless, so the VM handles individual debugger requests as they arrive, and posts events to the debugger as they happen.

Debug Data

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 dx converts Java bytecode to Dalvik bytecode, it must also convert this debug data.

dx must also ensure that it doesn't perform operations that confuse the debugger. For example, re-using registers that hold method arguments and the "this" 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, dx generates sub-optimal code in some situations when debugging support is enabled.

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 dx to make the DEX file smaller.

Usage

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:

-Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y

or

-agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=y

After the initial prefix, options are provided as name=value pairs. The options currently supported by the Dalvik VM are:

transport (no default)
Communication transport mechanism to use. Dalvik supports TCP/IP sockets (dt_socket) and connection over USB through ADB (dt_android_adb).
server (default='n')
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.
suspend (default='n')
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.
address (default="")
This must be hostname:port when server=n, but can be just port when server=y. This specifies the IP address and port number to connect or listen to.
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.)
This option has no meaning for transport=dt_android_adb.
help (no arguments)
If this is the only option, a brief usage message is displayed.
launch, onthrow, oncaught, timeout
These options are accepted but ignored.

To debug a program on an Android device using DDMS over USB, you could use a command like this:

% dalvikvm -agentlib:jdwp=transport=dt_android_adb,suspend=y,server=y -cp /data/foo.jar Foo

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. jdb -attach localhost:8700 after selecting it in the app list).

To debug a program on an Android device using TCP/IP bridged across ADB, you would first need to set up forwarding:

% 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

and then jdb -attach localhost:8000.

(In the above examples, the VM will be suspended when you attach. In jdb, type cont to continue.)

The DDMS integration makes the dt_android_adb 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.

Known Issues and Limitations

Most of the optional features JDWP allows are not implemented. These include field access watchpoints and better tracking of monitors.

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.

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.

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.

The only way to "unlock" the references is to detach and reattach the debugger.

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:

int test(int i) {
        if (i == 1) {
            return 0;
        }
        return 1;
    }

The Dalvik bytecode uses a common return instruction for both return statements, so when i is 1 the debugger will single-step through return 0 and then return 1.

Dalvik handles synchronized methods differently from other VMs. Instead of marking a method as synchronized and expecting the VM to handle the locks, dx inserts a "lock" instruction at the top of the method and an "unlock" instruction in a synthetic finally block. As a result, when single-stepping a return statement, the "current line" cursor may jump to the last line in the method.

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 catch block or finally 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 finally block.

Copyright © 2009 The Android Open Source Project

相关内容