Ubuntu x86-64汇编(3) 数值操作指令
指令标注 Operand Notation
指令instruction即运算operation, 操作的对象为一个或多个运算数operand, 使用不同的标记表示不同的约束
<reg> 寄存器, 运算数必须是一个寄存器. Register operand. The operand must be a register.
<reg8>, <reg16>, <reg32>, <reg64> 指定大小的寄存器运算数 Register operand with specific size requirement. For example, reg8 means a byte sized register (e.g., al, bl, etc.) only and reg32 means a double-word sized register (e.g., eax, ebx, etc.) only.
<dest> 目的运算数, 可以是寄存器或内存, 其内容在运算后将被运算结果覆盖, Destination operand. The operand may be a register or memory. Since it is a destination operand, the contents will be over written with the new result (based on the specific instruction).
<RXdest> 浮点的目的运算数, 必须是一个浮点寄存器. Floating point destination register operand. The operand must be a floating point register. Since it is a destination operand, the contents will be over written with the new result (based on the specific instruction).
<src> 来源运算数, 其值参与运算, 但是在指令操作后不会变化. Source operand. Operand value is unchanged after the instruction.
<imm> 立即数, Immediate value. May be specified in decimal, hex, octal, or binary.
<mem> 内存地址, 可以是一个变量名或者是一个间接引用(如内存地址). Memory location. May be a variable name or an indirect reference (i.e., a memory address).
<op> or <operand> 运算数, Operand, register or memory.
<op8>, <op16>, <op32>, <op64> 指定大小的运算数, Operand, register or memory, with specific size requirement. For example, op8 means a byte sized operand only and reg32 means a double-word sized operand only.
<label> 标记 Program label.
数据移动 Data Movement
mov <dest>, <src> mov ax, 42 mov cl, byte [bvar] mov dword [dVar], eax mov qword [qVar], rdx
将数值从来源寄存器或内存复制到目的寄存器或内存. 来源和目的必须是相同的尺寸(都是byte, 或都是word等). 目的不能是立即数, 来源和目的不能同时为内存. 如果要做内存到内存的复制, 需要两个指令.
特殊情况: 当目标寄存器为double word尺寸的整数寄存器时, 对应此double word的quadword上半部分会被全设为0.
例如当以下指令被执行时, rcx开始时被赋值为-1(全是0xF), 当100被复制到ecx后, rcx的上半部分会被0全部覆盖.
mov eax, 100 ; eax = 0x00000064 mov rcx, -1 ; rcx = 0xffffffffffffffff mov ecx, eax ; ecx = 0x00000064
指令说明
1. 复制来源运算数到目的运算数
2. 两个运算数不能同时为内存
3. 目的运算数不能为立即数
4. 对于来源和目的都是double word的运算数, 目的运算数对应的quadword的上半部分会被置零
地址和数值 Address vs Values
获取内存内容的唯一方法是通过方括号([]), 如果不使用方括号获取的是变量的地址. 例如
mov rax, qword [var1] ; 将var1的值复制到rax mov rax, var1 ; 将var1的地址复制到rax
因为忽略方括号不是语法错误, 编译器不会产生错误信息, 但是这容易让人产生疑惑. 另外, 变量的地址其实可以通过lea (load effective address)指令来获取, 用法举例如下
lea <reg64>, <mem> Place address of <mem> into reg64. lea rcx, byte [bvar] lea rsi, dword [dVar]
数值转换指令 Conversion Instructions
降级转换 Narrowing Conversion
降级转换用于将一个大尺寸的数值转换为较小类型的数值, 例如word到byte, 或double word到word
在降级转换中不需要特殊的指令, 寄存器或内存的低部可以直接访问, 例如如果数值50(0x32)存储在rax寄存器, 那么可以直接通过al访问获得小尺寸的数值
mov rax, 50 mov byte [bVal], al
这个例子因为50可以用一个byte表示, 所以al和rax的实际数值大小是一样的. 但是如果500(0x1f4)放置在rax寄存器, 那么寄存器依然可以访问, 但是结果就不一样了. bVal得到的值将会是0xf4
mov rax, 500 mov byte [bVal], al
升级转换 Widening Conversion
升级转换用于将小尺寸的数值转换为较大尺寸, 例如byte到word或word到double word. 因为尺寸扩大, 高地址部分的bit需要根据带符号或不带符号数做对应的填充. 所以做升级转换时, 必须先知道数值的符号属性, 才能对应使用正确的指令.
无符号转换
对于无符号的升级转换, 高地址部分需要置零. 例如转换值为50的al寄存器, 可以通过以下指令, 完成后rbx寄存器的值就是50
mov al, 50 mov rbx, 0 mov bl, al
这个通用的方式可以处理内存或其他寄存器. 另外有一个指令可以简化这个处理
movzx <dest>, <src> movzx <reg16>, <op8> movzx <reg32>, <op8> movzx <reg32>, <op16> movzx <reg64>, <op8> movzx <reg64>, <op16> ; Examples movzx cx, byte [bVar] movzx dx, al movzx ebx, word [wVar] movzx ebx, cx movzx rbx, cl movzx rbx, cx
这会将高地址部分的bit置零. 注意: movzx指令不允许使用quadword类型作为目的运算数. 因为之前提到过, mov使用double word作为运算数时, 会同时将其高地址部分的double word也置零.
movzx使用说明
1. 两个运算数不能同时为内存 both operands can not be memory.
2. 目的运算数不能为立即数, destination operands can not be an immediate.
3. 不允许使用立即数值
有符号数的转换
对于有符号数的升级转换, 根据原数值的符号, 高地址部分需要全部被填充为0或者1. 这个操作由一个符号数扩展操作完成. 其实就是将当前数的符号位的值, 直接填充到高地址位. 用于符号数扩展的有一系列的指令, 仅工作在A寄存器上, 用于将A寄存器的数值扩展到高地址位, 有时候会将数值扩展到D寄存器. 例如cwd指令, 用于将ax中的带符号数值, 扩展成double word尺寸的数值, 放入dx(上半部分)和ax(下半部分), 这种结果通常写作 dx:ax. 而cwde指令, 将ax中的带符号数值, 扩展成double word尺寸的数值并存储在eax寄存器.
常用指令用法及说明
cbw ;Convert byte in al into word in ax. Note, only works for al to ax register.
cwd ;Convert word in ax into double-word in dx:ax. Note, only works for ax to dx:ax registers.
cwde ;Convert word in ax into word into double-word in eax. Note, only works for ax to eax register.
cdq ;Convert double-word in eax into quadword in edx:eax. Note, only works for eax to edx:eax registers.
cdqe ;Convert double-word in eax into quadword in rax. Note, only works for rax register.
cqo ;Convert quadword in rax into word in double-quadword in rdx:rax. Note, only works for rax to rdx:rax registers.
movsx, movsxd ; Signed widening conversion (via sign extension).
Note 1, both operands can not be memory.
Note 2, destination operands can not be an immediate.
Note 3, immediate values not allowed.
Note 4, special instruction (movsxd) required for 32-bit to 64-bit signed extension.
movsx <dest>, <src> movsx <reg16>, <op8> movsx <reg32>, <op8> movsx <reg32>, <op16> movsx <reg64>, <op8> movsx <reg64>, <op16> movsxd <reg64>, <op32> movsx cx, byte [bVar] movsx dx, al movsx ebx, word [wVar] movsx ebx, cx movsxd rbx, dword [dVar]
整数运算指令 Integer Arithmetic Instructions
整数运算包括在整数上的加减乘除
加法 Addition
通用指令为
add <dest>, <src> add cx, word [wVvar] add rax, 42 add dword [dVar], eax add qword [qVar], 300
1. 结果将被放入目的运算数, 源运算数值不变
2. 两个运算数必须是同一尺寸
3. 目的运算数不能是立即数
4. 两个运算数不能同时为内存
代码例子
bNum1 db 42 bNum1 db 73 bAns db 0 wNum1 dw 4321 wNum2 dw 1234 wAns dw 0 dNum1 dd 42000 dNum2 dd 73000 dAns dd 0 qNum1 dq 42000000 qNum2 dq 73000000 qAns dq 0 ; bAns = bNum1 + bNum2 mov al, byte [bNum1] add al, byte [bNum2] mov byte [bAns], al ; wAns = wNum1 + wNum2 mov ax, word [wNum1] add ax, word [wNum2] mov word [wAns], ax ; dAns = dNum1 + dNum2 mov eax, dword [dNum1] add eax, dword [dNum2] mov dword [dAns], eax ; qAns = qNum1 + qNum2 mov rax, qword [qNum1] add rax, qword [qNum2] mov qword [qAns], raxInstruction Set Overview ; dAns = dNum1 + dNum2 mov eax, dword [dNum1] add eax, dword [dNum2] mov dword [dAns], eax ; qAns = qNum1 + qNum2 mov rax, qword [qNum1] add rax, qword [qNum2] mov qword [qAns], rax
在一些指令里, 包括上面的指令, 可以不指定具体的数值尺寸(例如byte, word, dword), 因为另一个运算数已经明确指定了尺寸的大小. 保留是为了编程的一致性, 便于阅读和维护.
除了加法指令, 还有一个自增指令, 可以使用于byte, word, dword, qword, 在作用于内存时, 要明确标明尺寸
inc <operand> inc word [wVvar] inc rax inc dword [dVar] inc qword [qVar]
代码例子
bNum db 42 wNum dw 4321 dNum dd 42000 qNum dq 42000000 inc rax ; rax = rax + 1 inc byte [bNum] ; bNum = bNum + 1 inc word [wNum] ; wNum = wNum + 1 inc dword [dNum] ; dNum = dNum + 1 inc qword [qNum] ; qNum = qNum + 1
带进位的加法 Addition with Carry
带进位的加法是一个特殊的加法指令, 会在运算中添加前一条指令产生的结果的进位数值. 这在进行大数(超过寄存器尺寸的数字)运算时非常方便.
例如进行如下的运算 17+25=42, 第一步进行7+5的运算, 第二步要紧接着第一步立即执行, 在执行加法时包含前一步产生的进位, 信息存储于rFlag, 通常的计算指令为
adc <dest>, <src> ;<dest> = <dest> + <src> + <carryBit> adc rcx, qword [dVvar1] adc rax, 42
代码例子
dquad1 ddq 0x1A000000000000000 dquad2 ddq 0x2C000000000000000 ;这些都是128bit数值, 会超过64-bit寄存器的尺寸 dqSum ddq 0 mov rax, qword [dquad1] mov rdx, qword [dquad1+8] add rax, qword [dquad2] adc rdx, qword [dquad2+8] mov qword [dqsum], rax mov qword [dqsum+8], rdx
减法 Subtraction
减法指令的格式
sub <dest>, <src> ;<dest> = <dest> <src> sub cx, word [wVvar] sub rax, 42 sub dword [dVar], eax sub qword [qVar], 300
src被减去dest的数值, 并且结果会保存到desc下. src的值不会改变. desc和src的数值尺寸必须一致, desc不能是立即数, desc和src不能同时为内存.
代码例子
bNum1 db 73 bNum2 db 42 bAns db 0 wNum1 dw 1234 wNum2 dw 4321 wAns dw 0 dNum1 dd 73000 dNum2 dd 42000 dAns dd 0 qNum1 dq 73000000 qNum2 dq 73000000 qAns dd 0 ; bAns = bNum1 bNum2 mov al, byte [bNum1] sub al, byte [bNum2] mov byte [bAns], al ; wAns = wNum1 – wNum2 mov ax, word [wNum1] sub ax, word [wNum2] mov word [wAns], ax ; dAns = dNum1 – dNum2 mov eax, dword [dNum1] sub eax, dword [dNum2] mov dword [dAns], eax ; qAns = qNum1 qNum2 mov rax, qword [qNum1] sub rax, qword [qNum2] mov qword [qAns], rax
自减指令
除了基本的减法指令外, 可以使用dec指令进行自减操作, 格式为
dec <operand> ;<operand> = <operand> 1 dec word [wVvar] dec rax dec dword [dVar] dec qword [qVar]
可以作用于byte, word, dword, qword, 如果使用于内存, 需要明确标明数值尺寸
代码例子
bNum db 42 wNum dw 4321 dNum dd 42000 qNum dq 42000000 dec rax ; rax = rax 1 dec byte [bNum] ; bNum = bNum 1 dec word [wNum] ; wNum = wNum 1 dec dword [dNum] ; dNum = dNum 1 dec qword [qNum] ; qNum = qNum 1
.
.