【文章标题】: 乱涂IL中间语言
【文章作者】: 有酒醉
【作者邮箱】: wuqr32@sina.com
【下载地址】: 自己搜索下载
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
一、序
.NET中间语言即IL,IL本身以二进制格式存在.对于IL指令大家可以自己查阅.NET Framework SDK 的 Tool Developers Guide/Docs
子文件夹的Word文档中的说明
二、第一个IL程序
现在让我们学习一下第一个IL程序,代码如下:
// HelloWorld.il
// This is our first IL Program!
.assembly extern mscorlib{}
.assembly HelloWorld
{
.ver 1:0:1:0
}
.module HelloWorld.exe
.method static void Main() cil managed
{
.maxstack 1
.entrypoint
ldstr "Hello,World!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
编译运行:
D:/>ilasm HelloWorld.il
Microsoft (R) .NET Framework IL Assembler. Version 1.1.4322.2032
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Assembling 'helloworld.il' , no listing file, to EXE --> 'HelloWorld.EXE'
Source file is ANSI
Assembled global method Main
Creating PE file
Emitting members:
Global Methods: 1;
Writing PE file
Operation completed successfully
D:/>HelloWorld.exe
Hello,World!
至此我们第一个IL程序出炉了! 下面我解释一下各个关键字的意思.
.assembly extern mscorlib{}
这一句对外指出要引用的程序集,当然你可以写多个由extern限定的.assembly指令,把该要的程序集全部包罗近来
.assembly HelloWorld
这一句指出程序集的名字.用大括号括起中的.ver指令表示它的版本号,大家有兴趣自己查查里面都可以写什么指令,这里就不废话咯
.module HelloWorld.exe
指出存储该模块的文件名称.据说如果不显示声明模块,ilasm.exe自动采取与程序集相同的模块.本人做了个测试,好象不起作用.难
道又是人品问题?
.method static void Main() cil managed
.method 指令声明一个静态方法,并且用.entrypoint来指明这是一个程序的入口函数.所以说嘛,在C#中Main必须为入口函数的说法
在IL中可不适用哦!在IL中,你可以取个变态的入口函数,没有人可以管你.
cil managed 表明此方法包含IL代码,这可有些重要,因为.NET也允许在方法中使用可执行代码来代替IL代码.
.maxstack 1
指出计算栈容量为1.计算栈不能多也不能少,否则编译不过去,至于计算栈怎么计算,真麻烦,说只要两三句,写就要一大堆!怎么办?:-)
ldstr "Hello,World!"
加载字符串"Hello,World!"到计算栈(计算栈满咯...,因为它的容量被声明为1)
call void [mscorlib]System.Console::WriteLine(string)
调用System.Console.WriteLine(string)函数,将计算栈上的字符串弹出并输出(因为该函数需要一个字符串,这时候计算栈为空).调用
函数时指出了程序集.
三、IL原理
IL基于虚拟机的概念!计算栈又是IL中的一个重要课题,我们通过下面图来稍微了解一下它!
|---------------------------------------------------------|
| 通常可用的内存 |-------------------------------| |
| | 当前方法可用的局部内存 | |
| |---------------| | |--------------| | |
| | 静态字段 ___FCKpd___0nbsp; | | 方法参数表 | | |
| | |/ | |------$-------| | |
| |---------------| / | | | |
| / | |------$-------| |----------| |
| |---------------| / | ___FCKpd___0nbsp; |<->|动态内存池 | |
| | 托管堆 | /|/| | |----------| |
| | | / | 计算栈 | | |
| | | /|/| | | |
| | | / | ___FCKpd___0nbsp; | | |
| | | / | |------$-------| | |
| | | / | | | |
| | |/ | | | |
| | $ | |------$-------| | |
| |---------------| | | 局部变量表 | | |
| | |--------------| | |
| |-------------------------------| |
|---------------------------------------------------------|
方法的内存区域
真辛苦啊,画这个破图竟然花了我347秒!
从图中可以了解到,所有的数据传输都必须经过计算栈,那么图里面的各个元素又代表什么呢?LOOK!
局部变量表 --它是存储局部变量的内存区域,必须在每个方法的开始位置声明
参数表 --本内存区域包含作为参数传递给方法的变量,如果当前方法是一个实例方法,那么它应该还包含this引用
局部内存池 --这是可以动态分配的内存区域.它和参数表的区别是:池所需要的内存可以在运行时确定.
计算栈 --最重要的内存区域!它是唯一执行实际运算的区域.
计算栈嘛,好象不太好理解.怎么办?打个比方说,你要测试数据,那么数据就得先复制到计算栈然后再测试.如果执行一些运算,比如加法之
类的,你也要先把它复制到计算栈,然后再进行加法运算.栈在通俗的讲是'先进后出'滴,所以这里的计算栈也不例外.好了,让我们GO ON!
静态字段 --当运行的方法当然也可以访问任意类的静态成员
托管堆 --它是存储引用数据类型和装相值(boxed value)类型的地方.
解释完毕!废话少说,我们来个实例瞧瞧计算栈是怎么计算的:
// Add Sample
// AddDemo.il
.assembly extern mscorlib{}
.assembly AddDemo
{
.ver 1:0:1:0
}
.module AddDemo.exe
.method static void Main() cil managed
{
.entrypoint
.maxstack 2
ldstr "The sum of the numbers is "
call void [mscorlib]System.Console::WriteLine(string)
ldc.i4.s 47
ldc.i4 345
add
call void [mscorlib]System.Console::WriteLine(int32)
ret
}
在分析该程序之前有一点要明确:计算栈的大小是以变量数目为单位计算,而不以字节或者任意固定单位来测量
分析:
ldstr "The sum of the numbers is "
ldstr的栈增量是1
加载一个字符串到计算栈: ...->...,string
call void [mscorlib]System.Console::WriteLine(string)
方法System.Console.WriteLine(string)需要从栈中找一个字符串来执行,所以它的栈增量是-1
...,string -> ...
ldc.i4.s 47
加载短整数47到栈中.所以它的栈增量是1.
指令ldc.i4.s加载范围是(-128~127).
...->...,value
ldc.i4 345
ldc.i4加载一个4字节的有符号整数到计算栈.现在,计算栈已经达到了由.maxstack指令指定的最大尺寸.
add
add指令执行加法运算,它从计算栈中弹出两个数(必须具有相同的数据类型),对它们进行加法运算,再把结果放回计算栈.所以,add指令具
有偏移量-1,它可以用下面来表示:
...,value,value --> ...,result
call void [mscorlib]System.Console::WriteLine(int32)
弹出一个数据项并输出.此时数据栈为空.
关于IL数据类型(请查看文档):
IL数据类型可以应用于类型标识,返回方法的签名中的类型.比如:
.method static int32 DoSomething(int16,float32,object)
或
call instance void [System.Drawing]System.Drawing.Rectangle::Intersect(
valuetype [System.Drawing]System.Drawing.Rectangle)
再谈谈IL对象引用,托管指针,非托管指针之间的区别:
1、object是托管堆中的一个对象引用.它与C#中的object类似.例如:
MyClass myClass = new MyClass();
myClass.DoSomething();
可以被转化为使用对象引用的IL代码
2、&是托管指针的IL表示.它通常用于向方法传递引用值.例如:
int x = 30
DoSomething(ref 30)
生成的IL代码将使用托管指针.
托管指针和对象引用不同.托管指针用以指向对象实例中的数据本身,而object实际指向的是引用对象的实例数据之前的头信息,包括一
些方法表头.
注意:如果对值类型调用方法(如 Int32.ToString()),你必须通过托管指针而不是对象引用来完成调用.我们来做个测试:
// T.il
.assembly extern mscorlib{}
.assembly T
{
.ver 1:0:1:0
}
.module T.exe
.method static void Main() cil managed
{
.entrypoint
.maxstack 1
ldc.i4.s 47
box int32
//unbox int32
call instance string int32::ToString()
call void [mscorlib]System.Console::WriteLine(string)
ret
}
编译运行:
D:/>ilasm t.il
Microsoft (R) .NET Framework IL Assembler. Version 1.1.4322.2032
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Assembling 't.il' , no listing file, to EXE --> 't.EXE'
Source file is ANSI
Assembled global method Main
Creating PE file
Emitting members:
Global Methods: 1;
Writing PE file
Operation completed successfully
D:/>t.exe
2042268872
是不是感到有些惊讶?因为int32是值类型,它的ToString实现要求所传入的第一个参数是需要被转换的int32类型的一个托管指针.而我们
却是用一个对象引用来代替栈中的托管指针,所以导致了错误.修正很简单,只要把注释部分去掉即可.
3、非托管指针
非托管指针用来指向任何字面量值.它和托管指针的操作相同.只是,单元收集器决定对那些对象进行收集时,它所关注的是托管指针而不
是非托管指针.当它在堆中移动对象时,会更新托管指针.如果非托管指针指向的对象被移动,那就完蛋鸟,它将指向一个无用的对象.所以
在C#中,对非托管指针有fixed关键字来锁定对象.
关于IL指令列表
<略>
四、IL编程
这里简单讲解几种基本的定义方式.
1、定义类型的命名空间,采用关键字.namespace.示例:
// This is a HelloWorld app with a class!
.assembly extern mscorlib{}
.assembly T
{
.ver 1:0:1:0
}
.module T.exe
.namespace com.yzl.chp1
{
.class public auto ansi T extends [mscorlib]System.Object
{
.method public static void EntryMethod() cil managed
{
.maxstack 1
.entrypoint
ldstr "Hello,World"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
}
分析:
在IL中,我们使用.namespace,.class指令.我们使用extends关键字来指示一个基类.这里的入口点方法为EntryMethod,主要是为了说明C#
中Main为入口函数在IL中并不适用.
public --访问修饰符,与C#中相同.
IL语言中的访问修饰类型
|-----------------------|-----------------------|-------------------|
|访问类型 |可见性 |C#中对应的访问类型 |
|-----------------------|-----------------------|-------------------|
|public |所有代码可见 |public |
|-----------------------|-----------------------|-------------------|
|private |只在相同类中可见 |private |
|-----------------------|-----------------------|-------------------|
|family |此类及基类可见 |protected |
|-----------------------|-----------------------|-------------------|
|assembly |同一程序集代码中可见 |internal |
|-----------------------|-----------------------|-------------------|
|familyandassem |同一程序集的派生类中 |N/A |
|-----------------------|-----------------------|-------------------|
|familyorassem |派生类及同一程序集 |protected internal |
|-----------------------|-----------------------|-------------------|
|privatescope |与private类似 |N/A |
|-----------------------|-----------------------|-------------------|
auto --指定类的内存中的布局方式.有三个选项:auto - 自动方式,Sequential - 字段在内存中依次排放,Explicit - 显示指定每个
字段的偏移量
ansi --表示字符串被转换为本机非托管字符串的方式.ansi - 指定字符串被转换为ASNI字符串,unicode - 采用UNICODE格式的字符
autochar - 右运行平台所决定
extends --指示一个基类
提示:如果在访问修饰类型前缀nested,那么我们也可以把上面的访问修饰类型应用于其他类型,示例:
.class public OuterClass
{
// 将访问修饰符作用于类
.class nested family TestClass
{
// ...
}
}
2、条件语句和分支
br --无条件跳转,对栈没影响.该命令还有一个简单的形式br.s(偏移量127~-128). 示例:
br Finish
...
Finish:
ble --比较栈中的两个数值,如果第一个数小于或等于第二个数,则分支流程.示例:
ldc.i4 -21
ldc.i4 10
ble Smaller
..
Smaller:
...
对栈影响:...,value,value -> ...
ble.un --无符号比较,如果一个负数和正数比较的话,负数大!
brfalse,brfalse.s --栈顶元素为零
brtrue,brtrue.s --栈顶元素不为零
示例:
.assembly T{}
.method static void Main() cil managed
{
.maxstack 2
.entrypoint
ldstr "Input first number"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
ldstr "Input second number"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
ble.s FirstSmaller
ldstr "The first number was larger then the second one"
call void [mscorlib]System.Console::WriteLine(string)
br.s Finish
FirstSmaller:
ldstr "The first number was less then or equals to the second one"
call void [mscorlib]System.Console::WriteLine(string)
Finish:
ldstr "Thank you"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
编译运行:
D:/>ilasm T.il
Microsoft (R) .NET Framework IL Assembler. Version 1.1.4322.2032
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Assembling 'T.il' , no listing file, to EXE --> 'T.EXE'
Source file is ANSI
Assembled global method Main
Creating PE file
Emitting members:
Global Methods: 1;
Writing PE file
Operation completed successfully
D:/>T.exe
Input first number
23
Input second number
32
The first number was less then or equals to the second one
Thank you
D:/>
3、定义有参数的方法,示例:
.assembly extern mscorlib{}
.assembly T
{
.ver 1:0:1:0
}
.module T.exe
.namespace com.yzl.chp1
{
.class T extends [mscorlib]System.Object
{
.method public static bool FirstIsGreater(int32 x,int32 y) cil managed
{
.maxstack 2
ldarg.0
ldarg.1
ble.s FirstSmaller
ldc.i4.1
ret
FirstSmaller:
ldc.i4.0
ret
}
// 入口方法
.method public static void Main() cil managed
{
.maxstack 2
.entrypoint
ldstr "Input first number"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
ldstr "Input second number"
call void [mscorlib]System.Console::WriteLine(string)
call string [mscorlib]System.Console::ReadLine()
call int32 [mscorlib]System.Int32::Parse(string)
// 调用FirstIsGreater方法
call bool com.yzl.chp1.T::FirstIsGreater(int32,int32)
brfalse.s FirstSmaller
ldstr "The first number was larger then the second one"
call void [mscorlib]System.Console::WriteLine(string)
br.s Finish
FirstSmaller:
ldstr "The first number was less then or equals to the second one"
call void [mscorlib]System.Console::WriteLine(string)
Finish:
ldstr "Thank you!"
call void [mscorlib]System.Console::WriteLine(string)
ret
}
}
}
说明:
ldarg.0 --加载第一个参数到栈中
ldarg.1 --加载第二个参数到栈中
相应的:
starg.0 --将栈中数值赋值给第一个参数
starg.1 --同上道理
4、局部变量,由指令.locals完成.示例1:
.method static void DoSomething() cil managed
{
.locals init(unsigned int32,string)
// code for method
}
init --初始化局部变量
注意:变量使用之前必须初始化,否则代码会变得不可验证.
操作局部变量的指令是:ldloc.s,stloc.s,ldloc.0,stloc.0,...
--------------------------------------------------------------------------------
【版权声明】: 本文原创于泉州软件基地, 转载请注明作者并保持文章的完整, 谢谢!
2007年02月06日 17:25:49