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
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.
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.
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.
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:
dt_socket
) and connection over USB through ADB (dt_android_adb
).
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. transport=dt_android_adb
.
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.
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.