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

学习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 类型)推送到计算堆栈上

 

未完待续......

posted @ 2016-10-27 16:05  Sniper_ZL  阅读(2181)  评论(0编辑  收藏  举报