X86指令系统
本文最后更新于:January 1, 2023 pm
X86指令系统
X86 32位寻址方式
寻址方式主要在三方面上进行运用:
- 操作数的寻址
- 转移地址的寻址
- IO端口的寻址
以下分别对X86指令的三方面寻址方式进行说明
操作数寻址
由冯诺依曼架构可以得到这样的结论: 操作数的来源只有三处:
- 指令直接给出
- 存放在寄存器中
- 存放在内存单元中
其中最简单的方式就是由指令直接给出,最复杂的则为存放在内存单元中,以下将分别说明不同的寻址方式!
立即寻址
立即寻址即是由指令直接给出源操作数。立即寻址只能用于指令的源操作数!
例:
1 |
|
寄存器(直接)寻址
即操作数直接存放在寄存器中,处理器会直接从寄存器中拿取操作数。
例:
1 |
|
寄存器间接寻址
操作数存放在内存之中,其偏移地址存放在寄存器之中,处理器先从寄存器中拿到偏移地址,再得到在内存之中的物理地址,再去该物理地址拿取操作数。
操作数的段基址取决于间址寄存器,若为EBP、ESP则默认在堆栈段,其他寄存器则为数据段。当然如果是flat模式则没有区别。
例:
1 |
|
直接寻址
操作数存放在内存之中,偏移地址由变量表示。
例:
1 |
|
特别的,在X86保护模式下用户不能使用常数作为存储单元地址,若使用常数则视为立即寻址。
例:
1 |
|
寄存器相对寻址
操作数存放在内存中,操作数的偏移地址为间址寄存器的内容加上一个位移量,位移量可以是一个常数,也可以是变量
例:
1 |
|
Hint: mov ax,[ebx]5
是错误的用法,当常数或变量在后面时,必须加加号,在前面可加可不加
基址-变址寻址
操作数存放在内存中,操作数的偏移地址为
例:
1 |
|
隐含寻址
指令中隐含了一个或两个操作数的地址,即操作数在默认的地址中。
例:
1 |
|
转移地址寻址
转移地址一般分为两种寻址方式:
- 直接寻址: 当前指令与目的地指令距离在-128~127字节内时,均采用立即寻址的方式,即将位移量(1B)放在指令中,形成2字节的jmp指令
- 间接寻址: 当指令与目的地指令距离超过了短转移(-128~127字节)的限制时,位移量则为4B,形成5字节的jmp指令
IO端口寻址
IO端口寻址也分为两种寻址方式:
- 直接寻址: 端口地址为8位时(可寻址256个端口),指令中的PORT直接由一个8位无符号常数提供,是立即寻址变体
例:
1 |
|
- 间接寻址: 当端口地址超过255时,地址码为16位(可寻址64K个端口),指令中的端口地址必须由DX指定(不能使用EDX指定),其中端口地址为0~255时也可使用间接寻址
例:
1 |
|
X86指令
学习汇编指令主要从五个角度出发:
- 指令格式
- 指令功能
- 对操作数的要求
- 指令对操作数的影响
- 指令对标志位的影响
在下面六大类的每条指令中均会说明该五个角度,并且需要记忆!
数据传送指令
数据传送指令一般对标志位无影响
mov
格式:
1 |
|
功能:
将src的操作数送到dest内
操作数要求:
- 两操作数长度必须相同
- 存储单元之间不能直接传送(可以用串操作指令实现直接传送)
- 段寄存器CS只能作源操作数,段寄存器之间不能直接传送
- 在源操作数是立即数时,目标操作数不能是段寄存器
- 标志寄存器(EFLAGS或FLAGS)一般不作为操作数在指令中出现
对操作数影响:
无影响
对标志位影响:
无影响
movzx/movsx
格式:
1 |
|
功能:
将位数短的操作数传到较长的寄存器,并进行0扩展(movzx)或符号扩展(movsx)
操作数要求:
- 源操作数位数要小于目的操作数长度
- 目的操作数必须为寄存器
对操作数影响:
源操作数无影响,目的操作数变为源操作数的0扩展或符号扩展
对标志位影响:
无影响
push/pop/pusha/pushd/popa/popd/pushfd/popfd
格式:
1 |
|
功能:
push/pop: 将16位数据入栈或出战
pusha/popa: 将8个16位通用寄存器按AX、CX、DX、BX、SP、BP、SI与DI的顺序入栈和从栈顶弹出8个字数据分别送入DI、SI、BP、SP、BX、DX、CX与AX
pushd/popd: 将8个32位通用寄存器按EAX、ECX、EDX、EBX、ESP、EBP、ESI与EDI的顺序入栈和从栈顶弹出8个双字数据分别送入EDI、ESI、EBP、ESP、EBX、EDX、ECX与EAX
pushfd/popfd: 32位标志寄存器EFLAGS进栈/出栈指令
操作数要求:
若为存储器操作数,需要声明为字存储单元(PTR)
对操作数影响:
对标志位影响:
对标志位无影响,但会影响SP内的值
push: SP变为SP-2
pop: SP变为SP+2
xchg
格式:
1 |
|
功能:
交换寄存器和内存单元的值
操作数要求:
- 必须有一个操作数是寄存器操作数
- 不允许使用段寄存器
对操作数影响:
源操作数变为目的操作数,目的操作数变为源操作数
对标志位影响:
无影响
in/out
格式:
1 |
|
功能:
对IO端口进行输入和输出
操作数要求:
port在0~255时可以是立即数,也可以是dx,port大于255时必须是dx
acc可以是al、ax或eax,但不能是其他寄存器
对操作数影响:
无影响
对标志位影响:
无影响
lea
格式:
1 |
|
功能:
将一个存储单元的32位(保护模式)偏移地址取出送目标寄存器
操作数要求:
- 源操作数必须为存储单元操作数
- 目的操作数必须为16位或32位寄存器操作数
对操作数影响:
若目的操作数为32位寄存器操作数,目的操作数变为源操作数的偏移地址,若目的操作数为16位寄存器操作数,目的操作数变为源操作数的偏移地址的低16位
对标志位影响:
无影响
lahf/sahf
格式:
1 |
|
功能:
lahf: 将FLAGS的低8位装入AH
sahf: 将AH装入FLAGS的低8位
操作数要求:
无要求,因为是隐含地址
对操作数影响:
lahf: AH内容变为FLAGS的低8位
sahf: FLAGS低8位变为AH内容
对标志位影响:
lahf: 无影响
sahf: 标志位低8位涉及到的标志位变为AH内容对应位
算术运算指令
算术运算指令的执行大多对状态标志位会产生影响,对标志位的影响是算术运算指令的应用思路之一。
add/adc/inc
格式:
1 |
|
功能:
add: 将oprd1与oprd2相加送到oprd1中
adc: 将oprd1与oprd2相加再加上CF标志位送到oprd1中。adc指令常用于多字节数相加,使用前要先将CF清零。
inc: oprd自增1
操作数要求:
add/adc: 与mov指令基本相同
inc:
- 不能是立即数
- 不能是段寄存器
对操作数影响:
add/adc: oprd1变为oprd1+oprd2+(CF),oprd2无影响
inc: oprd自增1
对标志位影响:
add/adc: 对6个状态标志位均产生影响(AF、PF、CF、OF、ZF、SF)
inc: 不影响CF,其他5个状态标志位仍然受到影响
adc实现多字节数相加示例:
1 |
|
sub/sbb/dec
格式:
1 |
|
功能:
sub: 将oprd1与oprd2相减送到oprd1中
sbb: 将oprd1与oprd2相减再减去CF标志位送到oprd1中。
inc: oprd自减1
操作数要求:
sub/sbb: 与mov指令基本相同
dec:
- 不能是立即数
- 不能是段寄存器
对操作数影响:
sub/sbb: oprd1变为oprd1-oprd2-(CF),oprd2无影响
inc: oprd自增1
对标志位影响:
sub/sbb: 对6个状态标志位均产生影响(AF、PF、CF、OF、ZF、SF)
dec: 不影响CF,其他5个状态标志位仍然受到影响
neg
格式:
1 |
|
功能:
用0减去操作数,相当于对该操作数求补,但不是补码。
操作数要求:
必须是寄存器操作数(8位、16位、32位均可)或存储器操作数
对操作数影响:
oprd变为0 - oprd
对标志位影响:
6个标志位(AF、PF、CF、OF、ZF、SF)都会被影响
对CF的影响:
- oprd为0时,CF=0
- oprd不为0时,CF=1
对OF的影响:
当字节操作数为-128(80H),或字操作数为-32768(8000H)或双字操作数为80000000H时,结果将无变化,但溢出标志OF被置1
cmp
格式:
1 |
|
功能:
用于比较两个数的大小,可作为条件转移指令转移的条件
操作数要求:
与mov指令基本相同
对操作数影响:
无影响
对标志位影响:
对6个状态标志位均产生影响(AF、PF、CF、OF、ZF、SF)
cmp指令对标志位的影响主要通过CF、ZF、OF、SF四个标志位来判断两个数的大小
- 无符号数比较:
- oprd1 > oprd2: CF = 0 & ZF = 0
- oprd1 = oprd2: ZF = 1
- oprd1 < oprd2: CF = 1 & ZF = 0
- 带符号数比较:
- oprd1 > oprd2: OF = SF & ZF = 0(为正数时,OF与SF相等表示结果仍为正数,说明oprd1大;为负数时,SF为1时结果为负数,OF为1表示结果溢出,oprd1大,SF为0时结果为正数,OF为0表示不溢出,说明oprd2的绝对值大,负数小)
- oprd1 = oprd2: ZF = 1
- oprd1 < oprd2: OF SF & ZF = 0(与oprd1 > oprd2结论相反,除了ZF = 0)
也可通过直接的跳转指令实现根据两数大小的跳转:
- 无符号数比较跳转:
- ja/jae: ja为当oprd1>oprd2时跳转,jae为当oprd1oprd2时跳转
- jb/jbe: jb为当oprd2>oprd1时跳转,jbe为当oprd2oprd1时跳转
- 带符号数比较跳转:
- jg/jge: jg为当oprd1>oprd2时跳转,jge为当oprd1oprd2时跳转
- jl/jle: jl为当oprd2>oprd1时跳转,jle为当oprd2oprd1时跳转
cmp实现20个无符号数寻找最大值示例:
1 |
|
mul/imul
格式:
1 |
|
功能:
mul/imul: 将隐含寻址的值乘以oprd倍送到扩展寄存器中(结果为64位时,由edx:eax构成64位寄存器)
imul: 将srcdest的结果送到dest中,若结果超过目的寄存器位数则截断高位
操作数要求:
mul/imul: 隐含寻址时,操作数只能是寄存器操作数或存储器操作数
imul: 可以是寄存器操作数或存储器操作数或比目的操作数位数少的立即数
对操作数影响:
mul/imul: 对oprd无影响,隐含寻址操作数变为乘积的低位
imul: dest操作数变为src*dest的结果的低位
imul:
对标志位影响:
mul: 只对CF和OF产生影响,若高位寄存器(即AH、DX、EDX)为全0则CF=OF=0,否则CF=OF=1
imul: 只对CF和OF产生影响,隐含寻址时,若高位寄存器(即AH、DX、EDX)全为低位的符号位扩展则CF=OF=0,否则CF=OF=1
imul: 只对CF和OF产生影响,如果存放结果的目的寄存器中丢弃了乘积高位的有效数值则CF=OF=1,否则CF=OF=0
div/idiv
1 |
|
功能:
将隐含寻址的目的操作数除以源操作数的倍数,结果放入隐含寻址的操作数中
操作数要求:
指令要求被除数是除数的双倍字长,因此除法指令常与扩展指令CBW、CWD、CDQ配合使用。
对操作数影响:
源操作数无影响,目的操作数按上图所示变化
对标志位影响:
无影响
cbw/cwd/cdq
1 |
|
功能:
扩展源操作数的符号位到指定位置
操作数要求:
必须在隐含寻址的寄存器内
对操作数影响:
源操作数无影响,目的操作数变为源操作数的符号位扩展
对标志位影响:
无影响
BCD码校正指令(daa/aaa/das/aas/aam/aad)
不要求掌握,了解即可。
在X86系统中,BCD码可以有两种表示方式:
- 压缩型: 一个字节表示两个BCD码,即两位十进制数。每4位表示1个十进制数。
- 非压缩型: 一个字节的低四位表示一个BCD码,而高四位对所表示的十进制数没有影响,常为0000或0011。
BCD码处理指令: 先用二进制数的加、减、乘、除运算指令对BCD码运算,再用BCD码校正指令对结果校正。
位操作指令
位操作大多数时候也是一种算术运算,如乘法在一定程度上可以视为左移,除法在一定程度上可以用右移操作今天代替。同时位操作往往也会导致标志位的变化,同时某些特殊的位操作目的是对特定的标志位操作。
and
格式:
1 |
|
功能:
实现两操作数按位相与的运算,使目标操作数的某些位不变,某些位清零
操作数要求:
与mov指令相同
对操作数影响:
源操作数无影响,目的操作数变为按位与的结果
对标志位影响:
CF=OF=0,其余状态位根据结果产生
判断某些位是否为0:
1 |
|
将CF和OF清零:
1 |
|
or
格式:
1 |
|
功能:
实现两操作数按位相或的运算,使目标操作数的某些位不变,某些位置1
操作数要求:
与mov指令相同
对操作数影响:
源操作数无影响,目的操作数变为按位或的结果
对标志位影响:
CF=OF=0,其余状态位根据结果产生
将CF和OF清零:
1 |
|
not
格式:
1 |
|
功能:
将操作数按位取反
操作数要求:
- 不能为立即数
- 操作数为存储器操作数时需要说明数据类型(db、dw、dd)
对操作数影响:
操作数变为按位取反的结果
对标志位影响:
无影响
xor
格式:
1 |
|
功能:
两操作数按位相异或,结果送目标地址,xor有个很重要的属性: 自身相异或结果为0,经常用以判断数据是否为预期数据
操作数要求:
与mov指令相同
对操作数影响:
源操作数无影响,目的操作数变为两操作数按位相异或结果
对标志位影响:
CF=OF=0,其余状态位根据结果产生
属性应用:
与test例相同,需要判断bit1、bit3、bit5是否为均1,用xor指令十分优雅但会改变数据
1 |
|
还有一种在不改变数据的情况下能一次性比较多位的方式: cmp指令
1 |
|
功能:
判断两操作数按位与结果对标志位的影响
操作数要求:
与mov指令相同
对操作数影响:
无影响
对标志位影响:
CF=OF=0,其余状态位根据结果产生
可跟and指令一样判断某些位数据,并且不影响测试数据,因此可用来判断多位且不影响数据
1 |
|
sal/shl
格式:
1 |
|
功能:
sal与shl在机器指令执行时为同一条指令,将oprd1左移oprd2位,区别在于sal将操作数视为无符号数,shl将操作数视为带符号数
操作数要求:
oprd2必须是8位立即数或CL寄存器内容
对操作数影响:
oprd1变为左移后的结果,oprd2无影响
对标志位影响:
影响标志位OF、SF、ZF、PF、CF
左移在二进制操作中可视为特殊乘法: 左移n位
sar/shr
格式:
1 |
|
功能:
sar与shr并不相同,sar高位补零,shr高位补符号位,将oprd1右移oprd2位,区别在于sar将操作数视为无符号数,shr将操作数视为带符号数
操作数要求:
oprd2必须是8位立即数或CL寄存器内容
对操作数影响:
oprd1变为右移后的结果,oprd2无影响
对标志位影响:
影响标志位OF、SF、ZF、PF、CF
右移在二进制操作中可视为特殊除法: 右移n位
rol/rcl
格式:
1 |
|
功能:
循环左移oprd2位,并将结果放入oprd1中
操作数要求:
与sal/shl相同
对操作数影响:
oprd1变为循环移位后的结果,oprd2无影响
对标志位影响:
影响标志位CF(rol无影响)、OF(若移位位数为1次,且移位前后目的操作数的最高位发生变化,那么OF置1,否则OF清零。若移位位数大于1,那么OF不确定)。其他标志位无影响
ror/rcr
格式:
1 |
|
功能:
循环右移oprd2位,并将结果放入oprd1中
操作数要求:
与sal/shl相同
对操作数影响:
oprd1变为循环移位后的结果,oprd2无影响
对标志位影响:
影响标志位CF(rol无影响)、OF(若移位位数为1次,且移位前后目的操作数的最高位发生变化,那么OF置1,否则OF清零。若移位位数大于1,那么OF不确定)。其他标志位无影响
通过带CF的循环移位可以实现多字节单元数据联合移位(如adc大数加法一般)
1 |
|
shld/shrd
格式:
1 |
|
功能:
双精度移位操作,移位时以一定数据进行填充空位而非固定0或1
操作数要求:
与sal/shl相同
对操作数影响:
src无影响,dest变为移位后结果
对标志位影响:
会影响SF、ZF、AF、PF、CF
串操作指令
串操作是针对数据块或字符串的操作,可实现存储器到存储器的数据传送,默认源串地址由[ESI]提供,目的串地址由[EDI]提供。
地址修改方向由DF标志位决定,DF=0时为增地址方向,DF=1时为减地址方向。
重复前缀
串操作指令前面可加上自动重复前缀,实现自动重复执行串操作,重复执行次数必须由ECX指定,每次执行ECX自减1。
共有5条重复前缀:
- rep: 无其他条件重复,即ECX 0时重复
- repe: 相等重复,即ZF = 1 & ECX 0时重复
- repz: 为零重复,即ZF = 1 & ECX 0时重复
- repne: 不相等重复,即ZF = 0 & ECX 0时重复
- repnz: 不为零重复,即ZF = 0 & ECX 0时重复
movs/movsb/movsw/movsd
格式:
1 |
|
功能:
实现存储器单元的直接传送
操作数要求:
源操作数必须是esi寄存器间接寻址单元,目的操作数必须是edi寄存器间接寻址单元,movs必须指明操作数结构
对操作数影响:
目的操作数会变为源操作数,确定串操作方向后添加重复前缀会自动移动地址
对标志位影响:
cmps/cmpsb/cmpsw/cmpsd
格式:
1 |
|
功能:
实现存储器单元的直接比较并影响标志位
操作数要求:
源操作数必须是esi寄存器间接寻址单元,目的操作数必须是edi寄存器间接寻址单元,cmps必须指明操作数结构
对操作数影响:
无影响,确定串操作方向后添加重复前缀会自动移动地址
对标志位影响:
与cmp相同
cmps指令常与条件重复前缀共同使用
1 |
|
scas/scasb/scasw/scasd
格式:
1 |
|
功能:
从edi串数据中扫描AL、AX、EAX寄存器内容数据
操作数要求:
必须是edi存储器操作数,scas必须指明操作数结构
对操作数影响:
无影响,确定串操作方向后添加重复前缀会自动移动地址
对标志位影响:
与sub相同
scas扫描找出对应字符所在串位置
1 |
|
lods/lodsb/lodsw/lodsd
格式:
1 |
|
功能:
将存储器操作数装入累加器
操作数要求:
必须是esi寄存器间接寻址存储器操作数,lods必须指明操作数结构
对操作数影响:
累加器内容变为[esi]
对标志位影响:
无影响
lods一般不需要重复前缀,因为累加器数据会被覆盖
stos/stosb/stosw/stosd
格式:
1 |
|
功能:
将累加器内容送入存储器操作数
操作数要求:
必须是edi寄存器间接寻址存储器操作数,stos必须指明操作数结构
对操作数影响:
存储器操作数变为累加器内容,确定串操作方向后添加重复前缀会自动移动地址
对标志位影响:
无影响
stos常用于将内存某个区域置同样的值
1 |
|
程序控制指令
程序控制指令一般没有操作数或只有单个操作数,且一般对标志位没有影响。其主要通过标志位控制程序执行的方向。
jmp
格式:
1 |
|
功能:
跳转到距离jmp指令下一条指令oprd字节的指令。如转移地址寻址中说的,有两个寻址方式,当采用直接寻址的方式时,称为短距离转移,jmp指令仅2字节长,当采用间接寻址方式时可按转移地址是否仍在本代码段分为近转移和远转移,jmp指令长为5字节
操作数要求:
操作数可以为标号,由汇编器进行翻译为8位的位移量或32位的位移量,也可为寄存器间接寻址的存储器单元
对操作数影响:
无影响
对标志位影响:
无影响
jc/jnc/jo/jno/jz/jnz/jp/jnp/jcxz/jcxnz
格式:
1 |
|
功能:
根据一个标志位或CX、ECX决定是否跳转
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
ja/jae/jb/jbe/jg/jge/jl/jle
格式:
1 |
|
功能:
见cmp
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
loop/loopz/loopnz
格式:
1 |
|
功能:
根据ECX内容和ZF决定是否跳转
操作数要求:
转移范围为当前EIP内容的-128~+127内
对操作数影响:
无要求
对标志位影响:
无影响
call
格式:
1 |
|
功能:
调用子过程,等价于以下代码:
1 |
|
操作数要求:
subprocess必须是子过程的开始地址,汇编器会代替程序员进行这件事
对操作数影响:
无影响
对标志位影响:
无影响
ret
格式:
1 |
|
功能:
在子程序中从堆栈中弹出断点,以返回原程序,其功能等价于以下代码:
1 |
|
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
int/into/iret
格式:
1 |
|
功能:
int: 给予程序发出软中断请求的能力
into: 检测是否溢出,如果溢出(OF=1)即等价于int 4
发出一个类型号为4的中断请求,如果未溢出则不做任何操作
iret: 中断服务程序结束的固定流程指令(恢复现场、开中断)
操作数要求:
int: 操作数为立即数,且范围必须在0~255(X86下中断向量表只有256个表项)
对操作数影响:
无影响
对标志位影响:
无影响
处理器控制指令
处理器一般通过标志位来控制指令的执行方式,因此控制指令一般可以直接更改标志位而无需运算来实现(如通过and、or实现CF和OF清零),或直接通过指令来实现处理器工作情况的控制,如暂停、让出总线使用权等。
clc/stc/cmc
格式:
1 |
|
功能:
实现对CF的直接操作
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
更改CF值
cld/std
格式:
1 |
|
功能:
实现对DF的直接操作,以进行对串操作的方向控制
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
更改CF值
cli/sti
格式:
1 |
|
功能:
实现对IF的直接操作,用以控制处理器的中断屏蔽状态,一般在中断处理时使用
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
更改IF值
nop
格式:
1 |
|
功能:
使处理器空闲一个指令周期(3个时钟周期)
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
hlt
格式:
1 |
|
功能:
阻塞CPU,等待中断请求或多机系统的同步
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
wait
格式:
1 |
|
功能:
使CPU等待,以使得CPU与协处理器或外部设备同步工作
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
esc
格式:
1 |
|
功能:
CPU让出总线,使得协处理器能够使用总线进行数据传送
操作数要求:
无要求
对操作数影响:
无影响
对标志位影响:
无影响
lock前缀
格式:
1 |
|
功能:
保证指令独占总线,防止协处理器破坏数据
操作数要求:
无要求
对操作数影响:
无要求
对标志位影响:
无要求
代码参考
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!