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

时间:2021-08-18 20:25:36

学习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 = ;
b = ;
c = ;
sum = a + b + c;
sumStr = sum.ToString();
Console.WriteLine(sumStr); for (int i = ; i < c; i++)
{
if (i > b)
{
Console.WriteLine("满足条件, 跳出循环");
break;
}
}
Console.ReadKey();
}
.method private hidebysig static void Sum(int32 sum, string sumStr) cil managed
{
.maxstack //定义函数代码所用堆栈的最大深度,也指Evaluation Stackk中最多能同时存在2个值
.locals init ( //变量的声明, (此时已经把num,num2,num3,num4,flag存入了Call Stack中的Record Frame中)
[] int32 num,
[] int32 num2,
[] int32 num3,
[] int32 num4,
[] bool flag) L_0000: nop //无任何操作, 可忽略
L_0001: ldc.i4. //加载 常量1 到栈中(压栈)
L_0002: stloc. //从栈中把 常量1 拿出来, 赋值给num(出栈, 此时栈中已经没有东西了)
L_0003: ldc.i4. //加载 常量2 到栈中(压栈)
L_0004: stloc.
L_0005: ldc.i4.
L_0006: stloc. L_0007: ldloc. //将num变量压栈
L_0008: ldloc. //将变量num2压栈 (此时栈中有两个值, num2在上面, num在下面)
L_0009: add //将num,num2求和的结果压栈(求和的时候, 会把两个值都提取出来, 所以结束后, 栈中只有一个结果值)
L_000a: ldloc. //将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的传参(sumStr)加载到堆栈中
L_0018: call void [mscorlib]System.Console::WriteLine(string) //调用Console.WriteLine方法 L_001d: nop
L_001e: ldc.i4.
L_001f: stloc. // i = 0
L_0020: br.s L_0043 //无条件跳转到下面, 去判断 i<c 是否成立 L_0022: nop
L_0023: ldloc. // i
L_0024: ldloc. // b
L_0025: cgt // i > b ? 1 : 0
L_0027: ldc.i4. //压栈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. // i
L_0040: ldc.i4. //
L_0041: add // i + 1
L_0042: stloc. // i = i + 1 L_0043: ldloc. // i
L_0044: ldloc. //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 类型)推送到计算堆栈上

未完待续......