网上看了好多关于GDB调试android本地代码的,但是都是直接上手,对于一点都不懂的我,真是难办,所以本人根据个人经历,总结下怎么从小白一步一步进行调试。
先讲一下GDB调试android本地代码的情况。开始看了好多资料,天真的以为用GDB命令就能直接启动so库,实际情况是,先写好JNI的应用之后,肯定是java代码去调用本地代码的,所以需要设置按钮之类的,在调试过程中,需要通过按钮或其他方式,启动jni的本地代码。每次设置了断点之后,需要点击按钮启动代码,然后代码运行到断点出停止,然后通过GDB命令查看各种信息,或者继续运行。
好了下面开始介绍
调试环境:
调试平台:ubuntu14.04LTS
目标手机:Android6.0虚拟机 X86架构,我用的nexus5,烧的原生系统,ROOT,直接自带gdbserver
软件环境:android-ndk-r10b搭配adt-bundle,并且自己准备一个包含JNI调用的应用
然后准备gdb和gdbserver,远程调试需要在目标手机上有个类似调试客户端的东西来给gdb传送数据什么的,然后在linux上接受这些信息,进行调试,所以需要这两个东西,在网上看的资料各种在线下载或者交叉编译什么的才能得到这两个东西,其实不用那么麻烦,NDK里自带(Android源码里边也带,请自行查找):
1. gdb:$NDK_DIR/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gdb,中间的路径根据自己情况更改。
2. gdbserver:位置:$NDK_DIR/prebuilt/android-arm64/gdbserver
因为我的测试机自带gdbserver,所以我直接用NDK目录中的gdb配合使用,如果不用ndk的,用Android源码目录下的GDB话效果一样的。
需要注意的是gdbserver和gdb的版本要匹配,否则会出错。
查看gdbserver版本号:
conan@conan-HP-Pro-3380-MT:~$ adb shell
shell@hammerhead:/ $ gdbserver --version
GNU gdbserver (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
gdbserver is free software, covered by the GNU General Public License.
This gdbserver was configured as "arm-eabi-linux"
查看gdb版本号:
conan@conan-HP-Pro-3380-MT:~/java/adt-bundle-linux-x86_64-20140321/android-ndk-r10b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin$ ./arm-linux-androideabi-gdb --version
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".
For bug reporting instructions, please see:
.
conan@conan-HP-Pro-3380-MT:~/java/adt-bundle-linux-x86_64-20140321/android-ndk-r10b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin$
网上还有人说了了这个问题,好像确实有影响,所以也适当注意下,我是通过RE管理器直接修改的:
对了,还有一点需要注意的是,Android从4.4开始,强制打开了SELinux,其规则是不允许一个进程attach到一个非自己的子进程或兄弟进程上进行调试的,哪怕这个进程是以root用户启动的也不行。想要知道当前SELinux的工作模式,可以在adb shell下键入getenforce命令,例如:
这是在我运行Android 5.0系统的Google Nexus 5上运行的结果,可以看出,其已经默认打开了强制(Enforcing)模式。所以,要想调试成功,必须要关闭SELinux的强制模式,可以通过下面的命令来关闭:
echo 0> /sys/fs/selinux/enforce
注意,这条命令必须用root用户来运行。下面看看运行后的结果:
可以看出,SELinux的模式已经从强制变成了允许(Permissive)。
准备妥当后就可以调试了,当然要写好自己的应用工程,包含jni代码的,我写的为JNI_test。
1 首先启动要调试的程序,ps 获取其进程号 (或者在eclipse的DDMS中直接查看)
conan@conan-HP-Pro-3380-MT:~$ adb shell
shell@hammerhead:/ $ ps | busybox grep jni
u0_a78 23954 319 920924 49544 sys_epoll_ 00000000 S com.example.jni_test
2 启动gdbserver attach到目标进程 (在adb进入手机上操作,注意此时需要root权限)
shell@hammerhead:/ $ su
root@hammerhead:/ # gdbserver remote:1234 --attach 23954
Attached; pid = 23954
Listening on port 1234
其中 remote:1234 表示映射成tcp的1234端口,这个时候重新打开一个 adb shell 再ps yahfa发现
shell@hammerhead:/ $ ps | busybox grep yahfa
u0_a78 23954 319 930024 49648 ptrace_sto 00000000 t com.example.hook_yahfa
进程状态已经变成 t了,表示 attach已经成功了。
3 启动gdb 来进行调试 (pc环境中操作,我用的是ubuntu系统)
conan@conan-HP-Pro-3380-MT:~/Android/android-6.0.0_r1/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin$ adb forward tcp:1234 tcp:1234
首先端口映射,映射成 1234 端口号
把/system/bin/app_process,/system/bin/linker,/system/lib/libc.so 从手机上拷贝出来(可以用adb pull 命令),其他so文件也拷贝出来也行,我们假定拷贝的目录为$lib
conan@conan-HP-Pro-3380-MT:~$ adb pull /system/bin/app_process /home/conan/java/tmp/GDB
269 KB/s (22144 bytes in 0.080s)
conan@conan-HP-Pro-3380-MT:~$ adb pull /system/bin/linker /home/conan/java/tmp/GDB
1351 KB/s (189636 bytes in 0.137s)
conan@conan-HP-Pro-3380-MT:~$ adb pull /system/lib/libc.so /home/conan/java/tmp/GDB
2997 KB/s (676872 bytes in 0.220s)
conan@conan-HP-Pro-3380-MT:~$
conan@conan-HP-Pro-3380-MT:~/java/adt-bundle-linux-x86_64-20140321/android-ndk-r10b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin$ ./arm-linux-androideabi-gdb
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".
For bug reporting instructions, please see:
.
(gdb) file /home/conan/java/tmp/GDB/app_process
Reading symbols from /home/conan/java/tmp/GDB/app_process...(no debugging symbols found)...done.
set solib-absolute-prefix $nostriplib_dir, $nostriplib_dir是存放了没有strip的so的目录,因为我们有源代码,而本身android 源码ndk编译的时候默认是加 -g选项的,没有被 strip的so库在/home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi这个目录下,而在libs下的so是被 strip过的版本
gdb) set solib-absolute-prefix /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi
(gdb)
(gdb) set solib-search-path /home/conan/java/tmp/GDB:/home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi
(gdb)
target remote :端口号 连上gdbserver
(gdb) (gdb) target remote :1234
Remote debugging using :1234
warning: Could not load shared library symbols for 118 libraries, e.g. /system/lib/libsigchain.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Reading symbols from /home/conan/java/tmp/GDB/linker...(no debugging symbols found)...done.
Loaded symbols for /home/conan/java/tmp/GDB/linker
Reading symbols from /home/conan/java/tmp/GDB/libc.so...(no debugging symbols found)...done.
Loaded symbols for /home/conan/java/tmp/GDB/libc.so
Reading symbols from /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/libJNI_test.so...done.
Loaded symbols for /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/libJNI_test.so
0xb6d4a2e4 in __epoll_pwait () from /home/conan/java/tmp/GDB/libc.so
进入gdb,并监听1234端口,警告可以忽略,因为没从手机上pull出这些库,不影响的。
这个时候已经进入 gdb了dir source 指定源码路径
(gdb) dir /home/conan/java/tmp/eclipse_android/JNI_test/jni
Source directories searched: /home/conan/java/tmp/eclipse_android/JNI_test/jni:$cdir:$cwd
(gdb)
设置完成以后
(gdb) list JNI_test.cpp:5
1 #include
2 #include
3
4 JNIEXPORT jstring JNICALL Java_com_example_jni_1test_jni_sayName
5 (JNIEnv *env, jclass){
6 int a = 1;
7 char b = 'a';
8 a = 1+2;
9 b = 's';
10 return env->NewStringUTF("This is a test message!");
发现已经有代码显示了
我们在这里设置断点,分别在6/7/8/9/10行设置断点,一共5个作为测试。
(gdb) b JNI_test.cpp:6
Breakpoint 1 at 0xa0208c8c: file jni/JNI_test.cpp, line 6.
(gdb) b JNI_test.cpp:7
Breakpoint 2 at 0xa0208c94: file jni/JNI_test.cpp, line 7.
(gdb) b JNI_test.cpp:8
Breakpoint 3 at 0xa0208c9c: file jni/JNI_test.cpp, line 8.
(gdb) b JNI_test.cpp:9
Breakpoint 4 at 0xa0208ca4: file jni/JNI_test.cpp, line 9.
(gdb) b JNI_test.cpp:10
Breakpoint 5 at 0xa0208cac: file jni/JNI_test.cpp, line 10.
然后在应用上点击按钮,执行so库里边的方法,这里我的按钮设置的会执行Java_com_example_jni_1test_jni_sayName这个方法,而因为我实现设置好了断点,所以执行到这里的时候会停下。
继续执行
(gdb) c
Continuing.
Breakpoint 1, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:6
6 int a = 1;
(gdb) c
Continuing.
Breakpoint 2, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:7
7 char b = 'a';
(gdb)
如果要查看变量或者汇编指令
(gdb) print b
$1 = 176 '\260'
(gdb) c
Continuing.
Breakpoint 3, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:8
8 a = 1+2;
(gdb) print b
$2 = 97 'a'
(gdb) print a
$3 = 1
(gdb) disas
Dump of assembler code for function Java_com_example_jni_1test_jni_sayName(JNIEnv*, jclass):
0xa0208c78 <+0>: push {r11, lr}
0xa0208c7c <+4>: add r11, sp, #4
0xa0208c80 <+8>: sub sp, sp, #16
0xa0208c84 <+12>: str r0, [r11, #-16]
0xa0208c88 <+16>: str r1, [r11, #-20]
0xa0208c8c <+20>: mov r3, #1
0xa0208c90 <+24>: str r3, [r11, #-8]
0xa0208c94 <+28>: mov r3, #97 ; 0x61
0xa0208c98 <+32>: strb r3, [r11, #-9]
=> 0xa0208c9c <+36>: mov r3, #3
0xa0208ca0 <+40>: str r3, [r11, #-8]
0xa0208ca4 <+44>: mov r3, #115 ; 0x73
0xa0208ca8 <+48>: strb r3, [r11, #-9]
0xa0208cac <+52>: ldr r0, [r11, #-16]
0xa0208cb0 <+56>: ldr r3, [pc, #24] ; 0xa0208cd0
0xa0208cb4 <+60>: add r3, pc, r3
0xa0208cb8 <+64>: mov r1, r3
0xa0208cbc <+68>: bl 0xa0208c3c <_JNIEnv::NewStringUTF(char const*)>
0xa0208cc0 <+72>: mov r3, r0
0xa0208cc4 <+76>: mov r0, r3
---Type to continue, or q to quit---return
其他相关的资料,直接贴上,有兴趣自己看吧:
需要的文件: gdbserver 位置:android-ndk-r8b/prebuilt/android-arm/gdbserver/gdbserver gdb 位置:android-ndk-r8b/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-gdb 步骤:
运行arm-linux-androideabi-gdb ,输入以下命令序列 file $lib/app_process $lib目录中有从手机拷贝出来的app_process,linker和libc.so这些文件 ,如我放到了/home/qrf/android_ndk_debug/mydev目录下
set solib-absolute-prefix $nostriplib_dir $nostriplib_dir是存放了没有strip的so的目录,如 set solib-absolute-prefix /home/qrf/code/cmso
set solib-search-path $lib:$nostriplib_dir 如set solib-search-path /home/qrf/android_ndk_debug/mydev:/home/qrf/code/cmso
target remote :端口号 连上gdbserver
dir source 指定源码路径 后面就是gdb调试的问题了,进入gdb调试界面可以用ctrl x + a 来切换到文本模式(可以查看源码), 如果要配合ddd前端, 用apt-get install ddd ,然后 用ddd --debugger arm-linux-androideabi-gdb 使用,但是ddd有时会不太灵光,还是直接使用gdb比较方便
附一些常用的gdb命令:
backtrace/bt //列出当前线程堆栈
thread apply all bt //列出所有线程调用堆栈
thread 线程号//切换线程上下文
ctrl x+a //切换到源码浏览窗口 ,再按ctrl x+a 切换回去
ctrl c //中断当前运行
c/continue //继续运行
info sharedlibrary //列出so加载列表
info threads //列出线程列表
info locals //列出当前堆栈上的局部变量
info breakpoints //列出断点
print 变量名 //列出变量内容
print 变量名= //修改变量名的值
b/break 源码文件名:行号 //下断点
d/delete 断点id //删除断点
f/frame 栈帧序号 //切换到指定的栈帧 如 f 0 顶层
s/step //下一步,有函数调用会进入
n/next //下一步,有函数调用不会进入
fin/finish //返回到上层函数调用
disable 断点id //禁用断点
enable 断点id //启用断点
disassemble/disas //查看汇编代码
gdb中,输入命令时,可以不用打全命令,只用打命令的前几个字符就可以了,当然,命令的前几个字符应该要标志着一个唯一的命令,在Linux下,你可以敲击两次TAB键来补齐命令的全称,如果有重复的,那么gdb会把其例出来。
==============显示gdb命令帮助信息===================
==============设置断点===================
===============设置观察点=================
===============设置捕捉点=================
===============维护被调试程序断点==================
===============为停止点设定运行命令================
===============显示被调试程序的信息================
===============运行及查看被调试信息================
===============显示源代码===============
===============搜索源代码===============
===============设置被调试程序参数/gdb配置==============
===============其他=================
===============shell命令=================
此外gdb中可以执行shell命令,使用SHELL环境变量定义的可执行程序来执行shell命令,常用的命令如下:
===============调整程序线路=================
一旦使用GDB挂上被调试程序,当程序运行起来后,你可以根据自己的调试思路来动态地在GDB中更改当前被调试程序的运行线路或是其变量的值,这个强大的功能能够让你更好的调试你的程序,比如,你可以在程序的一次运行中走遍程序的所有分支。
又按照上述教程进行了调试,终于调通,备份下记录文件:
conan@conan-HP-Pro-3380-MT:~/java/adt-bundle-linux-x86_64-20140321/android-ndk-r10b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin$ adb forward tcp:1234 tcp:1234
conan@conan-HP-Pro-3380-MT:~/java/adt-bundle-linux-x86_64-20140321/android-ndk-r10b/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin$ ./arm-linux-androideabi-gdb
GNU gdb (GDB) 7.6
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-linux-gnu --target=arm-linux-android".
For bug reporting instructions, please see:
(gdb) file /home/conan/java/tmp/GDB/app_process
Reading symbols from /home/conan/java/tmp/GDB/app_process...(no debugging symbols found)...done.
(gdb) file /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/lib
libJNI_test.so libstdc++.a
(gdb) file /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/libJNI_test.so
Reading symbols from /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/libJNI_test.so...done.
(gdb) set solib-absolute-prefix /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi
(gdb) set solib-search-path /home/conan/java/tmp/GDB:/home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi
(gdb) target remote :1234
Remote debugging using :1234
warning: Could not load shared library symbols for 118 libraries, e.g. /system/lib/libsigchain.so.
Use the "info sharedlibrary" command to see the complete listing.
Do you need "set solib-search-path" or "set sysroot"?
Reading symbols from /home/conan/java/tmp/GDB/linker...(no debugging symbols found)...done.
Loaded symbols for /home/conan/java/tmp/GDB/linker
Reading symbols from /home/conan/java/tmp/GDB/libc.so...(no debugging symbols found)...done.
Loaded symbols for /home/conan/java/tmp/GDB/libc.so
Reading symbols from /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/libJNI_test.so...done.
Loaded symbols for /home/conan/java/tmp/eclipse_android/JNI_test/obj/local/armeabi/libJNI_test.so
0xb6d4a2e4 in __epoll_pwait () from /home/conan/java/tmp/GDB/libc.so
(gdb) list
1 #include
2 #include
3
4 JNIEXPORT jstring JNICALL Java_com_example_jni_1test_jni_sayName
5 (JNIEnv *env, jclass){
6 int aaaaaaaaaaaaaaaaaa = 1;
7 char bbbbbbbbbbbbbbbbbbb = 'a';
8 return env->NewStringUTF("This is a test message!");
9 }
(gdb) dir /home/conan/java/tmp/eclipse_android/JNI_test/jni
Source directories searched: /home/conan/java/tmp/eclipse_android/JNI_test/jni:$cdir:$cwd
(gdb) list
Line number 10 out of range; jni/JNI_test.cpp has 9 lines.
(gdb) list jni/JNI_test.cpp:5
1 #include
2 #include
3
4 JNIEXPORT jstring JNICALL Java_com_example_jni_1test_jni_sayName
5 (JNIEnv *env, jclass){
6 int aaaaaaaaaaaaaaaaaa = 1;
7 char bbbbbbbbbbbbbbbbbbb = 'a';
8 return env->NewStringUTF("This is a test message!");
9 }
(gdb) b jni/JNI_test.cpp:6
Cannot access memory at address 0xc8c
(gdb) file /home/conan/java/tmp/GDB/app_process
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Load new symbol table from "/home/conan/java/tmp/GDB/app_process"? (y or n) y
Reading symbols from /home/conan/java/tmp/GDB/app_process...(no debugging symbols found)...done.
(gdb) dir /home/conan/java/tmp/eclipse_android/JNI_test/jni
Source directories searched: /home/conan/java/tmp/eclipse_android/JNI_test/jni:/home/conan/java/tmp/eclipse_android/JNI_test/jni:$cdir:$cwd
(gdb) b jni/JNI_test.cpp:6
Breakpoint 1 at 0xa0202c8c: file jni/JNI_test.cpp, line 6.
(gdb) b jni/JNI_test.cpp:8
Breakpoint 2 at 0xa0202c9c: file jni/JNI_test.cpp, line 8.
(gdb) c
Continuing.
c
Breakpoint 1, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:6
6 int aaaaaaaaaaaaaaaaaa = 1;
(gdb) c
Continuing.
Breakpoint 2, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:8
8 return env->NewStringUTF("This is a test message!");
(gdb) c
Continuing.
c
^C
Program received signal SIGINT, Interrupt.
0xb6d4a2e4 in __epoll_pwait () from /home/conan/java/tmp/GDB/libc.so
(gdb) c
Continuing.
Breakpoint 1, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:6
6 int aaaaaaaaaaaaaaaaaa = 1;
(gdb) bt
#0 Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:6
#1 0x9f18408e in ?? ()
#2 0x9f18408e in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) c
Continuing.
Breakpoint 2, Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:8
8 return env->NewStringUTF("This is a test message!");
(gdb) bt
#0 Java_com_example_jni_1test_jni_sayName (env=0xb4d96a80)
at jni/JNI_test.cpp:8
#1 0x9f18408e in ?? ()
#2 0x9f18408e in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) print aaaaaaaaaaaaaaaaaa
$1 = 1
(gdb) disas
Dump of assembler code for function Java_com_example_jni_1test_jni_sayName(JNIEnv*, jclass):
0xa0202c78 <+0>: push{r11, lr}
0xa0202c7c <+4>: addr11, sp, #4
0xa0202c80 <+8>: subsp, sp, #16
0xa0202c84 <+12>: strr0, [r11, #-16]
0xa0202c88 <+16>: strr1, [r11, #-20]
0xa0202c8c <+20>: movr3, #1
0xa0202c90 <+24>: strr3, [r11, #-8]
0xa0202c94 <+28>: movr3, #97; 0x61
0xa0202c98 <+32>: strbr3, [r11, #-9]
=> 0xa0202c9c <+36>: ldrr0, [r11, #-16]
0xa0202ca0 <+40>: ldrr3, [pc, #24]; 0xa0202cc0
0xa0202ca4 <+44>: addr3, pc, r3
0xa0202ca8 <+48>: movr1, r3
0xa0202cac <+52>: bl0xa0202c3c <_JNIEnv::NewStringUTF(char const*)>
0xa0202cb0 <+56>: movr3, r0
0xa0202cb4 <+60>: movr0, r3
0xa0202cb8 <+64>: subsp, r11, #4
0xa0202cbc <+68>: pop{r11, pc}
0xa0202cc0 <+72>: andeqr1, r0, r8, lsl #13
End of assembler dump.
(gdb)