不知道大家对于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为例:
|
int a = a + b + c + d + e + f + g + h + i; |
|
80x86 ASM |
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