时间旅行调试对象简介
本部分介绍如何使用数据模型查询时间旅行跟踪。 这可以是一个功能强大的工具,用于回答有关在时间旅行跟踪中捕获的代码等问题。
- 跟踪中有哪些异常?
- 在跟踪中的哪个时间点加载了特定的代码模块?
- 何时在跟踪中创建/终止线程?
- 跟踪中运行时间最长的线程是什么?
有一些 TTD 扩展可以将数据添加到会话和流程数据模型对象。 可以通过 dx(显示调试器对象模型表达式)命令、WinDbg 的模型窗口、JavaScript 和 C++ 访问 TTD 数据模型对象。 调试时间旅行跟踪时,会自动加载 TTD 扩展。
流程对象
添加到流程对象的主要对象可以在任何流程对象的 TTD 命名空间中找到。 例如 $@curprocess.TTD
。
:000> dx @$curprocess.TTD
@$curprocess.TTD
Threads
Events
Lifetime : [26:0, 464232:0]
SetPosition [Sets the debugger to point to the given position on this process.]
有关使用 LINQ 查询和调试器对象的常规信息,请参阅将 LINQ 与调试器对象结合使用。
属性
Object | 说明 |
---|---|
生命周期 | 描述整个跟踪生命周期的 TTD 范围对象。 |
线程 | 包含 TTD 线程对象的集合,在跟踪生命周期内每个线程都有一个。 |
事件 | 包含一组 TTD 事件对象,跟踪中的每个事件都有一个。 |
方法
方法 | 说明 |
---|---|
SetPosition() | 采用介于 0 到 100 之间的整数或 N:N 形式的字符串作为输入,并将跟踪跳转到该位置。 有关详细信息,请参阅!tt。 |
会话对象
添加到会话对象的主要对象可以在任何会话对象的 TTD 命名空间中找到。 例如 $@cursession.TTD
。
0:000> dx @$cursession.TTD
@$cursession.TTD : [object Object]
Calls [Returns call information from the trace for the specified set of methods: TTD.Calls("module!method1", "module!method2", ...) For example: dx @$cursession.TTD.Calls("user32!SendMessageA")]
Memory [Returns memory access information for specified address range: TTD.Memory(startAddress, endAddress [, "rwec"])]
DefaultParameterCount : 0x4
AsyncQueryEnabled : false
Resources
Data : Normalized data sources based on the contents of the time travel trace
Utility : Methods that can be useful when analyzing time travel traces
注意
TTDAnalyze 添加了一些用于扩展内部功能的对象和方法。 并非所有命名空间都有文档记录,而且当前的命名空间会随着时间推移而演变。
方法
方法 | 说明 |
---|---|
Data.Heap() | 跟踪期间分配的堆对象集合。 请注意,这是一个执行计算的函数,因此需要一段时间才能运行。 |
Calls() | 返回与输入字符串匹配的调用对象集合。 输入字符串可以包含通配符。 请注意,这是一个执行计算的函数,因此需要一段时间才能运行。 |
Memory() | 此方法采用 beginAddress、endAddress 和 dataAccessMask 参数并返回内存对象的集合。 请注意,这是一个执行计算的函数,因此需要一段时间才能运行。 |
对查询输出进行排序
使用 OrderBy() 方法按一列或多列对从查询返回的行进行排序。 本示例按 TimeStart 升序排序。
0:000> dx -r2 @$cursession.TTD.Calls("kernelbase!GetLastError").OrderBy(c => c.TimeStart)
@$cursession.TTD.Calls("kernelbase!GetLastError").OrderBy(c => c.TimeStart)
[0xb]
EventType : Call
ThreadId : 0x3a10
UniqueThreadId : 0x2
TimeStart : 39:2DC [Time Travel]
TimeEnd : 39:2DF [Time Travel]
Function : UnknownOrMissingSymbols
FunctionAddress : 0x7561ccc0
ReturnAddress : 0x7593d24c
ReturnValue : 0x0
Parameters
[0xe]
EventType : Call
ThreadId : 0x3a10
UniqueThreadId : 0x2
TimeStart : AF:36 [Time Travel]
TimeEnd : AF:39 [Time Travel]
Function : UnknownOrMissingSymbols
FunctionAddress : 0x7561ccc0
ReturnAddress : 0x4723ef
ReturnValue : 0x0
Parameters
要显示数据模型对象的更多深度,可使用 -r2 递归级别选项。 有关 dx 命令选项的详细信息,请参阅 dx(显示调试器对象模型表达式)。
本示例按 TimeStart 降序排序。
0:000> dx -r2 @$cursession.TTD.Calls("kernelbase!GetLastError").OrderByDescending(c => c.TimeStart)
@$cursession.TTD.Calls("kernelbase!GetLastError").OrderByDescending(c => c.TimeStart)
[0x1896]
EventType : Call
ThreadId : 0x3a10
UniqueThreadId : 0x2
TimeStart : 464224:34 [Time Travel]
TimeEnd : 464224:37 [Time Travel]
Function : UnknownOrMissingSymbols
FunctionAddress : 0x7561ccc0
ReturnAddress : 0x7594781c
ReturnValue : 0x0
Parameters
[0x18a0]
EventType : Call
ThreadId : 0x3a10
UniqueThreadId : 0x2
TimeStart : 464223:21 [Time Travel]
TimeEnd : 464223:24 [Time Travel]
Function : UnknownOrMissingSymbols
FunctionAddress : 0x7561ccc0
ReturnAddress : 0x7594781c
ReturnValue : 0x0
Parameters
查询中指定元素
要选择特定元素,可将各种限定符附加到查询中。 例如,查询显示包含“kernelbase!GetLastError”的第一个调用。
0:000> dx @$cursession.TTD.Calls("kernelbase!GetLastError").First()
@$cursession.TTD.Calls("kernelbase!GetLastError").First()
EventType : Call
ThreadId : 0x3a10
UniqueThreadId : 0x2
TimeStart : 77A:9 [Time Travel]
TimeEnd : 77A:C [Time Travel]
Function : UnknownOrMissingSymbols
FunctionAddress : 0x7561ccc0
ReturnAddress : 0x6cf12406
ReturnValue : 0x0
Parameters
在查询中过滤
使用 Select() 方法选择要查看和修改列显示名称的列。
本示例返回 ReturnValue 不为零的行,并选择具有“时间和错误”的自定义显示名称显示 TimeStart 和 ReturnValue 列。
0:000> dx -r2 @$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0).Select(c => new { Time = c.TimeStart, Error = c.ReturnValue })
@$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0).Select(c => new { Time = c.TimeStart, Error = c.ReturnValue })
[0x13]
Time : 3C64A:834 [Time Travel]
Error : 0x36b7
[0x1c]
Time : 3B3E7:D6 [Time Travel]
Error : 0x3f0
[0x1d]
Time : 3C666:857 [Time Travel]
Error : 0x36b7
[0x20]
Time : 3C67E:12D [Time Travel]
分组
使用 GroupBy() 方法对查询返回的数据进行分组,以使用结构化结果执行分析。本示例按错误号对时间位置进行分组。
0:000> dx -r2 @$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0).Select(c => new { Time = c.TimeStart, Error = c.ReturnValue }).GroupBy(x => x.Error)
@$s = @$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0).Select(c => new { Time = c.TimeStart, Error = c.ReturnValue }).GroupBy(x => x.Error)
[0x36b7]
[0x0]
[0x1]
[0x2]
[0x3]
[...]
[0x3f0]
[0x0]
[0x1]
[0x2]
[0x3]
...
将查询结果分配给变量
使用此语法将查询结果分配给变量dx $@var = <expression>
此示例将查询的结果分配给 myResults
dx -r2 @$myResults = @$cursession.TTD.Calls("kernelbase!GetLastError").Where(c => c.ReturnValue != 0).Select(c => new { Time = c.TimeStart, Error = c.ReturnValue })
使用 dx 命令,使用 -g 网格选项显示新创建的变量。 有关 dx 命令选项的详细信息,请参阅 dx(显示调试器对象模型表达式)。
0:000> dx -g @$myResults
========================================
= = (+) Time = (+) Error =
========================================
= [0x13] - 3C64A:834 - 0x36b7 =
= [0x1c] - 3B3E7:D6 - 0x3f0 =
= [0x1d] - 3C666:857 - 0x36b7 =
= [0x20] - 3C67E:12D - 0x2 =
= [0x21] - 3C6F1:127 - 0x2 =
= [0x23] - 3A547:D6 - 0x3f0 =
= [0x24] - 3A59B:D0 - 0x3f0 =
示例
查询异常
此 LINQ 查询使用 TTD.Event objec,用于显示跟踪中的所有异常。
0:000> dx @$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)
@$curprocess.TTD.Events.Where(t => t.Type == "Exception").Select(e => e.Exception)
[0x0] : Exception 0x000006BA of type Software at PC: 0X777F51D0
[0x1] : Exception 0x000006BA of type Software at PC: 0X777F51D0
[0x2] : Exception 0xE06D7363 of type CPlusPlus at PC: 0X777F51D0
查询特定 API 调用
使用 TTD.Calls object 来查询特定 API 调用。 在此示例中,调用 user32!MessageBox 时出错,Windows API 用于显示消息框。 我们列出对 MessageBoxW 的所有调用,按函数的开始时间对其进行排序,然后选择最后一次调用。
0:000> dx @$cursession.TTD.Calls("user32!MessageBoxW").OrderBy(c => c.TimeStart).Last()
@$cursession.TTD.Calls("user32!MessageBoxW").OrderBy(c => c.TimeStart).Last()
EventType : Call
ThreadId : 0x3a10
UniqueThreadId : 0x2
TimeStart : 458310:539 [Time Travel]
TimeEnd : 45C648:61 [Time Travel]
Function : UnknownOrMissingSymbols
FunctionAddress : 0x750823a0
ReturnAddress : 0x40cb93
ReturnValue : 0x10a7000000000001
Parameters
查询特定模块的加载事件
首先,使用 lm(列出加载的模块)命令显示已加载的模块。
0:000> lm
start end module name
012b0000 012cf000 CDog_Console (deferred)
11570000 1158c000 VCRUNTIME140D (deferred)
11860000 119d1000 ucrtbased (deferred)
119e0000 11b63000 TTDRecordCPU (deferred)
11b70000 11cb1000 TTDWriter (deferred)
73770000 73803000 apphelp (deferred)
73ea0000 74062000 KERNELBASE (deferred)
75900000 759d0000 KERNEL32 (deferred)
77070000 771fe000 ntdll (private pdb symbols)
然后使用以下 dx 命令查看特定模块在跟踪中的加载位置,例如 ntdll。
dx @$curprocess.TTD.Events.Where(t => t.Type == "ModuleLoaded").Where(t => t.Module.Name.Contains("ntdll.dll"))
@$curprocess.TTD.Events.Where(t => t.Type == "ModuleLoaded").Where(t => t.Module.Name.Contains("ntdll.dll"))
[0x0] : Module Loaded at position: A:0
此 LINQ 查询显示特定模块的加载事件。
0:000> dx @$curprocess.TTD.Events.Where(t => t.Type == "ModuleUnloaded").Where(t => t.Module.Name.Contains("ntdll.dll"))
@$curprocess.TTD.Events.Where(t => t.Type == "ModuleUnloaded").Where(t => t.Module.Name.Contains("ntdll.dll"))
[0x0] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0
FFFFFFFFFFFFFFFFFE:0 的地址指示跟踪的末尾。
查询跟踪中的所有错误检查
使用此命令按错误计数对跟踪中的所有错误检查进行排序。
0:000> dx -g @$cursession.TTD.Calls("kernelbase!GetLastError").Where( x=> x.ReturnValue != 0).GroupBy(x => x.ReturnValue).Select(x => new { ErrorNumber = x.First().ReturnValue, ErrorCount = x.Count()}).OrderByDescending(p => p.ErrorCount),d
==================================================
= = (+) ErrorNumber = ErrorCount =
==================================================
= [1008] - 1008 - 8668 =
= [14007] - 14007 - 4304 =
= [2] - 2 - 1710 =
= [6] - 6 - 1151 =
= [1400] - 1400 - 385 =
= [87] - 87 - 383 =
在创建线程时查询跟踪中的时间位置
使用此 dx 命令以网格格式 (-g) 显示跟踪中的所有事件。
0:000> dx -g @$curprocess.TTD.Events
==================================================================================================================================================================================================
= = (+) Type = (+) Position = (+) Module = (+) Thread =
==================================================================================================================================================================================================
= [0x0] : Module Loaded at position: 2:0 - ModuleLoaded - 2:0 - Module C:\Users\USER1\Documents\Visual Studio 2015\Proje... - =
= [0x1] : Module Loaded at position: 3:0 - ModuleLoaded - 3:0 - Module C:\WINDOWS\SYSTEM32\VCRUNTIME140D.dll at address 0... - =
= [0x2] : Module Loaded at position: 4:0 - ModuleLoaded - 4:0 - Module C:\WINDOWS\SYSTEM32\ucrtbased.dll at address 0X118... - =
= [0x3] : Module Loaded at position: 5:0 - ModuleLoaded - 5:0 - Module C:\Users\USER1\AppData\Local\Dbg\UI\Fast.20170907... - =
= [0x4] : Module Loaded at position: 6:0 - ModuleLoaded - 6:0 - Module C:\Users\USER1\AppData\Local\Dbg\UI\Fast.20170907... - =
= [0x5] : Module Loaded at position: 7:0 - ModuleLoaded - 7:0 - Module C:\WINDOWS\SYSTEM32\apphelp.dll at address 0X73770... - =
= [0x6] : Module Loaded at position: 8:0 - ModuleLoaded - 8:0 - Module C:\WINDOWS\System32\KERNELBASE.dll at address 0X73... - =
= [0x7] : Module Loaded at position: 9:0 - ModuleLoaded - 9:0 - Module C:\WINDOWS\System32\KERNEL32.DLL at address 0X7590... - =
= [0x8] : Module Loaded at position: A:0 - ModuleLoaded - A:0 - Module C:\WINDOWS\SYSTEM32\ntdll.dll at address 0X7707000... - =
= [0x9] : Thread created at D:0 - ThreadCreated - D:0 - - UID: 2, TID: 0x4C2C =
= [0xa] : Thread terminated at 64:0 - ThreadTerminated - 64:0 - - UID: 2, TID: 0x4C2C =
= [0xb] : Thread created at 69:0 - ThreadCreated - 69:0 - - UID: 3, TID: 0x4CFC =
= [0xc] : Thread created at 6A:0 - ThreadCreated - 6A:0 - - UID: 4, TID: 0x27B0 =
= [0xd] : Thread terminated at 89:0 - ThreadTerminated - 89:0 - - UID: 4, TID: 0x27B0 =
= [0xe] : Thread terminated at 8A:0 - ThreadTerminated - 8A:0 - - UID: 3, TID: 0x4CFC =
= [0xf] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\Users\USER1\Documents\Visual Studio 2015\Proje... - =
= [0x10] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\Users\USER1\AppData\Local\Dbg\UI\Fast.20170907... - =
= [0x11] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\WINDOWS\SYSTEM32\VCRUNTIME140D.dll at address 0... - =
= [0x12] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\Users\USER1\AppData\Local\Dbg\UI\Fast.20170907... - =
= [0x13] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\WINDOWS\SYSTEM32\ucrtbased.dll at address 0X118... - =
= [0x14] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\WINDOWS\SYSTEM32\apphelp.dll at address 0X73770... - =
= [0x15] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\WINDOWS\System32\KERNELBASE.dll at address 0X73... - =
= [0x16] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\WINDOWS\System32\KERNEL32.DLL at address 0X7590... - =
= [0x17] : Module Unloaded at position: FFFFFFFFFFFFFFFE:0 - ModuleUnloaded - FFFFFFFFFFFFFFFE:0 - Module C:\WINDOWS\SYSTEM32\ntdll.dll at address 0X7707000... - =
==================================================================================================================================================================================================
选择带有 + 符号的任何列对输出进行排序。
使用此 LINQ 查询以网格格式显示,创建线程时跟踪中的时间位置(类型 == “ThreadCreated”)。
dx -g @$curprocess.TTD.Events.Where(t => t.Type == "ThreadCreated").Select(t => t.Thread)
===========================================================================================================
= = (+) UniqueId = (+) Id = (+) Lifetime = (+) ActiveTime =
===========================================================================================================
= [0x0] : UID: 2, TID: 0x4C2C - 0x2 - 0x4c2c - [0:0, FFFFFFFFFFFFFFFE:0] - [D:0, 64:0] =
= [0x1] : UID: 3, TID: 0x4CFC - 0x3 - 0x4cfc - [0:0, 8A:0] - [69:0, 8A:0] =
= [0x2] : UID: 4, TID: 0x27B0 - 0x4 - 0x27b0 - [0:0, 89:0] - [6A:0, 89:0] =
===========================================================================================================
使用此 LINQ 查询以网格格式显示线程终止时跟踪中的时间位置(类型 == “ThreadTerminated”)。
0:000> dx -g @$curprocess.TTD.Events.Where(t => t.Type == "ThreadTerminated").Select(t => t.Thread)
===========================================================================================================
= = (+) UniqueId = (+) Id = (+) Lifetime = (+) ActiveTime =
===========================================================================================================
= [0x0] : UID: 2, TID: 0x4C2C - 0x2 - 0x4c2c - [0:0, FFFFFFFFFFFFFFFE:0] - [D:0, 64:0] =
= [0x1] : UID: 4, TID: 0x27B0 - 0x4 - 0x27b0 - [0:0, 89:0] - [6A:0, 89:0] =
= [0x2] : UID: 3, TID: 0x4CFC - 0x3 - 0x4cfc - [0:0, 8A:0] - [69:0, 8A:0] =
===========================================================================================================
对输出进行排序,以确定运行时间最长的线程
使用此 LINQ 查询以网格格式显示跟踪中近似运行时间最长的线程。
0:000> dx -g @$curprocess.TTD.Events.Where(e => e.Type == "ThreadTerminated").Select(e => new { Thread = e.Thread, ActiveTimeLength = e.Thread.ActiveTime.MaxPosition.Sequence - e.Thread.ActiveTime.MinPosition.Sequence }).OrderByDescending(t => t.ActiveTimeLength)
=========================================================
= = (+) Thread = ActiveTimeLength =
=========================================================
= [0x0] - UID: 2, TID: 0x1750 - 0x364030 =
= [0x1] - UID: 3, TID: 0x420C - 0x360fd4 =
= [0x2] - UID: 7, TID: 0x352C - 0x35da46 =
= [0x3] - UID: 9, TID: 0x39F4 - 0x34a5b5 =
= [0x4] - UID: 11, TID: 0x4288 - 0x326199 =
= [0x5] - UID: 13, TID: 0x21C8 - 0x2fa8d8 =
= [0x6] - UID: 14, TID: 0x2188 - 0x2a03e3 =
= [0x7] - UID: 15, TID: 0x40E8 - 0x29e7d0 =
= [0x8] - UID: 16, TID: 0x124 - 0x299677 =
= [0x9] - UID: 4, TID: 0x2D74 - 0x250f43 =
= [0xa] - UID: 5, TID: 0x2DC8 - 0x24f921 =
= [0xb] - UID: 6, TID: 0x3B1C - 0x24ec8e =
= [0xc] - UID: 10, TID: 0x3808 - 0xf916f =
= [0xd] - UID: 12, TID: 0x26B8 - 0x1ed3a =
= [0xe] - UID: 17, TID: 0x37D8 - 0xc65 =
= [0xf] - UID: 8, TID: 0x45F8 - 0x1a2 =
=========================================================
查询对内存范围的读取访问权限
使用 TTD.Memory object 查询以查询对内存范围的读取访问权限。
线程环境块 (TEB) 是一种结构,包含有关线程状态的所有信息,包括 GetLastError() 返回的结果。 可以通过为当前线程运行 dx $@teb
来查询此数据结构。 TEB 的成员之一是 LastErrorValue 变量,大小为 4 字节。 我们可以使用此语法引用 TEB 中的 LastErrorValue 成员。 dx &$@teb->LastErrorValue
。
示例查询显示如何在内存中查找在该范围中完成的每个读取操作,选择在创建对话框之前发生的所有读取操作,然后对结果进行排序以查找最后一次读取操作。
0:000> dx @$cursession.TTD.Memory(&@$teb->LastErrorValue, &@$teb->LastErrorValue + 0x4, "r")
@$cursession.TTD.Memory(&@$teb->LastErrorValue, &@$teb->LastErrorValue + 0x4, "r")
[0x0]
[0x1]
[0x2]
[0x3]
如果在跟踪中发生了“对话框”事件,我们可以运行查询来查找在内存中该范围内完成的每个读取操作,选择在创建对话框之前发生的所有读取操作,然后对结果进行排序以查找最后一次读取操作。 然后,通过在结果时间位置上调用 SeekTo() 来时间旅行,到达该时间点。
:000> dx @$cursession.TTD.Memory(&@$teb->LastErrorValue, &@$teb->LastErrorValue + 0x4, "r").Where(m => m.TimeStart < @$dialog).OrderBy(m => m.TimeStart).Last().TimeEnd.SeekTo()
Setting position: 458300:37
ModLoad: 6cee0000 6cf5b000 C:\WINDOWS\system32\uxtheme.dll
ModLoad: 75250000 752e6000 C:\WINDOWS\System32\OLEAUT32.dll
ModLoad: 76320000 7645d000 C:\WINDOWS\System32\MSCTF.dll
ModLoad: 76cc0000 76cce000 C:\WINDOWS\System32\MSASN1.dll
GitHub TTD 查询实验室
有关如何使用时间旅行调试记录调试 C++ 代码的教程,使用查询查找有关执行问题代码的信息,请参阅 https://github.com/Microsoft/WinDbg-Samples/blob/master/TTDQueries/tutorial-instructions.md。
实验室中使用的所有代码都可在此获取:https://github.com/Microsoft/WinDbg-Samples/tree/master/TTDQueries/app-sample。
故障排除 TTD 查询
将“UnknownOrMissingSymbols”作为函数名称
数据模型扩展需要完整的符号信息才能提供函数名称、参数值等。当完整的符号信息不可用时,调试器使用“UnknownOrMissingSymbols”作为函数名称。
- 如果有专用符号,则会获得函数名称和正确的参数列表。
- 如果有公共符号,则会获得函数名称和一组默认参数 - 四个无符号 64 位 ints。
- 如果查询的模块没有符号信息,则将“UnknownOrMissingSymbols”用作名称。
调用的 TTD 查询
查询不返回对 DLL 调用的任何内容时,原因可能有多种。
- 调用的语法不太正确。 尝试使用 x 命令:“x <call>”验证调用语法。 如果 x 返回的模块名称采用大写形式,请使用该名称。
- DLL 尚未加载,稍后会在跟踪中加载。 要解决此问题,可前往加载 DLL 并重做查询之后的某个时间点。
- 该调用是查询引擎无法跟踪的内联调用。
- 查询模式使用了通配符,这会返回过多函数。 尝试使查询模式更具体,以便匹配的函数数量足够少。