从零手写gdb调试器

我,子牙老师,一个手写过操作系统、编程语言、Java虚拟机、docker、Ubuntu系统,玩透Windows内核、Linux内核的...硬核男人

最近一段时间一直在玩调试器,发现好多小伙伴对调试器感兴趣,那就写篇文章,带大家撸一个最简单的调试器,如何?

省流,如果你只想拿到代码自己看,去我个人简介,获取

本文会分享这些:

  1. gdb能调试Java、Python编写的程序吗?
  2. gdb能调试Go、Rust编写的程序吗?
  3. gdb能调试C、C++编写的程序的底层原理
  4. gdb调试器实现的根基术
  5. 动手写gdb调试器

以下,enjoy

关于调试器

调试器大家应该不陌生吧,只要程序出现问题,就会用上。然后就能看到类似这样的画面:程序在断点处停下来,给你显示当前线程的栈、让你能与调试器交互的命令行、查看内存的窗口,还给你展示局部变量的值

调试器我们常用的功能:打断点、放程序运行、单步步入、单步步过,查看内存、查看寄存器、多线程调试、远程调试...

那我问一个问题吧:gdb能调试Java程序吗?你先想想,我后面会给出答案

目前常用的编程语言,或者说未来有前途的,我觉得是这几个:汇编、C、C++、Java、Python、Go、Rust,不同类型的编程语言,调试器的实现思路是不一样的

为什么不一样呢?因为像C、C++、Go、Rust,它们编译是直接生成可以直接在OS上运行的可执行文件,可以用gdb调试。更准确说的话,如果编译生成的可执行文件是原生的CPU指令组成,就可以用gdb去调试。再更准确的说,gdb原生支持C、C++编写的程序,gdb对go、rust编写的程序的调试是有限的,所以调试go推荐用dlv,调试rust推荐用lldb

为什么gdb对go、rust的支持有限呢?ChatGPT的回答。Go、Rust我目前玩得不是很多,我只能贴个答案了

CPU指令,又叫机器码,长啥样呢?看图

为什么Java、Python不能用gdb调试呢?因为它们编译生成的不是CPU原生指令,而是特定虚拟机的字节码。比如Java程序的

这些字节码,gdb都不认识,更别谈调试了。那能不能让gdb支持呢?如果有人愿意去写这个代码,那肯定也是可以的。其实硬要说的话,gdb也能调试Java程序,但是是在Java虚拟机层面,因为Java虚拟机hotspot是用C++编写的

Java的调试器是我见过的编程语言里最多的!

总结说一下:gdb只能调试原生CPU指令的程序,天然支持调试C、C++编写的程序,对Go、Rust编写的程序的调试能力有限,go推荐用dlv,rust推荐用lldb。Java、Python编写的程序,编译生成的是特定虚拟机的字节码,gdb调试不了,需要用专门的调试器才能调试

动手前

倘若现在就让你动手写调试器,你是不是无从下手?所以咱们先来分析一下gdb调试器是如何写出来的。这就要说到一个核心函数ptrace

调试器的功能几乎都是借助这个函数实现的,比如:启动程序调试、attach程序调试、下断点、单步执行代码、读写内存、读写寄存器、调试多线程。能力都体现在第一个参数上

看完你是不是想,那gdb查看所有断点的命令list b,好像ptrace没有啊。对,是没有,因为断点的保存、查看,包括支持条件断点,都是调试器基于ptrace能够下断点这一单一能力拓展实现的。

可以这样说,调试器的诞生,ptrace能力占一半,剩下的一半就是调试器本身的能力。比如查看多线程info threads、切换线程执行thread tid、远程调试、串口调试,都是调试器自身的能力!

怎么样?是不是你的好奇心已经生根了?那咱们就开干吧!

动手

本篇文章的代码,可以去我个人简介,获取

来个简单代码,用我写的调试器去调试它

看运行效果吧

先不看代码,先从这个运行效果中把规律提炼出来。有了规律你就大概知道代码怎么写了,再去看代码,一下就通了!

首先能看出来,启动了两个进程。那你猜一下:哪个是调试进程?哪个是test进程?上图,看你猜的对不对

父进程是调试进程,子进程是被调试进程,所以这个代码大概长这样子

接着看:调试进程(父进程),收到了被调试进程(子进程)发来的5号信号,父进程就停下来了,这里的停,就是你实现命令行让运行控制调试进程的关键

5号信号是什么呢?

什么情况Linux会产生SIGTRAP信号呢?

这里应该是断点指令触发!你是否很奇怪:调试器刚刚把程序启动起来,我们都还没下断点,它就断下来了,这个默认断点在哪呢?等下说

父进程能收到子进程的信号,一定是调用了wait函数,所以debugger_core中的代码大概长这样

这,就是gdb调试器的骨架!是gdb调试器的地基!你使用的gdb调试器的一切强大的功能,都是在这个地基上慢慢盖起来的!

怎么盖?你可以自己找资料学习,也可以学习我的课程《从零手写gdb调试器》。学完这门课程,你不仅对调试器的一切了如指掌:断点的底层、软件断点与硬件断点的控制、读写内存、读写调试器、控制程序运行、调试多线程、远程调试、串口调试、C语言源码级调试......你还能写出一个自己百分百可控的调试器,你还能写出gdb都没有的功能:借助Linux内核驱动窥探Linux内核内部,真正意义上的全能调试器!

第一个断点在哪?

要想知道第一个断点在哪,我们需要一些信息,比如断下来处的内存地址

这个地址在哪呢?查看进程的内存空间,看到了!

这个功能gdb没有,但是我在玩底层的过程中发现很常用,于是我就加到我自己写的调试器中了。所以我认为,每个玩底层的人,拥有一个自己百分百可控的调试器,是一件非常重要的事情!能大大提高你的学习效率及工作效率!

第一个断点在动态链接器ld.so的入口函数处,是gdb自己下的。你是不是想问:为什么在这个地方下断点?我也想问,感觉答案跟没说一样

反正知道第一个断点在ld.so中就行了,至于为什么要在这里下断点,知不知道答案也不重要了!

课程推荐

来看看详细的课程大纲

大纲中的所有功能,我已经全部实现

代码分支与课程节奏一一对应,方便你学习。代码写了4版,我才真正满意!

来看下核心代码

不论是技术研究上,还是底层硬核技术的教学上,我还算有点天赋,来看看大家对我课程的评价

真心做教育,践行"子牙出品,必属精品"

如果你想更多了解我,欢迎去我个人简介,获取我之前的文章及我的奋斗历程。白手起家程序员的职场心得,应该会对你有很大启发。

相关推荐
小莞尔4 小时前
【51单片机】【protues仿真】基于51单片机主从串行通信系统
c语言·单片机·嵌入式硬件·物联网·51单片机
Hello_Embed4 小时前
STM32 环境监测项目笔记(一):DHT11 温湿度传感器原理与驱动实现
c语言·笔记·stm32·单片机·嵌入式软件
逐步前行6 小时前
C标准库--浮点<float.h>
c语言·开发语言
水饺编程7 小时前
第3章,[标签 Win32] :窗口类03,窗口过程函数字段
c语言·c++·windows·visual studio
雨落在了我的手上10 小时前
C语言入门(十):函数的深入认识
c语言
wangjialelele11 小时前
端口号、常见协议和套接字
linux·运维·服务器·c语言·网络
deng-c-f11 小时前
Linux C/C++ 学习日记(26):KCP协议(二):kcp源码分享
c语言·c++·学习·网络编程·kcp
雾岛听蓝11 小时前
深入解析内存中的整数与浮点数存储
c语言·经验分享·笔记·visualstudio
Yupureki11 小时前
从零开始的C++学习生活 9:stack_queue的入门使用和模板进阶
c语言·数据结构·c++·学习·visual studio