65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 程序集查看器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (52投票s)

2002 年 11 月 29 日

8分钟阅读

viewsIcon

309234

downloadIcon

15886

一个应用程序,用于直接和通过反射检查 .NET 程序集

Screenshot

引言

Asmex 是一个 .NET 程序集文件的内部查看器。尽管市面上并不缺 .NET 程序集查看器,Asmex 具有一些独特的特性,并且其源代码在各种情况下都可能很有用。Asmex 的功能包括:

  • 从程序集中提取资源
  • 查看原始元数据表
  • 将程序集作为文件或全局程序集缓存条目打开
  • 查看反汇编(通过作弊,调用 ILDASM)
  • 查看 PE 文件结构
  • 浏览类型、命名空间、方法参数等

原理

Asmex 是一个教育性项目;其理念是创建一个涉及最低级别 .NET 知识但同时又能利用 WinForms 清晰 GUI 模型优势的应用程序。它在 我们的公司 用于培训和调试。

在低级别 .NET 方面,Asmex 包含读取原始元数据表等代码。我对 .NET 元数据存储的高效和巧妙方式总体上印象深刻。

WinForms 模型(相对于 MFC)的优雅之处体现在,它能将通过反射和二进制文件解析获得的异构数据放入一个通用的树形格式进行显示。同样,我对其比 MFC 等效方法工作量少很多感到印象深刻。一个通用的对象属性查看器(取自另一个项目)也被整合进了 Asmex —— 它利用 .NET 有趣的 Attribute 功能为树中的每个项提供属性列表。

Asmex 的设计并非旨在赢得奖项,*这也是*数据存储在派生自 GUI 树节点的类中的原因。抱歉 :)

本文将讨论 Asmex 源代码中(希望)更有可重用性和趣味性的部分。

PE 文件阅读器 / .NET 元数据阅读器

FileViewer 命名空间包含 Asmex 最有用的部分。下面是对 FileViewer 中处理的结构的简短而模糊的描述。如果您是 Microsoft 可执行文件格式的专家,可以跳过。否则,您可能会觉得它太模糊,这时您可以查看 Asmex 的源代码或询问我。我*非常*乐意谈论文件格式。顺便说一句,如果有人需要对 PE/.NET 文件格式以及 .NET 类型、资源和元数据概念进行简要概述,我将非常乐意以此为借口来写一篇。

背景 — PE 文件

几乎所有 Windows 可执行文件、DLL 或 EXE 都是 Portable Executable (PE) 格式文件。尽管 PE 格式本身对 .NET 来说很少有用,但在 .NET 的当前实现中,所有程序集都包含在特殊的 PE 格式文件中,这些文件省略了一些传统的部分,并添加了许多新部分。

总的来说,PE 文件由一个 PE 头组成,其中包含一个数据目录列表,以及紧跟在 PE 头后面的若干节。并非所有数据目录在 .NET 文件中都有意义,也不是所有节都存在。然而,那些仍然存在的节仍然很重要——特别是,最后一个数据目录条目指向 .NET 信息区域的开头。

背景 — .NET PE 文件

从 .NET 的角度来看,PE 文件的*真正*起始点是 COR20 头,它告诉 .NET 运行时在哪里查找元数据。COR20 头和 PE 头一样,指定了一些数据目录,以及程序集的入口点。其中大部分数据目录指向未用于检查程序集的修复信息等内容,但其中一个指向元数据流的开头。

背景 — 元数据流

.NET 以流(通常是四个)的形式存储元数据。这些流中的每一个都有不同的格式:

  • #Blob 流 包含二进制数据,其中包括方法签名;还有 UCS-2 格式的字符串。
  • #US 流 存储 UCS-2 格式的字符串。
  • #GUID 流 包含程序集中使用的所有 GUID 的列表,端到端。GUID 是通过 1 基索引而不是偏移量引用的,这只是为了增加难度。
  • #String 流 存储 UTF-8 格式的字符串,其中包含代码中类型、方法等的名称。
  • #~ 流 包含元数据表。
  • #- 流 在罕见情况下会替代 #~。我认为它包含未压缩的表。

背景 — 元数据表

元数据表只是文件内首尾相连的数据区域。表的数量是固定且已知的,每个表都有一个固定的、已知的范围,它里面的令牌(见下文)可以引用。表本身并不包含字符串、方法签名等内容;而是包含:

  • 指向另一个表中的行的令牌
  • 指向某个流中条目的数字

总的来说,表的结构是这样的:您必须知道您正在查看的特定列的属性才能解释其中找到的数字。考虑到 .NET 元数据非常丰富,这导致了非常小的数据大小。(可惜的是,这些数据又被塞进了效率不高的 PE 格式)。Asmex 会解包这些表,为每一行查找字符串等信息,并以相对友好的格式呈现。待办:UCS-2 字符串以十六进制形式显示。

背景 — 类型

在二进制级别查看 .NET 文件时,有两个非常重要的概念需要理解:

  • RVA — 这是 PE 的概念,PE 文件中的*大多数*指针都是这种格式。RVA 是一个项的相对虚拟地址 — 在 PE 文件加载到内存后,它相对于加载该 PE 文件的基地址的地址。这与 PE 文件内的偏移量不同。Asmex 在 ModHeaders.Rva2Offset 方法中进行 RVA 和实际文件偏移量之间的转换。
  • 编码令牌 — 这是 .NET 的概念,表示元数据表中的一个条目。它们相当复杂,MDTables 类提供了解释它们的函数。通常,它们同时指定了某个数据项的表和表中的行。待办:一个支持所有编码令牌操作的 CodedToken 类。

总的来说,FileViewer 中的每个类都代表了上述信息中的一个块。如果可能,每个类都描述了文件中实际的字节范围,因此继承自 Region,这是一个具有“start”和“length”属性的抽象基类。即使给定结构在文件中的物理位置信息在 Asmex 的树视图中并不那么有用,我们也会跟踪它,以防我们将来想创建一个可视化的 PE 文件检查器或 PE 文件发射器。

还有一些类不代表特定的字节范围,而是封装其他信息;这些包括与元数据表相关的类 TableTableCell,以及与 PE 导入和重定位表相关的类。

每个类在其构造函数中都接受一个 BinaryReader。假设此阅读器指向程序集文件,并且“定位”在正确的位置。在某些情况下,需要手动调整阅读器的偏移量,因为在转换 RVA 等时需要进行一些算术运算。

这些类应该可以作为广泛的 PE 和 .NET 结构文档。有关全面的文档,请参阅下面的参考书目。

反射树

一个简单的系统,用于表示从 PE 文件解析器或通过反射获得的层次数据。每个项由一个派生自 BaseNode 的类表示,该类持有一个数据对象的引用。然后,每个节点都有一个 GenerateChildren 方法来填充其下方的项,在需要时创建各种类型的新数据对象。通过反射查看 .NET 类型的逻辑包含在这些树节点类中。此逻辑并不复杂,并且已在许多地方介绍过,因此此处无需赘述。

通过派生新的节点类,并修改另一个节点的 GenerateChildren 方法,以便有时生成新节点,可以轻松地将新数据项添加到 Asmex。您还可以重写节点的 GetMenu 方法,为该节点类型添加上下文菜单操作。

此设计并非天才之作,但它能够以统一的方式呈现数据,并仅按需生成节点。在 MFC 中,可能需要构建一个大型的树形结构,并通过一团乱麻的消息将其连接到 CTreeCtrl

属性查看器

ObjViewer 命名空间包含几个定义通用属性查看器控件的类。ObjViewer 是一个 UserControl,它为任何给定的 object 提供一个名称-值对列表。当然,对象上可用的属性不一定能构成一个友好的对象视图,因此您可以使用 ObjViewerAttribute 属性来修改目标对象的属性——例如,指定某个属性应以十六进制显示或根本不显示。

GAC 浏览器

GACPicker 类允许用户从全局程序集缓存中选择一个程序集。它通过查看 GAC 的文件系统表示来实现此目的,因为在当前的 .NET 环境中似乎没有实际的 API。

荒谬的星球大战式写作

HintDlg 类以荒谬的星球大战风格的透视滚动文本形式显示提示。它使用 GraphicsPath.Warp 对文本应用伪透视变换。很烦人,但我觉得这是必须做的。

参考文献

关于 PE/.NET 文件格式信息,我建议阅读 ECMA-335 Partition II 的第 21-24 部分,该文档可在网上找到。尽管偶有不准确之处,《Inside Microsoft .NET IL Assembler》也是一本好书。

如果您想进一步了解程序集中的实际 CIL 指令,《Compiling for the .NET Common Language Runtime》是一本极好的书。

如果您想舒适地检查您的二进制文件,能否允许我谦虚地推荐我自己的 AXE 程序。

© . All rights reserved.