调试器

http://www.pediy.com/kssd/pediy11/111876.html

三种断点分别对应的异常类型及处理:
 
1. 软件断点(INT3):对应的异常是 EXCEPTION_BREAKPOINT,处理方式为恢复代码为原来字节,并将EIP减一,并设置单步以便进入单步后重新设置这个一般断点。
 
2. 硬件断点:对应的异常是 EXCEPTION_SINGLE_STEP,处理方式要看是否为硬件执行断点,如果是硬件执行断点,则先取消断点,设置单步,进入单步后重新设置这个硬件执行断点。
 
3.内存断点:将要设置内存断点的分页属性设置为 PAGE_NOACCESS,则会产生EXCEPTION_ACCESS_VIOLATION 异常。进入异常后先取消断点,设置单步,进入单步后再重新设置这个内存断点。

INT3断点被断下后,首先从断点链表中找到对应的断点信息结构体。如果没有找到,则说明该INT3断点不是用户下的断点,调试器不做处理,交给系统去处理(其他类型的断点触发异常也需要做同样的处理)。如果找到对应的断点,根据断点信息将断下地址处的字节还原为原来的字节,并将被调试进程的EIP减一,因为INT3异常被断下后,被调试进程的EIP已经指向了INT3指令后的下一条指令,所以为了让被调试进程执行本来需要执行的指令,应该让其EIP减1。

1. 创建调试进程后,为了能够让被调试程序断在OEP(程序入口点),我们可以在被调试程序的OEP处下一个一次性INT3断点。
 
2. 在创建调试进程的过程中(程序还没有执行到OEP处),会触发一个ntdll.dll中的INT3,遇到这个断点直接跳出不处理。这个断点在使用微软自己的调试工具WinDbg时会被断下,可以猜测,微软设置这个断点是为了能够在程序到达OEP之前就被断下,方便用户做一些处理(如设置各种断点)。
 
3. 因为INT3断点修改了被调试程序的代码内容,所以在进行反汇编和显示被调试进程内存数据的时候,需要检查碰到的0xCC字节是否是用户所下的INT3断点,如果是需要替换为原来的字节,再做相应的反汇编和显示数据工作。这一点olldbg做的很不错,而有一些国产的调试器好像没有注意到这些小的细节。

硬件断点
|---------------|----------------|
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 |
|---------------|----------------|

 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中的哪一个断点所断下。

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字节。


第四章 多内存断点
 
    内存断点通过修改内存分页的属性,使被调试程序触发内存访问、写入异常而断下。
 
多内存断点的数据关系:
 
    因为我设计的是多内存断点,即在同一个内存分页上可以下多个内存断点,同一个断点也可以跨分页下在几个内存分页上。所以从数据关系上来说断点和内存分页是多对多的关系。因此需要设计三个表:“内存断点信息表”,“内存分页属性表”,以及中间表“内存断点-分页对照表”。在用户下内存断点的时候,首先将断点所跨越的内存分页属性加入到“内存分页属性表”中。然后在中间表“内存断点-分页对照表”中添加内存断点对应的分页信息,一个内存断点对应了几个分页就会添加几条信息。内存断点的信息保存在“断点信息表”中。
三个表的属性字段如下:

内存断点的设置:
 
    内存断点的信息中需要用户输入确定的有:下断点首地址、断点的长度和断点的类型(访问还是写入)。根据用户输入的信息可以组成一个临时的内存断点结构体,然后到内存断点链表中查找是否已经存在同属性的内存断点,如果已经存在则不需要再设置,否则可以设置这个内存断点。
 
    设置内存断点,首先根据断点的首地址和长度可以确定断点所跨越的内存分页,用VirtualQueryEx API获取内存分页的属性,然后将内存分页的属性信息添加到“内存分页表”中(需要注意的是,如果“内存分页表”中已经存在同一内存分页的属性记录了,则不需要再添加重复的记录),同时将断点对应分页的信息添加到“内存断点-分页对照表”中,并设置断点所跨过的每一个内存分页的属性为不可访问(PAGE_NOACCESS)。
 
    这一点和OllyDbg的做法不大一样,OllyDbg设置内存访问断点是将断点所跨分页设置为PAGE_NOACCESS属性,而设置内存写入断点是将断点所跨分页属性设置为PAGE_EXECUTE_READ,而我的做法是不管哪种断点都将断点所跨内存页的属性设置为PAGE_NOACCESS,这样做的问题是会产生多余的异常,好处是设置断点,恢复断点时省去类型的判断。而且出于另外一个考虑,OllyDbg是只能设置一个内存断点的,所以它这样设置合情合理,而我设计的是多内存长度任意的断点。假设出现了用户先在某个分页上下了一个内存写入断点,之后用户又在同一个分页上下了内存访问断点,那么如果按照OllyDbg的方式,先将内存页的属性设置为PAGE_EXECUTE_READ,然后处理后一个内存断点时,将内存页的属性设置为PAGE_NOACCESS。而如果相反,出现了用户先在某个分页上下了一个内存访问断点,之后用户又在同一个分页上下了内存写入断点,内存页的属性首先被改为PAGE_NOACCESS,但不能根据第二个断点将内存页的属性改为PAGE_EXECUTE_READ,否则前一个内存访问断点就失效了。与其因设置不同的属性产生这么多种麻烦的情况,不如牺牲一点效率(多了一些异常的情况),对内存访问和写入断点都将断点所跨过的分页属性设置为PAGE_NOACCESS,再通过断点被断下后,异常记录结构体EXCEPTION_RECORD中的访问标志和断点信息中的类型标志来判断是否命中了用户所下的内存断点。
 
    处理完内存页的属性,将内存页原先属性信息、断点-分页对照信息加入对应链表之后,最后需要将断点信息添加到断点链表中。

内存断点精确命中的判断思路:
 
    根据产生访问异常时,异常的类型是访问还是写入,以及异常访问的地址这两个信息到“断点-分页对照表”中去查找。如果没有找到,则说明此异常不是用户调试所下的内存断点,调试器不予处理。
 
    如果找到,再根据断点序号,到“断点信息表”中查看断点的详细信息。看断点是否准确命中(下断的内存区域,断点的类型:如果是读异常则只命中访问类型断点;如果是写异常,则访问类型、写入类型断点都算命中)。
 
    如果遍历完“断点-分页对照表”,异常访问地址只是在“断点-分页对照表”中找到,但没有精确命中内存断点,则暂时恢复内存页的原属性,并设置单步,进入单步后再恢复该内存页为不可访问。
 
    如果在“断点-分页表”中找到,且精确命中某个断点,则先暂时恢复页属性,设置单步,并等待用户输入。程序运行进入单步后,再设置内存页属性为不可访问。


进入单步异常的原因:
 

1. 用户输入了单步进入的命令,调试器需要设置单步,让被调试程序单步执行。

2. 用户所下的INT3断点被断下后,调试器会暂时恢复INT3断点处的字节为原有的字节,并让被调试线程的EIP减一,为了重新设置这个INT3断点,调试器自己设置了单步。

3. 用户所下的硬件断点被断下时,会触发单步异常。

4. 用户所下的硬件执行断点被断下后,调试器会暂时取消掉该硬件执行断点,以便被调试进程顺利跑下去,为了重新设置该硬件执行断点,调试器自己设置了单步。

5. 用户所下的内存断点被断下后,调试器会暂时恢复产生访问异常的内存分页为原来的属性,以便被调试进程顺利跑下去,为了重新设置该内存分页的属性(以便内存断点继续起作用),调试器自己设置了单步。

爱程序 不爱bug 爱生活 不爱黑眼圈 我和你们一样 我和你们不一样 我不是凡客 我要做geek
原文地址:https://www.cnblogs.com/yifi/p/6483472.html