作为一名嵌入式开发工程师,我深知调试工具的重要性。在Linux环境下开发C/C++程序时,GDB(GNU Debugger)无疑是最强大的调试利器。今天我将分享GDB的基础用法和实战技巧,这些经验都来自我在STM32和51单片机项目中的实际调试积累。
GDB作为GNU项目的一部分,具有以下优势:
在嵌入式开发中,GDB配合OpenOCD或J-Link等工具,可以直接调试STM32等单片机程序,这是它相比其他调试器的独特优势。
在Ubuntu系统上安装GDB非常简单:
bash复制sudo apt-get update
sudo apt-get install gdb
对于ARM架构的交叉调试,还需要安装交叉编译工具链:
bash复制sudo apt-get install gdb-multiarch
要让程序支持GDB调试,编译时必须加上-g选项:
bash复制gcc -g test.c -o test
这里的-g选项会生成调试信息,包括:
在实际项目中,我们通常会结合优化选项使用:
bash复制gcc -g -O2 test.c -o test
注意:高优化级别可能会影响调试体验,某些变量可能被优化掉。开发阶段建议使用-O0或-O1。
启动GDB调试:
bash复制gdb ./test
或者附加到正在运行的进程:
bash复制gdb -p <pid>
退出GDB:
bash复制quit
# 或简写为q
查看源码是调试的基础操作,GDB提供了多种方式:
bash复制list
# 或简写为l
bash复制list 15
bash复制list main
bash复制set listsize 20
bash复制search printf
提示:在大型项目中,可以使用dir命令添加源码搜索路径:
bash复制dir /path/to/source
断点是调试的核心功能,GDB提供了灵活的断点设置方式:
bash复制break main
# 或简写为b main
bash复制break 20
bash复制break 30 if i==5
bash复制tbreak func
查看所有断点:
bash复制info breakpoints
# 或简写为i b
删除断点:
bash复制delete 1
# 或简写为d 1
禁用/启用断点:
bash复制disable 2
enable 2
经验分享:在嵌入式开发中,硬件断点资源有限,合理使用软件断点很重要。对于ROM中的代码,只能使用硬件断点。
启动程序:
bash复制run
# 或简写为r
带参数启动:
bash复制run arg1 arg2
继续执行:
bash复制continue
# 或简写为c
单步执行(进入函数):
bash复制step
# 或简写为s
单步执行(不进入函数):
bash复制next
# 或简写为n
执行到当前函数返回:
bash复制finish
执行到指定行:
bash复制until 50
# 或简写为u 50
设置观察点(变量改变时暂停):
bash复制watch var
设置捕获点(事件发生时暂停):
bash复制catch syscall open
反向调试(需要特殊版本GDB):
bash复制reverse-step
注意:在嵌入式环境中,某些高级功能可能受限于调试硬件的能力。
查看变量值:
bash复制print var
# 或简写为p var
查看表达式:
bash复制print i*2+1
修改变量值:
bash复制set var i=10
查看数组:
bash复制print array[0]@10
查看结构体:
bash复制print *struct_ptr
查看内存:
bash复制x/10xw 0x8000000
格式说明:
其他单位:
写入内存:
bash复制set {int}0x8000000 = 42
嵌入式开发技巧:在调试STM32时,经常需要直接查看外设寄存器值,内存操作命令非常有用。
查看调用栈:
bash复制backtrace
# 或简写为bt
带局部变量查看:
bash复制backtrace full
查看特定帧:
bash复制frame 2
# 或简写为f 2
查看帧信息:
bash复制info frame
查看寄存器:
bash复制info registers
查看特定寄存器:
bash复制print $pc
在ARM架构调试中,查看LR、PC等寄存器对分析异常非常有帮助。
查看线程:
bash复制info threads
切换线程:
bash复制thread 2
所有线程执行命令:
bash复制thread apply all bt
设置线程特定断点:
bash复制break file.c:30 thread 2
注意:在嵌入式RTOS环境中,线程调试需要调试器支持相应的RTOS插件。
定义快捷命令:
bash复制define printall
print var1
print var2
end
执行命令脚本:
bash复制source script.gdb
Python脚本扩展:
python复制python
import gdb
class MyCommand(gdb.Command):
def __init__(self):
super(MyCommand, self).__init__("mycmd", gdb.COMMAND_USER)
def invoke(self, arg, from_tty):
print("Hello from Python!")
MyCommand()
end
连接远程目标:
bash复制target remote :1234
文件传输:
bash复制remote put localfile remotefile
remote get remotefile localfile
在嵌入式开发中,经常通过OpenOCD或J-Link进行远程调试。
bash复制openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg
bash复制gdb-multiarch firmware.elf
target remote :3333
load
monitor reset halt
bash复制monitor reset # 复位芯片
monitor flash write # 写入Flash
monitor mww addr val # 写内存
bash复制set history save on
set print pretty on
define hook-quit
save breakpoints ~/.gdb-breakpoints
end
bash复制set logging on
set logging file debug.log
bash复制layout src
layout asm
layout regs
bash复制record full
reverse-step
bash复制while 1
step
print var
if var == 0
break
end
end
在实际项目中,我总结出以下经验:
调试是一门艺术,需要耐心和经验积累。GDB虽然学习曲线较陡,但一旦掌握,将成为你解决复杂问题的强大武器。希望这些经验能帮助你在嵌入式开发中更高效地使用GDB。