现在的位置: 首页 > 综合 > 正文

乱涂IL中间语言

2017年12月03日 ⁄ 综合 ⁄ 共 11973字 ⁄ 字号 评论关闭

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

 

抱歉!评论已关闭.