Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

这篇具有很好参考价值的文章主要介绍了Net 高级调试之二:CLR和Windows加载器及应用程序域介绍。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、简介
    今天是 Net 高级调试的第二篇文章,第一篇文章记录了自己学习 Net 高级调试的第一步,认识一些调试工具,有了工具的倚仗,我们开始仗剑走天涯了,开始Net 高级调试正式的征程了。我先说一下,我的文章,【调试测试】这部分一般分为两个部分,第一部分是要用到的所有测试代码样例,也为大家提供方便,我第一次做测试还是走了不少弯路的。第二部分,就是使用 Windbg 调试器调试代码的部分,但是,需要说明一下,使用 Windbg还是有一些技巧的,或者说是方法的,如果大家不熟悉,建议提前熟悉一下,因为我的测试过程,不会把所有的过程都照搬下来,会省略一下不太重要的步骤,但是,如果是第一次使用这个软件的,调试的时候,得到的结果可能就和我的不一样,这也是我的一步一步的、痛苦的经验。
    如果在没有说明的情况下,所有代码的测试环境都是 Net Framewok 4.8,但是,有时候为了查看源码,可能需要使用 Net Core 的项目,我会在项目章节里进行说明。好了,废话不多说,开始我们今天的调试工作。
    调试环境我需要进行说明,以防大家不清楚,具体情况我已经罗列出来。
          操作系统:Windows Professional 10
          调试工具:Windbg Preview(可以去Microsoft Store 去下载)
          开发工具:Visual Studio 2022
          Net 版本:Net Framework 4.8
          CoreCLR源码:源码下载
二、相关概念
    1、Net 框架
        Net 是一个虚拟的运行时环境,包含了一个虚拟的执行引擎(CLR)和一组相关的框架类库,如图:
        Net 高级调试之二:CLR和Windows加载器及应用程序域介绍
        1.1、宏观概念
            
              a)、ECMA
                C# 语言和公共语言基础结构 (CLI) 规范通过 Ecma International® 进行标准化。用通俗的话来说,ECMA是一个标准,它就是一个CLR的开发规范,或者说是一个设计文档。
                https://learn.microsoft.com/zh-cn/dotnet/fundamentals/standards

            b)、CLR
                公共语言运行时。是我们 C#,VB.Net,F#的运行时环境,当然,这也是高级调试要关注的部分。
                CLR 处理内存分配和管理。
                CLR 也是一种虚拟机,不仅可执行应用,还可使用 JIT 编译器快速生成和编译代码。
                最后,我们总结一下,CLR是针对 ECMA 标准的落地实现。

            c)、NET 框架
                NET框架有很多,比如:WPF,WinForm,WebForm,Mvc,WebAPI 等。

            d)、Net应用程序
                NET 应用程序,更多的指的是用户编写的应用程序,比如:基于 Winform 的ERP,基于 MVC、API 实现的网站系统。

        1.2、Net程序的编译过程
            Net程序的编译一般分为两个阶段,第一个阶段就是编译器编译,将C# 源码编译成为 IL 代码,第二个阶段就是 JIT 编译,将 IL 代码编译成为可以直接运行的机器代码。
            a)、编译器编译
                将我们的C#、VB.Net、F#等源码使用 Visual Studio,或者是 CSC 等类似的工具转换为 IL 代码。当然 IL 代码是不能直接运行的。
                当然,IL 代码也是可以看到的,我们可以使用 ILSpy,或者DnSpy工具,加载相应的程序集,就可以查看了,很简单,就不细说了。

            b)、JIT编译
                CLR 运行时会将 IL 代码转换成 机器代码。

            流程如下:【C# 源码】======》【编译器】=======》【Net 程序集(Exe或者Dll)】=====》【JIT即时编译(CLR)】=====》【机器代码】
                Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

    2、PE头及Windows 加载器
        
        2.1、什么是PE文件
            PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件,它的一个非常大的作用就是帮助 Windows 加载器 执行程序的入口。
            对于 Net 的 PE 文件,有几点需要注意:
            a)、AddressOfEntryPoint
               程序的入口点相对偏移地址,即(exe+AddressOfEntryPoint)。
              Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

            b)、DIRECTORY_ENTRY_COM_DESCRIPTOR
               Net 程序独有的节点配置。
              Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

               ILSpy 查看 Example_2_1_1.exe 的元数据。
                Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

            c)、EntryPointToken
                这个标签的地址,就是我们程序 Program.Main 方法的入口点地址。
                IL 代码里面也是有标记的。
                Net 高级调试之二:CLR和Windows加载器及应用程序域介绍 
             
        2.2、小知识
             Windbg 有一个伪寄存器命令 ?  $exentry,可以直接告诉我们 exe 程序的入口点地址。

    3、应用程序域        
         3.1、简介
          
对于 Windows 上的应用程序,大家都知道是按照【进程】进行隔离的Net 将这种进程隔离缩小到了【应用程序域】层,即一个进程会有多个【应用程序域】,然后将应用程序部署在【应用程序域】上。
          
在 CLR 上,应用程序域分为三类,分别是:SystemDomain、SharedDomain、Domain1。当然,这是说的在 Net Framework 的情况下,在 Net Core 框架下,只有两个应用程序域,分别是:SystemDomain、Domain1,去掉了 SharedDomain 这个应用程序域。

      
3.2、应用程序域
             a)、SystemDomain
                  
系统及作用域,用于创建其他作用域。
                
将 mscorlib.dll 加载到 SharedDomain 共享及应用程序域。
                记录字符串池中字符串常量。
                初始化特定异常(OutOfMemoryException、StackOverflowException)。

           b)、SharedDomain
                
加载 System 命名空间下的基本类型(String,Enum,ValueType)

           c)、Domain1
                用户的应用程序都是在这个域中运行。

三、调试测试
    这个章节里,很简单,一共分为两个部分,第一部分是要用到的测试代码的样例,第二部分,就是具体的测试操作过程,说明一下,假设大家对 Windbg 有些熟悉。

    1、测试代码

        1.1、代码样例1           
 1 namespace Example_2_1_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Console.WriteLine("Hello World");
 8             Console.ReadLine();
 9         }
10     }
11 }

    2、调试过程
        2.1、验证 CLR 和 JIT 的 存在。
            验证代码:Example_2_1_1
            操作描述:编译 Example_2_1_1 项目,打开 Windbg,通过【launch executable】加载我们的程序集。当我们成功加载程序集,还必须通过【g】命令,或者【Go】按钮执行程序,这个时候,才能加载所有的东西。当我们运行完之后,就能看到运行界面,就可以看到和 CLR 和 JIT 有关的东西。红色字体表明加载了 CLR 和 JIT 两个组件。
    
 1 0:000> g
 2 ModLoad: 74ec0000 74f39000   C:\Windows\SysWOW64\ADVAPI32.dll
 3 ModLoad: 771f0000 772af000   C:\Windows\SysWOW64\msvcrt.dll
 4 ModLoad: 757e0000 75855000   C:\Windows\SysWOW64\sechost.dll
 5 ModLoad: 753c0000 7547a000   C:\Windows\SysWOW64\RPCRT4.dll
 6 ModLoad: 711c0000 7124d000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll
 7 ModLoad: 771a0000 771e5000   C:\Windows\SysWOW64\SHLWAPI.dll
 8 ModLoad: 757d0000 757df000   C:\Windows\SysWOW64\kernel.appcore.dll
 9 ModLoad: 74eb0000 74eb8000   C:\Windows\SysWOW64\VERSION.dll
10 ModLoad: 70a10000 711c0000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll(CLR组件,加载的起始地址:70a1000011 ModLoad: 75870000 75a04000   C:\Windows\SysWOW64\USER32.dll
12 ModLoad: 77070000 77088000   C:\Windows\SysWOW64\win32u.dll
13 ModLoad: 70940000 709eb000   C:\Windows\SysWOW64\ucrtbase_clr0400.dll
14 ModLoad: 75fb0000 75fd3000   C:\Windows\SysWOW64\GDI32.dll
15 ModLoad: 709f0000 70a04000   C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll
16 ModLoad: 750c0000 7519b000   C:\Windows\SysWOW64\gdi32full.dll
17 ModLoad: 76390000 7640b000   C:\Windows\SysWOW64\msvcp_win.dll
18 ModLoad: 75550000 75670000   C:\Windows\SysWOW64\ucrtbase.dll
19 ModLoad: 74fa0000 74fc5000   C:\Windows\SysWOW64\IMM32.DLL
20 ModLoad: 75ff0000 76270000   C:\Windows\SysWOW64\combase.dll
21 (7fc.b18): Unknown exception - code 04242420 (first chance)
22 ModLoad: 6f530000 7093e000   C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\218db16dceaef380c6daf35c6a48f313\mscorlib.ni.dll
23 ModLoad: 76490000 76573000   C:\Windows\SysWOW64\ole32.dll
24 ModLoad: 75ff0000 76270000   C:\Windows\SysWOW64\combase.dll
25 ModLoad: 752a0000 752fc000   C:\Windows\SysWOW64\bcryptPrimitives.dll
26 ModLoad: 6f4a0000 6f52a000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll(这个就是JIT编译器组件,在进程中的起始地址:6f4a000027 ModLoad: 751a0000 7523b000   C:\Windows\SysWOW64\OLEAUT32.dll

            
        2.2、证明 Windows 加载器如何加载一个 Net 的程序集。
            验证代码:Example_2_1_1
            PPEE是绿色软件,不用安装,直接下载就可以使用。打开 PPEE 软件,打开 Example_2_1_1.exe,就可以看到 PE 文件。
            当我们双加一个 Net 的Exe应用程序的时候,操作系统会做很多工作,比如:在内核态生成进行的地址空间,地址空间生成成功后,然后在生成一个Process 的进程,再给这个进程生成一个主线程,在内核态还要针对进程生成 EProcess 的数据结构,针对线程生成一个 ETHREAD 的数据结构。当所有的准备工作都完成后,要开始执行程序,从哪里开始执行程序呢?我们来证明一下,或者说熟悉一下,这个过程就和 PE 头文件有关了。


            a、Windows 加载器会读取 PE 文件头里面的数据,来确定从哪里开始执行,第一步,我们通过 PPEE 查看 Example_2_1_1.exe PE 文件,在 PE 头里依次点击【NT Header】-->【Optional Header】,在窗体的右侧就可以看到,有一项【AddressOfEntryPoint】,它的值是:00002782,这个地址是相对地址,是相对程序进程起始地址来说的。
              如图:
              Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

              我们有了入口程序的相对起始地址,我们找一下应用程序的进程起始地址,二者相加,就是 Windows 加载器要执行的地址。想要查看 Example_2_1_1.exe 进程地址,需要借助 Windbg,红色部分就是 Example_2_1_1.exe 进程的起始地址。

              代码如下:

1 Executable search path is: 
2 ModLoad: 00ca0000 00ca8000   Example_2_1_1.exe
3 ModLoad: 770d0000 77272000   ntdll.dll
4 ModLoad: 71050000 710a2000   C:\Windows\SysWOW64\MSCOREE.DLL
5 ModLoad: 74cc0000 74db0000   C:\Windows\SysWOW64\KERNEL32.dll
6 ModLoad: 767a0000 769b3000   C:\Windows\SysWOW64\KERNELBASE.dll

              二者相加就是WIndows 加载器的入口点地址,还不是我们的 Program.Main的地址,00ca0000(Example_2_1_1进程起始地址),00002782 是 PE 头告诉的入口点地址,我们通过 U 命令,可以查看汇编代码。通过代码我们可以看到,执行了 jmp 指令,跳转的地址是:402000h
              

 1 0:000> u 00ca0000+00002782
 2 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2782(PE 头里的值)):
 3 00ca2782 ff2500204000    jmp(执行跳转指令)     dword ptr ds:[402000h]
 4 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2788):
 5 00ca2788 0000            add     byte ptr [eax],al
 6 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x278a):
 7 00ca278a 0000            add     byte ptr [eax],al
 8 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x278c):
 9 00ca278c 0000            add     byte ptr [eax],al
10 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x278e):
11 00ca278e 0000            add     byte ptr [eax],al
12 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2790):
13 00ca2790 0000            add     byte ptr [eax],al
14 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2792):
15 00ca2792 0000            add     byte ptr [eax],al
16 Example_2_1_1!COM+_Entry_Point <PERF> (Example_2_1_1+0x2794):
17 00ca2794 0000            add     byte ptr [eax],al

                接下来,我们看看 402000h 这个地址有什么东西。

                    再次执行 Windbg,重新加载 Example_2_1_1.exe,通过【g】命令继续运行,暂停时,【break】开始调试状态,必须切换到主线程,也就是 0号线程。
                

 1 //g 继续运行
 2 0:000> g
 3 ModLoad: 76c30000 76ca9000   C:\Windows\SysWOW64\ADVAPI32.dll
 4 ModLoad: 765b0000 7666f000   C:\Windows\SysWOW64\msvcrt.dll
 5 ModLoad: 76e30000 76ea5000   C:\Windows\SysWOW64\sechost.dll
 6 ModLoad: 75c40000 75cfa000   C:\Windows\SysWOW64\RPCRT4.dll
 7 ModLoad: 70fc0000 7104d000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscoreei.dll
 8 ModLoad: 75d20000 75d65000   C:\Windows\SysWOW64\SHLWAPI.dll
 9 ModLoad: 75530000 7553f000   C:\Windows\SysWOW64\kernel.appcore.dll
10 ModLoad: 74cb0000 74cb8000   C:\Windows\SysWOW64\VERSION.dll
11 ModLoad: 70810000 70fc0000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clr.dll
12 ModLoad: 76410000 765a4000   C:\Windows\SysWOW64\USER32.dll
13 ModLoad: 707f0000 70804000   C:\Windows\SysWOW64\VCRUNTIME140_CLR0400.dll
14 ModLoad: 76180000 76198000   C:\Windows\SysWOW64\win32u.dll
15 ModLoad: 70740000 707eb000   C:\Windows\SysWOW64\ucrtbase_clr0400.dll
16 ModLoad: 75b80000 75ba3000   C:\Windows\SysWOW64\GDI32.dll
17 ModLoad: 76670000 7674b000   C:\Windows\SysWOW64\gdi32full.dll
18 ModLoad: 76b30000 76bab000   C:\Windows\SysWOW64\msvcp_win.dll
19 ModLoad: 75550000 75670000   C:\Windows\SysWOW64\ucrtbase.dll
20 ModLoad: 754f0000 75515000   C:\Windows\SysWOW64\IMM32.DLL
21 Breakpoints 3 and 0 match
22 ModLoad: 75f00000 76180000   C:\Windows\SysWOW64\combase.dll
23 (3624.36d8): Unknown exception - code 04242420 (first chance)
24 ModLoad: 6f330000 7073e000   C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\218db16dceaef380c6daf35c6a48f313\mscorlib.ni.dll
25 ModLoad: 76cb0000 76d93000   C:\Windows\SysWOW64\ole32.dll
26 ModLoad: 75f00000 76180000   C:\Windows\SysWOW64\combase.dll
27 ModLoad: 76fa0000 76ffc000   C:\Windows\SysWOW64\bcryptPrimitives.dll
28 ModLoad: 6f2a0000 6f32a000   C:\Windows\Microsoft.NET\Framework\v4.0.30319\clrjit.dll
29 ModLoad: 76eb0000 76f4b000   C:\Windows\SysWOW64\OLEAUT32.dll
30 (3624.20f0): Break instruction exception - code 80000003 (first chance)
31 eax=0106b000 ebx=00000000 ecx=7717cee0 edx=7717cee0 esi=7717cee0 edi=7717cee0
32 eip=77143410 esp=05a2fa9c ebp=05a2fac8 iopl=0         nv up ei pl zr na pe nc
33 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
34 ntdll!DbgBreakPoint:
35 77143410 cc              int     3
36 
37 //切换到主线程
38 0:006> ~0s
39 eax=00000000 ebx=0000009c ecx=00000000 edx=00000000 esi=0138ee3c edi=00000000
40 eip=771410fc esp=0138ed24 ebp=0138ed84 iopl=0         nv up ei pl nz na pe nc
41 cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
42 ntdll!NtReadFile+0xc:
43 771410fc c22400          ret     24h

                我们通过【k】命令,查看显示调用栈

 1 0:000> k
 2  # ChildEBP RetAddr      
 3 00 0138ed84 768af25c     ntdll!NtReadFile+0xc
 4 01 0138ed84 6f7e9b71     KERNELBASE!ReadFile+0xec
 5 02 0138edf4 6ff1b275     mscorlib_ni+0x4b9b71
 6 03 0138ee20 6ff1b17b     mscorlib_ni!System.IO.__ConsoleStream.ReadFileNative+0x89 [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205] 
 7 04 0138ee4c 6f7ce6a3     mscorlib_ni!System.IO.__ConsoleStream.Read+0x9f [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134] 
 8 05 0138ee64 6f7ceb5b     mscorlib_ni!System.IO.StreamReader.ReadBuffer+0x33 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595] 
 9 06 0138ee80 70063786     mscorlib_ni!System.IO.StreamReader.ReadLine+0xe3 [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748] 
10 07 0138ee90 6fec1845     mscorlib_ni!System.IO.TextReader.SyncTextReader.ReadLine+0x1a [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 
11 08 0138ee98 03250876     mscorlib_ni!System.Console.ReadLine+0x15 [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984] 
12 WARNING: Frame IP not in any known module. Following frames may be wrong.
13 09 0138eea8 7081f036     0x3250876
14 0a 0138eeb4 708222da     clr!CallDescrWorkerInternal+0x34
15 0b 0138ef08 7082859b     clr!CallDescrWorkerWithHandler+0x6b
16 0c 0138ef7c 709cb11b     clr!MethodDescCallSite::CallTargetWorker+0x16a
17 0d 0138f0a0 709cb7fa     clr!RunMain+0x1b3
18 0e 0138f30c 709cb727     clr!Assembly::ExecuteMainMethod+0xf7
19 0f 0138f7f0 709cb8a8     clr!SystemDomain::ExecuteMainMethod+0x5ef
20 10 0138f848 709cb9ce     clr!ExecuteEXE+0x4c
21 11 0138f888 709c7305     clr!_CorExeMainInternal+0xdc
22 12 0138f8c4 70fcfa84     clr!_CorExeMain+0x4d
23 13 0138f8fc 7105e81e     mscoreei!_CorExeMain+0xd6
24 14 0138f90c 71064338     MSCOREE!ShellShim__CorExeMain+0x9e
25 15 0138f924 74cdf989     MSCOREE!_CorExeMain_Exported+0x8
26 16 0138f924 77137084     KERNEL32!BaseThreadInitThunk+0x19
27 17 0138f980 77137054     ntdll!__RtlUserThreadStart+0x2f
28 18 0138f990 00000000     ntdll!_RtlUserThreadStart+0x1b

              以上就是线程的调用栈,我们查找一下 _CorExeMain,这个方法,可以在 PPEE 软件里,在查看 Example_2_1_1.exe 的PE 头文件 DIRECTORY_ENTRY_IAT 里,效果如图:
                  Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

                 接着上面的说,我们在【k】命令的结果中查找 _CorExeMain 方法。这里的执行结果是一部分,红色部分是重点,MSCOREE 就是 mscoree.dll

 1 0a 0138eeb4 708222da     clr!CallDescrWorkerInternal+0x34
 2 0b 0138ef08 7082859b     clr!CallDescrWorkerWithHandler+0x6b
 3 0c 0138ef7c 709cb11b     clr!MethodDescCallSite::CallTargetWorker+0x16a
 4 0d 0138f0a0 709cb7fa     clr!RunMain+0x1b3(运行 Main 方法)
 5 0e 0138f30c 709cb727     clr!Assembly::ExecuteMainMethod+0xf7(加载必须的 dll 程序集)
 6 0f 0138f7f0 709cb8a8     clr!SystemDomain::ExecuteMainMethod+0x5ef(初始化系统程序域)
 7 10 0138f848 709cb9ce     clr!ExecuteEXE+0x4c(开始执行 exe)
 8 11 0138f888 709c7305     clr!_CorExeMainInternal+0xdc
 9 12 0138f8c4 70fcfa84     clr!_CorExeMain+0x4d(从这里开始执行入口地址的方法)
10 13 0138f8fc 7105e81e     mscoreei!_CorExeMain+0xd6(加载 CLR)
11 14 0138f90c 71064338     MSCOREE!ShellShim__CorExeMain+0x9e
12 15 0138f924 74cdf989     MSCOREE!_CorExeMain_Exported+0x8(这里就是在 PPEE 看到的入口地址 AddressOfEntryPoint:00002782)
13 16 0138f924 77137084     KERNEL32!BaseThreadInitThunk+0x19
14 17 0138f980 77137054     ntdll!__RtlUserThreadStart+0x2f
15 18 0138f990 00000000     ntdll!_RtlUserThreadStart+0x1b(ntdll是windows 32位的API)

                第12行代码就是 mscoree.dll 执行 _CorExeMain 方法,初始化环境,10 行代码加载 CLR,CLR 从第9 行执行入口函数,知道最后进入我们的托管层,我们可以使用 !clrstack 命令查看托管栈。
                

 1 0:000> !clrstack
 2 OS Thread Id: 0x36d8 (0)
 3 Child SP       IP Call Site
 4 0138eda4 771410fc [InlinedCallFrame: 0138eda4] 
 5 0138eda0 6f7e9b71 DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
 6 0138eda4 6ff1b275 [InlinedCallFrame: 0138eda4] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
 7 0138ee08 6ff1b275 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 205]
 8 0138ee3c 6ff1b17b System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) [f:\dd\ndp\clr\src\BCL\system\io\__consolestream.cs @ 134]
 9 0138ee5c 6f7ce6a3 System.IO.StreamReader.ReadBuffer() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 595]
10 0138ee6c 6f7ceb5b System.IO.StreamReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\streamreader.cs @ 748]
11 0138ee88 70063786 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363]
12 0138ee98 6fec1845 System.Console.ReadLine() [f:\dd\ndp\clr\src\BCL\system\console.cs @ 1984]
13 0138eea0 03250876 Example_2_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_2_1_1\Program.cs @ 10]
14 0138f020 7081f036 [GCFrame: 0138f020] 

                系统进入了托管栈,那它怎么知道要执行程序的 Main 方法呢?其实,在 PE 头文件里也有说明,在【DIRECTORY_ENTRY_COM_DESCRIPTOR】配置项里,我们点击该节点,在右侧显示详情,请注意【EntryPointToken】,值是:06000001,这个标记就是 Main方法。

                 效果如图:
                  Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

                  我们为了证明 06000001就是 Main 方法,我们使用 ILSpy 或者 DnSpy 查看程序集的元数据,效果如图:
                  Net 高级调试之二:CLR和Windows加载器及应用程序域介绍

                  其实,我们在 Main 方法的 IL 代码里也有标记,红色部分就是,注意。

 1 .method private hidebysig static 
 2     void Main (
 3         string[] args
 4     ) cil managed 
 5 {
 6     // Method begins at RVA 0x2050
 7     // Header size: 1
 8     // Code size: 19 (0x13)
 9     .maxstack 8
10     .entrypoint
11 
12     IL_0000: nop
13     IL_0001: ldstr "Hello World"
14     IL_0006: call void [mscorlib]System.Console::WriteLine(string)
15     IL_000b: nop
16     IL_000c: call string [mscorlib]System.Console::ReadLine()
17     IL_0011: pop
18     IL_0012: ret
19 } // end of method Program::Main


        2.3、查看应用程序域
            验证代码:Example_2_1_1
            EECLASS 存放在 LowFrequencyHeap ,MethodTable 存放在 HighFrequencyHeap

 1 0:000> !dumpdomain(执行的命令)
 2 --------------------------------------
 3 System Domain:      7115caf8(系统级程序域)
 4 LowFrequencyHeap:   7115ce1c(低频堆)
 5 HighFrequencyHeap:  7115ce68(高频堆)
 6 StubHeap:           7115ceb4(桩堆)
 7 Stage:              OPEN
 8 Name:               None
 9 --------------------------------------
10 Shared Domain:      7115c7a8(共享程序域)
11 LowFrequencyHeap:   7115ce1c(低频堆)
12 HighFrequencyHeap:  7115ce68(高频堆)
13 StubHeap:           7115ceb4(桩堆)
14 Stage:              OPEN
15 Name:               None
16 Assembly:           00b4f3d8 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
17 ClassLoader:        00b4de08
18   Module Name
19 6f531000    C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
20 
21 --------------------------------------
22 Domain 1:           00b01518(应用程序域)
23 LowFrequencyHeap:   00b01984(低频堆)
24 HighFrequencyHeap:  00b019d0(高频堆)
25 StubHeap:           00b01a1c(桩堆)
26 Stage:              OPEN
27 SecurityDescriptor: 00b02a58
28 Name:               Example_2_1_1.exe
29 Assembly:           00b4f3d8 [C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll]
30 ClassLoader:        00b4de08
31 SecurityDescriptor: 00b4f340
32   Module Name
33 6f531000    C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
34 
35 Assembly:           00b5b4d8 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_2_1_1\bin\Debug\Example_2_1_1.exe]
36 ClassLoader:        00b5afa8
37 SecurityDescriptor: 00b5aea0
38   Module Name
39 00d44044    E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\Example_2_1_1\bin\Debug\Example_2_1_1.exe

         2.4、通过命令 ? $exentry  查看入口点。
0:000> ?  $exentry
Evaluate expression: 5711746 = 00572782

            Net 高级调试之二:CLR和Windows加载器及应用程序域介绍文章来源地址https://www.toymoban.com/news/detail-711338.html


三、总结
    今天的内容真不少,完全记录下来还是挺费劲的,俗话说,没有苦哪里来的甜呢,一天的辛苦还是值得的,自己的收获也不少。学习如逆水行舟,不进则退,但是也也有另外一句话,学的越多,好像懂得越少。不管如何,不忘初心,继续努力,老天不会辜负努力的人。

到了这里,关于Net 高级调试之二:CLR和Windows加载器及应用程序域介绍的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Net 高级调试之一:开始认识一些调试工具

    一、简介 从今天开始一个长系列,Net 高级调试的相关文章,我自从学习了之后,以前很多模糊的地方现在很清楚了,原来自己的功力还是不够,所以有很多不明白,通过学习 Net 高级调试,眼前豁然开朗,茅塞顿开。其实,刚开始要学习《Net 高级调试》,还是很是很困难的

    2024年02月08日
    浏览(46)
  • Net 高级调试之十:轻量级代码生成的调试

    一、简介 今天是《Net 高级调试》的第十篇文章。说起来,高级调试,调试的内容还是挺多的,技巧也不少,但是,要想做一个合格的高级调试人员,还需要掌握如何调试动态生成的IL代码。今天要探讨的高级调试的技巧是如何调试通过 Emit 动态生成 IL 代码。可能有人会问,

    2024年02月05日
    浏览(59)
  • 聊一聊 .NET高级调试 内核模式堆泄露

    前几天有位朋友找到我,说他的机器内存在不断的上涨,但在任务管理器中查不出是哪个进程吃的内存,特别奇怪,截图如下: 在我的分析旅程中都是用户态模式的内存泄漏,像上图中的异常征兆已经明确告诉你了,不是用户态程序吃的内存,那就是内核态程序吃的,比如:

    2024年02月04日
    浏览(45)
  • Net 高级调试之七:线程操作相关命令介绍

    一、简介 今天是《Net 高级调试》的第七篇文章。上一篇文章我们说了值类型,引用类型,数组等的内存表现形式。有了这个基础,我们可以更好的了解我们的程序在运行时的状态,内存里有什么东西,它们的结构组成是什么样子的,对我们调试程序是更有帮助的。今天,我

    2024年02月05日
    浏览(52)
  • Net 高级调试之九:SOSEX 扩展命令介绍

    一、介绍 今天是《Net 高级调试》的第九篇文章。这篇文章设计的内容挺多的,比如:扩展的断点支持,如何查找元数据,栈回溯,对象检查,死锁检测等等,内容挺多的。功能特别强大,使用特别方便, 但是需要说明一点,这些功能不是 SOS 的功能,是 SOSEX 的扩展功能,但

    2024年02月05日
    浏览(45)
  • Net 高级调试之八:代码审查及杂项命令

    一、简介 今天是《Net 高级调试》的第八篇文章。这篇文章设计的内容挺多的,比如:如何查看方法的汇编代码,如何获取方法的描述符,对象同步块的转储,对象方法表的转储,托管堆和垃圾回收器信息的转储,CLR 的版本,GC 模式,等等,内容挺多的。内容虽然挺多,但是

    2024年02月05日
    浏览(43)
  • 聊一聊 .NET高级调试 中的一些内存术语

    在高级调试的旅程中,经常会有一些朋友问我什么是 工作集(内存) ,什么是 提交大小 ,什么是 Virtual Size , 什么是 Working Set 。。。截图如下: 既然有很多朋友问,这些用口头也不怎么好描述,刚好上午有时间就系统的聊一下吧。 可能有些朋友知道,内存中的虚拟地址被划分

    2024年02月05日
    浏览(52)
  • 聊一聊 .NET高级调试 中必知的符号表

    在高级调试的旅行中,发现有不少人对符号表不是很清楚,其实简而言之符号表中记录着一些程序的生物特征,比如哪个地址是函数(签名信息),哪个地址是全局变量,静态变量,行号是多少,数据类型是什么 等等,目的就是辅助我们可视化的调试,如果没有这些辅助我们看

    2024年02月05日
    浏览(49)
  • Net 高级调试之五:如何在托管函数上设置断点

    一、简介 今天是《Net 高级调试》的第五篇文章。今天这篇文章开始介绍如何在托管方法和非托管方法设置断点,我们要想调试程序,必须掌握调试的一些命令,动态调试的命令,我们在上一篇文章已经讲过了。光有命令也是不行的,要让这些调试命令有用,必须可以在方法

    2024年02月06日
    浏览(40)
  • Net 高级调试之十一:托管堆布局架构和对象分配机制

    一、简介 今天是《Net 高级调试》的第十一篇文章,这篇文章来的有点晚,因为,最近比较忙,就没时间写文章了。现在终于有点时间,继续开始我们这个系列。这篇文章我们主要介绍托管堆的架构,对象的分配机制,我们如何查找在托管堆上的对象,我学完这章,很多以前

    2024年02月05日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包