MSIL 心得

Microsoft intermediate language (MSIL)是一种编程语言,可以把它看成是组成.NET Framework的一部分,不论从内容还是形式上它都像是一种汇编语言,但是与传统的汇编语言又不太一样,初学MSIL的时候觉得它很亲切,我可以用使用高级语言编程的习惯来使用MSIL编程,例如它是面向对象的,可以用newobj指令生成一个类型实例,所以我在代码中可以这样来新建一个类型的对象:

newobj     instance void AOP_Programing.UsingAOP::.ctor()

可以用callvirt指令来调用其虚方法:

callvirt   instance void AOP_Programing.UsingAOP::Display()

    

1、MSIL初探

我们知道,对于托管应用,不论是Windows 桌面应用还是Web应用都会经过两次编译,第一次编译是由特定语言的编译器将源代码编译为MSIL,例如C#编译器可以将用C#写的源代码编译为MSIL,而在生成MSIL的同时会生成相应的元数据,例如如下简单例子:

    1)源代码:
    

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Console.ReadKey();
        }
    }
}

    2)用C#编译器编译后得到一个名字为hello.exe的可执行文件:

MSIL 心得 

 

     3)编译后生成的MSIL代码,用IL DASM打开:

 

MSIL 心得

 

    如:Main方法的MSIL如下:

 

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // 代码大小       19 (0x13)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldstr      "Hello World!"
  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_000b:  nop
  IL_000c:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_0011:  pop
  IL_0012:  ret
} // end of method Program::Main

 

     4)元数据:

由于较长所以不再列出,可以用Ctrl+M查看元数据。

简单的hello world代码包含更深层次的内容。

第二次编译发生在运行时,这个时候CLR会利用JIT编译器依据当前硬件平台将MSIL翻译为本地CPU指令,以方法为例,JIT编译器只会在方法第一次被调用时执行编译操作,这里会消耗一定CPU时间,完成编译后修改方法地址,下次再调用该方法时会直接通过该地址访问已经编译好的CPU指令。

从以上描述我们知道MSIL至少有如下优点:可移植性高,通用性强,只要有编译器支持,可以将任何语言翻译为MSIL,进而使其运行在 .Net平台上,极大提高代码复用性,这就是所谓的 compile-once-and-run anywhere,在将IL编译成本地CPU指令时,CLR会对其进行验证,因此MSIL又有高可靠性和安全性的优点。 

2、理解几类存储区

执行某个方法的时候会有以下几个存储区被用到:

     1) 局部变量区

方法所用到的每一个局部变量都需要在局部变量区初始化,格式为: .locals [ init ]‘(’ Local sSignature ‘)’,例如:.locals init (string V_0,uint8[] V_1),表明当前方法有两个局部变量,一个是String类型,一个是byte类型的数组,该区不能被直接访问。

     2) 静态字段存储区

     用来存储当前类型的全局变量,在C#中指声明为Static的字段,例如:.field public static int32 Length。

     3) 方法参数区

     用来存储被执行方法的传入参数,该区不能被直接访问。

     4) 托管堆

     引用类型的变量会被分配到这个区域,这个区域对象的生存期会受到垃圾回收器GC的全程监控,当GC被启动时,它将会对托管堆里不被任何其它对象所引用的对象进行内存回收,当然如果该对象定义了析构函数即使已经不被引用也可能不被回收。

     5) 非托管堆

     主要指由C++/CLI编写的非托管代码动态分配内存时可以将对象分配到这个区域。

     6) 动态内存池

     随着方法调用的结束而被回收,方法可以在这个区域动态分配内存。

     7) Evaluation Stack

     是一个非常重要的数据结构,它在内存分配和我们的应用之间起桥梁作用,所有的计算、结果数据的移入移出都要通过它,它是一个LIFO的栈,例如我们可以用各种load指令来从其它存储区取得数据放入Evaluation Stack,可以看成是push(压栈),也可以使用各种store指令来将当前计算结果存储到相应的存储区,可以看成是pop(出栈)。

     如果方法没有返回值则要保证方法调用结束时,Evaluation Stack为空,如果有返回值则方法调用结束的时候Evaluation Stack只存该返回值,如果违反上述规则,则运行时会抛出InvalidProgramException的异常。

在上述代码我们看到有个.maxstack指令,这个指令是用来指定同时在栈中存在的值,也就是栈容量,如果我们没有指定它的大小,则编译器会自动设为默认8,其实这个值是告诉我们当前应用处在正常情况下,如果发现运行的时候会出现超出这个容量的值,那么说明我们的代码可能存在逻辑问题,所以往大了说某种程度上这个值能告诉我们代码是否有潜在的逻辑问题,举个例子说明;

定义三个变量a、b、c,a的值为1,b的值为2,c的值为a+b,最后将c的值由控制台打印出来,代码如下:

 

// test.il 
.assembly extern mscorlib {
}
.assembly test{
    .ver 1:0:1:0
 }
.module test.exe
.method privatescope static void Mains() cil managed {
    .entrypoint 
    .maxstack  2    //MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得..代码1
    .locals init (int32 V_0,
              int32 V_1,
              int32 V_2)
    ldc.i4.1
    stloc.0
    ldc.i4.2
    stloc.1
    ldloc.0
    ldloc.1
    add                    //MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得..代码2
    stloc.2
    ldloc.2
    call    void [mscorlib]System.Console::WriteLine(int32)
    call    valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib] System.Console::ReadKey()

    pop                    //MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得MSIL 心得..代码3
    ret
}

 

由代码2可知我们用了add指令,所以.maxstack的值至少要为2,在我的32位机器上编译上述代码,如下:

MSIL 心得

    从编译结果上看可以知道我们生成了一个叫test.exe.的程序集,它包含了一个全局方法,其实就是我们的Mains方法,且是程序集的入口方法,与C#的入口方法必须叫Main不同,我们的入口方法叫Mains,说明用MSIL写代码时可以为入口方法起任何名字。双击这个exe文件则输出结果3,如果我们将代码1处相应指令改为.maxstack 1,重新编译后双击这个exe文件则有如下结果:
MSIL 心得
      Evaluation Stack 和其它存储区的关系如下:
    

 

MSIL 心得

 

 3、实践
      下面把MSIL指令集列出来,方便对照 

MSIL 心得MSIL 心得MSIL指令集
MSIL 心得MSIL Instruction Set
MSIL 心得Base Instructions
MSIL 心得    Instruction    Description    Stack Transition
MSIL 心得1    add    add two values, returning a new value    …, value1, value2…, result
MSIL 心得2    add.ovf.<signed>    add integer value with overflow check    …, value1, value2…, result
MSIL 心得3    and    bitwise AND    …, value1, value2 …, result
MSIL 心得4    arglist    get argument list    …  …, argListHandle
MSIL 心得5    beq.<length>    branch on equal    …, value1, value2  …
MSIL 心得6    bge.<length>    branch on greater than or equal to    …, value1, value2  …
MSIL 心得7    bge.un.<length>    branch on greater/equal, unsigned or unordered    …, value1, value2  …
MSIL 心得8    bgt.<length>    branch on greater than    …, value1, value2  …
MSIL 心得9    bgt.un<length>    branch on greater than, unsigned or unordered    …, value1, value2  …
MSIL 心得10    ble.<length>    branch on less than or equal to    …, value1, value2  …
MSIL 心得11    ble..un<length>    branch on less/equal, unsigned or unordered    …, value1, value2  …
MSIL 心得12    blt.<length>    branch on less than    …, value1, value2  …
MSIL 心得13    blt.un.<length>    branch on less than, unsigned or unordered    …, value1, value2  …
MSIL 心得14    bne.un<length>    branch on not equal or unorded    …, value1, value2  …
MSIL 心得15    br.<length>    unconditional branch    …,  …
MSIL 心得16    break    breakpoint instruction    …,  …
MSIL 心得17    brfalse.<length>    branch on false, null, or zero    …, value  …
MSIL 心得18    brtrue.<length>    branch on non-false or non-null    …, value  …
MSIL 心得19    call    call a method    …, arg1, arg2 … argn  …, retVal (not always returned)
MSIL 心得20    calli    indirect method call    …, arg1, arg2 … argn, ftn  …, retVal (not always returned)
MSIL 心得21    ceq    compare equal    …, value1, value2…, result
MSIL 心得22    cgt    compare greater than    …, value1, value2…, result
MSIL 心得23    cgt.un    compare greater than, unsigned or unordered    …, value1, value2…, result
MSIL 心得24    ckfinite    check for a finite real number    …, value  …, value
MSIL 心得25    clt    compare less than    …, value1, value2…, result
MSIL 心得26    clt.un    compare less than, unsigned or unordered    …, value1, value2…, result
MSIL 心得27    conv.<to type>    data conversion    …, value  …, result
MSIL 心得28    conv.ovf<to type>    data conversion with overflow detection    …, value  …, result
MSIL 心得29    conv.ovf.<to type>.un    unsigned data conversion with overflow detection    …, value  …, result
MSIL 心得30    cpblk    copy data from memory to memory    …, destaddr, srcaddr, size  …
MSIL 心得31    div    divide values    …, value1, value2…, result
MSIL 心得32    div.un    divide integer values, unsigned    …, value1, value2…, result
MSIL 心得33    dup    duplicate the top value of the stack    …, value  …, value, value
MSIL 心得34    endfilter    end filter clause of SEH    …, value  …
MSIL 心得35    endfinally    end the finally or fault clause of exception block    …  …
MSIL 心得36    initblk    initialize a block of memory to a value    …, addr, value, size  …
MSIL 心得37    jmp    jump to method    …  …
MSIL 心得38    ldarg.<length>    load argument onto the stack    …  …, value
MSIL 心得39    ldarga.<length>    load an argument address    …,  …, address of argument number argNum
MSIL 心得40    ldc.<type>    load numeric constant    …  …, num
MSIL 心得41    ldftn    load method pointer    …  …, ftn
MSIL 心得42    ldind.<type>    load value indirect onto the stack    …, addr  …, value
MSIL 心得43    ldloc    load local variable onto the stack    …  …, value
MSIL 心得44    ldloca.<length>    load local variable address    …  …, address
MSIL 心得45    ldnull    load a null pointer    …  …, null value
MSIL 心得46    leave.<length>    exit a protected region of code    …, 
MSIL 心得47    localloc    allocate space in the local dynamic memory pool    size  address
MSIL 心得48    mul    multiply values    …, value1, value2  …, result
MSIL 心得49    mul.ovf<type>    multiply integer values with overflow check    …, value1, value2  …, result
MSIL 心得50    neg    negate    …, value  …, result
MSIL 心得51    nop    no operation    …,  …,
MSIL 心得52    not    bitwise complement    …, value  …, result
MSIL 心得53    or    bitwise OR    …, value1, value2  …, result
MSIL 心得54    pop    remove the top element of the stack    …, value  …
MSIL 心得55    rem    compute the remainder    …, value1, value2  …, result
MSIL 心得56    rem.un    compute integer remainder, unsigned    …, value1, value2  …, result
MSIL 心得57    ret    return from method    retVal on callee evaluation stack (not always present) 
MSIL 心得…, retVal on caller evaluation stack (not always present)
MSIL 心得58    shl    shift integer left    …, value, shiftAmount  …, result
MSIL 心得59    shr    shift integer right    …, value, shiftAmount  …, result
MSIL 心得60    shr.un    shift integer right, unsigned    …, value, shiftAmount  …, result
MSIL 心得61    starg.<length>    store a value in an argument slot    …, value  …,
MSIL 心得62    stind.<type>    store value indirect from stack    …, addr, val  …
MSIL 心得63    stloc    pop value from stack to local variable    …, value  …
MSIL 心得64    sub    substract numeric values    …, value1, value2  …, result
MSIL 心得65    sub.ovf.<type>    substract integer values, checking for overflow    …, value1, value2  …, result
MSIL 心得66    switch    table switch on value    …, value  …,
MSIL 心得67    xor    bitwise XOR    MSIL 心得, value1, value2  MSIL 心得, result
MSIL 心得
MSIL 心得Object Model Instructions
MSIL 心得    Instruction    Description    Stack Transition
MSIL 心得1    box    convert value type to object reference    …, valueType  …, obj
MSIL 心得2    callvirt    call a method associated, a runtime, with an object    …, obj, arg1, … argN  …, returnVal (not always returned)
MSIL 心得3    cast class    cast an object to a class    …, obj  …, obj2
MSIL 心得4    cpobj    copy a value type    …, destValObj, srcValObj  …,
MSIL 心得5    initobj    Initialize a value type    …,addrOfValObj  …,
MSIL 心得6    isinst    test if an object is is an instance of a class or interface    …, obj  …, result
MSIL 心得7    ldelem.<type>    load an element fo an array    …, array, index  …, value
MSIL 心得8    ldelema    load address of an element of an array    …, array, index  …, address
MSIL 心得9    ldfld    load field of an object    …, obj  …, value
MSIL 心得10    ldflda    load field address    …, obj  …, address
MSIL 心得11    ldlen    load the length of an array    …, array  …, length
MSIL 心得12    ldobj    copy value type to the stack    …, addrOfValObj  …, valObj
MSIL 心得13    ldsfld    load static field of a class    …,  …, value
MSIL 心得14    ldsflda    load static field address    …,  …, address
MSIL 心得15    ldstr    load a literal string    …,  …, string
MSIL 心得16    ldtoken    load the runtime representation of metadata token    …  …, RuntimeHandle
MSIL 心得17    ldvirtfn    load a virtual method pointer    … object  …, ftn
MSIL 心得18    mkrefany    push a typed reference on the stack    …, ptr  …, typedRef
MSIL 心得19    newarr    Create a zero-base, on-dimensional array    …, numElems  …, array
MSIL 心得20    newobj    create a new object    …, arg1, … argN  …, obj
MSIL 心得21    refanytype    load the type out of a typed reference    …, TypedRef  …, type
MSIL 心得22    refanyval    load the address out of a typed reference    …, TypedRef  …, address
MSIL 心得23    rethrow    rethrow the current exception    …,  …,
MSIL 心得24    sizeof    load the size in bytes of a value type    …,  …, size (4 bytes, unsigned)
MSIL 心得25    stelem.<type>    store an element of an array    …, array, index, value  …,
MSIL 心得26    stfld    store into a field of an object    …, obj, value  …,
MSIL 心得27    stobj    store a value type from the stack into memory    …, addr, valObj  …,
MSIL 心得28    stsfld    store a static field of class    …, val  …,
MSIL 心得29    throw    throw an exception    …, object  …,
MSIL 心得30    unbox    convert boxed value type to its raw form    

   
      如何使用上述指令集表?以add    add two values, returning a new value    …, value1, value2…, result 这条指令为例,add是指令的名字,接着是指令的用途说明,表明该指令的作用是求栈中两个值的和,最后是执行add指令前后栈中数据的变化情况,...表示我们不关心的栈中原有值,add指令会将栈顶的value1和value2的值弹出并进行计算,最后将计算结果result压栈。从这些指令我们也可以看出,所有的计算操作都是发生在Evaluation Stack中的。 
      下面以K&R写的The C programming Language中一个求幂的代码为例:    

MSIL 心得MSIL 心得K&R
  1 .assembly extern mscorlib {
  2 }
  3 .assembly UsingMSIL{
  4     .ver 1:0:1:0
  5  }
  6 .module UsingMSIL.exe
  7 
  8 .class public auto ansi beforefieldinit UsingMSIL.Math
  9        extends [mscorlib]System.Object
 10 {
 11     
 12     .method public hidebysig specialname rtspecialname 
 13             instance void  .ctor() cil managed
 14     {
 15       .maxstack  8
 16       ldarg.0
 17       call       instance void [mscorlib]System.Object::.ctor()
 18       ret
 19     }
 20 
 21     .method public hidebysig instance void  Display() cil managed
 22     {
 23          .maxstack  6
 24          .locals init ([0] int32 i,
 25                        [1] bool a1)
 26                
 27          ldc.i4.0
 28          stloc.0
 29          br.s       ex
 30     lp:  ldstr      "{0} 2^{0}={1} -3^{0}={2}\n"
 31          ldloc.0
 32          box        [mscorlib]System.Int32
 33          ldarg.0
 34          ldc.i4.2
 35          ldloc.0
 36          call       instance int32 UsingMSIL.Math::Power(int32,
 37                                                          int32)
 38          box        [mscorlib]System.Int32
 39          ldarg.0
 40          ldc.i4.s   -3
 41          ldloc.0
 42          call       instance int32 UsingMSIL.Math::Power(int32,
 43                                                          int32)
 44          box        [mscorlib]System.Int32
 45          call       void [mscorlib]System.Console::WriteLine(string,
 46                                                              object,
 47                                                              object,
 48                                                              object)
 49          ldloc.0
 50          ldc.i4.1
 51          add
 52          stloc.0
 53     ex:  ldloc.0
 54          ldc.i4.s   10
 55          clt
 56          stloc.1
 57          ldloc.1
 58          brtrue.s   lp
 59          ret
 60     } 
 61 
 62 
 63     .method public hidebysig instance int32  Power(int32 basen,
 64                                                    int32 n) cil managed
 65     {
 66          .maxstack  2
 67          .locals init ([0] int32 i,
 68                      [1] int32 p,
 69                      [2] int32 a0,
 70                      [3] bool a1)
 71 
 72          ldc.i4.1
 73          stloc.1
 74 
 75          ldc.i4.1
 76          stloc.0
 77          
 78     lp:  ldloc.0
 79          ldarg.2
 80          cgt
 81          ldc.i4.1
 82          ceq
 83          stloc.3
 84          ldloc.3
 85          brtrue.s    ex
 86               
 87          ldloc.1
 88          ldarg.1
 89          mul
 90          stloc.1
 91          
 92          ldloc.0
 93          ldc.i4.1
 94          add
 95          stloc.0
 96          br.s        lp
 97 
 98     ex:  ldloc.1
 99          ret     
100     }
101 
102 } 
103 
104 .method private hidebysig static void  Main(string[] args) cil managed
105 {
106   .entrypoint
107   .maxstack  1
108   .locals init ([0] class UsingMSIL.Math m)
109   
110     newobj     instance void UsingMSIL.Math::.ctor()
111     stloc.0
112     ldloc.0
113     callvirt   instance void UsingMSIL.Math::Display()
114     nop
115     call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
116     pop
117     ret
118 } 
119 

 

从上往下看这段代码:

1)    .assembly extern 指令

用来指定代码中会用到的其他程序集,这些程序集的公共类型和方法可以在我们的代码中使用,例如上述代码中的12

.assembly extern  mscorlib{},实际上即使不在这里加mscorlib这个程序集,编译器也会自动加上,因为这个程序集包含了所有的内建类型的定义, Sytem.Object也定义在这个类集当中,所以C#编译器在编译过程中会自动加上对mscorlib的引用。

2) .assembly指令

定义了当前程序集的名字,另外也可以包含诸如版本号、public key token、程序集语言等信息,如代码35所示。

3).module指令

     一个程序集assembly最少包含一个module如代码6所示。

4) .Class指令

     用来声明一个类型,这个声明要包含类型的访问修饰符,如public等,编码方式、beforefieldinit标志(此标志使得运行库能够在任何时候执行类型构造函数方法,只要该方法在第一次访问该类型的静态字段之前执行即可。换句话说,beforefieldinit 为运行库提供了一个执行主动优化的许可。具体内容可以参见:http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/softwaredev/us0501StaticsinNET.mspx?mfr=true)、类型名以及当前类的父类等,如代码8所示。

5) .method指令

     用来定义一个方法,包括静态方法、构造函数、非静态方法等,下面对代码中求幂的方法进行说明。

   第63~64行,定义方法Power,有两个参数:basen和n,这两个参数被存储在方法参数存储区,分别为参数1和参数2,最后有个cil managed说明当前方法为托管代码;

   第66行,定义了Evaluation Stack的最大容量暂时设为8,可以等我们写完下面代码再写这个值;

   第6770行,定义了当前方法所用到的局部变量,这些局部变量位于该方法的局部变量存储区,在这里我们用到了4个局部变量,除了i和p外,a用来;

   第72~73行,为局部变量p赋初值1,ldc.i4.1是将常数1压栈到Evaluation Stack,stloc.1是将栈顶的数值弹出后存入局部变量p,上述i4代表int32类型,下表列出了常用类型及其对应的简写形式。

MSIL 心得

   第7576行,为局部变量i赋初值1,指令执行完后Evaluation Stack为空。

   第7880行,比较局部变量i和方法的参数2的值。第80行执行前,Evaluation Stack中有两个数据:分别为局部变量i和由ldarg.2指令从方法参数表中取出的参数2的值,也就是n的值。第80行执行结束后将比较结果压栈,此时Evaluation Stack中只有一个值,就是这个比较结果。

   第8185行,是对结束条件的判断,如果栈中值为1表明i>n,则由跳转指令brtrue.s跳转到98行,跳转前将Evaluation Stack栈顶值弹出,此时Evaluation Stack为空。

   第8790行,实现p=p×basen,更新局部变量p的值。

   第9296行,对控制循环的局部变量i进行加1操作后更新局部变量i,最后,执行br.s指令,无条件的跳转到第78行。

   第98~99行,由于这个方法有返回值,所以方法返回时需要将局部变量p的值压栈,最后方法返回,清空相关存储区。

   从上述描述可以看到Evaluation Stack 中最多会有,由于 
   上述代码中第21~60行是对Power方法的一个测试,其他没什么好说的,但是这段代码包含了一些不太好的编程习惯:你会发现有3个地方出现了box指令,只要有该指令就说明此处发生了装箱操作,也就是会发生以下事情:

1) 从托管堆中分配内存,大小为这个值类型字段所需的内存,另外还有type object pointer和sync block index所需的内存;

2) 将这个值类型的字段值复制到托管堆中的新对象中;

3) 返回新对象地址。

由此可见,装箱操作会影响到程序运行效率。

      最后,运行结果如下:
MSIL 心得
   

    关于MSIL的更多信息,可以通过查看微软向 European Computer Manufacturers Association (ECMA) 提供的文档(地址如下:http://www.ecma-international.org/publications/standards/Ecma-335.htm)以及MSDN来了解。

    最后,我认为时常将自己写的代码反汇编一下对提高代码质量有较大帮助,虽然微软为我们封装的很好,但是我觉得还是需要透过MSIL来了解一些幕后,我想这对我们的提高会有帮助的。

    继续学习中........ :)

转载于:https://www.cnblogs.com/vivounicorn/archive/2009/08/17/vivounicorn.html

上一篇:7-1 币值转换


下一篇:weblogic中部署项目报错org.hibernate.QueryException: ClassNotFoundException: org.hibernate.hql.ast.HqlToken .