我,子牙老师,一个手写过操作系统、编程语言、Java虚拟机、docker、Ubuntu系统,玩透Windows内核、Linux内核的...硬核男人
最近一段时间一直在玩调试器,发现好多小伙伴对调试器感兴趣,那就写篇文章,带大家撸一个最简单的调试器,如何?
省流,如果你只想拿到代码自己看,去我个人简介,获取
本文会分享这些:
- gdb能调试Java、Python编写的程序吗?
- gdb能调试Go、Rust编写的程序吗?
- gdb能调试C、C++编写的程序的底层原理
- gdb调试器实现的根基术
- 动手写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版,我才真正满意!
来看下核心代码
不论是技术研究上,还是底层硬核技术的教学上,我还算有点天赋,来看看大家对我课程的评价
真心做教育,践行"子牙出品,必属精品"
如果你想更多了解我,欢迎去我个人简介,获取我之前的文章及我的奋斗历程。白手起家程序员的职场心得,应该会对你有很大启发。