GDB 实验

发布时间 2023-11-13 11:27:53作者: Ba11ooner

实验一:安装并简单使用 GDB

预备工作

安装
sudo apt install gdb build-essential gcc
配置
必做
# 格式配置
if ! grep -qi 'kernel.core_pattern' /etc/sysctl.conf; then
  sudo sh -c 'echo "kernel.core_pattern=core.%p.%u.%s.%e.%t" >> /etc/sysctl.conf'
  sudo sysctl -p
fi
ulimit -c unlimited

# 限制解除
sudo bash -c "cat << EOF > /etc/security/limits.conf
* soft core unlimited
* hard core unlimited
EOF"

cat /etc/security/limits.conf
# 出现下列提示时证明配置成功
#* soft core unlimited
#* hard core unlimited
选做
# 设置在当前目录下创建 core(当不能在当前目录生成 core 时使用,如果能正常生成 core 则无需额外配置)
sudo bash -c "echo core.%p.%u.%s.%e.%t > /proc/sys/kernel/core_pattern"
cat /proc/sys/kernel/core_pattern

# 解除 core 文件大小限制
ulimit -c unlimited
ulimit -a

# 下列数值为 unlimited 证明配置成功
#core file size              (blocks, -c) unlimited
知识补充:清屏指令
# 与其他 Linux 工具类似
# ^L,即 Ctrl + L 清屏

实验过程

编写测试文件
cd ~
mkdir gdb-test
cd gdb-test
mkdir test-01
cd test-01
vim test.c
int actual_calc(int a, int b){
  int c;
  c=a/b;
  return 0;
}

int calc(){
  int a;
  int b;
  a=13;
  b=0;
  actual_calc(a, b);
  return 0;
}

int main(){
  calc();
  return 0;
}
生成 Core Dump
# 编译,注意一定要有 --ggdb 否则生成的 Core Dump 中不带 GDB 可以分析的错误信息
# 可以缩写为 gcc -g test.c -o test.o
gcc -ggdb test.c -o test.o

# 执行 test.o
/test.o
知识补充:Core Dump
什么是 Core Dump?

Core Dump 指的是在程序发生严重错误时,操作系统将程序运行时的内存状态和其他相关信息保存到文件中的过程。

为什么要引入 Core Dump?

Core Dump 通常包含了程序执行时的内存快照、寄存器状态、堆栈信息等,用来帮助开发人员在发生错误时进行调试和排查问题。

Linux 什么时候由谁生成 Core Dump?

在Linux中,当一个程序因为诸如段错误、内存访问违例等严重错误而崩溃时,操作系统会生成一个Core Dump文件。这个文件通常会被命名为core,保存在程序崩溃的当前工作目录下。

生成Core Dump文件的操作是由操作系统处理的,通常是通过内核提供的相关机制来实现的。在Linux中,可以通过配置ulimit和/proc文件系统中的一些参数来控制Core Dump文件的生成行为。

怎么分析 Core Dump?

分析Core Dump文件通常需要使用调试工具,比如gdb(GNU调试器)。通过加载Core Dump文件到gdb中,可以查看程序崩溃时的堆栈信息、寄存器状态等,并尝试重现并解决问题。

使用 GDB 分析 Core Dump
# 注意参数是 .o 文件 和 Core Dump
gdb ./test.out core.<p>.<u>.<s>.<e>.<t>
Backtracing
# 列举所有帧信息
bt # bt 是 backtracing 的缩写
Frame Inspection
# 查看要分析的帧
f <n> # f 是 frame 的缩写

# 查看帧对应语句的源码附近的源码,默认显示 10 行
list # list 可简写为 l,容易与 1 看混,所以后续不用缩写

# 打印变量 a 的值
p a # p 是 print 的缩写
# 列举帧
bt
#0  0x0000559050d5e13b in actual_calc (a=13, b=0) at test.c:3
#1  0x0000559050d5e171 in calc () at test.c:12
#2  0x0000559050d5e18a in main () at test.c:17
# 查看帧 0 信息
f 0
#0  0x0000559050d5e13b in actual_calc (a=13, b=0) at test.c:3
3	  c=a/b;

list
1	int actual_calc(int a, int b){
2	  int c;
3	  c=a/b;
4	  return 0;
5	}
6	
7	int calc(){
8	  int a;
9	  int b;
10	  a=13;

# 查看帧 1 信息
f 1
#1  0x0000559050d5e171 in calc () at test.c:12
12	  actual_calc(a, b);

list
7	int calc(){
8	  int a;
9	  int b;
10	  a=13;
11	  b=0;
12	  actual_calc(a, b);
13	  return 0;
14	}
15	
16	int main(){

# 查看帧 2 信息
 f 2
#2  0x0000559050d5e18a in main () at test.c:17
17	  calc();

list
12	  actual_calc(a, b);
13	  return 0;
14	}
15	
16	int main(){
17	  calc();
18	  return 0;
19	}

由此可见,帧序号体现调用顺序,帧序号越小,表示其所在的调用层次越深

参考文档

GDB 教程

core 生成

实验二:使用 GDB 进行单步调试

实验过程

编写测试文件
cd ~/gdb-test
mkdir test-02
vim test.c
#include <stdio.h>
int func02(int num){
    int ret = num * num;
    return ret;
}
int func01(int num){
    int i = 1;
    int sum = 0;
    while(i <= num){
        sum += func02(i);
        i++;
    }
    return sum;
}
int main(){
    int num =0;
    scanf("%d", &num);
    int result = func01(num);
    printf("%d", result);
    return 0;
}
编译
gcc -g test.c -o test
GDB 单步调试
进入 GDB
# 进入 gdb
gdb test -q
源码展示

默认一次显示 10 行,重复执行可以显示所有代码

(gdb) l
1	#include <stdio.h>
2	int func02(int num){
3	    int ret = num * num;
4	    return ret;
5	}
6	int func01(int num){
7	    int i = 1;
8	    int sum = 0;
9	    while(i <= num){
10	        sum += func02(i);
(gdb) l
11	        i++;
12	    }
13	    return sum;
14	}
15	int main(){
16	    int num =0;
17	    scanf("%d", &num);
18	    int result = func01(num);
19	    printf("%d", result);
20	    return 0;
(gdb) l
21	}
(gdb) l
Line number 22 out of range; test.c has 21 lines.
# 如果希望重新展示一次,则可以通过以下语句回退 21 行
l -21
完整执行
run
1
1[Inferior 1 (process 8549) exited normally] # 0 + 1*1 = 1

run
2
5[Inferior 1 (process 8550) exited normally] # 0 + 1*1 + 2*2 = 5

run
3
14[Inferior 1 (process 8552) exited normally] # 0 + 1*1 + 2*2 + 3*3 = 14
断点执行
  • next(可简写为n):大步,单个函数被视为一步
  • step(可简写为s):小步,每行代码被视为一步
  • stepi(可简写为si):小小步:每次执行一条机器指令
  • continue(可简写为c):继续执行程序,直到再次遇到断点
  • until(可简写为u):继续运行到指定位置
    u 29: 执行完前 28 行,显示第 29 行

代码整理

1	#include <stdio.h>
2	int func02(int num){
3	    int ret = num * num;
4	    return ret;
5	}
6	int func01(int num){
7	    int i = 1;
8	    int sum = 0;
9	    while(i <= num){
10	        sum += func02(i);
11	        i++;
12	    }
13	    return sum;
14	}
15	int main(){
16	    int num =0;
17	    scanf("%d", &num);
18	    int result = func01(num);
19	    printf("%d", result);
20	    return 0;
21	}
# 设置断点
b 16 # 输入前设置断点,下一步才能输入16
b 19 # 输出前设置断点,下一步才能输出
(gdb) run # run 直接执行完断点所在行的前一行,即执行完前 15 行,显示第 16 行
#Starting program: /home/xxx/gdb-test/test-02/test 3
#[Thread debugging using libthread_db enabled]
#Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
#
#Breakpoint 1, main () at test.c:16
#16	    int num =0;
(gdb) p num
#$1 = 4096 
# 分析:$1 = 4096,num 尚未初始化,证明第 16 行尚未执行

(gdb) next # 执行第 16 行,显示第 17 行
#17	    scanf("%d", &num);
(gdb) p num
#$2 = 0 
# 分析:$2 = 0,num 已经初始化,证明第 16 行已经执行

(gdb) next # 执行第 17 行,要求输入一个数字,此处输入 5,显示第 18 行
5
#18	    int result = func01(num);

(gdb) next # 执行第 18 行,显示第 19 行
#Breakpoint 2, main () at test.c:19
#19	    printf("%d", result);

(gdb) next # 执行第 19 行,显示第 20 行
20	    return 0;

(gdb) next # 执行第 20 行,显示第 21 行
21	}

(gdb) next # 执行第 21 行,显示奇怪信息
#__libc_start_call_main (main=main@entry=0x5555555551df <main>, argc=argc@entry=2, argv=argv@entry=0x7fffffffdfa8) at ../sysdeps/nptl/libc_start_call_main.h:74
#74	../sysdeps/nptl/libc_start_call_main.h: No such file or directory.

(gdb) next # 输出结果
55[Inferior 1 (process 8606) exited normally]

(gdb) next # 本次执行已完成
The program is not being run.

-q 参数说明

gdb test # 显示完整信息
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1
Copyright (C) 2022 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:
<https://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 test...
gdb test -q # -q: 只显示关键信息,introductory 和 copyright 等无用信息不显示
Reading symbols from test...

参考文档

GDB 单步调试

GDB 调试基础

GDB 使用详解