在 windbg 中进行内核调试时无法获得完整的用户模式堆栈跟踪
Unable to get full user-mode stacktrace while kernel debugging in windbg
我在 Windows 10 主机上有一个虚拟 Windows 7 x64 机器,我用 windbg 10.0.10586.567
对它进行内核调试。我是 运行 我自己的应用程序,我有完整的源代码和私有符号。每当我中断并请求应用程序线程的堆栈跟踪时,当我的应用程序的一个二进制文件是 "hit."
时,回溯总是停止
因此,例如,如果我闯入,切换到进程,并使用 !thread [thread address] 1f
请求堆栈跟踪,我会得到类似这样的结果(注意 "early" 零 return 地址在最后一行):
fffff880`0534e870 fffff800`026d6992 nt!KiSwapContext+0x7a
fffff880`0534e9b0 fffff800`026d81a2 nt!KiCommitThreadWait+0x1d2
fffff880`0534ea40 fffff800`029c7a2e nt!KeDelayExecutionThread+0x186
fffff880`0534eab0 fffff800`026d08d3 nt!NtDelayExecution+0x59
fffff880`0534eae0 00000000`76e7165a nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`0534eae0)
00000000`00276708 000007fe`fcf91203 ntdll!NtDelayExecution+0xa
00000000`00276710 00000001`410e7dd9 KERNELBASE!SleepEx+0xab
00000000`002767b0 00000000`00000000 MyApp!MainMessageLoop+0x4b1 [d:\whatever\path\myapplication.cpp @ 3024]
这看起来与您在调试 x64 进程的用户模式转储(缺少展开数据)时丢失二进制文件非常相似,除了在这种情况下堆栈跟踪通常不会停止 "this sudden",而是它在那个时候误入歧途,并显示虚假值。
一些额外的 info/things 我试过了:
- 我设置了正确的符号路径(Microsoft 符号服务器和主机上具有匹配 PDB 的本地文件夹,即使仅堆栈跟踪不需要后者)
- 我在主机上设置了一个包含匹配二进制文件的二进制路径 (
.exepath
)(我对此非常确定;将二进制文件直接从客户机复制到主机)
- 如果我在应用程序导出的 DLL 函数之一中放置一个断点,那么当调试器进入时,我会得到一个单行堆栈跟踪,如下所示:
0000000000274b40 0000000000000000 MyAppDLL!SomeExportedFunction+0x32 [d:\whatever\path\myapplicationDLL.cpp @ 232]
- 我几乎尝试了所有命令组合来获取堆栈跟踪(
.process /i
、.process /r /p
、!process -1 7
、.reload
s、.reload /user
s , .reload /f MyApp.exe
, !thread [address] 1f
, 等)没有成功
- 也尝试使用旧版本的 windbg (
6.11.0001.404
),结果相同
- 还以访客身份在 Windows 8.1 上尝试过相同的二进制文件,结果相同
!sym noisy
输出(省略无关行):
0: kd>.process /i [address]
0: kd>g
0: kd>.reload /user
0: kd> !process -1 2
0: kd> !thread [address] 1f
[...]
DBGHELP: d:\symbolcache\MyApp.pdb931C5A6C284779AD2F916CA324617E1\MyApp.pdb already cached
DBGHELP: MyApp - private symbols & lines
[...]
lmvm MyApp
输出:
[...]
Loaded symbol image file: MyApp.exe
Image path: C:\MyApp\MyApp.exe
[...]
有什么想法吗?
这不是问题的完美解决方案(或者有人可能会说根本不是任何解决方案),但我提供了这个临时答案和解决方法。
你应该能够得到你想要的信息,尽管使用 dps @rsp L10
.
这样的格式不是很好
在 x86-64 中,您没有 x86 ebp 链的并行,但 return 地址仍在堆栈上。这些将为您提供堆栈中的函数,它们之间的值将是传递给函数的参数(以及堆栈中保存的寄存器等)。 A random example from Google(因为我现在不在我的 Windows 机器上):
0:017> dps @rsp
00000000`1bb0fbb8 00000000`00000020
00000000`1bb0fbc0 00000000`00000000
00000000`1bb0fbc8 00000000`008bc6c6 Dolphin!ReadDataFromFifoOnCPU+0xb6 [d:\sources\comex\source\core\videocommon\fifo.cpp @ 245]
00000000`1bb0fbd0 00000000`1ba0ffeb
00000000`1bb0fbd8 00000000`00000020
00000000`1bb0fbe0 00000000`00000020
00000000`1bb0fbe8 00000000`00000800
00000000`1bb0fbf0 00000000`1ba0ffeb
00000000`1bb0fbf8 00000000`008c2ff5 Dolphin!InterpretDisplayListPreprocess+0x45 [d:\sources\comex\source\core\videocommon\opcodedecoding.cpp @ 87]
00000000`1bb0fc00 00000000`00000000
00000000`1bb0fc08 00000000`008bc041 Dolphin!RunGpu+0x81 [d:\sources\comex\source\core\videocommon\fifo.cpp @ 389]
00000000`1bb0fc10 00000000`8064cbc0
00000000`1bb0fc18 00000000`1bb0fcc0
00000000`1bb0fc20 00000000`00000000
00000000`1bb0fc28 00000000`008c2dda Dolphin!OpcodeDecoder_Preprocess+0x14a [d:\sources\comex\source\core\videocommon\opcodedecoding.cpp @ 326]
00000000`1bb0fc30 00000000`8064cbe0
鉴于您有符号,return 地址很容易区分。
展开数据是为用户模式模块延迟加载的,因此除非有人需要,否则不会被映射。不幸的是,内核调试器不会强制为用户图像显示信息,因此有时您会遇到这种情况。您可以通过转储 PE header (!dh) 并检查异常目录 (!pte imagename+offset) 的状态来查看数据是否已映射。
假设您拥有该应用程序,请尝试通过在您的应用程序中的某处执行堆栈遍历 NOP 来强制驻留信息:
PVOID stack[2];
(VOID)CaptureStackBackTrace(0, 2, (PVOID*)&stack, NULL);
这并不能保证整个目录都存在,但通常已经足够了。
我不小心发现了一个解决这个问题的 linker 开关:/DEBUGTYPE
和 PDATA
参数。如果您 link 使用此开关的二进制文件,展开信息将被复制到您的 PDB 中。
我recompiled/relinked有问题的应用程序带有/DEBUGTYPE:CV,PDATA
(如果指定/DEBUG
,/DEBUGTYPE:CV
是默认值,请参阅documentation),现在一切工作起来很有魅力,我总是得到完整的调用堆栈。
其中一个奇怪的方面:windbg 愉快地使用在 PDB 中找到的展开数据,但忽略了映射二进制文件中的相同数据(都在主机上)。
我在 Windows 10 主机上有一个虚拟 Windows 7 x64 机器,我用 windbg 10.0.10586.567
对它进行内核调试。我是 运行 我自己的应用程序,我有完整的源代码和私有符号。每当我中断并请求应用程序线程的堆栈跟踪时,当我的应用程序的一个二进制文件是 "hit."
因此,例如,如果我闯入,切换到进程,并使用 !thread [thread address] 1f
请求堆栈跟踪,我会得到类似这样的结果(注意 "early" 零 return 地址在最后一行):
fffff880`0534e870 fffff800`026d6992 nt!KiSwapContext+0x7a
fffff880`0534e9b0 fffff800`026d81a2 nt!KiCommitThreadWait+0x1d2
fffff880`0534ea40 fffff800`029c7a2e nt!KeDelayExecutionThread+0x186
fffff880`0534eab0 fffff800`026d08d3 nt!NtDelayExecution+0x59
fffff880`0534eae0 00000000`76e7165a nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ fffff880`0534eae0)
00000000`00276708 000007fe`fcf91203 ntdll!NtDelayExecution+0xa
00000000`00276710 00000001`410e7dd9 KERNELBASE!SleepEx+0xab
00000000`002767b0 00000000`00000000 MyApp!MainMessageLoop+0x4b1 [d:\whatever\path\myapplication.cpp @ 3024]
这看起来与您在调试 x64 进程的用户模式转储(缺少展开数据)时丢失二进制文件非常相似,除了在这种情况下堆栈跟踪通常不会停止 "this sudden",而是它在那个时候误入歧途,并显示虚假值。
一些额外的 info/things 我试过了:
- 我设置了正确的符号路径(Microsoft 符号服务器和主机上具有匹配 PDB 的本地文件夹,即使仅堆栈跟踪不需要后者)
- 我在主机上设置了一个包含匹配二进制文件的二进制路径 (
.exepath
)(我对此非常确定;将二进制文件直接从客户机复制到主机) - 如果我在应用程序导出的 DLL 函数之一中放置一个断点,那么当调试器进入时,我会得到一个单行堆栈跟踪,如下所示:
0000000000274b40 0000000000000000 MyAppDLL!SomeExportedFunction+0x32 [d:\whatever\path\myapplicationDLL.cpp @ 232]
- 我几乎尝试了所有命令组合来获取堆栈跟踪(
.process /i
、.process /r /p
、!process -1 7
、.reload
s、.reload /user
s ,.reload /f MyApp.exe
,!thread [address] 1f
, 等)没有成功 - 也尝试使用旧版本的 windbg (
6.11.0001.404
),结果相同 - 还以访客身份在 Windows 8.1 上尝试过相同的二进制文件,结果相同
!sym noisy
输出(省略无关行):0: kd>.process /i [address] 0: kd>g 0: kd>.reload /user 0: kd> !process -1 2 0: kd> !thread [address] 1f [...] DBGHELP: d:\symbolcache\MyApp.pdb931C5A6C284779AD2F916CA324617E1\MyApp.pdb already cached DBGHELP: MyApp - private symbols & lines [...]
lmvm MyApp
输出:[...] Loaded symbol image file: MyApp.exe Image path: C:\MyApp\MyApp.exe [...]
有什么想法吗?
这不是问题的完美解决方案(或者有人可能会说根本不是任何解决方案),但我提供了这个临时答案和解决方法。
你应该能够得到你想要的信息,尽管使用 dps @rsp L10
.
在 x86-64 中,您没有 x86 ebp 链的并行,但 return 地址仍在堆栈上。这些将为您提供堆栈中的函数,它们之间的值将是传递给函数的参数(以及堆栈中保存的寄存器等)。 A random example from Google(因为我现在不在我的 Windows 机器上):
0:017> dps @rsp
00000000`1bb0fbb8 00000000`00000020
00000000`1bb0fbc0 00000000`00000000
00000000`1bb0fbc8 00000000`008bc6c6 Dolphin!ReadDataFromFifoOnCPU+0xb6 [d:\sources\comex\source\core\videocommon\fifo.cpp @ 245]
00000000`1bb0fbd0 00000000`1ba0ffeb
00000000`1bb0fbd8 00000000`00000020
00000000`1bb0fbe0 00000000`00000020
00000000`1bb0fbe8 00000000`00000800
00000000`1bb0fbf0 00000000`1ba0ffeb
00000000`1bb0fbf8 00000000`008c2ff5 Dolphin!InterpretDisplayListPreprocess+0x45 [d:\sources\comex\source\core\videocommon\opcodedecoding.cpp @ 87]
00000000`1bb0fc00 00000000`00000000
00000000`1bb0fc08 00000000`008bc041 Dolphin!RunGpu+0x81 [d:\sources\comex\source\core\videocommon\fifo.cpp @ 389]
00000000`1bb0fc10 00000000`8064cbc0
00000000`1bb0fc18 00000000`1bb0fcc0
00000000`1bb0fc20 00000000`00000000
00000000`1bb0fc28 00000000`008c2dda Dolphin!OpcodeDecoder_Preprocess+0x14a [d:\sources\comex\source\core\videocommon\opcodedecoding.cpp @ 326]
00000000`1bb0fc30 00000000`8064cbe0
鉴于您有符号,return 地址很容易区分。
展开数据是为用户模式模块延迟加载的,因此除非有人需要,否则不会被映射。不幸的是,内核调试器不会强制为用户图像显示信息,因此有时您会遇到这种情况。您可以通过转储 PE header (!dh) 并检查异常目录 (!pte imagename+offset) 的状态来查看数据是否已映射。
假设您拥有该应用程序,请尝试通过在您的应用程序中的某处执行堆栈遍历 NOP 来强制驻留信息:
PVOID stack[2];
(VOID)CaptureStackBackTrace(0, 2, (PVOID*)&stack, NULL);
这并不能保证整个目录都存在,但通常已经足够了。
我不小心发现了一个解决这个问题的 linker 开关:/DEBUGTYPE
和 PDATA
参数。如果您 link 使用此开关的二进制文件,展开信息将被复制到您的 PDB 中。
我recompiled/relinked有问题的应用程序带有/DEBUGTYPE:CV,PDATA
(如果指定/DEBUG
,/DEBUGTYPE:CV
是默认值,请参阅documentation),现在一切工作起来很有魅力,我总是得到完整的调用堆栈。
其中一个奇怪的方面:windbg 愉快地使用在 PDB 中找到的展开数据,但忽略了映射二进制文件中的相同数据(都在主机上)。