03. x86基础指令
【说明】
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基础指令的更多相关文章
- Linux基础(03)、常用基础指令和操作
目录 一.什么是Linux 二.常用基础指令 2.1.vi编辑 2.2.Linux文件类型 2.3.常用指令:增.删.改.查.其他 三.Linux的目录和权限 3.1.目录 3.2.权限 3.3.修改 ...
- Linux 基础指令初识
Linux 基础指令初识 01. ls 指令 语法: ls [选项] [目录或文件] 功能:对于目录,该命令列出该目录下的所有子目录与文件.对于文件,将列出文件名以及其他信息 -a 列出目录下的所有文 ...
- DB2的基础指令 学习笔记
=======DB2基础指令======= 1.打开数据库db2 connect to 数据库名;2.查看数据库中有哪些表db2 list tables ;3.查看数据库中的表结构db2 descri ...
- Linux基础指令
Linux基础指令 只写了最简单的一些文件操作,基本没有带参数 查看当前目录 pwd 跳转到某路径 cd 查看当前目录下的文件 ls ls -l // -l 查看详细信息 打开当前所在文件夹 open ...
- Vue的介绍及基础指令
一.什么是Vue Vue.js是一个渐进式 JavaScript 框架 通过对框架的了解与运用程度,来决定其在整个项目中的应用范围,最终可以独立以框架方式完成整个web前端项目 为什么要学习Vue 三 ...
- Python 基础指令
## Python 基础指令 ```Shell $ pip install ipython== # 安装指定版本的python第三方库 $ python --version #查看版本 $ which ...
- vue学前班004(基础指令与使用技巧)
我学vue 的最终目的是为了 做apicloud 和vue 的开发 作为配合apicloud的前端框架使用 所以项目用不到的会暂时不介绍. (强烈建议 官网案例走一遍) 基础指令的学习(结合aui ...
- x86 体系指令
FASM 第二章 - 2.1 x86 体系指令 Author: 徐艺波 From: xuyibo.org Updated: 2008-04-17 官方论坛 本站软件反馈.软件开发交流. ...
- linux基础指令以及权限管理
基础指令 #打印字符串 echo hello linux #将file1 和 file2粘合在一起,打印到标准输出流 cat file1 file2 标准输入输出 标准输入,stdin,即键盘.鼠标输 ...
- x86汇编指令脚本虚拟机
简介 这是一个可以直接解释执行从ida pro里面提取出来的x86汇编代码的虚拟机. 非常精简,整体架构上不能跟那些成熟的虚拟机相比,主要目标是够用.能用.轻量就行,如果觉得代码架构设计的不是很好的话 ...
随机推荐
- KingbaseES V8R6 集群运维案例 -- 归档失败导致 Switchover 失败
案例说明: KingbaseES V8R6集群,备库在执行'repmgr standby switchover'时,切换失败,出现以下故障: 经检查发现是主库归档配置错误,主库出现归档失败导致. 适用 ...
- JWT 安全令牌
1 package com.reliable.yang.utils; 2 3 import io.jsonwebtoken.Jwt; 4 import io.jsonwebtoken.JwtBuild ...
- 论文阅读小结(B/S和C/S结构)
论文阅读小结 一.B/S 和 C/S 软件体系结构选择 1) C/S . B/S 结构概述 C/S 结构,即 Client/Server (客户机 / 服务器 ), C/S 结构软件分为客户机和服务器 ...
- Python连接mysql数据库和关闭数据库的方法
1 import pymysql 2 def get_conn(): 3 """ 4 :return: 连接,游标 5 """ 6 # 创建 ...
- C++类型双关
Σ(っ °Д °;)っ #include<iostream> struct Entity { int x, y; int* GetPtr() { return &x; } /*En ...
- 密码学系列之:SAFER
密码学系列之:SAFER 简介 分组密码是一个非常优秀的加密结构,很多常用加的加密算法使用的都是分组算法,比如DES.SAFER表示的也是一种分组密码算法.一起来看看吧. SAFER简介 SAFER的 ...
- mupdf实用操作demo,C++操作PDF文件
前文: 最近有个项目,需要读写PDF,本来想着挺简单的,读写PDF有那么多的库可以使用,唰唰的就完成了. 忘记了我写C++的,还是在国产系统上开发的. 所以一般的东西还不好使,因为项目需要在多个架构的 ...
- SpringBoot集成日志
1.日志工厂 如果一个数据库,出现了异常,我们需要排错,日志就是最好的助手! 曾经:sout.debug 现在:日志工厂 在Mybatis中具体使用那一个日志实现,在设置设定 STDOUT_LOGGI ...
- Avalonia下拉可搜索树(TreeComboBox)
1.需求分析 树形下拉的功能是ComboBox和TreeView的功能结合起来,再结合数据模板来实现这一功能. 2.代码实现 1.创建UserControl集成TreeView控件 2.将 ...
- 【直播预告】HarmonyOS极客松赋能直播第三期:一次开发多端部署与ArkTS卡片开发