软件调试之硬件断点

硬件断点

1.调试寄存器

IA-32处理器定义了8个调试寄存器,分别为DR0~DR7。在32位模式下,它们是32位的;在64位模式下,它们是64位的。如下图:

DR4、DR5 是保留的。

4个32位调试地址寄存器(DR0~DR3),64位下是64位的。

1个32位调试控制器(DR7),64位时,高32位保留未用。

1个32位的调试状态寄存器(DR6),64位时,高32位保留未用。

最多可以设置4个断点。其分工是DR0~DR3用来指定断点的内存(线性地址)或I/O地址。DR7定义断点的中断条件。当调试事件发生时,DR6向调试器报告事件的详细信息,以供调试器判断发生的是何种事件。

2.调试地址寄存器

设置在内存中间的断点,这个地址是断点的线性地址而不是物理地址,因为CPU是在线性地址被翻译为物理地址之前来做断点匹配工作的。

在保护模式下,不能针对一个物理内存地址设置断点。

3.调试控制寄存器

DR7有24位被划分成4组分别与4个调试地址寄存器相对应,如L0、G0、R/W0、LEN0这6位与DR0相对应,以此类推。

DR7各个位域的含义,如下表:

简称

全称

比特位

描述

R/W0~R/W3

读写域

R/W0:16,17

R/W1:20,21

R/W2:24,25

R/W3:28,29

分别与DR0~DR3这4个调试地址寄存器相对应,

用来指定被监控地址的访问类型,其含义如下。

● 00:仅当执行对应地址的指令时中断

● 01:仅当向对应地址写数据时中断

● 10:386和486不支持此组合。对于以后的CPU,可以通过把CR4寄存器的DE(调试扩展)位设为1启用该组合,其含义为“当向相应地址进行输入输出(即I/O读写)时中断”

● 11:当向相应地址读写数据时都中断,但是从该地址读取指令除外

LEN0~LEN3

长度域

LEN0:18,19

LEN1:22,23

LEN2:26,27

LEN3:30,31

分别与DR0~DR3这4个调试地址寄存器相对应,用来指定要监控的区域长度,其含义如下。

● 00:1字节长

● 01:2字节长

● 10:8字节长(奔腾4或至强CPU)或未定义(其他处理器)

● 11:4字节长 注意:如果对应的R/Wn为0(即执行指令中断),那么这里的设置应该为0,参见下文

L0~L3

局部断点启用

L0:0

L1:2

L2:4

L3:6

分别与DR0~DR3这4个调试地址寄存器相对应,用来启用或禁止对应断点的局部匹配。如果该位设为1,当CPU在当前任务中检测到满足所定义

的断点条件时便中断,并且自动清除此位。如果该位设为0,便禁止此断点

G0~G3

全部断点启用

G0:1

G1:3

G2:5

G3:7

分别对应DR0~DR3这4个调试地址寄存器,用来全局启用和禁止对应的断点。如果该位设为1,当CPU在任何任务中检测到满足所定义的断点条

件时都会中断;如果该位设为0,便禁止此断点。与L0~L3不同,断点条件发生时,CPU不会自动清除此位

LE和GE

启用局部或者全局(精确)断点(Local and  Global (exact)

breakpointEnable)

LE:8

GE:9

从486开始的IA-32处理器都忽略这两位的设置。此前这两位是用来启用或禁止数据断点匹配的。对于早期的处理器,当设置有数据断点时,需要启用本设置,这时CPU会降低执行速度,以监视和保证当有指令要访问符合断点条件的数据时产生调试异常

GD

启用访问检测

(General Detect

Enable)

13

启用或禁止对调试寄存器的保护。当设为1时,如果CPU检测到将修改调试寄存器(DR0~DR7)的指令,CPU会在执行这条指令前产生一个调试异常

读写域R/Wn ,占两个二进制位,可以指定4种访问方式。通过设置读写域,可以指定断点的访问类型。以下是3类典型的读写域使用方式:

  1. 读/写内存中的数据时中断:又称为数据访问断点(data access breakpoint)。利用数据访问断点,可以监控对全局变量或局部变量的读写操作。以WinDBG 为例,对内存区进行写操作时中断,ba w4 00401200,ba 代表break on access, w 表示写(write),4 表示4字节。对内存区进行读操作中断,ba r4 00401200,r 表示读(read)。
  2. 执行内存中的代码时中断:又称为代码访问断点(code access breakpoint)或指令断点(instruction breakpoint)
  3. 读写I/O(输入输出)端口时中断:又称为I/O访问断点(Input/Output access breakpoint)。

LENn(n=0,1,2,3,位于DR7中)位段可以指定1、2、4或8字节长的范围。

代码访问断点,长度域应为00,表示1字节长度。

数据和I/O访问断点,有两点需要注意:

  • 只要断点区域中的任一字节在被访问的范围内,都会触发该断点。
  • 边界对齐要求,2字节区域必须按字(word)边界对齐,4字节区域必须按双字(doubleword)边界对齐,8字节区域必须按4字(quadword)边界对齐。也就是说,CPU在检查断点匹配时会自动去除相应数量的低位。如果地址没有按要求对齐,可能无法实现预期的结果。例如,假设希望通过将DR0设为0xA003、将LEN0设为11(代表4字节长)实现任何对0xA003~0xA006内存区的写操作都会触发断点,那么只有当0xA003被访问时会触发断点,对0xA004、0xA005和0xA006处的内存访问都不会触发断点。因为长度域指定的是4字节,所以CPU在检查地址匹配时,会自动屏蔽起始地址0xA003的低2位,只是匹配0xA000。而0xA004、0xA005和0xA006屏蔽低2位后都是0xA004,所以无法触发断点。

4.指令断点

指令断点说明,代码片段:

MOV SS, EAX

MOV ESP, EBP

如果断点被设置在紧邻MOV SS EAX 的下一行,那么该断点永远不会被触发。原因时为了保护栈寄存器(SS) 和栈顶指针(ESP)的一致性,CPU执行MOV SS 指令时会禁止所有中断和异常,直到执行完下一条指令。

类似的有POP SS 指令的下一条指令处的指令断点也不会被触发。

POP SS

POP ESP

LSS 指令来加载SS和ESP寄存器,通过LSS指令可以改变SS和ESP两个寄存器。

5.调试异常

IA-32架构分配了两个中断向量来支持软件调试,即向量1和向量3。向量3 用于INT 3指令产生的断点异常(breakpoint exception,即#BP)。向量1 用于其他情况的调试异常,简称调试异常(debug exception ,即#DB)。硬件断点产生的是调试异常,CPU会执行1号向量所对应的处理例程。

导致调试异常的各种情况,如下表:

异常情况

DR6标志

DR7标志

异常类型

因为EFlags[TF]=1而导致的单步异常

BS=1

陷阱

调试寄存器DRn和LENn定义的指令断点

Bn=1 and (Gn=1 or

Ln=1)

R/Wn=0

错误

调试寄存器DRn和LENn定义的写数据断点

Bn=1 and (Gn=1 or

Ln=1)

R/Wn=1

陷阱

调试寄存器DRn和LENn定义的I/O读写断点

Bn=1 and (Gn=1 or

Ln=1)

R/Wn=2

陷阱

调试寄存器DRn和LENn定义的数据读(不包括

取指)写断点

Bn=1 and (Gn=1 or

Ln=1)

R/Wn=3

陷阱

当DR7的GD位为1时,企图修改调试寄存器

BD=1

错误

任务状态段(TSS)的T标志为1时进行任务切换

BT=1

陷阱

对于错误类调试异常,因为恢复执行后断点条件仍然存在,所以为了避免反复发生异常,调试软件必须在使用IRETD指令返回重新执行触发异常的指令前将标志寄存器的RF(Resume Flag)位设为1,告诉CPU不要在执行返回后的第一条指令时产生调试异常,则CPU执行完该条指令后会自动清除RF标志。

6.调试状态寄存器

调试状态寄存器(DR6)的作用是当CPU检测到匹配断点条件的断点或有其他调试事件发生时,用来向调试器的断点异常处理程序传递断点异常的详细信息,以便使调试器可以很容易地识别出发生的是什么调试事件。例如,如果B0被置为1,那么就说明满足DR0、LEN0和R/W0所定义条件的断点发生了。

调试状态寄存器(DR6)各个标志位具体含义,如下表:

简称

全称

比特位

描述

B0

Breakpoint 0

0

如果处理器检测到满足断点条件0的情况,那么处理器会在调用异常处理程序前将此位置为1

B1

Breakpoint 1

1

如果处理器检测到满足断点条件1的情况,那么处理器会在调用异常处理程序前将此位置为1

B2

Breakpoint 2

2

如果处理器检测到满足断点条件2的情况,那么处理器会在调用异常处理程序前将此位置为1

B3

Breakpoint 3

3

如果处理器检测到满足断点条件3的情况,那么处理器会在调用异常处理程序前将此位置为1

BD

检测到访问调试寄存器

13

这一位与DR7的GD位相联系,当GD位被置为1,而且CPU发现了要修改调试寄存器(DR0~DR7)的指令时,CPU会停止继续执行这条指令,把BD位设为1,然后把执行权交给调试异常(#DB)处理程序

BS

单步(Single step)

14

这一位与标志寄存器的TF位相联系,如果该位为1,则表示异常是由单步执行(single step)模式触发的。与导致调试异常的其他情况相比,单步情况的优先级最高,因此当此标志为1时,也可能有其他标志也为1

BT

任务切换(Task switch)

15

这一位与任务状态段(TSS)的T标志(调试陷阱标志,debugtrap flag)相联系。当CPU在进行任务切换时,如果发现下一个任务的TSS的T标志为1,则会设置BT位,并中断到调试中断处理程序

7.示例

硬件断点示例加深理解。

编号

地址寄存器

R/Wn

LENn

断点触发条件

0

DR0=A0001H

R/W0=11(读/

写)

LEN0=00(1B)

读写A0001H开始的1字节

1

DR1=A0002H

R/W1=01(写)

LEN1=00(1B)

写A0002H开始的1字节

2

DR2=B0002H

R/W2=11(读/

写)

LEN2=00(2B)

读写B0002H开始的2字节

3

DR3=C0000H

R/W3=01(写)

LEN3=11(4B)

写C0000H开始的4字节

内存访问示例:

访问类型

访问地址

访问长度

触发断点与否

读或写

A0001H

1

触发(与断点0匹配)

读或写

A0001H

2

触发(读与断点0匹配,写与断点0和1都匹配)

A0002H

1

触发(与断点1匹配)

A0002H

2

触发(与断点1匹配)

读或写

B0001H

4

触发(与断点2匹配,对B0002和B0003的访问落入断点2定义的区域)

读或写

B0002H

1

触发(与断点2匹配)

读或写

B0002H

2

触发(与断点2匹配)

C0000H

4

触发(与断点3匹配)

C0001H

2

触发(与断点3匹配)

C0003H

1

触发(与断点3匹配)

读或写

A0000H

1

A0002H

1

否(断点1的访问类型是写)

读或写

A0003H

4

读或写

B0000H

2

C0000H

2

否(断点3的访问类型是写)

读或写

C0004H

4

8. 硬件断点的设置方法

VS 设置硬件断点:

/*---------------------------------------------------------------------
// DataBP.cpp : Demonstrate setting data access breakpoint manually.
Software Debugging by Raymond Zhang, All rights reserved.
---------------------------------------------------------------------*/

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    CONTEXT cxt;
    HANDLE hThread = GetCurrentThread();//获取当前线程的CONTEXT结构,其中包含了线程的通用寄存器和调试寄存器信息
    DWORD dwTestVar = 0;
    //检查当前程序是否正在被调试
    if (!IsDebuggerPresent())
    {//如果不是正在被调试,当断点被触发时导致异常错误
        printf("This sample can only run within a debugger.\n");
        return E_FAIL;
    }

    cxt.ContextFlags = CONTEXT_DEBUG_REGISTERS | CONTEXT_FULL;
    if (!GetThreadContext(hThread, &cxt))
    {
        printf("Failed to get thread context.\n");
        return E_FAIL;
    }

    cxt.Dr0 = (DWORD)&dwTestVar;//将内存地址放入DR0
    cxt.Dr7 = 0xF0001;//4 bytes length read& write breakponits, 设置DR7,F表示4字节读写访问;01 表示启用DR0断点

    if (!SetThreadContext(hThread, &cxt)) //使寄存器设置生效
    {
        printf("Failed to set thread context.\n");
        return E_FAIL;
    }

    dwTestVar = 1;//修改内存数据以触发断点
    GetThreadContext(hThread, &cxt);
    printf("Break into debuger with DR6=%X.\n", cxt.Dr6);
    return S_OK;
}

WinDBG 使用ba命令设置硬件断点,如ba w4 0xabcd, CPU一旦再对内存地址0xabcd开始的4字节范围内的任何字节执行写访问,便会产生调试异常。如果把w4换成r4,那么读写这个内存范围都会触发异常。

参考:软件调试-硬件基础

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值