Skip to content
Published at:

GDB上手

GDB

官网:https://www.gnu.org/software/gdb/ Debugging with GDB:https://sourceware.org/gdb/current/onlinedocs/gdb/

简介

GDB, the GNU Project debugger, allows you to see what is going on `inside' another program while it executes -- or what another program was doing at the moment it crashed.

GDB的功能:

  • Start your program, specifying anything that might affect its behavior.
  • Make your program stop on specified conditions.
  • Examine what has happened, when your program has stopped.
  • Change things in your program, so you can experiment with correcting the effects of one bug and go on to learn about another.

启动gdb

启动gdb之前,需要编译出debug版本的程序,编译时加-g选项

bash
$ gcc -g main.c

启动方法:

  • 调试直接启动程序
  • 调试一个正在运行的程序,attach
  • 调试core文件

调试直接启动程序:

bash
# 语法: gdb <program>
$ gdb a.out

调试一个正在运行的程序,attach:

bash
# 语法: gdb <program> <PID>
$ gdb a.out 11127

调试core文件:

可以通过core文件调试程序来找到错误原因.比如跟踪常见的断错误

bash
# 语法: gdb <program> <core dump file>
$ gdb program core

通用

  • help
  • help <keyword>
  • tab键补全
  • Enter回车:执行上一条命令
  • 大多命令都有简写

断点

  • 设置断点:break/b
  • 查看断点:info breakpoints
  • 删除断点:delete
  • 关闭或开启断点:disable/enable
    • disable <断点序列号>:关闭指定序列号的断点
    • enable <断点序列号>:开启指定序列号的断点
bash
# 设置断点:break语法
# * break <行号>
# * break <文件名>:<行号>
# * break <函数名>
# * break <文件名>:<函数名>
(gdb) break 30          # 设置30行为断点
(gdb) break main.c:30   # 设置main.c的30行断点
(gdb) break main        # 设置main函数为断点
(gdb) break main.c:main # 设置main.c文件的main函数为断点

# 查看断点
(gdb) info breakpoints

# 删除断点:delete语法
# * delete <断点序列号>
# * delete breakpoints
(gdb) delete 3              # 删除序列号为3的断点
(gdb) delete breakpoints    # 删除所有断点

# 其它操作:disable/enable
#   * disable <断点序列号>:关闭指定序列号的断点
#   * enable <断点序列号>:开启指定序列号的断点
(gdb) disable 3
(gdb) enable 3

运行

  • run/r:运行程序,当遇到断点后,程序会在断点处停止运行,等待用户输入下一步的命令
  • next/n:单步跟踪程序,不会跳进去函数
  • nexti:单步跟踪程序(汇编)
  • continue/c:继续执行,到下一个断点处(或运行结束)
  • step/s:单步调试如果有函数调用,则进入函数
  • finish:运行程序,直到当前函数完成返回
  • call:调用程序中可见的函数
  • quit/q:退出

查看源代码

两种方式

  • list/l
    • list <行号>:默认显示10行代码信息
    • list <函数名>:以函数为中心,显示10行代码信息
    • list <文件名>:<函数名>:
    • show listsize:查看list命令显示的大小
    • set listsize:设置list命令显示的大小
  • layout(src源代码/asm汇编/regs寄存器/分屏)
    • layout src:源代码
    • layout asm:汇编
    • layout regs:寄存器
    • layout prev:上一种布局
    • layout next:下一种布局
    • refresh/Ctrl+L:刷新(屏幕出现花屏时使用)

信息查看显示

  • print/p:打印一个变量,取地址,表达式,函数
  • backtrace full:打印栈里的所有局部变量
  • display:类似print,每次单步调试都会输出
  • undisplay:取消display的设置
  • watch:监控变量,当变量变化时输出(删除watch使用delete命令)
  • whatis:查询变量或函数信息
  • info:查看相关信息
    • info breakpoints:查看断点设置
    • info display:查看display设置
    • info watchpoints:查看watch设置
bash
# print语法: print <expression>
# * print <变量>
# * print <变量取地址>
# * print <表达式>
# * print <函数调用>
(gdb) print a                       # 变量值
(gdb) print &a                      # 取地址
(gdb) print a++                     # 表达式
(gdb) print random_range(10, 20)    # 函数调用

# print 数组
# * print <数据变量名>
# * print <数据变量名[index]@lenght>
(gdb) print array       # 打印数组
(gdb) print array[5]@10 # 从第五个元素开始,打印10个元素

调试core文件

core文件:核心文件通常在系统收到特定的信号时由操作系统生成. 信号可以由程序执行过程中的异常触发,也可以由外部程序发送. 动作的结果一般是生成一个某个进程的内存转储的文件,文件包含了此进程当前的运行堆栈信息.

系统默认不会产生core文件,默认core文件大小为0,需要设置ulimit -c unlimited

bash
$ ulimit -a             # 查看一个进程启动时的资源限制参数
$ ulimit -c unlimited   # 设置core文件大小不设限制
$ gdb a.out core
(gdb) backtrace full

多线程调试

todo...

远程调试

如果远程和本地两台机架构相同:远程使用gdbserver,本地使用gdb 如果远程和本地两台机架构不同:远程使用gdbserver,本地使用gdb-multiarch

bash
# 远程
$ gdbserver :12345 a.out

# 本地
$ gdb
(gdb) target remote 127.0.0.1:port  # 连接远程gdbserver
(gdb) break main                    # 设置main函数断点
(gdb) continue                      # 开始调试
(gdb) next                          # 单步调试
(gdb) monitor exit                  # 关闭远程gdbserver

演示

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

int random_range(int min_num, int max_num) {
    if (min_num > max_num) {
        exit(EXIT_FAILURE);
    }
    int ret = 1 + rand() % (max_num - min_num) + min_num;
    return ret;
}

int main(int argc, char* argv[]) {
    int* nil = NULL;
    srand(time(NULL));
    int ret = 0;
    for (int i = 0; i < 200; i++) {
        ret = random_range(-100, 100);
        if (ret == 13) {
            printf("boom!\n");
            memcpy(nil, &ret, sizeof(nil));  // segmentation fault
        }
        printf("ret = %d\n", ret);
        usleep(10000);
    }
    return 0;
}

Refrences:

Updated at: