如何获取 C#程序 内核态线程栈
一:背景
1. 讲故事
在这么多的案例分析中,往往会发现一些案例是卡死在线程的内核态栈上,但拿过来的dump都是用户态模式下,所以无法看到内核态栈,这就比较麻烦,需要让朋友通过其他方式生成一个蓝屏的dump,这里我们简单汇总下。
二:如何生成内核态dump
1. 案例代码
为了方便演示,来一段简单的测试代码,目的就是观察 Console.ReadLine 方法的内核态栈。
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("hello world!");
Console.ReadLine();
}
}
通过 任务管理器 或者 Process Explorer 默认抓取的dump都是 ntdll 之上的空间,可以用 k 来看一下。
0:000> k 3
# Child-SP RetAddr Call Site
00 000000d6`7c9fe328 00007ffe`61405593 ntdll!NtReadFile+0x14
01 000000d6`7c9fe330 00007ffd`50724782 KERNELBASE!ReadFile+0x73
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: kd> !process 0 2 ConsoleApp1.exe
PROCESS ffffdb05c1641080
SessionId: 1 Cid: 1bc8 Peb: fd877dd000 ParentCid: 15ec
DirBase: 1b9ef3000 ObjectTable: ffffa105fc3d5280 HandleCount: 161.
Image: ConsoleApp1.exe
THREAD ffffdb05bf3c7080 Cid 1bc8.0924 Teb: 000000fd877de000 Win32Thread: ffffdb05c00d0ad0 WAIT: (Executive) KernelMode Alertable
ffffdb05c1902ef8 NotificationEvent
THREAD ffffdb05c0fc6080 Cid 1bc8.07c8 Teb: 000000fd877e4000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
ffffdb05be642ae0 NotificationEvent
THREAD ffffdb05be694080 Cid 1bc8.17dc Teb: 000000fd877e6000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
ffffdb05be645860 SynchronizationEvent
ffffdb05be646e60 SynchronizationEvent
ffffdb05be645d60 SynchronizationEvent
THREAD ffffdb05be7e2080 Cid 1bc8.1020 Teb: 000000fd877e8000 Win32Thread: 0000000000000000 WAIT: (UserRequest) UserMode Non-Alertable
ffffdb05b68b53a0 NotificationEvent
ffffdb05be651de0 SynchronizationEvent
1: kd> .thread ffffdb05bf3c7080
Implicit thread is now ffffdb05`bf3c7080
1: kd> k
*** Stack trace for last set context - .thread/.cxr resets it
# Child-SP RetAddr Call Site
00 fffff50f`606ed570 fffff800`52c1c9c0 nt!KiSwapContext+0x76
01 fffff50f`606ed6b0 fffff800`52c1beef nt!KiSwapThread+0x500
02 fffff50f`606ed760 fffff800`52c1b793 nt!KiCommitThreadWait+0x14f
03 fffff50f`606ed800 fffff800`52df04c4 nt!KeWaitForSingleObject+0x233
04 fffff50f`606ed8f0 fffff800`53010cdb nt!IopWaitForSynchronousIoEvent+0x50
05 fffff50f`606ed930 fffff800`52fcc9e8 nt!IopSynchronousServiceTail+0x50b
06 fffff50f`606ed9d0 fffff800`52ff9ae8 nt!IopReadFile+0x7cc
07 fffff50f`606edac0 fffff800`52e0f3f5 nt!NtReadFile+0x8a8
08 fffff50f`606edbd0 00007ffa`2fb4d124 nt!KiSystemServiceCopyEnd+0x25
09 000000fd`8797e108 00000000`00000000 0x00007ffa`2fb4d124
从卦中看,主线程的内核态栈中的 nt!NtReadFile 函数果然给找到了。
2. 使用 procdump
如果仅仅是看线程的内核态栈,我发现有一个非常简单的方式,就是在 procudump 中多加一个 mk 参数即可,截图如下:

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

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

如何获取 C#程序 内核态线程栈的更多相关文章
- Linux0.11内核源码——内核态线程(进程)切换的实现
以fork()函数为例,分析内核态进程切换的实现 首先在用户态的某个进程中执行了fork()函数 fork引发中断,切入内核,内核栈绑定用户栈 首先分析五段论中的第一段: 中断入口:先把相关寄存器压栈 ...
- linux0.11内核源码——用户级线程及内核级线程
参考资料:哈工大操作系统mooc 用户级线程 1.每个进程执行时会有一套自己的内存映射表,即我们所谓的资源,当执行多进程时切换要切换这套内存映射表,即所谓的资源切换 2.但是如果在这个进程中创建线程, ...
- 操作系统基本概念(内核态与用户态、操作系统结构)-by sixleaves
内核态与用户态(为什么存在这种机制.程序应处于哪个状态.如何判断当前所处状态.哪些功能需要内核态.如何实现这种机制) 1.首先我们应该思考清楚为什么会有内核态和用户态?(为什么存在这种机制) 因为计算 ...
- 《windows核心编程》- 线程栈
当系统创建线程的时候,会为线程栈预订一块地址空间区域,并给该区域调拨一些物理存储器.默认会预订1MB的地址空间并调拨两个页面的存储器.但是在构建 应用程序的时候可以改变这个默认值 在构建应用程序的时候 ...
- 总在用户态调试 C# 程序,终还是搭了一个内核态环境
一:背景 一直在用 WinDbg 调试用户态程序,并没有用它调试过 内核态,毕竟不是做驱动开发,也没有在分析 dump 中需要接触用内核态的需求,但未知的事情总觉得很酷,加上最近在看 <深入解析 ...
- Windbg 内核态调试用户态程序然后下断点正确触发方法(亲自实现发现有效)
先开启真机内核态kernel调试 !process 0 0 svchost.exe 找到进程cid的地址 然后进入 .process /p fffffa8032be2870 然后 .process ...
- {Python之线程} 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Threading模块 九 锁 十 信号量 十一 事件Event 十二 条件Condition(了解) 十三 定时器
Python之线程 线程 本节目录 一 背景知识 二 线程与进程的关系 三 线程的特点 四 线程的实际应用场景 五 内存中的线程 六 用户级线程和内核级线程(了解) 七 python与线程 八 Thr ...
- RT-thread内核之线程内核对象
在RT-Thread实时操作系统中,任务采用了线程来实现,线程是RT-Thread中最基本的调度单位,它描述了一个任务执行的上下文关系,也描述了这个任务所处的优先等级.重要的任务能拥有相对较高的优先级 ...
- Linux虚拟地址空间布局以及进程栈和线程栈总结【转】
转自:http://www.cnblogs.com/xzzzh/p/6596982.html 原文链接:http://blog.csdn.net/freeelinux/article/details/ ...
- Linux虚拟地址空间布局以及进程栈和线程栈总结
原文链接:http://blog.csdn.net/freeelinux/article/details/53782986[侵删] 本文转自多个博客,以及最后有我的总结.我没有单独从头到尾写一个总结的 ...
随机推荐
- 基于DPDK抓包的Suricata安装部署
一.背景 Suricata支持网卡在线抓包和离线读取PCAP包两种形式的抓包: 离线抓包天然具有速度慢.非实时的特点 在线捕获数据包又包括常规网卡抓包.PF_RING和DPDK的方式 由于项目分光的流 ...
- Windows10彻底关闭自动更新
此文操作流程只针对于Windows10 1803及以下版本,更新版本未测试. Windows10在本地组策略编辑器禁用了自动更新,系统依然会在某个时候触发自动更新,怎么办呢,方法来了. 这个方法是结合 ...
- 微软开源了一个 助力开发LLM 加持的应用的 工具包 semantic-kernel
在首席执行官萨蒂亚·纳德拉(Satya Nadella)的支持下,微软似乎正在迅速转变为一家以人工智能为中心的公司.最近微软的众多产品线都采用GPT-4加持,从Microsoft 365等商业产品到& ...
- WebSocket集群解决方案,不用MQ
首先不了解WebSocket的可以先看看这篇文章,以及传统的WebSocket方案是怎么做的,https://www.cnblogs.com/jeremylai7/p/16875115.html ...
- Solon v2.2.7 发布,支持 Java 8 到 Java 20
Solon 是一个高效的 Java 应用开发框架:更快.更小.更简单.也是一个有自己接口标准规范的开放生态. 150来个生态插件,覆盖各种不同的应用开发场景: 相对于 Spring Boot 和 Sp ...
- MySQL相关知识点思维导图整理
MySQL相关知识点思维导图整理 Xmind思维导图下载地址: 蓝奏云:https://shuihan.lanzoui.com/iXZw7frkn5a
- SpringBoot进阶教程(七十五)数据脱敏
无论对于什么业务来说,用户数据信息的安全性无疑都是非常重要的.尤其是在数字经济大火背景下,数据的安全性就显得更加重要.数据脱敏可以分为两个部分,一个是DB层面,防止DB数据泄露,暴露用户信息:一个是接 ...
- 记某gov门户网站渗透测试(已修复)
前言: 免责声明:涉及到的所有技术仅用来学习交流,严禁用于非法用途,未经授权请勿非法渗透.否则产生的一切后果自行承担! 该渗透测试项目为已授权项目,本文已对敏感部分做了相关处理. 正文: SQL注入( ...
- JavaScript快速入门(二)
文件中引入JavaScript 嵌入到HTML文件中 在body或者head中添加script标签 <script> var age = 10; console.log(age); < ...
- TS 导入导出那些事
前言 最近用 TypeScript 写 npm 包,各种模块.命名空间.全局定义等等扰得我睡不着觉. 我便苦心研究,总结了几个比较冷门的,国内貌似基本上找不到资料的导入导出用法,顺便在其中又插入一些不 ...