分类 调试 下的文章

两期我们分别介绍了通过 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 标志。

一、Windows系统的任务管理器里抓dump

启动任务管理器,选中某个进程,右键,弹出菜单"创建转储文件"

注意事项:

当你在64位Windows系统上抓32位进程的dmup文件时,如果用的是64位任务管理器,那么在用Windbg加载后,要用!wow64exts.sw切换到X86模式下,如果不想做这步切换,就要用32位的任务管理器来生成dmp文件。32位任务管理器在C:\Windows\SysWOW64\Taskmgr.exe

 适合的场景:在任务管理器里还能看到进程,当程序出现业务问题、性能问题、失去响应;当程序崩溃跳出系统错误提示框的时候,特别适合应用在客户机出现上述问题时使用。因为我们不用传其他工具到客户机上

二、用Windbg来抓取

2.1、方法一:启动windbg ,附加到我们关心的进程,执行命令 .dump [/ma/mdi] [存放路径]

适合的场景:在任务管理器里还能看到进程,当程序出现业务问题、性能问题、失去响应;当程序崩溃跳出系统错误提示框的时候,适合在公司内部测试时开发人员和测试人员使用

2.2、方法二  监视特定进程,当某进程崩溃时自动抓取dump

先运行可能存在问题的程序,然后执行命令在命令行里 执行 ..\WinDbg\adplus.vbs(exe) -crash -pn qq.exe -o c:\1.dmp。运行此命令后,WinDbg会跳出黑色窗口,用于监控进程的运行状况,注意不要关闭这个窗口,否则Dump就抓不到了。在目标进程里执行业务操作,当崩溃后,Dump文件会保存在指定的输出路径里。

在指定的输出目录下生成如下目录

参数说明:

  • -crash: 当程序挂掉的一刹那抓取Dump,这个参数只能抓到程序报错时的信息,如果程序不报错,则无法抓到Dump。
  • -hang: 当开启WinDbg之后就开始抓取Dump,主要用于抓取程序异常,但程序未崩溃的情况,例如进程的CPU使用率100%。
  • -pn:进程的PID或进程名,如果是进程名,会区分大小写。
  • -o: Dump输出路径.

适合的场景:程序已经运行但不知道什么时候会崩溃且不会有错误提示框的情况。

2.3、方法三 将Windbg设置为默认调试工具

首先,执行 在命令行Windbg.exe -I设置为默认调试工具,会弹出Windbg窗口

 

点“确定”窗口消失,然后运行程序和操作,当程序崩溃时WibDbg窗口再次弹出来,在windbg 命令行执行命令.dump [/ma/mdi] [存放路径]

参数说明:

-I: 将WinDbg作为默认的调试工具,注意I必须为大写,小写无效。

适合的场景:不知道什么时候会崩溃。无需指定要抓取的进程或PID,也不要求设置环境时必须存在进程,只要任意程序崩溃后都可以抓到Dump。

三、修改注册表

@echo off  
echo 正在启用Dump...  
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps"  
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpFolder /t REG_EXPAND_SZ /d "C:\CrashDump" /f  
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpType /t REG_DWORD /d 2 /f  
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /v DumpCount /t REG_DWORD /d 10 /f  
echo Dump已经启用  
pause  
@echo on 

将上述内存保存为*.bat,然后执行,就开始了自动抓dmp文件的功能,只要有程序崩溃,就会在指定的目录下生成。

键值说明:

名称:DumpCount,类型:REG_DWORD,最大保留Dump个数,默认为10.
名称:DumpType,类型:REG_DWORD,Dump类型(1-Mini dump, 2-Full dump),默认为1.
名称:DumpFolder,类型:REG_EXPAND_SZ,Dump文件保存的位置。

当不需要自动抓取时,可以将下面的内容

@echo off  
echo 正在关闭Dump...  
reg delete "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps" /f  
echo Dump已经关闭  
pause  
@echo on 

保存为bat执行,就不会在自动产生了

适合的场景:也是不知道什么时候程序会崩溃,也无法确定崩溃是否会重现,只要有崩溃就会被抓取,万无一失。

四、编程

直接使用Windows的API——MiniDumpWriteDumpSetHandleExceptionFilter。在我们自己的应用里使用这两个API来实现程序崩溃时自动产生dump文件,并且还可以实现自己的dump上传机制等,不过要注意如果某些库会导致我们程序的机制失效。

五、Process Explorer

 这个也是要注意在64位机上抓32位进程的问题,跟任务管理一样处理

 

写在最后:当然还有更多的工具和方法,包括上面介绍的工具和方法,我们可以根据实际情况和需要灵活应用,抓取dmp文件来帮助我们调试定位问题

人们可以使用术语“模块时间戳”来表示文件时间戳和Image头时间戳。虽然他们通常非常接近,但他们是不同的,不会是相同的。下面是一个比较/对比:

File timestamp Image header timestamp
What is it? This is tracked by the file system, and includes several metrics such as when the file was created, when it was last modified, and when it was last accessed. Emitted by the compiler and stored in the image header. Thus, it's in the contents of the file and separate from the meta-information tracked by the filesystem.
Who normally sets it? The file system. The compiler (which then generally creates a file to persists the results to, thus the file and image timestamps are usually very close)
Underlying storage 64-bit FileTime structure 32-bit time_t structure.
Win32 exposure kernel32!GetFileTime IMAGE_FILE_HEADER, exposed via the ImageHelp library.Matt Pietrek has an excellent article about cracking the PE file to get information like this. (The PE file format is publicly specced).
.NET exposure In .NET, these are accessible as System.DateTime objects via File.GetCreationTime, File.GetLastAccessTime, File.GetLastWriteTime. I don't think there are any .Net APIs to get these. (does anybody want them?).  The Pdb2Xml writer in MDbg sample alludes to this a little.BradA tells how to convert time_t to a System.DateTime.    

映像时间戳是您在调试器下看到的。例如,windbg的“lmv”命令将时间戳显示为原始32位值,并将其转换为有用的值:

Image name: notepad.exe
Timestamp: Tue Aug 03 23:05:55 2004 (41107CC3)
CheckSum: 00014F7F

可以从资源管理器查看文件时间戳。右键单击该文件并显示属性。为了进行比较,来自同一文件的时间戳通过文件系统:

创建时间:2004年8月9日星期一上午11:11:33

修改日期:2004年8月4日星期三上午4:00:00

访问时间:今天,2007年1月18日,晚上7:22:56


映像时间戳(和其他相关数据)也是转储文件中捕获的内容(请参阅MINIDUMP_MODULE)。因此,当调试器希望将小型转储中的模块与磁盘上的实际模块关联时,它可以使用映像头中的时间戳和校验和。这与PDB匹配的工作原理类似。

 

从38 6 开始 , IA 一3 2 处理器 内部都含有 8 个 3 2 位的调试 寄存器 DR0一 DR7 (如 图 1 所 示 )。 除了 DR4 和 DR5 保留 外 , 其它 6 个寄存器 分别是 :

  • 四 个 3 2 位的调试地址 寄存器 (DR0~DR3)
  • 一个 3 2 位的调试控制寄存器 (DR7) 
  • 一个 3 2 位的调试状态寄存器 (DR6) 

通过 以上寄存器可 以最多设置 4 个断点, 其基本分工是DR0一DR3 用来指定断点的内存 (线性地址) 或l/ O 地址 。 DR7 用 来进一步定义断点的中断条件。 DR6的作用是当调试事件发生向调试器 《d e b g u g , ) 报告事件的类别和属性 . 以供调试器判断发生 的是何种事 件 (哪个断点 、 单步跟踪 、 断点属性等等 )。 下面分 别详细介绍 D R 7 和DR6 的用法 。

3 2 位的DR7寄存器 中 , 有2 4 位是被划分成 4 组分 别与 4 个调试地址寄存器相对应的.比如 L0 . G0 . R/ W0 和 LEN0这 六位都是 与DR0相对应的 .L1 . G1. R/ W1 和 LEN1 这六位都是与DR1相对应的 依此类推。表 1 列出了DR7 中各个位的具体含义。

调试状态寄存器 DR6 是 当 CPU 检测到匹配 断点 条件的断点时 . 用来向调试器的断点异常处理程序传递该断点异常的具体情况 . 以使调试器可 以很容易 的识别 出发 生的是什么断点 。 比如如果 BO被设置为1, 那么就说 明满足 DR0、 L E N 0、和 R/ W0 所定义条件 的断点发 生了 。 下面的表分 别列出了 DR6 中各个位的具体含义。

通过表 1 的定义可 以看出 . 调试控制寄存器的各个位域提 供了很灵活 的方式 .允许我们通过不同的组合定义出各种复杂的断点条件。 下面我们先进一步介绍一下读写域R/ Wn , 通过 对它的设里 , 我们可 以指定要定义的断点的访问类型 (数据 、代码还是 l/ 0 ) . 即断点的访问条件 :
1、读写内存中的数据时中断 , 这种断点又被称为数据访 问断点 (data breakpointer )。 利用数据访问 断点 可 以监 控对全局变量 , 或局部变量的读写操作。 例如 . 在进行某些复杂的系统级调试 . 或者调 试多线程程序时 , 我们不知道哪个线程在何时修改了某一变量,这时我们就可 以设置一个数据访问断 点。 现代调试器大多还 都支持复杂的条件断点, 比如 当某个变 量等于某个确定的值时中断 . 这其实也是利用数据访问断点实 现的 . 其基本思路是设置 一个数据访问断点来监视这个变量 , 每次这个变量改变时 CPU 都会调用调试器 的中断处理程序 ,调试器检查这个变量的值 . 如 果不 满足规定的条件就立刻返回让 CPU 继续执行 。 如果满足 , 就中断到调 试环境。
2、执行 内存中的代 码时 中断. 这 种断点 又被称为代码访问断点 (Code Br e a k Po in t 》或指令断点 (i n s tr u e t io n b r e a k po in t ) 。 代码访问断点从实现 的功 能上 看与软件断点类似 , 都是 当 C PU 执行到某个地址 的指令时 中断。但是通 过寄存器 实现的代码访问断 点具有一个软件断点无法 实现 的优点 , 就是不 需要 软 件断点那样向 目标代码处插入指令 。 这个优点在某些 情况 下非 常重要 。 例如 . 当我们调 试位于 RO M (只读存储器 ) 上的 代码 《比 如 BIO S 中的 PO S T 程序 ) 时 根本没 有办法向那里 插入软件断点 ( lNT 3 ) 指令 . 因为 目标内存是只 读的 。 另外软件断点的另 一个局限是 只有 当 目标代码被加载进内存后才可 以 向该区域设置软件断点。而调 试寄存器 断点没有这 些限 制. 因为只 要把需 要 中断的内存地址 放入调试地址 寄存器 (DR0一DR3 ) . 并设置好调试控制寄存器 ( DR7 ) 的相应位就可 以 了。

3、读写 I/O(输入输出 ) 端口时中断 .这种断点又被称为I/O访问断点 ( I  /O   b r e a kp oi n t ) 。I/ O 访问断点对于调试设计使用输入输出端口的设备驱动程序非常有用。 也可以利用 I/O 访问断点来监视对I/O空间的非法读写操作 . 提 高系统的安全性 。 因为某些恶意程序在实现破坏动作时 . 需要对某些 I/O 端 口进行读写操作 。

读写域定义 了要监视的访问类型 . 地址 寄存器(DR0一DR3 ) 定义 了要监视的起始地址 。 那么要监视的区域长度呢 , 这便是长度域 L E N n (n= 0, 1, 2,3 . 位于 D R 7 中 ) 的任务。L EN n 位段可 以指定 1,2,4 或 8 字节长的范围。 需要说明的是 :
1、对于代码访 问断点 . 长度域应该为00,代表一字节长 度 。 另外地址寄存器应该指向指令的起始字节 。 也就是 CPU 只会用指令的起始字节来检查代码 断点 匹配 。
2、对于数据和 I/O访问断点 . 有两点需要注意 :
第一 只 要 断点区域中的任一字节在被访问的范围 内 . 都会触发该断点。
第二 , 边界对齐要求 . 两字节区域必 须按字 (w o r d ) 边界对齐 : 四字节区域必须按双 宇 (d o u b l e w o r d ) 边界对齐 : 八字 节区城必 须按四字 (qu a d w o r d ) 边界对齐。 也就是说 . CP U 在 检查断点匹配时会 自动去除相应数量的低位 。 因此如果地址没 有按耍求对齐可能无法实现预期的结果。 例如 假设希望通过将 DR0 设为0 x A 003.L E N 0 设为 1 1 (代表 4 字节长)实现任何对0 x A00 3 ~0 x A c 0 6 内存区的写操作都会触发断点: 那么只有当0 x A 00 3 被访问时会触发断点 . 对0x A 00 4、0x A 00 s 和0x A006 处的内存访问都不会触发断点 。 因为长度域指定的是4 字节 所以CPU 在检查地址匹配时 . 会自动将起始地址0x A003 的低4 位屏蔽掉 , 也就是会被看作是0x A00 )。 表3 给出了更多的例子 用来说明断点的触发条件 。

因为以上介绍的断点不需要像软件断点那样向代码中插入 软件指令 , 依靠处理器本身的功能便可 以实现 . 所以人们习惯 上把这些使用调试寄存器 (DR0~DR7 ) 设置的断点叫硬件断点 ,以与软件断点区别开来 。
l A 一 32 处理器专门分配了两个中断向量来处理调试异常 向量1 和向量3。 向量3 用来处理 INT 3 指令产生的断点异常。 向量1 用来处理调试异常(de b u g e x c e pt io n . 简称 # DB ) 。 硬件断点产生的是调试异常 .所以硬件断点发生时C U P 会执行 1 号向量对应的处理例程 。

硬件断点其 有很多优点 , 但是也有不足 , 最明显的就是数量限制因为只用 4 个调试地址寄存器 . 所以 lA 一3 2 Cpu 允许 最多设置 4 个硬件断点。 这基本可以满足大多数情况下的调试 需要 。

另一点祷要说明的是 . 只有在实模式或保护模式的内核优先级( ring0 ) 下 才能访问调试寄存器 . 否则便会导致保护性 异常。 这是 出于安全性的考虑. 那么像 v i s u a . s t u d i o 这样的用 户态调试器是如何设一硬件断点 (VC 6 支持数据访问断点。 没 必 要也不支持 l/ O 访问断点 , 因为从 Win d o w s 9 8 开始用户态 下不允许进行直接 l/ O 读写 ) 的呢 ? 答案是通过访问线程 的C ON E T X T 数据 (每个线程被挂起时 . 寄存器等上下文信息都被 保存起来 . 当该线程恢复执行时 . 寄存器会被恢复回来) 来间 接访问调试寄存器 。

下面给出一个C + + 例子演示一下如何手工设置 数据访问断点.

 

 

一、什么是Dump文件

又叫内存转储文件或者叫内存快照文件。是一个进程或系统在某一给定的时间的快照。比如在进程崩溃时或则进程有其他问题时,甚至是任何时候,我们都可以通过工具将系统或某进程的内存备份出来供调试分析用。dump文件中包含了程序运行的模块信息、线程信息、堆栈调用信息、异常信息等数据。

二、分类

Windows下Dump文件分为两大类:内核模式Dump和用户模式Dump

2.1、内核模式Dump

是操作系统创建的崩溃转储,最经典的就是系统蓝屏,这时候会自动创建内核模式的Dump。如果你抓取整个系统的内存dump文件, 那么你抓取的是内核态的dump文件.

2.2、用户模式Dump

如果你抓一个进程的dump文件, 那么你抓取的是用户态的dump文件。进一步可以分为完整Dump(Full Dump)和迷你Dump(Minidump)。

  • Full Dump
    包含了某个进程完整的地址空间数据,以及许多用于调试的信息
  • Minidump
    随着Windows XP,微软发布了一组新的被称为“minidump”的崩溃转存技术。Minidump很容易定制。按照最常用的配置,一个minidump只包括了最必要的信息,用于恢复故障进程的所有线程的调用堆栈,以及查看故障时刻局部变量的值。这样的dump文件通常很小(只有几K字节)。所以,很容易通过电子方式发送给软件开发人员。一旦需要,minidump甚至可以包含比原来的crash dump更多的信息。minidump可以定制,给我们带来了一个问题-保存多少应用程序状态信息才能既保证调试有效,又能够尽量保证minidump文件尽可能小?尽管调试简单的异常访问只需要调用堆栈和局部变量的信息,但是解决更复杂的问题需要更多的信息。例如,我们可能需要查看全局变量的值、检查堆的完整性和分析进程虚拟内存的布局。同时,可执行程序的代码段往往是多余的,开发用的机器上可以很容易找到这些执行程序。

 三、Dump文件的使用场景

场景一:对我们程序员来说,当我们的应用程序发布出去,在客户机上出线了问题(崩溃、失去响应、资源消耗过高,性能不理想),由于这些问题不是发生在开发环境下的,这时我们就需要在客户机上想办法抓取dmp文件,拿回来进行分析。总之,Dump文件对我们编程人员,特别是Windows系统上进行C++开发应用的开发人员,进行问题定位非常有帮助。

场景二:windows系统自己出了问题,比如蓝屏了,这时系统会自动产生dump文件,被微软收集分析