《Expert .NET 2.0 IL Assembler》 译者笔记
本来想放到译本的每章后面,写到第5章,才发现是狗尾续貂,我的功力和原作者相差实在太大,还是放在自己的地方上吧。
本书源码:
http://www.apress.com/book/downloadfile/3046(原书)
http://www.cnblogs.com/Jax/archive/2008/12/30/1365685.html(我的博客,未做任何改动)
第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#代码重新进行了排版:
http://www.cnblogs.com/Jax/archive/2009/01/02/1366897.html
第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:
可以看到,枚举只是一个在其中定义一系列常量字段和实例字段的结构。
如果只使用Color.Red这样的类型,则会作为常量处理,从而代码不再引用定义了的枚举类型,可以删除相应的dll(具体见第7章);
如果使用Color c = new Color();这样的语法,那么依赖于引用,不可以删除相应的dll。
第8章 基本类型和签名