本来想放到译本的每章后面,写到第5章,才发现是狗尾续貂,我的功力和原作者相差实在太大,还是放在自己的地方上吧。

 

第1章

这一章非常实用:

如果读者只是想初步了解一下IL,那么看完第1章就够了。同时能根据自身的需要决定是否要继续读下去。

CLR的底层运作机制,这里只是开了个头,以下各章会讲得更加详细。如果读者觉得修为不够,可以先拜读一下Jeffrey所著《CLR via C#》,毕竟后面这本书是从C#角度讲的,比较贴合实际,不像本书这么枯燥。

本章中那个判断奇偶数的示例,很值得细细品味。读者应该通过这个小例子,转变自己的观念——摆脱高级语言的束缚,而从汇编语言的角度思考问题。比如说,看到Console.Readline( )和Console.Writeline( ),分别想到进栈和出栈的逻辑。

此外,要分清指令和伪指令,建议读者使用SharpDevelop这个开源的IDE工具打开IL程序,这两种语句会以不同的颜色高亮显示(SharpDevelop1.1版本下,指令为蓝色字体,伪指令为绿色字体)。根据我的经验,声明性语句都是伪指令,涉及进出栈的操作都是指令。

总结陈词,要学习或研究IL,必须事先学一点汇编语言。

 

第2章 代码压缩和代码保护

代码压缩,涉及到了长参数和短参数形式的指令。用短参数形式代替长参数形式的指令,看上去有点吹毛求疵,不就是节省了几个字节么?但是对汇编器来说,这几个字节还是很重要的。关于指令的更多细节,参见第13章。

代码保护,讲的是IL的异常处理机制。不要奇怪汇编语言也有结构化异常处理(EH),因为IL本身也是一种OO的语言。那个leave指令很神奇的哦!一定要注意EH块的结束和开始都有严格的限制。关于EH的详细介绍,参见第14章。

 

第3章 优化代码

IL中也有别名机制。可以应用于类、方法、字段或自定义特性。要使用到.typedef关键字。别名在双向解析(尤其是反汇编)中幸存——因为它不是元数据的一部分。

#define SYM1 "SomeText"——这套技术在C#中也是有的,可以对比着看。

最神奇的是,IL中也有.this和.base。

 

第4章 PE头

本章是本书中最难啃的了。因为它涉及了大量的技术,尤其是非托管中的一些术语,以及汇编级别的很多概念。这对于只致力于托管应用程序开发的读者来说,是一个不小的障碍。我尽可能的给出每一个术语的解释以及英文对照。这些术语将会在以下各章先后出现,所以请读者务必搞清楚,上网Google是快的方法。

搞清楚本章介绍的几个流程图和结构图是关键,写一个程序,然后对其进行反汇编,对比着看其中的Header,是最好的学习方法。

了解 IL汇编器创建一个托管PE文件的步骤,也很重要的。

最好的学习方法,莫过于按照书中介绍的内容把PE头的结构读取出来,请参见Vijay Mukhi的Metadata Tables一书,我在自己的博客上对其C#代码重新进行了排版:

javascript:void(0)

 

第5章  元数据表

元数据的定义——描述数据的数据。全书最重要的东西。本章是概述。

元数据是一个二维表,表中的每一列都包括数据或者是对另一个表中一行的引用。这些二维表在exe或dll中表现为流的格式,需要对流进行解析,就连ILDSAM也是把解析的结果表现为层叠的关系,而看不到二维表的。

模块的元数据结构要么是优化的,要么是未优化的:

优化的元数据:方法表中的记录根据它们的父类进行排序,但由于元数据随意流出或增量编译的结果,使得具有不同父类的子表交叉存放成为可能。

未优化的元数据 :使用了中间检索表的元数据,以提供非交叉的、按照各自父亲排序的检索表。

元数据表示为一组命名流,每个流都表示一类元数据。这些流分为两种类型——元数据堆和元数据表。

元数据堆(metadata heap)是琐碎结构的存储区域,它保存了顺序存储的项。在元数据中,堆用来存储字符串和二进制对象。元数据堆分为以下三种:

String

GUID

Blob堆

通用元数据头由存储签名和存储头组成。紧跟在存储头之后的是流头的数组,其中的rcName字段存储流的名称。

在元数据中有六种命名流:

#Strings 包括了元数据项名称(类名称、方法名称,字段名称等等)的字符串堆。这个流不包括在模块的方法中定义或引用的文字常量。

#Blob 包括了类似于默认值、签名这样的内部元数据二进制对象的Blob堆。

#GUID GUID堆包括了各种全局唯一标识符。

#US 包括了用户自定义字符串的Blob堆。

#~ 压缩的(优化的)元数据流。

#- 未压缩的(未优化的)元数据流。

元数据表流

元数据表流的头结构、元数据表的描述符结构、元数据表的列描述符结构。

列中有一个字段标识列的类型编码:

0-63 最常见的45个表(0-44),以及19个备用表。

64-95 用来放置标记,标记是内部使用的,是RID和表索引的结合体。有13个可用的(64-76),以及19个备用的。

 

第6章  程序集和模块

本章1、2、3、7节都是讲CLR的,与IL无关。

AppDomain这里介绍的太简单,详细内容参见CLR via C#第21章。

清单Manifest中有三个元数据表是仅用于清单表的:Assembly、ExportedType、DeclSecurity。

Assembly元数据表,至多包括一笔记录,这个表出现在主模块的元数据中。这个表用途不广,所以很简单。

AssemblyRef元数据表

自动侦测技术:auto关键字的使用,注意这个关键字可以和部分指定结合,只用于GAC中的程序集。

Module元数据表,只包括一笔记录,它提供了当前模块的标识。只有一个字段可以显式设置(Name字段)。

AssemblyRef元数据表中托管方法的和File元数据表中的记录是一对多的。非托管方法没有配对的File记录。

ManifestResource元数据表讲解了托管资源的结构。注意不能使用IL操作托管资源,只有通过API——比如说C#中的一些方法。

ExportedType元数据表包括了在程序集的非主要模块中声明的公有类(在程序集外可见)的信息。

定义在主模块中的类和定义在ExportedType表中的类,它们的交集必须为空。

类的“转发器”(forwarder),指出了哪个程序集的某某类(曾经驻留于这个程序集中)被移走了,基于.NET 2.0。

ILAsm中清单声明的次序:本着“先声明,后引用”的原则,

1. AssemblyRef声明(.assembly extern)

2. ModuleRef声明(.module extern)

3. Assembly声明(.assembly)

4. File声明(.File)

5. ExportedType声明(.class extern)

6. ManifestResource声明(.mresource)

微软的VisualStudio也有害人的地方,它只能生成单模块程序集,以至于程序员普遍分不清楚程序集和模块。多模块程序集只能通过编译器选项手动设置来生成,网上有很多资料介绍这项技术——也就是AL工具。

 

第7章 类和命名空间

读完本章,你就可以去和其他人说:

1.类型划分为值类型和引用类型

2.引用类型包括对象类型、接口类型和指针类型,其中:

对象类型,即类,是完全或部分自描述值的类型。具有部分自描述值的类型称为抽象类。接口类型是部分自描述值类型。接口通常表示由类暴露的行为特性的子集;而类则实现了各自的接口。指针类型只是对项的引用,指出了项的位置。

——这不是奇思怪想,而是CLI标准。但也许,长时间在C#层面上的人,可能一时还接受不了。

这一章涉及了TypeDef、TypeRef、NestedClass和ClassLayout四种元数据表。其中ClassLayout元数据表要仔细研读,因为我们平常接触的不多。

命名空间不是元数据项,可以使用单引号避免多个句点所引起的全类名歧义。

在IL的接口中,居然可以实现静态方法,这是C#中所不允许的。

类的扩充技术,可以认为是C#中的partial class(部分类)——至少我是这么认为的。

枚举,如 enum Color{Red=1, White=2}对应IL:
《Expert .NET 2.0 IL Assembler》 译者笔记_自描述

可以看到,枚举只是一个在其中定义一系列常量字段和实例字段的结构。
如果只使用Color.Red这样的类型,则会作为常量处理,从而代码不再引用定义了的枚举类型,可以删除相应的dll(具体见第7章);
如果使用Color c = new Color();这样的语法,那么依赖于引用,不可以删除相应的dll。

 

第8章 基本类型和签名