一、简介
今天是《Net 高级调试》的第六篇文章。上一篇文章我们说了值类型,引用类型,数组等的内存表现形式。有了这个基础,我们可以更好的了解我们的程序在运行时的状态,内存里有什么东西,它们的结构组成是什么样子的,对我们调试程序是更有帮助的。今天,我们要说一些和线程有关的话题,虽然和线程相关,但是不是多线程的知识,不是线程安全的知识。今天我们讨论的是如何查看线程,它的表现形式,以及线程的调用栈,调用栈,又分为托管线程的调用栈和非托管线程的调用栈,这些也是我们高级调试必须掌握的。有了这些基础,我们就知道了程序的开始端点,调试的起点我们就找到了。虽然这些都是基础,如果这些掌握不好,以后的高级调试的道路,也不好走。当然了,第一次看视频或者看书,是很迷糊的,不知道如何操作,还是那句老话,一遍不行,那就再来一遍,还不行,那就再来一遍,俗话说的好,书读千遍,其意自现。
如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
操作系统:Windows Professional 10
调试工具:Windbg Preview(可以去Microsoft Store 去下载)
开发工具:Visual Studio 2022
Net 版本:Net Framework 4.8
CoreCLR源码:源码下载
二、基础知识
1、线程类相关介绍
1.1、简介
在高级调试的过程中,很难不和 线程、线程栈 打交道,所以好好的学习并掌握有关线程操作的命令是很有必要的。
2、获取 线程列表 的命令
2.1、查看线程列表。
可以使用【!t】命令获取所有的托管线程。
2.2、使用 Net 7.0 查看线程列表
可以使用【!t】命令获取所有的托管线程,如果我们想查看 CLR 的线程对象的结构就只能使用开源版本了,这里使用Net 7.0。
3、查看非托管线程栈
Windbg 是随 Windows 成长起来的非托管调试器,它自带的命令只能查看非托管调用栈,因为 C# 中的线程栈是托管函数,而托管函数是一种运行时编译的,所以这个命令往往看不到托管部分。
3.1、使用 k 命令查看非托管线程栈
【k】命令可以查看线程栈,但是么有办法展示托管函数,作为补充,可以使用【!clrstack】命令。
3.2、使用 kb 命令查看线程栈。
很多时候我们需要获取非托管函数的参数,这个时候我们可以使用【kb】命令。
3.3、查看托管函数栈
SOS 提供了一个专门查看托管函数调用栈的命令,毕竟只有 JIT更熟悉托管函数,也知道编译后的机器码放在什么位置。
这个命令就是【!clrstack】。
!clrstack -a:这个命令表示将线程栈中的所有局部变量和参数全部输出。
!clrstack -p:这个命令表示将线程栈中的参数全部输出。
!clrstack -l:这个命令表示将线程栈中的所有局部变量全部输出。
我们还可以查看所有托管线程栈,可以使用【~*e】命令。
3.4、查看托管和非托管合体
使用【!dumpstack】命令查看托管和非托管的线程栈。
3.5、执行所有线程的 DumpStack。
如果我们想查看所有线程的线程栈,可以使用【!EEStack】,也可以使用【~*e !dumpstack】,结果是一样的。
三、测试过程
废话不多说,这一节是具体的调试操作的过程,又可以说是眼见为实的过程,在开始之前,我还是要啰嗦两句,这一节分为两个部分,第一部分是测试的源码部分,没有代码,当然就谈不上测试了,调试必须有载体。第二部分就是根据具体的代码来证实我们学到的知识,是具体的眼见为实。
1、测试源码
1.1、Example_7_1_1
1 namespace Example_7_1_1 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 int a = 10; 8 int b = 11; 9 Test(12); 10 Console.ReadLine(); 11 } 12 13 private static void Test(int c) 14 { 15 Task.Run(() => { Run1(); }); 16 Task.Run(() => { Run2(); }); 17 Task.Run(() => { Run3(); }); 18 } 19 20 private static void Run1() 21 { 22 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run1 正在运行"); 23 Console.ReadLine(); 24 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run1 结束运行"); 25 } 26 27 private static void Run2() 28 { 29 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run2 正在运行"); 30 Console.ReadLine(); 31 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run2 结束运行"); 32 } 33 34 private static void Run3() 35 { 36 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run3 正在运行"); 37 Console.ReadLine(); 38 Console.WriteLine($"tid={Environment.CurrentManagedThreadId},Run3 结束运行"); 39 } 40 } 41 }