一、简介
    今天是《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

Net 高级调试之七:线程操作相关命令介绍-LMLPHPNet 高级调试之七:线程操作相关命令介绍-LMLPHP
 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 }
11-10 11:29