使用 procdump -mk 创建“内核”转储时没有堆栈

问题描述 投票:0回答:1

我正在尝试使用

-mk
(版本 11)的 
procdump
选项来获取 64 位 Windows 进程的堆栈跟踪的内核部分。内核转储已创建,但在 WinDbg 中打开它,我得到的只是单个堆栈帧 (
DbgkpLkmdSnapThreadInContext
),而不是完整的堆栈跟踪:

0: kd> k20
 # Child-SP          RetAddr               Call Site
00 fffff50e`7b54df30 00000000`00000000     nt!DbgkpLkmdSnapThreadInContext+0x9d

这是在 Windows 11 22H2 (22621.4169) 上完成的,但我在 Windows 10 上也得到了相同的结果。 我做错了什么吗?还是 procdump 的

-mk
选项坏了?


更多详情: 例如,我从 Windows 终端调用 procdump(以管理员权限运行)来创建完全相同的 Windows 终端进程 (pid 46304) 的转储:

PS C:\dev\Tools>  .\procdump.exe -mk -ma 46304 "C:\dev\test\dump.dmp"

ProcDump v11.0 - Sysinternals process dump utility
Copyright (C) 2009-2022 Mark Russinovich and Andrew Richards
Sysinternals - www.sysinternals.com

[08:23:43] Dump 1 initiated: C:\dev\test\dump.dmp
[08:23:43] Dump 1 writing: Estimated dump file size is 535 MB.
[08:23:44] Dump 1 complete: 536 MB written in 1.5 seconds
[08:23:44] Dump 1 kernel: C:\dev\test\dump.Kernel.dmp
[08:23:44] Dump count reached.

用户模式转储 (

dump.dmp
) 显示该进程位于系统调用内部:

0:000> k9
 # Child-SP          RetAddr               Call Site
00 00000048`c24ff558 00007ffd`199b53fa     win32u!NtUserGetMessage+0x14
01 00000048`c24ff560 00007ff6`f0b840b5     user32!GetMessageW+0x2a
02 (Inline Function) --------`--------     WindowsTerminal!WindowEmperor::WaitForWindows+0x61 [C:\__w\1\s\src\cascadia\WindowsTerminal\WindowEmperor.cpp @ 125] 
03 00000048`c24ff5c0 00007ff6`f0b8e2a6     WindowsTerminal!WindowEmperor::HandleCommandlineArgs+0x535 [C:\__w\1\s\src\cascadia\WindowsTerminal\WindowEmperor.cpp @ 102] 
04 00000048`c24ff8a0 00007ff6`f0b927f2     WindowsTerminal!wWinMain+0x166 [C:\__w\1\s\src\cascadia\WindowsTerminal\main.cpp @ 118] 
05 (Inline Function) --------`--------     WindowsTerminal!invoke_main+0x21 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 118] 
06 00000048`c24ff980 00007ffd`19e7257d     WindowsTerminal!__scrt_common_main_seh+0x106 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
07 00000048`c24ff9c0 00007ffd`1bc2af28     kernel32!BaseThreadInitThunk+0x1d
08 00000048`c24ff9f0 00000000`00000000     ntdll!RtlUserThreadStart+0x28

RIP 指向

00007ffd`198d1534
:

win32u!NtUserGetMessage:
00007ffd`198d1520 4c8bd1           mov     r10, rcx
00007ffd`198d1523 b804100000       mov     eax, 1004h
00007ffd`198d1528 f604250803fe7f01 test    byte ptr [7FFE0308h], 1
00007ffd`198d1530 7503             jne     win32u!NtUserGetMessage+0x15 (7ffd198d1535)
00007ffd`198d1532 0f05             syscall 
00007ffd`198d1534 c3               ret

所以我确实希望得到一些内核框架。 但是打开由 procdump 创建的内核转储 (

dump.Kernel.dmp
),我看到的只是
DbgkpLkmdSnapThreadInContext
堆栈帧,即使在显式切换到我的进程之后也是如此。另一方面,当收集实时内核转储时,我确实看到堆栈确实在继续:

0: kd> k20
 # Child-SP          RetAddr               Call Site
00 fffff50e`7bb86ba0 fffff803`48e5ffa5     nt!KiSwapContext+0x76
01 fffff50e`7bb86ce0 fffff803`48e613c7     nt!KiSwapThread+0xaa5
02 fffff50e`7bb86e30 fffff803`48f40176     nt!KiCommitThreadWait+0x137
03 fffff50e`7bb86ee0 fffff803`48f51c93     nt!KeWaitForSingleObject+0x256
04 fffff50e`7bb87280 ffff8968`68f4d670     nt!KeWaitForMultipleObjects+0x5d3
05 fffff50e`7bb874e0 ffff8968`68f4d2af     win32kfull!xxxRealSleepThread+0x310
06 fffff50e`7bb87600 ffff8968`68f508ab     win32kfull!xxxSleepThread2+0xaf
07 fffff50e`7bb87650 ffff8968`68f4dadc     win32kfull!xxxRealInternalGetMessage+0x15bb
08 fffff50e`7bb87950 ffff8968`687d6ed2     win32kfull!NtUserGetMessage+0x8c
09 fffff50e`7bb879e0 fffff803`4902b605     win32k!NtUserGetMessage+0x16
0a fffff50e`7bb87a20 00007ffd`198d1534     nt!KiSystemServiceCopyEnd+0x25
0b 00000048`c30ff8e8 00007ffd`199b53fa     win32u!NtUserGetMessage+0x14
0c 00000048`c30ff8f0 00000000`00000000     user32!GetMessageW+0x2a

但是procdump似乎无法捕获它。为什么?

上下文:我们已经在测试系统中使用 procdump 来自动创建挂起/崩溃进程的转储。我尝试扩展它以创建相关进程的内核转储(当然,仅堆栈跟踪)。

windows windbg dump crash-dumps procdump
1个回答
0
投票

TL;DR: 如果处理器中的逻辑核心过多(任何高于或接近的逻辑核心数),“内核小型转储”又名“内核分类转储”在当前 Windows 版本(至少 Windows 11 22H2)上会被破坏20 是危险的)。原因:转储限制为 1MB,并且包含每个核心的信息。如果核心太多,仅使用核心信息就达到 1MB 限制。


经过相当多的挖掘(通过 ProcMon 和反汇编程序),我想我知道发生了什么:首先,

procdump -mk
正在使用 WinAPI MiniDumpWriteDump()WriteKernelMinidumpCallback 功能来创建“内核迷你转储” ”.

该 API 在内部使用未记录的系统调用

NtSystemDebugControl()
。除此之外,这个系统调用实现了“内核迷你转储”,在该上下文中称为“内核分类转储”。人们可以在线找到有关该系统调用的一些信息,例如这里这里这里
SYSDBG_COMMAND
NtSystemDebugControl()
参数(第一个参数)需要设置为
SysDbgGetTriageDump = 29
。 此外,还需要传入一个充满数据的缓冲区以及缓冲区的大小。支持的最大缓冲区大小为 1MB。尝试传递更大的大小仍会产生最多 1MB 的转储。反汇编
NtSystemDebugControl()
中的
ntoskrnl.exe
时可以看到这一点(我使用的版本是10.0.19041.5007,Windows 10 22H2),其中包含诸如

之类的代码
usedBufferSize = 0x100000;
if (givenBufferSize <= 0x100000)
   usedBufferSize = givenBufferSize;

其中

givenBufferSize
是在调用
NtSystemDebugControl()
时指定的缓冲区大小。

然后代码调用内部函数

DbgkCaptureLiveDump()
(以创建内核分类转储),其中包含对
DbgkpLkmdSnapGlobals()
的调用。该函数基本上获取处理器信息(通过
KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS)
KeGetPrcb()
KeEnumerateProcessorDpcs()
)。就我而言,我有 16 个具有 SMT 的真实核心,即 32 个逻辑核心。因此,
KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS)
返回32。代码循环遍历每个“逻辑处理器”(意味着每个逻辑核心)并获取其“内核处理器控制块”(KPRCB)。然后,它显然将每一个写入分类转储缓冲区。事实是,KPRCB 结构非常庞大,并且从 Windows 版本到版本不断增长。在 Windows 11 23H2 上,它的大小似乎为 0xBF00。就我而言(Windows 10 22H2),我可以从反汇编中看到它的大小为 0xAF00 字节。 给定 1MB 缓冲区,我们可以将 23 KPRCB 放入该缓冲区。因此,如果逻辑核心超过 23 个,则整个缓冲区将仅填充处理器信息,而不会为实际感兴趣的数据(调用堆栈等)留下空间。 在更高版本的 Windows 上,情况更糟,因为 KPRCB 结构的大小甚至更大(Windows 11 23H2:0xBF00,意味着 21 个核心已填充转储)。 确实,我使用了虚拟机(Windows 10 22H2)并改变了虚拟机可用的核心数量。对于

=22 个核心,WinDbg 无法做到这一点。该门槛非常接近 23 个 KPRCB。 此外,在 32 个核心上使用转储时,WinDbg 命令

!prcb 0x15<=21 cores, WinDbg is able to display proper call stacks from the kernel triage dump. But with > (0x15=21) 显示信息,而

!prcb 0x16
(0x16=22) 失败。
因此,由于大小限制为 1MB,内核分类转储在具有多个内核的现代 CPU 上很容易被破坏(或至少无用)。

如果有人想直接玩

NtSystemDebugControl()

github.com/Sedeniono/livedump
.

© www.soinside.com 2019 - 2024. All rights reserved.