随笔- 7  文章- 0  评论- 16 

我们用C#写的源码最终是要转换成IL语言的,(注:C#源码有C#编译器生成元数据和IL,程序真正运行时,按需要由JIT将IL及时编译成机器码),理解NET技术做好的方式就是理解IL。

一旦你掌握了IL,理解NET技术就不成问题了,因为所有针对NET平台的语言最终都要编译成IL,找了张图:



下面通过一个例子来展开IL的学习,打开记事本,输入:

.method void vijay() 

{

}

这样就写了一个IL程序(但是不能运行),我们将它命名为a.il,用IL编译器试着编译一下,找到VS自带的命令行窗口(就不用设置环境变量了)


定位到a.il所在的文件夹,输入编译命令ilsam a.il

E:"il>ilasm a.il

Error: No entry point declared for executable

Could not create output file, error code=0x80004005

提示错误:可执行文件没有入口点(记得Main方法么,一样的道理)

IL中,一行代码的开始要么有个点 例如:.method void a()。任何以点开头的语句称为'指令directive'(针对编译器而言),效果是创建一个方法或者类(例如可以这样写: .class 意思是我要定义一个类,以后看多了就明白了)。不用点开头的语句称为'instruction',自己理解这两个文字的区别吧(其实点开头语句是编译器用来生成元数据的,不是点开头的语句是用来生成IL代码的,以后会具体说下,很好理解)。

语句:.method void a()  意思是定义一个叫a(名字随便起)的方法,没有参数,返回值为空。

为啥会报错,就像你得为C#程序指定一个入口函数Main一样,在IL中 你也得指定一个入口函数,告诉程序从哪里开始执行,

那就指定一个入口点,如下:

.method void a()

{

.entrypoint

}

用同样的命令再编,这样不报错了,而且生成了一个叫a.exe的文件(同一文件夹下)。 .entrypoint这句告诉编译器a是程序的入口函数。

运行一下程序,输入:a  报错:未处理的异常:  System.BadImageFormatException: 未能加载文件或程序集“a.exe”或它的某一个依赖项。该模块应包含一个程序集清

单。

文件名:“a.exe”。

意思是a.exe不是一个有效的'程序集','程序集'是NET中最小的可发布,版本控制的单元(NET框架设计书讲的很明白),将程序改成:

.assembly ak{}

.method void a()

{

.entrypoint

}

编译通过,运行通过,虽然啥也没输出。(这里说下,上面的代码应该报错的,因为没有给a方法指定返回指令,但它不报错,我也没辙),完整的程序应该是:

.assembly ak{}

.method void a()

{

.entrypoint

ret

}

.assembly ak{}

定义一个程序集,这样才能被CLR执行

.method void a()

定义了入口方法,程序将从这里开始执行

急着想看输出么,来个经典的HELLO,WORLD吧,如下:

.assembly extern mscorlib{}

.assembly ak{}

.method static void a()

{

.entrypoint

ldstr "HELLO WORLD"

call void [mscorlib]System.Console::WriteLine(string)

ret

}

编译运行,哈罗我的就出来了。

稍微解释一下这段程序:

.assembly extern mscorlib{}

表示:引用外部的程序集mscorlib(跟using System;完全不是一回事啊,以后说),为啥要引用外部程序集,很明显,你的程序里并没有定义叫WriteLine方法,为了使用这个方法,只有引用微软给你提供的程序集mscorlib。

 [mscorlib]System.Console::WriteLine(string)

这个[mscorlib]表示你调用的方法在哪个程序集里,System.Console(这个System是using System;产生的)表示在mscorlib程序集里的一个类,WriteLine表示类中的方法,这句话的意思是:去一个叫mscorlib的程序集里找一个叫System.Console的类中的一个叫Console的方法,方法接受一个string类型参数,不返回值。(至于是怎么找到mscorlib的,还有一大段话要说,先不说),跑题了,越撤越多,继续。

在上面的例子中没看到Main的影子吧,Main是C#的词,对IL不好使,IL只认.entrypoint

,意思是入口函数的名字可以随便起,只要用.entrypoint指明,当然只能由一个这样的方法。

ldstr "HELLO WORLD"

IL是基于栈操作的,意味着:你得把要操作的对象放在栈上,相应操作从栈上取走它需要的操作数,并把操作的结果放在栈上。例如1 + 2,写成IL是:

ldc.i4.1   把1放在栈上

ldc.i4.2   把2放在栈上

Add      把 1 2 从栈上取走,相加,把结果3放在栈上

因此:ldstr "HELLO WORLD"是把字符串HELLO WORLD放在栈上(实际上是放的字符串的引用,以后说)

call void [mscorlib]System.Console::WriteLine(string)

在IL中,方法调用时不仅要指明返回类型,方法的参数类型也必须指定,一句话:调用方法时,要指明:方法返回类型、方法所在的程序集、方法所在类型的全名、方法的名字、方法所有的参数类型,这样写确实比较繁琐,但对编译器和运行时的代码验证提供了很大的方便。

下面为方法a加一些属性:

.assembly extern mscorlib{}

.assembly ak{}

.method public hidebysig static void a() il managed

{

.entrypoint

ldstr "HELLO WORLD"

call void [mscorlib]System.Console::WriteLine(string)

ret

}

注意:目前为止还没出现'类'的概念。

Public:标识a方法的可见性,public表示改程序的任何代码都是访问到;

Hidebysig:将父类中的同名同签名的方法隐藏,如果有的话

Static:静态方法,不用多解释吧,入口方法必须为静态的

Il managed:方法为托管的 以后详细说

将上面的程序编译运行,结果还是一样。

类:

C#中如此定义类:class a{}

下面用IL定义个类,C#版HELLO WORLD:

.assembly extern mscorlib{}

.assembly ak{}

.class Hello{

.method public hidebysig static void Main() il managed

{

.entrypoint

ldstr "HELLO WORLD"

call void [mscorlib]System.Console::WriteLine(string)

ret

}

}

跟以前的区别就是.class Hello 表示要定义一个类,挺简单吧 呵呵

再给类加些属性.class private auto ansi Hello

Private:该类为本程序集可见,public:外部程序集可访问

Auto:类型实例在内存中的布局方式由CLR决定

ansi:指定了和非托管代码交互时的字符串转换规则,托管代码中,一个字符占2个字节,称为:Unicode characters,而在非托管代码中,一个字符占1个字节(C语言中),称:ANSI characters。

再改代码:

.assembly extern mscorlib{}

.assembly ak{}

.class private auto ansi Hello extends System.Object{

.method public hidebysig static void Main() il managed

{

.entrypoint

ldstr "HELLO WORLD"

call void [mscorlib]System.Console::WriteLine(string)

ret

}

}

这不用解释吧,所有类型都集成至Object类,以前不写为啥不报错,因为是默认的。

记得这句话不:如果C#一个类没有定义构造函数,编译器会自动生成一个默认构造函数,上面代码没看到啊,因为你没写,IL中是要自力更生的,添上:

.assembly extern mscorlib{}

.assembly ak{}

.class private auto ansi Hello extends System.Object{

.method public hidebysig static void Main() il managed

{

.entrypoint

ldstr "HELLO WORLD"

call void [mscorlib]System.Console::WriteLine(string)

ret

}

.method public hidebysig specialname rtspecialname instance void .ctor() il managed{

ldarg.0

call instance void [mscorlib]System.Object::.ctor()

ret

}

}

哇,好长,不过它就是一个方法,只不过待遇和别的方法不一样(体现在specialname rtspecialname ),构造函数的名字必须为.ctor() ,instance表示这是一个实例方法

看下方法都做了些什么:

ldarg.0

call instance void [mscorlib]System.Object::.ctor()

把第一个参数放栈上(ldarg.0 参数从0开始计算,取第二个参数就是ldarg.1),然后调用父类的构造函数call instance void [mscorlib]System.Object::.ctor()

上一帖说到:默认构造函数都要调用其父类的构造函数 一直到Object,构造函数的作用就是初始化实例的初始状态,子类会继承父类的实例字段,所以要调用其父类的构造函数让父类完成初始化操作:

说下:实例方法调用时,默认的传递一个this参数,this指向你要操作的实例,所以ldarg.0就是把this放栈上了,然后调用示例方法call instance void [mscorlib]System.Object::.ctor()

学习IL最好的方式是看编译器生成的现成IL,如下:
using System;

class tes{

static void Main(){

Console.WriteLine("HEllo,World");

}

} C#版的

将它编译:csc a.cs 便生成一个可执行文件a.exe,查看编译器生成的IL(所有NET语言源码都要被相应的编译器编译成IL)输入:ildasm a.exe 打开了窗口:


点击tes打开,看到两个方法:Main方法和编译器默认生成的构造函数.ctor

构造函数的IL:

.method public hidebysig specialname rtspecialname 

        instance void  .ctor() cil managed

{

  // 代码大小       7 (0x7)

  .maxstack  8

  IL_0000:  ldarg.0

  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()

  IL_0006:  ret

} // end of method tes::.ctor

哇,和咱们写的一样吧,它就多个 .maxstack  8,啥意思呢,表示:方法执行期间的最大栈帧数,这个一般是编译器按需生成的,你也可以自己指定,如果你指定3,那么栈上同时不能存在3个以上的操作数。

看下Main:

.method private hidebysig static void  Main() cil managed

{

  .entrypoint

  // 代码大小       13 (0xd)

  .maxstack  8

  IL_0000:  nop

  IL_0001:  ldstr      "HEllo,World"

  IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)

  IL_000b:  nop

  IL_000c:  ret

} // end of method tes::Main

哇,也几乎一模一样,就少了个nop,这个指令是用来将栈顶操作数弹出的,上面中的两个nop其实没啥用,因为nop时栈上没东西,还有就是一些: IL_000c:每个IL语句前面都有,干嘛的呢,用来标识IL语句在代码流中的位置,记得C#中的goto语句么,就是标识了类似goto语句的目的地。写个例子:

.assembly extern mscorlib{}

.assembly ak{}

.class private auto ansi Hello extends System.Object{

.method public hidebysig static void Main() il managed

{

.entrypoint

ldc.i4.1  1上栈

ldc.i4.2  2上栈

bgt dayu  如果1>2 就跳到一个叫dayu的地方 否则 继续执行

ldstr "1 < 2"

call void [mscorlib]System.Console::Write(string) 打印

br exit  无条件跳到一个叫 exit的地方

dayu:ldstr "1 > 2"    这个叫dayu的地方

call void [mscorlib]System.Console::Write(string) 打印

br exit 无条件跳到一个叫 exit的地方

exit: ret   这是个叫exit的地方

}}

一开始不懂得先记者,慢慢的以前不太明白的就理解了。

下一贴:IL基础

另附IL指令集:

Instruction

Description

Stack Transition

1

add

add two values, returning a new value

…, value1, value2à…, result

2

add.ovf.<signed>

add integer value with overflow check

…, value1, value2à…, result

3

and

bitwise AND

…, value1, value2 à…, result

4

arglist

get argument list

… à …, argListHandle

5

beq.<length>

branch on equal

…, value1, value2 à …

6

bge.<length>

branch on greater than or equal to

…, value1, value2 à …

7

bge.un.<length>

branch on greater/equal, unsigned or unordered

…, value1, value2 à …

8

bgt.<length>

branch on greater than

…, value1, value2 à …

9

bgt.un<length>

branch on greater than, unsigned or unordered

…, value1, value2 à …

10

ble.<length>

branch on less than or equal to

…, value1, value2 à …

11

ble..un<length>

branch on less/equal, unsigned or unordered

…, value1, value2 à …

12

blt.<length>

branch on less than

…, value1, value2 à …

13

blt.un.<length>

branch on less than, unsigned or unordered

…, value1, value2 à …

14

bne.un<length>

branch on not equal or unorded

…, value1, value2 à …

15

br.<length>

unconditional branch

…, à …

16

break

breakpoint instruction

…, à …

17

brfalse.<length>

branch on false, null, or zero

…, value à 

18

brtrue.<length>

branch on non-false or non-null

…, value à 

19

call

call a method

…, arg1, arg2 … argn à …, retVal (not always returned)

20

calli

indirect method call

…, arg1, arg2 … argn, ftn à …, retVal (not always returned)

21

ceq

compare equal

…, value1, value2à…, result

22

cgt

compare greater than

…, value1, value2à…, result

23

cgt.un

compare greater than, unsigned or unordered

…, value1, value2à…, result

24

ckfinite

check for a finite real number

…, value à …, value

25

clt

compare less than

…, value1, value2à…, result

26

clt.un

compare less than, unsigned or unordered

…, value1, value2à…, result

27

conv.<to type>

data conversion

…, value à …, result

28

conv.ovf<to type>

data conversion with overflow detection

…, value à …, result

29

conv.ovf.<to type>.un

unsigned data conversion with overflow detection

…, value à …, result

30

cpblk

copy data from memory to memory

…, destaddr, srcaddr, size à …

31

div

divide values

…, value1, value2à…, result

32

div.un

divide integer values, unsigned

…, value1, value2à…, result

33

dup

duplicate the top value of the stack

…, value à …, value, value

34

endfilter

end filter clause of SEH

…, value à …

35

endfinally

end the finally or fault clause of exception block

… à …

36

initblk

initialize a block of memory to a value

…, addr, value, size à …

37

jmp

jump to method

… à …

38

ldarg.<length>

load argument onto the stack

… à …, value

39

ldarga.<length>

load an argument address

…, à …, address of argument number argNum

40

ldc.<type>

load numeric constant

… à …, num

41

ldftn

load method pointer

… à …, ftn

42

ldind.<type>

load value indirect onto the stack

…, addr à …, value

43

ldloc

load local variable onto the stack

… à …, value

44

ldloca.<length>

load local variable address

… à …, address

45

ldnull

load a null pointer

… à …, null value

46

leave.<length>

exit a protected region of code

…, à

47

localloc

allocate space in the local dynamic memory pool

size à address

48

mul

multiply values

…, value1, value2 à …, result

49

mul.ovf<type>

multiply integer values with overflow check

…, value1, value2 à …, result

50

neg

negate

…, value à …, result

51

nop

no operation

…, à …,

52

not

bitwise complement

…, value à …, result

53

or

bitwise OR

…, value1, value2 à …, result

54

pop

remove the top element of the stack

…, value à …

55

rem

compute the remainder

…, value1, value2 à …, result

56

rem.un

compute integer remainder, unsigned

…, value1, value2 à …, result

57

ret

return from method

retVal on callee evaluation stack (not always present) à

…, retVal on caller evaluation stack (not always present)

58

shl

shift integer left

…, value, shiftAmount à …, result

59

shr

shift integer right

…, value, shiftAmount à …, result

60

shr.un

shift integer right, unsigned

…, value, shiftAmount à …, result

61

starg.<length>

store a value in an argument slot

…, value à …,

62

stind.<type>

store value indirect from stack

…, addr, val à …

63

stloc

pop value from stack to local variable

…, value à …

64

sub

substract numeric values

…, value1, value2 à …, result

65

sub.ovf.<type>

substract integer values, checking for overflow

…, value1, value2 à …, result

66

switch

table switch on value

…, value à …,

67

xor

bitwise XOR

..., value1, value2 à ..., result

 posted on 2008-09-18 14:23 红泥 阅读(...) 评论(...) 编辑 收藏