概述
笔者一开始上手写代码使用VS工具,对于当时单步调试,设置断点等还记忆犹新。后面转战Linux平台开发,虽然知道有gdb strace等好用工具,但一直没能实际操刀练手过。本文用于记录和总结笔者开发过程中遇到的好用的调试工具。文章对这些工具并不能很详细讲解,仅作为一个抛砖引玉的过程,学会这种调试方法后,其他功能可自行车资料扩展。
strace工具
按照strace官网的描述, strace是一个可用于诊断、调试和教学的Linux用户空间跟踪器。我们用它来监控用户空间进程和内核的交互,比如系统调用、信号传递、进程状态变更等。光看说明还是比较抽象的,我们以一个实际需求出发,看下strace工具能给我们带来多大的便利。
【问题背景】
在RK3399平台使用gst-play-1.0播放某个混合类型的视频源时,播放一段时间会卡住,并有如下异常log。
(gst-play-1.0:708): GStreamer-CRITICAL **: gst_mini_object_unref: assertion 'GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) > 0' failed
[ 1640.136164] import fd 52 failed -9
[ 1640.136713] rk_iommu ff670800.iommu: found import hdl -9
[ 1640.137197] iep ff670000.iep: try find negative idx -9
[ 1640.137713] iep ff670000.iep: iep_drm_map_iommu can not find -9 buffer in list
[ 1640.138355] rk_iommu ff670800.iommu: ion map iommu failed
[ 1643.516798] iep dpi mode inactivity
[ 1643.517145] IEP Power OFF
通过上述log中import fd 52 failed -9
, 可以初步定为问题 :drm buffer对应的fd出现异常。
既然是文件句柄异常,首先怀疑该文件句柄被意外close了。因此需要动态追踪gst-player-1.0在播放视频时关于close的调用情况。gstreamer管道本设计初衷就是让用户不必了解内部element的实现,gst-player-1.0构建的pipeline包含了多个element,这些element又有很多处调用close,如果通过添加打印的方式追踪gst-player-1.0对close调用情况的话,这应该会死人的。这时候strace就应该隆重出场了。
【需求描述】
我们来总结下 最终需求 :我们需要查看gst-player-1.0这个bin程序中谁触发了drm buffer对应fd(上述log中fd=52)的close调用? 我们通过指定strace追踪gst-player-1.0中的“open、close”系统调用,命令如下
strace -f -e trace=open,close gst-play-1.0 /data/bxysdw.mpg
【结论】
上图红框的打印便是strace的功劳,这句话出现印证了我上述的怀疑点:文件句柄被意外close了。
strace只是帮我们实时分析app中的系统调用情况,但究竟是谁触发这个close产生的,则需要通过gdb工具来分析。
gdb工具
在linux程序调成中gdb是最常用的工具。通过这个工具,我们可以像VS2010一样,可以单步调试,设置断点,打印变量值等等。本文衔接上述问题,演示如何使用gdb找到问题点。
【RK3399 gdb开启】
因为基于RK3399 buildroot开发,因此这里也提下如何开启gdb工具。在buildroot配置中按下图选择,然后重新编译rootfs.img
【gdb调试方法一:直接调试】
我们使用gstreamer播放的命令为:gst-player-1.0 /data/bxysdw.mpg
,本节使用gdb直接调试该bin程序。
- gdb + app 进入gdb调试页面
- 输入
r + app参数
,带参数运行app。 - 使用ctrl + c组合按键,可暂停app的运行。
- 输入
b + 断点函数名
,对所有调用该函数的地方设置断点。 - 输入
c
,继续运行app - 遇到断点则中断
- 输入
bt
,查看函数堆栈信息。
如下是在rk3399 evb上运行的log,注意(gdb)
提示符后面输入的是gdb内部命令。
[root@rk3399:/]# gdb gst-play-1.0
GNU gdb (GDB) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "aarch64-buildroot-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from gst-play-1.0...(no debugging symbols found)...done.
(gdb)
(gdb) r /data/bxysdw.mpg
===> ctrl z
(gdb) b close
Breakpoint 1 at 0x7fa4acb6f4 (22 locations)
(gdb) c
...省略运行时的log...
Thread 9 "mppvideodec0:sr" hit Breakpoint 1, 0x0000007fa72d1d2c in close ()
from /lib/libpthread.so.0
(gdb) bt
#0 0x0000007fa72d1d2c in close () from /lib/libpthread.so.0
#1 0x0000007fa4ca89d8 in gst_fd_mem_free (allocator=<optimized out>,
gmem=0x7f9c00e990) at gstfdmemory.c:71
#2 0x0000007fa752f404 in _gst_memory_free (mem=0x7f9c00e990) at gstmemory.c:97
#3 0x0000007fa74ffab8 in gst_memory_unref (memory=<optimized out>)
at ../gst/gstmemory.h:345
#4 _gst_buffer_free (buffer=0x7f980436a0) at gstbuffer.c:749
#5 0x0000007fa4c73e80 in gst_buffer_unref (buf=<optimized out>)
at /home/flc/rk3399-new/buildroot/output/rockchip_rk3399/host/bin/../aarch64-buildroot-linux-gnu/sysroot/usr/include/gstreamer-1.0/gst/gstbuffer.h:442
#6 gst_mpp_video_dec_loop (decoder=0x7f9c039680) at gstmppvideodec.c:495
#7 0x0000007fa7562bd8 in gst_task_func (task=0x7f980263b0) at gsttask.c:332
#8 0x0000007fa73ae028 in ?? () from /usr/lib/libglib-2.0.so.0
#9 0x0000007fa7439400 in __glib_assert_msg () from /usr/lib/libglib-2.0.so.0
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
通过上述堆栈信息可以看出,最终是通过gstmppvideodec.c:495的gst_mpp_video_dec_loop函数触发了这个close。如果你输入bt命令后,显示的都类似#8那样,带很多“?”,那表明运行时依赖的库都是strip过的,可以在buildroot/output/xxx/staging目录下找到对应库,然后push到板子中进行替换,然后重复上述过程。需要把“?”对应的库都替换为带有debug信息的库,才能看到完整信息。
【gdb调试方法二:间接调试】
间接调试是先运行app,然后可以在另一个命令终端(串口/adb shell)中使用gdb 对这个进程进行调试。
- 运行app(直接带参数)
- ctrl + z 将当前进程切换后台,并暂停。
- ps 查看app对应的进程id
- 在另一终端输入
gdb -p +进程id
,然后进入了gdb调试界面。 - 在gdb调试界面中输入
b + 断点函数名
,设置函数断点。 - 回到app的操作界面,输入
fg
,让app回到前台并且运行。 - 然后再回到gdb操作界面,输入
c
,让app运行到断点停止。
app终端log:
[root@rk3399:/]# gst-player-1.0 /data/bxysdw.mpg
===> 运行后,输入ctrl + z,让程序进入后台并暂停。
===> 等gdb终端设置好断点之后输入下面命令,让程序回到前台
[root@rk3399:/]# fg
gdb终端的界面log:
/ # ps
....略....
731 root 969m T gst-play-1.0 /data/bxysdw.mpg
749 root 2420 S /bin/sh -
751 root 2420 R ps
/ #
/ #
/ # gdb -p 731
GNU gdb (GDB) 8.1.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "aarch64-buildroot-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 731
[New LWP 732]
[New LWP 733]
[New LWP 734]
[New LWP 735]
[New LWP 736]
[New LWP 737]
[New LWP 738]
[New LWP 739]
[New LWP 740]
[New LWP 741]
[New LWP 742]
[New LWP 744]
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
warning: Unable to find libthread_db matching inferior's thread library, thread debugging will not be available.
Thread 1 "gst-play-1.0" received signal SIGTSTP, Stopped (user).
0x0000007fa72f632c in poll () from /lib/libc.so.6
(gdb) b close
Breakpoint 1 at 0x7fa4c2e6f4 (22 locations)
(gdb) c
...省略运行时的log...
Thread 9 "mppvideodec0:sr" hit Breakpoint 1, 0x0000007fa72d1d2c in close ()
from /lib/libpthread.so.0
(gdb) bt
#0 0x0000007fa72d1d2c in close () from /lib/libpthread.so.0
#1 0x0000007fa4ca89d8 in gst_fd_mem_free (allocator=<optimized out>,
gmem=0x7f9c00e990) at gstfdmemory.c:71
#2 0x0000007fa752f404 in _gst_memory_free (mem=0x7f9c00e990) at gstmemory.c:97
#3 0x0000007fa74ffab8 in gst_memory_unref (memory=<optimized out>)
at ../gst/gstmemory.h:345
#4 _gst_buffer_free (buffer=0x7f980436a0) at gstbuffer.c:749
#5 0x0000007fa4c73e80 in gst_buffer_unref (buf=<optimized out>)
at /home/flc/rk3399-new/buildroot/output/rockchip_rk3399/host/bin/../aarch64-buildroot-linux-gnu/sysroot/usr/include/gstreamer-1.0/gst/gstbuffer.h:442
#6 gst_mpp_video_dec_loop (decoder=0x7f9c039680) at gstmppvideodec.c:495
#7 0x0000007fa7562bd8 in gst_task_func (task=0x7f980263b0) at gsttask.c:332
#8 0x0000007fa73ae028 in ?? () from /usr/lib/libglib-2.0.so.0
#9 0x0000007fa7439400 in __glib_assert_msg () from /usr/lib/libglib-2.0.so.0
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
【gdb设置断点】
- 根据函数名设置断点
如上文使用的b close
,这种方式较为简单,所有调用到close函数的地方都被设为了断点。 - 设置源文件某一行代码为断点
比如命令:b gstmppvideodec.c:495
则表示在gstmppvideodec.c的495行设置为断点,每次执行到此处代码都会触发。值得注意的是,想指定某一行代码为断点,编译库或app时必须带-g
参数,比如gcc -g -o test test.c
。 - 条件触发断点
比如命令:b gst_mini_object_unref if mini_object==0
。函数声明如下图所示,该命令表示当gst_mini_object_unref
函数参数mini_object==0
时,触发断点。
【gdb其他常用命令】
- bt
打印堆栈信息,用法如上文。 - print
触发断点时,可通过该命令打印变量值。 - show
与print类似,但show表示每次触发断点都会自动打印你要show的变量。 - unshow
取消show操作,show时会显示标号,使用unshow + 标号
则可取消show。 - list
显示断点附近的源码,多次list,则可查看更多源码。 - 回车
在gdb中,回车表示执行上一次命令。
注意:有些库会发出SIGILL信号,在gdb调试时,可通过在gdb命令界面中输入如下命令屏蔽该信号:
handle SIGILL pass nostop noprint
meld 文件对比工具
工程源码对比有时也是Debug的一种途径。windows上经常使用Byond Compare文件对比工具,该工具收费。Ubuntu上可以使用原生diff进行文件对比,但习惯了带UI的人来说diff很别扭。此处推荐使用meld工具进行文件/文件夹对比。UI与Byond Compare差不多。
安装命令:sudo apt-get install meld
对比文件:meld file1 file2
程序前后台切换
切换后台并暂停:ctrl + z
切换到前台运行:fg
buildroot中给包添加"-g"编译选项
笔者此处介绍的方法较为笨拙,但也能达到目的。较为高大上的方法(比如直接配置编译脚本)笔者目前还不会,以后若学会,再进行补充。
以rk3399 buildroot 的gstreamer1为例,想使用buildroot原本的交叉编译环境,生成一个带-g
编译参数的库。可直转到buildroot/output/xxx/build/gstreamer1-1.14.4/目录,直接修改MakeFile 的CFLAGS,添加“-g”选项,然再直接make即可。