安卓* 系统级 Java*/C++ 代码调试

1 简介

应用开发人员当前可通过安卓* SDK 来设计和构建安卓应用项目,并使用 Eclipse* 进行调试。 但是它并没有提供相应的功能来调试系统级 Java*/C++ 代码,该代码位于安卓代码库中,不能使用 Android SDK 来构建和调试。 本文介绍了如何使用 Eclipse 来调试安卓系统级 Java/C++ 代码。

2 安装

2.1 安装 JDK

通过下列链接下载 JDK6 (Java SE 6 更新软件包): http://java.sun.com/javase/downloads/index.jsp
当前的 JDK6 名为 jdk-6u32-linux-x64.bin。 我们将举例说明如何使用。

先进行安装,然后使用默认的 JDK。

$ cd /usr/lib/jvm
$ sudo /path/to/jdk-6u32-linux-x64.bin
$ sudo ln -s jdk1.6.0_32 java-6-sun
$ export PATH=/usr/lib/jvm/java-6-sun/bin:$PATH
$ export JAVA_HOME=/usr/lib/jvm/java-6-sun

 

2.2 安装 Eclipse

通过下列网站下载 Eclipse 3.6.2 或更高版本:http://www.eclipse.org/downloads/
当前的 Eclipse Classic 版本时 3.7.2 (Indigo),下面让我们用它来举例说明。

$ mkdir ~/android-sdk
$ cd ~/android-sdk
$ tar zxf /path/to/eclipse-SDK-3.7.2-linux-gtk-x86_64.tar.gz

 

2.3 安装 Eclipse CDT 插件

要创建 C/C++ 项目并调试 C++ 代码,我们需要安装 CDT 插件。
启动 Eclipse。

$ cd ~/android-sdk/eclipse/
$ ./eclipse

 

如果系统中存在防火墙,请设置代理程序: “Window->Preferences->General->Network Connection”,如图 1 所示。


图 1. Eclipse 代理程序设置

点击“Help->Check for updates”,下载最新的 Eclipse 插件列表。
进入“Window->Preferences->Install/Update->Available Software Sites”并启用

http://download.eclipse.org/sequoyah/updates/2.0/

 


图 2. Eclipse 软件更新

安装 CDT 插件: 进入“Help->Install New Software”。
选择 “http://download.eclipse.org/sequoyah/updates/2.0/”,取消选择“Group items by category”复选框,然后选择并安装所有插件。

图 3. 安装 Sequoyah 插件

 

2.4 安装安卓 SDK

从下列网站下载 Android SDK: http://developer.android.com/sdk/index.html  当前的安卓 SDK 是 r18, 下面让我们用它来举例说明。
安装它:

$ cd ~/android-sdk/
$ tar zxf /path/to/android-sdk_r18-linux.tgz

启动安卓软件包管理器以下载安卓 SDK 软件包:

$ cd ~/android-sdk/android-sdk-linux/tools/
$ ./android

设置代理程序并选中 “Force https://...sources to be fetched using http://”

图 4. 安卓 SDK 管理器代理程序设置

下载您感兴趣的软件包(必须下载 Tools 软件包)

图 5. 安装安卓 SDK 更新

2.5 安装安卓 ADT 插件

安装安卓 ADT(安卓开发工具)。
启动 Eclipse。

$ cd ~/android-sdk/eclipse/
$ ./eclipse

 

点击“Help->Install New Software”,然后点击 “Add Repository” 按钮。
在出现的 “Add Repository” 对话框中,在 “Name” 处输入“ADT Plugin”,在 “Location” 处输入下列 URL:

https://dl-ssl.google.com/android/eclipse/

图 6. 安装 ADT 插件

 

按照下列步骤安装所有 ADT 插件。
然后进入“Window->Preference->Android” ,并在标有 “SDK location” 的框内输入安卓 SDK 路径。

图 7. 安卓 SDK 设置

 

2.6 安装安卓系统调试实用程序

保存下面的软件包,并将其解压到您的 Linux* 主机上。

$ cd ~/android-sdk
$ tar zxf android-debug-utility.tar.gz

3 调试安卓系统 Java 代码

3.1 调试 system_process

 

system_process 进程被称作安卓系统服务器。 系统启动时,zygote 对该进程进行分解(fork)。 下面我们来看一下如何使用 Eclipse 来调试该线程。

3.1.1 为 system_process 创建一个 pseudo(伪)安卓项目。

 

Dalvik 调试监测程序服务器(DDMS)只能在安卓项目中调试代码。 system_process 是从安卓代码库而非安卓项目中构建。 如要使用 Eclipse/DDMS 调试它,我们需要为其创建一个pseudo(伪)安卓项目。
进入“File->New->Project”,然后选择 “Android project”。

 


图 8. 创建一个安卓项目

 


图 9. 安卓软件包名称

 

点击“AndroidManifest.xml”,然后将软件包名称修改为 “system_process”。

             
图 10. 修改安卓软件包名称

3.1.2 使用 DDMS 调试 system_process

 

转至 DDMS 视图,选择 system_process,然后点击“debug”按钮。

 


图 11. Android DDMS 视图

转至调试视图,选择希望调试的任意线程,点击“suspend”按钮。

 


图 12. 安卓调试视图

要执行 Java 源代码级调试,我们需要指定安卓来源的位置。 点击“Edit Source lookup Path”按钮,然后点击“Add”按钮,选择“File System Directory”。 frameworks/base 和 libcore 是两个放置安卓 Java 代码的主要目录。

选择 Android frameworks/base。


图 13. 设置框架基本源路径

此外,我们还需要添加 libcore 目录。

 


图 14.设置 libcore 源路径

现在您将在编辑器窗口中看到 Java 代码。

 

图 15. 安卓项目调试工具

我们现在可以设置断点,并对安卓系统级 Java 代码进行调试。

3.2 调试安卓内建应用

安卓系统内建了许多应用(即所谓的系统应用),例如 Calendar、Settings、Gallery 等。这些项目位于安卓代码库中,并使用 system.img 进行构建。要调试 them?,我们需要为其创建 pseudo(伪) 项目,就像 system_process。 您需要将软件包名称更改为应用名称,而不是输入“system_process”。

 


图 16. 安卓应用软件包名称

然后转至 DDMS,选择“com.android.calendar”进程,并点击“debug”按钮。

 


图 17. 选择要调试的应用

 


图 18. 安卓应用调试工具

 

4 调试安卓系统 C/C++ 代码

4.1 调试 system_process

我们通常可以使用 gdbserver/gdb 来调试安卓原生 C/C++ 代码。 也就是说,使用 gdbserver 连接至正在运行的进程,然后使用 gdb 进行远程调试。 但是,system_ process 是由 zygote 在系统启动时创建,我们在连接前初始化已经完成。 要从一开始调试 system_process,我们要使用安卓调试实用程序,强迫 system_process 在开始的时候进入一个无限循环,以便我们能够尽早地建立连接。 然后,我们会使 system_process 退出无限循环并继续运行。

4.1.1 创建 C/C++ 项目

我们可以创建一个新的 C/C++ 项目。 此外,我们还可以将现有的安卓项目“system_process”转化为 C/C++ 项目。 点击“File->New->Convert to a C/C++ Project (Adds C/C++ Nature)”,选择 system_process 项目:

 


图 19. 将安卓项目转化为 C/C++ 项目

 

4.1.2 创建调试配置

转至“Debug”视图,点击“调试按钮->Debug Configurations”,创建一个 C/C++ 远程应用调试配置,输入 app_process 二进制的名称,并禁用自动构建功能。

 


图 20. 将应用映像设置为调试

 

转至安卓调试实用程序所在的目录,编辑 gdb 初始化脚本 sdk_x86_gdb.setup,用您的安卓根目录和设备名称来替换 $android 和 $__board,以便使 gdb 判别正确的符号文件目录:

shell echo set solib-absolute-prefix $android/out/target/product/$__board/symbols > 
tmp.gdb
shell              echo                   set                     solib-search-path 
$android/out/target/product/$__board/symbols/system/lib:$android/out/target/product/$
__board/symbols/system/lib/hw:$android/out/target/product/$__board/symbols/system/li
b/egl:$android/out/target/product/$__board/symbols/system/lib/soundfx:$android/out/tar
get/product/$__board/symbols/system/lib/bluez-plugin:$android/out/target/product/$__b
oard/symbols/system/vendor/lib/egl:$android/out/target/product/$__board/symbols/syste
m/vendor/lib/hw:$android/out/target/product/$__board/symbols/system/vendor/lib:$andr
oid/out/target/product/$__board/symbols/system/lib/parameter-framework-plugins/Audio:
$android/out/target/product/$__board/symbols/system/lib/soundfx:$android/out/target/p
roduct/$__board/symbols/system/usr/lib/alsa-lib >> tmp.gdb 
source tmp.gdb

或者在启动 Eclipse 之前导出两个环境变量:

$ export android=<android root directory>
$ export __board=<devicename>

点击“Debugger”选项卡,输入 sdk_x86_gdb.setup 的路径:


图 21. 设置 gdb 初始化脚本

点击“Connection”选项卡,将端口号改为 1234。

 


图 22. 设置 gdb 调试端口

4.1.3 调试 system_process

启动安卓设备,并使用 USB 线缆将其连接至主机。 重启 system_process,并使用 gdbserver 进行连接:

$ cd ~/android-sdk/android-debug-utility/target
$ ./start_system_server.sh
zygote:115
killall: gdbserver: no process killed
killall: gdb: no process killed
/lib/gdbserver: No such file or directory
mkdir failed for /lib, File exists
push: ./lib/ld-linux.so.2 -> /lib/ld-linux.so.2
push: ./lib/system_server -> /lib/system_server
push: ./lib/libthread_db.so.1 -> /lib/libthread_db.so.1
push: ./lib/libpthread.so.0 -> /lib/libpthread.so.0
push: ./lib/libc.so.6 -> /lib/libc.so.6
push: ./lib/libreadline.so.5.2 -> /lib/libreadline.so.5.2
push: ./lib/libreadline.so.6 -> /lib/libreadline.so.6
push: ./lib/gdb -> /lib/gdb
push: ./lib/libdl.so.2 -> /lib/libdl.so.2
push: ./lib/app -> /lib/app
push: ./lib/libz.so.1 -> /lib/libz.so.1
push: ./lib/libm.so.6 -> /lib/libm.so.6
push: ./lib/libutil.so.1 -> /lib/libutil.so.1
push: ./lib/libexpat.so.1 -> /lib/libexpat.so.1
push: ./lib/libpython2.6.so.1.0 -> /lib/libpython2.6.so.1.0
push: ./lib/libcrypto.so.0.9.8 -> /lib/libcrypto.so.0.9.8
push: ./lib/service -> /lib/service
push: ./lib/libssl.so.0.9.8 -> /lib/libssl.so.0.9.8
push: ./lib/gdbserver -> /lib/gdbserver
push: ./lib/libncurses.so.5 -> /lib/libncurses.so.5
20 files pushed. 0 files skipped.
1770 KB/s (10526293 bytes in 5.805s)
4 KB/s (440 bytes in 0.099s)

请通过 gdb 远程连接系统服务器

已连接;pid = 309

监听端口 1234

 

使用 adb 将本地 TCP 端口 1234 转发至安卓设备:

$ adb forward tcp:1234 tcp:1234

 

要使用 Eclipse 工具,请转至 Debug 视图:

“调试按钮->Debug Configurations”,选择配置“system_process_1234”,然后点击“Debug”按钮。

 


图 23. 启动 C/C++ 调试工具

等待 gdb 连接至 gdbserver:

 


图 24. system_server C/C++ 调试工具

暂停任务、转至 Console(控制台)并输入命令“thread 1”和“go”,使任务退出无限循环。

thread 1
[Switching to thread 1 (Thread 309)]
#0  setgid () at bionic/libc/arch-x86/syscalls/setgid.S:10
10      pushl   %ebx
go
system server in dead loop, trying to restore...done

设置断点,然后对代码进行调试。

4.2 调试安卓应用

与 system_process 类似,安卓应用也是通过 zygote 进行分解(fork)。 我们不能像使用主函数启动普通 C/C++ 程序那样来启动安卓应用。 要调试安卓应用,我们必须在开始的时候使其进入一个无限循环(如同 system_process 那样),这样我们能够尽早地将 gdbserver 连接到该应用。 然后,我们还需要为其创建一个 C/C++ 项目和调试配置。 方便起见,我们再次使用 C/C++ 项目 system_process 和调试配置 system_process_1234。

4.2.1 调试安卓应用

启动安卓设备,并使用 USB 线缆将其连接至主机。 执行下列脚本“start_app.sh”:

$ cd ~/android-sdk/android-debug-utility/target
$ ./start_app.sh
zygote:305
killall: gdbserver: no process killed
please launch the app

从安卓启动程序启动安卓应用,或者从 Eclipse 运行安卓应用。 屏幕上出现一条消息,告诉我们可以连接至应用。

$ ./start_app.sh
zygote:305
killall: gdbserver: no process killed
please launch the app
please connect to the app 1362 via gdb remote
Attached; pid = 1362
Listening on port 1234

要使用 Eclipse,请转至 Debug 视图。

“调试按钮->Debug Configurations”,选择配置“system_process_1234”,然后点击“Debug”按钮。

 

图 25. 启动 C/C++ 调试工具

等待 gdb 连接至 gdbserver。

 


图 26. 安卓应用 C/C++ 调试工具

 

暂停任务、转至 Console(控制台)并输入命令“thread 1”和“go”:

[New Thread 1374]
[Switching to Thread 1362]
thread 1
[Switching to thread 1 (Thread 1362)]
#0  0xb75cb343 in android::IPCThreadState::clearCallingIdentity (this=0x983d580) at frameworks/base/libs/binder/IPCThreadState.cpp:375
375 {
go
android app in dead loop, trying to restore...done

设置断点,然后对代码进行调试。

4.3 调试安卓服务

在安卓初始化脚本 init.rc 中,下列原生服务随系统一起启动:

service mtpd /system/bin/mtpd
    class main
    socket mtpd stream 600 system system
    user vpn
    group vpn net_admin inet net_raw
    disabled
    oneshot

service keystore /system/bin/keystore /data/misc/keystore
    class main
    user keystore
    group keystore
    socket keystore stream 666

这些服务通常使用 C/C++ 来编写,其条目为 main()。 但是这些服务由初始化进程启动,如果我们手动运行这些服务,其表现可能有所不同。 要对服务进行调试,我们须使用脚本 start_service.sh。此外,我们还需要为其创建 C/C++ 项目和 C/C++ 调试配置。 方便起见,我们再次使用现有的 system_process 项目和调试配置。

4.3.1 调试安卓服务

使用: start_service.sh [ service name ]

在主机上键入:

$ cd ~/android-sdk/android-debug-utility/target
$ ./start_service.sh mtpd
init:1
killall: gdbserver: no process killed
killall: gdb: no process killed
5 KB/s (585 bytes in 0.099s)
please start the service: start mtpd

在目标设备上键入:

# start mtpd

然后屏幕上会显示一条消息,告诉你现在可以对该服务进行调试。

user@ubuntu:~/android-sdk/android-debug-utility/target$ ./start_service.sh mtpd
init:1
killall: gdbserver: no process killed
killall: gdb: no process killed
5 KB/s (585 bytes in 0.099s)
please start the service: start mtpd
please connect to the app 1561 via gdb remote

与其它安卓 Java 应用不同,服务是独立的 C/C++ 应用,不能使用与 zygote 相同的映像 app_process,因此我们需要修改默认配置,以指定服务二进制的位置。

 


图 27. 将安卓服务二进制设置为调试

然后我们使用配置 system_process_1234 来启动调试工具,并等待其连接至 gdbserver。

 


图 28. 安卓服务 C/C++ 调试工具

 

暂停任务、转至 Console(控制台)并输入命令“thread 1”和“go”。

thread 1
[Switching to thread 1 (Thread 1561)]
#0  0x08049663 in main (argc=1, argv=0xbf9a3d64) at external/mtpd/mtpd.c:155
155  {
go
android service in dead loop, trying to restore...done

现在我们可以设置断点并对其进行调试。

5 同时进行 Java 和 C/C++ 调试

5.1 观察从 Java 到原生的所有调用追踪

有时候,我们希望观察从 Java 到原生的所有调用追踪。 要实现这一点,我们需要使用前面介绍的两种 Java 和 C/C++ 调试技巧。

  1. 使用脚本 start_system_server.sh 或 start_app.sh,使 system_process 和应用进入无限循环模式。
  2. 使用 DDMS 连接至 system_process 或应用。
  3. 输入“go”命令,使 system_process 或应用退出无限循环。
  4. 使用您感兴趣的原生函数来切换 C/C++ 断点。
  5. 输入“loop”命令,使原生函数进入无限循环模式。
  6. 开始运行。
  7. 现在转至安卓调试工具,停止线程,并检查每个线程的调用堆栈。 分别找到调用原生函数的线程,以及处于无线循环模式中的线程。

然后合并 Java 调用追踪和 C/C++ 调用追踪,并查看从 Java 到原生的整个调用追踪。
在下面的实例中,调用追踪为:
Java: WindowManagerService$Session.add-> SurfaceSession.init()->

C/C++: SurfaceSession_init()->android::SurfaceFlinger::createConnection()

system_process_1234 [C/C++ Remote Application]       

app_process [cores: 0,1]       
Thread [52] 10969 [core: 1] (Suspended : Container)       
Thread [51] 10968 [core: 1] (Suspended : Container)       
Thread [50] 10556 [core: 1] (Suspended : Container)       
Thread [49] 10547 [core: 1] (Suspended : Container)       
android::SurfaceFlinger::createConnection() at SurfaceFlinger.cpp:174 0x8643b728        
android::SurfaceComposerClient::onFirstRef() at SurfaceComposerClient.cpp:170 0x81f248b9       
android::RefBase::incStrong() at RefBase.cpp:304 0x8051e128       
sp() at RefBase.h:353 0x808711f3       
SurfaceSession_init() at android_view_Surface.cpp:109 0x808711f3       
dvmPlatformInvoke() at Call386ABI.S:133 0x8302be8f       
dvmCallJNIMethod_virtualNoRef() at Jni.c:1,849 0x8307505b       
dvmCheckCallJNIMethod_virtualNoRef() at CheckJni.c:158 0x83052741       
dvmInterpretDbg() at InterpC-portdbg.c:4,354 0x8304083d       
dvmInterpret() at Interp.c:1,335 0x83036521       
<...more frames...>      
<==
system_process [Android Application]       
DalvikVM[localhost:8600]       
Thread [<1> main] (Running)       
Thread [<50> Binder Thread #10] (Running)       
Thread [<49> Binder Thread #9] (Running)       
Thread [<47> Binder Thread #8] (Running)       
Thread [<44> Binder Thread #7] (Suspended)       
SurfaceSession.init() line: not available [native method]       
SurfaceSession.<init>() line: 29       
WindowManagerService$Session.windowAddedLocked() line: 5774       
WindowManagerService$WindowState.attach() line: 6107       
WindowManagerService.addWindow(WindowManagerService$Session, IWindow, WindowManager$LayoutParams, int, Rect, InputChannel) line: 1914       
WindowManagerService$Session.add(IWindow, WindowManager$LayoutParams, int, Rect, InputChannel) line: 5664       
WindowManagerService$Session(IWindowSession$Stub).onTransact(int, Parcel, Parcel, int) line: 68       
WindowManagerService$Session.onTransact(int, Parcel, Parcel, int) line: 5636       
WindowManagerService$Session(Binder).execTransact(int, int, int, int) line: 320       
NativeStart.run() line: not available [native method]

5. 观察 IPC 调用追踪

安卓系统进程间通信 (IPC) 通过 Binder 来实施。 一个任务调用一个函数,另外一项任务也能调用此函数,并通过执行任务获得结果。 有时候,我们希望观察 IPC 调用中不同任务之间的所有调用追踪。 幸运的是,binder IPC 具备同步功能。 也就是说,如果被调用者(callee)任务没有完成,则调用者(caller)任务不会返回。 所以,如果我们在被调用者任务中部署一个无限循环,调用者任务将在调用 IPC 时被阻止。 通过这种方式,我们能够观察被调用者和调用者任务的调用追踪。

要观察这两个任务,我们需要分别为每个任务启动一个 gdbserver/gdb 会话。 您可以通过调试工具 TCP 端口 1235 设置另外一个 C/C++ 项目 system_process_2 和另外一个调试配置。

  1. 按照 5.1 中的步骤操作,使用调试配置 system_process_1234 连接第一个任务。
  2. 按照相同的步骤,使用调试配置 system_process_1235 连接第二个任务。

注: 您需要修改 start_app.sh 以便将调试工具监听端口从 1234 指定为 1235,并使用 adb 向设备转发本地端口 1235。

  1. 使被调用者任务进入无限循环或者只是在感兴趣的函数中设置一个断点,然后运行该任务直到其停止。
  2. 观察这两个任务的调用追踪。

您现在可以看到从调用者任务到被调用者任务的所有调用追踪。 如果希望,您还可以将它们连接至其 Java 调试工具,这样您便可以看到所有 IPC 调用追踪中的 Java 追踪。

 

关于作者:

 

Jack Ren 目前在移动计算事业部 (MCG) 英特尔安卓工程设计团队担任安卓架构师一职。 从 Linux 内核到安卓软件堆栈,他在 Linux 开发方面拥有 8 年的丰富经验。 Jack 是 MCG 安卓上海团队的创始人和第一任经理。 从两年前的白手起家到如今的成品推出,他见证了 MCG 安卓软件开发方面的巨大发展。 目前,他的研究重点是安卓技术领域,例如 LLVM、Dalvik 和 UX,以及在上游为谷歌提供英特尔补丁。 他在英特尔 MCG/PSI 安卓开发团队的建立方面做出了突出的贡献。

 

 

声明

本文件中包含关于英特尔产品的信息。 本文件不构成对任何知识产权的授权,包括明示的、暗示的,也无论是基于禁止反言的原则或其他。 除相关产品的英特尔销售条款与条件中列明之担保条件以外,英特尔公司不对销售和/或使用英特尔产品做出任何其它明确或隐含的担保,包括对适用于特定用途、适销性或不侵犯任何专利、版权或其它知识产权的担保。

英特尔产品并非设计用于或有意用于任何英特尔产品发生故障可能会引起人身伤亡事故的应用领域。

英特尔可以随时在不发布声明的情况下修改规格和产品说明。 设计者不应信赖任何英特产品所不具有的特性,设计者亦不应信赖任何标有“保留权利”或“未定义”的说明或特性描述。 对此,英特尔保留将来对其进行定义的权利,同时,英特尔不应为因其日后更改该等说明或特性描述而产生的冲突和不相容承担任何责任。 本文信息可能随时更改,恕不另行通知。 请勿使用本文信息完成一项产品设计。

文中所述产品可能包含设计缺陷或错误,已在勘误表中注明,这可能会使产品偏离已经发布的技术规范。 这些缺陷或失误已收录于勘误表中,可索取获得。

订购产品前,请联系您当地的英特尔销售办事处或分销商,了解最新技术规范。

如欲获得本文或其它英特尔文献中提及的带订单编号的文档副本,可致电 1-800-548-4725,或访问 http://www.intel.com/design/literature.htm

性能测试中使用的软件和工作负载可能仅在英特尔微处理器上针对性能进行了优化。 诸如SYSmark和MobileMark等测试均系基于特定计算机系统、硬件、软件、操作系统及功能。 上述任何要素的变动都有可能导致测试结果的变化。 请参考其他信息及性能测试(包括结合其他产品使用时的运行性能)以对目标产品进行全面评估。

对本文件中包含的软件源代码的提供均依据相关软件许可而做出,任何对该等源代码的使用和复制均应按照相关软件许可的条款执行。

英特尔、酷睿、Core、VTune 和 Intel 标识是英特尔在美国和/或其他国家的商标。

英特尔公司 © 2012 年版权所有。 所有权利受到保护。

*其他的名称和品牌可能是其他所有者的资产。

 

 

有关编译器优化的更完整信息,请参阅优化通知