调试寄存器 原理与使用:DR0-DR7

下面介绍的知识性信息来自intel IA-32手册(可以在intel的开发手册或者官方网站查到),提示和补充来自学习调试器实现时的总结。

希望能给你带去有用的信息。

(DRx对应任意的一个 调试 寄存 器。LENn对应任意一个长度。Ln对应任意一个局部置位)

DR0-DR7可以直接被读写操作(MOV 指令之类的,DRx可以是源操作数也可以是目的操作数)

但是,DRx的访问是需要一定权限的。比如你用MOV操作的话,你需要在实地址模式,系统管理模式(smm)或者在保护模式(CPL设0).如果权限不够,将会在访问DRx的时候尝产生#GP(general-protection)异常

现在来看看DRx可以干些什么?

1.设置发生断点的地址(线性地址)
2.设置断点的长度(1,2,4个字节,但是执行断点只能是1)
3.设置在 调试异常产生的地址执行的操作
4.设置断点是否可用
5.在 调试异常产生时, 调试条件是否是可用

(以上直接翻译自"Intel 64 and IA-32 Architectures Software Developer’s Manual" volume 3。
以下来自个人的总结。当然,也是参考intel官方资料得来的)

我们来看看 调试 寄存 器的一些细节信息。
下图很重要,后面的介绍都是针对这个图说的。
(当然不是我画的,是来自intel  ia-32系统结构开发手册18章2节)。

调试 寄存 器 DR0-DR3
   这四个 寄存 器是用来设置 断点地址的。断点的比对在物理地址转换前(异常产生时,还没有将线性地址转换成物理地址)。由于只有0-3四个保存地址的 寄存 器,所以,硬件断点,在物理上最多只能有4个。
调试 寄存 器DR4-DR5 
   这两个 调试 寄存 器有CR4的DE标记控制。如果DE置位,那么对这两个 寄存 器的访问会导致#UD异常。如果DE置0,那么他们就被化名为DR6-DR7(你一定会问原来的DR6-DR7怎么办?这个…… 我也不知道。如果你搞明白了,一定记得告诉我)
调试 寄存 器DR7(控制 寄存 器)
   (先介绍DR7对DR6的理解有好处。)

DR7是 调试控制 寄存 器。控制方式嘛!继续看:
1.  L0-L3(由第0,2,4,6位控制):对应DR0-DR3,设置断点作用范围,如果被置位,那么将只对当前任务有效。每次异常后,Lx都被清零。
2.  G0-G3(由第1,3,5,7位控制):对应DR0-DR3,如果置位,那么所有的任务都有效。每次异常后不会被清零。以确保对所有任务有效。但是,不知道为什么,我在测试时:
设置Gn后,不能返回 调试异常给 调试 器(如果你知道为什么,记得告诉我)
3.  LE,GE(由第8,9位控制):这个在P6以下系列CPU上不被支持,在升级版的系列里面:如果被置位,那么cpu将会追踪精确的数据断点。LE是局部的,GE是全局的。(到底什么算精确的,我也不清楚,但是,我知道如果设置了这两个,cpu的速度会降低。我在测试中,都没有置位。)
4.  GD(由第13位控制):如果置位,追踪下一条指令是否会访问 调试 寄存 器。如果是,产生异常。在下面的DR6里面,你会知道他还和另外一个标志位有点关系。
5.  R/W0-R/W3:(由第16,17,20,21,24,25,28,29位控制):这个东西的处理有两种情况。
如果CR4的DE被置位,那么,他们按照下面的规则处理问题:
00:执行断点
01:数据写入断点
10:I/0读写断点
11:读写断点,读取指令不算
如果DE置0,那么问题会这样处理:
00:执行断点
01:数据写入断点
10:未定义
11:数据读写断点,读取指令不算
6.  LEN0-LEN3:(由第18.19.22.23.26.27.30位控制):指定内存操作的大小。
00:1字节(执行断点只能是1字节长)
01:2字节
10:未定义或者是8字节(和cpu的系列有关系)
11:4字节
调试 寄存 器DR6( 调试状态 寄存 器)
   这个 寄存 器主要是在 调试异常产生后,报告产生 调试异常的相关信息
1.  B0-B3(DR0-DR3):DRx指定的断点在满足DR7指定的条件下,产生异常。那么Bx就置位。但是,有时,即使Ln和Gn置0,也可能产生Bx被置位。这种现象可能这样出现(提示:在p6系列处理 器,REP MOVS在不断循环中产生的 调试异常需要执行完了才能准确返回给 调试进程):DR0的L0,G0都置0(DR0就是一个不能产生异常的断点了),然后在DR0指定的地址是一个REP指令的循环,这样,DR0就可能在这个循环之后的REP指令产生的 调试异常中将B0置位
2.  BD:BD需要DR7的GD置位,才有效。BD是在下一条指令要访问到某一个 调试 寄存 器的时候,被置位的。
3.  BS:单步执行模式时,被置位。单步执行是最高权限的 调试异常。
4.  BT:在任务切换的时候,被置位。但是必须在被切换去的任务的TSS段里面的T标记被置位的情况下才有效。在控制权被切换过去后,在执行指令前,返回 调试异常。但是,需要注意,如果 调试程序是一个任务,那么T标记的设置肯定就冲突了。然后,导致了死循环(BT的这些信息都是按照官方资料翻译而来,由于没有实际的操作,肯定会有理解上的出入。如果要深入的话,建议看官方资料)
  
有些 调试异常会将B0-B3清零。但是其他的DR6的位是不能被产生异常的进程清零的。每次 调试异常返回后, 调试进程都会先将DR6清零,再按照情况设置。以免产生不必要的错误。

对齐问题和64位处理 器
对齐问题:
这个问题是来源于LENn的设置,如果设置4字节,那么必须4字节对齐。例如:我们下4字节的断点,那么DRx需要是A0000/A0004/A0008这样的地址上。I/O地址是零扩展的(这个……也许意味着必须完全对齐)。因为,intel在比对地址时:用LENn的值去覆盖DRx里面保存的地址的低位。你可以想到,不对齐会有什么后果了吧。注意:执行断点只能是1字节。

再用图片解释下(当然,图片来自intel官方资料):


在64位处理 器下:
调试 寄存 器当然也是64位的。在操作过程中,写入,前面32位被置零。读取:只返回后32位。MOV DRx操作,前32位被忽略。
DR6-DR7的高32位被保留。置零。如果置位,会产生#GP异常。8字节的读写断点完全被支持。

最后,还是给个图片(64位处理 器的布局):

最后需要提醒一个小问题:数据写入断点设置后。是在原数据被修改后,才产生调试异常。所以,返回异常时,原有数据已经被修改。如果想保留原有数据,需要自己提前保存对应地址的数据。

VC利用调试寄存器实现硬件断点,处理断点异常

/************************************************************************
SetHardWareBP:
设置线程硬件断点
hThread:  线程句柄
dwAddr:    断点地址
dwDrIndex:  硬件寄存器(0~3)
nType:    断点类型(0:执行,1:读取,2:写入)
nLen:    读写断点数据长度(1,2,4)
/************************************************************************/BOOL SetHardWareBP(HANDLE hThread,DWORD dwAddr,DWORD dwDrIndex=0,UINT nType=0,UINT nLen=1)
{BOOL bResult=FALSE;CONTEXT context = {0};context.ContextFlags=CONTEXT_DEBUG_REGISTERS;if(::GetThreadContext(hThread,&context)){DWORD dwDrFlags=context.Dr7;//将断点地址复制进入对应Dr寄存器(参考CONTEXT结构)memcpy(((BYTE *)&context)+4+dwDrIndex*4,&dwAddr,4);  //决定使用哪个寄存器dwDrFlags|=(DWORD)0x1<<(2*dwDrIndex);//见OD读写断点时 这个置位了,执行没有(置位也正常-_-)dwDrFlags|=0x100;//先将对应寄存器对应4个控制位清零(先或,再异或,还有其它好方法吗) =.= 悲催的小学生dwDrFlags|=(DWORD)0xF<<(16+4*dwDrIndex);dwDrFlags^=(DWORD)0xF<<(16+4*dwDrIndex);//设置断点类型,执行:00 读取:11 写入:01//(不知何故,测试时发现不论是11还是01,读写数据时均会断下来)if (nType==1)    dwDrFlags|=(DWORD)0x3<<(16+4*dwDrIndex);  //读取else if(nType==2)  dwDrFlags|=(DWORD)0x1<<(16+4*dwDrIndex);  //写入//else if(nType==0) //dwDrFlags=dwDrFlags            //执行//设置读写断点时数据长度if (nType!=0){if(nLen==2 && dwAddr%2==0)      dwDrFlags|=(DWORD)0x1<<(18+4*dwDrIndex);  //2字节else if(nLen==4  && dwAddr%4==0)  dwDrFlags|=(DWORD)0x3<<(18+4*dwDrIndex);  //4字节}context.Dr7=dwDrFlags;if (::SetThreadContext(hThread,&context)) bResult=TRUE;}return bResult;
}
//异常处理
//直接从工程中拷出来的
typedef ULONG (WINAPI *pfnRtlDispatchException)(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext);
static pfnRtlDispatchException m_fnRtlDispatchException=NULL;BOOL RtlDispatchException(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext);ULONG WINAPI CSysHook::_RtlDispatchException( PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext )
{if(RtlDispatchException(pExcptRec,pContext)) return 1;return m_fnRtlDispatchException(pExcptRec,pContext);
}//Hook程序异常处理,当程序发生异常时,由ring0转回ring3时调用的第一个函数:KiUserExceptionDispatcher
BOOL CSysHook::HookSystemSEH()
{BOOL bResult=FALSE;BYTE *pAddr=(BYTE *)::GetProcAddress(::GetModuleHandleA("ntdll"),"KiUserExceptionDispatcher");if (pAddr){while (*pAddr!=0xE8)pAddr++;  //XP~Win7正常,Win8尚无缘得见m_fnRtlDispatchException=(pfnRtlDispatchException)((*(DWORD *)(pAddr+1))+5+(DWORD)pAddr);  //得到原函数地址DWORD dwNewAddr=(DWORD)_RtlDispatchException-(DWORD)pAddr-5;                //计算新地址CMemory::WriteMemory((DWORD)pAddr+1,(BYTE *)&dwNewAddr,4);  //这个写内存的自己改造吧bResult=TRUE;}return bResult;
}//异常处理函数
BOOL RtlDispatchException(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext)
{返回TRUE,这个异常我已经处理好了,继续运行程序返回FALSE,这个异常不是我的,找别人处理去
}

调试寄存器 原理与使用:DR0-DR7相关推荐

  1. 如何对抗硬件断点--- 调试寄存器

    1.前言 在我跨入ollydbg的门的时候,就对ollydbg里面的各种断点充满了疑问,以前我总是不明白普通断点,内存断点,硬件断点有什么区别,他们为什么 有些时候不能混用,他们的原理是什么,在学习了 ...

  2. 调试器原理_调试器的工作原理

    调试器原理 调试器是大多数(如果不是每种)开发人员在软件工程生涯中至少使用一次的软件之一,但是你们当中有多少人知道它们的实际工作原理? 在悉尼举行的linux.conf.au 2018上的演讲中,我将 ...

  3. 调试寄存器(debug registers, DRx)理论及实践

    导读: 标 题:DRx寄存器的使用(待续) (4千字) 发信人:hume   时 间:2003-06-18 17:33:11 详细信息: 调试寄存器(DRx)理论与实践 By Hume/冷雨飘心 前言 ...

  4. linux gdb 寄存器,x86 调试寄存器

    英文官方介绍<Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3A: System Programmin ...

  5. 【java】Java 动态调试技术原理及实践

    1.概述 转载:Java 动态调试技术原理及实践 一.动态调试要解决的问题 断点调试是我们最常使用的调试手段,它可以获取到方法执行过程中的变量信息,并可以观察到方法的执行路径.但断点调试会在断点位置停 ...

  6. chrome android远程调试工作原理

    注意⚠️:本文为个人学习原理所翻译的文章,文中介绍的方法还未实践验证,不保证有效可用.目前仅进行了原理学习,后续会补充实际操作过程中遇到的问题. chrome android远程调试工作原理 Chro ...

  7. python调试器原理_调试器工作原理——基础篇

    本文是一系列探究调试器工作原理的文章的第一篇.我还不确定这个系列需要包括多少篇文章以及它们所涵盖的主题,但我打算从基础知识开始说起. 关于本文 我打算在这篇文章中介绍关于Linux下的调试器实现的主要 ...

  8. 调试断点原理之普通断点

    以OD为调试器分析不同断点的区别: 普通断点原理:直接改写断点内存地址的第一个字节,替换为int3 (0xcc,软中断机制),并保存原始字节至OD维护的一张断点表处.程序运行到此处时会中断,抛出异常, ...

  9. SWD下载调试接口原理深度剖析

    由于我们公司自己需要开发烧录工具,本人通过google搜相关文档和看ARM公司的技术文档,终于实现了这个功能.该篇幅敢很自信的说把SWD理论讲的最浅显易懂的. 作为ARM嵌入式工程师,下载调试器都应该 ...

最新文章

  1. 深入解析Angular Component的源码示例
  2. 简单html图片轮播_web前端入门到实战:简单的图片轮播
  3. SAP EWM中仓库任务WT创建的函数
  4. C# 利用Jmail接收邮件
  5. web.config/app.config敏感数据加/解密的二种方法
  6. ACCESS高级注入
  7. MySQL sql99语法—等值连接
  8. octave安装 缺java_Octave信号包安装
  9. 动态规划-01背包问题详解
  10. 重磅!大数据知识总结和调参技巧开放下载了
  11. CentOS7上安装WordPress
  12. python pytest mark
  13. 论文都看不懂,你还搞什么人工智能?
  14. win7中安装redis
  15. 第八章——ICellStyle单元格样式操作
  16. shc加密shell脚本
  17. 【Python】爬虫爬取各大网站新闻(一)
  18. web audio api 实现音频播放
  19. Clickhouse 各种工具函数知识 -<日期函数>
  20. 【讲清楚,说明白!】Zabbix企业级自动化监控与无人报警系统--实战演练

热门文章

  1. pyhanlp 中文词性标注与分词简介
  2. Unity学习笔记(5):动态加载Prefab
  3. 亿景WideLink系统与微软统一通信的整合
  4. 多线程-多图下载综合案例-SDWebImage框架实现代码
  5. BigPipe学习研究
  6. Sorting It All Out--POJ 1094
  7. 单台主机 kafka + zookeeper 集群搭建
  8. get_k_data 接口文档 全新的免费行情数据接口
  9. 说好的敬畏每一行代码呢?Antd代码彩蛋炸翻一圈人
  10. 使用Oracle数据泵迁移数据库