转载请注明原文地址: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