zoukankan      html  css  js  c++  java
  • 《Inside Microsoft IL Assembler》学习笔记1:初步认识IL代码

    在《Inside Microsoft IL Assembler》一书的开始部分,给出了一段典型的IL代码。代码的功能很简单,从终端接受一个输入,判断这个输入是奇数还是偶数,并在屏幕上返回结果:
     1//----------- Program header
     2.assembly extern mscorlib { }
     3.assembly OddOrEven  { }
     4.module OddOrEven.exe
     5//----------- Class declaration
     6.namespace Odd.or {
     7    .class public auto ansi Even extends [mscorlib]System.Object {
     8//----------- Field declaration
     9        .field public static int32 val
    10//----------- Method declaration
    11        .method public static void check( ) cil managed {
    12            .entrypoint
    13            .locals init (int32 Retval)
    14        AskForNumber:
    15            ldstr "Enter a number"
    16            call void [mscorlib]System.Console::WriteLine(string)
    17            call string [mscorlib]System.Console::ReadLine()
    18            ldsflda valuetype CharArray8 Format
    19            ldsflda int32 Odd.or.Even::val
    20            call vararg int32 sscanf(string,int8*,,int32*)
    21            stloc Retval
    22            ldloc Retval
    23            brfalse Error
    24            ldsfld int32 Odd.or.Even::val
    25            ldc.i4 1
    26            and
    27            brfalse ItsEven
    28            ldstr "odd!"
    29            br PrintAndReturn
    30        ItsEven:
    31            ldstr "even!"
    32            br PrintAndReturn
    33        Error:
    34            ldstr "How rude!"
    35        PrintAndReturn:
    36            call void [mscorlib]System.Console::WriteLine(string)
    37            ldloc Retval
    38            brtrue AskForNumber
    39            ret
    40        }
     // End of method
    41    }
     // End of class
    42}
     // End of namespace
    43//----------- Global items
    44.field public static valuetype CharArray8 Format at FormatData
    45//----------- Data declaration
    46.data FormatData = bytearray(25 64 00 00 00 00 00 00//% d . . . . . .
    47//----------- Value type as placeholder
    48.class public explicit CharArray8 
    49              extends [mscorlib]System.ValueType { .size 8 }
    50//----------- Calling unmanaged code
    51.method public static pinvokeimpl("msvcrt.dll" cdecl) 
    52    vararg int32 sscanf(string,int8*) cil managed { }
    53

      看了书内的说明,结合自己的理解,我给这一段代码添加了详细的注释,相信对所有IL代码的初学者都会有所帮助,加上注释之后的代码如下:
     1//----------- Program header
     2.assembly extern mscorlib //说明要引用外部程序集mscorlib.dll(这里只能写文件名,而不能加扩展名)
     3//这里可以放入对引用程序集的一些声名,如版本、语言文化标志等
     4}

     5.assembly OddOrEven //指明了当前应用(程序集)的名称
     6//版本、语言文化标志等
     7}

     8.module OddOrEven.exe //定义了一个模块的元数据,用来指定当前模块
     9//----------- Class declaration
    10.namespace Odd.or //namespace不是一个单独的元数据项,只是它定义范围内的类型的前缀
    11{
    12    .class public auto ansi Even //将在元数据表中生成一个TypeDef记录;
    13                                           //注意,对于同一个类型,你可以使用任意数量的TypeDef段来定义同一个类型,这被称为“类修补”(class amendment)
    14    extends [mscorlib]System.Object //将在元数据表中生成一个TypeRef记录
    15    //public 关键字指明了类型在程序集外的可见性
    16    //anto 关键字指明了类型实例中字段的排列方式为自动
    17    //ansi 关键字指明了这个类型在和非托管代码交互时,字符串的解析方式为C-风格的数组性字符串(其余选项包括Unicode等)
    18    {
    19//----------- Field declaration
    20        .field public static int32 val //将在元数据表中加入一个FieldDef记录;在这里,public关键字的可能替换项包括assembly,family,private等
    21//----------- Method declaration
    22        .method public static void check( ) cil managed //将在元数据表中加入一个MethodDef记录;
    23        //关键字cil managed说明方法体中为IL代码,如果为native code的话可使用native unmanaged关键字
    24    {//在IL代码中,方法体一般包括三部分内容:
    25      //(1)操作instruction。IL是一个严格的基于堆栈的语言,它的所有操作都是基于栈的。并且栈的单位不是通常的字或字节,而是"slot"。slot没有固定程度,
    26      // 每一个slot都包含了当前正在使用它的那个元素的类型信息。我们谈论IL的堆栈深度,一般都是指slot的数目。
    27      //(2)标签labels(3)指令directive,编译后指令里的内容将不出现在IL代码里,而是在元数据表、结构化异常处理语句等中
    28            .entrypoint //derective, 说明这个位置是整个应用的入口点。每一个受托管的exe文件都必须由且仅有一个入口点,否则汇编器会报错。
    29            .locals init (int32 Retval) //derective 整个方法唯一一个内部变量;关键字init指明此变量必须在方法执行前被初始化。
    30        AskForNumber: //label,本质上就是个偏移量,指向紧跟在它后面的第一条指令。
    31            ldstr "Enter a number" //将字符串压栈(这个字符串常量将在编译的时候被存储到元数据表)
    32            call void [mscorlib]System.Console::WriteLine(string//调用这个方法的时候,栈顶字符串出栈;调用完毕后,没有东西压栈,因为这个方法没有返回值
    33            call string [mscorlib]System.Console::ReadLine() // 调用这个方法完毕后,返回的string对象将被压栈
    34            ldsflda valuetype CharArray8 Format //加载实例和静态字段的指令分别为ldfld和ldsfld,而加载它们的地址为ldflda和ldsflda
    35        //这里就是把类型为CharArray8的静态字段Format的地址压栈。
    36            ldsflda int32 Odd.or.Even::val //把静态字段val的地址压栈
    37            call vararg int32 sscanf(string,int8*,,int32*//调用一个全局的静态函数。这个函数从当前堆栈上取下栈顶的3个slot作为参数进行运算,并返回
    38        //一个整型值压栈。这里有两点需要注意:
    39        //a. 这个方法C运行库的一个非托管方法(参见下面对这个方法调用的声明)
    40        //b. 这个方法接受的是一个可变长度参数列表vararg。vararg包括必须参数和可选参数两部分,其中可选参数的数量和类型都是在调用处指定的。
    41        //参数中的省略号是一个“哨兵参数”(sentinel),它用来把方法的必选参数和可选参数分隔开来(更贴切的理解是,把它看作可选参数的开始部分,
    42        //因为以哨兵参数为结尾的参数列表是不合法的)。
    43            stloc Retval //从栈顶取出sscanf的返回值,并将其赋值给局部变量
    44            ldloc Retval //将Retval的值压栈
    45            brfalse Error //取栈顶的值,若为false(即0),则跳转到Error标签
    46            ldsfld int32 Odd.or.Even::val //将静态字段val的值压栈
    47            ldc.i4 1 //将int32类型的常量1压栈
    48            and //取栈顶的两个slot,对里面的值作按位布尔与操作,并将结果压栈
    49            brfalse ItsEven
    50            ldstr "odd!"
    51            br PrintAndReturn //跳转到指定标签
    52        ItsEven:
    53            ldstr "even!"
    54            br PrintAndReturn
    55        Error:
    56            ldstr "How rude!"
    57        PrintAndReturn:
    58            call void [mscorlib]System.Console::WriteLine(string)
    59            ldloc Retval
    60            brtrue AskForNumber
    61            ret //返回栈顶值,方法调用结束
    62        }
     // End of method
    63    }
     // End of class
    64}
     // End of namespace
    65//----------- Global items
    66//全局量属于它声明所在的模块(module),它们无法被别的程序集访问。
    67.field public static valuetype CharArray8 Format at FormatData
    68//----------- Data declaration
    69.data FormatData = bytearray(25 64 00 00 00 00 00 00//定义了模块中一个名为FormatData的数据段,它含有8个字节,其中前两个字节分别为
    70    //%(0x25)和d(0x64),后面6个字节都是0。任何数据都可以用bytearray来表示,比如在前面的程序中,ldstr "odd!"就完全可以用
    71    //ldstr bytearray(6F 00 64 00 64 00 21 00 00 00)来代替
    72//----------- Value type as placeholder
    73.class public explicit CharArray8 
    74              extends [mscorlib]System.ValueType { .size 8 }//声明了一个没有定义变量、仅定义了长度的值类型,这是声明一段内存的常用方法。
    75        //在这里我们就用这样一个8字节的值类型作为全局字段Format的类型。
    76//----------- Calling unmanaged code
    77.method public static pinvokeimpl("msvcrt.dll" cdecl) 
    78    vararg int32 sscanf(string,int8*) cil managed { }//定义了一个从托管堆中调用的非托管方法。pinvokeimpl("msvcrt.dll" cdecl) 指明了被调用的
    79    //方法位于非托管程序集msvcrt.dll,其中cdecl是CallingConvention枚举的一个可选值,表明由调用方来清理堆栈,这样才能支持可变参数的调用。
    80    //可以看到,这里声明的并非一个实际的非托管方法,而是由运行时产生的“存根”(stub),这个存根可以由托管代码直接访问。
    81


    接下来,为了能让自己的理解更清晰,我又将IL代码反编译成了C#代码如下:

     1using System.Runtime.InteropServices;
     2namespace Odd.or
     3{
     4    public class Even
     5    {
     6        // Fields
     7        public static int val;
     8
     9        // Methods
    10        public static unsafe void check()
    11        {
    12            int num;
    13            do
    14            {
    15                Console.WriteLine("Enter a number");
    16                num = sscanf(Console.ReadLine(), (sbyte*&Format, &val);
    17                Console.WriteLine((num == 0? "How rude!" : (((val & 1== 0? "even!" : "odd!"));
    18            }

    19            while (num != 0);
    20        }

    21    }

    22}

    23
    24//下面是一些全局类
    25[StructLayout(LayoutKind.Explicit, Size=8)]
    26public struct CharArray8
    27{
    28}

    29
    30public static CharArray8 Format; // data size: 8 bytes
    31
    32[DllImport("msvcrt.dll", CallingConvention=CallingConvention.Cdecl, PreserveSig=false)]
    33public static extern unsafe int sscanf(string A_0, sbyte* A_1, __arglist);
    34

    本来,我是希望把这段C#代码重新编译为IL,从而检验反编译器的准确性,但我发现这段C#代码编译的时候会出错,我看了出错说明,不清楚应该怎样改动代码才能通过编译。希望有心的朋友看一下这段C#,或者拷贝到本地编译一下,然后帮我指出其中的错误,多谢了!

    参考资料:MSIL instruction table

  • 相关阅读:
    安装Linux Mint 17后要做的20件事
    通过HttpURLConnection 上传和下载文件(二)
    Apache Solr入门教程(初学者之旅)
    厉害了,利用深度学习开发人脸识别老板探测器(附源码)
    Solr教程--官方自带数据的三个练习及讨论翻译版本
    solr启动时报错org.apache.solr.common.SolrException: undefined field text的解决办法
    Qt项目里的源代码默认都是Unicode,原因大概是因为qmake.conf里的定义
    How to Capture the Integer-Divide-By-Zero Error in C++(提前定义信号)
    Model-View-Controller Explained in C++
    How to Use the Dynamic Link Library in C++ Linux (C++调用Delphi写的.so文件)
  • 原文地址:https://www.cnblogs.com/xingyukun/p/802207.html
Copyright © 2011-2022 走看看