GDB: The GNU Project Debugger

GDB: The GNU Project Debugger


【后台回复 “gdb” 获取《Debugging with gdb》pdf 及 《gdb速查表》】

什么是GDB

GDB,GNU 项目调试器,通常以其命令 gdb 而闻名,允许查看另一个程序在执行时“内部”发生了什么,或者另一个程序在崩溃时正在做什么。

gdb是一个交互式控制台,可逐步浏览源代码,分析执行的内容,并基本上对错误应用程序中出现的问题进行逆向工程。

GDB 可以做4种主要的事情(以及支持这些事情的其他事情)来帮助捕获行为中的错误:

  • 启动的程序,指定任何可能影响其行为的内容。
  • 让程序在指定条件下停止。
  • 当程序停止时检查发生了什么。
  • 更改程序中的某些内容,以便可以尝试纠正一个错误的影响并继续了解另一个错误。

这些程序可能在与 GDB 相同的机器(本机)、另一台机器(远程)或模拟器上执行。

安装gdb

sudo apt-get install gdb

# 查看版本
(base) qiancj@qiancj-HP-ZBook-G8:~$ gdb --version
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

调试GDB3种方式:

  1. 直接调试目标程序:gdb ./hello_randy
  2. 附加到进程:gdb attach pid
  3. 调试core文件:gdb filename corename

用法

选择调试对象及其文件:

  --args             可执行文件之后的参数被传递给下级
  --core=COREFILE    分析核心转储 COREFILE。
  --exec=EXECFILE    使用 EXECFILE 作为可执行文件。
  --pid=PID          附加到正在运行的进程 PID。
  --directory=DIR    在 DIR 中搜索源文件。
  --se=FILE          使用FILE作为符号文件和可执行文件。
  --symbols=SYMFILE  从 SYMFILE 中读取符号。
  --readnow          首次访问时完全读取符号文件。
  --readnever        不要读取符号文件。
  --write            设置写入可执行文件和核心文件。

初始命令和命令文件:

  --command=FILE, -x 从 FILE 执行 GDB 命令。
  --init-command=FILE, -ix
                     与-x类似,但在加载inferior之前执行命令。
  --eval-command=COMMAND, -ex
                     执行单个 GDB 命令。
                     可以多次使用并与 --command 结合使用。
  --init-eval-command=COMMAND, -iex-ex 类似,但在加载之前。
  --nh               不要读取 ~/.gdbinit。
  --nx               不要读取任何目录中的任何 .gdbinit 文件。

输出和用户界面控制:

  --fullname         emacs-GDB 接口使用的输出信息。
  --interpreter=INTERP
                     选择特定的解释器/用户界面
  --tty=TTY          正在调试的程序使用 TTY 进行输入/输出ged.
  -w                 使用 GUI 界面。
  --nw               不要使用 GUI 界面。
  --tui              使用终端用户界面。
  --dbx              DBX 兼容模式。
  -q, --quiet, --silent
                     启动时不打印版本号。

操作模式:

  --batch            处理选项后退出。
  --batch-silent     与 --batch 类似,但抑制所有 gdb stdout 输出。
  --return-child-result
                     GDB 退出代码将是子进程的退出代码。
  --configuration    打印有关 GDB 配置的详细信息,然后退出。
  --help             打印此消息然后退出。
  --version          打印版本信息然后退出。

远程调试选项:

  -b BAUDRATE        设置用于远程调试的串口波特率。
  -l TIMEOUT         设置远程调试的超时(以秒为单位)。

其他选项:

  --cd=DIR           将当前目录更改为 DIR。
  --data-directory=DIR,-D
                     将 GDB 的数据目录设置为 DIR。

进入debug后常用命令

命令名称命令缩写命令说明
runr运行一个待调试的程序
continuec让暂停的程序继续运行
nextn运行到下一行
steps单步执行,遇到函数会进入
untilu运行到指定行停下来
finishfi结束当前调用函数,回到上一层调用函数处
returnreturn结束当前调用函数并返回指定值,到上一层函数调用处
jumpj将当前程序执行流跳转到指定行或地址
printp打印变量或寄存器值
backtracebt查看当前线程的调用堆栈
framef切换到当前调用线程的指定堆栈
threadthread切换到指定线程
breakb添加断点
tbreaktb添加临时断点
deleted删除断点
enableenable启用某个断点
disabledisable禁用某个断点
watchwatch监视某一个变量或内存地址的值是否发生变化
listl显示源码
infoi查看断点 / 线程等信息
ptypeptype查看变量类型
disassembledis查看汇编代码
set argsset args设置程序启动命令行参数
show argsshow args查看设置的命令行参数

Example

假设有以下代码

#include <stdio.h>   //printf
#include <stdlib.h>  //srand

#include <iostream>

using namespace std;

int main() {
  srand(time(NULL));
  int alpha = rand() % 8;
  cout << "Hello Randy." << endl;
  int beta = 2;

  printf("alpha is set to is %s\n", alpha);
  printf("kiwi is set to is %s\n", beta);

  return 0;
}  // main

很明显,代码里面 printf 里面应该打印的是整型,但是匹配的字符是字符(%s)

但这是一个简单的问题,可以了解调试过程。编译并运行它以查看错误:

(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test$ g++ -o randy test_gdb.cpp
test_gdb.cpp: In function ‘int main()’:
(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test$ g++ -o randy test_gdb.cpp
test_gdb.cpp: In function ‘int main()’:
test_gdb.cpp:14:29: warning: format ‘%s’ expects argument of type ‘char*’, but argument 2 has type ‘int’ [-Wformat=]
   14 | printf("alpha is set to is %s\n", alpha);
      |                            ~^     ~~~~~
      |                             |     |
      |                             char* int
      |                            %d
test_gdb.cpp:15:28: warning: format ‘%s’ expects argument of type ‘char*’, but argument 2 has type ‘int’ [-Wformat=]
   15 | printf("kiwi is set to is %s\n", beta);
      |                           ~^     ~~~~
      |                            |     |
      |                            char* int
      |                           %d
(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test$ ./randy
Hello Randy.
Segmentation fault (core dumped)

排查段错误

从此输出中,可以推测变量 alpha 设置正确,否则,不会期望它后面的代码行。

当然,这并不总是正确的,但这是一个很好的工作理论,如果使用 printf 作为日志和调试器,它基本上与你可能得出的结论相同。

从这里,可以假设错误位于成功打印的那一行之后的某行中。但是,目前尚不清楚该错误是在下一行还是在几行之后。

GNU 调试器是一个交互式的疑难解答,因此可以使用 gdb 命令来运行有缺陷的代码。

为了获得最佳结果,应该从包含调试符号的源代码重新编译有缺陷的应用程序。

加上-g重新编译程序,然后运行程序

(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test$ g++ -g -o randy test_gdb.cpp
(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test$ gdb ./randy
--Type <RET> for more, q to quit, c to continue without paging--help
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./randy...
(gdb) start
Temporary breakpoint 1 at 0x1229: file test_gdb.cpp, line 8.
Starting program: /home/qiancj/codes/test/randy 

Temporary breakpoint 1, main () at test_gdb.cpp:8
8       int main() {
(gdb) continue
Continuing.
Hello Randy.

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
65      ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.

可以在开启gdb后,一路next,逐行打印代码,也能发现问题:

8       int main() {
(gdb) next
9         srand(time(NULL));
(gdb) next
10        int alpha = rand() % 8;
(gdb) next
11        cout << "Hello Randy." << endl;
(gdb) next
Hello world.
12        int beta = 2;
(gdb) next
14        printf("alpha is set to is %s\n", alpha);
(gdb) next

Program received signal SIGSEGV, Segmentation fault.
__strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
65      ../sysdeps/x86_64/multiarch/strlen-avx2.S: No such file or directory.

也可以通过break设置断点,可以在某一行设置断点,也可以在某个函数处设置断点

(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test$ gdb ./randy

For help, type "help".
Type "apropos word" to search for commands related to "word"...
--Type <RET> for more, q to quit, c to continue without paging--c
Reading symbols from ./randy...
(gdb) break 11
Breakpoint 1 at 0x1259: file test_gdb.cpp, line 11.
(gdb) start
Temporary breakpoint 2 at 0x1229: file test_gdb.cpp, line 8.
Starting program: /home/qiancj/codes/test/randy 

Temporary breakpoint 2, main () at test_gdb.cpp:8
warning: Source file is more recent than executable.
8       int main() {
(gdb) c
Continuing.

Breakpoint 1, main () at test_gdb.cpp:11
11        cout << "Hello Randy." << endl;

断点成功断在了11行,通过print查看变量的值

(gdb) print alpha
$1 = 4
(gdb) print beta
$2 = 0

继续进行,可以步进代码行来到达将 beta 设置为一个值的位置:

(gdb) next
Hello world.
12        int beta = 2;
(gdb) print beta
$3 = 0
(gdb) next
14        printf("alpha is set to is %s\n", alpha);
(gdb) print beta
$4 = 2

可以通过watch设置观察点(watch beta > 0),当条件满足时,程序就会断住

(gdb) watch beta > 0
No symbol "beta" in current context.
(gdb) break 12
Breakpoint 1 at 0x1281: file test_gdb.cpp, line 12.
(gdb) continue
The program is not being run.
(gdb) start
Temporary breakpoint 2 at 0x1229: file test_gdb.cpp, line 8.
Starting program: /home/qiancj/codes/test/randy 

Temporary breakpoint 2, main () at test_gdb.cpp:8
warning: Source file is more recent than executable.
8       int main() {
(gdb) c
Continuing.
Hello world.

Breakpoint 1, main () at test_gdb.cpp:12
12        int beta = 2;
(gdb) watch beta  > 0
Hardware watchpoint 3: beta  > 0
(gdb) c
Continuing.

Hardware watchpoint 3: beta  > 0

Old value = false
New value = true
main () at test_gdb.cpp:14
14        printf("alpha is set to is %s\n", alpha);

next 手动步进完成代码的执行,或者可以用断点观察点捕捉点来控制代码的执行。

可以以不同形式查看 beta 的值

(gdb) print beta // 查看 beta 的值
$2 = 2
(gdb) print /o beta // 以8进制查看 beta的值
$3 = 02
(gdb) print /o &beta // 以8进制查看beta的内存地址
$4 = 03777777777753074
(gdb) whatis beta // 查看beta 的类型
type = int

(gdb) print /x beta // 以16进制查看 beta的值
$5 = 0x2

当通过whatis查看beta类型的时候,会让我们想起来 printf 打印 beta 值的时候,使用的是%s,所以代码中必须使用 %d 来代替 %s

查看某个函数的反汇编代码

(gdb) disassemble main
Dump of assembler code for function main():
   0x0000555555555229 <+0>:     endbr64 
   0x000055555555522d <+4>:     push   %rbp
   0x000055555555522e <+5>:     mov    %rsp,%rbp
   0x0000555555555231 <+8>:     sub    $0x10,%rsp
   0x0000555555555235 <+12>:    mov    $0x0,%edi
   0x000055555555523a <+17>:    callq  0x5555555550f0 <time@plt>
   0x000055555555523f <+22>:    mov    %eax,%edi
   0x0000555555555241 <+24>:    callq  0x555555555100 <srand@plt>
   0x0000555555555246 <+29>:    callq  0x5555555550d0 <rand@plt>
   0x000055555555524b <+34>:    cltd   
   0x000055555555524c <+35>:    shr    $0x1d,%edx
   0x000055555555524f <+38>:    add    %edx,%eax
   0x0000555555555251 <+40>:    and    $0x7,%eax
   0x0000555555555254 <+43>:    sub    %edx,%eax

当代码编译但随后发现存在错误时,这尤其令人沮丧,但这是最棘手的错误的工作方式。

如果它们很容易被抓住,它们就不会是bug。使用 GDB 是追捕和消除它们的一种方法。

GDB 速查表

用了GDB,才体会到Visual Studio的调试功能做的多么人性化。

其实本质上可能都一样的,都是调用gdb软件,然后设置断点,查看变量值。

Reference


>>>>> 欢迎关注公众号【三戒纪元】 <<<<<

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值