C#语言:MSIL指令

  • 通常一个IL指令有操作码(opcode)和指令参数(instruction parameter,有时也称作操作数 operand)两个部分构成,操作码长度为1或2字节,当为2字节长时,第1个字节总是0xFE。IL中的整数在PE中存储时,遵循“little endian”原则,也就是最不重要的字节置于最前,比如代码中的ldc.i4 0x11223344,在PE文件中存储时的字节顺序为0x44332211。

(一)流程控制指令
流程控制指令包含:直接跳转、比较跳转、选择跳转、中断(调试断点)指令、托管异常处理相关指令和返回指令。

1、直接跳转指令

●br(0x38)。从当前位置移动个位置。
●br.s(0x2B)。br的短指令格式。

这里,和在IL代码中通常以标签表示,一般不直接输入整数,如:
...
BranchTarget:
...
br BranchTarget
...

2、条件跳转指令
条件跳转指令需要从栈顶取一个bool类型的值进行比较,下面称该值为,.NET中的true值为非0,false值为0。

●brfalse(brnull,brzero)(0x39)。为0则跳转。
●brfalse.s(brnull.s,brzero.s)(0x2C)。brfalse的短指令格式。
●brtrue(brinst)(0x3A)。不为0则跳转。
●brtrue.s(brinst.s)(0x2D)。brtrue的短指令。

3、比较跳转指令
比较跳转指令从堆栈顶取两个值并进行比较,根据比较结果判断是否跳转,进行比较时,先入栈,后入栈,因此栈顶元素为。
●beq(0x3B)。等于则跳转。
●deq.s(0x2E)。beq的短指令格式。
●bne.un(0x40)。不能于则跳转(无符号)。
●bne.un.s(0x33)。bne.un的短指令格式。
●bge(0x3C)。大于或等于则跳转。
●bge.s(0x2F)。bge的短指令格式。
●bge.un(0x41)。大于或等于则跳转   (无符号)。
●bge.un.s(0x34)。bge.un的短指令格式   (无符号)。
●bgt(0x3D)。大于则跳转。
●bgt.s(0x30)。bgt的短指令格式。
●bgt.un(0x42)。大于则跳转   (无符号)。
●bgt.un。s(0x35)。bgt.un的短指令格式   (无符号)。
●ble(0x3E)。小于等于则跳。
●ble.s(0x31)。ble的短指令格式。
●ble.un(0x43)。小于等于则跳转   (无符号)。
●ble.un.s(0x36)。ble.un的短指令格式   (无符号)。
●blt(0x3F)。小于则跳转。
●blt.s(0x32)。blt的短指令格式。
●blt.un(0x44)。小于则跳转(无符号)。
●blt.un.s(0x37)。blt.un的短指令格式   (无符号)。

4、选择指令
选择指令的格式如下:
●switch...(0x45)。
代码中的写法如下:

switch(Label1,Lable2,...,LabelN)
... // Default case
Label1:
...
Label2:
...

Switch会建立跳转地址表,并从栈顶取unsigned int32,根据这个uint32的值选择跳转目标,值为0时就跳转带第一个地址Label1。

5、中断指令
●break(0x01)。调试断点。如果程序处在被调试状态,调试器会中断在这句指令,反之该指令不起任何作用。

6、托管异常处理相关指令
.NET 程序中,当从try和catch块中跳出时,不能使用普通的跳转指令,而必须使用特殊的leave指令。
●leave(0xDD)。 清空堆栈,调试据当前指令偏移处。
●leave.s(0xDE)。leave的短指令格式。
  除try与catch块以外个其他异常处理代码块,如filter、finally和fault块,则不能用leave指令跳出,必须使用下面的指令:
●endfilter(0xFE 0x11)。表示filter块的终结。从栈顶去4字节整数值,该值为1时代表该异常应该被处理,不为1时代表继续搜索合适的异常处理。
●endfinally(endfault)(0xDC)。表示finally或fault块的终结,并情况堆栈。

7、返回指令
●ret(0x2A)。从一个方法返回。
这个指令可能是程序分析过程中最常见的指令之一,如果方法有返回值,则ret执行时堆栈顶应有相应的值,如果方法的返回值为void,则ret执行时堆栈应为空。

 

(二)算术指令
算术指令数量较多,它包括数据处理、堆栈操作、常数载入、间接存储和载入、算术运算、位操作、数据转换、逻辑条件检测和块操作几个方面。

1、堆栈操作
堆栈操作指令的功能是对堆栈进行操作,他们没有指令参数。
●nop(0x00)。空指令,没有任何操作。类似与Win32中的 nop (0x90)。
●dup(0x25)。复制当前的栈顶元素,如果堆栈为空则指令出错 (underflow)。
●pop(0x26)。出栈,既移除当前栈顶元素,同样,在堆栈为空时出错。

2、常数载入
常数载入指令用于将其参数(必须是常量,不可以是变量或参量名)入栈。
●ldc.i4(0x20)。载入。
●ldc.i4.s(0x1F)。载入。
●ldc.i4.ml(ldc.i4.M1)(0x15)。载入-1。
●ldc.i4.0(0x16)。载入0。
●ldc.i4.1(0x17)。载入1。
●ldc.i4.2(0x18)。载入2。
●ldc.i4.3(0x19)。载入3。
●ldc.i4.4(0x1A)。载入4。
●ldc.i4.5(0x1B)。载入5。
●ldc.i4.6(0x1C)。载入6。
●ldc.i4.7(0x1D)。载入7。
●ldc.i4.8(0x1E)。载入8。
●ldc.i8(0x21)。 载入.
●ldc.r4(0x22)。 载入(单精度)。
●ldc.r8(0x23)。载入(双精度)。

3、间接载入
间接载入指令从栈顶取一个托管指针(&类型),载入该指针所指向的数值,并入栈。指令中小数点后的名称表明了在载入数据的类型.
●ldind.i1(0x46)。 载入1字节有符号整数。
●ldind.u1(0x47)。 载入1字节无符号整数。
●ldind.i2(0x48)。载入2资字节有符号整数。
●ldind.u2(0x49)。载入2字节无符号整数。
●ldind.i4(0x4A)。载入4字节有符号整数。
●ldind.u4(0x4B)。载入4字节无符号整数。
●ldind.i8(ldind.u8)(0x4c)。载入8字节有(或无)符号整数。
●ldind.i(0x4D)。载入native int,大小为当前系统的指针大小。
●ldind.r4(0x4E)。载入单精度浮点数据。
●ldind.r8(0x4F)。载入双精度浮点数据。
●ldind.ref(0x50)。载入对象引用(Object Reference)。

4、间接存储
间接存储指令从栈顶先后取一个值个一个指针,并将该值存储至所指位置。有于指令完成的功能仅仅是内存的复制,并不关注数据的具体类型,因此没有和间接载入指令那样的无符号类型指令。
●stind.ref(0x51)。存储一个对象引用。
●stind.i1(0x52)。存储1字节整数。
●stind.i2(0x53)。存储2字节整数。
●stind.i4(0x54)。存储4字节整数。
●stind.i8(0x55)。存储8字节整数。
●stind.i(0xDF)。存储一个指针大小的数据。
●stind.r4(0x56)。存储单精度浮点数。
●stind.r8(0x57)。存储双精度浮点数。

5、算术运算
算术运算指令从淡定取出两个参数进行相应运算,并将计算结果入栈.
●add(0x58)。加法。
●sub(0x59)。减法。
●mul(0x5A)。乘法。对于浮点数,存在两个特殊数值,无限(infinity)和NaN(Not a Number),并且规定0×infinity=NaN。
●div(0x5B)。除法。整数除0时抛出DivideByZero异常;浮点数另外遵循以下原则:0/0=NaN,infinity/infinity=NaN,x/infinity=0
●div.un(0x5C)。无符号整数相除。
●rem(0x5D)。模运算。整数运算时模0会抛出DivideByZero异常;浮点数另外遵循以下原则:
infinity rem x=NaN,x rem 0 =NaN,x rem infinity=x
●rem.un(0x5E)。无符号整数的模运算。
●neg(0x65)。取反操作,也就是正负号变换。(热数指令,进从堆栈取一个数值。)带溢出的算术指令和前述指令类似,唯一的区别是在计算移除时,带溢出指令会抛出 Overflow 异常。
●add.ovf(0xD6)。加法。
●add.ovf.un(0xD7)。无符号加法。
●sub.ovf(0xDA)。减法。
●sub.ovf.un(0xDB)。无符号减法。
●mul.ovf(0xD8)。乘法。
●mul.ovf.un(0xD9)。无符号乘法。

6、位操作指令
位操作指令包含四个,前三个取两个操作数,另有一条指令只去一个操作数,并将结果入栈。
●and(0x5F)。按位与。
●or(0x60)。按位或。
●xor(0x61)。按位异或。
●not(0x66)。按位求反(单个操作数)。
一条not指令相当于下面三条指令完成的功能:
 idc.i4.1
 add
 neg

7、移位操作
移位操作指令从栈中分别取移位的位数和带一位的数值,并将结果入栈. MSIL 中没有循环移位操作符.
●shl(0x62)。左移位。
●hr(0x63)。右移位。
●shr.un(0x64)。无符号右移位。

8、转换指令
转换指令从栈顶取值,进行相应转换后,将结果入栈。指令分不带溢出和带溢出两种,不同的类型之间进行转换时,会有相应的精度或数值上的变化,此时带溢出指令会派出Overflow异常。对象引用不能进行转换。
●conv.i1(0x67)。转换为int8。
●conv.i2(0x68)。转换为int16。
●conv.i4(0x69)。转换为int32。
●conv.r4(0x6B)。转换为float32。
●conv.r8(0x6C)。转换为float64。

 

(三)参数、局部变量与字段寻址指令
标题中的“寻址”与Win32/64下的传统寻址含义不同,传统编程中寻址直接以内存地址为操作对象,而这里的寻址是抽象的,所有与内存直接打交道的事都由.NET完成。

1、方法参数载入
下列指令用于将方法的参数载入至堆栈。
●ldarg(0xFE 0x09)。载入第个参数并入栈。注意,对于instance方法,第一个(序数为0)的参数总是.this,这是不可见的;而对于static的方法,没有.this指针,因此第一个参数(序数为0)就是传入的第一个参数。由于元素格式的限制,参数最大的个数不超过65535(0xFFFF),包含隐藏数。
●ldarg.s(0x0E)。ldarg的短指令格式。
●ldarg.0(0x02)。载入第0个参数并入栈。
●ldarg.1(0x03)。载入第1个参数并入栈。
●ldarg.2(0x04)。载入第2个参数并入栈。
●ldarg.3(0x05)。载入第3个参数并入栈。

2、方法参数的地址载入
●ldarga(0xFE 0x0A)。载入第个参数的地址并入栈。
●ldarga.s(0x0F)。ldarga的短指令格式。

3、方法参数存储
●starg(0xFE 0x0B)。从栈顶取值并存入第个参数。
●starg.s(0x10)。strg的短指令格式。

4、方法参数列表
●arglist(0xFE0x00)。取得方法参数列表的句柄,该指令主要用于参数个数可变的方法中,并将[mscorlib]SYstem.RuntimeArgumentHandle类型的句柄入栈。

5、局部变量载入
●ldloc (0xFE 0x0C)。载入第个局部变量并入栈,序号从0开始,最大个数为65534(0xFFFE)。
●ldloc.s(0x11)。ldloc的短指令格式。
●ldloc.0 (0x06)。载入第1个(序号为0)局部变量并入栈。
●ldloc.1 (0x07)。载入第2个(序号为1)局部变量并入栈。
●ldloc.2 (0x08)。载入第3个(序号为2)局部变量并入栈。
●ldloc.3 (0x09)。载入第4个(序号为3)局部变量并入栈。

6、局部变量引用的载入
●ldloca (0xFE 0x0D)。载入第个局部变量的引用(托管指针)并入栈。
●ldloca (0x12)。ldloca的短指令格式。

7、局部变量保存
●stloc (0xFE 0x0E)。从栈顶取值并存入第个局部变量。
●stloc.s (0x13)。stloc的短指令格式。

8、局部内存块分配
●localloc (0xFE 0x0F)。分配一块局部内存块,该指令从栈顶取块的大小(native unsigned int 类型),并将分配的内存块的托管指针入栈。

9、前缀指令
用于从栈顶取指针数据的指令钱,表示指针的性质。另外有三个前缀指令用于方法调用和队列操作。
●unaligned.(0xFE 0x12)。表明栈顶的指针数据大小为个字节,而不是系统指针的大小。这里的可以为1、2和4。
●volatile.(0xFE 0x13)。表明栈顶指针所指内容有可能被别的线程改变。

10、字段寻址
用于对字段寻址,由于字段的签名(Signature)没有表明该字段是 instance 还是static,所以必须在指令上区分两种不同的寻址方式。
●ldfld (0x7B)。从栈顶取实例指针,然后按取字段,并入栈。实例指针可以使对象引用、类型实例的托管指针、或是类型实例本身。类型实例的非托管指针也可以,但会使代码变成不可验证(unverfiable)。
●ldsfld (0x7E)。载入静态字段并入栈。
●ldflda (0x7C)。从栈顶取实例指针,然后按取字段的托管指针,并入栈。该指令与ldfld的区别是,栈顶指针不能是类型实例,只可以是对象引用或托管(非托管)指针。
●ldsflda (0x7F)。载入静态字段的托管指针并入栈。
●stfld (0x7D)。从栈中取待存储数据和实例指针,并将该数据存储到对就的字段中。
●stsfld (0x80)。存储数据到静态字段中。

 

(四)方法调用
.NET中的方法调用按调用的方式分为直接(direct)和间接(indirect)两种,并且由于方法的元数据中没有指明方法是虚的(virtual)还是非虚的(nonvirtual),所以又细分为两种指令分别对应虚方法和非虚方法,不过对于instance和static方法没有区分指令。

1、直接调用
●jmp (0x27)。放弃房钱方法的执行,直接转至所指的目标。
●call< token>(0x28),以不同v-table的方式调用一个instance或static方法。
●callvirt (0x6F)。通过实例的v-table调用所指的方法。

2、间接调用
●ldftn (0xFE 0x06)。取得所标示方法的指针并入栈。
●ldvirtftn (0xFE 0x07)。从栈顶取对象引用(实例指针),并从相应的v-table中取得方法的指针后入栈。
●calli (0x29)。从栈顶获取方法指针,并从栈中一次读取方法的参数,按标示的方法调用方法。

3、尾部调用(tail call)

4、虚方法调用的约束前缀
●constrained. (0xFE 0x16)。标示其后接的callvirt指令是一个约束调用。

 

(五)类与值类型操作指令
●ldnull(0x14)。读取空的对象引用并入栈。
●ldobj (0x71)。从堆栈获取值类型实例的托管指针,然后读取所标示的值类型的实例,并入栈。
●stobj (0x81)。依次从对战中取得值类型的值(value type value),以及值类型实例的指针,然后将该值存储在指针所指的实例中。
●ldstr (0x72)。常用指令,读入一个字符串,建立[mscorlib]System.String的实例,并入栈。

 

(六)向量操作指令
元素载入
下面的指令用来读取向量中的一个元素,他们依次从栈中取得元素序号、向量对象的引用。如果对象引用为空,则抛出NullReference异常;如果序号超出向量的元素个数,则抛出IndexOutOfRange异常。
●ldelem.i1(0x90)。载入int8类型的向量元素。
●ldelem.i2(0x92)。载入int16类型的向量元素。
●ldelem.i4(0x94)。载入int32类型的向量元素。
●ldelem.i8(ldelem.u8)(0x95)。载入int64类型的向量元素。
●ldelem.i(0x97)。载入native int类型的向量元素。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值