废话以后有时间再加。

首先编译时开启调试选项:

g++ main.cpp -g -O0

-O0指定编译器的优化级别为0,即不优化。

然后编译出来的可执行文件,默认名字是a.out. 直接了当,用gdb打开之,

gdb a.out

要调试必然要打断点,两种方式:指定行数;指定函数。

(gdb) break 10  // create breakpoint at line 10
(gdb) break main  // create breakpoint at the entrance of main

使用list在gdb中查看代码块以确定你要在哪一行设置断点(这就很麻烦,所以一般直接在main函数打个断点,然后单步去run)。

设好断点以后,使用run启动程序,程序会在断点处停顿,等待你的输入指令。 gdb-demo

使用next进行单步执行,使用step步入。所谓步入就是如果有函数调用,程序会跟踪到所调用的函数内部的代码,而单步的话,则会直接完成函数调用,获得返回值(如果有的话)。

使用info locals查看栈变量的值,使用info args查看函数传入参数的值。使用print <variable>查看指定变量的值。

备注

  • GDB里面的命令都有缩写(break=b, next=n, step=s, …)
  • 什么命令也不敲直接回车默认执行上一条命令
  • 使用help <command>来获取相关命令的使用帮助

1.1 绑定进程

sudo sysctl -w kernel.yama.ptrace_scope=0  # 开启attach debug的必要条件
 
gdb <bin>         # GDB运行一个二进制,用于调试使用
gdb attach <pid>  # 绑定正在运行的进程,原则上禁止直接attach线上进程
gdb <bin> <pid>
gdb -p <pid>
 
# eg
gdb -p `pidof firefox`

202502181657gdb-attach

1.2 查看代码

  • 查看程序代码
> dir <source_directory>  # 添加cpp原文件目录
> l                       # 默认显示暂停处代码,一直执行l会向下滚动显示
> l  <function>           # 显示函数代码
> l  <file:function>      # 显示文件中某函数代码
> l  <file:line>          # 显示文件中指定行数代码
  • 查看汇编指令
> disass                  # 显示当前汇编指令

1.3 断点

  • 断点设置
> b <file:line>                   # 设置文件中某行断点
> b <file:function>               # 设置文件中某函数断点
> b <namespace::class::function>  # 设置某类的成员函数断点
> b <location> <thread-id>        # 设置某个线程在某处的断点 
> b <location> if <condition>     # 设置某处的条件断点
  • 断点查看
> info b                          # 查询所有断点
  • 断点删除/启用/禁用
> d <break-id>                    # 删除某断点
> disable  <break-id>             # 禁用某断点
> enable  <break-id>              # 启用某断点
  • 断点设置自动执行命令
> command <break-id>
> p <var>                         # 运行到断点处时自动打印变量<var>
> end

1.4 打印变量

  • print打印
> p <expr>              # 打印变量<var>
> p <var>=<value>       # GDB过程中更改某个变量的值
  • display打印
> display <expr>        # 打印某个变量或表达式,expr 表示要查看的目标变量或表达式
> display/fmt <expr>    # 参数 fmt用于指定输出变量或表达式的格式
/fmt功 能
/x以十六进制的形式打印出整数。
/d以有符号、十进制的形式打印出整数。
/u以无符号、十进制的形式打印出整数。
/o以八进制的形式打印出整数。
/t以二进制的形式打印出整数。
/f以浮点数的形式打印变量或表达式的值。
/c以字符形式打印变量或表达式的值。

display和print区别在于,display会一直打印某个值

  • 打印protobuf message
> p <var>.DebugString()   # 使用DebugString()将proto对象内部结构打印出来
  • 打印内存地址
# n:为正整数,表示需要打印的内存单元个数
# 
# f:打印格式, 如下
# - x: 十六进制
# - d: 十进制
# - u: 十六进制
# - o: 八进制
# - t: 二进制
# - a: 十六进制
# - c: 字符格式
# - f: 浮点数
# 
# u: 内存单元大小,如下:
# - b: 单字节
# - h: 双字节
# - w: 四字节
# - g: 八字节
> x/<n/f/u> <addr>  	# addr: 要打印的内存地址
  • dump内存内容至文件
# 将start_addr至end_addr的内存内容以二进制形式dump到file文件,file文件名自定义即可
> dump binary memory <file> <start_addr> <end_addr>
  • 打印长字符串
    gdb会限制打印字符串的最大长度。使用下列命令可修改限制。
> show print elements     # 显示字符串最大打印长度
> set print elements 0    # 取消字符串最大打印长度
  • 打印CPU寄存器的值
> i r                     # 打印所有寄存器的值
> i r es                  # 打印寄存器es的值

1.5 线程调试

# 可事先dump某个进程下所有线程的thread id和backtrace,方便gdb调试
pstack <pid>
> info threads                # 查看当前所有线程信息
> bt                          # 查看当前线程的backtrace
> bt full                     # 查看当前线程更详细的backtrace(每个栈帧上的参数)
> thread <thread-id>          # 切换到某一个线程
> set scheduler-locking on    # 多线程环境下,只有当前被调试线程会执行
> set scheduler-locking off   # 多线程环境下,除当前被调试线程之外的其他线程也在同步执行
> set scheduler-locking step
 
# 多线程环境下,对当前被调试线程用step调试时,其他线程不会执行;使用next调试时,其他线程也许会执行

1.6 运行控制

> r arg1 arg2 ...             # 重新开始运行二进制,如果需要传入参数内需要arg1 arg2...
> stop                        # 暂停运行
> c                           # 继续执行(continue)
> n                           # 单步执行(next),遇到函数则跳过
> s                           # 单步执行(step),遇到函数则跳入函数体
> finish                      # 运行直到跳出当前函数
> until line                  # 运行直到到达指定行
> call command                # 运行C++命令
> shell                       # 进入shell模式,回到linux终端
> exit                        # 退出shell模式,回到gdb命令行
> set $var=XXX                # 设置gdb变量
> set var=XXX                 # 设置程序中变量

1.7 结束调试

# gdb二进制时,使用q/quit退出
> q
> quit
# gdb attach进程时,使用detach退出
> detach

1.8 调试core

一般情况下,当设置了ulimit -c unlimited之后,当程序遇到异常时,会自动转储core文件(即crash时会dump core文件),方便开发者查看分析现场。

但是,如果想对一个正常运行的进程进行转储, 可使用gcore命令:

gcore <pid>       # 将进程<pid>转储到core文件中

调试core文件

# 调试core文件,两个gdb命令都可
gdb bin core
gdb -c core bin
> bt full         # 查看异常的backtrace

1.9 开启日志

gdb默认不开启日志。可使用如下命令开启

> set logging on   # 设置gdb日志开启,gdb会在当前目录下生成gdb.txt记录gdb命令行所有输出结果,方便回溯历史。

参考:

  1. https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/gdb.html
  2. 100个gdb小技巧