GDB 入门--配置

GDB 入门--配置

[TOC]
本文介绍 GDB 的安装, 环境配置, 调试的开始与结束等基础操作.

安装

Linux 下

  • 安装与否确认
1
gdb -v
  • 二进制安装
1
sudo apt -y install gdb
  • 源码安装

下载地址: http://ftp.gnu.org/gnu/gdb/
前提: GCC 编译器

1
2
3
4
5
6
tar -zxvf gdb-9.2.tar.gz
mkdir gdb-build-9.2
cd gdb-build-9.2
./configure
make
sudo make install

前期准备: 编译选项

  • 打开 -g 选项
    较早以前的 C 语言编译器也允许使用 -gg 选项来产生调试信息, 但是现在版本的 GDB 不再支持这种格式产生的调试信息, 所以不建议使用 -gg 选项.

  • -Og 优化级别要求

    • 优化程序介于 O0 ~ O1 之间.
    • 在保持快速编译和良好调试体验的同时, 提供较为合理的优化级别.
  • CMake 构建示例

1
2
3
SET(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

调用 GDB

调用方式

  1. 直接使用 gdb 命令启动 GDB 调试器
    需启动后借助 file 或者 exec-file 命令指定执行程序.

  2. 调试尚未执行的程序
    gdb program

  3. 调试正在执行的程序(可能需要 sudo 获取权限).
    pidof EXE_FILE_NAME

      1. gdb attach PID
      1. gdb FILENAME PID
      1. gdb -p PID

退出

  • detach 命令
  • quit( 或 q )命令

调试执行异常崩溃的程序 core dump

core dump 原因举例: 内存访问越界( 例如数组越界 , 输出字符串时该字符串没有 \0 结束符等 ) , 非法使用空指针等.

通过 GDB 调试产生的 core 文件, 往往可以更快速的定位问题.

ulimit 命令

  • core 生成开关

    • ulimit -c
    • 若结果为 0, 则表示关闭了此功能, 不会生成 core 文件.
  • 设置 core 文件大小

    • ulimit -c unlimited/0/1024
    • 不限制 core 文件的大小/不生成/上限 1024Kb.
    • 如果生成的信息超过此大小, 将会被裁剪, 最终生成一个不完整的 core 文件或者根本就不生成. 如果生成被裁减的 core 文件, 调试此 core 文件的时候, gdb 也会提示错误.
  • ulimit -a

    • 列出所有资源的限制.
    • 输出举例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      $ ulimit -a  
      core file size (blocks, -c) 0
      data seg size (kbytes, -d) unlimited
      file size (blocks, -f) unlimited
      max locked memory (kbytes, -l) 4
      max memory size (kbytes, -m) unlimited
      open files (-n) 2048
      pipe size (512 bytes, -p) 8
      stack size (kbytes, -s) 10240
      cpu time (seconds, -t) unlimited
      max user processes (-u) 7168
      virtual memory (kbytes, -v) unlimited

ulimit 命令设置后只对一个终端有效, 所以另起终端后需要重新设置.

设置 core 文件的名称和文件路径

默认生成路径: 输入可执行文件运行命令的同一路径下.
默认生成名字: 默认命名为 core. 新的 core 文件会覆盖旧的 core 文件.

例如设置 PID 作为文件扩展名:
/proc/sys/kernel/core_uses_pid 可以控制 core 文件的文件名中是否添加 PID 作为扩展.

  • 文件内容为 1, 表示添加 PID 作为扩展名, 生成的 core 文件格式为 core.xxxx. 为 0 则表示生成的 core 文件同一命名为 core.

  • 可通过以下命令修改此文件:

    1
    echo "1" > /proc/sys/kernel/core_uses_pid 
  • /proc/sys/kernel/core_pattern或者 /etc/sysctl.conf 文件, 可以控制 core 文件保存位置和文件名格式. 到底哪一个和系统版本相关, 需要实际测试.

    • 使用 vi 可能无法成功编辑 proc/sys/kernel/core_pattern, 只能使用 echo 命令修改或者命令 sysctl 修改.

      1
      2
      3
      echo "/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern
      #OR
      sysctl -w kernel.core_pattern=/corefile/core.%e.%p.%s.%E
    • 参数列表

      • %p - insert pid into filename 添加pid(进程id).
      • %u - insert current uid into filename 添加当前uid(用户id).
      • %g - insert current gid into filename 添加当前gid(用户组id).
      • %s - insert signal that caused the coredump into the filename 添加导致产生core的信号.
      • %t - insert UNIX time that the core dump occurred into filename 添加core文件生成时的unix时间.
      • %h - insert host name where the core dump happened into filename 添加主机名.
      • %e - insert coredumping executable name into filename 添加导致产生core的命令名.
    • 修改 /etc/sysctl.conf

      • 添加需要保存的路径 kernel.core_pattern = /tmp/corefile/core.%e.%t, 需要注意的是该路径必须应用有写的权限, 不然 core 文件是不会生成的. 再执行命令 sysctl -p 即可生效.
      • 需要注意的是, 如果 /tmp/corefile 目录原先不存在, 那么生成的 core dump 文件就无处存放, 所以要先确定设置的目录是事先存在的.
    • 快速验证配置是否生效
      用命令: kill -s SIGSEGV $$ 验证是否能生成 core 文件.

查看问题

gdb 程序名 core 名.
例子 问题源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

static void sub(void);
int main(void)
{
sub();
return 0;
}

static void sub(void)
{
int *p = NULL;
/* derefernce a null pointer, expect core dump. */
printf("%d", *p);
}

报错:

1
2
3
4
5
6
$ gcc -Wall -g foo.c  
$ ./a.out
Segmentation fault

$ ls -l core.*
ls: core.*: No such file or directory

设置 core dump

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ ulimit -c 1024  

$ ulimit -a
core file size (blocks, -c) 1024
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
max locked memory (kbytes, -l) 4
max memory size (kbytes, -m) unlimited
open files (-n) 2048
pipe size (512 bytes, -p) 8
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 7168
virtual memory (kbytes, -v) unlimited

$ ./a.out
Segmentation fault (core dumped)
$ ls -l core.*
-rw------- 1 uniware uniware 53248 Jun 30 17:10 core.9128

查看结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ gdb --core=core.9128  

GNU gdb Asianux (6.0post-0.20040223.17.1AX)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-asianux-linux-gnu".
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x08048373 in ?? ()
(gdb) bt
#0 0x08048373 in ?? ()
#1 0xbfffd8f8 in ?? ()
#2 0x0804839e in ?? ()
#3 0xb74cc6b3 in ?? ()
#4 0x00000000 in ?? ()

此时用 bt 看不到 backtrace, 也就是调用堆栈, 原来 GDB 还不知道符号信息在哪里. 我们告诉它一下:

1
2
3
4
5
6
(gdb) file ./a.out  
Reading symbols from ./a.out...done.
Using host libthread_db library "/lib/tls/libthread_db.so.1".
(gdb) bt
#0 0x08048373 in sub () at foo.c:17
#1 0x08048359 in main () at foo.c:8

启动参数

启动参数 功能
-pid
-p
调试特定进程的程序
-symbols file
-s file
仅从指定 file 文件中读取符号表
-quient
-q
-silent
取消启动 GDB 调试器时打印的介绍信息和版权信息
-cd directory 以 directory 作为启动 GDB 调试器的工作目录, 而非当前所在目录
-args args1 args2 向可执行文件传递执行所需要的参数
-x file_name 从指定的文件读取断点设置等命令

gdb 的长选项可以是 -, 或者 --. 一般选择较短的 -.

1
2
3
4
5
$ gdb -help
$ gdb --help

$ gdb -args ./a.out a b c
$ gdb --args ./a.out a b c

运行环境设置

file 命令

指定要调试的目标程序. 可以在运行 gdb 过后指定要调试的程序.

传递被调试程序参数

argcargv[] 接收参数.

  • 启动时, -args 选项

    1
    $ gdb -args ./a.out a b c
  • 启动后, set args 命令

    1
    2
    3
    (gdb) set args a b c
    (gdb) show args
    Argument list to give program being debugged when it is started is "a b c".
  • 运行程序时

    1
    2
    3
    4
    5
    6
    (gdb) r a b
    Starting program: /home/xmj/tmp/a.out a b
    (gdb) show args
    Argument list to give program being debugged when it is started is "a b".
    (gdb) r
    Starting program: /home/xmj/tmp/a.out a b

    可以看出, 参数已经被保存了, 下次运行时直接运行 run 命令, 即可.

  • 让已经设置的参数为空

    1
    (gdb) set args

设置环境变量

  • 临时设置 PATH 环境变量
1
(gdb) path /temp/demo
  • 设置被调试程序的环境变量: set env varname=value
1
set env LD_PRELOAD=/lib/x86_64-linux-gnu/libpthread.so.0

指定当前工作目录

  • 在 gdb 中直接执行 cdpwd 命令
1
2
3
4
(gdb) pwd
Working directory /home/xmj.
(gdb) cd tmp
Working directory /home/xmj/tmp.

重定向

GDB 调试器允许对执行程序的输入和输出进行重定向, 使其从文件或其它终端接收输入, 或者将执行结果输出到文件或其它终端.

1
(gdb) run > a.txt

help 命令

  1. help 命令不加任何参数会得到命令的分类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
  1. help class: class 类别下所有命令的列表和命令功能.
  2. help command 命令得到某一个具体命令的用法.
  3. apropos regexp 命令查找所有符合 regexp 正则表达式的命令信息.
    1
    2
    3
    4
    5
    6
    7
    (gdb) apropos set
    awatch -- Set a watchpoint for an expression
    b -- Set breakpoint at specified line or function
    br -- Set breakpoint at specified line or function
    bre -- Set breakpoint at specified line or function
    brea -- Set breakpoint at specified line or function
    ......

启动时不显示提示信息

启动时 -q 选项.
长期设置: 在 ~/.bashrc 中, 为 gdb 设置一个别名: alias gdb="gdb -q".

退出时不显示提示信息

1
2
3
4
5
A debugging session is active.

Inferior 1 [process 29686 ] will be killed.

Quit anyway? (y or n) n

使用 (gdb) set confirm off.
也可以把这个命令加到 .gdbinit 配置文件里, 不用每次输入.

输出信息多时不会暂停输出

当输出信息较多时, gdb 会暂停输出, 并会打印 ---Type <return> to continue, or q <return> to quit--- 这样的提示信息, 如下面所示:

1
2
3
81 process 2639102      0xff04af84 in __lwp_park () from /usr/lib/libc.so.1
80 process 2573566 0xff04af84 in __lwp_park () from /usr/lib/libc.so.1
---Type <return> to continue, or q <return> to quit---Quit

使用 set pagination off 或者 set height 0 命令. 这样 gdb 就会全部输出, 中间不会暂停.

设置命令提示符

当存在多个 GDB 版本时可以通过提示符进行区分.
查看当前 GDB 版本

1
$ gdb -q `which gdb`

使用 set prompt (main gdb) 来区分到底是在用哪个版本的 gdb.

设置源文件查找路径

有时在运行状态下, gdb 不能准确地定位到源文件的位置(比如文件被移走了, 等等), 此时可以用 directory PATH(dir PATH) 命令设置查找源文件的路径.

如果希望在 gdb 启动时, 加载 code 的位置, 避免每次在 gdb 中再次输入命令, 可以使用 gdb 的 -d 参数.

1
gdb a.out -d /search/code/some

有时调试程序时, 源代码文件可能已经移到其它的文件夹了. 此时可以用 set substitute-path from PATH1 to PATH2 命令设置新的文件夹( PATH2 )目录替换旧的(PATH1).

支持预处理器宏信息

使用 gcc -g 编译生成的程序, 是不包含预处理器宏信息的.
例子代码

1
2
3
4
5
6
7
8
9
#include <stdio.h>

#define NAME "Joe"

int main()
{
printf ("Hello %s\n", NAME);
return 0;
}
1
2
(gdb) p NAME
No symbol "NAME" in current context.

使用 gcc -g3 进行编译可以获得预处理的宏信息:

1
2
(gdb) p NAME
$1 = "Joe"

在 gdb 中执行 shell 命令和 make

1
2
3
4
5
(gdb) shell ls
(gdb) shell gcc test.c -g
#OR
(gdb) !ls
(gdb) !gcc test.c -g

! 和命令之间不需要有空格.

当在构建环境(build 目录)下调试程序的时候, 可以直接运行 make.

1
(gdb) make CFLAGS="-g -O0"

指定程序的输入输出设备

在 gdb 中, 默认下程序的输入输出是和 gdb 使用同一个终端. 也可以为程序指定一个单独的输入输出终端.

  • 首先, 打开一个新终端, 使用如下命令获得设备文件名:
    1
    2
    $ tty
    /dev/pts/2
  • 然后, 通过命令行选项指定程序的输入输出设备:
    1
    2
    $ gdb -tty /dev/pts/2 ./a.out
    (gdb) r
  • 或者, 在 gdb 中, 使用命令进行设置:
    1
    (gdb) tty /dev/pts/2

开始与结束

run( r )与 start 命令

  • 例子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //存储路径为 /tmp/demo/main.c
    //其生成的可执行文件为 main.exe, 位于同一路径下
    #include<stdio.h>
    int main(int argc,char* argv[])
    {
    FILE * fp;
    if((fp = fopen(argv[1],"r")) == NULL){
    printf("file open fail");
    }
    else{
    printf("file open true");
    }
    return 0;
    }

调试过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# pwd   <--显示当前工作路径
/tmp/demo
# ls <-- 显示当前路径下的文件
a.txt main.c main.exe
# cd ~ <-- 进入 home 目录
# gdb -q <-- 开启 GDB 调试器
(gdb) cd /tmp/demo <-- 修改 GDB 调试器的工作目录
Working directory /tmp/demo.
(gdb) file main.exe <-- 指定要调试的目标文件
Reading symbols from main.exe...
(gdb) set args a.txt <-- 指定传递的数据
(gdb) run <-- 运行程序
Starting program: /tmp/demo/main.exe a.txt
file open true[Inferior 1 (process 43065) exited normally]

默认情况下, run 命令会一直执行程序, 直到执行结束. 如果程序中手动设置有断点, 则 run 命令会执行程序至第一个断点处.
start 命令会执行程序至 main() 主函数的起始位置, 即在 main() 函数的第一行语句处停止执行( 该行代码尚未执行 ).
使用 start 命令启动程序, 等价于先在 main() 主函数起始位置设置一个断点, 然后再使用 run 命令启动程序.
程序执行过程中使用 run 或者 start 命令, 表示的是重新启动程序.

finishreturn 命令

  • finish 命令会执行函数到正常退出.
  • return命令是立即结束执行当前函数并返回.
  • return 命令还有一个功能, 即可以指定该函数的返回值.

资源推荐

cheat sheet: https://users.ece.utexas.edu/~adnan/gdb-refcard.pdf

作者

cx

发布于

2022-07-30

更新于

2022-07-31

许可协议