当前位置: 首页 > news >正文

Linux 开发工具(3)

本章我们将学习Linux中的调试工具——gdb。

一.gdb初识

1.gdb基本概念

gdb是一种调试工具,调试这个概念我们在使用vs2022时已经有所耳闻,而gdb是Linux端一款功能强大的调试工具。

问题1:调试是什么?

1.找出问题。gdb并不负责为我们解决问题,但是要解决问题的前提是得发现问提,而我们可以通过打断点的方式把程序分成多块,依次排查问题。

2.发现某个区域有问题,查看上下文。

我们将围绕这两个话题对gdb进行讲解。

2.快速认识gdb

在进行调试之前,我们需要了解一个前置知识:在Linux中,被gcc/g++编译后的文件是release版本的。这里我们用一个求和函数为例。

wujiahao@VM-12-14-ubuntu:~/gdb$ vim mycode.c
wujiahao@VM-12-14-ubuntu:~/gdb$ gcc mycode.c -o mycode
wujiahao@VM-12-14-ubuntu:~/gdb$ readelf -S mycode |grep -i debug
wujiahao@VM-12-14-ubuntu:~/gdb$ 

可以看到当前的可执行文件mycode并不包含debug信息,而程序要调试,就必须以debug模式编译,也就是说——必须包含debug信息才行。所以我们接下来用该指令来生成debug版本的可执行程序。

 gcc mycode.c -o mycode -std=c99 -g

编译之后再查看是否包含debug信息:已经出现。

wujiahao@VM-12-14-ubuntu:~/gdb$ readelf -S mycode |grep -i debug[28] .debug_aranges    PROGBITS         0000000000000000  00003036[29] .debug_info       PROGBITS         0000000000000000  00003066[30] .debug_abbrev     PROGBITS         0000000000000000  000031a1[31] .debug_line       PROGBITS         0000000000000000  00003275[32] .debug_str        PROGBITS         0000000000000000  00003301[33] .debug_line_str   PROGBITS         0000000000000000  000033f7

接着我们一边进行调试一边讲解gdb。

3.更好的gdb

为了更好的体验,我们这里使用cgdb,其各种指令和用法与gdb没有区别,但是它可以将可执行程序和调试信息分开,便于调试。首先我们用以下指令调试可执行程序

cgdb mycode

进入之后的界面如下。如果想退出,可以输入

quit

4.gdb使用

gdb基础指令如下,我们根据实际例子讲解重点。

命令作⽤样例
list/l显⽰源代码,从上次位置开始,每次列出10⾏list/l 10
list/l 函数名列出指定函数的源代码list/l main
list/l ⽂件名:⾏号列出指定⽂件的源代码list/l mycmd.c:1
r/run从程序开始连续执⾏run
n/next单步执⾏,不进⼊函数内部, 逐过程 F10next
s/step单步执⾏,进⼊函数内部, 逐语句 F11step
break/b [⽂件名:]⾏号在指定⾏号设置断点break 10break test.c:10
break/b 函数名在函数开头设置断点break main
info break/b查看当前所有断点的信息info break
finish执⾏到当前函数返回,然后停⽌finish
print/p 表达式打印表达式的值print start+end
p 变量打印指定变量的值p x
set var 变量=值修改变量的值set var i=10
continue/c从当前位置开始连续执⾏程序continue
删除所有断点delete breakpoints
删除序号为n的断点delete breakpoints 1
disable breakpoints禁⽤所有断点disable breakpoints
enable breakpoints启⽤所有断点enable breakpoints
info/i breakpoints查看当前设置的断点列表info breakpoints
display 变量名跟踪显⽰指定变量的值(每次停⽌时)display x
undisplay 编号取消对指定编号的变量的跟踪显⽰undisplay 1
until X⾏号执⾏到指定⾏号until 20
backtrace/bt查看当前执⾏栈的各级函数调⽤及参数backtrace
info/i locals查看当前栈帧的局部变量值info locals
quit退出GDB调试器quit

我们可以顺手把mycode.c的Makefile写好。

mycode:mycode.c2     gcc -o $@ $^ -std=c99 -g3 .PHONY:clean4 clean:5     rm -f mycode        

mycode.c的内容如下。

wujiahao@VM-12-14-ubuntu:~/gdb$ cat mycode.c
#include <stdio.h>int Sum(int s, int e)
{int result = 0;for(int i = s; i <= e; i++){result += i;}return result;
} int main()
{int start = 1;int end = 100;printf("I will begin\n");int n = Sum(start, end);printf("running done, result is: [%d-%d]=%d\n", start, end, n);return 0;
}

1.打断点

b +filename +n:在filename函数的第n行打断点,这里是在20行处打断点

b +filename +funcname:在filename函数的funcname函数处打断点

我们可以使用指令查看当前存在的断点信息。

info b

Make breakpoint pending on future shared library load? (y or [n]) b 20   
Please answer y or [n].
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (mycode 20) pending.
Make breakpoint pending on future shared library load? (y or [n]) b main   
Please answer y or [n].
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 2 (mycode.c main) pending.
(gdb) info b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   <PENDING>  mycode 20
2       breakpoint     keep y   <PENDING>  mycode.c main

2.删除断点

d +Num

这里需要注意,其实对断点的操作,除了创建断点之外使用行号,其余大部分都是使用“断点编号”。

(gdb) d 1
(gdb) d 2
(gdb) info b
No breakpoints or watchpoints.

3.开始调试

next/n:逐过程,跳过函数内部——我们可以理解为黑盒测试

step/s:逐语句,进入函数内部——我们可以理解为白盒测试

只要断点打好,只要输入r就会再次回到这个断点处。

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/wujiahao/gdb/mycode
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
I will beginBreakpoint 4, main () at mycode.c:19
19          int n = Sum(start, end);

bt查看调用栈

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
I will beginBreakpoint 4, main () at mycode.c:19
19          int n = Sum(start, end);
(gdb) bt
#0  main () at mycode.c:19

现在的程序都是以main函数为入口调用其他函数的。

finish执行到当前函数的返回并停止。

这里我们可以观察一个现象:对于Sum函数,局部变量result如何把值返回给外部的其他变量?

通过寄存器返回的。寄存器在cpu中,寄存器不可被引用不可被修改,这也就是为什么它是有常性的。

我们可以反汇编拿到这个可执行程序的汇编代码。

objdump -S mycode>mycode.s

可以明显看到有一个存放到寄存器的操作。也就是说,在main中的这个语句做了两件事:

调用Sum函数+将寄存器的返回值存到变量n中。

int n=Sum(start,end)

我们可以观察临时变量的值,使用以下指令。

p name

(gdb) n
I will begin
19          int n = Sum(start, end);
(gdb) p n
$3 = 21845
(gdb) nBreakpoint 1, main () at mycode.c:20
20          printf("running done, result is: [%d-%d]=%d\n", start, end, n);
(gdb) p n
$4 = 5050

可以看到,变量n在接收Sum的返回值之前一直是存储着随机值的状态。当执行到20行,n拿到寄存器中的值,变为我们期望的结果:5050.

4.补充知识

断点可以被使能。我们可以把已存在的断点像开关一样打开或关闭。

disable+Num

例如这里我们对第16行代码的断点关闭,可以看到断点信息中Num2的断点Enb选项变为了n

(gdb) info b 
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555551d8 in main at mycode.c:20breakpoint already hit 1 time
2       breakpoint     keep y   0x00005555555551a9 in main at mycode.c:16breakpoint already hit 1 time
(gdb) disable 2
(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555551d8 in main at mycode.c:20breakpoint already hit 1 time
2       breakpoint     keep n   0x00005555555551a9 in main at mycode.c:16breakpoint already hit 1 time

而在被调试的代码中可以看的更清楚:开着的断点为红色,关着的断点为黄色。

5.如何调试,如何理解调试

找出问题。打断点会把我们的代码分块,如果在运行过程中没有问题,说明问题不在当前块中。所以可以直接运行到下个断点处(continue)进行进一步排查。

finish—->确认问题是否在函数内

until line ——>将当前代码逻辑跑完,直接跳转到line行,局部快速执行

2.发现某个区域有问题,查看上下文

这里我们假设要根据start和end设置这个flag值的正负,但是不慎设置为0,我们要通过调试发现问题在哪。首先跟这个代码逻辑相关肯定在调用这个函数的地方,可以现在第20行打断点。

(gdb) b 14
Breakpoint 1 at 0x1198: file mycode.c, line 14.
(gdb) b 20
Breakpoint 2 at 0x11b7: file mycode.c, line 20.

然后我们可以使用display进行变量监控。

display name

(gdb) b 22
Breakpoint 1 at 0x11cd: file mycode.c, line 22.
(gdb) r
Starting program: /home/wujiahao/gdb/mycode
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
I will beginBreakpoint 1, main () at mycode.c:22
22          int n = Sum(start, end);
(gdb) s
Sum (s=1, e=100) at mycode.c:8
8           int result = 0;(gdb) n
9           for(int i = s; i <= e; i++)
1: i = 1
2: result = 1

不想用这个监控的话可以根据编号

undisplay Num

我们接着说。

在循环里执行代码都没有发现问题,我们直接until+line跳转到当前函数的line处。

(gdb) until 14
Sum (s=1, e=100) at mycode.c:14
14             return result*flag;
2: result = 5050

此时临时变量i已经销毁,但我们发现:result事实上显示的是正确的绝对值。

我们继续运行。

$2 = 21845
(gdb) n
23          printf("running done, result is: [%d-%d]=%d\n", start, end, n);
(gdb) n   
running done, result is: [1-100]=0
24          return 0;

奇怪的是,一退出Sum函数,结果就立马变成0了,我们合理怀疑嫌疑人是flag。

(gdb) p flag
$3 = 0

果然,就是因为flag导致我们的输出结果为0。

tips:可以通过info locals查看一个函数内所有的临时变量。

二.调试技巧

1.watch:执行时监视一个表达式或变量的值。

可以看到watch的本质是创建了一个watchpoint。

(gdb) watch result
Hardware watchpoint 2: result(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555551cd in main at mycode.c:22breakpoint already hit 1 time
2       hw watchpoint  keep y                      result

tips:如果你有一些变量不想被修改,但是你怀疑是因为它修改引起了问题,你就可以watch它,如果发生变化gdb会通知你。

2.set var:在调试过程中遇到的需要修改的变量。例如我们这里把可能有错的flag修改为1,修改之后结果正确。

(gdb) set var flag=1
(gdb) p flag
$6 = 1
(gdb) n
23          printf("running done, result is: [%d-%d]=%d\n", start, end, n);
(gdb) n
running done, result is: [1-100]=5050
24          return 0;

3.条件断点

如下面的第11行断点,只有i的值为10时才会在此中断。等i增加到大于10时,断点不会再被触发,会直接结束函数执行。

(gdb) b 11 if i==10
Breakpoint 3 at 0x555555555186: file mycode.c, line 11.
(gdb) n
9           for(int i = s; i <= e; i++)
1: i = 10
2: result = 55
(gdb) n
11             result += i;

除此之外,我们也可以给已有的普通断点添加条件。

condition Num ....

tips:cgdb分屏操作:按ESC进入代码屏,i回到gdb屏。

http://www.dtcms.com/a/390196.html

相关文章:

  • Hive 运行
  • PPT中为图片添加透明渐变的蒙版
  • 数字签名过程中的消息摘要和加密的作用
  • Unity物理系统笔记
  • 区分同步(Synchronous)和异步(Asynchronous)
  • 隐语开源隐私计算SecretFlow,实测性能提升10倍,纵向联邦SecureBoost算法(已开源)
  • 云南食品安全管理员考试都考哪些知识点
  • AAAI2025 | 视觉语言模型 | 西电等提出少样本语言驱动多模态分类模型DiffCLIP
  • Coze(扣子)零基础开发02-建一个简单机器人
  • 混合架构(SpringCloud+Dubbo)的整合方案与适用场景(三)
  • SPI 通信协议
  • vue3学习日记(十六):路由配置详解
  • 河南省 ERA5 日值气象数据处理教程(2020–2025)
  • 继承和多态常见面试问题解析
  • 博士生如何进行文献阅读和文献整理?
  • 矩阵分析线性表示例题
  • OpenEuler---jumpserver堡垒机部署
  • STM32 驱动 MAX31865 读取 PT100 温度方案
  • 第四次编程记录
  • 2024年7月 自旋散射效应
  • 理解神经网络中的批量数据处理:维度、矩阵乘法与广播机制
  • UDP传输大数据?真的能兼顾速度和可靠性吗?
  • 某税网登录逆向-sm2-HMacSHA256-sm4-滑块
  • HashMap 添加元素put()的源码和扩容方法resize()的源码解析
  • Windows系统如何查看SSH公钥?
  • 苹果软件代码混淆与多框架应用加固 iOS混淆、ipa文件安全、跨端应用安全防护全流程指南
  • 第一章 神经网络的复习:神经网络的推理
  • MinIO 4 节点集群部署实战:RPM 安装 + mc 工具攻略(网站托管、自动备份)
  • 支持向量机 SVM 预测人脸数据集时数据是否标准化的对比差异
  • 学习笔记:Vue 透传