Android gdb调试

       Android对于C/C++代码的调试方式一般选用gdb+gdbserver的方式,其中gdbserver运行在目标系统中(如手机),gdb运行在宿主机上(如linux)。gdb主要有以下几个功能:

  • 启动程序,可以按照自定义的要求随心所欲的运行程序
  • 可让被调试的程序在所指定的断点处停住(断点可以是条件表达式)
  • 当程序被停住时,可以检查此时程序中所发生的事
  • 动态的改变程序的执行环境

1. 需要准备的东西

  • 开发机:Ubuntu 12.04 LTS
  • 目标手机:手机一部
  • gdbserver
  • gdb
  • 被调试的C/C++程序

       一般android源码中已有编译好的gdbserver和gdb程序,如在高通msm8976(64位)平台上,使用的gdbserver位于:prebuilts/misc/android-arm64/gdbserver64,gdb位于:prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-gdb。在目标手机中,若系统是userdebug或eng版本,gdbserver已经安装在system/bin/下。
       要调试C\C++程序,必须把调试信息加入到可执行文件中,在Android.mk中添加LOCAL_CFLAGS += -g为被调试程序添加调试信息,并重新编译程序。如果没有添加LOCAL_CFLAGS += -g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。

2. 开始调试

       以调试手机中的bootanimation(位于system/bin)可执行程序来说明调试的方法和过程。

2.1 启动gdbserver

adb shell gdbserver64 :9090 /system/bin/bootanimation
命令说明:在tcp端口9090上监听bootanimation程序,gdb只要也连上9090端口即可。
注意:对于32位的app,需要使用gdbserver命令,而64位的应用需要使用gdbserver64,切不可用错,否则会导致gdb无法连接到gdbserver。
结果:正常的显示结果一定包含下图红色方框中的内容

这里写图片描述

2.2 设置端口转发

在开发机上设置端口转发,命令如下:
adb forward tcp:9090 tcp:9090
命令说明:表示通过adb映射tcp端口1234,命令中前面的是local的端口,后面的是remote的端口。Android给出的命令解释如下:

这里写图片描述

注意:命令中的端口号必须与gdbserver命令中的监听端口号相同,否则会导致gdb无法与gdbserver连接。

2.3 在开发机上启动gdb调试

       以msm8976平台为例,在Android源码中,对于32位的app使用的gdb客户端是:prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/bin;而64位的app使用的gdb客户端为:prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/bin/aarch64-linux-android-gdb。

  • 为了可以在开发机的任意位置上都能够调用gdb和adb客户端命令,可以在Android源码根目录下执行如下命令:
user@~/android $ . build/envsetup.sh
user@~/android $ lunch msm8952_64-userdebug

注意:该操作非必须,如果不进行该项操作,必须进入到gdb客户端所在的目录执行命令。建议执行该操作,方便后续操作。

  • 启动gdb客户端aarch64-linux-android-gdb
user@~/android$ aarch64-linux-android-gdb out/target/product/general/obj/EXECUTABLES/bootanimation_intermediates/LINKED/bootanimation

命令说明:调试可执行文件bootanimation
结果:正常的显示结果如下,注意红色方框中的内容最后一定是done

这里写图片描述

  • 如果调用的可执行文件中使用了第三方库,一定要在gdb命令窗口中为该可执行文件建立符号连接
(gdb) set solib-absolute-prefix out/target/product/general/symbols/
(gdb) set solib-search-path out/target/product/general/symbols/
  • 连接gdbserver进行调试
(gdb)target remote ip:port

命令说明:gdb连接目标机器或进程,其中ip为手机的IP地址,port为gdbserver命令中的监听端口号。
结果:正常的显示结果如下

这里写图片描述

在启动gdbserver的控制台中会显示如下结果:

这里写图片描述

划去的host为开发机的ip地址。

  • 接下来就可以在gdb命令窗口中用gdb命令调试程序了

3. 再次调试

       一般如果想再次调试程序时,在gdb中只要执行r命令即可,但是在使用gdb+gdbserver高度Android程序时,这样做却不行,因为一次调试结束后,gdbserver就会退出,如下:

这里写图片描述

       此时要想再次调试程序,需要如下操作:

  • 启动gdbserver,建议与第一次调试被调试程序的启动命令相同,这样可以节省一些操作
  • 若gdbserver中监听的端口与第一次调试时使用的端口相同,则不需要设置端口转发,否则仍然要设置端口转发
  • 在之前执行的gdb交互式命令窗口中执行target remote ip:port,连接目标机器或进程
  • 连接成功后可以使用gdb命令调试程序

4. 调试时的注意事项

  • 编译时请禁止编译优化选项,即添加-O0编译选项,否则会导致代码在调试时出现跳来跳去的可能,并且在打印变量值是出现
  • 在编译程序时需要添加-g编译选项以为程序添加gdb调试功能
  • 如果在开发机上执行”target remote ip:port”后,在gdbserver端打印出如下信息

    这里写图片描述

       说明使用的gdbserver与gdb版本不匹配。通常按如下方式匹配:
       gdbserver64<————————————>aarch64-linux-android-gdb
       gdbserver<—————————————->arm-linux-androideabi-gdb

5. gdb命令说明

       gdb中,输入命令时,可以不用打全命令,只用打命令的前几个字符就可以了,当然,命令的前几个字符应该要标志着一个唯一的命令,在Linux下,你可以敲击两次TAB键来补齐命令的全称,如果有重复的,那么gdb会把其例出来。
==============显示gdb命令帮助信息===================

  • help        显示gdb命令种类
  • help subcommand 显示subcommand的帮助信息
  • apropos word   搜索与word相关的命令

==============设置断点===================

  • b(break) 在函数或某一行处设置断点
    如果只记得部分函的前缀,可以这样:(gdb) b make_ <按TAB键>。
    break filename:linenum       在源文件filename的linenum行处停住
    break filename:function       在源文件filename的function函数的入口处停住
    break *address                       在程序运行的内存地址处停住
    break       break命令没有参数时,指在下一条指令处停住
    break … If …        在条件成立时程序停住
    break thread threadnum       定义在线程threadnum上的断点,如break BootAnimation.cpp:364 thread 28 if bartab > lim

===============设置观察点=================

  • watch expr       设置观察点,当表达式expr的值变化时,程序停住
  • rwatch expr       设置观察点,当表达式expr的值被读时,程序停住
  • awatch expr       设置观察点,当表达式expr的值被读或写时,程序停住

===============设置捕捉点=================

  • catch       设置被调试程序捕捉点,当event发生时,程序停住
    catch catch [args]       捕捉一个C++捕捉到的异常
    catch throw [args]       捕捉一个C++抛出的异常
    catch syscall [args]       捕捉系统调用

===============维护被调试程序断点==================

  • condition N COND       修改断点号为N的停止条件为COND
  • ignore N COUNT       忽略断点号为N的停止条件COUNT次
  • clear [linenum|funname|*]       清除指定的行或函数断点
  • d(delete) [breakpoints][range]        清除指定的断点
  • disable [breakpoints] [range…]        禁用指定的断点
  • enable [breakpoints] [range…]        启用指定的断点

===============为停止点设定运行命令================

  • commands N       调试程序在断点号为N的断点处停止后,执行命令
    执行命令
    end

===============显示被调试程序的信息================

  • info subcommand       显示被调试程序的某些信息,可用help info查看子命令。如:
    info breakpoints [n]       显示所有断点(或断点n)信息
    info watchpoints [n]       显示所有观察点(或观察点n)信息
    info program       查看被调试程序的执行状态
    info args        打印出当前函数的参数名及其值
    info locals       打印出当前函数中所有局部变量及其值
    info display       查看display设置的自动显示的信息
    info frame       查看栈帧信息,包括程序语言

===============运行及查看被调试信息================

  • r(run)       运行被调试程序
  • c(continue)       从断点出开始继续执行直到结束或下一个断点
  • s(step)       单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是此函数被编译有debug信息
  • n(next)       单步跟踪,如果有函数调用,他不会进入该函数
  • p(print)       打印表达式的值
    可以查看全局变量(所有文件可见)、静态全局变量(当前文件可见)、局部变量(当前Scope可见)
    p file::var       查看文件file中的变量var
    p func::var       查看函数file中的变量var
    p start@len       查看一段连续的内存空间的值,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度,如:
    int array = (int ) malloc (len * sizeof (int));
    p *array@len
    p/format expr       按指定的格式显示表达式expr(format:x, d, f, c, u, o, t, a)
  • display[/fmt] expr       程序停下来后就会显示expr的值
  • undisplay/delete displaynum/disable displaynum/enable displaynum
  • bt        查看当前函数调用堆栈信息
  • f(frame)       查看当前栈层信息,包括栈的层编号、当前的函数名、函数参数值、函数所在文件及行号和函数执行到的语句
  • finish       运行程序,直到当前函数完成返回
  • u(until)       运行程序直到退出循环体

===============显示源代码===============

  • l(list)       列出具体的函数或代码行
    list       显示当前行后面的源程序
    list -        显示当前行前面的源程序
    list +       显示当前行后面的源程序
    list linenum       显示程序第linenum行的周围的源程序
    list file:filenum       显示文件file中的filenum行
    list file:func       显示文件file中的函数func
    list funcname       显示函数名为function的函数的源程序,如list android::BootAnimation::movie

===============搜索源代码===============

  • forward-search reg       利用正则表达式前向搜索源码
  • search reg       利用正则表达式前向搜索源码
  • reverse-search reg       利用正则表达式在全部源码中进行搜索

===============设置被调试程序参数/gdb配置==============

  • set subcommand       设置gdb环境变量,可以使用help set查看set子命令。如:
    set args       设置被调试程序运行参数
    set directories       设置源文件搜索路径,多个使用“:”分割
    set solib-search-path       设置符号表搜索路径
    set environment varname [=value]       设置环境变量。如:set env USER=llj
    set listsize num       设置一次显示源代码的行数
    set step-mode on       打开step-mode模式,于是,在进行单步跟踪时,程序不会因为没有debug信息而不停住。这个参数有很利于查看机器码
    set print address on       打开地址输出, 当程序显示函数信息时,GDB会显出函数的参数地址
    set print array on       打开数组显示,打开后当数组显示时,每个元素占一行,如果不打开的话,每个元素则以逗号分隔
    set print elements num       设置显示数组元素的最大个数
    set print null-stop on/off       如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示
    set print pretty on/off       打开后gdb显示结构体时会比较漂亮
    set print sevenbit-strings       设置字符显示,是否按“/nnn”的格式显示
    set print union on/off       设置显示结构体时,是否显式其内的联合体数据
  • show subcommand       显示调试器的信息,使用help show查看子命令。如:
    show environment [varname]       查看环境变量
    show listsize       显示当前listsize的设置
    show directories       显示定义了的源文件搜索路径
    show language       查看gdb当前的语言环境

===============其他=================

  • q(quit)       退出gdb
  • handle        处理信号

===============shell命令=================
       此外gdb中可以执行shell命令,使用SHELL环境变量定义的可执行程序来执行shell命令,常用的命令如下:

  • show environment SHELL       查看shell执行程序
  • make       重新编译程序,相当于shell make
  • cd       必变工作目录,相当于shell cd
  • pwd       查看当前工具目录,相当于shell pwd

===============调整程序线路=================
       一旦使用GDB挂上被调试程序,当程序运行起来后,你可以根据自己的调试思路来动态地在GDB中更改当前被调试程序的运行线路或是其变量的值,这个强大的功能能够让你更好的调试你的程序,比如,你可以在程序的一次运行中走遍程序的所有分支。

  • 修改变量的值
    print x = 4       C/C++语法,把变量x的值修改为4
    set var width=10       用gdb命令将参数width值修改为10
  • 跳转执行
    jump +num       当前运行点向下偏移num行开始执行
    jump linenum       从当前调试文件的linenum行开始执行
    jump file:linenum       从file的linenum行开始执行
  • 产生信号量
    singal SIGNAL       发送信号SINGAL给被调试程序
  • 强制函数返回
    return       强制函数返回,忽略未执行的语句
    return result       强制函数返回结果result,忽略未执行的语句
  • 强制调用函数
    call func       调用当前程序中的函数
    print func       调用当前程序中的函数
发布了50 篇原创文章 · 获赞 54 · 访问量 18万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览