玩转gdb调试工具(上)

[复制链接]
查看1006 | 回复9 | 2023-9-6 17:28:01 | 显示全部楼层 |阅读模式

安装gdb

安装:

apt install gdb

查看版本:

gdb --version

image-20230330131514838

gdb的使用

我们将以下面的程序为例进行gdb的学习:

#include <stdio.h>
int main ()
{
    unsigned long long int n, sum;
    n = 1;
    sum = 0;
    while (n <= 100)
    {
        sum = sum + n;
        n = n + 1;
    }
    return 0;
}

gdb调试器是在程序运行过程中工作的,因此首先首先要将需要调试的程序编译成一个可执行程序:

gcc main.c -o main.out -Og

image-20230330132701722

不同于gcc中学到的编译生成可执行文件的过程,gdb需要的可执行文件还需要进行调试处理。所以上面的编译语句中新增了两个编译选项

O :大写的O,表示gcc对编译过程进行优化,去除一些比如没有用到的变量之类的代码。这个选项分为五个等级,其中O0 表示不优化,O1~O4 优化力度逐渐增强。but我们常用的是O,表示力度在0-1之间,即保证对程序做了优化,又能不影响效率。

g : 表示生成适合gdb的可执行文件。

启动gdb调试

在上面已经生成了一个可执行文件,接下来就直接启动gdb调试:

gdb main.out

直接执行上面的命令就可以进入gdb调试工具中,但是进入之前会打印一些gdb相关信息,这个可以使用--silent 将这些信息关掉

image-20230330134747653

gdb工具提供了相当多的调试命令,先了解一些常用的命令:

调试指令 作 用
(gdb) break xxx<br />(gdb) b xxx 在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置。
(gdb) run<br />(gdb) r 执行被调试的程序,其会自动在第一个断点处暂停执行。
(gdb) continue<br />(gdb) c 当程序在某一断点处停止运行后,使用该指令可以继续执行,直至遇到下一个断点或者程序结束。
(gdb) next<br />(gdb) n 令程序一行代码一行代码的执行。
(gdb) print xxx<br />(gdb) p xxx 打印指定变量的值,其中 xxx 指的就是某一变量名。
(gdb) list<br />(gdb) l 显示源程序代码的内容,包括各行代码所在的行号。
(gdb) quit<br />(gdb) q 终止调试。

想起了第一次使用vim编辑器的困惑,所以先重点说说怎么终止调试。quit 或者q

image-20230330134815250

在gdb工具中输入quit回车,即可终止调试。

然后再正式的来看gdb调试的常见手段:

list 或者 l

显示源代码内容,带行号

list list命令一次性打印10行,,类似于more命令,继续按enter键到下一行。

image-20230330135009714

break 或者 b

设置断点,程序运行至断点处停止运行,只是暂停,并没有终止运行代码

image-20230330135331591

run 或者 r

执行正在被调试的程序,会在第一个断点处停止

image-20230330135353301

print 或者 p

打印指定变量的值

image-20230330135535630

continue 或者 c

然后可以设置下一个断点,这里设置12,然后使用continue命令继续执行代码,程序会在12行的断点处停止执行

image-20230330135825218

接下来再次打印变量n的值,会发现经过循环,n的值已经改变了:

image-20230330135907301

这样子,一次简单的调试就结束了,然后按执行quit退出调试:会问你是不是要退出调试,是的!

image-20230330140013750

这就是一次简单的gdb调试,也是以后用gdb最多的命令了。

调用gdb的方式

上面的例子中已经调用过一次了,它直接调用c程序的可执行文件,命令如下:

gdb main.out --silent

这只是调用gdb调试的其中一种方式,常用的还有:进程调用、core文件调用,还有一种方式是直接执行gdb命令调用,当然这种方式调用因为没有可调式的文件,调用之后需要显示的指定相关的file,这个后面细说。

进程调用

进程调用也就是通过一个正在执行的程序去调用gdb,需要提供相关的进程号!所以第一个问题就是,怎么获取正在执行的程序的进程号?写一个代码看看。

#include <stdio.h>
int main () {
    int i = 0;
    while (1) {
        i++;
    }
    return 1;
}
pid的获取
pidof <filename>

image-20230330141713299

可以看到demo.out 的pid为389104

然后我们通过这个进程号来调用gdb:

gdb -p 389104

image-20230330141910497

注意,当 GDB 调试器成功连接到指定进程上时,程序执行会暂停。如上所示,程序暂停至第 6 行代码 num++ 的位置,此时可以通过断点调试、逐步运行等方式监控程序的执行过程。例如:

先查看一下当前执行的代码前后逻辑,可以使用list或者l

image-20230330142132959

然后设置个断点 break 5 继续执行continue 然后打印一下相关变量的值print i

image-20230330142402412

当前变量的值就蛮有趣的,明明是i++,现在却是一个负数!为什么呢?

然后要退出当前调试的话,怎么办?分两步:

  1. 执行 detach 指令,使 GDB 调试器和程序分离;

    image-20230330142653641

  2. 执行 quit(或 q)指令,退出 GDB 调试。

core文件调用

也就是程序执行发生了异常崩溃,这种情况常见与空指针、内存泄漏、野指针等情况。对于linux系统和Android系统都可以使用core dump来调试。core dump是内核的一种转储机制,当程序发生异常的时候,就将发生异常时候的寄存器信息,内存信息都保存下来。

core dump功能

不过此功能在Android的user版本和一般用户的linux发行版当中都是没有打开的,可以通过ulimit -a 指令来看系统是否打开:

image-20230330144128169

产生core dump文件

手动写一个野指针的例子看一下:

#include <stdio.h>
int main() {
    char *p = NULL;
    *p = 123;

    return 0;
}

编译gcc -g main.c -o main.out 执行./main.out 之后产生段错误,并提示core文件已转储。段错误又称为访问权限冲突,指的是当前程序访问了不可访问的存储空间,比如访问的不存在的空间,又或者是受系统保护的内存空间。

image-20230330144335553

就在同级目录下:

image-20230330144625782

使用core调用gdb
gdb main.out core

image-20230330144803888

会明确告诉你哪里出了问题。

直接使用gdb命令

效果如此:

image-20230330145138671

但我们明确的知道现在我们没有绑定任何可执行程序。所以我们需要这么做:

file <file path>

image-20230330145331787

然后就可以进行一些常规的debug了!

gdb调试器中启动程序

接着上面的动作看,我们通过gdb命令直接进入gdb调试器,然后通过file命令指定了要调试的可执行文件。然后怎么开始运行?gdb中有两种常用的开始运行命令,分别是run和start:

  • run:开始运行,直到遇到第一个断点

  • start:开始运行,进入main函数之后,第一条语句前停止!相当于在第一条语句出设置了断点

    image-20230330145914419

因为这个程序存在错误,我们不往下执行了,只需要输入kill 便可结束当前运行的程序!

image-20230330150220582

指定参数

众所周知main函数是可以指定参数的,当某个可执行程序需要指定参数执行的时候就需要:

gdb main.out --args arg1 arg2 ...

写个程序来看看吧:

#include <stdio.h>
int print_args(char *str1, char *str2) {
    printf("%s\n", str1);
    printf("%s\n", str2);

    return 0;
}
int main(int argc, char *argv[]) {
    print_args(argv[0], argv[1]);

    return 0;
}

显然这个函数需要给main函数多传一个参数才能执行,所以:

gdb --args demo.out klelee

image-20230330152043476

或许还有别的方法指定参数?如果我是通过gdb命令进来的呢?啊,对了,确实有!通过下面的命令可以再gdb中指定参数:

set args <args list>

相关应用如下:

image-20230330152304565

小秘密:gdb下可以用cd命令哦

image-20230330152427524

回复

使用道具 举报

可乐klelee | 2023-9-6 17:29:07 | 显示全部楼层
今天太忙了,只能随随便便发一篇了
回复 支持 反对

使用道具 举报

jkernet | 2023-9-6 19:10:18 来自手机 | 显示全部楼层
学习打卡
回复

使用道具 举报

ai_mcu | 2023-9-6 19:38:47 | 显示全部楼层
可乐klelee 发表于 2023-9-6 17:29
今天太忙了,只能随随便便发一篇了

随便发的质量都这么高
明天总会更好
回复 支持 反对

使用道具 举报

Ject | 2023-9-6 22:51:32 | 显示全部楼层
随便发的质量都这么高
回复 支持 反对

使用道具 举报

爱笑 | 2023-9-7 08:24:43 | 显示全部楼层
随便发的质量都这么高
用心做好保姆工作
回复 支持 反对

使用道具 举报

开发板 | 2023-9-7 08:52:06 | 显示全部楼层
随便发的质量都这么高
回复 支持 反对

使用道具 举报

粉色小风扇 | 2023-9-7 09:10:01 | 显示全部楼层
随便发的质量都这么高
回复 支持 反对

使用道具 举报

496199544 | 2023-9-7 09:11:32 来自手机 | 显示全部楼层
随便发的质量都这么好
回复 支持 反对

使用道具 举报

iiv | 2023-9-10 10:28:06 | 显示全部楼层
点赞~
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则