理解WebKit和Chromium: 調試Android系統上的Chromium

轉載請註明原文地址:http://blog.csdn.net/milado_njuhtml

1. Android上的調試技術

在Android系統上,開發人員可以使用兩種不一樣的語言來開發應用程序,一種是Java語言,開發人員使用的是Android SDK來配置和編譯這些代碼,生成Java語言的class文件,也就是Java虛擬機執行的二進制代碼。Android系統使用.dex文件將一系列的class文件壓縮在一塊兒。第二種是C/C++語言,使用Android NDK來配置和編譯這些代碼。這些代碼通過NDK編譯後就是彙編碼併合成動態連接庫,也就是.so文件。調試Java代碼和C/C++代碼需要不一樣的技術和方法,如下分別來介紹它們。

1.1 前提條件

因爲Android是基於Java語言的,因此不論什麼應用程序都離開不開Java代碼。而對於一個Android應用程序,開發人員是否使用C++代碼來編寫部分邏輯則是可選的。因此,對於許多Android應用程序來講,僅僅Java語言就已經足夠了。但是對於Chromium來講,調試C++代碼是必需的。

想要調試Android的Java代碼,首先需要的是Oracle JDK(現在也可以使用OpenJDK了)和Android SDK,它們各自是Java的執行和調試工具,Android開發和調試的各類工具。其實,爲了開發和調試上的方便,開發人員一般也需要下載Eclipse。Android團隊爲了方便開發人員,將這些工具都打包在一塊兒,詳情見這裏:http://developer.android.com/tools/index.html。

因爲Android設備是小型設備,不適合用來做爲開發人員的開發和調試機器,因此調試Android應用程序通常是使用PC來完畢的,筆者偏向使用個人Linux開發機器來調試Android應用程序。因此,調試Android應用程序實際上需要調試機器和被調試的Android設備。上面說到的那些JDK和SDK工具都是安裝在開發機器上的。開發人員可以使用實際的設備來調試應用程序,同一時候,也可以經過建立一個虛擬設備來模擬Android設備執行應用程序。讀者可以經過如下的連接來獲取建立虛擬設備的相關方法: http://developer.android.com/tools/devices/index.html

從上面可以看出,調試Android應用程序實際上涉及兩個硬件設備(或者虛擬)。不一樣於調試Linux上的應用程序,因爲開發人員一般就是使用本地調試器來調試當前機器上執行的程序。在調試Android環境中的應用,開發人員一般需要使用到「遠程調試」的技術。遠程調試需要涉及到兩個不一樣的設備或者機器,而調試工具和被調試程序二者可以經過其餘輔助設施和套接字來通訊,以完畢調試功能。

1.2 開啓/關閉調試功能

想要調試某個Android設備上的應用程序,必須首先要打開該設備上的「開發人員選項」。在一些設備中,該選項是缺省不顯示的,需要用戶在「設置」中的「關於手機」中的Android信息上連續點擊屢次以後纔會出現「開發人員選項」。一般,「開發人員選項」會出現「USB調試」選項,開發人員需要打開該選項才幹夠經過開發機器調試該設備上的應用程序。
在「開發人員選項」中還有許多對開發人員很實用的子選項,好比「不鎖定屏幕」。筆者很喜歡這一功能,它有助於開發人員調試程序而不會出現設備老是鎖屏等煩人的狀況。

當設置完Android設備以後,後面面臨的問題就是被調試程序,這個對於調試Java代碼來講也要細細的說一下。
首先來講一個沒有被「root」的Android設備,也就是一般你們可以看到的設備。在這樣的狀況下,在編寫被調試程序的時候,需要在AndroidManifest.xml中做相應的設置,也就是"Application"元素的屬性「debuggable」需要設爲「true」,這樣在上面說到的「開發人員選項中」的子菜單「選擇調試應用」就可以找到需要被調試程序的名稱。關於調試的屬性,見如下的文章:
http://developer.android.com/guide/topics/manifest/application-element.html
然後,對於一個被"root"過的Android設備來講,開發人員可以調試不論什麼應用程序,因此不需要上面涉及的設置「debuggable」屬性等過程。所有的應用程序都可以被DDMS(davilk debugger monitor service)看到,因此開發人員可以使用調試器來調試它們。

1.3 Java代碼調試基礎

首先來看調試Android應用程序中Java代碼的工做方式。 下圖是Android官方站點上給出的調試應用的架構圖,其主要包括兩個部分,也就是Android設備(或者Android模擬器)和遠程調試機器,二者直接經過USB來鏈接,而負責調試的框架則是"adb"等工具所提供。



在Android設備中,當需要調試某個應用程序的時候,設備端的「adbd」會創建和應用之間的通訊方式,並將經過USB創建同調試端的"adb host daemon"的鏈接。「adb host daemon」就是在開發機器中執行的後臺服務進程,在安裝Android SDK以後,使用「adb」會觸發建立該服務進程。
實際上這一過程仍是基於Java的遠程調試協議來進程的,Android設計者們將它們直接拿過來使用。特別之處在於,Android創建了Java調試器和被調試程序之間的鏈接,這些鏈接是Android特別設計的。上圖可以幫助你們理解這一過程。
「adb」是Android SDK中很重要的工具,它可以用來安裝、卸載應用程序,執行各類命令等,並且用來幫助調試Android應用。而DDMS稱爲
Dalvik Debug Monitor Server,可以提供port forwarding、屏幕截取、獲取應用程序各類執行信息的一中工具,很實用。在Eclipse中,開發人員可以使用它相應用進行深層次的分析,細節不在這裏的討論範圍內。

1.4 C/C++代碼調試基礎

因爲並非所有的Android應用都使用C/C++代碼和Android NDK來編寫,因此這部分的內容對於調試某些應用實際上是可選的。但是因爲Chromium的絕大部分代碼都是使用C++來編寫的,因此調試本地代碼是對學習Chromium來講是一個很重要的部分。下圖是筆者理解的本地代碼的調試基礎框架。java


同Java遠程調試類似的是也是需要Android設備和開發機器的協同工做。首先看Android設備上的應用。不一樣於Java遠程調試,這裏主要使用gdbserver來輔助調試,gdbserver是包括在NDK中並需要執行在Android設備上的。而後看開發機器上的gdb。讀者應該不會陌生,gdb就是一個調試工具,不一樣於一般見到的,這裏它可以調試一個遠程的應用,只是它首要鏈接的是一個gdbserver。經過ADB所提供的port forwarding技術,gdb就可以同gdbserver進行通訊,整個調試過程就天然串聯起來。
要調試Android應用的本地代碼相同需要上面類似的過程,也就是開啓USB調試功能。不一樣點在於,僅僅有被「root"後的Android設備才幹被使用GDB調試本地代碼,固然還有許多其餘的調試技術不需要「root」設備,但是就使用gdb調試代碼而言,開發人員依舊需要該Android設備被「root」過,這一條件顯得太過於苛刻,但是筆者眼下沒有發現更好的辦法。

1.5 各類調試技術

除了使用Java調試器和GDB調試器調試代碼,另外一些其餘的技術用來幫助開發人員調試應用的一些問題,典型的好比Android的Logging機制、ANR、tombstone、內存分析等等,後面逐步介紹它們。

2. 調試Chromium代碼

因爲自己Chrome瀏覽器的代碼沒有所有開源出來,因此如下以調試Android系統上的Content Shell爲例來講明怎樣調試Chromium代碼的Java代碼和本地代碼。

2.1 調試Content Shell 的Java代碼

值得注意的是,當使用虛擬設備來執行Chromium應用程序的時候,必定需要打開"-gpu on"這個選項,也就是使用開發機器的GPU來加速虛擬設備的圖形操做,不然,Chromium不能在虛擬設備中執行。
如下這樣的是直接從Eclipse中啓動某應用然後應用直接停在開發人員設置的斷點處。主要是由調試器自己啓動應用,於是在啓動應用的時候就會直接創建好調試所使用的環境設置。這一方法的長處就是可以設置隨意的斷點,並且都可以在斷點處暫停執行。
上面說到的調試方法對於Chromium來講並不適用,緣由在於Chromium不是直接由Eclipse建立並且編譯的項目,Chromium使用本身一套複雜的腳原本編譯本身的生成結果,儘管仍然依賴Android SDK和NDK。那麼假設調試這樣的類型的應用呢?對於Content Shell來講,一般使用到的技術就是成爲一種"post mortem"的技術,也就是說直接在Android中點擊啓動某應用,而後再使用調試工具鏈接(attach)上該應用。這是因爲眼下沒法使用eclipse來編譯Content Shell而生成應用的APK。那麼假設想要調試一個Java代碼,可能開發人員來不及鏈接上該應用,代碼已經被執行過了,有什麼辦法呢?
Android提供了接口,也就是android.os.Debug.waitForDebugger()。C ontent Shell已經支持該機制,因此僅僅需要在開發及其上執行例如如下命令就能夠:
adb shell echo "chrome --wait-for-java-debugger" >>/data/local/tmp/content-shell-command-line
這樣在Content Shell啓動的時候,該應用首先讀入上述「content-shell-command-line"文件,並且等待調試器鏈接上而後由調試器決定什麼時候繼續往下執行。在某些設備上,因爲「開發人員選項」中已經包括了「等待調試器」子選項,因此開發人員並不需要上面複雜的設置,而是直接打開該選項就能夠。
那麼讀者可能要問了,Eclipse和Android調試工具需要怎麼設置才幹附着並鏈接上Content Shell應用程序呢?答案是在Eclipse中建立一個已有的Android項目,該項目指向Content Shell所在的根文件夾(該文件夾包括了Content Shell所使用的AndroidManifest.xml,讀者很easy在Chromium源碼中找到),這樣就建立了該Android項目。編譯只是沒有關係,開發人員仍然可以調試Content Shell應用。同一時候,該項目不會包括所有的Java代碼,因此開發人員可以導入這些源碼,這樣就可以方便的調試Content Shell應用了。下圖是調試Chrome Shell(類似於Content Shell)的演示樣例。

2.2 調試Content Shell的本地代碼

Chromium的所有代碼都會編譯成一個動態庫,該動態庫會被包括在終於的Android APK中。實際上該動態庫的大小很大,超過1G大小,因此通常是將所有的符號信息去除以後才被包括到終於生成的APK中的。
調試Content Shell中的本地代碼需要例如如下步驟:
I. 使用ADB命令從設備中將app_process和系統庫下載到本地開發機器上,放入文件夾「/absolute-source-path/out/target/product/product-name/symbols/system/lib」中。
II. 創建基於USB的鏈接也就是使用port forward機制,好比 adb forward tcp:5039 tcp:5039
III. 啓動需要被調試的Android應用。
IV. 將gdbserver從NDK中複製到Android設備上,並在Android設備中啓動「gdbserver」:gdbserver :5039 /system/bin/executable or gdbserver :5039 --attach pid
V. 在開發機器上啓動gdb並執行例如如下操做:
file app_process
directory /absolute-source-path/src
set solib-absolute-prefix /absolute-source-path/out/target/product/product-name/symbols
set solib-search-path /absolute-source-path/out/target/product/product-name/symbols/system/lib
target remote :5039
shared
上面創建了調試工具和被調試應用之間的鏈接,開發人員可以像傳統使用gdb的方式來調試應用程序了。 實際上調試Content Shell等Chromium編譯出來的應用並不需要開發人員反覆上面複雜的過程,因爲源碼中已經包括了各類調試腳本,見$CHROMIUM_SRC/build/android/中的以「adb_gdb*"開頭的各個腳本,好比調試Content Shell直接使用adb_gdb_content_shell。這些腳本的目的就是執行上面所說的各個步驟,並且包括了靈活的擴展功能,可以設置參數來調試不一樣類型的編譯結果和應用的不一樣進程(好比browser進程和sandboxed的renderer進程)。
要調試這些應用的本地代碼,僅僅能使用前面說的"post mortem"技術。Chromium使用了wait-for-debugger來等待gdb鏈接到該應用上,方法就是類似於上面提到的「等待調試器」技術,僅僅只是否是Java Debugger而已:
adb shell echo "chrome --wait-for-debugger" >>/data/local/tmp/content-shell-command-line

3. 其餘調試技術

3.1 Logging

經過在代碼中打印出不一樣級別的日誌信息,開發人員很easy知道應用成語在執行過程當中的狀態信息,而這並不需要各類各樣的調試工具和「root」Android設備等麻煩步驟。更好的是,在Java代碼和C/C++代碼中都可以使用該功能:
Java代碼使用:android.util.Log類,詳情請見。
C/C++代碼使用__android_log_print()函數。不要直接使用c的printf函數,那樣在Android的控制檯中看不到輸出信息。
由此,開發人員可以使用Android的"logcat"命令來查看日誌輸出的結果信息,如如下所看到的的命令:
adb shell logcat -b main -v threadtime

3.2 Tombstone

當某個應用發生崩潰的時候,Android系統會爲該應用生成一種稱爲「tombstone」的文件,該文件包括了該應用發生崩潰時候的現場信息,包括調用棧、寄存器信息、線程信息等等。只是,因爲應用一般並無包括符號信息的動態庫,因此開發人員比較難以發現出錯的未知。下圖是一個tombstone文件:

好比在Chromium的Android版中,帶有符號信息的動態庫超過1G大小,這顯然不利於把它直接放在APK文件裏,那麼怎麼辦呢?
筆者本身基於他人的開源碼編寫了一個python腳本,使用它,開發人員可以輕易的輸入tombstone文件和帶有符號信息的動態庫,獲得崩潰發現時候的調用棧,裏面包括了具體的代碼調用過程,很方便並且有利於發現問題。該腳本的主要思想是利用NDK提供的arm-eabi-addr2line工具來將地址轉換爲源碼中的位置信息。基本的命令例如如下:
cmd = "arm-linux-androideabi-addr2line"  + " -f -e " + SYMBOLS_DIR + lib  + " 0x" + addr

3.3 ANR

ANR的含義是指Application not responding,主要是指UI在指定的時間內沒有響應,這時候Android系統會彈出一個對話框提示用戶是等待仍是殺死該應用程序。想要了解它背後的出錯的具體信息和調用棧也很easy,開發人員可以使用如下的命令來獲取:
adb shell dumpsys input

3.4 其餘

在Android提供的開發人員工具中,讀者還可以發現許多其餘用於調試程序的工具,好比用於收集內存使用、內存泄露信息的工具,它們可以幫助分析性能數據。好比DDMS提供的「hprof」,shell命令中的dumpsys等等。它們對於分析許多問題很實用。限於篇幅這裏再也不一一介紹。

4. 參考資料

  • 4.1 https://sites.google.com/a/itspaclub.com/www/android-debug/android-debug-theory/1-4-debugging-techniques
  • 4.2 http://www.kandroid.org/online-pdk/guide

相關文章
相關標籤/搜索