还不会gdb?看这一篇就够了
目录
gdb是什么?它有啥威力
gdb常用命令
gdb实战
基本使用
解决coredump!
总结
大家好,我是小李。
今天这一篇博客来跟大家介绍一个非常有用的工具 —— gdb,不管是学习还是工作中,用好gdb,能让你的程序更加丝滑!
gdb是什么?它有啥威力
首先,简单介绍下gdb。gdb的全称是GNU debugger,看名字就知道 gdb 是用来对程序进行 debug 的。
具体来说,gdb有啥用呢?想想这个场景,当我们满心欢喜的写了若干 h 的代码,并且review了好久之后,终于要开始运行了,结果发现输出和预期咋不一样捏?怎么还coredump了捏?当然,有小伙伴会说了,我在可能发生错误的地方用printf把一些可能出错的变量都打出来不就好了吗?这种方式的确也是一种debug 的方式,但是这种方式非常麻烦,对于小程序尚且可以很好地debug,但是对于大型程序来说,这种方式就显得非常“玩具”了。
printf对比于gdb来说:
首先,你可能要加上数不清的 printf 语句,重新编译运行,调试完又得把这些printf语句全部删除,很麻烦;
其次,printf不够灵活,如果我想当某个变量满足一定条件的时候,就让它正常执行,否则单步观察它是怎么影响后续程序的。此时,printf无法达到这样的效果,但是gdb可以。
gdb常用命令
小李相信任何一种工具都是设计者耗费大量精力设计编码出来的,我们作为使用者无法做到面面俱到,完全深入。但是我们能做到的是掌握它的一些常见用法,熟练运用到学习和工作中提升生产效率。
gdb博大精深,但是常用到的命令其实也不多。接下来,小李给大家简单介绍下一些常用命令:
首先,我们在编译的时候要加上 -g 参数,这样编译器才会在生成的 ELF 格式文件中加上一些必要的信息以供gdb进行识别和响应,这是使用gdb的前提;
gcc -o filename -g filename.c 然后,启用 gdb 进行调试:
gdb --args filename arglist # 进入调试,并传入参数 arglist start / run —— 在gdb里面让程序跑起来:使用 run 或者 start 命令可以在gdb里面让程序运行起来,其中 start 默认在main函数开头有个断点,让程序在 main 函数开头停下来;而 run 则会在遇到第一个手动设置的断点时才停下来;
break —— 强大的断点命令:gdb中最核心的部分之一就是打断点,我们可以使用 break 命令在任意想要让程序停下来的地方打断点,其中断点可以设置为函数名,也可以是指定的代码行:
b funcName # break可以简写为b,其中funcName是想要断点的函数名,程序执行到funcName的时候会自动停下
b lineNum # lineNum是代码行号
info b # 查看当前程序的所有断点信息
delete bID # 通过 info b 可以查看到断点信息bID,使用delete删除某个断点
disable bID # 关闭断点bID
enable bID # 开启断点bID tbreak # 设置临时断点
next / step —— 让程序往下走“一步”:当程序执行到我们设置断点的地方然后停下来了,这个时候我们想要看看程序接下来是怎么变化的,可以使用 next / step命令。其中step和next都表示让程序单步执行一行代码,但是它们的主要区别在于如果下一行是函数调用的话,step会跳进函数里面继续执行,而next命令则直接执行完这个函数;
n # next 可以简写为n
s # step 可以简写为s
finish —— 跳出这个函数:当我们使用step进入某个函数后,通过观察发现这个函数没啥问题,现在想要从这个函数跳出去,而这个函数里面有个循环了若干次的for/while语句,如果使用next命令单步执行到函数末尾的话就有点太慢了。此时可以使用 finish 命令直接执行完这个函数,返回到被调用的地方。
watch —— 监视某个变量:我们如果只是想观察程序中某个变量是怎么变化的,那么可以使用 watch 命令,当被观察的值发生变化的时候,程序会自动打印出观察量变化前后的值。
watch var # 观察变量 var
watch (var > 20) # 当var的值大于20的时候,watch才会进行观察
backtrace + frame —— 查看调用栈:当我们执行到某个断点时发现了异常值,那么这个异常值是怎么一步一步变成这样的呢?此时可以使用backtrace命令查看函数调用栈,函数调用关系被压在数据结构栈中,当前函数在栈中的下标是 0,调用链往上的函数编号递增;然后搭配 frame 命令可以进入函数调用栈中其他任意函数
bt # backtrace 可以缩写为 bt
frame btID # 使用frame 加上函数在栈中的编号可以跳到调用栈中的某个函数进行观察
info —— 强大的查看命令:使用info 命令可以查看局部变量、断点信息、线程信息、寄存器信息等...
info b
info locals
info threads
info registers
quit —— 退出gdb调试
gdb实战
基本使用
掌握了上述基本命令之后,我们可以使用它们的组合进行快乐地 debug 了,一般的流程和组合如下:
-g编译
gcc -o finename -g filename.c --args 带参数gdb:我们可以给执行文件传入参数进行debug
gdb --args ./filename arg_lists break 设置断点并运行:我们在想要进行观察的函数或者代码行打断点,然后运行程序
b lineNum run print / bt 查看关键信息:
print var # 打印想要观察的变量 var
bt # 查看函数调用栈,并获取函数在栈中的编号 btId
frame btID # 进入调用栈中的函数,继续使用 print 观察
解决coredump!
写代码最怕遇到的是什么?segmentation fault!
一般产生segmentation fault是因为访问了不该被访问的内存地址,此时,linux会在/proc/sys/kernel/core_pattern指定目录下生成一个 coredump 文件,其中包含了产生异常时的进程空间信息。
有同学会说了 “小李啊,我没找到coredump文件啊”,使用 ulimit -c unlimited 设置完再试试捏?然后搭配上面提到的基本命令,就可以快乐地进行错误定位啦!
ulimit -a # 查看系统给core文件配置的大小
ulimit -c unlimited # 给 core 文件配置无上限,便于生成 coredump 文件
gdb filename coredump # 对core文件进行gdb
bt # 查看产生异常时的调用栈关系
frame btID # 进入调用栈中的某个函数
info locals # 查看函数的局部变量信息
up n/ down n # 当前函数栈往前 or 往后几步
总结
以上就是 gdb 的基本使用方法了,掌握了上述基本命令和基本场景使用就能应对学习和工作中的大部分问题了。工具的使用关键在于熟能生巧,希望同学们能多多使用,但也祝大家都能写出没有bug的代码!
小李也是在不断地学习与总结中,非常欢迎同学们和小李进行留言交流,大家一起学习进步。
本文将根据工作场景进行持续更新,欢迎同学们关注我呀!