0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

GDB调试原理是什么?

璟琰乀 来源:IOT物联网小镇 作者:IOT物联网小镇 2020-12-18 15:17 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

一、前言这篇文章来聊聊大名鼎鼎的GDB,它的豪门背景咱就不提了,和它的兄弟GCC一样是含着金钥匙出生的,在GNU的家族中的地位不可撼动。相信每位嵌入式开发工程师都使用过gdb来调试程序,如果你说没有用过,那只能说明你的开发经历还不够坎坷,还需要继续被 BUG吊打。

我们都知道,在使用gcc编译时,可以使用-g选项在可执行文件中嵌入更多的调试信息,那么具体嵌入了哪些调试信息?这些调试信息是如何与二进制的指令之间进行相互交互?在调试的时候,调试信息中是如何获取函数调用栈中的上下文信息?

针对上面这些疑惑,道哥用两篇文章把这些底层最深处的问题彻底描述清楚,让你一次看过瘾。

第一篇文章,就是当前这一篇,主要内容是介绍GDB的底层调试原理,我们来看一下GDB是通过什么机制来控制被调试程序的执行顺序。

第二篇文章,我们选择一个体积小巧、五脏俱全的LUA语言来进行剖析,从源代码分析到函数调用栈,从指令集到调试库的修改,一网打尽。

内容比较多,看完本文需要的时间可能长一些,为了您的健康,不建议在处于蹲姿的时候阅读这篇文章。

二、GDB调试模型GDB调试包括2个程序:gdb程序和被调试程序。根据这2个程序是否运行在同一台电脑中,可以把GDB的调试模型分为2种:

本地调试

远程调试

本地调试:调试程序和被调试程序运行在同一台电脑中。

远程调试:调试程序运行在一台电脑中,被调试程序运行在另一台电脑中。

关于可视化调试程序并不是重点,它只是一个用来封装GDB的外壳而已。我们既可以用黑乎乎的终端窗口来手动输入调试命令;也可以选择集成开发环境(IDE),这个IDE中已经嵌入了器调试,这样就可以使用各种button来代替手动输入调试命令了。

与本地调试相比,远程调试中多了一个GdbServer程序,它和目标程序都是运行在目标机中,可能是一台x86电脑或者是一个ARM板子。图中的红线表示GDB与GdbServer之间通过网络或者串口进行通讯。既然是通讯,那么肯定需要一套通讯协议:RSP协议,全称是:GDB Remote Serial Protocol(GDB远程通信协议)。

关于通讯协议的具体格式和内容,我们不需要关心,只需要知道:它们都是字符串,有固定的开始字符(‘$’)和结束字符(‘#’),最后还有两个十六进制的ASCII字符作为校验和,了解这么多就足够了。至于更多的细节,如果实在闲的XX可以瞄几眼,其实这些协议,就像社会中各种奇葩的规定一样,都是一帮砖家在厕所里想出来的。

aUNFNf.png

在第二篇讲解LUA的文章中,我们会实现一个类似的远程调试原型。其中的通信协议也是字符串,直接把 HTTP 协议进行简化之后就拿过来使用了,十分清晰、方便。

三、GDB调试指令为了完整性,这里把部分GDB调试指令贴一下,有感性认识即可。

YFRvuu.png

另外,这里没有列举所有的指令,列出的指令都是常用的,比较容易理解。在讲解LUA的时候,我们会选择其中的某些指令进行详细的对比,包括底层的实现机制。

每一个调试指令都有很多的命令选项,例如断点相关的就包括:设置断点、删除断点、条件断点、临时停用启用等等。这篇文章的重点是理解gdb底层的调试机制,所以应用层的这些指令的使用方法就不再列出了,网络上的资源很多。

四、GDB与被调试程序之间的关系为了方便描述,先写一个最最简单的C程序:

#include 《stdio.h》int main(int argc, char *argv[]){ int a = 1; int b = 2; int c = a + b; printf(“c = %d ”, c); return 0;}

编译命令:

$ gcc -g test.c -o test

我们对可执行程序 test 进行调试,输入命令:

$ gdb 。/test

输出如下:

在最后一行可以看到光标在闪烁,这是gdb程序在等着我们给它下达调试命令呢。

当上面这个黑乎乎的终端窗口在执行gdb 。/test的时候,在操作系统里发生了很多复杂的事情:

系统首先会启动gdb进程,这个进程会调用系统函数fork()来创建一个子进程,这个子进程做两件事情:

调用系统函数ptrace(PTRACE_TRACEME,[其他参数]);

通过execc来加载、执行可执行程序test,那么test程序就在这个子进程中开始执行了。

fI3qu2.png

补充一点:文中有时称之程序,有时称之进程。“程序”描述的是一个静态的概念,就是一堆数据躺着硬盘上,而“进程”描述的是动态的过程,是这个程序被读取、加载到内存上之后,在操作系统中有一个任务控制块(一个数据结构),专门用来管理这个进程的。

铺垫了半天,终于轮到主角登场了,那就是系统调用函数ptrace(其中的参数后面会解释),正是在它的帮助下,gdb才拥有了强大的调试能力。函数原型是:

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

我们先来看一下 man 中对这个函数的简介:

eIjMny.png

tracer就是调试程序,可以理解为gdb程序;tracee就是被调试程序,对应于图中的目标程序test。一般喜欢用-er和-ee来表示主动和被动的关系,例如:employer就是雇主(老板),employee就是苦逼的被雇佣者(打工人)。

ptrace系统函数是Linux内核提供的一个用于进程跟踪的系统调用,通过它,一个进程(gdb)可以读写另外一个进程(test)的指令空间、数据空间、堆栈和寄存器的值。而且gdb进程接管了test进程的所有信号,也就是说系统向test进程发送的所有信号,都被gdb进程接收到,这样一来,test进程的执行就被gdb控制了,从而达到调试的目的。

也就是说,如果没有gdb调试,操作系统与目标进程之间是直接交互的;如果使用gdb来调试程序,那么操作系统发送给目标进程的信号就会被gdb截获,gdb根据信号的属性来决定:在继续运行目标程序时是否把当前截获的信号转交给目标程序,如此一来,目标程序就在gdb发来的信号指挥下进行相应的动作。

EVVz2i.png

五、GDB如何调试已经执行的服务进程是否有小伙伴会提出这样一个疑问:上面被调试的程序test是从头开始执行的,是否可以用gdb来调试一个已经处于执行中的服务进程呢?答曰:可以。这就涉及到ptrace系统函数的第一个参数了,这个参数是一个枚举类型的值,其中重要的是2个:PTRACE_TRACEME和PTRACE_ATTACH《。

在上面的讲解中,子进程在调用ptrace系统函数时使用的参数是PTRACE_TRACEME,注意橙色文字:是子进程调用ptrace,相当于子进程对操作系统说:gdb进程是我的爸爸,以后你有任何想发给我的信号,请直接发给gdb进程吧!

如果想对一个已经执行的进程B进行调试,那么就要在gdb这个父进程中调用ptrace(PTRACE_ATTACH,[其他参数]),此时,gdb进程会attach(绑定)到已经执行的进程B,gdb把进程B收养成为自己的子进程,而子进程B的行为等同于它进行了一次 PTRACE_TRACEME操作。此时gdb进程会发送SIGSTO信号给子进程B,子进程B接收到SIGSTOP信号后,就会暂停执行进入TASK_STOPED状态,表示自己准备好被调试了。

RjMvmm.png

所以,不论是调试一个新程序,还是调试一个已经处于执行中状态的服务程序,通过ptrace系统调用,最终的结果都是:gdb程序是父进程,被调试程序是子进程,子进程的所有信号都被父进程gdb来接管,并且父进程gdb可查看、修改子进程的内部信息,包括:堆栈、寄存器等。

关于绑定,有几个限制需要了解一下:不予许自我绑定,不允许多次绑定到同一个进程,不允许绑定1号进程。

六、偷窥GDB如何实现断点指令大道理已经讲完了,这里我们通过设置断点(break)这个调试指令,来偷窥一下 gdb 内部的调试机制。还是以上面的代码为例子,这里再重新贴一下代码:

#include 《stdio.h》int main(int argc, char *argv[]){ int a = 1; int b = 2; int c = a + b; printf(“c = %d ”, c); return 0;}

来看一下编译出来的反汇编代码是什么样的,编译指令:

gcc -S test.c; cat test.S)

uuM7r2.png

这里只贴了一部分反汇编代码,只要能说明底层的原理就达到我们的目的了。

上面说到,在执行gdb 。/test之后,gdb就会fork出一个子进程,这个子进程首先调用ptrace然后执test程序,这样就准备好调试环境了。

我们把源码和汇编代码放在一起,方便理解:

yEz6ny.png

在调试窗口输入设置断点指令“break 5”,此时gdb做2件事情:

对第5行源码所对应的第10行汇编代码存储到断点链表中。

在汇编代码的第10行,插入中断指令INT3,也就是说:汇编代码中的第10行被替换为INT3。

EruU3e.png

然后,在调试窗口继续输入执行指令“run”(一直执行,直到遇到断点就暂停),汇编代码中PC指针(一个内部指针,指向即将执行的那行代码)执行第10行时,发现是INT3指令,于是操作系统就发送一个SIGTRAP信号给test进程。

此刻,第10行汇编代码被执行过了,PC指针就指向第11行了。

eeIrQv.png

上面已经说过,操作系统发给test的任何信号,都被gdb接管了,也就是说gdb会首先接收到这SIGTRAP个信号,gdb发现当前汇编代码执行的是第10行,于是到断点链表中查找,发现链表中存储了第10行的代码,说明第10行被设置了断点。于是gdb又做了2个操作:

把汇编代码中的第10行“INT3”替换为断点链表中原来的代码。

2. 把 PC 指针回退一步,也即是设置为指向第10 行。

然后,gdb继续等待用户的调试指令。

3Mvuaa.png

此刻,就相当于下一条执行的指令是汇编代码中的第10行,也就是源码中的第5行。从我们调试者角度看,就是被调试程序在第5行断点处暂停了下来,此时我们可以继续输入其他调试指令来debug,比如:查看变量值、查看堆栈信息、修改局部变量的值等等。

七、偷窥GDB如何实现单步指令next还是以刚才的源代码和汇编代码为例,假设此时程序停止在源码的第6行,即汇编代码的第11行:

e26bae.png

在调试窗口输入单步执行指令next,我们的目的是执行一行代码,也就是把源码中第6行代码执行完,然后停止在第7行。gdb在接收到next执行时,会计算出第7行源码,应该对应到汇编代码的第14行,于是gdb就控制汇编代码中的PC指针一直执行,直到第13行执行结束,也就是PC指向第14行时,就停止下来,然后继续等待用户输入调试指令。

八、总结通过break和next这2个调试指令,我们已经明白了gdb中是如何处理调试指令。当然,gdb中的调试指令还有很多,包括更复杂的获取堆栈信息、修改变量的值等等,有兴趣的小伙伴可以继续深入跟踪。

后面我在写LUA语言中的调试库时,会更深入、详细的讨论这个问题,毕竟LUA语言更小巧、简单。我也会把LUA代码中如何设置PC指针的代码部分给小伙伴演示一下,这样我们对于一门编程语言的内部实现就会有更好的理解和掌握,也可能会录制一个视频,这样就能更好的讲解LUA语言中的内部细节。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 程序
    +关注

    关注

    117

    文章

    3836

    浏览量

    84762
  • 编译器
    +关注

    关注

    1

    文章

    1669

    浏览量

    51081
  • gdb
    gdb
    +关注

    关注

    0

    文章

    61

    浏览量

    13816
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    ElfBoard技术贴|如何在【RK3588】ELF 2开发板进行GDB调试

    GDB(GNU Project Debugger)是在Linux环境下功能全面的调试工具。它提供了一系列强大的调试功能,包括设置断点、单步执行、打印与观察变量、查看寄存器及堆栈信息等。在Linux软件开发的实践中,
    的头像 发表于 11-11 13:47 4919次阅读
    ElfBoard技术贴|如何在【RK3588】ELF 2开发板进行<b class='flag-5'>GDB</b><b class='flag-5'>调试</b>

    蜂鸟的GDB调试时无法在断点处停止是什么原因?

    各位大佬们,我们使用GDB调试程序的时候,在main函数打上断点,然后在gdb中continue,程序并没有在main函数入口处停下?请问大佬知道原因吗?谢谢~
    发表于 11-07 06:15

    RV-STAR无法连接到GDB server的解决办法

    demo跑一跑,现在卡在了“下载程序到开发板”这一步,connect to gdb server失败,使用的是板载调试器。请问这是什么情况? 具体信息如下: 1. 我已经安装
    发表于 11-07 06:15

    在qemu上体验芯来RISC-V处理器运行鸿蒙LiteOS-M内核

    在qemu上体验芯来RISC-V处理器运行鸿蒙LiteOS-M内核 1.本文概述 2.下载qemu 3.下载鸿蒙LiteOS-M 4.运行与测试 5.gdb调试 1.本文概述 由于前几天
    发表于 10-31 09:04

    RVMCU课堂「6」: 手把手教你玩转RVSTAR—QEMU篇

    模式的界面如下 IDE提供了丰富的调试工具,具体的就不一一介绍了,大家可以去尝试。 虽然是在QEMU仿真环境下,但应用的还是gdb调试工具,所以也是支持gdb
    发表于 10-31 08:54

    VScode调试qemu-vexpress-a9工程,GDB出错怎么解决?

    按官方教程编译了,也修改了qemu-dbg.bat文件。启动调试后qemu窗口闪了一下,然后vscode弹窗表示不行。 有小伙伴遇到过吗?
    发表于 09-28 08:17

    【干货分享】RT-Trace国产调试工具 | 技术集结

    专为嵌入式开发者打造的一款高性能、多功能的调试工具。它集成了实时系统跟踪、数据监控、GDB远程调试、Flash程序烧录、虚拟终端等多种功能于一体,支持通过WebU
    的头像 发表于 09-16 08:07 571次阅读
    【干货分享】RT-Trace国产<b class='flag-5'>调试</b>工具 | 技术集结

    【老法师】多核异构处理器中M核程序的启动、编写和仿真

    有很多研究单片机的小伙伴在面对多核异构处理器时,可能会对多核的启动流程感到困惑——因为不熟悉GCC编程和GDB调试,所以也无法确定多核异构处理器的程序是否能像单片机那样方便地编写和仿真。本篇
    的头像 发表于 08-13 09:05 3635次阅读
    【老法师】多核异构处理器中M核程序的启动、编写和仿真

    国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—GDB程序调试方法说明

    (Linux) GDB工具是GNU项目调试器,基于命令行使用。和其他的调试器一样,可使用GDB工具单步运行程序、单步执行、跳入/跳出函数、设置断点、查看变量等等,它是UNIX/LINU
    的头像 发表于 08-05 15:33 334次阅读
    国产!全志T113-i 双核Cortex-A7@1.2GHz 工业开发板—<b class='flag-5'>GDB</b>程序<b class='flag-5'>调试</b>方法说明

    PSoC 6 BLE PIONEER KIT烧录Bluetooth_LE_Battery_Server的demo进行调试时,无法成功烧录的原因?

    3334 端口监听 gdb 连接 信息:SWD DPIDR 0x6ba02477 信息:kitprog3:获取设备(模式:重置)... [psoc6.cpu.cm0] 由于调试请求而停止,当前模式:线程
    发表于 07-01 07:37

    硬件调试:JLink 驱动配置与调试技巧

    摘要: 本文深入探讨了 JLink 调试器在嵌入式系统硬件调试中的应用,详细阐述了 JLink 驱动配置的方法以及硬件调试技巧。本文以国科安芯的AS32系列MCU芯片为例,通过分析 JLink
    的头像 发表于 06-12 23:20 1267次阅读
    硬件<b class='flag-5'>调试</b>:JLink 驱动配置与<b class='flag-5'>调试</b>技巧

    有办法让SDK 1.3.5与JTAG调试器配合使用吗?

    使用 Segger J- 6EDL_SPI_LINK GDB 服务器的调试配置。 我看到控制台中打印了多个错误,如下所示: 827^error,msg=\"Dwarf Error:无法找到从
    发表于 05-23 07:32

    使用OpenOCD调试DEMO_FX3_U3V_CAM01时出错,是哪里的原因?

    我正试图用 OCD 调试演示板,但出现了以下错误 Error in services launch sequence GDB prompt not read我按照《EzUsbSuite 用户指南》第 3 章的要求进行操作。 你知道我做错了什么吗?
    发表于 05-21 07:07

    STM32CubeIDE无法启动正常调试是哪里出了问题?

    STM32CubeIDE始终正常使用中,昨日系统升级为 macOS Sequoia 版本,启动调试出现错误提示,重新安装应用程序及 st-stlink-server 驱动,仍提示同样错误。 目前
    发表于 03-14 16:30

    为什么会报错Could not determine GDB version using command: arm-none-eabi-gdb --version?

    我已经重启电脑了,没用; 重装软件没有,没有; 将ST_link_server卸载重装也没有; 将arm-none-eabi-gdb加入windows 11系统环境变量,也没用; 将软件的GDB
    发表于 03-12 08:08