标题: Net 高级调试之二:CLR和Windows加载器及应用程序域介绍 [打印本页] 作者: 知者何南 时间: 2023-11-20 03:35 标题: 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)和一组相关的框架类库,如图: 1.1、宏观概念
2.1、什么是PE文件
PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件,它的一个非常大的作用就是帮助 Windows 加载器 执行程序的入口。
对于 Net 的 PE 文件,有几点需要注意: a)、AddressOfEntryPoint
程序的入口点相对偏移地址,即(exe+AddressOfEntryPoint)。
b)、DIRECTORY_ENTRY_COM_DESCRIPTOR
Net 程序独有的节点配置。
ILSpy 查看 Example_2_1_1.exe 的元数据。
c)、EntryPointToken
这个标签的地址,就是我们程序 Program.Main 方法的入口点地址。
IL 代码里面也是有标记的。
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,这个地址是相对地址,是相对程序进程起始地址来说的。
如图:
我们有了入口程序的相对起始地址,我们找一下应用程序的进程起始地址,二者相加,就是 Windows 加载器要执行的地址。想要查看 Example_2_1_1.exe 进程地址,需要借助 Windbg,红色部分就是 Example_2_1_1.exe 进程的起始地址。
代码如下:
二者相加就是WIndows 加载器的入口点地址,还不是我们的 Program.Main的地址,00ca0000(Example_2_1_1进程起始地址),00002782 是 PE 头告诉的入口点地址,我们通过 U 命令,可以查看汇编代码。通过代码我们可以看到,执行了 jmp 指令,跳转的地址是:402000h。
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方法。
效果如图:
我们为了证明 06000001就是 Main 方法,我们使用 ILSpy 或者 DnSpy 查看程序集的元数据,效果如图:
其实,我们在 Main 方法的 IL 代码里也有标记,红色部分就是,注意。