不知道大家对于Common Language Infrastructure有什么认识呢?“噢!天啊!看到那么几个英文我就头痛了!”如果真是这样,那么你就没有办法继续看下去了,因为这里面的东西基本上只能够找到英文的资料。
实际上这个看似很深奥的东西并没有你想象的那么难,当然,也不是一个简单的东西。关于这方面的资料其实非常好找,虽然数量不多,但是却是非常之实用,就在你VS安装的目录里面。假如你安装的是VS2003,装在了E:\VS.NET 2003这个目录里面,那么相关的资料就在E:\VS.NET 2003\SDK\v1.1\Tool Developers Guide\里面。这个目录下面有两个子目录,一个是docs,全部都是文档,另外一个是Samples,全部都是例子,不过所有的东西都是英文的。
什么是CLI呢?中文应该翻译成“公共语言底层结构”。CLI应该包括CIL和CLR:CIL是Common Intermediate Language,中文是“公共中间语言”,也就是那个“IL汇编”;CLR是Common Language Runtime,中文是“公共语言运行库”。除了这些之外,任何一个.NET语言还收到CLS的约束,CLS——Common Language Specification,公共语言规格说明书,这个东西主要用于约束所有.NET语言,使得他们能够互相协作,不存在某种语言产生的代码不能够被另外一种语言所支持。
如果你真的有兴趣看看我说的那个目录里面的文档,那么你会发现几乎你所能够想得到的底层的东西都有了,包括CIL的语法和二进制代码,CLI可执行程序文件结构,怎么写一个调试器(Debugger),分析器(Profiler),编译器(Compiler)……什么?你不相信连编译器都有了?在 E:\VS.NET 2003\SDK\v1.1\Tool Developers Guide\Samples里面有三个编译器的文件!一个Lisp.NET,一个MyC,一个Simple Managed C。还有更多的例子呢,诸公自便。
今天首先讲讲CIL以及CIL的VM。关于CIL的文档,在Partition III CIL.doc中。如果不想看的话,我可以简单讲讲。CIL的VM是一个栈式机,和x86的依赖寄存器的机制很不一样,所谓的栈式机就是说指令所需要的数据都用堆栈保存。这种栈式机在真实的计算机当中并不多见,尤其在CISC的CPU里面是很难见到的,即使在RISC都很少见(也许我孤陋寡闻吧)。原因简单点讲就是栈式机的指令非常简单,为了进行一个计算需要多个指令来完成,甚至需要多次读出和写入数据。正是由于指令简单,所以由于虚拟机上面却非常的方便,虚拟机程序做起来可以容易许多。栈式机主要的指令有五大类:(数据)压栈、弹出、运算、转移、其他,其中前三个是栈式机的核心。以CIL为例:
|
|
|
|
我们先看看右边的CIL,ldloc的意思是把局部变量压到堆栈里面,后面的.0表示第零个局部变量(也就是a了);add则把栈顶的两个元素弹出来并且相加,结果压到堆栈顶上;stloc则把栈顶的内容弹出来,保存到局部变量当中。现在应该看出来了,ld就是load的缩写,st是store的缩写,loc是local。那么还有没有其他的呢?大家可以看一下下面的表:
主要操作 | 操作数范围/条件 | 操作数类型 | 操作数 | |||||||||
缩写 | 全称 | 含义 | 缩写 | 全称 | 含义 | 缩写 | 全称 | 含义 | 缩写 | 全称 | 含义 | |
ld | load | 将操作数压到堆栈当中,相当于: push ax |
arg | argument | 参数 | ? | ? | 操作数中的数值 | .0 | ? | 第零个参数 * | |
.1 | ? | 第一个参数 | ||||||||||
.2 | ? | 第二个参数 | ||||||||||
.3 | ? | 第三个参数 | ||||||||||
.s xx | (short) | 参数xx | ||||||||||
a | address | 操作数的地址 | 只有 .s xx,参见ldarg.s | |||||||||
loc | local | 局部变量 | 参见ldarg | |||||||||
fld | field | 字段(类的全局变量) | 参见ldarg | xx | ? | xx字段,eg: ldfld xx | ||||||
c | const | 常量 | .i4 | int 4 bytes | C#里面的int,其他的类型例如short需要通过conv转换 | .m1 | minus 1 | -1 | ||||
.0 | ? | 0 | ||||||||||
.1 | ? | 1 | ||||||||||
…… | ||||||||||||
.8 | 8 | |||||||||||
.s | (short) | 后面跟一个字节以内的整型数值(有符号的) | ||||||||||
? | ? | 后面跟四个字节的整型数值 | ||||||||||
.i8 | int 8 bytes | C#里面的long | ? | ? | 后面跟八个字节的整型数值 | |||||||
.r4 | real 4 bytes | C#里面的float | ? | ? | 后面跟四个字节的浮点数值 | |||||||
.r8 | real 8 bytes | C#里面的double | ? | ? | 后面跟八个字节的浮点数值 | |||||||
null | null | 空值(也就是0) | ? | ? | ? | ? | ? | ? | ||||
st | store | 将堆栈内容弹出到操作数中,相当于: pop ax |
参见ld ** | |||||||||
conv | convert | 数值类型转换,仅仅用纯粹的数值类型间的转换,例如int/float等 | ? | ? | ? | .i1 | int 1 bytes | C#里面的sbyte | ? | ? | ? | |
.i2 | int 2 bytes | C#里面的short | ||||||||||
.i4 | int 4 bytes | C#里面的int | ||||||||||
.i8 | int 8 bytes | C#里面的long | ||||||||||
.r4 | real 4 bytes | C#里面的float | ||||||||||
.r8 | real 8 bytes | C#里面的double | ||||||||||
.u4 | uint 4 bytes | C#里面的uint | ||||||||||
.u8 | uint 8 bytes | C#里面的ulong | ||||||||||
b/br | branch | 条件和无条件跳转,相当于: jmp/jxx label_jump |
br | ? | ? | 无条件跳转 | ? | ? | ? | ? | ? | 后面跟四个字节的偏移量(有符号) |
.s | (short) | 后面跟一个字节的偏移量(有符号) | ||||||||||
false | false | 值为零的时候跳转 | ? | ? | ? | 参见br | ||||||
true | true | 值不为零的时候跳转 | ? | ? | ? | |||||||
b | eq | equal to | 相等 | ? | ? | ? | ||||||
ne | not equal to | 不相等 | un | unsigned or unordered | 无氟好的(对于整数)或者无序的(对于浮点) | |||||||
gt | greater than | 大于 | ||||||||||
lt | less than | 小于 | ||||||||||
ge | greater than or equal to | 大于等于 | ||||||||||
le | less than or equal to | 小于等于 | ||||||||||
call | call | 调用 | ? | ? | ? | ? | ? | (非虚函数) | ? | |||
? | ? | ? | virt | virtual | 虚函数 |
* 最左边的是参数0,然后是参数1、2、3……。如果不是在静态当中,参数0相当于C#的this(VB的Me),该参数不需要代码传递,此时最左边的参数是参数1,也就是从.1开始。
** starg只有.s形式,没有.0、.1等形式,除此之外和ldarg相同。
+ 从左到右依次合并,就可以得到一个指令,例如:ld + arg + a + .s xx? =>? ldarga.s xx,即,读出参数xx的地址并压栈。
上面这些只是一些比较常用的、但是稍微难理解的指令,其他的可以看Partition III CIL.doc这个文档。现在我们再回到表一的例子,是不是觉得其实CIL非常好理解呢?所以虚拟机用栈式机的形式是比较容易实现的,比起x86里面一堆的寄存器、状态/标志位以及指令对状态/标志位的影响,可简单多了!不过我们也可以看到,用80x86汇编10条指令能够完成的操作,用CIL则需要18条指令,而且这已经是经过手动优化过的CIL了,如果你用C#写,然后编译出来的很可能还要多出一些代码。如果我们数一下包含的字节数,可以看到80x86有32bytes,而CIL只有20bytes,也许会让你觉得CIL似乎更为紧凑,其实是因为这个CIL是一个优化形式,实际的情况CIL并不会比x86汇编小多少,甚至完全可能更大!栈式机另外一个问题是,每一次的操作都必须访问至少两次内存,并且这两次访问的肯定不是同一个地方:一个是某一个内存块,另外一个是堆栈。因此不可能象x86CPU那样,直接访问CPU内部存储器,甚至连访问缓存效率都会打对折(需要访问两个完全无关的地方)。而我们知道CPU内部存储器式最快的,完全没有延时,缓存次之(一级缓存延时约1到2个周期,二级缓存延时3到5个周期),最慢的就是内存了(延时约十个周期左右,甚至更长)。所以一般说来,真实的CPU是很少做成栈式机形式的。
那么我们阅读cil有些什么技巧呢?我觉得需要注意这么几点:
- 牢记这是一个栈式机,所有指令都和栈有关。
- 注意当前函数是否为静态函数
- 就这么多了
说了半天,哪里找什么CIL来看呢?这个就简单了,在E:\VS.NET 2003\SDK\v1.1\Bin里面有一个ildasm.exe的程序,这个程序就是“反汇编”工具,用它来打开一个.NET程序就能够看到实际的CIL了。比如我们可以打开一个System.Windows.Forms.dll(在C:\WINNT\Microsoft.NET\Framework\v1.x.xxxx里面),哇,看到了吧?举个例子?好,你可以看看我举的一个例子。
接下来应该说些什么呢?反正CIL我是说完了。且听下回分解吧……
文章来源:http://dotnet.blogger.cn/sumtec/articles/193.aspx