Ubuntu x86-64汇编(4) 数值操作指令
整数乘法指令 Integer Multiplication
对于有符号数的乘法有特殊的规则, 因此无符号数乘法和有符号数乘法对应着不同的指令mul和imul. 乘法会产生两倍尺寸的数值结果, 即两个n-bit数相乘会产生2n-bit的数. 两个8bit数相乘会产生16bit的数. 对于乘法指令有许多变种, 例如对于带符号乘法, 一些指令能将结果裁剪至和源数值一样的尺寸.
无符号的乘法 Unsigned Multiplication
通常这种乘法的格式为
mul <src> mul <op8> mul <op16> mul <op32> mul <op64> mul word [wVvar] mul al mul dword [dVar] mul qword [qVar]
src运算数必须是寄存器或内存地址, 不能为立即数. 对于这样的单运算数乘法指令, 在执行时, 会使用A寄存器中对应尺寸的数值(al/ax/eax/rax). 分别对应8bit, 16bit, 32bit, 64bit尺寸的运算数. 乘法的结果会被放置在A寄存器以及D寄存器中. 下面的表格显示了不同尺寸下的无符号乘法及其结果
Byte: ax = al * <src>
Word: dx:ax = ax * <src>
Double: edx:eax = eax * <src>
Quad: rdx:rax = rax * <src>
这样的结果位置组合, 是由于要向下兼容早先的版本, 会让人有些困惑.
代码例子
bNumA db 42 bNumB db 73 wAns dw 0 wAns1 dw 0 wNumA dw 4321 wNumB dw 1234 dAns2 dd 0 dNumA dd 42000 dNumB dd 73000 qAns3 dq 0 qNumA dq 420000 qNumB dq 730000 dqAns4 ddq 0 ; wAns = bNumA^2 or bNumA squared mov al, byte [bNumA] mul al ; result in ax mov word [wAns], ax ; wAns1 = bNumA * bNumB mov al, byte [bNumA] mul byte [bNumB] ; result in ax mov word [wAns1], ax ; dAns1 = wNumA * wNumB mov ax, word [wNumA] mul word [wNumB] ; result in dx:ax mov word [dAns1], ax mov word [dAns1+2], dx ; qAns3 = dNumA * dNumB mov eax, dword [dNumA] mul dword [dNumB] ; result in edx:eax mov dword [qAns3], eax mov dword [qAns3+4], edx ; dqAns4 = qNumA * qNumB mov rax, qword [qNumA] mul qword [qNumB] ; result in rdx:rax mov qword [dqAns4], rax mov qword [dqAns4+8], rdx
带符号数乘法 Signed Multiplication
带符号数的乘法允许更大范围的运算数和尺寸. 通常的格式为
imul <src> imul <dest>, <src/imm32> imul <dest>, <src>, <imm32> imul <op8> imul <op16> imul <op32> imul <op64> imul <reg16>, <op16/imm> imul <reg32>, <op32/imm> imul <reg64>, <op64/imm> imul <reg16>, <op16>, <imm> imul <reg32>, <op32>, <imm> imul <reg64>, <op64>, <imm> imul ax, 17 imul al imul ebx, dword [dVar] imul rbx, dword [dVar], 791 imul rcx, qword [qVar] imul qword [qVar]
所有的格式中, dest运算数都必须是寄存器, 并且所有的运算数都不能为byte. 当使用单个运算数时, imul的格式和mul是一致的. 当使用两个运算数时, 相乘的是src和dest运算数, 并且结果会存放在desc运算数, 覆盖原值. 立即数的尺寸受src运算数的限制, 可以是double word(32bit), 甚至quadword(64bit), 运算结果被裁剪为dest的尺寸后存放在desc. desc不支持8bit的运算数.
代码例子
wNumA dw 1200 wNumB dw 2000 wAns1 dw 0 wAns2 dw 0 dNumA dd 42000 dNumB dd 13000 dAns1 dd 0 dAns2 dd 0 qNumA dq 120000 qNumB dq 230000 qAns1 dq 0 qAns2 dq 0 ; wAns1 = wNumA * 13 mov ax, word [wNumA] imul ax, 13 ; result in ax mov word [wAns1], ax ; wAns2 = wNumA * wNumB mov ax, word [wNumA] imul ax, word [wNumB] ; result in ax mov word [wAns2], ax ; dAns1 = dNumA * 113 mov eax, dword [dNumA] imul eax, 113 ; result in eax mov dword [dAns1], eax ; dAns2 = dNumA * dNumB mov eax, dword [dNumA] imul eax, dword [dNumB] ; result in eax mov dword [dAns2], eax ; qAns1 = qNumA * 7096 mov rax, qword [qNumA] imul rax, 7096 ; result in rax mov qword [qAns1], rax ; qAns2 = qNumA * qNumB mov rax, qword [qNumA] imul rax, qword [qNumB] ; result in rax mov qword [qAns2], rax ; 计算 qAns1 = qNumA * 7096 的另一种方式是: ; qAns1 = qNumA * 7096 mov rcx, qword [qNumA] imul rbx, rcx, 7096 ; result in rax mov qword [qAns1], rax
.
整数除法指令 Integer Division
对有符号数和无符号数, 也有不同的除法指令div和idiv. 指令格式
div <src> div <op8> div <op16> div <op32> div <op64> div word [wVvar] div bl div dword [dVar] div qword [qVar] idiv <src> idiv <op8> idiv <op16> idiv <op32> idiv <op64> idiv word [wVvar] idiv bl idiv dword [dVar] idiv qword [qVar]
因为 dividend / divisor = quotient, 除法要求被除数的尺寸大于除数. 例如要除以8bit除数, 被除数必须为16bit. 和乘法一样, 运算中需要结合使用A和D寄存器, 这也是为了与旧的架构相兼容. 除法中A和D寄存器的组合如下:
Byte Divide: ax for 16-bits
Word Divide: dx:ax for 32-bits
Double-word divide: edx:eax for 64-bits
Quadword Divide: rdx:rax for 128-bits
Byte: al = ax / <src> , rem in ah
Word: ax = dx:ax / <src>, rem in dx
Double: eax = eax / <src>, rem in edx
Quad: rax = rax / <src>, rem in rdx
正确地设置被除数是问题的关键, 对于word, double word, quadword类型的被除数, 都需要用到A和D寄存器. 如果之前刚刚进行过乘法运算, 那么A和D就正好已经被赋值. 否则当前数值就需要升级成较大尺寸并将上半部分放到D寄存器. 对于无符号数, 高地址部分总是全为零, 而对于有符号数, 则需要根据之前的说明对高地址部分进行扩展.出书可以是一个内存地址或寄存器, 但是不能为立即数. 另外, 结果将被放置在A寄存器(al/ax/eax/rax), 余数部分会被放置在ah, dx, edx, 或者rdx 寄存器. 对更大尺寸的被除数的使用与乘法指令一样, 对于简单的除法, 要使用合适的转换保证数值被正确的初始化, 对于无符号数相除, 要将高地址部分置零, 对于有符号数相除, 要根据实际情况置零或置一. 零作为除数参与除法运算将会导致程序崩溃, 要避免零除数.
代码例子
bNumA db 63 bNumB db 17 bNumC db 5 bAns1 db 0 bAns2 db 0 bRem2 db 0 bAns3 db 0 wNumA dw 4321 wNumB dw 1234 wNumC dw 167 wAns1 dw 0 wAns2 dw 0 wRem2 dw 0 wAns3 dw 0 dNumA dd 42000 dNumB dd 3157 dNumC dd 293 dAns1 dd 0 dAns2 dd 0 dRem2 dd 0 dAns3 dd 0 qNumA dq 730000 qNumB dq 13456 qNumC dq 1279 qAns1 dq 0 qAns2 dq 0 qRem2 dq 0 qAns3 dq 0 ; ---- ; example byte operations, unsigned ; bAns1 = bNumA / 3 (unsigned) mov al, byte [bNumA] mov ah, 0 mov bl, 3 div bl ; al = ax / 3 mov byte [bAns1], al ; bAns2 = bNumA / bNumB (unsigned) mov ax, 0 mov al, byte [bNumA] div byte [bNumB] ; al = ax / bNumB mov byte [bAns2], al mov byte [bRem2], ah ; ah = ax % bNumB ; bAns3 = (bNumA * bNumC) / bNumB (unsigned) mov al, byte [bNumA] mul byte [bNumC] ; result in al div byte [bNumB] ; al = ax / bNumB mov byte [bAns3], al ; ---- ; example word operations, unsigned ; wAns1 = wNumA / 5 (unsigned) mov ax, word [wNumA] mov dx, 0 mov bx, 5 div bx ; ax = dx:ax / 5 mov word [wAns1], ax ; wAns2 = wNumA / wNumB (unsigned) mov dx, 0 mov ax, word [wNumA] div word [wNumB] ; ax = dx:ax / wNumB mov word [wAns2], ax mov word [wRem2], dx ; wAns3 = (wNumA * wNumC) / wNumB (unsigned) mov ax, word [wNumA] mul word [wNumC] ; result in dx:ax div word [wNumB] ; ax = dx:ax / wNumB mov word [wAns3], ax ; ---- ; example doubleword operations, signed ; dAns1 = dNumA / 7 (signed) mov eax, dword [dNumA] cdq ; eax → edx:eax mov ebx, 7 idiv ebx ; eax = edx:eax / 7 mov dword [dAns1], eax ; dAns2 = dNumA / dNumB (signed) mov eax, dword [dNumA] cdq ; eax → edx:eax idiv dword [dNumB] ; eax = edx:eax/dNumB mov dword [dAns2], eax mov dword [dRem2], edx ; edx = edx:eax%dNumB ; dAns3 = (dNumA * dNumC) / dNumB (signed) mov eax, dword [dNumA] imul dword [dNumC] ; result in edx:eax idiv dword [dNumB] ; eax = edx:eax/dNumB mov dword [dAns3], eax ; ---- ; example quadword operations, signed ; qAns1 = qNumA / 9 (signed) mov rax, qword [qNumA] cqo ; rax → rdx:rax mov rbx, 9 idiv rbx ; eax = edx:eax / 9 mov qword [qAns1], rax ; qAns2 = qNumA / qNumB (signed) mov rax, qword [qNumA] cqo ; rax → rdx:rax idiv qword [qNumB] ; rax = rdx:rax/qNumB mov qword [qAns2], rax mov qword [qRem2], rdx ; rdx = rdx:rax%qNumB ; qAns3 = (qNumA * qNumC) / qNumB (signed) mov rax, qword [qNumA] imul qword [qNumC] ; result in rdx:rax idiv qword [qNumB] ; rax = rdx:rax/qNumB mov qword [qAns3], rax
逻辑指令 Logical Instructions
基础逻辑指令
与操作 and <dest>, <src> and ax, bx and rcx, rdx and eax, dword [dNum] and qword [qNum], rdx 或操作 or <dest>, <src> or ax, bx or rcx, rdx or eax, dword [dNum] or qword [qNum], rdx 或非操作 xor <dest>, <src> xor ax, bx xor rcx, rdx xor eax, dword [dNum] xor qword [qNum], rdx 非操作 not <op> not bx not rdx not dword [dNum] not qword [qNum]
移位操作
逻辑左移, 从右边补零. <imm>和cl 取值必须在1~64之间, <dest>不能是立即数
shl <dest>, <imm> shl <dest>, cl shl ax, 8 shl rcx, 32 shl eax, cl shl qword [qNum], cl
逻辑右移, 从左边补零. <imm>和cl 取值必须在1~64之间, <dest>不能是立即数
shr <dest>, <imm> shr <dest>, cl shr ax, 8 shr rcx, 32 shr eax, cl shr qword [qNum], cl
算术移位将运算数作为一个有符号数进行处理并保留符号. 但是算数移位使用的是四舍五入而标准除法使用的是裁剪, 所以不能用算术移位代替除法指令
算术左移
sal <dest>, <imm> sal <dest>, cl sal ax, 8 sal rcx, 32 sal eax, cl sal qword [qNum], cl
算术右移
sar <dest>, <imm> sar <dest>, cl sar ax, 8 sar rcx, 32 sar eax, cl sar qword [qNum], cl
循环移位指令只是移位方向不同,它们移出的位不仅要进入CF,而且还要填补空出的位
循环左移
rol <dest>, <imm> rol <dest>, cl rol ax, 8 rol rcx, 32 rol eax, cl rol qword [qNum], cl
循环右移
ror <dest>, <imm> ror <dest>, cl ror ax, 8 ror rcx, 32 ror eax, cl ror qword [qNum], cl
控制指令 Control Instructions
.
.