汇编学习笔记(3)-80x86指令集
前言
(1)指令的一般格式
[标号:] 助记符 [操作数1 , [操作数2]] [; 注释]
一行一条指令
助记符就是指令的名称,每条指定必定有个助记符。 助记符前面的标号是给汇编编译器看的,由我们自己取名,一般取表示本段功能的相关的名字,对编译器而言表示的是指令的地址。
每个指令根据指令作用的不同会带有一个或者两个操作数,如果有两个操作数,则操作数中间用 逗号"," 隔开。 ;之后到本行结束为注释 是写个我们自己看的内容,用于描述指令的功能,方便理解程序功能。编译器会将注释内容直接舍弃。
注意: 每条指令都会有一些使用限制,有些限制可能是适用于所有指令的,我会尽量用相同的颜色标记出相同的限制。
(2)全局规则:
源操作数和目的操作数类型要保持一致,要么都是字,要么都是字节
除了串操作指令,源操作数和目的操作数不能同时为 内存单元(可以使用其他寄存器中转)
段寄存器之间数据不能互相传递
cs段寄存器只能用专门的指令来操作
(3)指令集分类
根据指令集的功能大致可分为六类:
(1) 数据传送
MOV; XCHG; LEA; LDS; LES; PUSH; POP;
(2) 算数运算
ADD; ADC; INC; SUB; SBB; DEC; NEG; CMP; MUL; IMUL; DIV; IDIV; CBW; CWD;
(3) 逻辑运算
NOT; AND; OR; XOR; TEST; SAL; SHL; SAR;SHR; ROL; ROR; RCL; RCR;
(4) 串操作
(5) 程序控制
JMP; JC; JNC; JP; JPE; JA; JNBE; JAE; JNB; JB; JNAE; JE; JZ; JNZ; JNEL; JO; JNO; JS; JNS; JG; JNLE; JGE; JNL; JL; JNGE; JLE; JNG; JCXZ; LOOP; LOOPE; LOOPZ; LOOPNE ;LOOPNZ; JCXZ;
(6) CPU控制(标志位控制)
LAHF; SAHF; PUSHF; POPF; CLC; STC; CMC; CLD; STD; CLI; STI;
数据传送指令
1. 数据传送 MOV
MOV DST, SRC
这条指令上面已经用过很多次了,作用就是将SRC的数据复制到DST,指令本身不会对SRC的数据做任何的修改。
限制:
源操作数可以是寄存器,累加器,内存单元,立即数 目的操作数可以是寄存器,累加器,内存单元
源和目的不能同时是段寄存器
代码段寄存器CS 不能作为目的
指令指针寄存器不能作为源,也不能作为目的寄存器
立即数不能直接传送到段寄存器,立即数永远不能作为目的操作数
2. 交换指令 XCHG
XCHG VALUE1, VALUE2
这条指令交换两个数值。
限制
两个操作数不能有段寄存器,也不能同时是内存单元
不能有立即数
3.地址传送指令
(1) LEA指令 (Load Effective Address)
LEA REG, OPRD
该指令的作用是将操作数OPRG的地址传送到REG寄存器中,和MOV有本质区别,MOV是将操作数OPRG的内容传送到寄存器,所以LEA的源操作数一定是内存,目的操作数一定是16位寄存器
限制:
OPRG 必须是内存地址
REG 必须是16位通用寄存器
(2) LDS指令 (Load pointer into DS)
LDS REG, OPRD
这个指令是LEA的 高级版本,传送的是32位地址,指令会将段地址存储到DS寄存器,而偏移部分则存储到REG寄存器中。
限制:
REG可以16位通用寄存器,或者指针寄存器(sp bp,IP不可以)和变址寄存器(SI DI),但是一般都是变址寄存器
OPRD 必须是32位地址
(3) LES指令 (Load pointer into ES)
LES REG, OPRD
和LDS是一模一样的,唯一的区别就是段地址存储到ES寄存器中
4.堆栈操作指令
堆栈是用来存储临时数据的一段内存,由SS和SP两个寄存器定义, SS 定义了堆栈的基础地址,SP定义了堆栈的当前位置,如下图所示
SS 在低地址,SP在高地址,有数据入栈之后SP就向上移动,当SP和SS移动到一个位置的时候即表示堆栈满了,所以SS定义堆栈地址,SP定义堆栈大小,因此堆栈的方向是向低地址方向生长的,先入栈的数据地址反而高。在这种机制下也存在一个问题,就是出栈的时候就没有一个限制了。应为SP到哪里才算是堆栈空了? 所以使用的时候要当心。
用途:
现场和返回地址的保护
寄存器内容的保护
传递参数
存储局部变量
入栈指令 PUSH
PUSH SRC
该指令将源操作数SRC压入栈中,他先将SP指针减2,然后将SRC存入SP所指位置。
SRC可以是通用寄存器也可以是段寄存器也可以是内存单元。数据存储入栈的时候16位数据高字节存储在高地址,低字节存储在低地址。
出栈指令 POP
POP DST
将栈顶数据传送到DST中,DST可以是通用寄存器,段寄存器(除CS)也可以是内存单元
算数运算指令
基本规则
加减运算对 有符号数和无符号数是一视同仁的,意思就是对数据的加减运算,他会同时认为是无符号运算而影响CF AF标志位,并同时认为是有符号运算而影响OF SF标志位。
只有通用寄存器和内存单元可以存放计算结果。两个操作数中不能同时是内存单元。
如果有两个操作数,类型必须一致。
1. 加法指令
(1)普通加法指令 ADD
ADD OPD1, OPD2
这条指令完成功能是 将OPD1 和 OPD2 的内容相加存储到OPD1中,C代码表示的话就是 OPD1 = OPD1 + OPD2
(2)带进位加指令 ADC
ADC OPD1, OPD2
这条指令完成功能是 将OPD1 + OPD2 + 进位标志CF 的结果相加存储到OPD1中,C代码表示的话就是 OPD1 = OPD1 + OPD2 +CF
(3)加1指令 INC
INC OPD
这条指令的功能等效于 ADD OPD , 1,这条指令不影响
简单解释下加法命令对标志位的影响
比如指令组
MOV AX 7896H ; AH = 78H AL= 96H,对各标志位无影响
ADD AL, AH ; AL = AL + AH, AL(10EH) = 96H + 78H, 而进制就是 0001 0000 1110 = 1001 0110 + 0111 1000
; 这里注意 AL=0E, 应为AL只能保存8位所以最高位的1 被舍弃了,AL= 0000 1110
; 显然结果也不为0 所以ZF = 0
; 结果中1的位数是奇数 PF = 0
; 首先按照无符号数相加 这里很明显的 最高位进位了所以 CF =1
; 然后看AF寄存器 0110 + 1000 = 1110 也是很明显的没有发生进位所以 AF =0
; 然后看有符号数计算怎么算按照有符号数的话 96H 由于最高位是1 所以就是负数了 表示的值就是 -6A,注意CPU认为数据都是补码形式保存。
; 实际的计算是 AL(0EH) = -6AH + 78H,这就是设计的巧妙了,无论是按照有符号还是无符号,二进制结果都是一样的。
; 首先看SF 显然数据是正数 所以 SF =0
; 再看OF 数据也没超过表示范围 OF =0
2.减法指令
(1)普通减法指令 SUB
SUB OPD1, OPD2
这套指令完成的功能就是 将 OPD1 -OPD2的结果存储到 OPD1中, C代码表示就是OPD1=OPD1-OPD2
(2)带借位减法指令 SBB
SBB OPD1, OPD2
这套指令的功能就是 将 OPD1 -OPD2 - CF的内容保存到OPD1中,C代码表示就是OPD1 = OPD1 -OPD2 -1
(3)减1指令 DEC
DEC OPD
等效于 SUB OPD1 , 1
(4)取补指令 NEG
NEG OPD
这条指令的作用 将 0-OPD 的结果存储到 OPD中,C代码表示就是 OPD = 0 - OPD
(5)比较指令 CMP
CMP OPRD1, OPRD2
这条指令的作用是OPRD1-OPRD2 结果不保存,但是影响标志位
简单解释下减法法命令对标志位的影响
MOV BX, 9048H ; BL=48H BH = 90H
SUB BH,BL ; BL(48H) = 90H - 48H 1001 0000 - 0100 1000 = 0100 1000
; 显然结果也不为0 所以ZF = 0
; 结果中1的位数偶数 PF = 1
; 按照无符号数 整体没发生借位所以CF =0
; 0000 - 0100 借位了所以 AF =1
; 按照有符号数 90H 实际表示的数是 - 70H
; 所以实际的计算是 -70H - 48H = -B8H = -184( 1 0100 1000)
; 很明显溢出了 所以OF = 1
; 由于溢出了符号位被覆盖了所以本来是负数的 现在变成正数了所以SF = 0
3.乘法指令
乘法是这样的被乘数总是隐藏在AL 或者AX寄存器中,根据成数的长度选择不同寄存器
(1)无符号乘法 MUL
MUL OPD1
所以如果OPD 是字节那么 就是 AX = AL * OPD,如果OPD是字那么 就是 DX AX = AX * OPD,DX存储数据的高16位,AX存储数据的低16位
如果结果的高半部分不为0 其实意思 字节 * 字节 = 字,或者 字* 字 = 双字了 CF =1 ,OF =1
(2)有符号乘法 IMUL
IMUL OPDR
用法和MUL完全一样
由于有符号乘法的符号位始终在高字节中,所以 字节 * 字节 永远等于 字,或者 字* 字 永远等于 双字了 ,那么标记位的变化是根据高位中是否置包含符号位而没有其他有效数据来判断,仅仅是符号位则 cf =0 of =0
含有有效数据则CF =1 OF =1
举例:
字数据相乘结果是 1000 0000 1010 1010 那么高位 1000 0000 仅仅是符号位有数据那么 CF =0 OF =0, 如果结果是1000 0001 1010 1010那么高位1000 0001 中除了符号位,还有其他有效数据所以
CF =1 ,OF =1
4.除法指令
和乘法指令一样,被除数总是存储在AX 或者 DX和 AX寄存器中
(1)无符号除法指令 DIV
DIV OPDR
如果OPDR 是字节数据,那么商存储在 AL寄存器 余数存储在 AH寄存器,如果OPDR 是字数据那么商存储在AX寄存器 余数存储在DX寄存器
不影响标志位,但是如果除数是0 ,或者AL寄存器,或者AX寄存器无法存储下 商的时候会认为是溢出,导致触发0号中断
(2)有符号触发指令 IDIV
IDIV OPDR
存储结果的方式和无符号除法一样
也不影响标志位,当除数是0,或者寄存器无法保存下结果的时候触发0号中断
5.符号位扩展指令
(1)字节转换为字指令 CBW
CBW
将AL寄存器的数据的符号位放到到AH,功能就是AX =AL
效果就是假设 AL= 1001 0001 AH = 0000 0000,CBW后 AL= 0001 0001 AH = 1000 0000
(2)字转双字 CWD
CWD
将AX的符号位扩展到DX中,作用同CBW一样
逻辑运算和移位指令
基本规则
如果指令有两个操作数,则最多只能有一个是存储器操作数
只有通用寄存器和存储器可以用于存放操作结果
1. 逻辑运算指令
(1)否操作指令 NOT
NOT OPRD
这条指令的作用就是将操作数OPRD取反,然后存储到OPRD中去
(2)与操作指令 AND
AND OPRD1, OPRD2
将两个操作数进行按位的与操作,然后结果存储到OPRD1中,注意这个操作会影响PF ZF SF 标志位,所以有个技巧就是自己与自己进行于运算可以清除 进位标志CF
(3)或操作指令 OR
OR OPRD1, OPRD2
将两个操作数进行或操作,结果存储到OPRD1中,这个操作也是会影响会影响PF ZF SF 标志位,并且也可是通过自己或自己的方式清除 进位标志CF
(4)异或操作 XOR
XOR OPRD1, OPRD2
将两个操作数进行异或操作,结果保存到OPRD1中,这个操作也是会影响会影响PF ZF SF 标志位,如果自己和自己异或将会得到0 并且也会清除 进位标志 CF
(5)测试指令 TEST
TEST OPRD1, OPRD2
这条指令也是进行逻辑与操作的,和AND指令的不同在于他不将结果存储到OPRD1中,也就是说这个指令只影响标志位,不影响操作数。
举例:
比如想测试AL中的 第6和第2位是否为1 则可以使用指令
test al, 01000100B
如果都为一则ZF标志位会置位,否则ZF=0
2.一般移位指令
移位指令分为 左移 和 右移指令,在这基础上还分为逻辑移动和算数移动两种,所以一共有四条指令
SAL OPRD, m ; 算数左移
SHL OPRD, m ;逻辑左移
SAR OPRD, m ;算数右移
SHR OPRD, m ;逻辑右移
其中 m 移动的位数, 要么是1 要么是AL寄存器
移位操作会影响 PF, SF, ZF和 OF标志位
对于左移操作,其实算数左移和逻辑左移是一模一样的, 将操作数OPRD 向左移动m位,每移动一位右边会补0 ,同时移动出去的最高位会进入CF 标志位。
对于右移操作,算数右移会保持左边的符号位不动其他位右移,而逻辑右移则不考虑符号位不符号位的通通右移, 空位用0 补足,最高位进入CF标志位 。所以算数右移1位相当于 有符号数/无符号数 除以2 而逻辑右移通通认为是无符号数 除以2
3. 循环移位指令
循环移位和一般移位的区别就是,一般移位就是移动多少位之后移出去的直接丢弃,后面补0,而循环移位会将移动出去的位放到另一端,将向一个圈不停的转,举个例子
比如 操作数
一般左移的结果就是 01010101 => 10101010 => 01010100 => 10101000 => 01010000 => 10100000 => 01000000
循环左移的结果就是 01100101 => 11001010 => 10010101 => 00101011 => 01010110 => 10101100 => 01011001
循环移位分为带进位的循环移位和不带进位的循环移位
ROL OPRD, m ; 循环左移,他每移动一位,操作数左移,最高位进入最低位,同时最高位进入CF
ROR OPRD, m ; 循环右移,他每移动一位,操作数右移,最低位进入最高位,同时最低位进入CF
RCL OPRD, m ; 带进位的循环左移,他每移动一位,操作数左移,最高位进入CF标志,原来的CF进入最低位
RCR OPRD, m ; 带进位的循环右移,他每移动一位,操作数左移,最低位进入CF标志,原来的CF进入最高位
带进位标志的循环移动就相当于 在原来的操作数前面多加了一位进行移位操作,原来是 8位操作数那么CF就添加到最前面变成9位操作数进行移位操作。所以 不带进位的循环移动每移动8位相当于还原,带进位的循环移动每移动9位则相当于还原。
带进位的循环的一个用处就是可以将其他操作数的最低位或者最高位送入CF,然后将CF循环入另一个操作数来完成数据的拼装。
程序控制
基本规则
8086/8080 CPU提供了大量的用于流程控制的指令,按照功能可以分为如下四类
无条件转移指令和有条件转移指令
循环指令
过程调用和过程返回指令
软中断指令和中断放回指令
同时根据转移的时候是否设置CS寄存器的值又分为 段内转移(近转移) 和 段间转移(远转移)
条件转移和 循环指令 只能是 段内转移
软中断指令和中断放回指令 只能是 段间转移
无条件转移和过程调用以及过程返回指令 则是 段内 段间都可以
对于无条件转移和过程调用指令而言,根据确定目的地址的方式不同还分为直接转移和间接转移
无条件,条件,循环指令是不影响标志位的
1. 无条件转移指令
(1)无条件段内直接转移指令 JMP
JMP 标号
这条指令是的控制无条件地转移到标号地址处。 例如
NEXT: MOV AX,CX
......
JMP NEXT
对于这条指令对应的机器指令是
指令操作码 | 地址差
地址差会在编译的时候由编译器计算出,计算的是目的地和JMP指令之后下一条指令的差值,所以这条指令的实际操作就是将差值加到IP寄存器上。
如果地址差需要一个字节表示那么称之为 短转移, 如果需要一个字表示那么称之为近转移。使用一个字还是一个字节表示地址差会在编译的时候计算。编译器是顺序编译的,如果编译到这条JMP指令的时候还没出现JMP的那个标号
那么编译器就无法计算出地址差,这个时候编译器会默认使用字作为操作数,如果你能确定实际上字节就够了,那么你可以是使用SHORT指令显式的告诉编译器使用字节地址,具体指令格式如下
JMP SHORT NEXT
这个使用地址差的转移方式,称之为相对转移,相对转移是有利于代码段的浮动加载,意思是无论代码加载在内存哪个位置都可以很好的运行。
(2)无条件段内间接转移 JMP
JMP OPRD
这条指令控制无条件的转移到OPDR指定的地指出, OPRD可以是通用寄存器也可以是字存储单元,如下
JMP CX
JMP WORD PTR [1234H]
(3)无条件段间直接转移 JMP
JMP FAR PTR 标号
FAR PTR 就是只是编译器这是一个断间转移。
JMP FAR PTR NEXT
生成的机器指令将会是
操作码 偏移地址 段地址
这种指令中直接包含转移目的地的转移方式叫做绝对转移
(4)无条件断间间接转移 JMP
JMP OPRD
OPRD 必须是双字的所以指令一般如下
JMP DWORD PTR [1234H]
那么内存1234H处低字节存储的就是IP 高字节存储的就是CS
顺带一提PTR 类似于 指针的意思, XXX ptr 就指定接下来的数据 是 什么类型的指针 作用大致相当于 C 中的 强制类型转换 (XXX*) 比如 DWORD PTR A 相当于 (DWORD*) A
2. 条件转移指令
条件转移都是段内转移,同时先使用相对转移,即通过在IP地址上加个地址差的方式实现。
条件转移是不影响标志位的。
下图是网上找到的关于条件转移指令的说明
有符号数 大于使用G 等于使用E 小于使用L
无符号数 大于使用A 等于使用E 小于使用B
N 表示不
条件转移指令需要配合CMP指令或者TEST等其他指令来配合使用
先使用CMP等指令进行操作,这会影响到标志位,然后使用以上的指令就会根据 CMP等指令影响的标志位做出反映。
循环指令
使用条件转移指令可以实现循环,但是为了方便操作8086CPU还是设计了四个循环指令
(1)计数循环指令 LOOP
LOOP 标号
这条指令使寄存器CX的值减1,如果结果不等于0,则转移到标号,否则顺序执行LOOP指令后的指令。
所以LOOP 相当于
DEC CX
JNZ 标号
(2)等于/全零循环指令 LOOPE/LOOPZ
LOOPE 标号
或者
LOOPZ 标号
这条指令使寄存器CX的值减1,如果结果不等于0,并且ZF=1, 则转移到标号,否则顺序执行LOOP指令后的指令。这条指令的CX减一操作不影响标志位。
(3)不等于/非全零循环指令 LOOPNE/LOOPNZ
LOOPNE 标号
或者
LOOPNZ 标号
这条指令使寄存器CX的值减1,如果结果不等于0,并且ZF=0, 则转移到标号,否则顺序执行LOOP指令后的指令。这条指令的CX减一操作不影响标志位。
(4)跳转指令 JCXZ
JCXZ 标号
该指令执行当CX=0的时候转跳到标号处,否则顺序执行
标志操作指令
此组命令是专门针对标记寄存器和标志位进行的。
1. 标志传送指令
(1) LAHF指令(Load AH with Flags)
LAHF
此指令是将标志位的低8位(SF ZF AF PF CF)保存到AH 寄存器中
(2) SAHF指令(Store AH with Flags)
SAFH
此指令和LAHF是相对的,SAFH指令是将AH寄存器中的内容传送到标记寄存器的低八位
(3) PUSHF 指令
PUSHF
此指令是将标志寄存器的内容全部压栈。
(4) POPF 指令
POPF
此命令和PUSHF是一对的,将堆栈中的数据传送到标记寄存器。
2. 标志位操作指令
此组指令是专门用来处理指定标志位的。
(1) 清进位标志位 CLC (Clear Carry flag)
CLC
使进位标志设为0
(2) 置进位标志位 STC (SeT Carry flag)
STC
使进位标志设为1
(3) 进位标志取反 CMC (CoMplement Carry flag)
CMC
如果CF=1 则 CF 置为0 ,如果CF=0 则 CF置为1
(4) 清方向标志 CLD (CLear Direction flag)
CLD
使方向标志DF 置为0,使串操作地址按照增的方式变化
(5) 置方向标志 STD (Set Direction flag)
STD
使方向标志DF 置为0, 使串操作地址按照减的方式变化
(6) 清中断允许标志 CLI (CLear Interrupt enable flag)
CLI
该指令使中断允许标志IF 设为0 ,于是CPU就不响应外部装置的可屏蔽中断,但是对不可屏蔽中断和内部中断都没有音响。
(7) 置中断允许标志 STI (SeT Interrupt enable flag)
STI
该指令使中断允许标志IF 设为1 ,这样CPU就可以相应可屏蔽中断