如何获取 C#程序 内核态线程栈
source link: https://www.cnblogs.com/huangxincheng/p/17458487.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
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,总有一些千奇百怪的问题,太难了,这里总结一下给后来人少踩坑吧。
Recommend
-
15
发布于 2020-02-17 08:32更新于 2020-02-17 00:47 获取图片宽高的方法有很多种,本文介绍 .NET 中获取图片宽高的几种方法并评估其性能。如果你打算对大量图片进行一些处理,本文可能有用。 本文即将评估的方法...
-
29
线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分,线程有两种类型:“用户级线程”和“内核级线程”。 用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调...
-
14
上一节从C语言源代码层面较为详细的讨论了Linux创建进程的过程,其实就是创建进程运行所需的内存空间,填充描述进程的 task_struct 结构体,以及加载进程的程序而已。Linux 内核并无专门创建线程的机制我们之前提到,Linux并不特殊...
-
12
在本专栏的第41节,我们介绍了C语言程序开发中,有些任务可以放在后台运行,以避免主逻辑阻塞,造成C语言程序进入“未响应”等假死状态,影响用户使用体验。获取线程函数的返回值在C语言程序开发中,需要放在后台运行的任务通常以线程的形式实现...
-
7
我们在 linux 学习系列文章的第 8 节中提到,对于 linux 来说,线程只是一种和其他一些进程共享某些资源的特殊进程而已(例如地址空间),linux 并不严格区分线程和进程。这种设计是简洁的,因为只要使用一套 C 语言函数,和适当的参数设置,就可以实现进程和线...
-
11
Linux内核获取当前进程指针 2014-04-30 16:10:00 一、内存数据表示: 我们在教材或阅读中,经常需要直观的用图示来展示数据在内存中的分布,那么数据是如何在内存中组织的呢?不同的机器有不同的表...
-
6
获取Linux内核未导出符号 2013-05-07 18:16:00 从Linux内核的2.6某个版本开始,内核引入了导出符号的机制。只有在内核中使用EXPORT_SYMBOL或EXPORT_SYMBOL_GPL导出的符号才能在内核模块中直接使用。 ...
-
16
前言¶ 前面 pref event 示例中我们是通过 bpf_probe_read(&data.file_name, sizeof(data.file_name), PT_REGS_PARM2(ctx))...
-
3
V2EX › 程序员 请问如何获取鼠标点击处的程序信息 techstay ·
-
5
如何正确地获取线程ID? 2022-01-09 16:44:21 / easeapi / 错误反馈
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK