Emit学习(2) - IL - 常用指令介绍

时间:2023-01-01 22:33:53

学习Emit必不可少的, 会使用到IL中间代码. 初见IL代码, 让我有一种汇编的感觉, 让我想起了, 大学时, 学习8051的汇编语言. 多的就不扯了, 直接进入正题, OpCodes指令集是不是有一种让人望而却步的感觉, 那么多, 具体我没有数过, 但是肯定是比8051的指令多不少, 应该有200多个吧, 不过在实际使用的过程中, 肯定是用不到这么多的, 所以只要掌握一些常用的就够用了, 其余的, 查资料就可以了(大学老师当时也是这么教的, 实际使用中, 也确实是这样的)

一、示例

上一篇的结束部分, 贴出了一个中文注释版的OpCodes文件, 这部分内容跟那个文件是有很大关联的. 貌似, 贴在这一篇更合适呢...

嗯, 还是应该从示例里去开始讲

来源 : http://www.cnblogs.com/zery/p/3366175.html

static void Sum(int sum, string sumStr)
{
            int a, b, c;
            a = 1;
            b = 2;
            c = 3;
            sum = a + b + c;
            sumStr = sum.ToString();
            Console.WriteLine(sumStr);

            for (int i = 0; i < c; i++)
            {
                if (i > b)
                {
                    Console.WriteLine("满足条件, 跳出循环");
                    break;
                }
            }
            Console.ReadKey();
}

 

.method private hidebysig static void Sum(int32 sum, string sumStr) cil managed
{
        .maxstack 2   //定义函数代码所用堆栈的最大深度,也指Evaluation Stackk中最多能同时存在2个值
        .locals init (    //变量的声明, (此时已经把num,num2,num3,num4,flag存入了Call Stack中的Record Frame中)
            [0] int32 num,
            [1] int32 num2,
            [2] int32 num3,
            [3] int32 num4,
            [4] bool flag)

        L_0000: nop        //无任何操作, 可忽略
        L_0001: ldc.i4.1   //加载 常量1 到栈中(压栈)
        L_0002: stloc.0    //从栈中把 常量1 拿出来, 赋值给num(出栈, 此时栈中已经没有东西了)
        L_0003: ldc.i4.2   //加载 常量2 到栈中(压栈)
        L_0004: stloc.1 
        L_0005: ldc.i4.3 
        L_0006: stloc.2 

        L_0007: ldloc.0   //将num变量压栈
        L_0008: ldloc.1   //将变量num2压栈 (此时栈中有两个值, num2在上面, num在下面)
        L_0009: add       //将num,num2求和的结果压栈(求和的时候, 会把两个值都提取出来, 所以结束后, 栈中只有一个结果值)
        L_000a: ldloc.2   //将num3压栈
        L_000b: add       //将num3,(num+num2)求和, 并压栈, 此时栈中, 只有最后的结果值
        L_000c: starg.s sum //将栈顶的值传给传参sum(短格式)

        L_000e: ldarga.s sum  //加载sum的地址到堆栈上(短格式)
        L_0010: call instance string [mscorlib]System.Int32::ToString()  //调用ToString()方法, 完成格式转换,将结果值放入堆栈中
        L_0015: starg.s sumStr   //将堆栈顶的值传给传参sumStr(短格式)

        L_0017: ldarg.1   //将索引为1的传参(sumStr)加载到堆栈中
        L_0018: call void [mscorlib]System.Console::WriteLine(string)  //调用Console.WriteLine方法

        L_001d: nop 
        L_001e: ldc.i4.0 
        L_001f: stloc.3      // i = 0
        L_0020: br.s L_0043 //无条件跳转到下面, 去判断 i<c 是否成立

        L_0022: nop 
        L_0023: ldloc.3   // i
        L_0024: ldloc.1   // b
        L_0025: cgt         // i > b ? 1 : 0
        L_0027: ldc.i4.0  //压栈0
        L_0028: ceq         //比较的结果在与0比较, (i > b ? 1 : 0) == 0 ? 1 : 0
        L_002a: stloc.s flag  //将结果存入本地变量flag
        L_002c: ldloc.s flag  //加载flag到堆栈中
        L_002e: brtrue.s L_003e //为真跳转到 L_003e

        L_0030: nop 
        L_0031: ldstr "\u6ee1\u8db3\u6761\u4ef6, \u8df3\u51fa\u5faa\u73af" //"满足条件, 跳出循环"
        L_0036: call void [mscorlib]System.Console::WriteLine(string)
        L_003b: nop 
        L_003c: br.s L_004d

        L_003e: nop 
        L_003f: ldloc.3    // i
        L_0040: ldc.i4.1  // 1
        L_0041: add        // i + 1
        L_0042: stloc.3   // i = i + 1

        L_0043: ldloc.3  // i
        L_0044: ldloc.2  //c
        L_0045: clt          // i < c ? 1 : 0
        L_0047: stloc.s flag  //将结果传给 flag
        L_0049: ldloc.s flag  //加载flag变量到堆栈中
        L_004b: brtrue.s L_0022  //为真跳转 L_0022

        L_004d: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
        L_0052: pop   //移除当前位于计算堆栈顶部的值
        L_0053: ret     //即为  return  标记 返回值
}

ldc.i4.1:  i4--int32, 1--数值, 合起来就是  加载int32的数值1到堆栈中

stloc.0: 0--前面声明的locals变量组中的第0个  将堆栈顶的值付给locals0变量

ldloc.0: 加载locals0变量到堆栈中

add : 将栈顶的两个值求和, 并将结果压栈

二、常用的指令

*:https://en.wikipedia.org/wiki/List_of_CIL_instructions

来源:http://blog.csdn.net/joyhen/article/details/47276433

1. 常用的加载类指令

ldarg (及多个变化形式)

ld -- load , arg -- argument, 对这个大家都不陌生吧, 就不多解释了

加载方法的参数的值到栈中。除了泛型ldarg(需要一个索引作为参数),还有后其他很多的变化形式。'.'有个数字后缀的ldarg操作码来指定需要加载的参数。

a -- address, s -- short

ldarga/ldarga.s表示的是加载参数的地址, 而不是加载参数的值

ldc (及多个变化形式)

c -- constant, const这个关键字大家肯定都很熟了, constant表示常量

加载一个常数到栈中

Ldc.I4.2   i4表示是int32的值(1个表示8位), 2表示常量

ldfld (及多个变化形式) 加载一个对象实例的成员到栈中
ldloc (及多个变化形式)

loc -- locals

加载一个本地变量到栈中

ldobj 获得一个堆对象的所有数据,并将它们放置到栈中. OpCodes:将地址指向的值类型对象复制到计算堆栈的顶部。
ldstr 加载一个字符串数据到栈中

 

2. 常用的弹出操作指令

pop  删除当前栈顶的值,但是并不影响存储的值
starg

st -- store

存储栈顶的值到给出方法的参数,根据索引确定这个参数. OpCodes:将位于计算堆栈顶部的值存储到位于指定索引的参数槽中

stloc (及多个变化形式) 弹出当前栈顶的值并存储在一个本地变量列表中,根据所以确定这个参数
stobj 从栈中复制一个特定的类型到指定的内存地址
stfld 用从栈中获得的值替换对象成员的值

 

 3. 常用的其他操作类指令

add, sub, mul, div, rem

用于两个数加减乘除求模, 并将结果推送到计算堆栈上 

and, or, not, xor 用于在两个值上进行二进制操作
ceq, cgt, clt

用不同的方法比较两个在栈上的值

c -- compare

ceq:是否相等 -- 如果这两个值相等,则将整数值 1 (int32) 推送到计算堆栈上;否则,将 0 (int32) 推送到计算堆栈上

cgt:是否大于 -- 如果第一个值大于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。

cgt.un -- 比较两个无符号的或不可排序的值, un -- unsigned 无符号

clt:是否小于 -- 如果第一个值小于第二个值,则将整数值 1 (int32) 推送到计算堆栈上;反之,将 0 (int32) 推送到计算堆栈上。

box, unbox

在引用类型和值类型之间转换

box: 装箱

unbox: 拆箱

ret 退出方法和返回一个值
beq, bgt,bge,ble, blt, switch

控制方法中的条件分支

b -- break, eq,e -- equal

beq:如果两个值相等,则将控制转移到目标指令;

bgt:如果第一个值 > 第二个值,则将控制转移到目标指令

bge:如果第一个值 >= 第二个值,则将控制转移到目标指令

ble:如果第一个值 <= 第二个值,则将控制转移到目标指令

blt:如果第一个值 < 第二个值,则将控制转移到目标指令

switch:实现跳转表

所有的分支控制操作码都需要给出一个CIL代码标签作为条件为真的跳转目的地

brtrue

如果 value 为 true、非空或非零,则将控制转移到目标指令

br/br.s

br:(无条件)中止到代码标签

br.s:无条件地将控制转移到目标指令(短格式)

call 调用一个成员
newarr, newobj

在内存中创建一个新的数组或新的对象类型

newarr:将对新的从零开始的一维数组(其元素属于特定类型)的对象引用推送到计算堆栈上

newobj:创建一个值类型的新对象或新实例,并将对象引用(O 类型)推送到计算堆栈上

 

未完待续......