翼度科技»论坛 编程开发 .net 查看内容

如何获取 C#程序 内核态线程栈

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
一:背景

1. 讲故事

在这么多的案例分析中,往往会发现一些案例是卡死在线程的内核态栈上,但拿过来的dump都是用户态模式下,所以无法看到内核态栈,这就比较麻烦,需要让朋友通过其他方式生成一个蓝屏的dump,这里我们简单汇总下。
二:如何生成内核态dump

1. 案例代码

为了方便演示,来一段简单的测试代码,目的就是观察 Console.ReadLine 方法的内核态栈。
  1.     internal class Program
  2.     {
  3.         static void Main(string[] args)
  4.         {
  5.             Console.WriteLine("hello world!");
  6.             Console.ReadLine();
  7.         }
  8.     }
复制代码
通过 任务管理器 或者 Process Explorer 默认抓取的dump都是 ntdll 之上的空间,可以用 k 来看一下。
  1. 0:000> k 3
  2. # Child-SP          RetAddr               Call Site
  3. 00 000000d6`7c9fe328 00007ffe`61405593     ntdll!NtReadFile+0x14
  4. 01 000000d6`7c9fe330 00007ffd`50724782     KERNELBASE!ReadFile+0x73
  5. 02 000000d6`7c9fe3b0 00007ffe`215bc742     0x00007ffd`50724782
复制代码
问题来了,如果我要看下 ntdll!NtReadFile 函数对应在内核态中的 nt!NtReadFile 方法怎么办呢?只能抓内核态dump,抓内核态dump的方式有很多,这里聊一下其中的两种方式。
2. 使用 notmyfault 抓取

说到 蓝屏 我相信有很多朋友都知道,简而言之就是内核态代码出bug导致系统崩溃,也有朋友知道通过增加一些配置可以在蓝屏的时候自动生成 dump 文件,这种 dump 文件就属于内核态,配置如下:

但这里有一个问题,操作系统不可能无缘无故的蓝屏,那怎么办呢?微软想了一个办法,人为的造蓝屏,所以提供了一个叫 notmyfault.exe 的工具, MSDN网址:https://learn.microsoft.com/en-us/sysinternals/downloads/notmyfault
有了这些前置基础,接下来就可以操练一下,双击 notmyfault.exe 工具,崩溃原因选择默认的 High IRQL fault,最后点击 Crash 按钮,稍等片刻电脑就会蓝屏。截图如下:

我这里用的是一台物理的 迷你主机 测试,再次远程连接后,在 C:\Windows 下会生成一个 MEMORY.dmp 文件,截图如下:

拿到 dump 之后就可以用 windbg 中的 !process 之类的命令分析了,非常爽。
  1. 1: kd>  !process 0 2 ConsoleApp1.exe
  2. PROCESS ffffdb05c1641080
  3.     SessionId: 1  Cid: 1bc8    Peb: fd877dd000  ParentCid: 15ec
  4.     DirBase: 1b9ef3000  ObjectTable: ffffa105fc3d5280  HandleCount: 161.
  5.     Image: ConsoleApp1.exe
  6.         THREAD ffffdb05bf3c7080  Cid 1bc8.0924  Teb: 000000fd877de000 Win32Thread: ffffdb05c00d0ad0 WAIT: (Executive) KernelMode Alertable
  7.             ffffdb05c1902ef8  NotificationEvent
  8.         THREAD ffffdb05c0fc6080  Cid 1bc8.07c8  Teb: 000000fd877e4000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
  9.             ffffdb05be642ae0  NotificationEvent
  10.         THREAD ffffdb05be694080  Cid 1bc8.17dc  Teb: 000000fd877e6000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
  11.             ffffdb05be645860  SynchronizationEvent
  12.             ffffdb05be646e60  SynchronizationEvent
  13.             ffffdb05be645d60  SynchronizationEvent
  14.         THREAD ffffdb05be7e2080  Cid 1bc8.1020  Teb: 000000fd877e8000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
  15.             ffffdb05b68b53a0  NotificationEvent
  16.             ffffdb05be651de0  SynchronizationEvent
  17. 1: kd> .thread ffffdb05bf3c7080
  18. Implicit thread is now ffffdb05`bf3c7080
  19. 1: kd> k
  20.   *** Stack trace for last set context - .thread/.cxr resets it
  21. # Child-SP          RetAddr               Call Site
  22. 00 fffff50f`606ed570 fffff800`52c1c9c0     nt!KiSwapContext+0x76
  23. 01 fffff50f`606ed6b0 fffff800`52c1beef     nt!KiSwapThread+0x500
  24. 02 fffff50f`606ed760 fffff800`52c1b793     nt!KiCommitThreadWait+0x14f
  25. 03 fffff50f`606ed800 fffff800`52df04c4     nt!KeWaitForSingleObject+0x233
  26. 04 fffff50f`606ed8f0 fffff800`53010cdb     nt!IopWaitForSynchronousIoEvent+0x50
  27. 05 fffff50f`606ed930 fffff800`52fcc9e8     nt!IopSynchronousServiceTail+0x50b
  28. 06 fffff50f`606ed9d0 fffff800`52ff9ae8     nt!IopReadFile+0x7cc
  29. 07 fffff50f`606edac0 fffff800`52e0f3f5     nt!NtReadFile+0x8a8
  30. 08 fffff50f`606edbd0 00007ffa`2fb4d124     nt!KiSystemServiceCopyEnd+0x25
  31. 09 000000fd`8797e108 00000000`00000000     0x00007ffa`2fb4d124
复制代码
从卦中看,主线程的内核态栈中的 nt!NtReadFile 函数果然给找到了。
2. 使用 procdump

如果仅仅是看线程的内核态栈,我发现有一个非常简单的方式,就是在 procudump 中多加一个 mk 参数即可,截图如下:

接下来使用 Terminal 执行 procdump,输出如下:
  1. PS C:\Users\Administrator\Desktop> procdump -ma -mk ConsoleApp -o D:\testdump
  2. ProcDump v11.0 - Sysinternals process dump utility
  3. Copyright (C) 2009-2022 Mark Russinovich and Andrew Richards
  4. Sysinternals - www.sysinternals.com
  5. [16:24:49] Dump 1 initiated: D:\testdump\ConsoleApp1.exe_230605_162449.dmp
  6. [16:24:50] Dump 1 writing: Estimated dump file size is 57 MB.
  7. [16:24:50] Dump 1 complete: 57 MB written in 0.1 seconds
  8. [16:24:50] Dump 1 kernel: D:\testdump\ConsoleApp1.exe_230605_162449.Kernel.dmp
  9. [16:24:50] Dump count reached.
复制代码

从卦中看,当前生成了两个 dmp 文件,一个是用户态dump,一个是内核态dump,也能看到后者还不到 1M,和刚才用 notmyfault 生成的 500M dump 所存储的信息量相差甚远,但对我目前的场景来说已经够用了。
接下来打开 ConsoleApp1.exe_230605_162449.Kernel.dmp 文件,使用 !process 找到 ConsoleApp1.exe 的进程。
  1. ..................................................
  2. For analysis of this file, run !analyze -v
  3. nt!DbgkpLkmdSnapThreadInContext+0x95:
  4. fffff804`5e688b51 488364242800    and     qword ptr [rsp+28h],0 ss:0018:ffffe10d`62386fd8=ffffe10d5b8fa810
  5. 0: kd> !process 0 2 ConsoleApp1.exe
  6. Unable to read _LIST_ENTRY @ fffff8045ea1e080
  7. 0: kd> .reload /user
  8. Loading User Symbols
  9. 0: kd> !process 0 2 ConsoleApp1.exe
  10. Unable to read _LIST_ENTRY @ fffff8045ea1e080
复制代码
从卦中看居然报错了,那怎么办呢?办法肯定是有办法的,可以到用户态dump中寻找进程ID即可。
  1. 0:000> ~
  2. .  0  Id: 3adc.5920 Suspend: 0 Teb: 000000d6`7cb98000 Unfrozen
  3.    1  Id: 3adc.2240 Suspend: 0 Teb: 000000d6`7cba0000 Unfrozen
  4.    2  Id: 3adc.514 Suspend: 0 Teb: 000000d6`7cba2000 Unfrozen
  5.    3  Id: 3adc.3c68 Suspend: 0 Teb: 000000d6`7cba4000 Unfrozen ".NET Finalizer"
复制代码
拿到 3adc 进程号后再找下面的主线程,观察它的线程栈信息,输出如下:
  1. 0: kd> .process 3adc
  2. Implicit process is now 00000000`00003adc
  3. 0: kd> !process
  4. PROCESS ffffcf8d5d5b0080
  5.     SessionId: none  Cid: 3adc    Peb: d67cb97000  ParentCid: 4c80
  6.     DirBase: 367d95000  ObjectTable: ffff8e81710bbb40  HandleCount: <Data Not Accessible>
  7.     Image: ConsoleApp1.ex
  8.     VadRoot ffffcf8d5b20fcb0 Vads 90 Clone 0 Private 1529. Modified 941. Locked 2.
  9.     DeviceMap ffff8e8172645110
  10.     Token                             ffff8e815e216060
  11.     ReadMemory error: Cannot get nt!KeMaximumIncrement value.
  12. fffff78000000000: Unable to get shared data
  13.     ElapsedTime                       00:00:00.000
  14.     UserTime                          00:00:00.000
  15.     KernelTime                        00:00:00.000
  16.     QuotaPoolUsage[PagedPool]         153768
  17.     QuotaPoolUsage[NonPagedPool]      12648
  18.     Working Set Sizes (now,min,max)  (14126, 50, 345) (56504KB, 200KB, 1380KB)
  19.     PeakWorkingSetSize                14033
  20.     VirtualSize                       2101882 Mb
  21.     PeakVirtualSize                   2101888 Mb
  22.     PageFaultCount                    15757
  23.     MemoryPriority                    BACKGROUND
  24.     BasePriority                      8
  25.     CommitCharge                      1628
  26.     Job                               ffffcf8d53a102c0
  27.         THREAD ffffcf8d5ae14080  Cid 3adc.5920  Teb: 000000d67cb98000 Win32Thread: ffffcf8d54c3a3b0 RUNNING on processor 0
  28.         THREAD ffffcf8d4f63e080  Cid 3adc.2240  Teb: 000000d67cba0000 Win32Thread: 0000000000000000 INVALID
  29.         THREAD ffffcf8d69a32080  Cid 3adc.0514  Teb: 000000d67cba2000 Win32Thread: 0000000000000000 INVALID
  30.         THREAD ffffcf8d55003580  Cid 3adc.3c68  Teb: 000000d67cba4000 Win32Thread: 0000000000000000 INVALID
  31. 0: kd> .thread ffffcf8d5ae14080
  32. Implicit thread is now ffffcf8d`5ae14080
  33. 0: kd> k
  34.   *** Stack trace for last set context - .thread/.cxr resets it
  35. # Child-SP          RetAddr               Call Site
  36. 00 ffffe10d`62386fb0 fffff804`5e688a7b     nt!DbgkpLkmdSnapThreadInContext+0x95
  37. 01 ffffe10d`623874f0 fffff804`5e01dcd0     nt!DbgkpLkmdSnapThreadApc+0x3b
  38. 02 ffffe10d`62387520 fffff804`5e01bb67     nt!KiDeliverApc+0x1b0
  39. 03 ffffe10d`623875d0 fffff804`5e01ad6f     nt!KiSwapThread+0x827
  40. 04 ffffe10d`62387680 fffff804`5e01a613     nt!KiCommitThreadWait+0x14f
  41. 05 ffffe10d`62387720 fffff804`5e439c68     nt!KeWaitForSingleObject+0x233
  42. 06 ffffe10d`62387810 fffff804`5e411fe9     nt!IopSynchronousServiceTail+0x238
  43. 07 ffffe10d`623878b0 fffff804`5e20d9f5     nt!NtReadFile+0x599
  44. 08 ffffe10d`62387990 00007ffe`6390d184     nt!KiSystemServiceCopyEnd+0x25
  45. 09 000000d6`7c9fe328 00000000`00000000     0x00007ffe`6390d184
复制代码
怎么样,上面的 nt!NtReadFile+0x599 函数就是。
三:总结

有时候真的需要去抓内核态dump,总有一些千奇百怪的问题,太难了,这里总结一下给后来人少踩坑吧。

来源:https://www.cnblogs.com/huangxincheng/archive/2023/06/05/17458487.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具