在GNU编译器中,GDB无疑起着无足轻重的位置,常言有道:写工程3分靠写,7分靠调。GDB作为GNU编译器下的标准调试工具,在我们的项目编写即调试过程中会被平凡的使用到。而由于我们在IDE环境下娇生惯养出来的习惯,我们可能不太容易上手一个用命令行为主要操作和显示形式的调试器。所以在这篇中我们会为大家详细的讲解GDB工具的使用,来让大家快速的入门。
之前我们在安装和介绍调试器驱动的时候,曾举过例子,来使用指令开启一个操作接口,用以为gdb程序提供连接。这就是嵌入式GDB的工作方式,详细来说就是,使用调试器的驱动软件指令将调试器连接目标板,并将驱动软件的GDB指令接口开启在一个设定的计算机端口(此处的端口为计算机本地的网络端口)上,一般OpenOCD的默认端口是3333。在调试器驱动启动端口后,我们开启GDB软件连接本地的调试器端口,就可以开始调试了。了解了它的工作,我们下面就用一个具体的例子来讲解。
首先我们将调试器与目标板以及计算机之间的线连接好(此处默认调试器为st-link、对于其他调试器只要稍微更改OpenOCD的启动配置文件就可),并将st-link的接口设置到虚拟机上。随后我们通过OpenOCD指令连接调试器:
$ openocd -f /usr/local/share/openocd/scripts/interface/stlink-v2.cfg -f /usr/local/share/openocd/scripts/target/stm32f4x_stlink.cfg
在执行完此条指令后该终端就会一直执行OpenOCD的程序了,不要关闭它,我们再打开一个终端界面,进入我们的工程目录,比如我这里进入的就是我的libopencm3样例工程下的blink子工程目录。
$ cd '/home/yangliu/workspace/libopencm3-my-example/blink'
然后我们使用指令输入调试文件并打开GDB程序。
$ arm-none-eabi-gdb blink.elf
然后我们在GDB的指令界面中,输入连接指令,连接本地的3333端口。
(gdb)target remote localhost:3333
此时我们就将GDB与调试器接上了,运行OpenOCD的终端也会显示收到连接信息。
不过我们还没有完成所有的初始操作,此时我们需要输入指令来复位、停止MCU并加载elf文件:
(gdb)monitor reset
(gdb)monitor halt
(gdb)load
这里 monitor意为这向连接的外部软件发送指令,此处即向OpenOCD发送指令,因为reset halt等指令不是GDB的内部指令,而是OpenOCD的指令。随后的load指令会将启动时输入GDB的elf文件加载入MCU,即下载到MCU的flash。
到这里我们的所有GDB连接的初始操作就完成了,当然我们不仅可以使用OpenOCD启动调试端口,st-link的驱动也也可以,使用指令:st-util就可以启动它,不过st-link驱动默认的端口是4242。
在我们将elf调试文件加载到MCU中后,我们就可以进行调试操作了。首先来介绍一下GDB调试的基本指令:
c ,continue的缩写,在嵌入式GDB中我们不能使用RUN指令来运行代码,相应的我们需要continue指令来运行。
b ,break缩写,用于在程序中打断点,使用方式有很多种
s,step缩写,会进入子函数的单步运行
n,next缩写,即会跳过子函数的单步运行
u,until缩写使用时后方加行号如: u 16 即运行到第16行
finish,完成并跳出当前的子函数
p,print缩写,使用时后面加变量名称如:p tmp ,用来显示变量数值
display ,使用时后面加变量名称如:display tmp ,用于跟踪变量数值,在每次执行停下时会自动显示变量数值。
bt,查看堆栈
q/Ctrl+d,使用q即quit指令或快捷键Ctrl+d来正常退出GDB
在GDB中其实还有很多的指令及指令特性我没有介绍到,但是我们常用的也就是这几条,更多的大家可以自己去百度或查看GDB的PDF说明文档来了解使用。
我们上面介绍了GDB的指令行操作模式,虽然在指令行下增加了强大的条件断点的功能,但是也显然的对代码调试我们不能实时的看到他的执行多少有些不便,那么下面我就来介绍开启GDB的GUI调试的隐藏模式吧~
在GDB的指令窗口我们输入
(gdb)-
对就是一个 减号,然后回车我们来见证奇迹的时刻~
刷新世界观有没有~,然后我们就愉快的调试吧~。
上面我们介绍了GDB通过计算机本地端口链接调试器驱动软件的方式,无疑开启两个终端并输入一大堆的连接和加载指令是很不方便的,有没有什么会计的方式的,那答案必然是肯定的。
其实除了通过端口链接,GDB与调试器还可以通过系统标准管道链接,下面是一段我从上篇中提到的,我的libopencm3工程中rules.mk文件中截取的片段:
%.debug: %.elf
@printf " GDB DEBUG $<\n"
$(Q)$(GDB) -iex 'target extended | $(OOCD) $(OOCDFLAGS) -c "gdb_port pipe"' \
-iex 'monitor reset halt' -ex 'load' -ex 'break main' -ex 'c' $(*).elf
在这里我们通过 -c gdb_port pipe来快速启动OpenOCD并与GDB连接,同时在main函数打断点并执行到main处。具体这里的参数输入的方式和意义大家可以查询GDB的说明书,这里就不做太多的解释了,大家想要改写为自己的应用时只需要将其中和工程相关的参数 elf 文件,的名称修改就可以了,(这里-iex 参数前缀是GDB的指令,意为在执行GDB本体调试程序前先执行-iex 后引号中的外部指令)。
在这篇中我们介绍了GDB的使用方式,同时也解答了上篇中留下的问题,样例makefile中的 debug 功能是怎样实现的。
不过在调试中需要注意的是,如果我们不在程序中添加断点或在continue指令后的代码不存在断点,程序就会一直跑下去,而我们的GDB指令窗口也不会响应再输入的指令,此时我们就需要按动开发板上的reset复位按键才可以使得GDB再次响应操作。
如果我们没有使用指令或快捷键正常退出GDB窗口,可能会造成终端锁定socket端口,从而在这个终端被关闭以前,我们将无法再次链接调试器,此时我们只要关闭输错的终端,再重新打开一个就可以了。