内容包括:条件码寄存器、二进制数加减规则、汇编指令[mov、call、push、pop、ret、add/sub、imul、cmp、shr/shl、lea、jmp、jcondition(jge、jle)]
一、条件码寄存器:描述最近算数或逻辑运算符操作结果的属性,存储值有1和0 , 1表示符合该属性,0表示不符合。
- CF: Carry Flag(进/借位标志) 无符号数操作结果溢出(发生进位/借位)
- ZF: Zero Flag(零标志) 结果为0
- SF: Sign Flag(符号标志) 结果为负数
- OF: Overflow Flag(溢出标志) 有符号数操作结果溢出(数据位高位和符号位高位 进位与否情况相同)
CF是无符号操作数 操作结果的属性,OF是有符号操作数操作结果的属性
二、二进制数有符号数溢出、无符号数溢出举例:(后续学完硬件加法器的内容,再来详细梳理一下)
1. 有符号数:这里举例为1个字节的有符号数,范围为 -128 ~ 127,正数最大为0111 1111 即 2^7 - 1 = 127,负数最大为1000 0000 即 -2^7 = -128
- 数据位高位进位,符号位未进位,溢出,如:0100 1111 + 0100 1010 =(0)1001 1001 [79 + 74 = -103](原来十进制数的结果往-128~127区间右边溢出)
- 数据位高位未进位,符号位进位,溢出,如:1000 0111 + 1000 0000 = (1)0000 0111 [(-121) +(-128)= 7] (原来十进制数的结果往-128~127区间左边溢出)
- 数据位高位进位,符号位进位,不溢出,如:1111 1111 + 1100 0000 = (1) 1011 1111 [(-1) + (-64)= (-65)]
- 数据位高位未进位,符号位未进位,不溢出,如:0000 1111 + 0000 1001 =(0) 0001 1000 (15 + 9 = 24)
注:括弧中为加法器进位,存储1个字节的内存空间或寄存器中的值只看后8位。
总结:
(1)溢出的情况就是两个大的正数相加,或两个大的负数相加(超过了当前符号数类型所能表示数的范围)。
(2)溢出发生时,数据位高位与符号位一个进位一个不进位。
(3)不溢出即两个小的负数相加,或两个小的正数相加,或一正一负相加。
(4)不溢出时,数据位高位与符号位 进位与否情况相同。
2. 无符号数:举例1个字节的无符号数,范围为0~255,即0000 0000 ~ 1111 1111
- 借位如: 1000 0110 + 0000 1010=(0)1001 0000 [134 - 246 = 134+(-246)= 144] (原来十进制数的结果往0~255区间左边溢出)[做减法时,此时未发生进位C=0,CF=C^1,即C与1异或,因此CF=1]
- 进位如:1000 0110 + 1111 0110 = (1)0111 1100 [134 + 246 = 124](原来十进制数的结果往0~255区间右边溢出)
注:减法相当于取补码,-246即246的二进制取反加1后再进行运算。
3. 无符号数、有符号数机器码的联系
如在8位字长的计算机中(即int类型空间为1字节):
- 无符号数赋值给有符号数时,机器码相同:unsigned int x = 134;int m = x,此时m和x的机器码均为86H即1000 0110B,只是最终表示的十进制值不同,无符号数表示的是134,有符号数表示的是-122。
- 将两个无符号数各自赋值给两个有符号数,两个有符号数相加/减的机器码,与两个原来两个无符号数相加/减的机器码相同:unsigned int x = 134;int m = x;unsigned int y = 246;int n = y;则x-y的机器码与m-n的机器码相同,均为90H=10010000B,其表示的十进制为多少,取决于接收结果的空间是int还是unsined类型 。
三、mov指令:
将第二个操作数(寄存器的内容、内存中的内容或常数值)复制到第一个操作数(寄存器或内存),但不能用于直接从内存复制到内存。
如:不能直接mov DWORD PTR[esp+32],DWORD PTR[esp+44]
必须 mov eax,DWORD PTR[esp+32]
mov DWORD PTR[esp+44],eax #利用临时寄存器来间接复制
(这里DWORD是4字节,ptr是pointer指针的缩写,可以理解为是临时的类型转换,相当于C语言中的强制类型转换。默认是word 2字节)
四、call指令
用于实现子程序(过程、函数等)的调用。
- 如:_B:
- (后面接B函数的操作)
- 在A中调用B,即A函数里有 call _B
这个时候esp低地址会出现存储返回地址的空间。
如esp-4地址指向的空间存储返回地址,即存储A函数中call_B下一条语句的地址,以便ret时继续执行下一条语句。
这里要注意,A函数内部的变量,如果要传入B,如B(a,b),需要在A栈的栈顶存储传入的值,B中以ebp+8和ebp+12的形式去拿值(值传递的实现)。
五、push指令(改变esp位置)
压栈操作,将操作数压入内存的栈。
当A函数调用B函数时,B函数内部会有push语句,即
- _B:
- push ebp
- mov ebp,esp
- sub esp,16 #这地方的16不是唯一值,可随情况变化
1. push ebp:
ebp寄存器中存储的是A函数的栈基地址,push ebp就是往低地址开一个空间,将ebp的内容存到这个空间,同时esp指向这个空间。
如调用函数B,A函数栈空间esp - 4存储了返回地址,此时需要再在esp - 8地址指向的空间存储ebp寄存器的内容,即esp - 8的空间存储A函数的栈基地址,再esp = esp - 8。
2. mov ebp,esp
原先ebp = A的栈基地址,现在ebp = esp,即将ebp指向存储了A栈基地址的空间,这样的目的是将ebp作为新函数的栈基地址。
3. sub esp,16
即esp = esp-16,esp作为新函数栈顶,而新函数栈空间大小即为16字节。
六、pop指令(改变esp位置)
出栈工作,esp地址指向空间中的内容出栈,esp值加4
如 pop ebp,即弹出栈顶元素到ebp,esp=esp+4
七、ret指令
实现子程序(过程、函数等)的调用及返回。
- 如A调用了B,B函数执行完需要返回时,实际包含了
- mov esp,ebp ,即esp = ebp ,esp从B函数栈栈顶,更改为指向A函数栈栈顶地址 - 8的位置。
- pop ebp,即esp = esp+4,ebp = A函数栈基地址
- 存储返回地址也pop掉,即esp = esp + 4,这样esp又指向原来A栈的栈顶,ebp指向A栈栈基。下一条语句的这个地址可能是用一个寄存器存储了(我猜测),当esp,ebp都复原后,再将PC=下一条语句的地址。
八、add/sub指令:
左右操作数相加/减,且结果放到左边操作数里,左边操作数可以是寄存器,也可以是内存空间。
九、imul指令:
带符号整数乘法指令,有两种格式:①两个操作数,将两个操作数相乘, 将结果保存在第一个操作数中,第一个操作数必须为寄存器;②三个操作数,将第二个和第三个操作数相乘,将结果保存在第一个操作数中,第一个操作数必须为寄存器。
十、cmp指令:
cmp用于比较两个操作数的值
- 如:cmp dword ptr[var] ,10 即 var指向的内存空间里存储的值减去10,看结果的属性,从而设置条件码的值。
- 如:cmp R1,R2 ,即R1-R2,若R1 = 5,R2 = 10,结果即为 -5 ,如果R1和R2是无符号数,CF=1,ZF=0,SF=1,OF不发生变化;如果R1和R2是有符号数,CF不发生变化,ZF=0,SF=1,OF=0。
十一、shl/shr指令
shl为逻辑左移,shr为逻辑右移
- 如:shl eax,1 将eax逻辑左移1位,相当于十进制数乘2的操作。
十二、lea指令:
地址传送指令,将有效地址传送到指定的寄存器
- 如:mov DWORD PTR[esp+16],5 #把常量5放入栈顶指针偏移16个位置的地址指向的空间 lea eax,[esp+16] #把这个地址再放入eax临时寄存器
十三、jmp指令:
控制IP转移到<lable>所指示的地址。
- 如:jmp <lable>
- lable:...
- 也可以jmp 地址
十四、jcondition指令:
条件转移指令:依据CPU状态一系列条件状态转移,即若条件码满足,就跳转到lable标签的语句。
- jge<lable>(jump when greater than or equal to)
- 跳转条件:结果大于等于0,即SF = 0或ZF=1
- jle<lable>(jump when lesss than or equal to)
- 跳转条件:结果小于等于0,即SF = 1或ZF=1