因个人此前调试c代码的时候只会在代码中加打印信息,然后编译代码,运行程序,搜索打印信息,查找问题...因此代码的调试效率很慢,经大佬提示使用gdb调试后,代码的调试效率提高了不少。因此对gdb调试的过程进行记录形成一个学习笔记,加强记忆,也方便大家的学习和交流。故本文档仅针对那些对于gdb调试一无所知的小白同学,当然也欢迎大佬们指正文档中的纰漏之处。大家相互学习,共同进步!
1. gdb介绍
gdb是GNU开源组织发布的一个强大的UNIX下的程序调试工具。一般来说,gdb主要完成下面四个方面的功能:
(1) 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序。 (2) 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式) (3) 当程序被停住时,可以检查此时你的程序中所发生的事。 (4) 动态的改变你程序的执行环境。
2. 基本操作
以c2_adas项目为例。
-
编译层面,在编译的时候需要在Makefile(CMakelists也是一样)里使用debug模式对程序进行编译,如下所示:
**************************************************
Makefile文件里的设定的变量,dbg,用来设置编译模式
export dbg:=release
**************************************************
Makefile.param.mk文件里,设定编译选项,如release模式下执行-o2的优化选项
ifeq ($(dbg),release)
$(warning release version)
DBG_FLG := -DGLOBAL_RELEASE_EN=1
OPTZM_FLG := -O2
else
ifdef g_release
_DEFS := -DGLOBAL_RELEASE_EN=$(g_release)
endif
DBG_FLG := -g
OPTZM_FLG := -O0
endif说明:
-O0: 不做任何优化,这是默认的编译选项。
-O和-O1: 对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化。
-O2: 是比O1更高级的选项,进行更多的优化。Gcc将执行几乎所有的不包含时间和空间折中的优化。当设置O2选项时,编译器并不进行循环打开()loop unrolling以及函数内联。与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。
-g 选项:将调试信息加入到可执行文件,这个是关键的一步。
-
编译完成后,在板子上执行程序,输入如下命令:
./gdb --args ./jimu_adas.elf 1 #若可执行文件不需要参数,则不用加--args选项
板子终端会打印如下信息:
# ./gdb --args ./jimu_adas.elf 1
GNU gdb (GDB) 9.2
Copyright (C) 2020 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-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 ./jimu_adas.elf...-
常规的调试步骤是,开始打断点调试,例如,我想在adas.c文件的DetectFunc2函数,或者1451行输入断点,则可在gdb模式下输入如下命令:
break adas.c:DetectFunc2 #按tab键可以自动补全,在adas.c文件里的DetectFunc2函数入口打上断点
break adas.c:1451 #或者写入如下命令,在adas.c文件中的1451行处打上断点-
输入n或next单步调试,实际信息是
1414 in../../adas/.//adas.c
,除了行号和文件名,并无其他有用的信息。
(gdb) n
1414 in ../../adas/.//adas.c若将文件路径添加进gdb中,执行如下命令:
dir /mnt/hewen/work/03_jmu_adas/c2_adas_src2/adas/
输入list file:N 查看指定文件的代码,
list adas.c:1414
得到如下信息:(gdb) list adas.c:1414
1409 //ptCameraProc = (TCameraProc *)ptThread->ptArg;
1410 s32RdFd = PipeGetReadDescriptor(&ptThread->tPipe);
1411 SimpleThreadLog_construct(&threadLog, 0, 1000);
1412 while (ptThread->s32Status != CMD_EXIT)
1413 {
1414 run_time = now_ms();
1415 tWaitTime.tv_sec = 0;
1416 tWaitTime.tv_usec = WAIT_UTIME;
1417 if (FdSelectOneSocket(s32RdFd, &tWaitTime, FD_SET_READ) > 0)
1418 {备注:打印出adas.c文件,1414行的前5行后5行的源码
此时再次执行
n
命令,单步调试,则会打印行数和源码,方便查看变量信息,如下所示:(gdb) n
1416 tWaitTime.tv_usec = WAIT_UTIME;-
输入p 变量名,则可打印变量的值,如下所示:
(gdb) p tWaitTime
$1 = {tv_sec = 0, tv_usec = 0}备注:对于指针变量,则可在变量前加*来打印指针变量指向的内存的值,若指针变量为void类型,则需要加上指针类型的强制转换来打印指针变量指向的内存的值,否则打印出来的也是无效的内容;
-
输入finish可以跳出函数,但不结束程序,如下所示:
(gdb) finish
Run till exit from #0 CnnDetect_run (pHandle=0xbf4280, detInput=0x7fde48c850) at ../../adas/.//cnn_detect.c:468当前执行的行数为cnn_detect.c文件的468行,执行完函数后跳出去。
-
输入info breakpoints命令可以查看当前gdb环境打的断点,如下所示:
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000004363d8 in DetectFunc2 at ../../adas/.//adas.c:1396
breakpoint already hit 1 time-
输入c或continue命令,可以执行到下一个断点;
-
如果函数的循环次数很多,可以输入until命令,执行完当前的循环;
-
输入bt或backtrace命令,打印当前的函数调用栈的所有信息,如下所示:
(gdb) bt
#0 0x0000000000436510 in DetectFunc2 (ptArg=0xe13a60) at ../../adas/.//adas.c:1444
#1 0x0000007ff7bb5648 in ?? () from /lib/libpthread.so.0
#2 0x0000007ff77e3f0c in ?? () from /lib/libc.so.6输入frame n(n指的是栈的编号)命令查看栈详细信息,如
(gdb) frame 0
#0 0x0000000000436510 in DetectFunc2 (ptArg=0xe13a60) at ../../adas/.//adas.c:1444
1444 ptPicture->detOut = CnnDetect_run(g_ptAdas->ptCnnDetectCtx, &detInput);
(gdb) frame 1
#1 0x0000007ff7bb5648 in ?? () from /lib/libpthread.so.0备注:输入1号和2号栈对应的是系统的库文件,如多线程相关的库和基础的c库等,无法得到有效的信息。bt命令是在程序运行报错的情况下很有效果,可以定位到最后崩溃时刻的函数。
-
输入info locals命令,可以查看当前的函数的变量,如下所示:
(gdb) info locals
cx = <optimized out>
cy = <optimized out>
detInput = {imageWidth = 1920, imageHeight = 1080, dataFormat = 0, src_phy_mem = {virt = 0x7fd6107000, phys = 1696645120, size = 3110400}}
tWaitTime = {tv_sec = 0, tv_usec = 9994}
s32RdFd = 10
i = <optimized out>
ptThread = 0xe13a60
ptPicture = 0x7fe28b8018
s32FrameNum = 0
dtime0 = 10608187.375065999
dtime1 = 0
run_time = 10983210.168804999
total_time = 0
time_stamp = 0
time_start = 0
time_end = 0
threadLog = {cameraId = 0, printIntervalTime = 1000, startTime = 0, frameNum = 0, totalTime = 0, runTime = 11746464.414445, checkRun = 1}
__func__ = "DetectFunc2" -
输入q命令,退出gdb调试模式
退出后,此前设定的gdb调试的参数,如断点信息都不会存在,慎重退出。
3. 总结
当前文档总结的是gdb的基本操作,其他的更复杂的一些操作命令这里并未提及,若有兴趣自行查阅资料。需要强调的是gdb调试用熟练了,比在代码中加打印信息的调试方式要快一万倍。
4. 参考资料
--by Nero 20221230
-