CPU对软件调试的支持(三)

两期我们分别介绍了通过 INT3指令设置的软件断点 和通过调试寄存器设置 的硬件断点。 除了断点 . 还有一类常用的方法使C P U 中断到调试器 . 这便是调试陷阱标志 (debug trap  f l a g ) 。 也就 是 当这些陷阱标志被设置时, 一旦有符合陷阱条件的事件发生,就会陷入到调试器 。 IA 一3 2 处理器所支持的调试陷阱标志可以概括如下

  • 80866就支持的单步执行标志 (标志寄存器E F a g I s 的一位)
  • 3 8 6 引入的任务状态陷阱标志(任务状态段TSS 的T标志)
  • 奔腾Pro引入 的分支到分支单步执行标志 (D e b u gC t !寄存 器 中的BT F 标志)

下面分别详细介绍 。

单步执行标志 (T F )

从 8 0 8 6 C pU 开始 , x 86 系列处理器的标志(EF Ia g s ) 寄存器 中便有一个陷阱标志位 ( b i t 8 )名为Trap Flag 简称T F。当 T F 位为 1 时 CPU 每执行完一条指令便会产生一个调 试异常 《# DB ) . 中断到调试器的调试异常处理 程序。 这就是 通 常所说 的单步执行 。 调试异常的向量号是 1 因此 T F 标志引发的是 1 号异常服例程 。在 80 86 和 2 8 6 时代 , 这个服务 例程是专门用来处理 单步事件的。 从 3 8 6 开始 . 硬 件断点发.生时也会产生调试异常 . 调 用 1 号服务例程 . 但可利用调试 状态寄存器(DR66 ) 来识别发生的是何种事件 。 为了表达方便我们把因 T F标志 触发的软件异常称 为单步异 常。

单步异常属于陷阱类异常。 也 就是 C P U 总是 在执 行完导致此类异常的指令后 才产生该异常。 这意味 着 当因单步异常中断到调 试器 中时, 导致 该异 常的指令已经执行 完毕 了 。 软件 断点 异常(#BP) 和硬 件断点中的数据及I/O 断点 也是 陷阱类异常. 但是硬 件断点 中的指令访 问异 常是错误类异常 . 也就是当由于此异常而 中断到调试器 时 . 相应调试地址寄存器 DRn中所指地址 处的 指令还 没“执行 。 这是 因为CPU是 在取指操作时进行此类断点 匹配的 。

CPU是何 时检查 TF标志的呢?’IA-32手册的原 文是 "while an instruction is begin executed" 也就 是在执行一个指令的过程 中。 尽管没有说过程 中的哪个阶段 (开 始 、 中间还 是末尾 ) . 可以推测应 该是 一条指令即将执行完毕的时 候 。 也就是说 当CPU在 将执行完一条指令的时候检测TF位 ,如果该位为 1那么 CPU会先清除此位 , 然后准备产生异常。 但 是 这里 有个例外 , 对于 那 些可 以设置 TF 位的指令 (例如 POPF) CPU 不会在执行这些指令期间做以上 检查。 也就是说这些 ,旨令不会立刻产生单步异常 . 而是其后的下一 条指令将 产生单步异常。
因 为 C P U 在进入 异 常 处理 例 程前会 自动 清除 T F标 ,因此 当 CPU中断到调 试 器 中后 再观 察 TF标 志 . 它总是0。

既然调试异常的向量号是 1 ,可 不可 以像 INT 3 那样通过在代码 中插入INT 1 这样的指令来实现手工断点呢 , 对于应用 程序答案是否定的。 INT 3尽 管具有INTn的形式 . 但是它具有独特的单字节机器码 . 而且 其作用就是产生一 个断点异常 一(#BP ) 。 因此 系统对其有特别的对待 .允 许其在用户模式下执行。 而 INT 1则不然, 它属于普通的 INTn 指令 机器码为 CD01。 保护模式下如果执行 INTn指令时,当前的 CPL大于引用的门描述符的DPL。那 么便会导致通 用保 护异常 (# GP) 。 在 W id n o w s 20 0 0 和 x p 这样的操作系统下,INT1对用的中断门描述符的DPL 为 0 . 这要求只有内核模式的代码 才能执 行 IN T 1指令,访 问该中断门。 也就是说 , 用户模 式下 的应用程序没有权利使用INT 1指令 。 一 旦使用就会导致一个一般保护性异常 ( # GP) 。W id n o w s会将其封装为一个访 `问违例错误 (如图2一 2 1 所示 )。 在内核模式 下 可 以在代码 (驱 动程序)中写入INT 1指令。CP U 执行到该指令时会转去执行 1号向量对应的处理例程 , 如果在使用 W in D g b 进行内核级调试. 那 么会中断到W i n o b g 中 . W i n o b g 会以为是发生了一个单 步异常 如 图 1 所示 。

下面谈谈调试高级语言时的单步机制 。 由于高级语言的一条语句通常都对应多条汇编指令 例如 , 表 1 中C+ 十的一条语句= i a 十b* “ + d / e + f / g 十 h对应于 1 5 条汇编语句 。 因此容易想到单 步执行这条C+ + 语句有几种可能方法 。 一种是也用一标志一步 步的走过每条汇编指令 , 这种方 法意味着会产生 1 5 次调试异常 . CPU 中断到调试器 1 5 次.不过中间的 1 4 次都是简单的重 新设 皿起 下F 标志 便让 CPU返 回执行 。 第二种方法是在 C + + 语 句对应 的最后一 条汇编指令处动态插入一 条lN 下 3指令 . 让C P U 一下子 跑到那里 然后 再单步执行一次将被替换的那条 指令执行完 . 这种方 法需要 CPU中断到调试器 两次 。 第三种方法是 在这条 C++ 语句的下一条语 句的第一 条汇编指令处( 即行 18 ) 替换入一 个 N I 下 3 . 这样 C PU 中断到调试器一次就可 以了 。
表 1 : 高级语 言的单步执行

后两种方法较第一种方法速度会快很多 . 但是不幸的是并不总能正确的预测 出最后高级语言对应的最后一条指令和下一行语句开始指令 (要替换为INT 3 的那一条指令)。比如 2 8 行的 e l s e if (b ) 语句就很难判断出它对应 的最后一条汇编语句 和 下一 条高级 语言语 句的起始指令 。 因此 今天 的大 多数调试器在进行高级语言调试时都是使 用第一种方法来实现 单步跟踪的。关于 TF标志还有一点值得注意: . INTn 和 INTO 指令会清除TF标志 , 因此调试器在单步跟踪这些指令时 . 必须做特别处理。

任务状态陷阱标志

除了标志寄存器中的陷阱标志 (TF ) 位.38 6 引入 了一种新 的调试陷阱标志 任务状态段 (TSS ) 中的T标志。任务状态段 (Ta s k一S t a te S e g m e n t ) 用来记录 一个任务 (CPU 可以独立调度和执行 的程序段 ) 的状态 . 包括通用寄存器的值 . 段寄存器 的值和其它重要信息 。 在任务切换时 , 当前任务的状态会被保存到这个内存段里。当要恢复执行这个任务时 . 会先根据这个 记录来把寄存器 的值恢复回来。 T S S 中 , 在宇节偏移为 10 0 的 1 6 位字 (w o r d ) 的最低位是 调试陷阱标志位 . 简称 T标志 。 T 标志被设置为 1, 那么 当CPU切 换到这个任务时 , 便会产生调试异常。 准确的说CPU 是在程序控制权已经转移到新的任务 , 但还没有开始执行新任务的第一 条指令前产 生异常的。 调试 中断处理程序可 以通过调试状态 寄存器 (DR6 ) 的 BT来识别出发生的是否是任务切换异 常。 值得注意的是 . 如果调试器接管了调试异常处理 . 而且该处理例程 属于一个独立 的任务 , 那 么一定不要设置该任务的TSS段中的 T位。否则便会导致死循环。

分支到分支单步执行标志 ( B T F )

在 lA 一 3 2 处理器 家族中 , 所有的 P e n i t u m Pr o  Pe n t l u m II和 Pe n t . u m III处理器 (包括相应的 C e .e r o n 《赛扬 ) 和 X e o n (至 强 ) 版本) 因为都是基于相 同的 6 P 内核 《C o r e ) . 因此经常被 统称为P6处理器 。 P6处理器引入 了一项新 的对调试非 常有用 的功能 : 记录 分支 、 中断和异 常事件 . 和 针对分 支单步执行(s in g le 一s z e p o n b r a n e h ) 的功能。 奔腾 4 处理器对这一功能又 做了很大增强 。 下面具体介绍一下按分支单步执行的功能和使 用方法 。
首先解释一下分支到分支单步执行的含义 。 前面我们介绍过 , 当 ElF a g s 寄存器的T F 位为 1 时 C PU 每执行完一条指 令便会中断到调试器 . 也就是以指令为单位单步执行 。 顾名思义, 针对分支单步执行就是以分支为单位单步执行 。换句 话说 , 每单步 ( s t ep ) 一次 C P U 会一直执行到有分支 、 中 断或异常发生 。 下面 结合表 2 中的代码进行说明。
那么如何启用按分支单步执行呢? 简单来说 . 就是要 同时设置TF和BTF 标志 。TF 标志位干 E F Ia g s寄存器, 大家已经很 熟悉。 BTF 标志位于名为DebugCtlMSR 寄存器中 。

在V C6 的ID E 环境下(系统的 C PU 应该是 P6 或更高 ),先在第 2 2 行设置一个断点 . 然后按F5 运 行到这个断点位置 。 行 1 9是用来启用按分支单步执行功能的 . 也就是设置BTF 标志. 细节我们等一下再讲。 接下来 . 我们按 F10单步执行 , 会发现一下子会执行到行 3 1 . 从第 2 2 行单步一次执行到第 3 1 行 这便 是按分支单步执行的效果。 那么为什么会执行到第 3 1 行呢? 按照分支到分支单步执行的定义 , CP U 会在执行到下一次分支、 中 断或异常发生时停止。 对于我们的例子 . CP U 在执行第 2 0 行对 应的第一条汇编指令时 , CP U 会检测到 TF 标志 (因为我们是按F10单步执行 . 所以 VC 6 会帮助我们设置TF 标志)。

此外 ,P6及以后的 lA 一 3 2 CP U 还会检变 BTF 标志 . 当发现BTF标志也被设置起时 , C P U会认为当前是在按分支单步执行 . 所 以会判断当前指令是否是分支指令 , 如果不是 ,CP U 便会继续执 行。 因为第 2 2 行的第一条汇编指令不是分支指令 . 所以 CP U 会 继续执行。 依次类推 . CP U 会连续执行到第 2 4 行的 i f 语句对应的最后一条汇编指令jne (参见表 3 )。 因为这条语句是分支语句 ,所以 CPU会认为已经满足停止执行的条件 , 在清除 , TF 和BTF标志后,产生单步异常中断到调试器 。 中断到调试器时 . 分支语句已经执行完毕 , 但是跳转到的那条语句 (即表 2 中的行 3 1 } 还 没有执行 。

对以上过程还有有几 点需要说明:

第一 如果在从第 2 2 行执行到 2 4 行过程中,有中断或异 常发生 , 那么 C P U 也会认为停止单步执行的条件已经满足. 因 此 按分支单步执行的全称是按分支 、 异常和中断单步执行。

第二 , CPU 认为有分支发生的条件是执行以下分支指令 :JM P (无条件跳转), JCC (包括条件跳转指令 , 如 J A 、 J A E、 JNE等等)、L OO P (循环 ) 和 Call( 函数 / 过程调用 ) 。

第三 , 由于只有内核代码 才能访问MS R 寄存器 (通过RDMSR 和四WRMSR 指令) 所以上面的例子 中 , 在 WriteMSR()函数中 我们使用一个未公开 的 A p l  ZwSystemDebugControl()来设置 BTF 标志。

原文地址:https://www.cnblogs.com/yilang/p/12121200.html

时间: 2024-08-30 12:56:38

CPU对软件调试的支持(三)的相关文章

软件调试——CPU异常列表

CPU异常主要分为三类:错误类异常,陷阱类异常和终止类异常 1 错误类异常 Fault CPU遇到该类异常后,会先将CS和EIP(当前发生错误的指令,而不是下一条指令)压栈,然后跳到异常处理函数中,执行完成后恢复到原位置重新执行该指令,如果还有错误,还会再进. 例如内存缺页异常就是错误类异常,CPU遇到缺页异常时会跳转到异常处理,将缺少的内存页从物理内存中置换回来,再恢复重新执行内存访问指令. 2 陷阱类异常 Trap CPU遇到该类异常后,会将CS和EIP压栈,这个EIP就是当前指令的下一条指

《软件调试的艺术》学习笔记——GDB使用技巧摘要

<软件调试的艺术>学习笔记——GDB使用技巧摘要 <软件调试的艺术>,因为名是The Art of Debugging with GDB, DDD, and Eclipse. 作者是美国的Norman Matloff和Peter Jay Salzman,中文版由张云翻译.是人邮出版社图灵程序设计丛书初版.这里称为"艺术",个人觉得有点过了,但是其中关于gdb以及在gdb基础之上集成的DDD和Eclipse调试技巧的整理确实是做的很好,对于Linux/开源社区下的

调试SQLSERVER (三)使用Windbg调试SQLSERVER的一些命令

调试SQLSERVER (三)使用Windbg调试SQLSERVER的一些命令 调试SQLSERVER (一)生成dump文件的方法调试SQLSERVER (二)使用Windbg调试SQLSERVER的环境设置 windbg命令分为标准命令.元命令.扩展命令 标准命令提供最基本的调试功能,不区分大小写.如:bp g dt dv k等 元命令提供标准命令没有提供的功能,也内建在调试引擎中,以.开头.如.sympath .reload等 扩展命令用于扩展某一方面的调试功能,实现在动态加载的扩展模块中

利用开源软件strongSwan实现支持IKEv2的企业级IPsec VPN,并结合FreeRadius实现AAA协议(下篇)

续篇—— 利用开源软件strongSwan实现支持IKEv2的企业级IPsec VPN,并结合FreeRadius实现AAA协议(上篇) 上篇文章写了如何构建一个支持IKEv2的VPN,本篇记录的是如何利用freeradius,以及结合Daloradius进行VPN的 Web 管理.先让freeradius做个自述吧. 一.Radius 介绍 远端用户拨入验证服务(RADIUS, Remote Authentication Dial In User Service)是一个AAA协议,意思就是同时

软件调试之INT 3讲解

第4章断点和单步执行 断点和单步执行是两个经常使用的调试功能,也是调试器的核心功能.本章我们将介绍IA-32 CPU是如何支持断点和单步执行功能的.前两节将分别介绍软件断点和硬件断点,第4.3节介绍用于实现单步执行功能的陷阱标志.在前三节的基础上,第 4.4节将分析一个真实的调试器程序,看它是如何实现断点和单步执行功能的. 4.1  软件断点 x86系列处理器从其第一代产品英特尔8086开始就提供了一条专门用来支持调试的指令,即INT 3.简单地说,这条指令的目的就是使CPU中断(break)到

配送短信猫软件丰富,支持短信猫二次开发

配送短信猫软件丰富,支持短信猫二次开发 短信猫主要是用于二次开发领域,支持将短信收发功能集成.嵌入到其他系统.软件当中.最终实现短信收发除了需要有短信猫硬件外还需要相应短信猫软件的支持,即所谓的短信猫开发包.短信猫SDK或短信猫接口程序.而支持短信猫二次开发的软件非常丰富,有不同款式.有免费有收费,采用不同开发方式. 以下介绍我公司的几款短信猫开发软件,如下: 免费短信猫DLL开发包 提供有多种开发语言示例DEMO,方便程序员开发调用,免费短信猫开发包,免加密狗,自行测试调试使用. 短信服务器8

《软件调试的艺术》笔记--停下来环顾程序

1.断点列表 创建的每个断点(包括断电.监视点和捕获点)都标识为从1开始的唯一整数标识符.这个标识符用来执行该断点上的各种 操作.调试器还包含一种列出所有断点及其属性的方法. 调试下面的代码:(代码1) #include <stdio.h> void display(int i) { i = i + 1; printf("i = %d\n",i); } int main(void) { int i = 1; display(i); return 0; } 设置断点--显示断

&lt;读书笔记&gt;软件调试之道 :从大局看调试-零容忍策略

声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! -------------------------------------------------------------------------------------------- 缺陷优先 如何使缺陷修复与软件开发相结合? 如何估计缺陷修复花费的时间? 如何确保项目不会陷入<人月神话>中所描述的无数缺陷修复的焦油坑中呢? 缺陷优先 要采用早起缺陷修复原则,并且基于以下两

&lt;读书笔记&gt;软件调试之道 :问题的核心-如何修复缺陷

声明:本文档的内容主要来源于书籍<软件调试修炼之道>作者Paul Butcher,属于读书笔记.欢迎转载! 修复缺陷 对于一个好的修复来说,不仅仅是让软件运行正确,还需要为将来奠定基础.一些列零散的未经仔细考虑的修改,都将是原本的简洁设计逐步消失. 好的修复必须同时实现以下目标: 修复问题 避免引入回归 维持或者提高代码的整体质量 -----------需要参考的规则如下---------- 1.清除障碍 确保一切从头开始,当你不舍得抛弃诊断阶段所做的修改时,利用源码控制系统. 需要对所做的修