程序开发,很大一部分的时间是用于调试,调试也是一门值得了解的学科,我这总结一般调试手段:
- 加日志
- 加断言
- 代码调试
- 调试core文件
加日志
这个就无需赘言了,你怀疑哪个地方,或者在出现问题的时候,需要知道哪个地方有问题,那么日志肯定是少不了的,而且这个也是最基本的,适用于所有语言。
但是经验来看,加日志属于事后,或者是预判,不是很灵活,因为总有你没想到的地方,所以我们发现,做程序开发,在排查线上问题的时候,总会抱怨其他程序员没有加日志。关键还是在于计划赶不上变化。
日志是必须的,但是只靠日志,在某些场景还是没法解决问题的:
- 你不知道哪个地方出了问题,你只能在怀疑的地方又加日志
- 每次加日志,你肯定得改代码,如果是编译型语言你还得重新打包,慢
- 加了日志之后,发现打印出的信息还是太少,返回去又搞一遍,返工
加断言
这个在 c 语言开发里经常看见,通常是你认为绝对的一段代码逻辑,直接断言,或者在排查问题的时候,怀疑某一个地方,那么加一个断言,这个本质逻辑就是:如果代码走到我怀疑的地方,那么就证明了我某些推论,断言之后,还可以留下内存 core 文件,以便调试。
调试core文件
这个是c/c++程序比较常见的一种调试方式,c 程序一般线上都会开 core 开关,到跑到异常的时候,整个程序内存镜像 dump 出来,相当于保存了现场。这个有时候也是排查 c 程序运行唯一手段。
golang程序经常用这种方式,排查一些问题。golang 有两个调试工具:
- gdb
- dlv
gdb 能调试 golang 程序的,但是不好调试,有几个原因:
- 关键在于golang的协程是用户态的执行单元,gdb 调试的是系统的执行题,进程,线程,所以根本很难调试到指定的协程。
- 协程数太多,这个也是最尴尬的地方,生产环境的 golang 程序动不动就成千上万个 goroutine,你列都列不完。
- gdb 对 golang 程序的数据结构理解不完善。打印变量经常打印不出来。
但是 gdb 有自己的独一无二的特点:
- 可以 gcore 文件,这个 dlv 就做不到,所以我们经常用 gdb 来 core 文件,用 dlv 来分析 core
- x 命令分析内存更透测
代码调试
代码调试无论那种语言,都是很重要的一个环节。要能够离线的对二进制或者在线的对进程进行调试,设置断点喽,打印一些程序变量喽,这些都是无侵入式的,不需要改代码即可。
- 比如 c/c++ 可以使用 gdb 进行单步调试,或者 attach 到已经在运行的进程里进行调试。
- python 可以使用 pdb 进行单步调试
- golang 可以 gdb 和 dlv 进行离线和在线调试
golang 调试
golang 调试有两个工具:
- gdb
- dlv
都支持的三种调试场景:
- 二进制文件
- 在线进程
- core 文件
gdb
# 调试二进制文件
gdb --args {二进制文件} {参数}
# 调试在线进程
gdb {二进制文件} -p {pid}
# 调试 core 文件
gdb {二进制文件} {core 文件}
dlv
# 调试二进制文件
dlv exec {二进制文件} -- {参数}
# 调试在线进程
dlv attach {二进制文件} {pid}
# 调试 core 文件
dlv core {二进制文件} {core 文件}
python 调试
python的调试有两个手段:
- 侵入式
- 非侵入式
侵入式:
#!/usr/bin/env python
def test_print():
# 断点位置
import pdb; pdb.set_trace()
print("test print")
if __name__ == "__main__":
test_print()

非侵入式:
python -m pdb test.py

后面抽个时间分享下 gdb,dlv怎么调试golang程序的,敬请期待。
坚持思考,方向比努力更重要。关注公众号:奇伢云存储,获取更多干货。
