Android代码调试
与Java一样,Dalvik实现了一个标准的调试接口,称为Java调试线协议
(Java Debug Wire Protocol, JDWP
)。所有用来调试Dalvik和Java上的程序的工具都是基于此协议开发的。 深入Java调试体系
Android设备监视器(Monitor)和Dalvik调试监视服务器(DDMS)都采用了JDWP标准协议,它们用JDWP访问指定应用的信息(线程、堆使用情况、正在进行的方法调用)
1.调试应用程序
点击工具栏中的Debug As
图标(like a bug)进入Debug界面,想要返回代码界面点击右上角的Java
按钮。
左上角的小窗是各个栈帧,点击某个栈帧会在代码窗口显示附近代码。
2.显示framework层源代码
点击栈帧时可以显示Android framework层源代码:首先要正确初始化AOSP资料库。文档
下一步,为Eclipse创建类路径。在AOSP根目录下运行make idegen
命令创建idegen.sh
脚本,在顶层目录下创建excluded-paths
文件,以排除顶层目录下不想包含的所有目录:
1 | ^abi/.* |
新建一个Java Project命名为AOSP Framework Source,取消Use Default Location复选框,指定AOSP根目录。
调试刚才的示例应用,右键栈帧窗口中的项目名,选择Edit Source Lookup...
Add路径为Java Project,选上一步创建的AOSP Framework Source项目,再点击父栈帧时会显示framework层的代码。
3.伪造调试设备
对于原厂设备,启动Android SDK中自带的DDMS或Monitor只显示可调试进程。
使用eng配置生成的工程设备允许访问所有进程。eng与user或userdebug之间的主要区别是系统属性ro.secure和ro.debuggable,user和userdebug生成时将这两个值设置为1和0;而eng生成时为0和1。
修改已root设备以支持调试系统服务和预装应用并不复杂,介绍一种简单但不是永久有效的方法,设备重启后失效。首先,获取一份setpropex工具,此工具可以在已root设备上修改只读的系统属性:
1 | shell@hammerhead:/data/local/tmp $ su |
断开shell连接,在主机上使用adb root命令以root权限重启ADB守护进程。
最后重启所有依赖Dalvik VM的进程。在修改ro.debuggable属性后启动的任何进程都是可调试的。为了强制重启Android Dalvik层,可以简单地结束system_server进程:
1 | root@hammerhead:/data/local/tmp # ps |
设备重启后,Monitor中会出现所有的Dalvik进程。
4.附加到其他进程
处于完全调试模式下的设备也支持实时调试任何Dalvik进程。在Eclipse启动并处于运行状态下,点击右上角的DDMS
,在Devices窗口中选择目标进程,比如system_process。在Run菜单中选择Debug Configurations打开对话框,在对话框左边双击Remote Java Application
新建一个链接,Name
设为Attacher,Connect选项卡中Project
设为AOSP Framework Source项目,Host
设为127.0.0.1,Port设为8700
。
最后点击Apply,点击Debug。
调试原生代码
关于如何使用原生代码编程可以参考《Android C++高级编程》
1.使用Eclipse进行调试
打开要调试的目标项目,首先,需要告知Android生成的应用必须支持调试:选择Project->Properties
,点开C/C++生成选项
并选择Environment
,点击Add按钮,变量名输入NDK_DEBUG
,值输入1
。点击OK就可以开始调试了。为确保新的环境变量生效,选择Project->Build All
。
先在Java代码调用Native代码之前下断点,点击Debug As
开启调试,再在Native代码中想要调试的位置下断点进行调试:
2.使用AOSP进行调试
编译AOSP代码,烧录到Nexus5设备上,具体过程可以参考我的另一篇文章。
将GDB服务器二进制文件上传到设备:
1 | android4.4.2 $ adb push prebuilts/misc/android-arm/gdbserver/gdbserver /data/local/tmp |
调试过程使用标准的TCP/IP连接将GDB客户端连接到GDB服务器上。建议通过USB使用ADB进行调试。用ADB的端口转发功能为GDB客户端打开一个管道:
1 | android4.4.2 $ adb forward tcp:31337 tcp:31337 |
下一步将GDB服务器执行目标程序或附加至进程:
1 | ~ $ adb shell |
打开另一个终端:
1 | android4.4.2 $ cd prebuilts/gcc/linux-x86/arm/arm-eabi-4.7/bin |
在另一个终端中adb连接到设备,查看so库加载的基址:
1 | root@hammerhead # cat /proc/15078/maps |
从中可以看到so库的基址是74f1c000,在IDA中找到想要调试的代码地址,加上基址得到实际内存中代码的地址:
切换到gdb客户端:
1 | (gdb) break *0x74f1d998 |
剩下就可以使用GDB命令进行调试了。
3.使用IDA调试
GDB对thumb指令支持不好,调试thumb指令时最好还是用IDA。
- 打开ddms
打开ddms才能打开调试端口,才能用jdb - adb push android_server /data/local/tmp/
android_server在IDA安装目录的dbgsvr目录中1
2
3
4adb shell
cd /data/local/tmp
chmod 777 android_server
./android_server - adb forward tcp:23946 tcp:23946
- adb shell am start -D -n com.bruce.jnitest
- IDA attach到目标应用上
Debugger->Attach->Remote ARMLinux/Android debugger
选择com.bruce.jnitest
,进程过多可以ctrl+F查找
- suspend on library loading
Debugger->Debugger options...
选择suspend on libaray load/unload
,然后按F9继续执行。 - jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700
弹出的框都点cancel就行了,在右边的Modules窗口中找到要调试的so库双击。
在新窗口中找到想要调试的函数,右键添加断点,继续执行程序。(在linker时会停多次,继续执行即可)
下面触发应用调用库函数,控制流即停在断点处
有时在一个函数里无法使用F5,这时在函数中按P,IDA会把这段代码作为函数分析,再按F5即可。
reference
《Android安全攻防权威指南》
http://drops.wooyun.org/tips/6840