调试项目
在构建应用后,您可能需要对其进行调试。本节介绍 NDK 的调试工具。
首先介绍如何使用 ndk-gdb
工具调试代码。 最后说明 ndk-stack
工具,该工具可帮助您在调试时使用 ADB logcat 工具。
ndk-gdb
NDK 包含一个名为 ndk-gdb
的帮助程序 shell 脚本,可轻松地为 NDK 生成的机器代码启动原生调试会话。
要求
要运行原生调试,您必须遵循以下要求:
- 使用
ndk-build
脚本构建您的应用。ndk-gdb
脚本不支持使用旧的make APP=
方法进行构建。 - 通过添加一个将
android:debuggable
属性设置为true
的
元素,在AndroidManifest.xml
文件中启用应用调试。 - 构建在 Android 2.2(Android API 级别 8)或更高版本上运行的应用。
- 在运行 Android 2.2 或更高版本的设备或模拟器上进行调试。 在
AndroidManifest.xml
文件中声明的 API 级别对于调试并不重要。 - 在 Unix shell 中开发您的应用。在 Windows 上,使用 Cygwin 或实验性
ndk-gdb-py
Python 实现。 - 使用 GNU Make 3.81 或更高版本。
用法
如需调用 ndk-gdb
脚本,切换到应用目录或该目录下面的任何目录。 例如:
cd NDK/ndk-gdb
此处,$PROJECT
指向项目的根目录,$NDK
指向 NDK 安装路径。
调用 ndk-gdb
时,它配置此会话以查找源文件和生成的原生库的符号/调试版本。 成功附加到您的应用进程后,ndk-gdb
将输出一长串错误消息,表示其无法找到各种系统库。 这很正常,因为您的主机不包含您的目标设备上的这些库的符号/调试版本。 您可以完全忽略这些消息。
接下来,ndk-gdb
显示一个正常的 GDB 提示。
您使用与 GNU GDB 交互的方式与 ndk-gdb
进行交互。例如,您可以使用 b
设置断点,使用 c
(表示“continue”)继续执行。 有关完整的命令列表,请参阅 GDB 手册。
请注意,当您退出 GDB 提示时,您正在调试的应用进程将停止。此行为是 gdb 的一个局限。
ndk-gdb
可处理许多错误情况,如果它发现问题,将显示信息性错误消息。这些检查包括确保符合以下条件:
- 确保 ADB 位于您的路径中。
- 确保您的应用在其清单中声明为可调试。
- 确保设备上安装的具有相同软件包名称的应用同样可调试。
默认情况下,ndk-gdb
搜索一个已运行的应用进程,如果未找到,则显示错误。 不过,您可以使用 --start
或 --launch=
选项在调试会话前自动启动您的 Activity。 如需了解详细信息,请参阅选项。
选项
若要查看完整的选项列表,请在命令行上键入 ndk-gdb --help
。表 1 显示了许多比较常用的选项及其简要说明。
表 1. 常用 ndk-gdb 选项及其说明。
通过这个指定的选项启动 ndk-gdb
将启动应用清单中列出的第一个可启动 Activity。 使用 --launch=
来启动下一个可启动 Activity。 若要转储可启动 Activity 的列表,请从命令行运行 --launch-list
。
| 选项 | 说明> |
| --verbose
|
此选项指示构建系统打印有关原生调试会话设置的详细信息。 仅在调试程序无法连接到应用,且 ndk-gdb
显示的错误消息不充分时才需要用它调试问题。
|
| --force
| 默认情况下,如果 ndk-gdb
发现另一个原生调试会话已在相同设备上运行,它将会取消运行。 此选项将终止另一个会话,并将其替换为新的会话。 请注意,此选项不会终止正在调试的实际应用,您必须另行终止它。 |
| --start
|
当您启动 ndk-gdb
时,默认情况下,它尝试附加到目标设备上您的应用已运行的实例。 您可以替换此默认行为,在调试会话前使用 --start
在目标设备上显式启动应用。
|
| --launch=
|
此选项类似于 --start
,不过它允许您从应用启动特定 Activity。 仅当您的清单定义多个可启动 Activity 时,才可使用该功能。
|
| --launch-list
|
这个便捷选项打印在您的应用清单中找到的所有可启动 Activity 名称的列表。--start
使用第一个 Activity 名称。
|
| --project=
| 此选项指定应用项目目录。如果您希望不必先切换到项目目录就可启动脚本,则该选项很有用。 |
| --port=
|
默认情况下,ndk-gdb
使用本地 TCP 端口 5039 与它在目标设备上调试的应用进行通信。 使用不同的端口让您可以在本地调试连接至相同主机的不同设备或模拟器上运行的程序。
|
| --adb=
|
此选项指定 adb 工具可执行文件。 只有在您未设置包括该可执行文件的路径时才需要使用此选项。
|
| * -d
* -e
* -s
|
这些标志与具有相同名称的 adb 命令类似。如果您有连接至主机的多个设备或模拟器,请设置这些标志。 其含义如下所示:
-d
- 连接至单个物理设备。
-e
- 连接至单个模拟器设备。
-s
- 连接至特定设备或模拟器。此处,
是设备的名称(如adb devices
命令所列出)。
或者,您可以定义 ADB_SERIAL
环境变量以列出特定的设备,无需具体的选项。
|
| * --exec=
* -x
|
此选项指示 ndk-gdb
在连接至它正在调试的进程后运行在
中找到的 GDB 初始化命令。 如果您要重复执行某些操作,如设置断点列表,然后继续自动执行,则该功能很有用。
|
| --nowait
|
停用暂停 Java 代码,直到连上 GDB。传递此选项可能会使调试程序错过早期的断点。
|
| --tui
-t
|
启用 Text User Interface(如果可用)。
|
| --gnumake-flag=
|
此选项是在查询 ndk-build
系统以获取项目信息时要传递到该系统的额外标志(或多个标志)。 您可以在同一个命令中使用此选项的多个实例。
|
| --stdcxx-py-pr={auto|
|
在 C++ 标准库中使用指定的 Python pretty-printer 显示相应类型。auto
模式通过查看用于 libstdc++
库的.so
文件运行,因此,该模式仅适用于共享库。 以静态方式链接到 libstdc++
库时,您必须指定所需的打印机。 默认值为 none
。
|
注:此表中的最后三个选项仅适用于 ndk-gdb
的 Python 版本。
线程支持
如果您的应用在 Android 2.3(API 级别 9)以前的版本上运行,则 ndk-gdb
无法正确调试原生线程。 调试程序只能调试主线程,abd 完全忽略其他线程的执行。
如果您在非主线程上执行的函数上放置一个断点,则程序将退出,GDB 将显示以下消息。
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
The program no longer exists.
ndk-stack
ndk-stack
工具让您可以在堆叠追踪出现在 adb logcat
的输出中时过滤它们。 它还可以从源代码将共享库中的任意地址替换为对应的
值,从而更容易找出问题所在。
例如,它可将下面的内容:
I/DEBUG ( 31): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG ( 31): Build fingerprint: 'generic/google_sdk/generic/:2.2/FRF91/43546:eng/test-keys' I/DEBUG ( 31): pid: 351, tid: 351 %gt;%gt;%gt; /data/local/ndk-tests/crasher <<< I/DEBUG ( 31): signal 11 (SIGSEGV), fault addr 0d9f00d8 I/DEBUG ( 31): r0 0000af88 r1 0000a008 r2 baadf00d r3 0d9f00d8 I/DEBUG ( 31): r4 00000004 r5 0000a008 r6 0000af88 r7 00013c44 I/DEBUG ( 31): r8 00000000 r9 00000000 10 00000000 fp 00000000 I/DEBUG ( 31): ip 0000959c sp be956cc8 lr 00008403 pc 0000841e cpsr 60000030 I/DEBUG ( 31): #00 pc 0000841e /data/local/ndk-tests/crasher I/DEBUG ( 31): #01 pc 000083fe /data/local/ndk-tests/crasher I/DEBUG ( 31): #02 pc 000083f6 /data/local/ndk-tests/crasher I/DEBUG ( 31): #03 pc 000191ac /system/lib/libc.so I/DEBUG ( 31): #04 pc 000083ea /data/local/ndk-tests/crasher I/DEBUG ( 31): #05 pc 00008458 /data/local/ndk-tests/crasher I/DEBUG ( 31): #06 pc 0000d362 /system/lib/libc.so I/DEBUG ( 31):
转换为更容易阅读的输出:
********** Crash dump: ********** Build fingerprint: 'generic/google_sdk/generic/:2.2/FRF91/43546:eng/test-keys' pid: 351, tid: 351 >>> /data/local/ndk-tests/crasher <<< signal 11 (SIGSEGV), fault addr 0d9f00d8 Stack frame #00 pc 0000841e /data/local/ndk-tests/crasher : Routine zoo in /tmp/foo/crasher/jni/zoo.c:13 Stack frame #01 pc 000083fe /data/local/ndk-tests/crasher : Routine bar in /tmp/foo/crasher/jni/bar.c:5 Stack frame #02 pc 000083f6 /data/local/ndk-tests/crasher : Routine my_comparison in /tmp/foo/crasher/jni/foo.c:9 Stack frame #03 pc 000191ac /system/lib/libc.so Stack frame #04 pc 000083ea /data/local/ndk-tests/crasher : Routine foo in /tmp/foo/crasher/jni/foo.c:14 Stack frame #05 pc 00008458 /data/local/ndk-tests/crasher : Routine main in /tmp/foo/crasher/jni/main.c:19 Stack frame #06 pc 0000d362 /system/lib/libc.so
用法
若要使用 ndk-stack
,首先,您需要一个包含应用共享库的符号版本的目录。 如果您使用 NDK 构建系统 (ndk-build
),则这些共享库文件位于 $PROJECT_PATH/obj/local/
下,其中
表示您的设备的 ABI。 默认情况下,系统使用 armeabi
ABI。
可通过两种方式使用此工具。您可以将 logcat 文本作为直接输入馈送到程序。例如:
adb logcat | PROJECT_PATH/obj/local/armeabi
您也可以使用 -dump
选项将 logcat 指定为输入文件。例如:
adb logcat > /tmp/foo.txt
PROJECT_PATH/obj/local/armeabi -dump foo.txt
该工具在开始解析 logcat 输出时将查找第一行星号。例如:
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
注:在复制/粘贴追踪时,请别忘了此行,否则 ndk-stack
无法正常工作。