Android环境下的GDB调试

基本概念

gdb是GNU开发的针对Linux/Unix环境下程序的调试工具。为了节约目标系统的资源,gdb通常采用gdb+gdbserver的方式进行调试。

在Android GDB调试场景下,gdb运行在PC端,gdbserver运行在Android系统中。在实际的调试过程中,PC端的gdb参照调试符号文件向gdbserver发出命令,gdbserver就会向运行程序发出信号,从而实现对Android系统运行程序的跟踪与控制:

  • 启动尚未运行C/C++程序,或者绑定到正在运行的C/C++程序
  • 让被调试的程序在指定的断点处中止运行
  • 查看当前中止状态下的对用堆栈,局部变量等信息
  • 通过对变量赋值,调用函数等操作来动态的改变程序的执行环境

调试环境准备

  1. gdb
    gdb运行在PC端,通常位于Android源码中的prebuilt目录下。不同的项目,不同的平台,gdb的位置可能不同,但是我们可以在进入prebuilt目录下通过find命令来查找:find ./ -name “gdb“,常见路径列举如下:
    prebuilts/gdb/linux-x86/bin/gdb
    prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-gdb

  2. gdbserver
    gdbserver运行在Android系统中,默认已经安装在eng或者userdebug的软件版本中;根据被调试程序的运行位数(32位/64位)的不同,gdbserver需要选择相匹配的版本gdbserver/gdbserver64。
    注意:如果手机中找不到gdbserver,我们可以将源码prebuilt目录下的gdbserver安装到手机中

  3. 带有调试符号信息的文件
    要调试C/C++程序,必须要有调试符号信息。按照Android源码的编译规则,调试符号信息通常位于在目录out/target/product/[PRODUCT_NAME]/sysmbols下;为了能够在调试过程中获取到更多的调试信息,建议对源码进行全编,并在调试目标的Android.mk中关闭编译优化:
    LOCAL_CFLAGS += -O0 -g // -O0:优化级别为0, -g:打开调试

调试举例

以mediaserver进程为例;

Step 1. 启动gdbserver,并绑定到mediaserver的进程上;

adb shell gdbserver :8888 --attach 1232

注意:
a. 使用gdbserver,还是使用gdbserver64,需要依据进程的位数来决定;进程的位数可以通过file命令查看:

adb shell file system/bin/mediaserver                                                                                                                          

b. 1232为进程id,可以ps命令查看;

adb shell ps -ef | grep mediaserver

c. 如果不绑定进程id,则gdbserver会重新创建一个新的进程:

adb shell gdbserver :8888 system/bin/mediaserver

创建的mediaserver相比于系统的启动方法,会丢失一些group,selinux context信息。因此如果系统已经启动了调试的进程,可以直接绑定到指定PID上;否则才可以考虑通过gdbserver创建新的进程;

Step 2. 将PC指定端口映射到Android系统中的指定端口上

adb forward tcp:8888 tcp:8888

Step 3. PC端启动gdb client

prebuilts/gdb/linux-x86/bin/gdb out/target/product/[PRODUCT_NAME]/symbols/system/bin/mediaserver

Step 4. 指定含有调试符号文件的symbols路径

(gdb) set solib-absolute-prefix out/target/product/[PRODUCT_NAME]/symbols/
(gdb) set solib-search-path out/target/product/[PRODUCT_NAME]/symbols/

Step 5. 将gdb与已启动的gdbserver通过tcp/port相连接

(gdb)target remote :8888

Step 6. 开始各种姿势的调试

1. 显示被调试程序的信息
(gdb) info thread:  打印当前进程所有的thread信息
(gdb) info breakpoints [n]       显示所有断点(或断点n)信息 
(gdb) info watchpoints [n]       显示所有观察点(或观察点n)信息 
(gdb) info program       查看被调试程序的执行状态 
(gdb) info args        打印出当前函数的参数名及其值 
(gdb) info locals       打印出当前函数中所有局部变量及其值 
(gdb) info display       查看display设置的自动显示的信息 
(gdb) info frame       查看栈帧信息,包括程序语言
(gdb) print a  打印变量a的值
(gdb) print &a 打印变量a的地址
2. 线程信息调试
(gdb) thread: 打印当前gdbserver所跟踪的thread信息
(gdb) thread [Id]: 切换到指定Id的thread
3. 堆栈信息调试
(gdb) bt full 显示当前的堆栈信息
4. 显示当前运行的源码信息
(gdb) ctrl + x + a (full) : 相当强大的功能,将会在当前窗口创建一个小的窗口用于显示源码
(gdb) up/down: 依据调用堆栈显示上一级/下一级调用的源码
4. 程序单步执行/继续执行控制/退出执行
(gdb) c 运行程序直至断点/观察点处停住
(gdb) n 单步执行
(gdb) quit 退出执行
5. 断点信息调试
(gdb) b(reak) filename:linenumber 运行到文件指定行停住;不需要指定文件的路径
(gdb) b(reak) filename:function 运行到文件指定函数入口停住;不需要指定文件的路径
6. 观察点信息调试
(gdb) watch *(long *)0x7777777      观察long类型变量,当变量变化时,程序停住
(gdb) rwatch *(long *)0x7777777       观察long类型变量,当变量的值被读时,程序停住
(gdb) awatch *(long *)0x7777777       设置观察点,当变量的值被读或写时,程序停住
7. 改变运行环境调试
(gdb) print x = 4       C/C++语法,把变量x的值修改为4 
(gdb) jump +num       当前运行点向下偏移num行开始执行
(gdb) jump linenum       从当前调试文件的linenum行开始执行 
(gdb) jump file:linenum       从file的linenum行开始执行
(gdb) singal SIGNAL       发送信号SINGAL给被调试程序
(gdb) return       强制函数返回,忽略未执行的语句 
(gdb) return result       强制函数返回结果result,忽略未执行的语句
(gdb) call func       调用当前程序中的函数 

你可能感兴趣的:(android-ndk)