[调试器实现]第三章 硬件断点

一 硬件断点介绍
 
     硬件断点,顾名思义是由硬件提供给我们的调试寄存器组,我们可以对这些硬件寄存器设置相应的值,然后让硬件帮我们断在需要下断点的地址。
 
     硬件断点是CPU提供的功能,所以要怎么做就得听CPU的硬件寄存器的了。先来看看关于硬件寄存器的说明。Intel 80386以上的CPU 提供了调试寄存器以用于软件调试。386和486拥有6个(另外两个保留)调试寄存器:Dr0,Dr1,Dr2,Dr3,Dr6和Dr7。这些寄存器均是 32位的,如下图所示(该图来源于看雪文章《调试寄存器(DRx)理论与实践》(http://www.pediy.com/bbshtml/BBS6 /pediy6751.htm),在此对文章作者Hume/冷雨飘心表示感谢):
 
|---------------|----------------|
Dr0| 用于一般断点的线性地址 
|---------------|----------------|
Dr1| 用于一般断点的线性地址 
|---------------|----------------|
Dr2| 用于一般断点的线性地址 
|---------------|----------------|
Dr3| 用于一般断点的线性地址 
|---------------|----------------|
Dr4| 保留 
|---------------|----------------|
Dr5| 保留 
|---------------|----------------|
Dr6| |BBB BBB B |
| |TSD 3 2 1 0 |
|---------------|----------------|
Dr7|RWE LEN ... RWE LEN | G GLGLGLGLGL |
| 3 3 ... 0 0 | D E E 3 3 2 21 100 |
|---------------|----------------|
31 15 0
 
     Dr0~3用于设置硬件断点,由于只有4个断点寄存器,所以最多只能设置4个硬件调试断点,产生的异常是 STATUS_SINGLE_STEP(单步异常) 。Dr7是一些控制位,用于控制断点的方式,Dr6用于显示是哪个硬件调试寄存器引起的断点,如果是 Dr0~3或单步(EFLAGS的TF)的话,则相对应的位会置一。 
即如果是Dr0引起的断点,则Dr6的第0位被置1,如果是Dr1引起的断点,则Dr6的第1位被置1,依此类推。因为硬件断点同时只会触发一个,所以 Dr6的低4位最多只有一个位被置1,所以在进入单步后,我们可以通过检测Dr6的低4位是否有值为1的位,来判断当前进入单步的原因是否是因为硬件断点 被断下。如果是因为硬件断点被断下,也可以通过判断Dr6的低4位中哪一位是1,来进一步判断是被Dr0到dr3中的哪一个断点所断下。
 
调试控制寄存器Dr7比较重要,其32位结构如下:
http://bbs.pediy.com/attachment.php?attachmentid=42527&stc=1&d=1272723631
1. 位0 L0和位1 G0:用于控制Dr0是全局断点还是局部断点,如果G0置位则是全局断点,L0置位则是局部断点。G1L1~G3L3用于控制D1~Dr3,其功能同上。
 
2. LE和GE:P6 family和之后的IA32处理器都不支持这两位。当设置时,使得处理器会检测触发数据断点的精确的指令。当其中一个被设置的 时候,处理器会放慢执行速度,这样当命令执行的时候可以通知这些数据断点。建议在设置数据断点时需要设置其中一个。切换任务时LE会被清除而GE不会被清 除。为了兼容性,Intel建议使用精确断点时把LE和GE都设置为1。 
 
3. LEN0到LEN3:指定调试地址寄存器DR0到DR3对应断点所下断的长度。如果R/Wx位为0(表示执行断点),则LENx位也必须为0(表示1字节),否则会产生不确定的行为。LEN0到LEN3其可能的取值如下:
(1)00 1字节
(2)01 2字节
(3)10 保留
(4)11 4字节
 
4. R/W0到R/W3:指定各个断点的触发条件。它们对应于DR0到DR3中的地址以及DR6中的4个断点条件标志。可能的取值如下:
(1) 00 只执行
(2) 01 写入数据断点
(3) 10 I/O端口断点(只用于pentium+,需设置CR4的DE位,DE是CR4的第3位 )
(4) 11 读或写数据断点
 
5. GD位:用于保护DRx,如果GD位为1,则对Drx的任何访问都会导致进入1号调试陷阱(int 1)。即IDT的对应入口,这样可以保证调试器在必要的时候完全控制Drx。 
 
二 设置硬件断点
 
     通过上面的介绍,我们知道设置一个硬件断点一般只需要以下几个步骤。
(1) 在Dr0到Dr3中找一个可用的寄存器,将其值填写为要断下的地址。
(2) 设置Dr7对应的GX或LX位为1。(例如断点设置在Dr0上则设置Dr7的G0或L0位为1)。
(3) 设置Dr7对应的断点类型位(R/W0到R/W3其中之一)为执行、写入或访问类型。
(4) 设置Dr7对应的断点长度位(LEN0到LEN3其中之一)为1、2或4字节。
 
 
三 处理硬件断点
 
     在硬件断点的介绍中已经说过,硬件断点被断下后将触发单步异常,因此在进入单步异常后,我们检测Dr6的低4位是否有值为1的位,就可以判断是否是因为硬件断点被断下,以及进一步判断是被Dr0到Dr3中的哪一个断点所断下。
硬件断点有三种类型,硬件执行断点、硬件访问断点和硬件写入断点。硬件断点被断下后,所断下的位置(也就是程序的EIP值)会因为断点的类型不同而有差 异。对于硬件执行断点,会断在所下断点地址指令处,也就是EIP的值和断点设置的值一样,下断点的指令还没有被执行。而对于硬件访问、写入断点,会断在所 下断点地址指令的下一条指令处,也就是EIP的值已经是断点指令后的下一条指令的地址了,下断点地址处的指令已经被执行了。
 
     究其原因是因为硬件执行断点只需要查看EIP的值就可以判断是否命中硬件执行断点了,所以在指令执行前就可以断下,而硬件访问、写入断点是需要 在CPU拿到完整指令代码并译码完毕之后才能判断是否命中了硬件访问、写入断点的。此时EIP已经指向了下一条指令,又因为intel的cpu指令是变长 指令集,所以不易倒推实际触发硬件访问、写入断点的指令地址,所以intel对硬件访问、写入断点的处理是停在触发异常指令后的下一条指令处(这一段是我 本人的理解,如有不对之处,请读者多多指教)。
由于不同类型的硬件断点触发异常的情况不同,所以要区别对待。对于硬件执行断点,触发异常断点被断下后,要先暂时取消硬件执行断点,然后设置单步,到下一 次的单步中进行硬件断点的恢复工作。对于硬件访问、写入断点则不需要做多余的处理,断下后显示一下断点信息,并等待用户操作就可以了。
 
     因为硬件断点的设计比较死板,照着intel手册的说明一步步来就可以了。对Dr7的操作也就是一些位操作。我的代码里面是一个大大的SWITCH里面套了4个小SWITCH来做的,显得拖堂冗长、很不好看,所以就不放上来了。
 
硬件断点设计需要注意的几点如下:
 
1. 设置硬件断点的时候也要检查是否重复设置了。
 
2. 硬件执行断点被断下后,此时需要暂时取消掉该硬件执行断点(否则程序一直被断在这里,跑不下去)。并设置单步,在下一次单步中重设该硬件执行断点。
 
3. 如果硬件执行断点被断下之后,此时用户执行了取消该断点的操作,则不需要在下一次的单步中恢复该断点的设置了(这一点同样适用于INT3断点和内存断点)。
 
 
本系列文章参考书目、资料如下:
1.《加密与解密3》 编著:段钢
2.《调试寄存器(DRx)理论与实践》 作者:Hume/冷雨飘心
3.《数据结构》 作者:严蔚敏

上传的图像
1.JPG
posted @ 2015-05-10 10:36  银河彼岸  阅读(2742)  评论(0编辑  收藏  举报