GDB调试器

我们在讲指针时用 GDB 调试段错误。

本篇将详细介绍 gdb 的最常用命令日志记录检测点,最后介绍如何用 gdb 调试进程以及用gdb 调试一个开源项目的调试版本 —— glmark2。

gdb介绍

GDB, the GNU Project debugger —— gdb官网

gdb 是一款调试器,能打断点。支持多种语言,例如 c、c++、go。

Tip:有关 GNU Project,请看本篇扩展

官网显示最新版本是13.2(20230704)。点击官网顶部[documentation]可查看文档。

安装GDB

笔者已经用 apt 源安装了gbd:

jjj-pc:~/pj/glmark2$ sudo apt install gdb
正在读取软件包列表... 完成
正在分析软件包的依赖关系树
正在读取状态信息... 完成
gdb 已经是最新版 (9.1-0kylin1)。
下列软件包是自动安装的并且现在不需要了:
  archdetect-deb dmeventd libaio1 libdebian-installer4 libdevmapper-event1.02.1 liblvm2cmd2.03 localechooser-data lvm2 user-setup
使用'sudo apt autoremove'来卸载它(它们)。
升级了 0 个软件包,新安装了 0 个软件包,要卸载 0 个软件包,有 6 个软件包未被升级。

笔者gbd版本是 9.1

jjj-pc:~/pj/glmark2$ gdb --version
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.

最常用命令

man gdb 告诉我们最常用的命令有:break、run、print、c、next、list、step、quit。

// 以下是一些最常用的GDB命令:
Here are some of the most frequently needed GDB commands:

       break [file:]function
           Set a breakpoint at function (in file).

       run [arglist]
           Start your program (with arglist, if specified).

       bt  Backtrace: display the program stack.

       print expr
           // 显示表达式的值
           Display the value of an expression.

       c   Continue running your program (after stopping, e.g. at a breakpoint).

       next
           // 执行下一条程序语句(在停止后);跳过该行中的任何函数调用。
           Execute next program line (after stopping); step over any function calls in the line.

       edit [file:]function
           look at the program line where it is presently stopped.

       list [file:]function
           type the text of the program in the vicinity of where it is presently stopped.

       step
           Execute next program line (after stopping); step into any function calls in the line.

       help [name]
           Show information about GDB command name, or general information about using GDB.

       quit
           Exit from GDB.

准备一段 C 代码用作gdb命令学习:

#include <stdio.h>

// add 函数
int add(int a, int b) {
    int sum = a + b;
    return sum;
}

int main() {
    int num1 = 3;
    int num2 = 5;
    
    int result = add(num1, num2);
    
    printf("两个整数的和为:%d\n", result);
    
    return 0;
}

run 和 quit

通过 gdb demo 运行进入gdb模式,输入 run 运行程序,输入 quit 则退出gdb。详细请看:

// 通过 -g 编译出有调试信息的可执行文件
jjj-pc:~/pj$ gcc demo.c -o demo -g
// gdb 运行
jjj-pc:~/pj$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 输入 run 运行程序
(gdb) run
Starting program: /home/jjj/pj/demo
两个整数的和为:8
[Inferior 1 (process 3022770) exited normally]
// 输入 quit 退出
(gdb) quit
jjj-pc:~/pj$

Tip: ctrl + z 能直接退出gdb

list

如果不知道在哪行或哪个方法打断点,可以通过 list 查看源码。请看示例:

jjj-pc:~/pj$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 查看源码
(gdb) list
1       #include <stdio.h>
2
3       // 自定义函数,用于计算两个整数的和
4       int add(int a, int b) { // a, b 叫形参
5           int sum = a + b;
6           return sum;
7       }
8
9       int main() {
10          int num1 = 3;
// 一次显示不完,继续查看后面10行
(gdb) list
11          int num2 = 5;
12
13          // 调用自定义函数计算两个整数的和
14          int result = add(num1, num2); 
15
16          printf("两个整数的和为:%d\n", result);
17
18          return 0;
19      }
// 到底了。
(gdb) list
Line number 20 out of range; demo.c has 19 lines.
// 查看5到10行
(gdb) list 5,10
5           int sum = a + b;
6           return sum;
7       }
8
9       int main() {
10          int num1 = 3;
(gdb)

break 和 info break

break(简写 b) 用于打断点,info break 用于查看打了哪些断点。请看示例:

jjj-pc:~/pj$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 给 mian 方法打断点
(gdb) break main
Breakpoint 1 at 0x1167: file demo.c, line 9.
// 给11行打断点
(gdb) b 11
Breakpoint 2 at 0x117a: file demo.c, line 11.
// 查看打了哪些断点
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001167 in main at demo.c:9
2       breakpoint     keep y   0x000000000000117a in main at demo.c:11
// 查看打了哪些断点
(gdb) info break
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001167 in main at demo.c:9
2       breakpoint     keep y   0x000000000000117a in main at demo.c:11

next 和 step

代码中断后,输入 next 和 step 都会执行下一行,然而 next 会跳过函数,step 会进入函数。请看示例:

  • next 跳过函数
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 打断点
(gdb) b 9
Breakpoint 1 at 0x1167: file demo.c, line 9.
// 运行
(gdb) run
Starting program: /home/jjj/pj/demo
// 在断点处停止
Breakpoint 1, main () at demo.c:9
9       int main() {
// 下一行
(gdb) next
10          int num1 = 3;
(gdb) next
// 下一行
11          int num2 = 5;
(gdb) next
// 下一行。跳过函数
14          int result = add(num1, num2);
(gdb) next
// 下一行
16          printf("两个整数的和为:%d\n", result);
(gdb) next
两个整数的和为:8
18          return 0;
  • step 进入函数
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
(gdb) b 9
Breakpoint 1 at 0x1167: file demo.c, line 9.
(gdb) run
Starting program: /home/jjj/pj/demo

Breakpoint 1, main () at demo.c:9
9       int main() {
(gdb) step
10          int num1 = 3;
(gdb) step
11          int num2 = 5;
(gdb) step
14          int result = add(num1, num2); 
(gdb) step
add (a=21845, b=1431654909) at demo.c:4
4       int add(int a, int b) { 
(gdb) step
5           int sum = a + b;
(gdb) step
6           return sum;
(gdb) step
7       }
(gdb) step
main () at demo.c:16
16          printf("两个整数的和为:%d\n", result);
(gdb) step
两个整数的和为:8
18          return 0;

continue

next 和 step 会执行下一行,而continue(简写c) 会执行到下一个断点处停止。 请看示例:

Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
// 断点
(gdb) b main
Breakpoint 1 at 0x1167: file demo.c, line 9.
// 断点
(gdb) b 16
Breakpoint 2 at 0x1193: file demo.c, line 16.
// 运行后在第一断点处停止
(gdb) run
Starting program: /home/jjj/pj/demo

Breakpoint 1, main () at demo.c:9
9       int main() {
// 下一个断点
(gdb) c
Continuing.

Breakpoint 2, main () at demo.c:16
16          printf("两个整数的和为:%d\n", result);
(gdb)

print

print 用于查看表达式的值。请看示例:

// 在11行打断点,查看 num1 
(gdb) list 9,11
9       int main() {
10          int num1 = 3;
11          int num2 = 5;
(gdb) b 11
Breakpoint 1 at 0x117a: file demo.c, line 11.
(gdb) run
Starting program: /home/jjj/pj/demo

Breakpoint 1, main () at demo.c:11
11          int num2 = 5;
// 查看num1的值
(gdb) print num1
$1 = 3
// 查看num1的地址
(gdb) print &num1
$2 = (int *) 0x7fffffffe2c4

gdb 小技巧

shell

gdb 可以执行 shell 命令。请看示例:

Type "apropos word" to search for commands related to "word"...
Reading symbols from demo2...
// ll 命令没有
(gdb) shell ll
bash: ll:未找到命令
// 执行 ls 命令
(gdb) shell ls
demo2  demo2.c
// 执行 cat 命令
(gdb) shell cat demo2.c
#include <stdio.h>

int main() {
    int i;

    for (i = 1; i <= 5; i++) {
        printf("Iteration %d\n", i);
    }

    return 0;
}

日志记录

set logging on 用于打开日志记录功能。该命令执行后,GDB将记录所有交互式会话的输入和输出到一个日志文件中。请看示例:

jjj-pc:~/pj/dir1$ gdb demo2
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo2...
// 开启日志功能
(gdb) set logging on
Copying output to gdb.txt.
Copying debug output to gdb.txt.
// 以下是一系列操作
(gdb) info b
No breakpoints or watchpoints.
(gdb) list
1       #include <stdio.h>
2
3       int main() {
4           int i;
5
6           for (i = 1; i <= 5; i++) {
7               printf("Iteration %d\n", i);
8           }
9
10          return 0;
(gdb) b 4
Breakpoint 1 at 0x1155: file demo2.c, line 6.
(gdb) run
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
(gdb) n
7               printf("Iteration %d\n", i);
(gdb) n
Iteration 1
6           for (i = 1; i <= 5; i++) {
(gdb) print i
$1 = 1
(gdb) c
Continuing.
Iteration 2
Iteration 3
Iteration 4
Iteration 5
[Inferior 1 (process 3040270) exited normally]
(gdb) quit
// 退出 gdb 后,发现同级目录生成一个叫 gdb.txt 文件。
jjj-pc:~/pj/dir1$ ls
demo2  demo2.c  gdb.txt
// 日志文件内容
jjj-pc:~/pj/dir1$ cat gdb.txt
No breakpoints or watchpoints.
1       #include <stdio.h>
2
3       int main() {
4           int i;
5
6           for (i = 1; i <= 5; i++) {
7               printf("Iteration %d\n", i);
8           }
9
10          return 0;
Breakpoint 1 at 0x1155: file demo2.c, line 6.
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
7               printf("Iteration %d\n", i);
6           for (i = 1; i <= 5; i++) {
$1 = 1
Continuing.
[Inferior 1 (process 3040270) exited normally]

监视点

GDB中的"watch points"(监视点)是一种调试功能,用于在程序执行期间监视变量内存地址的更改。请看示例:

比如有个大程序,我要查看某个变量变化状态,被谁修改的。

用以下示例模拟,假如要监视变量 i 的变化情况。做法如下:

jjj-pc:~/pj/dir1$ cat demo2.c
#include <stdio.h>

int main() {
    int i;

    for (i = 1; i <= 5; i++) {
        printf("Iteration %d\n", i);
    }

    return 0;
}
  • 方式1:监视变量
jjj-pc:~/pj/dir1$ gdb demo2
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo2...
// 在 `int i` 处打断点
(gdb) b 4
Breakpoint 1 at 0x1155: file demo2.c, line 6.
// 运行,并在断点处中断
(gdb) run
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 监视变量 i
(gdb) watch i
Hardware watchpoint 2: i
(gdb) info watchpoinst
Undefined info command: "watchpoinst".  Try "help info".
// 使用 info watchpoints 命令查看所有当前设置的监视点的状态
(gdb) info watchpoints
Num     Type           Disp Enb Address            What
2       hw watchpoint  keep y                      i
// 继续。在 i 的值变化时中断,现在是1,是在第 6 行改变的
(gdb) c
Continuing.

Hardware watchpoint 2: i
// 旧值 0 变成 新值 1
Old value = 0
New value = 1
main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 继续
(gdb) c
Continuing.
Iteration 1

Hardware watchpoint 2: i

Old value = 1
New value = 2
0x0000555555555178 in main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 继续
(gdb) c
Continuing.
Iteration 2

Hardware watchpoint 2: i

Old value = 2
New value = 3
...
  • 方式2:监视内存地址指向的内容
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo2...
(gdb) b 4
Breakpoint 1 at 0x1155: file demo2.c, line 6.
(gdb) run
Starting program: /home/jjj/pj/dir1/demo2

Breakpoint 1, main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
// 取得变量的地址
(gdb) print &i
$1 = (int *) 0x7fffffffe2bc
// 使用 * 操作符是因为你想要监视内存地址 0x7fffffffe2bc 处的内容,而不是该地址本身。
(gdb) watch *0x7fffffffe2bc
Hardware watchpoint 2: *0x7fffffffe2bc
// 继续。在 i 的值变化时中断,现在是1,是在第 6 行改变的
(gdb) c
Continuing.

Hardware watchpoint 2: *0x7fffffffe2bc

Old value = 0
New value = 1
main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
(gdb) c
Continuing.
Iteration 1

Hardware watchpoint 2: *0x7fffffffe2bc

Old value = 1
New value = 2
0x0000555555555178 in main () at demo2.c:6
6           for (i = 1; i <= 5; i++) {
(gdb) c
...

扩展

GNU项目

GNU(GNU's Not Unix)项目是由Richard Stallman于1983年发起的一个自由软件运动。该项目旨在创建一个完全自由和开源的操作系统,以提供用户自由运行、研究、分发和改进软件的权利。

GNU项目的目标是为用户提供一个类似Unix的操作系统,但不同于Unix,它完全由自由软件组成。自由软件指的是用户拥有运行、复制、分发、研究和修改软件的自由。GNU项目的理念是,用户应该拥有对计算机软件的完全控制权,能够自由地使用、学习和修改软件,从而推动自由软件社区的发展和分享。

为实现这一目标,GNU项目开发了一系列自由软件工具和应用程序,如GNU编译器套件(GCC)、GNU调试器(GDB)、GNU Bash shell、GNU Core Utilities等。此外,GNU项目还推动了GNU宇宙文档计划,提供了丰富的自由文档资源,帮助用户学习和使用自由软件。

总而言之,GNU项目是一个致力于推广自由软件概念的运动,旨在提供一个完全自由和开源的操作系统。通过开发和推广自由软件工具,GNU项目为用户提供了更多选择和控制权,同时促进了自由软件社区的发展。

core文件

Core文件是操作系统在程序崩溃或异常终止时生成的一种特殊文件。它记录了程序发生异常时的内存状态、寄存器状态和其他调试信息,以便后续进行调试。

Core文件通常与可执行文件位于同一目录,并以文件名前缀为 "core" 开头,后跟进程ID号。例如,如果程序的可执行文件名为 "my_program",并且进程ID为1234,则生成的core文件可能命名为 "core.1234"。

操作系统是否允许生成core文件,可以使用ulimit命令。ulimit -c 如果返回值是0,则表示禁止生成core文件。如果返回值不是0,则表示core文件生成被允许,并且返回值表示core文件的最大大小(以KB为单位)。

笔者这里对核文件没有限制:

// 核文件的最大大小无限制
jjj-pc:~/pj/dir1$ ulimit -c
unlimited

Tip:ulimit 用于修改 shell 资源限制。

jjj-pc:~/pj/dir1$ ulimit --help
ulimit: ulimit [-SHabcdefiklmnpqrstuvxPT] [限制]
    修改 shell 资源限制。

    在允许此类控制的系统上,提供对于 shell 及其创建的进程所可用的
    资源的控制。

    选项:
      -S        使用软 (`soft') 资源限制
      -H        使用硬 (`hard') 资源限制
      -a        所有当前限制都被报告
      -b        套接字缓存尺寸
      -c        创建的核文件的最大尺寸
      -d        一个进程的数据区的最大尺寸
      -e        最高的调度优先级 (`nice')
      -f        有 shell 及其子进程可以写的最大文件尺寸
      -i        最多的可以挂起的信号数
      -k        分配给此进程的最大 kqueue 数量
      -l        一个进程可以锁定的最大内存尺寸
      -m        最大的内存进驻尺寸
      -n        最多的打开的文件描述符个数
      -p        管道缓冲区尺寸
      -q        POSIX 信息队列的最大字节数
      -r        实时调度的最大优先级
      -s        最大栈尺寸
      -t        最大的CPU时间,以秒为单位
      -u        最大用户进程数
      -v        虚拟内存尺寸
      -x        最大的文件锁数量
      -P        最大伪终端数量
      -T        最大线程数量
// 所有当前限制都被报告
jjj-pc:~/pj/dir1$ ulimit -a
// core文件大小限制为无限制
core file size          (blocks, -c) unlimited
// 数据段大小限制为无限制
data seg size           (kbytes, -d) unlimited
// 调度优先级限制为0。
scheduling priority             (-e) 0
// 文件大小限制为无限制。用于限制单个文件的最大大小
file size               (blocks, -f) unlimited
// 待处理信号数量限制为31429。
pending signals                 (-i) 31429
// 最大锁定内存大小限制为65536 KB。
max locked memory       (kbytes, -l) 65536
// 最大内存大小限制为无限制。
max memory size         (kbytes, -m) unlimited
// 打开文件数量限制为1024。
open files                      (-n) 1024
// 管道大小限制为512字节。
pipe size            (512 bytes, -p) 8
// POSIX消息队列大小限制为819200字节。
POSIX message queues     (bytes, -q) 819200
// 实时优先级限制为0
real-time priority              (-r) 0
// 栈大小限制为8192 KB。用于限制每个进程栈的大小
stack size              (kbytes, -s) 8192
// 用于限制一个进程在CPU上运行的最长时间
cpu time               (seconds, -t) unlimited
// 最大用户进程数限制为31429
max user processes              (-u) 31429
// 虚拟内存大小限制为无限制。
virtual memory          (kbytes, -v) unlimited
// 文件锁定数量限制为无限制。
file locks                      (-x) unlimited

创建一个会产生core文件的程序:

#include <stdio.h>

int main() {
    int* ptr = NULL;
    *ptr = 10; // 访问空指针将导致段错误

    return 0;
}

疑惑:笔者在调试台中不能生成 core 文件,暂时终止GDB core 文件。

进程调试

写个一直循环的程序:

jjj-pc:~/pj$ cat dir1/demo.c
#include <stdio.h>
#include <unistd.h>

int main() {
    while (1) {
        printf("Hello, World!\n");
        sleep(1); // 暂停1秒钟
    }

    return 0;
}

编译运行,得到进程号:

jjj-pc:~/pj/dir1$ gcc demo.c -o demo -g
// & 只后台运行
jjj-pc:~/pj/dir1$ ./demo &
[1] 3072058

通过 gdb -p 3072058 即可调试进程:

// gdb 进程
jjj-pc:~/pj/dir1$ gdb -p 3072058
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 3072058
Reading symbols from /home/jjj/pj/dir1/demo...
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
(No debugging symbols found in /lib/x86_64-linux-gnu/libc.so.6)
Reading symbols from /lib64/ld-linux-x86-64.so.2...
(No debugging symbols found in /lib64/ld-linux-x86-64.so.2)
0x00007f3ae9acc1e4 in clock_nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
// 查看源码
(gdb) list
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main() {
5           while (1) {
6               printf("Hello, World!\n");
7               sleep(1); // 暂停1秒钟
8           }
9
10          return 0;
// next
(gdb) n
Single stepping until exit from function clock_nanosleep,
which has no line number information.
0x00007f3ae9ad1ef7 in nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) n
Single stepping until exit from function nanosleep,
which has no line number information.
0x00007f3ae9ad1e2e in sleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) n
Single stepping until exit from function sleep,
which has no line number information.
main () at demo.c:6
6               printf("Hello, World!\n");
// next
(gdb) n
7               sleep(1); // 暂停1秒钟
// next
(gdb) n
6               printf("Hello, World!\n");
// next
(gdb) n
7               sleep(1); // 暂停1秒钟
(gdb) n
6               printf("Hello, World!\n");
(gdb)

Tipman gdb 中也有gdb进程或core的介绍:

You can also start with both an executable program and a core file specified:

        gdb program core

You can, instead, specify a process ID as a second argument or use option "-p", if you want to debug a running process:

        gdb program 1234
        gdb -p 1234

glmark2 调试版本

glmark2是一个用于测试OpenGL渲染性能的工具,它通过运行一系列图形场景和基准测试来评估计算机的图形处理能力。

笔者需要调试版本进行定位,故决定源码编译。

  • 首先下载项目
git clone https://github.com/glmark2/glmark2.git
cd glmark2
  • 修改 glmark2/wscript 文件,在def options(处增加一行
def options(opt):
    opt.add_option('--debug', action='store_true', default=False, help='Enable debug mode')

通过 ./waf --help 则会显示 --debug

jjj-pc:~/pj/glmark2$ ./waf --help
waf [commands] [options]
...

Options:
  --version             show program's version number and exit
  ...
  // debug
  --debug               Build with debug symbols
  • 运行./waf configure 命令来配置构建环境
jjj-pc:~/pj/glmark2$ ./waf configure --with-flavors=x11-gl --debug
Setting top to                           : /home/jjj/pj/glmark2
Setting out to                           : /home/jjj/pj/glmark2/build
Checking for 'gcc' (C compiler)          : /usr/bin/gcc
Checking for 'g++' (C++ compiler)        : /usr/bin/g++
Checking for header stdlib.h             : yes
Checking for header string.h             : yes
Checking for header stdint.h             : yes
Checking for header stdio.h              : yes
Checking for header dlfcn.h              : yes
Checking for header unistd.h             : yes
Checking for header jpeglib.h            : yes
Checking for header math.h               : yes
Checking for header string.h             : yes
Checking for library m                   : yes
Checking for library jpeg                : yes
Checking for function memset             : yes
Checking for function sqrt               : yes
Checking for program 'pkg-config'        : /usr/bin/pkg-config
Checking for 'libpng12'                  : yes
Checking for 'x11'                       : yes
Checking for 'libdrm'                    : yes
Checking for 'gbm'                       : yes
Checking for 'libudev'                   : yes
Checking for 'wayland-client'            : yes
Checking for 'wayland-cursor'            : yes
Checking for 'wayland-egl'               : yes
Prefix                                   : /usr/local
Data path                                : /usr/local/share/glmark2
Including extras                         : No
Building flavors                         : ['x11-gl']
'configure' finished successfully (2.189s)

:笔者安装了一些依赖包,由于未做笔记,也就忘记了,请读者自行完成,通常根据报错也能搜索到缺少什么包。

Tip: 关于调试版除了增加 --debug,还可以直接修改 wscript 中如下代码:

// 修改前
if is_win:
    configure_win32(ctx)
else:
    configure_linux(ctx)

// 修改后
if is_win:
    configure_win32(ctx)
    ctx.env.append_unique('CXXFLAGS', '/Zi')
    ctx.env.append_unique('LINKFLAGS', '/DEBUG')
else:
    configure_linux(ctx)
    ctx.env.append_unique('CXXFLAGS', '-g')
    ctx.env.append_unique('LINKFLAGS', '-g')
  • 运行./waf build 命令来编译glmark2的调试版本
jjj-pc:~/pj/glmark2$ ./waf build
Waf: Entering directory `/home/jjj/pj/glmark2/build'
[ 1/71] Compiling src/main.cpp
[ 2/71] Compiling src/canvas-generic.cpp
[ 3/71] Compiling src/native-state-x11.cpp
[ 4/71] Compiling src/gl-state-glx.cpp
[ 5/71] Compiling src/glad/src/glx.c

...

[64/71] Compiling src/scene.cpp
[65/71] Compiling src/shared-library.cpp
[66/71] Compiling src/text-renderer.cpp
[67/71] Compiling src/texture.cpp
[68/71] Compiling doc/glmark2.1.in
[69/71] Linking build/src/libmatrix-gl.a
[70/71] Linking build/src/libcommon-gl.a
[71/71] Linking build/src/glmark2
Waf: Leaving directory `/home/jjj/pj/glmark2/build'
'build' finished successfully (36.123s)
jjj-pc:~/pj/glmark2$
  • 查看生成的 glmark2 可执行文件,并通过 gdb 给 main 函数打断点
// 查找生成的可执行文件位置
jjj-pc:~/pj/glmark2$ find -name glmark2
./android/src/org/linaro/glmark2
./build/src/glmark2
// gdb 可执行文件
jjj-pc:~/pj/glmark2$ gdb ./build/src/glmark2
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./build/src/glmark2...
// 查看源码。说明已经是调试版本
(gdb) list
146     {
147         std::stringstream ss;
148
149         for (int i = 0; i < argc; i++)
150         {
151             if (i > 0) ss << " ";
152             ss << argv[i];
153         }
154
155         return ss.str();
(gdb) b main
Breakpoint 1 at 0x1b670: file ../src/main.cpp, line 160.
(gdb) run
Starting program: /home/jjj/pj/glmark2/build/src/glmark2

Breakpoint 1, main (argc=1, argv=0x7fffffffe388) at ../src/main.cpp:160
160     {
(gdb) n
162         NativeStateX11 native_state;
(gdb) n
175         if (!Options::parse_args(argc, argv))
(gdb) n
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
(gdb) list 179
174
175         if (!Options::parse_args(argc, argv))
176             return 1;
177
178         /* Initialize Log class */
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
180
181         if (!ResultsFile::init(Options::results_file)) {
182             Log::error("%s: Could not initialize results file\n", __FUNCTION__);
183             return 1;
(gdb) list 178
173     #endif
174
175         if (!Options::parse_args(argc, argv))
176             return 1;
177
178         /* Initialize Log class */
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
180
181         if (!ResultsFile::init(Options::results_file)) {
182             Log::error("%s: Could not initialize results file\n", __FUNCTION__);
(gdb) n
80            new_allocator() _GLIBCXX_USE_NOEXCEPT { }
(gdb) n
179         Log::init(Util::appname_from_path(argv[0]), Options::show_debug);
(gdb)

Tip:如果 gdb 对应的可执行文件不是一个调试版本,输入 list 是看不到源码的

gdb 中断

笔者需要排查一个glmark2应用卡住的问题,由于现象是偶现的,所以才有上面编译glmark2调试版本的需求。

打算用gdb运行glmark2调试版本,当glmark2异常时,gdb会中断,然后分析问题。

:如果看起来 glmark2 卡住,但其实并没有卡死,还在好好地跑着,那么这个调试方法就没有用

我们用示例演示一下 glmark2 异常,gdb 会中断。还是上文进程调试的示例:

gdb 运行程序:

jjj-pc:~/pj/dir1$ cat demo.c
#include <stdio.h>
#include <unistd.h>

int main() {
    while (1) {
        sleep(1); // 暂停1秒钟
    }

    return 0;
}
jjj-pc:~/pj/dir1$ gcc demo.c -o demo -g
jjj-pc:~/pj/dir1$ gdb demo
GNU gdb (Ubuntu 9.1-0kylin1) 9.1
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.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from demo...
(gdb) list
1       #include <stdio.h>
2       #include <unistd.h>
3
4       int main() {
5           while (1) {
6               sleep(1); // 暂停1秒钟
7           }
8
9           return 0;
10      }
// 运行程序
(gdb) run
Starting program: /home/jjj/pj/dir1/demo

下面我们用 kill 发送信号模拟程序出错

kill 用于给进程发送信号。它有很多信号,平时常写的 kill -9 111等于kill -s SIGKILL 111,也就是强制中止进程。kill -11 用于向进程发送 SIGSEGV信号的命令,SIGSEGV是一种段错误信号,用于指示进程访问了无效的或不允许访问的内存地址,导致了段错误。

// kill - 给进程发送信号
jjj-pc:~$ whatis kill
kill (1)             - send a signal to a process
// 有这么多信息
// kill -9 111 等于 kill -s SIGKILL 111
jjj-pc:~$ kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
jjj-pc:~$

查看刚才运行程序的进程号,并给该进程发送段错误信息。

jjj-pc:~$ ps aux |grep demo
jjj     2056  0.0  0.0 240000  4744 ?        Sl   6月07   0:00 /usr/libexec/geoclue-2.0/demos/agent
// 我们的程序路径就是 /home/jjj/pj/dir1/
jjj  3119734  0.0  0.0   2356   588 pts/1    S+   19:57   0:00 /home/jjj/pj/dir1/demo
jjj  3119754  0.0  0.0  12120   720 pts/6    S+   19:57   0:00 grep --color=auto demo
// 给进程发送段错误的信号
jjj-pc:~$ kill -11 3119734

gdb 接收到信号,于是中断如下:

Starting program: /home/jjj/pj/dir1/demo

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e991e4 in clock_nanosleep () from /lib/x86_64-linux-gnu/libc.so.6
(gdb)

在使用GDB(GNU调试器)运行一个带有调试信息的程序时,除了程序发生段错误导致GDB中断之外,还有其他一些场景可能导致GDB中断:断点、手动暂停(按下Ctrl+C键)、接收信号(如SIGINT)、异常情况。

07-05 10:20