在linux下的C/C++编程中,调试是经常会用到的手段,在没有可视化调试的情况下,往往就需要用到最原始高效的工具——GDB。如果需要对可执行程序作调试,那么在编译程序时就需要加上部分编译选项(-g)才能使生成的可执行程序具备调试信息。
# 编译可执行文件,最简单例子
g++ test.cc # 没有调试信息
g++ -g test.cc #有调试信息
如果没有-g,当你想使用gdb命令调试程序时,会提示“(No debugging symbols found in a.out)”,你也就无法正常地使用接下来的gdb相关命令操作了。
同样地,如果对于已编译完的可执行程序,可以通过strip命令移除调试信息,移除后的可执行文件可以明显看出来占用空间变少。
strip a.out
下面进入正文。
# 第一种直接+可执行文件启动
gdb ... # file_name为可执行文件,param表示命令行参数(如果有)
# 第二种,可以先启动gdb再跟可执行文件
gdb
file #指定可执行文件
set args ... #指定命令行参数(如果有)
命令 | 缩写 | 说明 |
run | r | 启动程序 |
break | b | 添加断点,有两种写法: 1)break file.cpp:30,表示在file.cpp的第30行打断点。 2)break xxclass::xxfunc(),表示在xxclass::xxfunc()函数打断点。 |
info | 查看断点/进程信息,每个断点/进程都有个序号表示,写法: 1)info breakpoints 2)info threads |
|
delete | del | 删除某个断点,写法: 1)delete 3,表示删除第3个断点 |
disable/enable | 除了直接删除断点,也可以临时让某个断点失效/生效,用法跟delete类似。 | |
continue | c | 让暂停的程序继续运行 |
step | s | 单步调试,会进入函数内部 |
next | n | 运行到下一行,不会进入函数内部 |
backtrace | bt | 查看函数调用栈,每级栈都有个序号表示 |
frame | 切换到某一级调用栈,写法: 1)frame 3,表示切到编号为3的栈顶。 |
|
list | l | 显示文件代码,方便知道在哪里打断点等。 |
p | 打印一些变量或表达式等信息,万物皆可打印。。例如: 1)print a,打印变量a的值。 2)print b.size(),打印数组b的大小。 |
|
show args | 查看之前设置的命令行参数 | |
set args | 重新设置命令行参数 | |
display | 跟print类似,但是只要设置一次,之后每次在断点停下时都会自动打印出来。 | |
watch | 用于监控变量或地址的值是否发生变化,如果发生变化,程序会自动中断,这样你就知道是在哪里改变了这个变量的值了。 |
综上,日常调试中用到上面这些基本够用了。
gdb还可以直接调试正在运行中的进程,需要注意的是,通过命令进入后进程会挂起,可以通过使用上一节命令进行操作。
# 两种方式均可以,表示指定进程的pid,可以通过pd命令查到。
gdb -p
gdb attach
gdb还可以对coredump文件进行Bug定位。程序运行崩溃时一般都可以自动生成coredump文件,当大型程序崩溃时,可以通过gdb快速定位到问题代码。这里可以转到另一篇博客。
gdb还可以调试多线程程序,上小节讲到,可以通过info threads看到线程信息,实际上还可以进行进一步操作,更多操作可以移步这里。
info threads #可以看到当前有哪些线程
thread #切换到对应的线程,其中为一个数字,是某个线程的编号