• Emit学习(2)


    学习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 "u6ee1u8db3u6761u4ef6, u8df3u51fau5faau73af" //"满足条件, 跳出循环"
            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 类型)推送到计算堆栈上

    未完待续......

  • 相关阅读:
    QStringLiteral(源代码里有一个通过构造函数产生的从const char*到QString的隐式转换,QStringLiteral字符串可以放在代码的任何地方,编译期直接生成utf16字符串,速度很快,体积变大)
    utf8格式源代码中的字符串,默认都会当作char来处理,除非用L""符号来修饰
    Qt Installer Framework的学习
    发布Qt Quick桌面应用程序的方法
    先对数组排序,在进行折半查找(C++)
    7个高效习惯
    人活着是靠精神(几个例子都是我自己发现的)
    VC++对象布局的奥秘:虚函数、多继承、虚拟继承
    delphi数字签名验证及能够获取数字签名文件信息(利用wintrust.dll的导出函数,翻译一下)
    算法系列之二十:计算中国农历(二)
  • 原文地址:https://www.cnblogs.com/elvinle/p/6003716.html
Copyright © 2020-2023  润新知