gdb调试Android动态库脚本

发表于2018-12-30
评论0 6.6k浏览

1. 背景

  • 为什么会弄这玩意?因为要弄这个热更工具,gdb是绕不过的坎。为什么要弄这个工具,因为我们一直以来客户端世界里都没有lua(Miniserver的不算,那就是跑在客户端的服务器,通过网络层和客户端通信),用ulua之类的大家都得学,多个东西多个鬼,能少则少。
  • 理论上,ndk提供了gdb的封装脚本ndk-gdb,理论上能方便无缝地像在linux server一样远程连gdbserver。但因为gdb所在环境和手机环境的多样性,这个脚本基本跑不通。而且这个脚本不稳定,也在随版本号升级,你可以想象把脚本过一遍,还原出该设的环境变量,一升级,日了狗了,又不行了,还得再过一遍。×#&%¥!,还是把里面的东西扒出来,直接用gdb+gdbserver。但即使是直接用gdb+gdbserver,各个命令在不同环境的表现还是有差异,例如原来能用的adb shell的后台命令更新后就一直占着前台。

2. 调试

以下脚本均为bash脚本,在git bash下执行。gdb调试android的动态库,本质和gdb远程调试Linux程序一样:远端启动gdbserver attach上要调试的应用,同时开放监听端口,然后gdb远程连上去调试。所以其涉及远端(Android)和本机两个系统。

2.1. 准备环境变量

export PACKAGE_NAME=com.test.test
export NDK=/d/android-ndk-r13b/
export GDBSERVER_BIN=$NDK/prebuilt/android-x86/gdbserver/gdbserver
export GDBC=$NDK/prebuilt/windows-x86_64/bin/gdb.exe
export START_ACTIVITY=$PACKAGE_NAME/$PACKAGE_NAME.UnityPlayerActivity

就这几个,因人而异,自己改,都很简单,不多做解释了,下面的命令会用到。
我用的是模拟器,规避权限问题,所以用的是x86版本的。

2.2. 准备远端环境

用Debug版本的动态库构建App,安装App,App本身是不是debug版本无所谓(App的这个debug版本是给java调试用的,如果是在没有root的真机上,需要开App的Debug,用run-as切到app对应userid,才能attach)。其实用Release版本的动态库也可以调,只是所有调试信息都没了,只能用public的符号或绝对地址下断点,想看参数得直接用栈指针,像下面这样。Ok,这么说,读者应该知道怎么灵活选择了,你想调的那个库是Debug版就行了。

b __android_log_print
commands
silent
printf "string:%s\n", *(char**)($esp+12)
bt
cont
end

2.3. 准备本机环境

  • 得保证能连上模拟器,有adb。
  • 本机环境我用的是Windows + git bash,如果你用其他环境,既然你都用了,我相信你能处理好。
  • 建个空目录,在里面启动git bash,执行以下脚本,而且每次App内动态库有变化,都要执行一次。
# 把端口映射打开,adb负责把本机的5039端口,映射到远端Android的5039端口
adb forward tcp:5039 tcp:5039 

#把远端Android的动态库取回本地,因为gdb只能从本地加载符号
#注意路径前无/,不然读者可以试试,很讨厌的
adb pull system/bin/app_process
adb pull system/bin/linker
adb pull system/lib/libc.so
adb pull data/data/$PACKAGE_NAME/lib/lib*.so

# gdb.setup是gdb启动的初始化脚本,省得每次gdb再敲一堆命令
# 脚本里忽略两个中断信号,设置so符号查找路径为本目录,然后连接远程的gdbserver
cat >gdb.setup <<end_of_gdb handle="handle" sigpwr="sigpwr" nostop="nostop" handle="handle" sigxcpu="sigxcpu" nostop="nostop" set="set" solib-search-path="solib-search-path" target="target" remote="remote" end_of_gdb="end_of_gdb" echo="echo" install="install" gdbserver="gdbserver" copy="copy" gdbserver_bin="gdbserver_bin" adb="adb" push="push" gdbserver="gdbserver" data="data" adb="adb" shell="shell" chmod="chmod" data="data" app="app" function="function" kill_gdbserver="kill_gdbserver" gdbserver_pid="adb shell " ps="ps" grep="grep" gdbserver="gdbserver" awk="awk" print="print" if="if" gdbserver_pid="gdbserver_pid" then="then" echo="echo" kill="kill" pre="pre" gdb="gdb" server="server" process="process" id:="id:" adb="adb" shell="shell" kill="kill" gdbserver_pid="gdbserver_pid" fi="fi" app="app" function="function" start_app="start_app" kill_gdbserver="kill_gdbserver" app_pid="adb shell " ps="ps" grep="grep" v="v" z="z" grep="grep" package_name="package_name" awk="awk" print="print" if="if" app_pid="app_pid" then="then" echo="echo" kill="kill" pre="pre" package_name="package_name" process="process" id:="id:" adb="adb" shell="shell" kill="kill" app_pid="app_pid" fi="fi" adb="adb" shell="shell" am="am" start="start" w="w" n="n" start_activity="start_activity" app_pid="adb shell " ps="ps" grep="grep" v="v" z="z" grep="grep" package_name="package_name" awk="awk" print="print" echo="echo" app="app" process="process" id:="id:" gdb="gdb" function="function" map_app="map_app" echo="echo" mapping="mapping" addr="addr" app_pid="adb shell " ps="ps" grep="grep" v="v" z="z" grep="grep" package_name="package_name" awk="awk" print="print" adb="adb" shell="shell" cat="cat" proc="proc"> maps
    sed -i -e '/add-symbol-file/d' gdb.setup
    ls lib*.so linker |xargs -n 1 -i bash -c "printf 'add-symbol-file {} (0x' && (grep {} maps | grep 'r-xp' | sed -e 's/-.*$//' | tr -d '\r\n')&& printf '+0x' && (readelf -S {}|grep ' .text ' | sed -e 's/^.*PROGBITS\s*//' -e 's/\s.*$//' |tr -d '\r\n') && echo ')'" | sed -e 's/^\(.*\)0x+/#not fould in maps ->\10x0+/' >>gdb.setup

    cat gdb.setup|grep add-symbol-file
}
  • 在远端启动gdbserver,attach app
function attach_app(){
    echo "killing launched gdbserver if any..."
    kill_gdbserver

    echo "starting gdbserver attaching $PACKAGE_NAME..."
    APP_PID=`adb shell "ps | grep -v ' Z ' |grep $PACKAGE_NAME" | awk '{print $2;}'`
    adb shell "data/local/tmp/gdbserver --attach :5039 $APP_PID &" 

    echo "data/local/tmp/gdbserver --attach :5039 $APP_PID &"
    #sleep 1

    #下面的注释掉了,因为升了版本后,跑到这就挂起了,下面就跑不到了
    #笔者只能另开中断跑下面的命令,就注释掉了
    #echo "running gdb..."
    #$GDBC -x ./gdb.setup 
}

2.5. 调

建议开三个终端(git bash),全都定义好变量和封装的函数。
一个跑

start_app

一个接着跑

attach_app

最后一个跑

map_app #这条命令比较慢,仅第一次启动的时候跑就好了
$GDBC -x ./gdb.setup

然后,读者就可以放飞自我了。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引