使用GDB调试Native库

无论是Android, 还是FxOS。GDB都是调试Native程序的好帮手。这里以SurfaceFlinger为例,简单介绍Android+MTK平台的GDB设置步骤和常用的调试技巧。

基本GDB设置

配置一个最最简单的GDB调试环境,首先需要以下2个基本前提:

  • 在PC端下载与设备(手机)软件相匹配的代码树(以及相应的prebuilt toolchain),以及编译生成的symbol文件
  • 设备运行的软件为eng版
  • 在PC端下载可用的adb工具

 


完成了这些以后,就可以着手开始建立Android GDB调试环境了。简单地说,配置GDB环境分为三个步骤

  • 建立ADB端口转发
  • 在设备(手机)端启动gdbserver
  • 在PC端启动gdb client
建立ADB端口转发

使用ADB命令将设备(手机)端输出到某端口的数据转发到PC的某个端口。比如我们将设备的5039端口转到PC的5039D端口:

adb forward tcp:5039 tcp:5039
在设备(手机)端启动gdbserver

首先用”adb shell”登录到手机上,用”ps | grep “找到要调试程序的pid(如果是想调试一个新启动的进程,这步就不需要了)。
在adb shell中键入”gdbserver :”或者“gdbserver : –attach ”启动gdbserver。以surfaceflinger为例子,假设当前surfaceflinger进程的pid是149,则命令为:

gdbserver :5039 --attach 149

成功之后,shell中显示成功连上指定的进程,并且在指定的端口监听:
gdbserver_screen

在PC端启动gdb client

1. 启动gdb client
PC端(Ubuntu)一般会自带GDB的调试程序,但是自带的那个是用来调试PC本地程序的。为了调试远程的Android设备,我们必须找到那个特殊的gdb(与设备端匹配的,包括设备端的体系结构,一般是arm;以及arm-eabi的版本)调试程序。在mtk的mt6589平台中,相关的gcc工具链都位于“<VIEW_ROOT>/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6/bin/” 下面。

除了GDB程序之外,我们还必须指定要调试的目标程序。这个目标程序并不会在PC端被执行(当然的,它是在设备端执行的),但是PC端的GDB程序需要通过它读取到符号表信息以及代码。在编译过整个项目之后,所有的目标程序和库文件都位于”<VIEW_ROOT>/out/target/product/<PRODUCT_NAME>/symbols“下面。而surfaceflinger程序就位于该目录下的“/system/bin”下。值得注意的是,在”<VIEW_ROOT>/out/target/product/<PRODUCT_NAME>“下我们也可以找到”/system/bin/surfaceflinger”。和symbols目录下的那个同名文件相比,这个要小不少,其实就是将symbols下的文件strip之后得到的结果,也是最终烧到手机中运行的那个;由于它没有debug信息,因此不能把它作为调试目标。

./prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6/bin/arm-linux-androideabi-gdb  ./out/target/product/[PRODUCT]/symbols/system/bin/surfaceflinger

启动之后,GDB打印出host(PC)和目标设备(手机)的系统,以及调试的目标文件:
gdb-client-connect

2. 设置symbol lib的查找路径
启动gdb client之后,我们需要设置PC端库文件的查找路径。库文件的加载方式有2种,完整路径和当前路径(根据环境变量的路径进行查找)。因此在GDB设置的时候需要针对这两种情况进行分别设置。“set solib-absolute-prefix ”设置对应于设备(手机)根目录的路径。“set solib-search-path ”设置寻找被链接的库文件的当前路径(方式2)。由于我们无法确定程序编写时的库文件查找方式,合理的做法是在GDB中同时设置这两个变量。

set solib-absolute-prefix [VIEW_ROOT]/out/target/product/[PRODUCT_NAME]/symbols
set solib-search-path [VIEW_ROOT]/out/target/product/[PRODUCT_NAME]/symbols/system/lib:[VIEW_ROOT]/out/target/product/[PRODUCT_NAME]/system/vendor/lib:[VIEW_ROOT]/out/target/product/[PRODUCT_NAME]/system/vendor/lib/egl:[VIEW_ROOT]/out/target/product/[PRODUCT_NAME]/system/vendor/lib/hw

设置之后,GDB会花一些时间读取符号文件。

3. 连接服务端
链接服务端可以在设置库文件路径之前或之后。命令很简单,只需要告诉链接的PC端口号(由adb forward转发的目标端口)。

target remote:5039

客户端正常连接之后,在设备端(服务端)会显示进入remote debugging
gdb-connected-server

客户端会暂时打断设备上目标程序的执行,进入GDB调试模式,提示用户可以开始调试了(设置断点,查询调用栈……)
gdb-connected-client

使用脚本

由于每次使用GDB都需要设置solib-absolute-prefix和solib-search-path比较麻烦,可以将这些GDB命令写道一个txt文件中,在运行GDB的时候使用-x自动加载这个文件并执行其中的GDB命令。

[GDB] -x [SCRIPT] [PROGRAMM]

或者在GDB命令行中使用”source 【script】”执行。

模块重编

通常来说,一般平台都支持单个模块的编译和更新,以surfaceflinger在mtk平台上为例:
1. 进入项目的根目录
2. 执行

 ./makeMtk -t -o=TARGET_BUILD_VARIANT=eng [PRODUCT_NAME] mm [PATH_TO_MODULE]

其中,指向模块的目录“PATH_TO_MODULE”必须存在一个<>文件。

禁用优化

在GDB调试的时候,经常遇到变量被“优化掉”,或者当前执行位置无法指向源代码中正确的行号;导致在GDB调试时非常困扰。在这时,我们可以在模块的<>中禁用编译器优化(-O0)来解决:

......
LOCAL_CFLAGS += -O0
......

设置之后,由于编译器不会检查makefile的时间戳,我们手动地修改所有参与编译的时间戳,保证在编译模块的时候所有源文件被重编

touch [PATH-TO-MODULE] *

使用DDD

如果嫌弃GDB的命令行界面,也可以采用DDD来进行调试。DDD只是一个前端界面,我们必须还要指定后端的gdb,否则它会采用PC自带的那个gdb。

ddd --debugger ./prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.6/bin/arm-linux-androideabi-gdb [PROGRAM]

ddd

Leave a Reply

Your email address will not be published. Required fields are marked *