时间旅行调试 - 示例应用演练
本实验室介绍时间旅行调试 (TTD),使用一个存在代码缺陷的小型示例程序。 TTD 用于调试、识别问题及分析其根本原因。 尽管这个小程序中的问题很容易找到,但常规过程可用于更复杂的代码。 这一常规过程可概述如下。
- 捕获失败程序的时间旅行跟踪。
- 使用 dx(显示调试器对象模型表达式)命令来查找记录中存储的异常事件。
- 使用 !tt(时间旅行)命令前往跟踪中异常事件的位置。
- 从跟踪中的这一点开始向后退一步,直到有问题的故障代码进入范围。
- 在范围内的故障代码中,查看本地值并对可能包含错误值的变量进行假设。
- 确定值不正确的变量的内存地址。
- 使用 ba(访问时中断)命令在可疑变量的地址上设置内存访问 (ba) 断点。
- 使用 g- 返回可疑变量的最后一个内存访问点。
- 查看该位置或之前的几条指令是否是代码缺陷的关键所在。 如果是这样,则已完成。 如果错误值来自其他变量,则在第二个变量的访问断点上设置另一个断点。
- 使用 g- 返回第二个可疑变量的最后一个内存访问点。 查看该位置或之前的几条指令是否包含代码缺陷。 如果是这样,则已完成。
- 重复此过程,直到找到设置了导致错误的错误值的代码。
虽然此过程中描述的一般技术适用于广泛的代码问题,但有些独特的代码问题需要采用独特的方法。 演练中演示的技术应有助于扩展调试工具集,并将说明使用 TTD 跟踪可以实现的一些功能。
实验室目标
完成本实验室后,将能够将常规过程与时间旅行跟踪一起使用,以便查找代码中的问题。
实验室设置
要完成实验室内容,需要使用以下硬件。
- 运行 Windows 10 或 Windows 11 的笔记本电脑或台式计算机(主机)
需要以下软件才能完成实验室内容。
- WinDbg。 有关安装 WinDbg 的信息,请参阅 WinDbg - 安装
- Visual Studio,用于构建示例 C++ 代码。
此实验室包含以下三个部分。
第 1 部分:生成示例代码
在第 1 部分中,将使用 Visual Studio 构建示例代码。
在 Visual Studio 中创建示例应用
在 Microsoft Visual Studio 中,单击文件>新建>项目/解决方案...,然后单击 Visual C++ 模板。
选择 Win32 控制台应用程序。
提供 DisplayGreeting 的项目名称,然后单击确定。
取消选中安全开发生命周期 (SDL) 检查。
单击完成。
将以下文本粘贴到 Visual Studio 中的 DisplayGreeting.cpp 窗格中。
// DisplayGreeting.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <array> #include <stdio.h> #include <string.h> void GetCppConGreeting(wchar_t* buffer, size_t size) { wchar_t const* const message = L"HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!"; wcscpy_s(buffer, size, message); } int main() { std::array <wchar_t, 50> greeting{}; GetCppConGreeting(greeting.data(), sizeof(greeting)); wprintf(L"%ls\n", greeting.data()); return 0; }
在 Visual Studio 中,单击项目>DisplayGreeting 属性。 然后单击 C/C++ 和代码生成。
设置以下属性。
设置 “值” 安全检查 禁用安全检查 (/GS-) 基本运行时检查 默认 注意
虽然不建议使用这些设置,但可以想象有人会建议使用这些设置来加快编码或方便某些测试环境。
在 Visual Studio 中,单击构建>构建解决方案。
如果一切顺利,创建窗口将会显示一条消息,表明创建成功。
找到生成的示例应用文件
在解决方案资源管理器中,右键单击 DisplayGreeting 项目,然后选择在文件资源管理器中打开文件夹。
导航到包含示例的已编译 exe 和符号 pdb 文件的 Debug 文件夹。 例如,如果这是存储项目的文件夹,则导航到 C:\Projects\DisplayGreeting\Debug。
运行具有代码缺陷的示例应用
双击 exe 文件以运行示例应用。
如果出现此对话框,请选择关闭程序
在本演练的下一部分中,我们将记录示例应用的执行情况,看一看是否可以确定发生此异常的原因。
第 2 部分:记录“DisplayGreeting”示例的跟踪
在第 2 部分中,将记录示例“DisplayGreeting”应用的不当行为跟踪
要启动示例应用并记录 TTD 跟踪,请执行以下步骤。 有关记录 TTD 跟踪的常规信息,请参阅时间旅行调试 - 记录跟踪
以管理员身份运行 WinDbg,以便能够记录时间旅行跟踪。
在 WinDbg 中,选择文件>开始调试>启动可执行文件(高级)。
输入要记录的用户模式可执行文件的路径,或选择浏览导航到可执行文件。 有关在 WinDbg 中使用“启动可执行文件”菜单的信息,请参阅 WinDbg - 启动用户模式会话。
选中用时间旅行调试记录进程框,从而在启动可执行文件时记录跟踪。
单击配置和记录开始记录。
出现“配置记录”对话框时,单击记录以启动可执行文件并开始记录。
将显示记录对话框,指示正在记录跟踪。 之后不久,应用程序会崩溃。
单击关闭程序,关闭“DisplayGreeting 已停止工作”对话框。
当程序崩溃时,跟踪文件将被关闭并写入磁盘。
调试器将自动打开跟踪文件并为其编制索引。 索引编制是一个能有效调试跟踪文件的过程。 对于较大的跟踪文件,该索引编制过程需要更长的时间。
(5120.2540): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: D:0 [Unindexed] Index !index Indexed 10/22 keyframes Indexed 20/22 keyframes Indexed 22/22 keyframes Successfully created the index in 755ms.
注意
关键帧是用于编制索引的跟踪中的位置。 自动生成关键帧。 较大的跟踪将包含更多关键帧。
此时将位于跟踪文件的开头,并已准备好在时间上向前和向后移动。
现在便已记录了 TTD 跟踪,可以重播跟踪或处理跟踪文件,例如与同事共享。 有关使用跟踪文件的详细信息,请参阅时间旅行调试 - 使用跟踪文件
在本实验室的下一部分中,我们将分析跟踪文件,找出代码中存在的问题。
第 3 部分:分析跟踪文件记录以识别代码问题
在第 3 部分中,将分析跟踪文件记录以识别代码问题。
配置 WinDbg 环境
输入以下命令,将本地符号位置添加到符号路径,并重新加载符号。
.sympath+ C:\MyProjects\DisplayGreeting\Debug .reload
键入以下命令,将本地代码位置添加到源路径。
.srcpath+ C:\MyProjects\DisplayGreeting\DisplayGreeting
要查看堆栈和局部变量的状态,请在 WinDbg 功能区上选择视图、局部变量、视图和堆栈。 整理窗口以便同时查看它们、源代码和命令窗口。
在 WinDbg 功能区上,选择源和开放源文件。 找到 DisplayGreeting.cpp 文件并将其打开。
检查异常
加载跟踪文件时会显示发生异常的信息。
2fa8.1fdc): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68ef8100 ebx=00000000 ecx=77a266ac edx=69614afc esi=6961137c edi=004da000 eip=77a266ac esp=0023f9b4 ebp=0023fc04 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:0023fac0=00000000
使用 dx 命令列出记录中的所有事件。 异常事件已列在事件中。
0:000> dx -r1 @$curprocess.TTD.Events ... [0x2c] : Module Loaded at position: 9967:0 [0x2d] : Exception at 9BDC:0 [0x2e] : Thread terminated at 9C43:0 ...
注意
在本演练中,使用了三个句点来表示已删除多余的输出。
单击异常事件以显示有关该 TTD 事件的信息。
0:000> dx -r1 @$curprocess.TTD.Events[17] @$curprocess.TTD.Events[17] : Exception at 68:0 Type : Exception Position : 68:0 [Time Travel] Exception : Exception of type Hardware at PC: 0X540020
单击“异常”字段以进一步深入查看异常数据。
0:000> dx -r1 @$curprocess.TTD.Events[17].Exception @$curprocess.TTD.Events[17].Exception : Exception of type Hardware at PC: 0X540020 Position : 68:0 [Time Travel] Type : Hardware ProgramCounter : 0x540020 Code : 0xc0000005 Flags : 0x0 RecordAddress : 0x0
异常数据表明这是 CPU 引发的硬件故障。 它还提供了 0xc0000005 异常代码,表明这是一个访问违规行为。 这通常表示在尝试写入无权访问的内存。
单击异常事件中的 [时间旅行] 链接以移动到跟踪中的该位置。
0:000> dx @$curprocess.TTD.Events[17].Exception.Position.SeekTo() Setting position: 68:0 @$curprocess.TTD.Events[17].Exception.Position.SeekTo() (16c8.1f28): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 68:0 eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00540020 esp=00effe4c ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00540020 ??
值得注意的是,堆栈和基指针指向的是两个完全不同的地址。
esp=00effe4c ebp=00520055
这可能表示堆栈损坏 - 可能是函数返回后损坏了堆栈。 为了验证这一点,我们需要回到 CPU 状态损坏之前,看看能否确定堆栈损坏发生的时间。
检查局部变量并设置代码断点
在跟踪故障点时,通常会在错误处理代码的真正原因之后几步结束。 通过时间旅行,我们可以一次返回指令,找到真正的根本原因。
从主页功能区使用单步回退命令来回退三条指令。 在此过程中,请继续检查堆栈和内存窗口。
指令窗口将显示时间旅行位置和后退三个指令时的寄存器。
0:000> t- Time Travel Position: 67:40 eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00540020 esp=00effe4c ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 00540020 ?? ??? 0:000> t- Time Travel Position: 67:3F eax=00000000 ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=0019193d esp=00effe48 ebp=00520055 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 DisplayGreeting!main+0x4d: 0019193d c3 0:000> t- Time Travel Position: 67:39 eax=0000004c ebx=00cf8000 ecx=99da9203 edx=69cf1a6c esi=00191046 edi=00191046 eip=00191935 esp=00effd94 ebp=00effe44 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 DisplayGreeting!main+0x45:
注意
在本演练中,命令输出显示了可替代 UI 菜单选项的命令,以便习惯使用命令行的用户使用命令行命令。
在跟踪的这一点上,我们的堆栈和基指针的值更有意义,因此我们似乎越来越接近代码中发生损坏的点。
esp=00effd94 ebp=00effe44
同样值得注意的是,局部变量窗口包含了目标应用中的值,而源代码窗口则突出显示了跟踪中此时准备执行的代码行。
为了进一步调查,我们可以打开内存窗口,查看 0x00effe44 基指针内存地址附近的内容。
要显示关联的 ASCII 字符,请在“内存”功能区中选择文本,然后选择ASCII。
基指针不再指向指令,而是指向消息文本。 因此,这里有些问题,这可能接近我们损坏堆栈的时间点。 为了进一步调查,我们将设置断点。
注意
在这个非常小的示例中很简单,只用在代码中查找就可以了,但要是有数百行代码和数十个子例程,则可以使用此处介绍的技术来减少查找问题所需的时间。
TTD 和断点
使用断点是一种常见方法,用于在感兴趣的某个事件中暂停代码执行。 TTD 可以设置断点并及时返回,直到记录跟踪后命中该断点。 在问题发生后检查进程状态,以确定断点的最佳位置,这一功能可实现 TTD 独有的额外调试工作流程。
内存访问断点
可以设置在访问内存位置时触发的断点。 使用 ba(访问时中断)命令,语法如下。
ba <access> <size> <address> {options}
选项 | 说明 |
---|---|
e | execute(当 CPU 从地址中提取指令时) |
r | read/write(当 CPU 读取或写入地址时) |
w | write(当 CPU 写入地址时) |
请注意,任何时候都只能设置四个数据断点,并且必须确保正确对齐数据,否则将无法触发断点(word 必须以可被 2 整除的地址结尾,dword 必须可被 4 整除,quadword 必须可被 0 或 8 整除)。
为基指针设置内存访问断点上的中断
在跟踪的这一点上,我们希望在写入内存访问基指针 - ebp(在本示例中为 00effe44)时设置一个断点。 为此,请运用使用要监控的地址的 ba 命令。 我们希望监控四个字节的写入,因此指定 w4。
0:000> ba w4 00effe44
选择视图,然后选择断点以确认它们已按预期进行设置。
在“主页”菜单中,选择返回以及时返回,直到命中断点。
0:000> g- Breakpoint 0 hit Time Travel Position: 5B:92 eax=0000000f ebx=003db000 ecx=00000000 edx=00cc1a6c esi=00d41046 edi=0053fde8 eip=00d4174a esp=0053fcf8 ebp=0053fde8 iopl=0 nv up ei pl nz ac pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216 DisplayGreeting!DisplayGreeting+0x3a: 00d4174a c745e000000000 mov dword ptr [ebp-20h],0 ss:002b:0053fdc8=cccccccc
选择视图,然后选择局部变量。 在局部变量窗口中,我们可以看到目标变量只包含消息的一部分,而源包含所有文本。 这些信息支持了堆栈已损坏的观点。
此时,我们可以检查程序堆栈以查看哪些代码处于活动状态。 在视图功能区中选择堆栈。
由于 Microsoft 提供的 wscpy_s() 函数不太可能存在这样的代码错误,因此我们进一步查看堆栈。 堆栈显示 Greeting!main 调用 Greeting!GetCppConGreeting。 在这个的非常小的代码示例中,我们此时只用打开代码,就可能很容易找到错误。 但为了说明可用于更大、更复杂程序的技术,我们将设置一个新的断点来进一步研究。
为 GetCppConGreeting 函数设置访问断点上的断点
使用断点窗口通过右键单击现有断点并选择删除来清除现有断点。
使用 dx 命令确定 isplayGreeting!GetCppConGreeting 函数的地址。
0:000> dx &DisplayGreeting!GetCppConGreeting &DisplayGreeting!GetCppConGreeting : 0xb61720 [Type: void (__cdecl*)(wchar_t *,unsigned int)] [Type: void __cdecl(wchar_t *,unsigned int)]
使用 ba 命令为内存访问设置断点。 由于函数将只从内存中读取来执行,因此我们需要设置 r - 读取断点。
0:000> ba r4 b61720
确认硬件读取断点在断点窗口中处于活动状态。
由于我们想知道问候语字符串的大小,因此将设置一个监视窗口来显示 sizeof(greeting) 的值。 在视图功能区中,选择监视并提供 sizeof (greeting)。 如果值不在范围中,则监视窗口将显示 - 无法绑定名称“greeting”。
在“时间旅行”菜单上,使用时间旅行至开始或使用
!tt 0
命令移动到跟踪的开始。0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
在“主页”菜单中,选择转到或使用
g
命令,在代码中向前移动,直到命中断点。0:000> g Breakpoint 2 hit Time Travel Position: 4B:1AD eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046 eip=00b61721 esp=00ddf7a4 ebp=00ddf864 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!GetCppConGreeting+0x1: 00b61721 8bec mov ebp,esp
在“主页”菜单中,选择后退一步或使用
g-u
命令后退一步。0:000> g-u Time Travel Position: 4B:1AA eax=00ddf800 ebx=00fa2000 ecx=00ddf800 edx=00b61046 esi=00b61046 edi=00b61046 eip=00b61917 esp=00ddf7ac ebp=00ddf864 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!main+0x27: 00b61917 e8def7ffff call DisplayGreeting!ILT+245(?GetCppConGreetingYAXPA_WIZ) (00b610fa)
看来我们找到了根本原因。 我们声明的 问候 数组长度为 50 个字符,而传入 GetCppConGreeting 的 sizeof(greeting)为 0x64,100。
当我们进一步查看大小问题时,我们还注意到消息长度为 75 个字符,包含字符串字符的末尾时为 76 个字符。
HELLO FROM THE WINDBG TEAM. GOOD LUCK IN ALL OF YOUR TIME TRAVEL DEBUGGING!
修复代码的一种方法是将字符数组的大小扩展到 100。
std::array <wchar_t, 100> greeting{};
我们还需要在此代码行中将 sizeof(greeting) 更改为 size(greeting)。
GetCppConGreeting(greeting.data(), size(greeting));
要验证这些修补程序,我们可以重新编译代码,并确认代码运行时没有错误。
使用源窗口设置断点
执行此调查的另一种方法是通过单击任何代码行来设置断点。 例如,单击源窗口中 std:array 定义行的右侧将设置一个断点。
在“时间旅行”菜单上,使用时间旅行至开始命令移动到跟踪的开始。
0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
在“主页”功能区上单击转到以返回,直到命中断点。
Breakpoint 0 hit Time Travel Position: 5B:AF eax=0000000f ebx=00c20000 ecx=00000000 edx=00000000 esi=013a1046 edi=00effa60 eip=013a17c1 esp=00eff970 ebp=00effa60 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!DisplayGreeting+0x41: 013a17c1 8bf4 mov esi,esp
为问候变量设置访问断点上的断点
执行此调查的另一种替代方法是对可疑变量设置断点,并检查哪些代码正在改变它们。 例如,要在 GetCppConGreeting 方法中对问候变量设置断点,请使用此过程。
本演练的这一部分假定仍位于上一部分的断点处。
来自视图和局部变量。 在局部变量窗口中,问候语在当前上下文中可用,因此我们能够确定其内存位置。
使用 dx 命令检查问候语数组。
0:000> dx &greeting &greeting : ddf800 : { size=50 } [Type: std::array<wchar_t,50> *] [<Raw View>] [Type: std::array<wchar_t,50>] [0] : 3 [Type: wchar_t] [1] : 0 [Type: wchar_t]
在此跟踪中,问候语位于 ddf800 的内存中。
使用断点窗口通过右键单击现有断点并选择删除来清除任何现有断点。
使用要监控写入访问的内存地址通过 ba 命令设置断点。
ba w4 ddf800
在“时间旅行”菜单上,使用时间旅行至开始命令移动到跟踪的开始。
0:000> !tt 0 Setting position to the beginning of the trace Setting position: 15:0 (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 15:0 eax=68e28100 ebx=00000000 ecx=77a266ac edx=69e34afc esi=69e3137c edi=00fa2000 eip=77a266ac esp=00ddf3b8 ebp=00ddf608 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 ntdll!LdrpInitializeProcess+0x1d1c: 77a266ac 83bdbcfeffff00 cmp dword ptr [ebp-144h],0 ss:002b:00ddf4c4=00000000
在“主页”菜单上,选择转到以前进到问候数组的第一个内存访问点。
0:000> g- Breakpoint 0 hit Time Travel Position: 5B:9C eax=cccccccc ebx=002b1000 ecx=00000000 edx=68d51a6c esi=013a1046 edi=001bf7d8 eip=013a1735 esp=001bf6b8 ebp=001bf7d8 iopl=0 nv up ei pl nz na po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202 DisplayGreeting!GetCppConGreeting+0x25: 013a1735 c745ec04000000 mov dword ptr [ebp-14h],4 ss:002b:001bf7c4=cccccccc
或者,我们可以前往跟踪的末尾,并通过代码反向工作,以查找数组内存位置写入到的跟踪中的最后一个点。
使用 TTD.Memory 对象查看内存访问情况
另一种确定跟踪内存中已访问的点的方法是使用 TTD.Memory 对象和 dx 命令。
使用 dx 命令检查问候语数组。
0:000> dx &greeting &greeting : 0xddf800 [Type: std::array<wchar_t,50> *] [+0x000] _Elems : "꽘棶檙瞝???" [Type: wchar_t [50]]
在此跟踪中,问候语位于 ddf800 的内存中。
使用 dx 命令查看内存中的四个字节,从具有读取写入访问权限的地址开始。
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw") @$cursession.TTD.Memory(0x1bf7d0,0x1bf7d4, "rw") [0x0] [0x1] [0x2] [0x3] [0x4] [0x5] [0x6] [0x7] [0x8] [0x9] [0xa] ...
单击任一匹配项以显示有关该内存访问事件的详细信息。
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5] @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5] EventType : MemoryAccess ThreadId : 0x710 UniqueThreadId : 0x2 TimeStart : 27:3C1 [Time Travel] TimeEnd : 27:3C1 [Time Travel] AccessType : Write IP : 0x6900432f Address : 0xddf800 Size : 0x4 Value : 0xddf818 OverwrittenValue : 0x0 SystemTimeStart : Monday, November 18, 2024 23:01:43.400 SystemTimeEnd : Monday, November 18, 2024 23:01:43.400
单击[时间旅行],让 TimeStart 将跟踪定位在时间点。
0:000> dx @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo() @$cursession.TTD.Memory(0xddf800,0xddf804, "rw")[5].TimeStart.SeekTo() (1e5c.710): Break instruction exception - code 80000003 (first/second chance not available) Time Travel Position: 27:3C1 eax=00ddf81c ebx=00fa2000 ecx=00ddf818 edx=ffffffff esi=00000000 edi=00b61046 eip=6900432f esp=00ddf804 ebp=00ddf810 iopl=0 nv up ei pl nz ac po nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000212 ucrtbased!_register_onexit_function+0xf: 6900432f 51 push ecx
如果对跟踪中最后出现的读/写内存访问感兴趣,可以单击列表中的最后一项或追加 .Last() 函数到 dx 命令的末尾。
0:000> dx -r1 @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last() @$cursession.TTD.Memory(0xddf800,0xddf804, "rw").Last() EventType : MemoryAccess ThreadId : 0x710 UniqueThreadId : 0x2 TimeStart : 53:100E [Time Travel] TimeEnd : 53:100E [Time Travel] AccessType : Read IP : 0x690338e4 Address : 0xddf802 Size : 0x2 Value : 0x45 SystemTimeStart : Monday, November 18, 2024 23:01:43.859 SystemTimeEnd : Monday, November 18, 2024 23:01:43.859
然后,我们可以单击 [时间旅行] 移动到跟踪中的该位置,并使用本实验室前面所述的技术进一步查看该点的代码执行。
有关 TTD.Memory 对象的详细信息,请参阅 TTD.Memory 对象。
总结
在此非常小的示例中,问题可以通过查看几行代码来确定,但在较大的程序中,此处提供的技术可用于减少查找问题所需的时间。
记录跟踪后,可以共享跟踪和重现步骤,并且问题在任何电脑上都可以重现。