调试寄存器 原理与使用: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利用调试寄存器实现硬件断点,处理断点异常

  1.  
    /************************************************************************
  2.  
    SetHardWareBP:
  3.  
    设置线程硬件断点
  4.  
    hThread: 线程句柄
  5.  
    dwAddr: 断点地址
  6.  
    dwDrIndex: 硬件寄存器(0~3)
  7.  
    nType: 断点类型(0:执行,1:读取,2:写入)
  8.  
    nLen: 读写断点数据长度(1,2,4)
  9.  
    /************************************************************************/
  10.  
     
  11.  
    BOOL SetHardWareBP(HANDLE hThread,DWORD dwAddr,DWORD dwDrIndex=0,UINT nType=0,UINT nLen=1)
  12.  
    {
  13.  
    BOOL bResult=FALSE;
  14.  
     
  15.  
    CONTEXT context = {0};
  16.  
    context.ContextFlags=CONTEXT_DEBUG_REGISTERS;
  17.  
    if(::GetThreadContext(hThread,&context))
  18.  
    {
  19.  
    DWORD dwDrFlags=context.Dr7;
  20.  
     
  21.  
     
  22.  
    //将断点地址复制进入对应Dr寄存器(参考CONTEXT结构)
  23.  
    memcpy(((BYTE *)&context)+4+dwDrIndex*4,&dwAddr,4);
  24.  
     
  25.  
    //决定使用哪个寄存器
  26.  
    dwDrFlags|=(DWORD)0x1<<(2*dwDrIndex);
  27.  
     
  28.  
    //见OD读写断点时 这个置位了,执行没有(置位也正常-_-)
  29.  
    dwDrFlags|=0x100;
  30.  
     
  31.  
     
  32.  
    //先将对应寄存器对应4个控制位清零(先或,再异或,还有其它好方法吗) =.= 悲催的小学生
  33.  
    dwDrFlags|=(DWORD)0xF<<(16+4*dwDrIndex);
  34.  
    dwDrFlags^=(DWORD)0xF<<(16+4*dwDrIndex);
  35.  
     
  36.  
     
  37.  
    //设置断点类型,执行:00 读取:11 写入:01
  38.  
    //(不知何故,测试时发现不论是11还是01,读写数据时均会断下来)
  39.  
    if (nType==1)
  40.  
    dwDrFlags|=(DWORD)0x3<<(16+4*dwDrIndex); //读取
  41.  
    else if(nType==2)
  42.  
    dwDrFlags|=(DWORD)0x1<<(16+4*dwDrIndex); //写入
  43.  
    //else if(nType==0)
  44.  
    //dwDrFlags=dwDrFlags //执行
  45.  
     
  46.  
     
  47.  
    //设置读写断点时数据长度
  48.  
    if (nType!=0)
  49.  
    {
  50.  
    if(nLen==2 && dwAddr%2==0)
  51.  
    dwDrFlags|=(DWORD)0x1<<(18+4*dwDrIndex); //2字节
  52.  
    else if(nLen==4 && dwAddr%4==0)
  53.  
    dwDrFlags|=(DWORD)0x3<<(18+4*dwDrIndex); //4字节
  54.  
    }
  55.  
     
  56.  
    context.Dr7=dwDrFlags;
  57.  
    if (::SetThreadContext(hThread,&context)) bResult=TRUE;
  58.  
    }
  59.  
    return bResult;
  60.  
    }

  1.  
    //异常处理
  2.  
    //直接从工程中拷出来的
  3.  
    typedef ULONG (WINAPI *pfnRtlDispatchException)(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext);
  4.  
    static pfnRtlDispatchException m_fnRtlDispatchException=NULL;
  5.  
     
  6.  
    BOOL RtlDispatchException(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext);
  7.  
     
  8.  
    ULONG WINAPI CSysHook::_RtlDispatchException( PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext )
  9.  
    {
  10.  
    if(RtlDispatchException(pExcptRec,pContext)) return 1;
  11.  
    return m_fnRtlDispatchException(pExcptRec,pContext);
  12.  
    }
  13.  
     
  14.  
    //Hook程序异常处理,当程序发生异常时,由ring0转回ring3时调用的第一个函数:KiUserExceptionDispatcher
  15.  
    BOOL CSysHook::HookSystemSEH()
  16.  
    {
  17.  
    BOOL bResult=FALSE;
  18.  
    BYTE *pAddr=(BYTE *)::GetProcAddress(::GetModuleHandleA("ntdll"),"KiUserExceptionDispatcher");
  19.  
    if (pAddr)
  20.  
    {
  21.  
    while (*pAddr!=0xE8)pAddr++; //XP~Win7正常,Win8尚无缘得见
  22.  
    m_fnRtlDispatchException=(pfnRtlDispatchException)((*(DWORD *)(pAddr+1))+5+(DWORD)pAddr); //得到原函数地址
  23.  
    DWORD dwNewAddr=(DWORD)_RtlDispatchException-(DWORD)pAddr-5; //计算新地址
  24.  
    CMemory::WriteMemory((DWORD)pAddr+1,(BYTE *)&dwNewAddr,4); //这个写内存的自己改造吧
  25.  
    bResult=TRUE;
  26.  
    }
  27.  
    return bResult;
  28.  
    }
  29.  
     
  30.  
    //异常处理函数
  31.  
    BOOL RtlDispatchException(PEXCEPTION_RECORD pExcptRec,CONTEXT * pContext)
  32.  
    {
  33.  
    返回TRUE,这个异常我已经处理好了,继续运行程序
  34.  
    返回FALSE,这个异常不是我的,找别人处理去
  35.  
    }

详情jpg 转 rar

调试寄存器 原理与使用:DR0-DR7的更多相关文章

  1. 转——调试寄存器 原理与使用:DR0-DR7

    下面介绍的知识性信息来自intel IA-32手册(可以在intel的开发手册或者官方网站查到),提示和补充来自学习调试器实现时的总结. 希望能给你带去有用的信息. (DRx对应任意的一个调试寄存器. ...

  2. Win32调试API原理

    在Win32中自带了一些API函数,它们提供了相当于一般调试器的大多数功能,这些函数统称为Win32调试API(Win32 Debug API).利用这些API可以做到加载一个程序或捆绑到一个正在运行 ...

  3. VC利用调试寄存器实现硬件断点源码

    [文章标题]:VC利用调试寄存器实现硬件断点源码 [文章作者]:yhswwr(SilenceRet) [作者QQ]:3412259 [编写语言]:C++ [使用工具]:VS2008.VC++9 [本文 ...

  4. Atitit web remote远程调试的原理attilax总结

    Atitit web remote远程调试的原理attilax总结 Jvm是vm打开一个debug port,然后ide先连接..然后执行url,就会vm会与ide沟通.. Php的xdebug po ...

  5. xdebug调试的原理

    转自 https://segmentfault.com/a/1190000002528341 使用PhpStorm+xdebug单步调试程序可以快速帮助自己熟悉项目代码! 运行原理 在实际使用前,我们 ...

  6. Java 动态调试技术原理及实践

    本文转载自Java 动态调试技术原理及实践 导语 断点调试是我们最常使用的调试手段,它可以获取到方法执行过程中的变量信息,并可以观察到方法的执行路径.但断点调试会在断点位置停顿,使得整个应用停止响应. ...

  7. 嵌入式调试器原理和各类调试器集锦(JLINK、STLINK、CCDEBUG)

    工欲善其事,必先善其器.调试器在嵌入式开发调试中的重要性不言而喻,单步.断点和监察的效率远高于串口打印.但是,调试器对于一般开发人员往往是一个黑匣子.今天我们就来谈谈调试器的原理,顺便把自己的几类调试 ...

  8. java进行远程部署与调试及原理解析

    远程调试,特别是当你在本地开发的时候,你需要调试服务器上的程序时,远程调试就显得非常有用. JAVA 支持调试功能,本身提供了一个简单的调试工具JDB,支持设置断点及线程级的调试同时,不同的JVM通过 ...

  9. webpack最简单的入门教程里bundle.js之运行单步调试的原理解析

    读这篇文章的朋友,请确保对webpack有最基础的认识. 您可以阅读我前一篇文章:Webpack 10分钟入门 来在本地运行一个Webpack的hello world项目.https://www.to ...

随机推荐

  1. Android——进度条控制图片透明度

    xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android= ...

  2. Java命令学习系列(三)——Jmap

    Java命令学习系列(三)——Jmap 2015-05-16 分类:Java 阅读(479) 评论(0) Jmap jmap是JDK自带的工具软件,主要用于打印指定Java进程(或核心文件.远程调试服 ...

  3. Restful --- 让JSON回归单纯

    设计模式才是软件哲学的根本.. 一种软件架构风格.设计风格,而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓 ...

  4. Spring 4 官方文档学习(十一)Web MVC 框架之themes

    http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-themeresolver ...

  5. 基本上每个应用程序领域的程序员都有使用 C++

    C++ 的使用基本上每个应用程序领域的程序员都有使用 C++. C++ 通常用于编写设备驱动程序和其他要求实时性的直接操作硬件的软件. C++ 广泛用于教学和研究. 任何一个使用苹果电脑或 Windo ...

  6. Eclipse最经常使用快捷键总结

     1. ctrl+shift+r:打开资源 这可能是全部快捷键组合中最省时间的了. 这组快捷键能够让你打开你的工作区中不论什么一个文件,而你仅仅须要按下文件名称或mask名中的前几个字母,比方appl ...

  7. 前台的js对象数组传到后台处理。在前台把js对象数组转化为json字符串,在后台把json字符串解析为List<>

    前台的js对象数组传到后台处理.在前台把js对象数组转化为json字符串,在后台把json字符串解析为List<>

  8. linux上nginx上配置虚拟主机的相关配置

    1.配置主配置: nginx/conf/nginx.conf 2.虚拟主机配置:nginx/conf/extra/learn.weixin.com.conf 配置完后,重启服务器!

  9. 【HMM】隐马尔科夫模型

    http://www.hankcs.com/nlp/hmm-and-segmentation-tagging-named-entity-recognition.html

  10. 第六章 mybatis注入映射器

    为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现:Map ...