深入探索GDB调试技巧及其底层实现原理

news2024/12/24 14:24:33

本文分为两个大模块,第一部分记录下本人常用到的GDB的调试方法和技巧,第二部分则尝试分析GDB调试的底层原理。

一、GDB调试

要让程序能被调试,首先得编译成debug版本,当然release版本的也能通过导入符号表来实现调试,目前没试过。

GDB打断点用break命令,一般简写b,断点有多种形式。

1.1 行断点

可以在指定的文件的指定行里打断点,形式是:break 源文件名字 : 行号,比如:

b source.cpp:22

1.2 函数断点

感觉更常用的是函数断点,因为我们在定位问题的时候,往往定位到某个关键函数,该函数可能被多次调用,被调用的位置也很多,那么用行断点就不太方便了,GDB可以给一个函数打上断点,打上断点后,用continue,简写c,程序执行

b source.cpp:22 if i == 12

到函数被调用处就会阻塞,而不需要关注它在哪个文件哪一行被调用了。用法如下:

b func1

当进程阻塞在这之后,我们就可以用step命令,简写s,来进入该函数内部,然后进一步用next或step来跟踪函数里面的代码

1.3 条件断点

在调试一些循环语句中,我们有时候需要观察某自增变量达到一个特定值的时候,代码的行为,这个时候就需要条件变量,比如for循环语句里,我们只想在i == 12的时候观察程序的运行,那么就可以在断点位置后面加上一个触发条件,比如:

b source.cpp:22 if i == 12
那么程序只会在i==12的时候阻塞,在i取其它值的时候,程序可以正常运行

1.4 多线程调试

当程序有多个线程的情况时,某个函数可能会被多个线程调用我们可以先用info threads查看线程编号,然后再限定下哪个线程指定到这里需要阻塞,比如我们指定编号为3的线程:

break source.cpp : 22 thread 3
或
break func1 thread 3

或者我们指定仅运行当前线程,如下:

set scheduler-locking on

on就是打开,off关闭后就是运行所有线程。

注意:一般是用step进入到函数里面,只想跟踪该函数内部的执行时才使用该命令,否则其余情况线程不能切换,可能对调试会造成麻烦。

从语句就可以看出,它的意思就是设置(线程)调度关闭/开启。

因为在大型工程里面,一个函数被多个线程调用,而那些线程调用我们这个目标函数具体做什么事情我们并不关心,我们只需要在当前的线程里,(该函数也可能在一个循环里多次调用,服务器进程经常有这种情况),当前面几次函数执行完还没有达到我们想要的结果时,如果发生了线程切换,那将很麻烦,而限定程序不切换线程,那么一直执行当前这个线程,那就更好定位问题了。

比如调试某基于PG内核的数据库的SQL入口函数的时候,该函数会被十多个线程调用,而我的问题出现在主线程上,所以我需要设置线程不切换。

另外,在多进程情况下(有fork()时),GDB默认模式下,只能调试这个父进程不会跟踪子进程,不过可以设置,命令:set follow-fork-child,这样就会跟踪子进程了

1.5 删除断点和忽略断点

用info break查看断点信息,每个断点都有个编号,当某些断点不需要时,我们可以用delete删除它,,比如删除断点3:

delete 3

也可以将某行代码上的所有断点都清除,clear:

clear source.cpp : 22

如果只是暂时忽略某个断点,还可以设置忽略次数,比如忽略断点3一共12次,ignore:

ignore 3 12

2. next和step

next简写成n,当执行到某一行我们想要继续往下一行代码走时就可以用该命令;

step简写成s,它也是单步执行,与next不同的是1,如果当前代码行是调用了某个函数,那么step会进入该被调用的函数里面,一般比较接近我们的问题相关的代码时,就可以用step进入函数内部,再单步调试。

3. 查看栈帧

在多线程环境下,因为每个线程都有一个栈,所以首先得切换线程,info threads查看线程编号,加入要切换到的线程是3号,那么thread 3即可切换到3号线程。如果前面设置了关闭线程切换,那就不用管。

查看栈帧的命令是backtrace,简写bt。它会依次从栈顶往栈底列出当前线程的栈帧,如下所示,#0即是栈顶,也就是说,当前线程正在执行exec_simple_query()函数,而且我们可以看到该函数被传入的参数的值

3.1回退栈帧

使用up n和down n可以对栈帧进行回退和前进,想改变当前调试的函数时很好用。如当前在栈帧0处,那么 up 5就会切换到栈帧#5处(up叫up但却是往栈底走的,为了不记忆错乱,记成它会走到栈帧序号更大的栈帧),再down 4,那么就到了栈帧#1的地方

需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

4. attach和detach

我们经常需要调试一个已经在运行的进程,一般先用top命令查看其进程号,或者ps -ef | grep 进程名字查看,其中-ef可以把前台、后台的进程都展示出来。

查询到PID之后,就用gdb attach PID调试该进程;注意,调试完该进程后,用detach命令分离被调试进程和gdb,这样该程序将不再受gdb的控制,而gdb也可以继续去attach其它进程。

如果没有detach,那么当我们杀死gdb进程的时候,被调试的进程也会被杀死。

看看GDB的官方文档对detach的描述:

detach When you have finished debugging the attached process, you can use the detach command to release it from GDB control. Detaching the process continues its execution. After the detach command, that process and GDB become completely independent once more, and you are ready to attach another process or start one with run. detach does not repeat if you press RET again after executing the command. If you exit GDB or use the run command while you have an attached process, you kill that process. By default, GDB asks for confirmation if you try to do either of these things; you can control whether or not you need to confirm by using the set confirm command (see section Optional warnings and messages).

5. handle信号处理

GDB在调试进程的时候,可能会受到来自进程的各种信号,这个时候我们需要定义下GDB遇到某种信号时,做某种处理,其语法格式为:

handle 信号类型 处理方式

比如我调试PG内核的时候,就会收到SIGUSR2,这是用户自定义信号,某个进程收到该信号时,默认的处理方式是进程终止,因此当没有在gdb调试前设置针对该信号的处理方式时,输入c后,调试并没有正常进行,而是停了下来,并且打印了一些信息,这个时候就需要使用handle来处理SIGUSR2信号,如下:

handle SIGUSR2 nostop noprint

然后再输入c去continue,就能正常进行调试了。

6. 查看代码

gdb attach 进程之后,执行layout src会出现两个窗口,上方窗口用于看代码,开了两个窗口不能上下切换查看历史命令。

可以切换两个窗口间焦点,用fs next,这样就可以使用上下键查看历史命令了。

7. 查看函数汇编代码

disassemble funcName

8. 内存泄漏

像数据库内核这种代码量庞大的项目,可以用静态代码检测工具去检测内存泄漏。

如果要在中小型项目中用GDB调试的时候去帮助判断是否发生内存泄漏的话,可以给malloc/free或者自己封装的内存申请/释放函数打上断点,并且打印对应的指针的值,可以设置跟踪变量,比如malloc返回的指针p进行跟踪:watch p,因为它如果被释放并且被置空的话,最后是可以看到该变量为0x0的。

还可以在GDB中call一下glibc库函数:malloc_stats()函数可以统计本进程具体的内存使用情况,精确到字节,观察in use bytes 的数值变化。

二、GDB调试原理

GDB能够对程序进行调试,源自于一个系统调用:ptrace

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

第一个参数request 参数指定了我们要使用 ptrace 的什么功能。

2.1 调试一个可执行程序test

用GDB去运行一个程序,比如gdb ./test,或者是先进入gdb,再执行./test运行程序test,第一个参数就是PTRACE_TRACEME,顾名思义,就是“跟踪我”。

参数 pid 表示的是要跟踪进程的 pid, addr 表示要监控的被跟踪子进程的地址。

这个时候,原理就是开启一个GDB进程,然后GDB进程fork出一个子进程,让子进程执行PTRACE_TRACEME,然后子进程再调用execve(),如下图

此时,GDB进程及其子进程就可以读写test进程的指令空间、数据空间、堆栈和寄存器的值。而且gdb进程接管了test进程的所有信号,也就是说系统向test进程发送的所有信号,都被gdb进程接收到。

(其实应该是内核给gdb的子进程发信号,然后该进程给其父进程即GDB进程发信号,父子进程间通信很容易)

2.2 GDB调试一个已经存在的程序即gdb attach原理

我们用gdb attach PID的时候,ptrace第一个参数传入的就是PTRACE_ATTACH,这是父进程调用 attach 到已经运行的子进程中;这个命令会有权限的检查, 普通用户进程不能 attach 到 root 进程中,但一般调试的都是普通用户进程,所以也没遇到过问题。

这个过程就是:运行一个GDB进程,他调用ptrace()尝试去attach附着目标进程test,此时GDB需要给test进程发送一个信号SIGSTOP,要求test停止,这个信号是不能忽略的,然后test进程就进入TASK_STOPED状态,(用top -u 用户名可以看到被gdb attach的进程如果没有continue的话,其进程状态是t,这个就是暂停或被跟踪),然后之后状态是被跟踪状态TASK_TRACED,这个不重要,反正状态都是t,而不是Run。

这个过程的示意图如下

2.3 GDB断点原理

在某行代码处打一个断点,其实就是将该行代码的汇编 (是指令级别!!!)用INT 3中断指令代替,原来的代码被保存到“断点链表”中。

这个是软中断,硬中断是外设给CPU中断,让CPU停下,这个是内核在CPU待执行指令中插入的中断指令 (勘误,CPU执行到int 3中断指令才不会停下,CPU只是个执行指令的机器它不会自己停下,只不过此时执行中断指令,然后CPU被操作系统内核代码占据,也就是进入所谓的CPU的内核态,然后内核会进行补不同进程的调度),所以是软中断。(都是让CPU收到中断指令,只是看是硬件发的还是软件发的)

INT n这种中断指令,CPU执行到这里时,内核调用相应的中断处理程序,对于INT 3,那就是当前进程test停止运行,将CPU交给GDB进程用。

INT 3 是x86系列处理器提供的专门用来支持调试的指令。简单地说,这条指令的目的就是使CPU中断(break)到调试器,以供调试者对执行现场进行各种分析

这里还有个细节,就是运行到中断指令的话,这句指令不是执行完了吗,那我们到断点处,是怎么继续运行该断点处的代码的?

实际上,CPU轮到GDB进程后,GDB会去断点链表里找到原先的汇编指令(源代码也一样),将断点那一行的INT 3又替换回原先的代码,而且让PC指针回退回该行。

所以我们想执行断点处的代码的话,输入指令n,就行了,而不是直接执行断点的下一行。

PC:Program Counter,是通用寄存器,但是有特殊用途,用来指向当前运行指令的下一条指令

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2239001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kubernetes的基本概念

Kubernetes是谷歌以Borg为前身,基于谷歌15年生产环境经验的基础上开源的一个项目,Kubernetes致力于提供跨主机集群的自动部署、扩展、高可用以及运行应用程序容器的平台。 一、资源对象概述 Kubernetes中的基本概念和术语大多是围绕资源对象(Resource Object)来说的,而资…

JavaWeb后端开发案例——苍穹外卖day01

day1遇到问题&#xff1a; 1.前端界面打不开&#xff0c;把nginx.conf文件中localhost:80改成81即可 2.前后端联调时&#xff0c;前端登录没反应&#xff0c;application.yml中默认用的8080端口被占用&#xff0c;就改用了8081端口&#xff0c;修改的时候需要改两个地方&…

(一)<江科大STM32>——软件环境搭建+新建工程步骤

一、软件环境搭建 &#xff08;1&#xff09;安装 Keil5 MDK 文件路径&#xff1a;江科大stm32入门教程资料/Keil5 MDK/MDK524a.EXE&#xff0c;安装即可&#xff0c;路径不能有中文。 &#xff08;2&#xff09;安装器件支持包 文件路径&#xff1a;江科大stm32入门教程资料…

软件开发的各类模型

目录 软件的生命周期 常见开发模型 瀑布模型 螺旋模型 增量模型、迭代模型 敏捷模型 Scrum模型 常见测试模型 V模型 W模型&#xff08;双V模型&#xff09; 软件的生命周期 软件的生命周期包括需求分析&#xff0c;计划&#xff0c;设计&#xff0c;编码&#xff0c;…

ElasticSearch学习笔记一:简单使用

一、前言 该系列的文章用于记录本人从0学习ES的过程&#xff0c;首先会对基本的使用进行讲解。本文默认已经安装了ES单机版本&#xff08;当然后续也会有对应的笔记&#xff09;&#xff0c;且对ES已经有了相对的了解&#xff0c;闲话少叙&#xff0c;书开正文。 二、ES简介 …

C++笔记---异常

1. 异常的概念 1.1 异常和错误 异常通常是指在程序运行中动态出现的非正常情况&#xff0c;这些情况往往是可以预见并可以在不停止程序的情况下动态地进行处理的。 错误通常是指那些会导致程序终止的&#xff0c;无法动态处理的非正常情况。例如&#xff0c;越界访问、栈溢出…

python opencv3

三、图像预处理2 1、图像滤波 为图像滤波通过滤波器得到另一个图像。也就是加深图像之间的间隙&#xff0c;增强视觉效果&#xff1b;也可以模糊化间隙&#xff0c;造成图像的噪点被抹平。 2、卷积核 在深度学习中&#xff0c;卷积核越大&#xff0c;看到的信息越多&#xff0…

ENSP作业——小型园区网

题目 根据上图&#xff0c;可得需求为&#xff1a; 1.配置交换机上的VLAN及IP地址。 2.设置SW1为VLAN 2/3的主根桥&#xff0c;设置SW2为VLAN 20/30的主根桥&#xff0c;且两台交换机互为主备。 3.可以使用super vlan。&#xff08;本次实验中未使用&#xff09; 4.上层通过静…

解决 Vue3、Vite 和 TypeScript 开发环境下跨域的问题,实现前后端数据传递

引言 本文介绍如何在开发环境下解决 Vite 前端&#xff08;端口 3000&#xff09;和后端&#xff08;端口 80&#xff09;之间的跨域问题&#xff1a; 在开发环境中&#xff0c;前端使用的 Vite 端口与后端端口不一致&#xff0c;会产生跨域错误提示&#xff1a; Access to X…

Windows系统中Oracle VM VirtualBox的安装

一.背景 公司安排了师带徒&#xff0c;环境搭建问题一直是初级程序员头疼的事情&#xff0c;我记录一下这些基础的内容&#xff0c;方便初学者。大部分开发者的机器还是windows系统&#xff0c;所以写了怎么安装。 二.版本信息及 操作系统&#xff1a;windows11 家庭版…

uniapp 集成 uview

注意&#xff1a;HBuildX新建项目时必须选择vue2版本&#xff0c;vue3会不支持uview 下载安装方式&#xff1a; uview安装网站&#xff1a;uView2.0重磅发布&#xff0c;利剑出鞘&#xff0c;一统江湖 - DCloud 插件市场 配置&#xff1a; 1.安装sass插件 // 安装sass npm i …

24.11.12 JavaScript2

prompt() confirm() 这些函数 会阻止js解析器(js解析器执行引擎 读取运行js) 执行 不要使用 2history对象 历史记录对象 对应浏览器前进后退按钮 history 在历史记录里 back 前进 forward 后退go 0当前文档 负数 后退n个文档 正数 前进n个文档<!…

STM32cubemx+Proteus仿真和keil5联合调试

前面两步 STM32cubemx生成代码 https://blog.csdn.net/weixin_52733843/article/details/143637304 Proteus新建工程 https://blog.csdn.net/weixin_52733843/article/details/143578853 1 *Proteus仿真联合调试* 在Proteus中&#xff0c;双击STM32F103C6芯片&#xff0c…

信号的解析

信号 1.概念2.接口3.信号产生的过程1.信号的产生1.1信号的产生方式 2.信号的处理3.信号的保存阻塞信号 4.信号集操作函数 1.概念 信号量&#xff08;Semaphore&#xff09;是一个用于多线程或多进程同步的变量。它是操作系统提供的一种同步机制&#xff0c;用于控制多个线程或…

linux-c 使用c语言操作sqlite3数据库-1

一、练习目标 1、目标 1、使用sqlite3_exec执行查询语句&#xff0c;并将查询结果insert到链表中&#xff0c;最后打印链表的内容&#xff1b; 2、使用sqlite3_get_table执行查询语句&#xff0c;并以key&#xff1a;value的方式&#xff0c;打印查询结果。 2、环境准备 2.1、…

软件需求规格书评审报告,系统需求设计申评审,代码和测试过程评审报告,软件各类资质评审资料(word原件)

1.需求规格说明评审报告 2.系统设计评审报告 3.编码与测试评审报告 软件全套资料部分文档清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0…

flink sql同步mysql数据表到mysql

1. 关闭防火墙和selinux systemctl stop firewalld systemctl disable firewalld systemctl status firewalld2.安装java8 yum list java-1.8* yum install java-1.8.0-openjdk* -yjava -version3.下载和部署mysql yum -y install wget wget https://dev.mysql.com/get/Down…

【分布式事务】二、NET8分布式事务实践: DotNetCore.CAP 框架 、 消息队列(RabbitMQ)、 多类型数据库(MySql、MongoDB)

介绍 DotNetCore.CAP简称CAP, [CAP]是一个用来解决微服务或者分布式系统中分布式事务问题的一个开源项目解决方案, 同样可以用来作为 EventBus 使用,CAP 拥有自己的特色,它不要求使用者发送消息或者处理消息的时候实现或者继承任何接口,拥有非常高的灵活性。我们一直坚信…

vue3项目中内嵌vuepress工程两种实现方式

目录 一、示例二、创建vuepress工程三、配置vue项目的打包命令四、 通过iframe嵌套实现过程五、 将vue项目打包&#xff0c;启本地服务运行index.html 一、示例 vue项目&#xff0c;点击用户手册按钮&#xff0c;通过a标签跳转到vuepress框架搭建的页面。点击后者通过路由跳转…

智能座舱多屏项目,中控屏切换语言,后排屏闪黑屏问题

1. 背景 智能座舱多屏项目&#xff0c;中控屏切换语言&#xff0c;后排屏闪黑屏问题 2. 详细分析过程 通过events log查看activity的生命周期&#xff1a;adb shell logcat -b events com.android.rwhvac.view.behind.BehindActivity2 : displayId 2 副屏app com.android.…