一、整数运算
汇编语言程序中执行数学操作的基本过程是整数运算。
1、加法
1)ADD指令
ADD指令用于把两个整数相加,ADD指令的格式如下:
add source,destination
/*其中source可以是立即值、内存位置或者寄存器。destination参数可以是寄存器或者内存位置中存储
的值(但是不能同时使用内存位置作为源和目标)。加法的结果存放在目标位置*/
.section .data
data:
.int 40
.section .text
.globl _start
_start:
movl $0, %eax
movl $0, %ebx
movl $0, %ecx
movb $20, %al
addb $10, %al
movsx %al, %ebx
addw %cx, %bx
movsx %bx, %ebx
movl $100, %edx
addl %edx, %edx
addl data, %eax
addl %eax, data
movl $1, %eax
movl $0, %ebx
int $0x80
.section .data
data:
.int -40
.section .text
.globl _start
_start:
movl $-10, %eax
movl $-200, %ebx
movl $80, %ecx
addl data, %eax
addl %ecx, %eax
addl %ebx, %eax
addl %eax, data
addl $210, data
movl $1, %eax
movl $0, %ebx
int $0x80
2)检测进位或者溢出情况
对于无符号整数,当二进制加法造成进位情况时(结果大于允许的最大值),进位标志
(carry flag)就会被设置为1.对于带符号整数,当出现溢出情况时(结果值小于允许的最小负
值或大于允许的最大正值),溢出标志(overflow flag)就会被设置为1。
下面是无符号数加法进位的例子:
.section .text
.globl _start
_start:
movl $0, %ebx
movb $190, %bl
movb $100, %al
addb %al, %bl
jc over
movl $1, %eax
int $0x80
over:
movl $1, %eax
movl $0, %ebx
int $0x80
下面是有符号数加法进位的例子:
.section .data
output:
.asciz "The result is %d\n"
.section .text
.globl _start
_start:
movl $-1590876934, %ebx
movl $-1259230143, %eax
addl %eax, %ebx
jo over
pushl %ebx
pushl $output
call printf
add $8, %esp
pushl $0
call exit
over:
pushl $0
pushl $output
call printf
add $8, %esp
pushl $0
call exit
3)ADC指令
如果必须处理非常大,不能存放在双字数据长度(ADD指令可以使用的最大长度)中的带
符号或这无符号整数,可以把值分割为多个双字数据元素,并且对每个元素执行独立的加法操
作。Intel提供的方案如下:
为了执行多组字节的加法操作,可以把多个ADC指令链接到一起。
ADC指令的格式如下:
adc source, destination
/*其中source可以是立即数或者8位、16位或者32位寄存器或内存位置,destination可以是
8位、16位或者32位寄存器或内存位置值(和ADD指令类似,source和destination不能
同时是内存位置)*/
.section .data
data1:
.quad 7252051615
data2:
.quad 5732348928
output:
.asciz "The result is %qd\n"
.section .text
.globl _start
_start:
movl data1, %ebx
movl data1+4, %eax
movl data2, %edx
movl data2+4, %ecx
addl %ebx, %edx
adcl %eax, %ecx
pushl %ecx
pushl %edx
push $output
call printf
addl $12, %esp
pushl $0
call exit
2、减法
1)SUB指令
SUB指令可以用于无符号和带符号整数。SUB指令的格式如下:
sub source, destination
/*其中从destination的值中减去source的值,结果存储在destination操作数的位置。*/
下面程序演示了SUB指令:
.section .data
data:
.int 40
.section .text
.globl _start
_start:
movl $0, %eax
movl $0, %ebx
movl $0, %ecx
movb $20, %al
subb $10, %al
movsx %al, %eax
movw $100, %cx
subw %cx, %bx
movsx %bx, %ebx
movl $100, %edx
subl %eax, %edx
subl data, %eax
movl $1, %eax
movl $0, %ebx
int $0x80
与SUB指令密切相关的是NEG指令。它生成值的补码。
2)减法操作中的进位和溢出
对于无符号整数。subtest2.s程序演示了这个问题:
.section .text
.globl _start
_start:
movl $5, %eax
movl $2, %ebx
subl %eax, %ebx
jc under
movl $1, %eax
int $0x80
under:
movl $1, %eax
movl $0, %ebx
int $0x80
使用进位标志确定无符号整数的减法产生负数结果的情况。
下面是带符号减法的例子:
.section .data
output:
.asciz "The result is %d\n"
.section .text
.globl _start
_start:
movl $-1590876934, %ebx
movl $1259230143, %eax
subl %eax, %ebx
jo over
pushl %ebx
pushl $output
call printf
add $8, %esp
pushl $0
call exit
over:
pushl $0
pushl $output
call printf
add $8, %esp
pushl $0
call exit
带符号减法超出范围则溢出标志被设为1。
3)SBB指令
SBB指令在多字节减法操作中利用进位和溢出标志实现跨越数据边界的借位特性。
格式如下:
sbb source, destination
/*其中进位位被添加到source值,然后从destination值中减去source值得到结果。*/
.section .data
data1:
.quad 7252051515
data2:
.quad 5732348928
output:
.asciz "The result is %qd\n"
.section .text
.globl _start
_start:
movl data1, %ebx
movl data1+4, %eax
movl data2, %edx
movl data2+4, %ecx
subl %ebx, %edx
sbbl %eax, %ecx
pushl %ecx
pushl %edx
push $output
call printf
add $12, %esp
pushl $0
call exit
3、递增和递减
INC和DEC指令用于对无符号整数值进行递增(INC)和递减(DEC)。这两个指令不会影响
进位标志,所以可以递增或递减计数器的值。
INC destination
DEC destination
/*其中destination可以是8位、16位或者32位寄存器,或者内存中的值 */
4、乘法
1)使用MUL进行无符号整数乘法
MUL指令用于两个无符号整数乘法。MUL指令的格式如下:
mul source
/*其中source可以是8位、16位或者32位寄存器或内存值。
目标位置总是使用EAX寄存器的某种形式,这取决于源操作数的长度。
MUL指令的目标位置必须是源操作数的两倍长度。*/
不过,当和16位源操作数相乘时,EAX寄存器不被用于保存在32位结果。为了向下兼容老式
的处理器。Intel使用DX:AX寄存器对保存32位乘法结果值。对于32位源值,目标位置使用64位
EDX:EAX寄存器对。
2)MUL指令范例
.section .data
data1:
.int 315814
data2:
.int 165432
result:
.quad 0
output:
.asciz "The result is %qd\n"
.section .text
.globl _start
_start:
movl data1, %eax
mull data2
movl %eax, result
movl %edx, result+4
pushl %edx
pushl %eax
pushl $output
call printf
add $12, %esp
pushl $0
call exit
3)使用IMUL进行带符号整数乘法
MUL指令只能用于无符号整数,而IMUL指令可以用于带符号和无符号整数。
IMUL有三种指令格式:
imul source
/*source操作数可以是8位、16位或者32位寄存器或内存中的值,它位于AL、AX或者EAX寄存器
(取决于源操作数的长度)中的隐含操作数相乘。然后,结果被存放到AX寄存器、DX:AX寄存器对
或者EDX:EAX寄存器对中*/
IMUL指令的第二种格式允许指定EAX寄存器之外的目标操作数:
imul source, destination
/*其中source可以是16位或者32位寄存器或内存中的值,destination必须是16位或者32
位通用寄存器。这种格式允许把乘法操作的结果存放到哪个位置*/
这种格式的缺陷在于乘法操作的结果被限制为单一目标寄存器的长度。使用这种格式时必须非
常小心,不要溢出目标寄存器。
IMUL指令的第三种格式允许指定3个操作数:
imul multiplier, source, destination
/*其中mutiplier是一个立即数,source是16位或者32位寄存器或内存中的值,destination必须是通用
寄存器。这种格式允许执行一个值(source)和一个带符号整数(mutiplier)的快速乘法操作,把结果
存储到通用寄存器(destination)中。*/
4)IMUL指令范例
.section .data
value1:
.int 10
value2:
.int -35
value3:
.int 400
.section .text
.globl _start
_start:
movl value1, %ebx
movl value2, %ecx
imull %ebx, %ecx
movl value3, %edx
imull $2, %edx, %eax
movl $1, %eax
movl $0, %ebx
int $0x80
5)检查溢出
.section .text
.globl _start
_start:
movw $680, %ax
movw $100, %cx
imulw %cx
jo over
movl $1, %eax
movl $0, %ebx
int $0x80
over:
movl $1, %eax
movl $1, %ebx
int $0x80
5、除法
1)无符号除法
DIV指令用于无符号整数的除法操作。格式如下:
div divisor
/*其divisor(除法)是隐含的被除数要除以的值,它可以是8位、16位或者32位寄存器或内存中的值。
在执行DIV指令之前,被除数必须已经存储到了AX寄存器(对于16位值)、DX:AX寄存器对(对于
32位值)或者EDX:EAX寄存器对(对于64位值)。*/
这就是说,当除法操作完成时,会丢失被除数,所以要确保这不是这个值的唯一拷贝。
.section .data
dividend:
.quad 8335
divisor:
.int 25
quotient:
.int 0
remainder:
.int 0
output:
.asciz "The quotient is %d, and the remainder is %d\n"
.section .text
.globl _start
_start:
movl dividend, %eax
movl dividend+4, %edx
divl divisor
movl %eax, quotient
movl %edx, remainder
pushl remainder
pushl quotient
pushl $output
call printf
add $12, %esp
pushl $0
call exit
2)带符号除法
IDIV用于带符号除法。只有一种格式:
idiv divisor
/*其中divisor可以是8位、16位或者32位寄存器或内存中的值*/
对于带符号整数的除法,余数的符号总是与被除数的符号相同。
3)检查除法错误
除以0或者商(或者余数)溢出目标寄存器。
二、整数运算
乘法和除法是处理器上最为耗费时间的两种指令。但是,可以运用一些技巧帮助加
快应用程序的执行速度。移位指令提供了基于2的乘方的乘法和除法操作的快速和容易的方
式。
1、移位乘法
为了使整数乘以2的乘方,必须把值向左移位。可以使用下面两个命令——SAL(向左算术移
位)和SHL(向左逻辑移位)。它们有3种不同的格式:
sal destination
sal %al, destination
sal shifter, destination
/*第一种格式把destination的值向左移1位,这等同于使值乘以2。
第二种格式把destination的值向左移动CL寄存器中指定的位数。
第三种格式把destination的值向左移动shifter值指定的位数。
*/
.section .data
value1:
.int 25
.section .text
.globl _start
_start:
movl $10, %ebx
sall %ebx
movb $2, %cl
sall %cl, %ebx
sall $2, %ebx
sall value1
sall $2, value1
movl $1, %eax
movl $0, %ebx
int $0x80
2、移位除法
SHR指令清空移位造成的空位,所以它只能用于对无符号整数进行移位操作。SAR指令根据
整数的符号位,要么清空,要么设置移位造成的空位。对于负数,空位被设置为1,但是对于整数
它们被设置为0。
3、循环移位
循环移位指令执行的功能和移位指令一样,只不过溢出位被存放回值的另一端,而不是被丢
弃。
三、十进制运算
1、不打包BCD的运算
用于把二进制运算结果转换为不打包BCD格式的指令有4条:
- AAA:调整加法操作的结果
- AAS:调整减法操作的结果
- AAM:调整乘法操作的结果
- AAD:准备除法操作的被除数
这些指令必须和一般的无符号整数指令ADD、ADC、SUB、SBB、MUL和DIV组
合在一起使用。AAA、AAS和AAM指令在它们各自的操作之后使用,把二进制结果
转换为不打包BCD格式。
这些指令都使用一个隐含的操作数——AL寄存器。AAA、AAS和AAM指令假设
前一个操作的结果放在AL寄存器中,并且把这个值转换为不打包BCD格式。AAD指
令假设被除数以不打包BCD格式存放在AX寄存器中,并且把它转换为DIV指令要处理
的二进制格式。结果是正确的一个不打包BCD值,AL寄存器中的商和AH寄存器中的
余数。
.section .data
value1:
.byte 0x05, 0x02, 0x01, 0x08, 0x02
value2:
.byte 0x03, 0x03, 0x09, 0x02, 0x05
.section .bss
.lcomm sum, 6
.section .text
.globl _start
_start:
xor %edi, %edi
movl $5, %ecx
clc
loop1:
movb value1( , %edi, 1 ), %al
adcb value2( , %edi, 1 ), %al
aaa
movb %al, sum( , %edi, 1 )
inc %edi
loop loop1
adcb $0, sum( , %edi, 4 )
movl $1, %eax
movl $0, %ebx
int $0x80
2、打包BCD的运算
处理打包BCD值时,可用的指令只有2条:
- DAA:调整ADD或者ADC指令的结果
- DAS:调整SUB或者SBB指令的结果
.section .data
value1:
.byte 0x25, 0x81, 0x02
value2:
.byte 0x33, 0x29, 0x05
.section .bss
.lcomm result, 4
.section .text
.globl _start
_start:
xor %edi, %edi
movl $3, %ecx
loop1:
movb value2( , %edi, 1 ), %al
sbbb value1( , %edi, 1 ), %al
das
movb %al, result( , %edi, 1 )
inc %edi
loop loop1
sbbb $0, result( , %edi, 4 )
movl $1, %eax
movl $0, %ebx
int $0x80
四、逻辑操作
1、布尔逻辑
布尔操作如下:
- AND
- NOT
- OR
- XOR
AND、OR和XOR指令使用相同的格式:
and source, destination
/*其中source可以是8位、16位或者32位立即值,寄存器或内存中的值,destination可以是8位
、16位或者32位寄存器或内存中的值*/
2、位测试
TEST指令例子:
.section .data
output_cpuid:
.asciz "This processor supports the CPUID instruction\n"
output_nocpuid:
.asciz "This processor does not support the CPUID instruction\n"
.section .text
.globl _start
_start:
pushfl #把EFLAGS寄存器的值保存到堆栈顶部
popl %eax #把EFLAGS的值读到EAX寄存器中
movl %eax, %edx
xor $0x00200000, %eax
pushl %eax
popfl #把EAX的值储存在EFLAGS中
pushfl
popl %eax
xor %edx, %eax
test $0x00200000, %eax
jnz cpuid
pushl $output_nocpuid
call printf
add $4, %esp
pushl $0
call exit
cpuid:
pushl $output_cpuid
call printf
add $4, %esp
pushl $0
call exit