【文章标题】: 乱涂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