【说明】

x86指令代码语法

制作程序时,指令数据使用代码表示,这些指令代码称为汇编代码,汇编代码由汇编器转换为对应的指令数据和数学数据。

x86指令代码主要有两种语法:英特尔语法、AT&T语法,之后介绍x86指令时使用英特尔语法。

指令地址码类型表示方式

指令地址码有多种类型,这里使用如下简写词表示地址码类型。

imm,立即数。
imm8,8位立即数。
imm16,16位立即数。
imm32,32位立即数。
imm64,64位立即数。

reg,通用寄存器。
reg8,8位通用寄存器。
reg16,16位通用寄存器。
reg32,32位通用寄存器。
reg64,64位通用寄存器。

mem,内存数。
mem8,1字节内存数。
mem16,2字节内存数。
mem32,4字节内存数。
mem64,8字节内存数。

16进制数据

汇编语言中经常使用8进制、16进制数据,其中最常用的是16进制,因为16进制是2进制连续翻倍的结果,所以16进制包含了2进制的进位关系,一位16进制数据即可表示四位2进制数据,两位16进制数据就可以表示1字节,从而将一个长度很大的二进制数据转换为长度较小的16进制数据,16进制单个数字最大表示十进制的15,其中10-15使用字母a-f表示。

使用两位16进制数据记录1字节时,低位数字表示二进制数据的低4位,高位数字表示二进制数据的高4位,比如 F0 = 1111 0000,所有的4位二进制数据都对应一个16进制数字,将这些转换关系背诵下来就可以直接使用记忆转换,无需计算,就像乘法口诀表一样,使用非常方便,对应关系如下:

1   0001
2   0010
3   0011
4   0100
5   0101
6   0110
7   0111
8   1000
9   1001
A   1010
B   1011
C   1100
D   1101
E   1110
F   1111

比如二进制数据 1001 1011 0011,按照以上对应关系转换为16进制等于 9B3。

指定数据进制

在汇编语言中使用一个数据时应该指定它的进制,指定方式如下:

99,默认为10进制
10B,添加后缀B,表示2进制。
67Q,添加后缀Q,表示8进制。
0CH,添加后缀H,表示16进制,若第一个数字为字母则需要额外添加前缀0。
0xA,添加前缀0x,表示16进制。

【读写数据指令】

读写内存与寄存器

mov指令用于在内存与寄存器之间读写数据,数据的寻址方式有如下三种:
1.寄存器寻址,要读写的数据在寄存器中,地址码指定要读写的寄存器编号。
2.内存寻址,要读写的数据在内存中,地址码指定要读写的内存地址。
3.立即数寻址,需要写入的数据直接存储在指令地址码中。

mov reg, reg/mem/imm
mov mem, reg/imm

地址码1,指定写入数据的位置。
地址码2,指定读取数据的位置。

注:
1.不能在两个内存单元之间读写数据,此操作需要使用寄存器进行中转,因为这种操作实际上是两个步骤,应该使用两个指令分开执行。
2.若使用两个寄存器地址码,则长度需要相同(扩展数据长度指令、读写IO端口指令除外),之后不再重复说明。
3.地址码使用内存寻址时,需要将地址码放在[]符号内。

寄存器寻址

mov ax,bx    ;将bx中的数据写入ax

立即数寻址

mov ax,99    ;将99写入ax寄存器,其中99直接存储在指令的地址码中

内存寻址

读写内存单元时,段地址默认存储在ds寄存器中,也可以不使用默认的ds寄存器作为段地址,而是自行指定一个寄存器作为段地址,比如es,若是x86-64处理器,则在开启内存分页机制后不再使用段地址寄存器。

内存寻址可以在内存与寄存器之间读写数据,也可以将一个立即数写入到内存,但是不能在两个内存单元之间读写数据,此需求需要使用寄存器进行中转。

mov可以读写4种长度的数据:1字节、2字节、4字节、8字节,读写的两个数据长度必须相同,若在内存与寄存器之间读写数据,操作的内存单元长度由寄存器的长度确定,若将一个立即数写入内存单元,则需要使用如下关键词指定内存数据长度:
byte ptr,1字节
word ptr,2字节
dword ptr,4字节
qword ptr,8字节

注:以上关键词是标准英特尔语法所用,nasm汇编器使用的英特尔语法需要省略ptr。

根据指定内存地址的方式又分为:直接内存寻址、间接内存寻址。

直接内存寻址

直接内存寻址使用一个立即数指定要操作的内存地址,操作的内存地址固定不变。

mov ax,[0x404020]           ;将0x404020地址处的数据写入ax寄存器,[]符号内指定偏移地址,段地址默认在ds寄存器中
mov byte ptr[0x404020],9    ;将9写入地址0x404020处,占1个存储单元

间接内存寻址

直接内存寻址将读写的地址写入指令地址码,操作的地址固定不变,使用不灵活,而间接内存寻址使用一个寄存器存储要读写的地址,寄存器中存储的地址也称为指针,使用间接内存寻址的优势是可以修改指令读写的地址,若指令需要在不同情况下读写不同的地址就应该使用间接内存寻址,只需要在不同情况下修改指针寄存器为不同的值即可,当多条指令都需要操作同一个地址、并在环境改变时统一更改地址时,使用间接内存寻址的优势更明显,指针寄存器就像是内存地址的上游供应商,修改了指针寄存器就等于修改了所有使用它的下游指令。

mov ax,[rcx]    ;间接内存寻址,[]符号内指定寄存器,将内存数据写入ax寄存器

使用间接内存寻址时,内存地址可以使用多个数据组合的方式指定,这种方式在读写数组元素时很方便。

mov eax, [rbx+2]        ;寄存器+立即数,也可使用减立即数,此时立即数存储为补码,下同
mov eax, [rbx+rsi]      ;寄存器+寄存器
mov eax, [rbx+rsi+2]    ;寄存器+寄存器+立即数
mov eax, [rbx*4+2]      ;寄存器×立即数+立即数
mov eax, [rbx*4+rcx]    ;寄存器×立即数+寄存器

mov指令操作码说明

实际上读写不同长度数据时、或向不同寄存器写入立即数时,mov指令的操作码不同,CPU将读写数据功能按照数据长度与数据所在位置细分为多个子功能,每个子功能使用不同的指令操作码,可以认为mov是一类指令的统称,汇编器通过长度关键词、或者使用的寄存器确定汇编代码中的mov使用哪个具体的指令。

读写数据并扩展长度

无符号数

movzx reg, reg/mem

地址码1,存储扩展后的数据,扩展的长度由接收数据的寄存器决定,扩展后的高位全部使用0填充。
地址码2,提供需要扩展的数据,地址码2的长度应该小于地址码1,若使用内存寻址则需要指定数据长度。

注意:movzx不能用于将32位寄存器中的数据扩展为64位长度,比如 movzx rax,eax 这样是错误的,因为将eax写入rax时,rax的高位会自动清0,无需扩展,直接使用即可,但是将ax写入eax时,eax的高位不会清0,这是x86-64与x86的一个区别。

movzx ax, al      ;al扩展为ax,ax高位全部设置为0
movzx eax, al
movzx eax, ax
movzx eax, bx
movzx rax, byte[0x404020]

有符号数

movsx指令用于读取并扩展有符号数,新增高位的值使用扩展长度数据的符号位补充,若是正数,则新增高位全部使用0填充,若是负数,则新增高位全部使用1填充。
原因如下:若扩展数据为负数,则最高位的符号位应该设置为1,表示负数,其余新增高位的1表示负数补码,将扩展后的高位全部设置为1即可满足两个补数相加产生进位的规则。

movsx ax, al                ;ax的高8位为al的符号位,若al为负数,则ax的高8位全部为1
movsx eax, al
movsx eax, ax
movsx rax, eax              ;32位寄存器扩展为64位,对于有符号数不会有扩展使用限制
movsx rax, byte[0x404020]

读写指针

lea指令设计用于读写指针(非指针指向的数据),它操作固定长度的数据,在32位处理器中操作32位数据,在64位处理器中操作64位数据。

lea reg, reg/mem

地址码1,接收数据。
地址码2,要读取的数据。

lea rax,[rbx]         ;将rbx中的数据写入rax,虽然使用[]符号,但是这里并非间接内存寻址,这与 mov rax,[rbx] 的功能不同
lea rax,[0x404020]    ;将一个立即数写入rax,数据放在[]符号内,注意这并非表示内存寻址

lea支持读写指针时进行运算,常用于遍历数组,lea支持以下数据运算方式:

lea  rax, [rbx+4]
lea  rax, [rbx+rcx]
lea  rax, [rbx+rcx+4]
lea  rax, [rbx*4+2]
lea  rax, [rcx+rbx*4]

注:虽然lea设计用于读写长度固定的指针,但也可用于读写与指针长度相同的其它数据,因为lea支持读写数据时进行运算,所以经常使用lea进行一些数学运算优化,使用一条lea指令代替多条数学运算指令,执行速度更快。

数据扩展指令

数据扩展指令用于将ax系列寄存器中的有符号数扩展长度。

cbw,将al寄存器中的8位有符号数扩展为16位,扩展结果使用ax存储,al存储低位,ah存储高位。
cwde,将ax中的16位有符号数扩展为32位,扩展结果使用eax存储。
cdqe,将eax中的32位有符号数扩展为64位,扩展结果使用rax存储。
cwd,将ax中的16位有符号数扩展为32位,使用dx+ax存储,dx存储高位,ax存储低位。
cdq,将eax中的32位有符号数扩展为64位,使用edx+eax存储,edx存储高位,eax存储低位。
cqo,将rax中的64位有符号数扩展为128位,使用rdx+rax存储,rdx存储高位,rax存储低位。

其中 cwde、cdqe、cqo 为x86-64新增指令。

数据交换指令

xchg用于将两个地址码中的数据进行交换,使用方式如下:

xchg reg, reg/mem
xchg mem, reg

两个地址码不能同时使用内存寻址,这一点与mov相同。

转换字节序

x86处理器使用小端序方式存储数据,可使用bswap指令修改数据的字节排序方式,将数据在小端序与大端序之间转换。

bswap reg32/reg64

读写栈空间

若有一组数据需要集中存储在内存中,有两种常用的管理方式:队列和栈。

1.队列,最先存储的数据最先使用,最后存储的数据最后使用,就像排队一样。
2.栈,最先存储的数据最后使用,最后存储的数据最先使用,就像弹夹一样,最后压入的子弹最先被击发。

队列使用方式很简单,使用两三条指令即可实现,但是栈使用方式很复杂,需要通过多条指令组合实现,为了增加栈方式的使用效率,CPU提供了专用指令和寄存器实现栈功能,SS+SP寄存器指定栈空间顶部地址,push指令向栈内写入数据,pop读取栈空间数据。

push/pop指令操作数据的长度与CPU位宽相同,在8086处理器中操作2个字节,在x86-64处理器中操作8个字节,长度小的数据需要扩充长度才能写入栈内。

push对栈的操作是从高地址向低地址的顺序使用,这样操作是因为历史原因,并没有特殊优势,执行push时,CPU首先将SP减去CPU位宽,之后将数据写入SP指定的地址处,防止覆盖栈内之前的数据。
pop对栈的操作是从低地址到高地址的顺序使用,执行pop时,CPU首先从SP指定的地址处读取数据,读取完毕后将SP的值增加CPU位宽,定位到下一个数据。

push rax    ;将rax中的数据入栈存储
pop  rdx    ;栈内数据出栈写入rdx

使用栈指令读写数据不灵活,只能按固定顺序、固定长度进行读写,若需自由读写栈内数据可以使用mov指令,并且执行mov时不会修改sp寄存器的值。

mov rax,[rsp]
mov rbx,[rsp+8]
mov rcx,[rsp+16]

当栈空间需要使用mov和push/pop同时操作时容易导致混乱,此时可以首先将sp中的数据写入bp,之后mov使用bp寄存器指定内存地址,不与push/pop共用sp寄存器,当mov使用sp、bp指定偏移地址时,默认使用ss作为段地址寄存器,而非ds。

读写IO地址空间

读IO端口

in al/ax/eax, dx/imm

地址码1,接收数据,只能使用al、ax、eax寄存器,分别读取1字节、2字节、4字节数据。
地址码2,设置读取的IO地址,可以使用立即数指定,也可以使用dx寄存器指定。

in al,0x60    ;读取IO地址空间0x60中的数写入al
in eax,dx     ;使用dx寄存器指定IO地址

写IO端口

out dx/imm, al/ax/eax

地址码1,设置接收数据的IO地址。
地址码2,提供数据。

out 0x60,al   ;将al中的数据写入0x60地址中
out dx,ax     ;将ax中的2字节数据写入dx指定的地址中

读写标志寄存器

lahf,将标志寄存器的0-7位写入ah寄存器。
sahf,将ah寄存器写入标志寄存器的0-7位。

pushf,将标志寄存器的0-15位入栈。
popf,出栈16位数据修改标志寄存器的0-15位。

pushfq,将64位的标志寄存器入栈。
popfq,出栈64位数据修改标志寄存器。

sti,将IF位设置为1。
cli,将IF位设置为0。

stc,将CF位设置为1。
clc,将CF位设置为0。

std,将DF位设置为1。
cld,将DF位设置为0。

读写数据二进制位

读二进制位

bt指令读取一个数据指定位置的二进制位,并写入标志寄存器的CF位。

bt reg/mem, reg/imm

地址码1,指定要操作的数据,长度至少16位,不能使用8位寄存器和8位内存数据。
地址码2,指定要操作的二进制位编号,编号从0开始,0表示最低位。

写二进制位

bts,将一个数据指定位置的二进制数字设置为1,并将原值写入到标志寄存器的CF位中。
btr,将一个数据指定位置的二进制数字设置为0,并将原值写入到标志寄存器的CF位中。
btc,将一个数据指定位置的二进制数字取反值(1变0、0变1),并将原值写入到标志寄存器的CF位中。

使用方式同bt。

查询二进制位

查询一个数据第一次出现数字1的位置。

bsf,从低位开始查询,若所有位都为0则将标志寄存器的ZF位设置为1,否则将ZF位设置为0,并将第一次出现1的位置的编号写入指定寄存器,编号从0开始,最低位的编号为0。
bsr,从高位开始查询,但是最终查询到的数字1所在位置的编号依然从低位开始计算,编号从0开始。

bsf reg, reg/mem
bsr reg, reg/mem

地址码1,存储查询结果,最少使用16位寄存器。
地址码2,指定查询的数据,若为寄存器则需要与地址码1长度相同。

【数组操作指令】

对于一组长度相同并且虚拟地址连续的数据,CPU提供了一些指令方便操作它们,这些指令使用si指定读取数据的地址、使用di指定写入数据的地址,指令执行完毕后会自动修改di、si的值,从而定位到下一个数据。

读数据

以下指令将si指定地址处的数据写入到ax系列寄存器。

lodsb,读取ds+si指定地址中的8位数据写入到al。
lodsw,读取ds+si指定地址中的16位数据写入到ax。
lodsd,读取ds+si指定地址中的32位数据写入到eax。
lodsq,读取ds+si指定地址中的64位数据写入到rax。

每次读取数据后,si的值会自动增加或减小,方便下次读取,读写顺序由标记寄存器的DF位决定。

若DF为0,则si递增,执行lodsb加1、执行lodsq加8。
若DF为1,则si递减,执行lodsb减1、执行lodsq减8。

写数据

以下指令将ax系列寄存器中的数据写入di指定的地址处,同时对di进行递增或递减操作。

stosb,将al中的8位数据写入es+di指定地址中。
stosw,将ax中的16位数据写入es+di指定地址中。
stosd,将eax中的32位数据写入es+di指定地址中。
stosq,将rax中的64位数据写入es+di指定地址中。

此类指令可以与rep指令配合使用,将一段内存空间设置为指定的值,rep用于循环执行指令,循环次数由cx寄存器决定,执行rep时首先判断cx是否为0,不为0则执行一次循环,之后将cx减1,循环执行到cx为0为止。

mov  rcx, 100      ;设置rep循环次数
mov  rax, 0        ;设置写入的数据
lea  rdi, [rsp]    ;设置写入的地址
rep  stosq         ;执行100次stosq

移动数据

以下指令将一组数据从一个位置移动到另一个位置,要读取的地址由ds+si寄存器决定,要写入的地址由es+di寄存器决定,同时对si、di进行递增或递减操作。

movsb,移动1字节数据。
movsw,移动2字节数据。
movsd,移动4字节数据。
movsq,移动8字节数据。

查询数据

在数组中查询是否包含指定数据,同时对di进行递增或递减操作。

scasb,在es+di地址处开始查询,是否包含al寄存器中的8位数据,查询原理是使用减法运算,若减法结果为0则等于查询的数据,一般与repz/repnz配合使用,若找到数据则di存储了此数据下一个位置的地址。
scasw,同上,查询ax中的16位数据。
scasd,同上,查询eax中的32位数据。
scasq,同上,查询rax中的64位数据。

比较数据

比较指令使用减法运算判断两个数据的关系,并通过计算结果设置标志寄存器相关位的值,参与比较的两个数据的地址由si、di提供,同时对si、di进行递增或递减操作。

cmpsb,比较两个8位数据。
cmpsw,比较两个16位数据。
cmpsd,比较两个32位数据。
cmpsq,比较两个64位数据。

rep循环指令

rep系列指令用于循环执行以上指令,循环次数使用CX寄存器指定。

rep,无条件循环,一般与stosb、movsb配合使用。
repz,有条件循环,当标志寄存器的ZF位为1时则执行循环。
repnz,有条件循环,当标志寄存器的ZF位为0时则执行循环。

【整数四则运算】

加法

add 指令

add指令计算两个数据相加,两个加数长度需要相同,支持4种长度数据运算:1字节、2字节、4字节、8字节。

add reg, reg/mem/imm
add mem, reg/imm

两个地址码分别指定两个加数,结果存储在地址码1。

add ax,bx     ;ax加bx,结果存储在ax,两个寄存器的长度必须相同。

adc 指令

adc带进位加法指令,在x86-64中用于计算长度超过8字节的数据,此时需要将数据拆分为高位和低位两部分,分开进行计算,低位使用add计算,若计算结果产生了溢出,则设置标志寄存器CF位为1,高位使用adc计算,adc会额外加CF位的值。

adc reg, reg/mem/imm
adc mem, reg/imm

inc 指令

inc是递增指令,将一个数据自加1。

inc reg/mem

xadd 指令

xadd指令交换两个数据并进行加法运算,计算结果存储在第一个地址码中,等同于xchg与add的组合。

xadd reg/mem, reg

有符号数加法

加法指令操作的都是无符号数,x86并没有设计有符号数加法指令,负数加法也使用无符号的方式进行计算,并得出正确结果。

对于正数+负数,需要计算正数-负数相反数,计算机中实际计算的是正数+负数补码,从而将减法转换为加法,计算结果会自动设置为正确的符号位。
若计算结果为正,则加补码的数据位部分肯定要产生溢出的进位,这个进位参与到符号位运算中,符号位计算为:0+1+1=10,高位的1溢出丢弃,最终符号位为0。
若计算结果为负,则加补码的数据位部分不会产生溢出的进位,此时符号位计算为:0+1+0=1,最终符号位为1,之后存储负数补码。

对于负数+负数,计算两个负数补码相加,计算结果还是补码,与两个原码相加效果相同,符号位同样会自动设置为正确值,有兴趣的可以自行验证。

减法

处理器执行减法运算时会将减数转换为补数,之后送入加法器运算。

sub 指令

sub指令计算两个数据相减,两个减数长度需要相同,支持4种长度数据运算:1字节、2字节、4字节、8字节。

sub reg, reg/mem/imm
sub mem, reg/imm

地址码1为被减数,地址码2为减数,计算结果存储在第一个地址码中。

sub eax, ebx    ;eax减ebx,结果存储在eax

sbb 指令

sbb功能类似adc,用于对长度超过8字节的数据进行减法,将两个减数分为高位和低位两组,低位使用sub执行减法,若低位减法发生借位则设置CF位为1,高位使用sbb执行减法,sbb会额外减去CF位的值。

sbb reg, reg/mem/imm
sbb mem, reg/imm

dec 指令

dec是递减指令,将一个数据减1。

dec reg/mem

有符号数减法

减法指令操作的也都是无符号数,有符号数也使用无符号数减法指令计算,最终得出正确结果,比如减负数,根据负负得正规则,等同于计算加法,而计算机中负数使用补码存储,减负数会自动取补码的补码,等于取原码,并计算加法,符合减负数等于加正数的运算规则。

乘法

mul 指令

mul计算两个无符号整数乘法。

mul reg/mem

乘数1在ax系列寄存器中,乘数2使用地址码指定,乘法指令源于16位的8086CPU,计算结果使用dx+ax系列寄存器存储,dx存储高位,ax存储低位,若乘法结果长度超过乘数长度,则设置标志寄存器CF位为1,告知程序使用dx+ax系列调用乘法结果。

在32位、64位x86处理器中虽然有了长度更大的寄存器,但是依然使用DX+AX系寄存器存储计算结果。

mul bl                   ;al乘bl,结果存储在ax
mul ebx                  ;eax乘ebx,结果存储在edx+eax
mul dword ptr[404030]    ;eax乘4字节内存数据,结果存储在edx+eax

imul 指令

imul计算两个有符号整数乘法,有三种使用形式:单地址码形式、双地址码形式、三地址码形式。

单地址码形式类似mul,乘数1在ax系列寄存器中,乘数2所在位置使用地址码指定。

imul reg/mem

双地址码形式,地址码1存储乘数1,地址码2存储乘数2,计算结果存储在地址码1中,若使用两个寄存器则长度需要相同,存储乘法结果时可能会溢出。

imul reg, reg/mem/imm

三地址码形式,地址码1存储乘法结果,地址码2存储乘数1,地址码3存储乘数2。

imul reg, reg/mem, imm

除法

除法指令注意事项:
1.除数不能为0,否则会产生内中断,运算器为了防止无限循环除以0会禁止执行这种除法。
2.被除数的长度要为除数的2倍,否则产生内中断。

div 指令

div计算无符号整数除法。

div reg/mem

被除数在AX或DX+AX寄存器中,除数所在位置使用地址码指定,若不能整除则保留余数,计算结果的商存储在AX系寄存器中,余数存储在DX系寄存器或AH寄存器中。

div同样源于16位的8086处理器,除法运算操作的数据安排如下:
除数长度8位,被除数使用AX存储,商使用AL存储,余数使用AH存储。
除数长度16位,被除数使用DX+AX存储,DX存储高位、AX存储低位,商使用AX存储,余数使用DX存储。

若商长度过大,无法存储在AX系寄存器中,则会产生内中断。

mov eax,10   ;被除数
mov ebx,3    ;除数
cdq          ;扩展被除数长度,edx存储高位,eax存储低位
div ebx      ;除法,eax存储商,edx存储余数

idiv 指令

idiv是有符号整数除法,使用方式同div。

idiv reg/mem

【数据移位运算】

移位运算将数据的二进制位整体向高位或低位移动,常用于快速得到乘以2或除以2的结果,向高位移动也称为左移,向低位移动也称为右移,这样称呼源于在纸上书写,数据高位写在左边、低位写在右边。

移位方式

左移运算

对于无符号数,最高位二进制数字被丢弃,新增低位使用0填充,若最高位为1,则左移后会导致高位溢出,则结果将不符合乘以2。
对于有符号数,若是负数,则使用补码存储,左移操作的是补码而非原码,补码新增低位使用0填充,补码转原码时先减1再取反,对应的原码新增低位也是0,最终补码左移等于原码左移。

右移运算

对于无符号数,最低位二进制数字被丢弃,新增高位使用0填充,若最低位为1,则右移后低位1被丢弃,不是除以2的结果,有少量误差。
对于有符号数,最低位二进制数字被丢弃,新增高位使用符号位填充,若为正数则高位补0,若为负数则高位补1,补1的原因是负数使用补码存储,补码转原码时有一个取反的过程,此时补1的高位会转换为0,从而实现补码右移等同于原码右移。

移位指令

shl reg/mem, cl/imm8    ;逻辑左移,适用于无符号数,新增低位补0,溢出的高位存储在标志寄存器CF位中。
shr reg/mem, cl/imm8 ;逻辑右移,适用于无符号数,新增高位补0,溢出的低位存储在标志寄存器CF位中。
sal reg/mem, cl/imm8 ;算数左移,适用于有符号数,符号位会参与移动,但是溢出的符号位会保存在CF位中,数据位溢出后不会进行记录。
sar reg/mem, cl/imm8 ;算数右移,适用于有符号数,符号位不参与移动,空出的高位使用符号位填充,正数补0、负数补1。

地址码1,指定需要移位的数据。
地址码2,指定移动的次数,使用cl寄存器寻址、或立即数寻址,若为立即数则长度不能超过8位。

实际上有符号数左移也可以使用逻辑左移,在不发生溢出的情况下依然能够得出正确结果。
逻辑左移正数时,符号位为0,若数据位(符号位除外的位)最高位为0,则移动后代替符号位,依然为正,若数据位最高位为1,则移动后数据位溢出导致错误,并且符号变为1,错上加错最终还是错误结果。
逻辑左移负数时,符号位为1,若补码最高数据位为1,则原码最高数据位为0,所以只有补码溢出原码才不会溢出,此时补码溢出的数字位1代替原来的符号位1,符号位不变,若补码最高数据位为0则原码最高数据位为1,左移后原码肯定会发生溢出,虽然符号位变为0导致结果不正确,但是错上加错最终还是错误结果。

控制新增位的值

以下指令可以控制新增位使用0填充还是1填充。

shld reg/mem, reg, cl/imm8    ;左移
shrd reg/mem, reg, cl/imm8    ;右移

地址码1,指定需要移动的数据。
地址码2,指定新增位填充的数据。
地址码3,指定移动位数。

循环位移指令

循环位移指令将丢弃的位放在新增的位中,移动方式组成一个圆形。

rol reg/mem, cl/imm8    ;循环左移
ror reg/mem, cl/imm8    ;循环右移
rcl reg/mem, cl/imm8    ;带进位循环左移
rcr reg/mem, cl/imm8    ;带进位循环右移

【按位逻辑运算】

按位逻辑运算将数据中的每个二进制位进行逻辑运算,并根据计算结果组成一个新数据。

与运算

and指令执行按位与运算。

and reg, reg/mem/imm
and mem, reg/imm

将两个数据同位置的数字进行与运算,若都为1则运算结果此位为1,否则运算结果此位为0,结果保存在地址码1,此指令会影响标志寄存器的SF、ZF、PF位。

and ax, ax          ;若ax为0则ZF位为1,从而判断ax是否为0,若ax为负则SF位为1,从而判断ax是否为负
and ax, 1111B       ;提取ax低4位的值,其它位全部设置为0
and ax, 100B        ;提取ax第3位的值,其它位全部设置位0,之后通过PF位判断第三位是否为1
and al, 11111101B   ;设置al第2位为0,其它位不变,若第2位原值为0则等于不执行任何操作

或运算

or指令执行按位或运算。

or reg, reg/mem/imm
or mem, reg/mem/imm

同位置的两个数字有一个为1则运算结果此位为1,否则此位为0,结果保存在地址码1。

or ax, bx      ;合并ax、bx中的数字1
or ax, 100B    ;设置ax第3位为1,其它位不变

非运算

not指令执行按位非运算,将一个数据的每个位取反值。

not reg/mem

异或运算

xor指令执行按位异或运算。

xor reg, reg/mem/imm
xor mem, reg/mem/imm

判断两个数字是否不同,若同位置的数字不同则运算结果此位为1,若相同则此位为0,结果保存在地址码1。

xor eax, eax    ;清空eax,执行速度比 mov eax,0 更快

test

test指令执行按位与运算,运算方式同and指令,但是它不保存计算结果,只是通过计算结果设置标志寄存器的SF、ZF、PF位,用于对数据进行判断并保留数据原值。

test reg, reg/mem/imm
test mem, reg/mem/imm

补码运算

neg指令计算一个无符号数的补码,具体操作是二进制位取反并加1,若操作数是0则设置标志寄存器CF位为0,否则CF为1。

neg reg/mem

【数据比较指令】

cmp指令用于判断两个数据之间的关系,比如大于、小于、等于、大于等于、小于等于,比较结果使用标志寄存器存储,某些指令会使用标志寄存器存储的数据关系确定是否执行,比如有条件读写数据指令、有条件跳转指令。

cmp指令会执行减法操作,但是不保留计算结果,只是根据减法结果设置标志寄存器的ZF、PF、SF、CF、OF、AF位,从而记录两个数据之间的关系。

cmp reg, reg/mem/imm
cmp mem, reg/imm

地址码1,指定被减数。
地址码2,指定减数。

保存比较结果

数学运算、逻辑运算、比较指令都会修改标志寄存器,若需保存关系结果可以使用set系列指令,无需自行读取标志寄存器。

set reg8/mem8

若满足关系则将地址码指定的数据设置为1,否则设置为0。

set是一系列指令的统称,不是一条具体的指令,set后缀不同字母表示不同的执行条件,分别如下:

sete,等于
setne,不等于

sets,为负
setns,不为负

setz,为0
setnz,不为0

setc,进位
setnc,不进位

seto,溢出
setno,不溢出

setp,PF位为1
setnp,PF位为0

seta,无符号数大于
setae,无符号数大于等于

setb,无符号数小于
setbe,无符号数小于等于

setg,有符号数大于
setge,有符号数大于等于

setl,有符号数小于
setle,有符号数小于等于

以上指令有些作用相同,比如setz、sete,都是ZF=1则满足关系,它们本质上是同一条指令,指令操作码相同,只不过使用了不同的关系描述方式,方便记忆。

cmp eax, ebx    ;比较eax与ebx
sete al ;若等于,则将al写入1,否则al为0

【有条件操作数据指令】

有条件读写数据

此类指令为x86-64新增,根据cmp比较指令或数学运算指令的结果确定是否执行读写操作,此类指令有多种,指令代码以cmov开头,结尾字母与set系列指令相同,地址码格式如下:

cmov reg, reg/mem
cmp     eax, 0
cmovg   edx, eax   ;执行条件同setg,若eax大于0则将eax写入edx,否则不执行

比较并交换

cmpxchg用于比较两个数据并根据比较结果对数据进行交换,相当于cmp与xchg的组合。

cmpxchg reg/mem, reg

地址码1,提供比较的数据,其与ax系寄存器进行比较(使用ax系寄存器的长度与地址码中指定的寄存器长度相同)。
若两个数据相等,将地址码2中的数据写入地址码1中。
若两个数据不等,将地址码1中的数据写入ax系寄存器。

【程序执行流程控制指令】

无条件跳转指令

CPU每执行一条指令时就将IP寄存器的值增加,定位到下一条指令的地址,实现顺序执行,若需跳转执行则使用跳转指令,跳转指令会修改指令地址寄存器的值,在32位x86处理器中跳转指令涉及到修改CS寄存器,在x86-64处理器中开启内存分页使用模式后段地址寄存器作废,只需要修改IP寄存器。

无条件跳转

jmp reg64/mem64/imm    ;在64位处理器中只能使用64位寄存器、64位内存数

若使用寄存器寻址、内存寻址,则寄存器和内存中直接存储要跳转到的地址。
若使用立即数寻址,则立即数设置IP寄存器增加的值,通过让IP与一个数据相加得出要跳转到的地址,向前跳转则加负数,在编写汇编代码时可以直接编写要跳转到的地址,汇编器会自动转换为跳转到此处IP需要加的数据。

无条件跳转并返回

若跳转执行之后还需要返回,可以使用call指令,call在跳转之前会首先将现在顺序执行的下一条指令的地址存储到栈中,之后通过执行ret指令返回执行,ret会取栈中保存的内存地址写入到IP寄存器。

call reg64/mem64/imm    ;使用方式同jmp
ret ;调用栈中数据修改ip。
retn ;调用栈中数据修改ip,x86-64废弃。
retf ;调用栈中数据修改ip和cs。

其中三个ret指令可选使用一个imm8地址码,用于栈平衡,比如  ret 8 等于如下两条指令的组合:

ret
add sp, 8

栈平衡的作用是在C语言函数执行完毕后释放栈内参数占用的栈空间,具体知识在之后的C语言中讲解。

有条件跳转指令

根据cx寄存器跳转

jcxz imm     ;若cx为0则跳转到指定地址处执行,否则不执行,64位模式中此条指令作废
jecxz imm ;若ecx为0则跳转
jrcxz imm ;若rcx为0则跳转

立即数地址码指定ip寄存器要加的数据,在编写汇编代码时可以直接指定要跳转到的地址,汇编器会自动转换为跳转到此处ip需要加的数据。

根据标志寄存器跳转

此类指令根据标志寄存器存储的判断结果确定是否执行,指令以j字母开头,结尾字母与set系列指令相同,有一个立即数地址码,使用方式与jrcxz相同。

cmp ax, bx
je x ;若ax等于bx则跳过中间代码,转到地址标号x处执行
...... ;代码略
x: ;设置地址标号,x表示此处的内存地址
...... ;代码略

循环指令

根据cx寄存器循环

loop指令同样是跳转指令,用于向前跳转实现循环执行,循环次数由CX寄存器确定,若CX不为0则执行loop指令,每执行一次将CX减1,若CX为0则跳过loop指令,执行下一条指令。

mov rcx,5    ;循环5次
x:
......       ;循环代码略
loop x       ;跳转到地址x处循环执行

根据cx、标志寄存器ZF位循环

loopz/loope,若cx不为0,并且标志寄存器的ZF位为1则执行。
loopnz/loopne,若cx不为0,并且标志寄存器的ZF位为0则执行。

使用有条件跳转指令实现循环

很多情况下循环条件在编写代码时不能确定,而是在程序执行期间确定,此时可以通过有条件跳转指令实现循环。

......         ;初始代码略
x:
......         ;循环代码略
inc ebx        ;每次循环ebx加1
cmp eax,ebx    ;比较eax与ebx
jne x          ;不相等则跳转到地址x处循环执行

中断指令

int,发出自定义编号内中断,并将CS、IP、标志寄存器入栈存储,中断编号使用立即数地址码指定,示例:int 3
iret,将栈中的数据取出并写入CS、IP、标志寄存器,中断处理程序执行完毕后会使用iret指令返回之前的程序执行。
into,若标志寄存器的OF位为1则产生4号内中断,等于若溢出则执行 int 4
hlt,暂停执行,等待接收中断,接收到中断后恢复执行。
syscall,发出0x80号内中断,Linux系统调用使用。
sysret,中断处理程序返回,Linux系统调用使用。

nop

nop指令不执行任何操作,常用于程序文件内部数据地址对齐。

【浮点数指令】

浮点运算单元

浮点数运算器的设计很复杂,早期的CPU没有集成浮点数运算单元,而是将浮点数运算单元做成一个独立的芯片,并使用总线与CPU连接,现在的CPU内部都会集成浮点数运算单元,英文简称FPU,FPU不使用x86中的指令以及寄存器,他有自己专用的指令集(也称为x87)和寄存器,FPU要操作的数据需要首先从内存写入到FPU寄存器,数据操作完毕后从FPU寄存器栈写入到内存。

FPU支持如下类型数据运算:
1.单精度浮点数
2.双精度浮点数
3.扩展双精度浮点数
4.16位整数
5.32位整数
6.64位整数
7.BCD数据

计算结果的舍入

若浮点数计算结果的长度超过存储长度,则需要对数据进行舍入操作,舍入方式有四种:
1.向下舍入,舍入后比原值小,正数丢弃超出的数字,负数丢弃超出数字后再将最低位+1。
2.向上舍入,舍入后比原值大,负数丢弃超出的数字,正数丢弃超出数字后在将最低位+1。
3.向0舍入,舍入后的值更接近0,正负数都直接丢弃超出的数字位即可。
4.最近值舍入,舍入到最接近的值,类似十进制四舍五入的方式,二进制为0舍1入。

注:IEEE754规则中规定的最近值舍入方式为round to nearest even(银行家舍入法),具体行为是在最近值舍入的基础上保证舍入结果的末尾是双数,在二进制数据中是在0舍1入的基础上保证最低存储位为0,若执行0舍操作后最低存储位为1,则对最低存储位进行+1,若执行1入操作后最低存储位为1,则放弃1入操作,若理解困难的话,你可以直接理解为以最低存储位进行0舍1入操作,而不是以超出的数字位进行舍入。

常用寄存器

通用寄存器

FPU有8个通用寄存器,名为R0-R7,长度都是80位,这组寄存器以栈的方式使用,所以称为寄存器栈,R0-R7的使用顺序组成一个圆形,也就是循环作为栈顶使用,入栈出栈操作都不需要移动其它数据,栈顶寄存器也称为st0,栈底寄存器也称为st7,FPU使用状态寄存器记录现在哪个寄存器作为栈顶。

状态寄存器(FPU status)

长度16位,用于记录FPU的工作状态,比如是否发生了某种异常、比较指令的运算结果、寄存器栈中的哪个寄存器为栈顶。

0位,无效操作异常(I异常),又分为两个子类:无效数据(IA异常)、寄存器栈入栈出栈操作错误(IS异常)。
1位,非规格化数异常(D异常),操作数不是正确的浮点数编码。
2位,除数为0异常(Z异常)。
3位,向上溢出异常(O异常),计算结果舍入后的值超出浮点数可表示的最大合规数。
4位,向下溢出异常(U异常),计算结果舍入后的值超出浮点数可表示的最小合规数。
5位,舍入异常(P异常),计算结果发生舍入,得出不精确的合规数。
6位,寄存器栈故障。
7位,异常汇总,任何异常发生则此位为1。
8位,C0位。
9位,C1位。
10位,C2位。
11-13位,TOP位,存储寄存器栈中栈顶的寄存器编号,值为 000B 至 111B。
14位,C3位。
15位,不使用。

C1位可以用作符号指示位,说明数据的正负,还可用于说明异常状态的具体原因,比如IS异常是出栈错误还是入栈错误,P异常是发生了向上舍入还是非向上舍入。
C0、C2、C3位用于记录比较指令的结果,同时还服务于fxam指令,用于检查st0中数据的具体类型,具体细节参考开发手册。

控制寄存器(FPU control)

长度16位,用于控制FPU的工作方式,比如各种异常是否生效、计算结果精度控制、计算结果舍入方式,每个位的作用如下:

0位,是否屏蔽无效操作异常,1表示屏蔽。
1位,是否屏蔽非规格化数异常。
2位,是否屏蔽除数为0异常。
3位,是否屏蔽向上溢出异常。
4位,是否屏蔽向下溢出异常。
5位,是否屏蔽舍入异常。
6-7位,保留。
8-9位,计算结果精度控制(00单精度,01未使用,10双精度,11扩展双精度)。
10-11位,舍入控制(00最近值舍入,01向下舍入,10向上舍入,11向0舍入)。
12位,无限大控制,已废弃。
13–15位,保留。

标识寄存器(FPU tag)

长度16位,用于说明寄存器栈内数据的类型,每个寄存器使用2个数字位说明数据类型,每个值的含义如下:

00,有效数据。
01,值为零。
10,无效数据(无限大、不合规、NaN、编码错误)。
11,空,没有存储数据。

标识寄存器默认为0,使用FPU时需要首先执行finit指令,将标识寄存器设置为0xFFFF。

常用指令

控制指令

finit,检查错误条件后初始化FPU,工作方式设置为忽略所有异常、计算结果精度64位、使用最近值舍入方式,寄存器栈设置为空、栈顶设置为R0寄存器。
fstsw,将FPU状态寄存器写入ax寄存器或内存。
fstcw,将FPU控制寄存器写入内存。
fldcw,读取内存数据修改FPU控制寄存器。

读写数据指令

fld mem32/mem64/mem80        ;将内存数写入st0
fst mem32/mem64/st           ;将st0写入内存或其它st
fstp mem32/mem64/mem80/st    ;同fst,再执行一次出栈 fild mem16/mem32/mem64       ;将内存中的有符号整数转换为浮点数,并写入到st0
fist mem16/mem32             ;将st0中的浮点数转换为有符号整数,并写入到内存
fistp mem16/mem32/mem64      ;同fist,再执行一次出栈 fxch       ;st0与st1交换
fxch st    ;st0与指定st交换 fld1     ;将1.0写入寄存器栈
fldz     ;将0.0写入寄存器栈
fldpi    ;将圆周率写入寄存器栈
fld tbyte ptr[0x404030]    ;标准intel语法,tbyte ptr 表示10字节
fld tword[0x404030]        ;nasm汇编器语法,tword 表示10字节

数据运算指令

fadd,加法。
fsub,减法。
fmul,乘法。
fdiv,除法。

fadd st(1)           ;st1与st0相加,结果存储在st0,标准intel语法
fadd st1             ;nasm汇编器语法
fadd st(1), st(0)    ;st1与st0相加,结果存储在st1,标准intel语法
fadd st1, st0        ;nasm汇编器语法

FPU数据运算指令有很多,并且以上4个基础运算指令有多种使用方式,这里不逐个介绍,具体参考开发手册。

数据比较指令

fcom系列

fcom,比较st0与st1,或st0与指定数据。
fcomp,同fcom,并出栈一次。
fcompp,同fcom,并出栈两次。

fcom系列指令的比较结果记录在FPU状态寄存器中。

若st0大于指定数据,C0=0,C2=0,C3=0
若st0小于指定数据,C0=1,C2=0,C3=0
若st0等于指定数据,C0=0,C2=0,C3=1

fcomi系列

fcomi,比较st0与st1,或st0与指定st。
fcomip,同fcomi,并出栈一次。

fcomi系列指令将比较结果记录在x86标志寄存器中,具体行为是将C0、C2、C3的值转移到CF、PF、ZF中,之后直接使用x86有条件跳转指令、有条件读写数据指令即可。

若st0大于指定数据,CF=0,PF=0,ZF=0
若st0小于指定数据,CF=1,PF=0,ZF=0
若st0等于指定数据,CF=0,PF=0,ZF=1

fcomi          ;比较st0与st1
fcomi st(2)    ;比较st0与st2

st0 操作指令

fabs,取st0绝对值。
fchs,取st0相反数。
ftst,判断st0是否为0。
fxam,检测st0的具体类型。
frndint,将st0小数位清0,使用最近值舍入方式,结果依然是浮点数。

【SIMD】

SIMD中文名为单指令多数据,是一种技术的名称,表示使用一条指令同时处理多个数据,多个数据以排列顺序进行组合,之后由一条指令同时进行读、写、运算操作,相比一条指令只操作1到2个数据的方式执行速度更快。

x86处理器实现SIMD功能的指令集有MMX、SSE、AVX。

MMX操作64位打包数据,它没有自己的运算器和寄存器,使用FPU进行数据的存储和运算,MMX有8个64位寄存器,名为 mm0 - mm7,对应FPU的8个通用寄存器,SSE操作128位打包数据,SSE有自己的运算器和寄存器,AVX操作256位打包数据,AVX-512操作512位打包数据,本文使用SSE指令介绍SIMD技术。

SSE寄存器

通用寄存器

SSE有16个128位的通用寄存器,名为 xmm0 - xmm15,其中 xmm8 - xmm15 只存在于64位的x86-64处理器中。

xmm寄存器可以存储如下类型的数据:

1个32位浮点数,使用寄存器的低32位存储。
1个64位浮点数,使用寄存器的低64位存储。
2个64位浮点数。
4个32位浮点数。
2个64位整数。
4个32位整数。
8个16位整数。
16个8位整数。

若xmm寄存器存储的是单个数据,则此数据称为标量数据(scalar),若xmm寄存器存储的是多个数据,则此数据称为打包数据(packed),标量数据和打包数据使用不同的指令进行操作。

MXCSR 控制寄存器

SSE有一个名为MXCSR的控制寄存器,长度32位,但只使用低16位,每个位的功能如下:

0位,IE位,是否检测无效操作异常,0表示不检测。
1位,DE位,是否检测非规格化数异常。
2位,ZE位,是否检测除数为0异常。
3位,OE位,是否检测向上溢出异常。
4位,UE位,是否检测向下溢出异常。
5位,PE位,是否检测舍入异常。
6位,DAZ位,是否启用DAZ功能(denormal are zero),值为1表示启用DAZ,此时非规格化浮点数会修改为0再进行运算,同时不检测非规格化数异常。
7位,IM位,是否屏蔽无效操作异常,0表示不屏蔽,若屏蔽则即使检测到异常也不产生内中断,处理器使用自己默认的处理方式。
8位,DM位,是否屏蔽非规格化数异常。
9位,ZM位,是否屏蔽除数为0异常。
10位,OM位,是否屏蔽向上溢出异常。
11位,UM位,是否屏蔽向下溢出异常。
12位,PM位,是否屏蔽舍入异常。
13-14位,RC位,计算结果舍入控制。
15位,FTZ位,是否启用FTZ功能(flush to zero),值为1表示启用,此时在下溢异常被屏蔽时将计算结果设置为0,同时设置UE、PE位的值为1。

内存地址对齐

多数SSE指令操作内存数据时要求数据所在内存地址为16的倍数。

读写 MXCSR 寄存器指令

若需修改MXCSR寄存器,首先使用STMXCSR指令将MXCSR寄存器读取到内存中,之后在内存中修改,最后使用LDMXCSR指令将修改后的内存数据写入MXCSR寄存器。

stmxcsr mem32    ;将mxcsr读取到内存中
ldmxcsr mem32    ;将内存数写入mxcsr

读写标量数据指令

movss xmm, xmm/mem32    ;读写单精度浮点数,地址码1接收数据(使用xmm寄存器低32位),地址码2提供数据(使用xmm寄存器低32位)
movss mem32, xmm        ;若地址码1使用内存寻址,则地址码2只能使用xmm寄存器 movsd xmm, xmm/mem64    ;读写双精度浮点数,同上
movsd mem64, xmm

读写打包数据指令

操作64位数据

movlps xmm, mem64    ;读写2个单精度浮点数,地址码1接收数据(使用xmm寄存器低64位),地址码2提供数据
movlps mem64, xmm    ;同上,将xmm寄存器低64位写入内存,movlps指令不支持在两个xmm寄存器、或两个内存单元之间读写 movhps xmm, mem64    ;读写2个单精度浮点数,地址码1接收数据(使用xmm寄存器高64位),地址码2提供数据
movhps mem64, xmm    ;同上 movhlps xmm, xmm     ;读取地址码2的高64位,写入地址码1的低64位 movlhps xmm, xmm     ;读取地址码2的低64位,写入地址码1的高64位

操作128位数据

movaps xmm, xmm/mem128    ;将地址码2中的数据写入地址码1,内存数据需要地址对齐
movaps mem128, xmm movups xmm, xmm/mem128    ;将地址码2中的数据写入地址码1,内存数据无需地址对齐,执行速度比movaps稍慢
movups mem128, xmm

任意读取打包数据

extractps reg32/mem32, xmm, imm

地址码1,接收标量数据。
地址码2,提供打包数据。
地址码3,说明要读取打包数据中4个32位数据的哪一个,指定数据编号,编号从高位开始分配,从0开始计算

标量数据运算指令

addss xmm, xmm/mem32    ;单精度浮点数加法,结果保存在地址码1
addsd xmm, xmm/mem64    ;双精度浮点数加法 subss xmm, xmm/mem32    ;单精度浮点数减法,地址码1为被减数,结果保存在地址码1
subsd xmm, xmm/mem64    ;双精度浮点数减法 mulss xmm, xmm/mem32    ;单精度浮点数乘法
mulsd xmm, xmm/mem64    ;双精度浮点数乘法 divss xmm, xmm/mem32    ;单精度浮点数除法
divsd xmm, xmm/mem64    ;双精度浮点数除法 maxss xmm, xmm/mem32    ;查找最大单精度浮点数
maxsd xmm, xmm/mem64    ;查找最大双精度浮点数 minss xmm, xmm/mem32    ;查找最小单精度浮点数
minsd xmm, xmm/mem64    ;查找最小双精度浮点数

打包浮点数运算指令

对两个打包数据每个同位置的数据进行运算。

addps xmm, xmm/mem128   ;单精度浮点数加法,计算4个浮点数相加,结果保存在地址码1
addpd xmm, xmm/mem128   ;双精度浮点数加法 subps xmm, xmm/mem128   ;单精度浮点数减法,地址码1为被减数,结果保存在地址码1
subpd xmm, xmm/mem128   ;双精度浮点数减法 mulps xmm, xmm/mem128   ;单精度浮点数乘法
mulpd xmm, xmm/mem128   ;双精度浮点数乘法 divps xmm, xmm/mem128   ;单精度浮点数除法
divpd xmm, xmm/mem128   ;双精度浮点数除法 maxps xmm, xmm/mem128   ;查找最大单精度浮点数
maxpd xmm, xmm/mem128   ;查找最大双精度浮点数 minps xmm, xmm/mem128   ;查找最小单精度浮点数
minpd xmm, xmm/mem128   ;查找最小双精度浮点数

打包整数运算指令

使用方式同打包浮点数运算指令。

paddsb xmm, xmm/mem128    ;打包1字节有符号整数加法
paddsw xmm, xmm/mem128 ;打包2字节有符号整数加法
paddusb xmm, xmm/mem128 ;打包1字节无符号整数加法
paddusw xmm, xmm/mem128 ;打包2字节无符号整数加法 psubsb xmm, xmm/mem128 ;打包1字节有符号整数减法
psubsw xmm, xmm/mem128 ;打包2字节有符号整数减法
psubusb xmm, xmm/mem128 ;打包1字节无符号整数减法
psubusw xmm, xmm/mem128 ;打包2字节无符号整数减法

打包数据 horizontal 运算指令

horizontal运算在打包数据内部进行运算。

haddps xmm, xmm    ;单精度浮点数加法
haddpd xmm, xmm    ;双精度浮点数加法 hsubps xmm, xmm    ;单精度浮点数减法
hsubpd xmm, xmm    ;双精度浮点数减法 phaddw xmm, xmm    ;2字节有符号整数加法
phaddd xmm, xmm    ;4字节有符号整数加法 phsubw xmm, xmm    ;2字节有符号整数减法
phsubd xmm, xmm    ;4字节有符号整数减法

执行haddps指令时,两个xmm寄存器中的打包数据都会计算自身低位2个浮点数与高位2个浮点数相加,计算结果形成的4个浮点数继续打包(地址码1计算结果在低位),打包后的结果存储在地址码1,其它horizontal指令功能类似。

数据类型转换指令

cvtss2sd xmm, xmm/mem32          ;单精度浮点数转双精度浮点数,地址码1存储结果(使用低32位),地址码2提供转换数据
cvtsd2ss xmm, xmm/mem32          ;双精度浮点数转单精度浮点数 cvtss2si  reg32, xmm/mem32       ;单精度浮点数转整数,使用最近值舍入法
cvttss2si reg32, xmm/mem32       ;单精度浮点数转整数,使用截断法,丢弃小数位 cvtsd2si  reg64, xmm/mem64       ;双精度浮点数转整数,使用最近值舍入法
cvttsd2si reg64, xmm/mem64       ;双精度浮点数转整数,使用截断法,丢弃小数位 cvtsi2ss xmm, mem32/mem64/reg32/reg64    ;整数转单精度浮点数
cvtsi2sd xmm, mem32/mem64/reg32/reg64    ;整数转双精度浮点数 cvtpi2ps xmm, mm/mem64      ;2个4字节整数转单精度浮点数,地址码1存储结果(使用低64位),地址码2提供转换数据(mm表示mmx寄存器)
cvtps2pi mm, xmm/mem64      ;2个单精度浮点数转4字节整数,地址码1存储结果,地址码2提供转换数据,使用最近值舍入法
cvttps2pi mm, xmm/mem64     ;2个单精度浮点数转4字节整数,地址码1存储结果,地址码2提供转换数据,使用截断法

数据比较指令

comiss xmm, xmm/mem32    ;标量单精度浮点数比较(若有NaN数则产生无效操作异常),比较结果存储在标志寄存器中。
comisd xmm, xmm/mem64    ;标量双精度浮点数比较(若有NaN数则产生无效操作异常),比较结果存储在标志寄存器中。
ucomiss xmm, xmm/mem32   ;标量单精度浮点数比较(若有SNaN数则产生无效操作异常),比较结果存储在标志寄存器中。
ucomisd xmm, xmm/mem64   ;标量双精度浮点数比较(若有SNaN数则产生无效操作异常),比较结果存储在标志寄存器中。

SSE数据比较指令有很多,不仅用于比较单个数据的大于、小于、等于关系,还可以比较打包数据的各种关系,这里不一一介绍,参考开发手册。

03. x86基础指令的更多相关文章

  1. Linux基础(03)、常用基础指令和操作

    目录 一.什么是Linux 二.常用基础指令 2.1.vi编辑 2.2.Linux文件类型 2.3.常用指令:增.删.改.查.其他 三.Linux的目录和权限 3.1.目录 3.2.权限 3.3.修改 ...

  2. Linux 基础指令初识

    Linux 基础指令初识 01. ls 指令 语法: ls [选项] [目录或文件] 功能:对于目录,该命令列出该目录下的所有子目录与文件.对于文件,将列出文件名以及其他信息 -a 列出目录下的所有文 ...

  3. DB2的基础指令 学习笔记

    =======DB2基础指令======= 1.打开数据库db2 connect to 数据库名;2.查看数据库中有哪些表db2 list tables ;3.查看数据库中的表结构db2 descri ...

  4. Linux基础指令

    Linux基础指令 只写了最简单的一些文件操作,基本没有带参数 查看当前目录 pwd 跳转到某路径 cd 查看当前目录下的文件 ls ls -l // -l 查看详细信息 打开当前所在文件夹 open ...

  5. Vue的介绍及基础指令

    一.什么是Vue Vue.js是一个渐进式 JavaScript 框架 通过对框架的了解与运用程度,来决定其在整个项目中的应用范围,最终可以独立以框架方式完成整个web前端项目 为什么要学习Vue 三 ...

  6. Python 基础指令

    ## Python 基础指令 ```Shell $ pip install ipython== # 安装指定版本的python第三方库 $ python --version #查看版本 $ which ...

  7. vue学前班004(基础指令与使用技巧)

    我学vue 的最终目的是为了 做apicloud 和vue 的开发  作为配合apicloud的前端框架使用 所以项目用不到的会暂时不介绍. (强烈建议  官网案例走一遍) 基础指令的学习(结合aui ...

  8. x86 体系指令

    FASM 第二章 - 2.1 x86 体系指令 Author: 徐艺波  From: xuyibo.org  Updated: 2008-04-17   官方论坛   本站软件反馈.软件开发交流.   ...

  9. linux基础指令以及权限管理

    基础指令 #打印字符串 echo hello linux #将file1 和 file2粘合在一起,打印到标准输出流 cat file1 file2 标准输入输出 标准输入,stdin,即键盘.鼠标输 ...

  10. x86汇编指令脚本虚拟机

    简介 这是一个可以直接解释执行从ida pro里面提取出来的x86汇编代码的虚拟机. 非常精简,整体架构上不能跟那些成熟的虚拟机相比,主要目标是够用.能用.轻量就行,如果觉得代码架构设计的不是很好的话 ...

随机推荐

  1. KingbaseES V8R6 集群运维案例 -- 归档失败导致 Switchover 失败

    案例说明: KingbaseES V8R6集群,备库在执行'repmgr standby switchover'时,切换失败,出现以下故障: 经检查发现是主库归档配置错误,主库出现归档失败导致. 适用 ...

  2. JWT 安全令牌

    1 package com.reliable.yang.utils; 2 3 import io.jsonwebtoken.Jwt; 4 import io.jsonwebtoken.JwtBuild ...

  3. 论文阅读小结(B/S和C/S结构)

    论文阅读小结 一.B/S 和 C/S 软件体系结构选择 1) C/S . B/S 结构概述 C/S 结构,即 Client/Server (客户机 / 服务器 ), C/S 结构软件分为客户机和服务器 ...

  4. Python连接mysql数据库和关闭数据库的方法

    1 import pymysql 2 def get_conn(): 3 """ 4 :return: 连接,游标 5 """ 6 # 创建 ...

  5. C++类型双关

    Σ(っ °Д °;)っ #include<iostream> struct Entity { int x, y; int* GetPtr() { return &x; } /*En ...

  6. 密码学系列之:SAFER

    密码学系列之:SAFER 简介 分组密码是一个非常优秀的加密结构,很多常用加的加密算法使用的都是分组算法,比如DES.SAFER表示的也是一种分组密码算法.一起来看看吧. SAFER简介 SAFER的 ...

  7. mupdf实用操作demo,C++操作PDF文件

    前文: 最近有个项目,需要读写PDF,本来想着挺简单的,读写PDF有那么多的库可以使用,唰唰的就完成了. 忘记了我写C++的,还是在国产系统上开发的. 所以一般的东西还不好使,因为项目需要在多个架构的 ...

  8. SpringBoot集成日志

    1.日志工厂 如果一个数据库,出现了异常,我们需要排错,日志就是最好的助手! 曾经:sout.debug 现在:日志工厂 在Mybatis中具体使用那一个日志实现,在设置设定 STDOUT_LOGGI ...

  9. Avalonia下拉可搜索树(TreeComboBox)

    1.需求分析   树形下拉的功能是ComboBox和TreeView的功能结合起来,再结合数据模板来实现这一功能. 2.代码实现   1.创建UserControl集成TreeView控件   2.将 ...

  10. 【直播预告】HarmonyOS极客松赋能直播第三期:一次开发多端部署与ArkTS卡片开发