《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

 

 

posted @ 2007-07-01 19:35  EagleFish(邢瑜琨)  阅读(2182)  评论(3编辑  收藏  举报