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

gdb调试常用指令及案例讲解

一、常用指令

运行

-g:使用该参数编译可以执行文件,得到调试表。

编译

# 运行
gdb ./a.out

# 设置参数
set args -s ./data/uvd.tcl

 控制参数

断点

list/l                     :list 1 列出源码。根据源码指定 行号设置断点。
b                          :b 20 在 20 行位置设置断点。
b 文件名:行数
b 10 if i = 6              :条件断点(当 i = 6 时打在第10行),再执行 run
info b                     : 查看断点信息表
delete 1                   : 删除断点1
delete                     : 删除所有断点
enable 1                   : 使能断点1
disable 1                  :禁用断点1
save breakpoints 文件名.txt :保存断点
source 文件名.txt           : 还原断点信息

 退出

ctrl+c:退出输入
c     : 继续执行

 重新启动或运行程序

默认情况下,run 指令会一直执行程序,直到执行结束。如果程序中手动设置有断点,则 run 指令会执行程序至第一个断点处;

run/r: 运行程序/重新运行

查看调用栈

bt: 查看调用栈

单步调试或退出

n/next         : 下一条指令(会越过函数)
s/step         : 下一条指令(会进入函数)
p/print        :p i 查看变量的值。
c/continue     :继续执行断点后续指令。
finish         :结束当前函数调用。
q/quit         :退出 gdb 当前调试。

跳转执行

在gdb调试中,可以回溯到之前代码,或者跳过前面的代码直接执行后面代码

1.通过找到line的地址,设置寄存器

2.在line设置断点,然后 jump 行数

3.通过标签,然后jump 标签

调试跳过指定函数

1.进入函数执行finish或者return,可以结束这个函数

2. 跳过一个文件执行: skip file 文件

 3.跳过一个目录下所有文件 skip -gfi 文件夹/*.*

通过 display 设置跟踪变量

        和 print 命令一样,display 命令也用于调试阶段查看某个变量或表达式的值,它们的区别是,使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动帮我们打印出来,而 print 命令则不会。

        也就是说,使用 1 次 print 命令只能查看 1 次某个变量或表达式的值,而同样使用 1 次 display 命令,每次程序暂停执行时都会自动打印出目标变量或表达式的值。因此,当我们想频繁查看某个变量或表达式的值从而观察它的变化情况时,使用 display 命令可以一劳永逸。​​ 

undisplay:取消设置跟踪变量。 使用跟踪变量的编号

观察点

watch 写观察点: 当观察点背写入时产生中断

rwatch:读观察点: 当观察点的值背读取时产生中断

awatch:读写观察点: 读写观察点

二、死锁或dump

方法1: attach

最近遇到一个程序卡死的问题,借助 gdb 轻松定位,供大家参考。

遇到程序卡死不退处,可能不知道卡死在什么地方,如果程序非常简单,也许 printf 大法就可以很快定位。但是对于大型程序,尤其是一些框架程序,printf 大法可能就力不从心了。

实际的程序很复杂,这里给出一个极简版,一个多线程程序:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

void* pthread_run1()
{
    printf("=== thread 1\n");
    while(1)
    {
        sleep(1);
    }
    return NULL;
}

void* pthread_run2()
{
    printf("=== thread 2\n");
    while(1)
    {
        sleep(1);
        return NULL;
    }
    return NULL;
}


int main()
{

    pthread_t tid1;
    pthread_t tid2;

    pthread_create(&tid1, NULL, pthread_run1, NULL);
    pthread_create(&tid2, NULL, pthread_run2, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}

编译(gcc hello.c -g -pthread)后运行:

xxx:~/code/multithread$ ./a.out 
=== thread 1
=== thread 2

程序卡住不退处,当然我这里的例子使用 ctrl-c 信号可以让程序退出,而我实际的程序是这里会卡死。不过这不是重点,重点是怎么知道程序卡死(或卡住)在哪里呢?当然这个简单的例子,你直接 review 代码就能看出,或者简单加几个 printf 就能定位出卡死的位置。上面也说过这个是极简版,review 和 printf 很难发现问题。这个时候我们就可以借助 gdb 了。

首先,查看当前程序的进程号(pid),使用 ps 命令:ps aux | grep a.out,得到 pid 为 1801781。

xxx 1801781  0.0  0.0  84576   476 pts/1    Sl+  21:24   0:00 ./a.out

然后,启动 gdb,接着 attach 该 pid:

GNU gdb (Ubuntu 9.1-0ubuntu1) 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".
(gdb) attach 1801781
Attaching to process 1801781
[New LWP 1801782]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
__pthread_clockjoin_ex (threadid=140508208416512, thread_return=0x0, clockid=<optimized out>, abstime=<optimized out>, block=<optimized out>) at pthread_join_common.c:145
145	pthread_join_common.c: No such file or directory.
(gdb) up
#1  0x0000564d11e93274 in main () at hello.c:38
38	    pthread_join(tid1, NULL);
(gdb)

可以看到程序卡在源码的 38 行。 卡住的原因是线程 join 的时候等不到线程函数返回。

注意事项:

  • 如果启动 gdb 后 attach pid 没有权限,比如信息如下,则可以使用 sudo gdb。
  • 如果你的程序不是 -g 编译的,只会看到最底层代码位置,这个时候因为没有调试信息,使用 up 命令也无法显示源码。建议编译 debug 版本来定位,可以获取丰富的信息。

方法2:重复查看调用栈,确定在哪里死锁了

Ctrl+C
bt
Ctrl+C
bt

三、多线程

查看线程信息

info thread:查看所有线程信息
thread num :切换线程

线程查找,线程断点 

thread find:查找线程(只能查找地址,id,名字。后面的函数不能)

thread name:设置线程名字(先切换到像改名字的线程,然后执行)

b breakpoint thread id:为线程设置断点(只有设置的线程会在中断处停止,其他线程不会)

为线程执行命令

thread apply 2 3 i args 显示线程2,线程3的参数

thread apply 2-6 i args 显示线程2-6的参数

thread apply all i args显示所有线程的参数

thread apply 2 -s i args对线程2执行 i args命令,如果命令错误就没有提示

thread apply 2 3 -q i args会把线程2,线程3的信息连在一起显示

线程的日志信息控制

线程创建和结束时会在终端打印很多输出,为了不影响观看,于是可以控制

show print thread-events:查看当前线程显示状态

set print thread-events off:关闭当前线程显示状态

四、内存泄漏检测

通过gdb来查看某些函数是否有内存泄漏问题。比较函数进出的内存占用情况来判断

注意事项:在内存中,除了malloc分配地址,本身也还有链表来存储空间也需要内存

call malloc_stats()查看当前的内存分布

call malloc_info(0,stdout)把内存分布情况按照xml格式输出

加入 -fsanitize=address选项,在发生内存泄漏,堆栈溢出,野指针,全局内存会直接定位到具体代码

 五、外部命令和保存文件

在gdb中可以使用shell命令,也可以使用 代替shell

设置保存debug文件:set logging file 名字.txt

保存文件:set logging on

查看文件:!cat 文件.txt 或者 shell cat 文件.txt

六、多进程

调试子进程

默认是父进程调试。如果要设置子进程需要进行设置

set follow-fork-mode child

父进程和子进程一起调试

先设置到子进程,然后

set detach-on-fork off:关闭父进程的detach

i inferiors查看当前进程及其子进程信息

inferior 号码:切换进程号码

多进程调试

inferior就是一个调试对象,就是一个调试进程

首先在新建一个gdb,此时i inferior是空的,执行需要调试的进程,首先add-inferior,然后利用attach 进程号进行添加,然后再重复这个过程

在调试过程中,可以只执行一个进程,或者两个都执行。

show schedule-multiple:显示是否所有进程都执行

set schedule-multiple on:开启所有进程一起执行

detach inferiors 号码:关闭号码调试的进程

remove-inferior:删除一个add之后的inferior(不能删除当前的,只能删除之前的)

七、核心存储core-dump

 为活着的进程生成core-dump文件

1.利用gdb attach 进程号添加进程

2.利用gcore test.core生成文件

 当程序发送段错误时生成core-dump文件

ulimit -c 查看是否允许保留core文件。0为不保存

ulimit -c unlimited 允许生成core文件

修改生成dump文件的名字:sudo echo -e "%e-%p-%t" > /proc/sys/kernel/core_pattern

利用dump文件进行调试

参考:

GDB调试大全-CSDN博客


http://www.kler.cn/a/441968.html

相关文章:

  • Qt QML专栏目录结构
  • 【Django开发】django美多商城项目完整开发4.0第12篇:商品部分,表结构【附代码文档】
  • 数据库高可用方案-01-数据库备份还原方案
  • edge浏览器恢复旧版滚动条
  • 1.6 从 GPT-1 到 GPT-3.5:一路的风云变幻
  • VUE3 vite下的axios跨域
  • 三菱FX系列PLC以太网通讯处理器ModbusTCP通讯
  • 原生JS实现聊天窗口
  • 技术解决方案|复合机器人在cnc行业的上下料
  • 利用CNN与多尺度特征、注意力机制的融合实现低分辨率人脸表情识别,并给出模型介绍与代码实现
  • 【HarmonyOS NEXT】Web 组件的基础用法以及 H5 侧与原生侧的双向数据通讯
  • 校园点餐系统|Java|SSM|JSP|
  • 实景三维在城乡规划、地下管网管理和智慧城市中的应用
  • 单片机 GPIO
  • Qwen文章阅读笔记
  • 问题总结一
  • 深入探索Vue.js中的插值表达式:数据绑定的艺术
  • MapGIS 10.7大数据GIS升级!打造数据要素底座,赋能新质生产力
  • 自签名CA证书
  • 在 Linux 系统中,让 apt 使用 HTTP 代理
  • 文件上传之文件内容检测
  • 项目23:简易网络爬虫 --- 《跟着小王学Python·新手》
  • 突破时间与空间限制的富媒体百宝箱——智能工具箱:让云上内容生产更easy
  • 如何评估并持续优化AI呼入机器人的使用效果
  • ae学习笔记
  • Spring基础分析07-Spring JdbcTemplate