赞
踩
在开发Android Native程序时(仅C/C++代码,无APK应用),之前在调试的过程中一直只是使用添加LOG的方式来定位程序的问题,而在Linux上开发平台程序时,可以方便使用GDB工具来调试,所以迫切的希望在调试Android Native程序也能一样方便。
在Android官方文档使用 GDB中介绍了如何使用GDB调试。它提到的前置条件是使用Android源码中gdbclient.py脚本,此脚本会设置端口转发,在设备上启动相应的 gdbserver,在主机上启动相应的 gdb,配置 gdb 以查找符号,然后将 gdb 连接到远程 gdbserver。后续还提到可以使用 VS Code 调试程序前端(而非 GDB CLI 接口)来控制和调试在设备上运行的原生代码。它使用了gdbclient.py脚本生成json文件,然后在VS Code中的launch.json文件配置。若有调试机对应的Android源码,这当然是一种很好的方式,详细使用方法可以参考使用VS Code调试Android C++代码。但存在的问题是,我们没有对应的Android源码时,该怎么办呢?
在Android gdb调试中详细阐述了使用gdb调试的过程。Android对于C/C++代码的调试方式一般选用gdb+gdbserver的方式,其中gdbserver运行在目标系统中(如手机),gdb运行在宿主机上(如linux)。 一般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/下。在vscode debug Android真机中展示了如何使用VS Code调试Android native代码。里面简单的提到了如何配置VS Code中的launch.json文件。在Android Debugging with Visual Studio Code中提到gdb工具可以使用Android NDK中内置的,而不是使用Android源码中的,显然这样比使用源码中的会更加方便。gdb的具体路径:
Windows <NDK_ROOT>\prebuilt\windows-x86_64\bin
Linux <NDK_ROOT>\prebuilt\linux-x86_64\bin
macOS <NDK_ROOT>\prebuilt\darwin-x86_64\bin
同时该文章详细记录了VS Code调试配置的过程,是一篇非常有用的参考文档。现在我们探索的过程已经差不多了,从中了解了如何使用VS Code + NDK在无需Android源码下调试Native程序。接下来我们实际操作一下,并观察是否会存在未知的问题。
1、安装Visual Studio Code以及在其中安装C/C++扩展插件。
2、下载好Android NDK,方便使用其中GDB,它在<NDK_ROOT>/prebuilt/linux-x86_64/bin/目录下。
3、编译项目时,需要添加调试信息,即在Android.mk中添加LOCAL_CFLAGS += -g,或者通过在ndk-build命令行上传递NDK_DEBUG=1。同时如果项目的Application.mk文件指定APP_OPTIM设置,必须将其设置为debug以禁用编译器优化。
1、VS Code中打开需要调试的项目,点击运行>>启动调试(或直接按F5快捷键)。
在弹出的窗口中选择C++(GDB/LLDB),若无此选项请检查C/C++扩展插件是否安装。
选择默认配置,然后会在当前项目中.vscode中生成launch.json文件。
2、launch.json的设置属性如下所述。
program:要调试的程序。这应该指向带有调试符号的可执行文件的本地版本(非剥离版本),通常在项目的构建目录下的obj/local/armeabi-v7a中(对于64位构建,则位于obj/local/arm64-v8a中)。
miDebuggerPath:gdb可执行文件的路径。如上所示,它应指向Android NDK中的目录。
miDebuggerServerAddress:要连接的目标地址。
preLaunchTask:在启动调试器之前要执行的任务。
示例launch.json文件:
{ "version": "0.2.0", "configurations": [ { "name": "gdb android", "type": "cppdbg", "request": "launch", "program": "${workspaceFolder}/obj/local/arm64-v8a/test", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": true, "MIMode": "gdb", "setupCommands": [ { "text": "-enable-pretty-printing", "description": "为 gdb 启用整齐打印" }, ], // 打印更多调试log // "logging": { // "engineLogging": true, // }, "preLaunchTask": "build task", "miDebuggerPath": "${env:ANDROID_NDK}/prebuilt/linux-x86_64/bin/gdb", "miDebuggerServerAddress": ":9090" } ] }
有关其他信息,请参考 C/C ++调试文档,日志记录属性"engineLogging"可用于启用其他日志记录输出,这对于调试器未按预期运行时的故障排除很有用。
3、前面提到preLaunchTask任务,这些任务也可以用于支持调试。可以定义一个任务来编译代码,push程序到手机端,转发调试器端口等操作。
在.vscode中添加一个tasks.json文件。如下:
{
"version": "2.0.0",
"tasks": [
{
"label": "build task",
"type": "shell",
"command": "source debug.sh"
}
]
}
注意task中"label"的值和launch中"preLaunchTask"的值一致。这里我们执行一个shell命令,将具体的任务放在debug.sh中处理。有关任务配置的更多信息,请参见VSCode文档。
4、在项目中新建debug.sh文件,内容如下:
#!/bin/bash
ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=./Application.mk APP_BUILD_SCRIPT=Android.mk clean
ndk-build NDK_DEBUG=1 NDK_PROJECT_PATH=. NDK_APPLICATION_MK=./Application.mk APP_BUILD_SCRIPT=Android.mk -j16
adb push obj/local/arm64-v8a/test /system/bin/
adb shell chmod 777 /system/bin/test
adb forward tcp:9090 tcp:9090
# adb shell gdbserver64 :9090 /system/bin/test
gnome-terminal -- bash -c "adb shell gdbserver64 :9090 /system/bin/test"
a、首先使用ndk-build编译代码,里面增加了NDK_DEBUG=1,使其生成的程序包含调试信息;再将生成的程序push到/system/bin/目录下,同时赋予可执行权限。
b、设置端口转发
在开发机上设置端口转发,命令如下:
adb forward tcp:9090 tcp:9090
命令说明:表示通过adb映射tcp端口1234,命令中前面的是local的端口,后面的是remote的端口。命令中的端口号必须与gdbserver命令中的监听端口号相同,否则会导致gdb无法与gdbserver连接。
c、在目标设备上执行gdbserver(32bit的程序)或者gdbserver64(64bit的程序)
方式1:调试运行中的应用或进程
pid=`adb shell pidof test`
adb shell gdbserver64 :9090 --attach $pid
方式2:如需在进程启动时对其进行调试
adb shell gdbserver64 :9090 /system/bin/test
我们这里使用方式2测试,详细使用方法可以查看Android官方文档使用 GDB。
一切准备ok,接下可以开始调试了。
三种方式启动:
1、通过单击VS Code窗口左侧的Debug图标,启用“开始调试”面板;
2、从面板顶部的列表中选择“运行”,然后单击执行“启动调试”按钮;
3、直接快捷键F5。
一旦调试器开始连接,VSCode调试控制台将显示来自调试器的消息,并在必要时允许执行手动调试器命令(必须停止程序以执行调试命令)。调试面板将显示调试信息(变量监视,调用堆栈,断点等),调试器工具栏将提供对常见调试命令的访问。还支持基于鼠标光标的变量显示。
在上述测试用例中,由于代码简单,调试过程比较流畅。但在实际项目使用中,发现开启调试时非常缓慢。下面尝试解决此问题。
在VS Code调试控制台中会打印一些gdb的信息,从中可以看到它从手机端的动态库中读取了符号表信息。这就是导致启动过程缓慢的原因。
在Very slow debugging中提出了解决方案。通过set sysroot指定动态库的路径。我们需要先将所依赖的库pull到本机,如:
fingerprint=`adb shell getprop | grep ro.build.fingerprint`
old_fingerprint=$(cat debug_so_path/fingerprint.txt)
if [ "$old_fingerprint" != "$fingerprint" ]; then
echo "$fingerprint" > debug_so_path/fingerprint.txt
adb pull system/lib64/libm.so debug_so_path/system/lib64/
adb pull system/lib64/liblog.so debug_so_path/system/lib64/
adb pull system/lib64/libz.so debug_so_path/system/lib64/
adb pull system/lib64/libc.so debug_so_path/system/lib64/
adb pull system/lib64/libdl.so debug_so_path/system/lib64/
adb pull system/lib64/libc++.so debug_so_path/system/lib64/
adb pull system/lib64/libnetd_client.so debug_so_path/system/lib64/
adb pull system/bin/linker64 debug_so_path/system/bin/
fi
这里是判断了手机指纹信息,若调试手机变更,则需重新pull。
这时可以在上述的调试控制台中设置solib_path。
-exec set sysroot=./debug_so_path/
更加方便的设置方式是在前面提到的launch.json文件中直接添加。
"setupCommands": [
{
"text": "-enable-pretty-printing",
"description": "为 gdb 启用整齐打印"
},
// 新增
{
"text": "set sysroot ${workspaceFolder}/debug_so_path/",
"description": "set sysroot path"
},
],
接下来测试中,发现依然比较慢,继续分析原因发现是因为依赖的库太多,需要加载很多符号表,导致缓慢。
使用readelf查看目标程序的依赖关系
chenls@chenls-pc:arm64-v8a$ readelf -a test | grep NEED
[ 9] .gnu.version_r VERNEED 0000000000000510 00000510
0x0000000000000001 (NEEDED) 共享库:[libm.so]
0x0000000000000001 (NEEDED) 共享库:[liblog.so]
0x0000000000000001 (NEEDED) 共享库:[libc.so]
0x0000000000000001 (NEEDED) 共享库:[libdl.so]
0x000000006ffffffe (VERNEED) 0x510
0x000000006fffffff (VERNEEDNUM) 1
chenls@chenls-pc:arm64-v8a$
我所在实际项目中由于依赖了libandroid.so ,libandroid.so 又依赖了其它非常多的动态库,这就会导致加载符号表时非常慢,目前的解决方案是,在调试时修改代码,减少库的依赖,加快调试速度。此方法只是避开了问题,若有更好的方式,请告知。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。