我,子牙老师,一个手写过操作系统、编程语言、Java虚拟机、docker、Ubuntu系统,玩透Windows内核、Linux内核的...硬核男人
前两天写了篇文章《从零手写gdb调试器》,就有小伙伴在后台留言

估计有很多小伙伴都有这样的疑惑吧,那就写篇文章细细聊聊
以下,enjoy
程序断下来的秘密
当我们点亮代码前面的红圈,本质是下断点,当CPU运行到那行代码对应的指令,程序就会断下来。感官上就是程序停下来了,有点像Java世界的STW,那一刻,世界仿佛按下了暂停键

有好奇的小伙伴心中就生出了疑问:为什么?奈何调试器相关的资料奇少,所以这个疑问就一直存在着,找不到答案
从gdb的角度,让程序断下来的两个方式:软件断点、硬件断点

软件断点的本质就是将那块内存的一字节位置改为0xCC,0xCC对应的汇编指令是int 3,即3号中断,是一个CPU软中断。是不是没概念,别急,文章后面证明给你看
硬件断点是基于CPU中的8个调试寄存器DR0-DR7实现的。其中DR4、DR5是保留的。DR6为调试状态寄存器,标识哪个断点触发了中断。DR7可以看成是调试寄存器中的控制寄存器,比如开关硬件断点。所以算下来,硬件断点最多只能下四个,即DR0-DR3。是不是没概念,别急,文章后面证明给你看
硬件断点从CPU的角度来说,是一种硬中断,2号中断
学过我的课程《手写操作系统》的都知道这张图。当我们自己写操作系统的时候,我们需要从BIOS手中接管CPU中断异常处理,就需要定义中断向量表。其中0-19是CPU固定死的,需要按这个图来配置

比如除零异常,对应的就是0号异常,这是CPU内部写死的,你如果不把中断向量表中index=0的位置定义成除零异常处理例程,你写的操作系统就乱套了
你是不是想知道操作系统中的中断向量表长啥样,其实没啥神奇的就是一个数组,如图

但是这里面每个value有点复杂,是一个中断门描述符。你说这东西没人教怎么学得会?我是觉得操作系统没人教是真的学不会,我自己也是跟老师学才学会的。有人教学起来还算简单,没人教根本无从学起

CPU触发中断与异常,怎么抛给操作系统,又怎么抛给应用程序,说白了就是这些是怎么串起来的,值得深究。如果你想对CPU的中断与异常了解得更多,想自己亲身实践出来,欢迎去我公众号【硬核子牙】看我之前的文章,详细了解我的课程《手写操作系统》
断点与调试器是如何关联的
以Linux系统为例,不同的OS可能不一样哦。如图

以软件断点为例吧:
-
CPU高兴地执行着指令,执行到0xCC,CPU马上就兴奋了,这不是3号中断吗,要去执行中断处理程序了
-
CPU拿出中断向量表的地址并拿到3号中断的处理程序idt_addr[3]
-
CPU开始执行3号中断处理例程,核心逻辑就是给调试进程发送SIGTRAP信号,即5号信号
-
调试进程通过waitpid拿到SIGTRAP信号,开始进行相关处理:将0xCC还原成下断点前的指令,程序寄存器rip-1(为什么要这样?一两句话讲不清)
-
调试进程放出命令行给用户操作
这就是断点与调试器,在CPU层面、Linux内核层面、应用程序层面、调试器层面关联的秘密!也是所有调试器得以实现的基石!
论证软件断点
接下来开始论证
1、查看main函数的内存地址:0x400542,未下断点之前,这个位置的值是0x55,对应的汇编指令是push rbp

2、下断点,0x55变成了0xCC,对应的汇编指令是int3

3、执行continue,程序在断点处停下来了,0xCC又还原成0x55了

软件断点就是这么玩的!怎么样,写一个调试器玩底层方便吧!快来吧!
论证硬件断点
接下来论证硬件断点
1、先查看DR寄存器

2、查看函数hello的内存地址,并下硬件断点

3、查看DR寄存器是否被修改了,确实修改了

4、执行continue,确实停在硬件断点处了

这就是硬件断点的全部展示
最后再打个小广告,如果你想学习手写操作系统及实战Linux内核,如果你对调试器底层实现感兴趣,同时想写一个自己可以百分百控制的调试器,调试你自己写的操作系统,欢迎去我公众号【硬核子牙】看我之前的文章,了解课程详情。