intel-32基础语法(中)
# 算术指令
# INC 指令
INC指令用于将操作数加1。它对可以在寄存器或内存中的单个操作数起作用。
INC指令具有以下语法-
INC destination
目标操作数可以是8位,16位或32位操作数。
例
INC EBX ; 32-bit 寄存器 自增1
INC DL ; 8-bit 寄存器 自增1
INC [count] ; 变量count 自增1
# DEC指令
DEC指令用于将操作数减1。它对可以在寄存器或内存中的单个操作数起作用。
DEC指令具有以下语法-
DEC destination
操作数目的地可以是8位,16位或32位操作数。
segment .data
count dw 0
value db 15
segment .text
inc [count]
dec [value]
mov ebx, count
inc word [ebx]
mov esi, value
dec byte [esi]
# ADD和SUB指令
ADD和SUB指令用于对字节,字和双字大小的二进制数据进行简单的加/减,即分别用于添加或减去8位,16位或32位操作数。
ADD和SUB指令具有以下语法-
ADD/SUB destination, source
ADD / SUB指令可以发生在-
- 寄存器 to 寄存器
- 内存 to 寄存器
- 寄存器 to 内存
- 寄存器 to 常量数据
- 内存 to 常量数据
但是,像其他指令一样,使用ADD / SUB指令也无法进行存储器到存储器的操作。ADD或SUB操作设置或清除溢出和进位标志。
下面的示例将要求用户输入两位数字,分别将这些数字存储在EAX和EBX寄存器中,将这些值相加,将结果存储在“ res ”存储位置中,最后显示结果。
SYS_EXIT equ 1
SYS_READ equ 3
SYS_WRITE equ 4
STDIN equ 0
STDOUT equ 1
segment .data
msg1 db "Enter a digit ", 0xA,0xD
len1 equ $- msg1
msg2 db "Please enter a second digit", 0xA,0xD
len2 equ $- msg2
msg3 db "The sum is: "
len3 equ $- msg3
segment .bss
num1 resb 2
num2 resb 2
res resb 1
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg1
mov edx, len1
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num1
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg2
mov edx, len2
int 0x80
mov eax, SYS_READ
mov ebx, STDIN
mov ecx, num2
mov edx, 2
int 0x80
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, msg3
mov edx, len3
int 0x80
; moving the first number to eax register and second number to ebx
; and subtracting ascii '0' to convert it into a decimal number
mov eax, [num1]
sub eax, '0'
mov ebx, [num2]
sub ebx, '0'
; add eax and ebx
add eax, ebx
; add '0' to to convert the sum from decimal to ASCII
add eax, '0'
; storing the sum in memory location res
mov [res], eax
; print the sum
mov eax, SYS_WRITE
mov ebx, STDOUT
mov ecx, res
mov edx, 1
int 0x80
exit:
mov eax, SYS_EXIT
xor ebx, ebx
int 0x80
编译并执行上述代码后,将产生以下结果-
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
具有硬编码变量的程序-
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov eax,'3'
sub eax, '0'
mov ebx, '4'
sub ebx, '0'
add eax, ebx
add eax, '0'
mov [sum], eax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,sum
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The sum is:", 0xA,0xD
len equ $ - msg
segment .bss
sum resb 1
编译并执行上述代码后,将产生以下结果-
The sum is:
7
⚠️ 认真阅读发现,前面我们示例都是个位数的运算,如果直接换成个位以上的数运算就会得到错误的结果,原因就是溢出,下面在 乘法 中介绍 “进位” 相关的处理
# MUL 乘法指令
1、mul乘法指令是无符号计算的,也就是不支持负数(imul支持负数)
2、mul是单指令,也就是后面只能跟一个参数(imul支持多参)
32 位模式下,MUL(无符号数乘法)指令有三种类型:
1、执行 8 位操作数与 AL 寄存器的乘法; 2、执行 16 位操作数与 AX 寄存器的乘法; 3、执行 32 位操作数与 EAX 寄存器的乘法。
乘积溢出
MUL 指令中的单操作数是乘数。乘积结果存放的空间是被乘数和乘数大小的两倍,因此不会发生溢岀
比如,两个8位二进制数的乘积不会超过16位。两个16位乘积不会超过32位等等(除法会溢出)
MUL无符号、单指令乘法表:
MUL: 无符号乘 ;影响 OF、CF 标志位 ;
指令格式: ;MUL r/m ;参数是乘数 ;
如果参数是 r8/m8, 将把 AL 做乘数, 结果放在 AX ;(8位)
如果参数是 r16/m16, 将把 AX 做乘数, 结果放在 EAX ;(16位)
如果参数是 r32/m32, 将把 EAX 做乘数, 结果放在 EDX:EAX (32位)
被乘数 | 乘数 | 乘积存放位置 | 解析 | 标记位 |
---|---|---|---|---|
AL | reg8/mem8 | AX | MUL操作数是8位寄存器,自动将AL当作被乘数。结果存放AX | |
AX | reg16/mem16 | DX:AX | MUL操作数是16位寄存器,自动将AX当作被乘数。结果的低位放在AX,高位在DX | CF = 1 标记位1表示DX有高位数据。CF = 0 表示DX没有高位数据,AX中已经显示全 |
EAX | reg16/mem32 | EDX:EAX | MUL操作数是32位寄存器,自动将EAX当作被乘数。结果的低位在EAX,高位在EDX |
# 1、8位汇编
8086芯片 intel-8 位汇编个位乘法
MUL(乘法)指令,在C语法中,使用 int a = 2 * 3; 能很方便的计算乘法,而在汇编中却没有这么方便,而只有学习汇编,你才能更深入理解 2 * 3 底层到底是如何实现的
示例:乘积小于10处理
;调用并输出
%macro run_out 0
mov eax, 4
mov ebx, 1
int 0x80
%endmacro
;退出
%macro return 0
mov eax,1
int 0x80
%endmacro
;乘法:乘积小于10处理 ( num1,num2)
%macro mul_oper 2
mov al, %1
sub al, '0'
mov bl, %2
sub bl, '0'
mul bl ;这里乘法指令是一种【隐含寻址】方式,下面备注介绍
add al, '0'
mov [res], al
mov ecx,res
mov edx,1
%endmacro
section .text
global _start
_start:
mul_oper '3','3' ;计算3*3结果
run_out
return
segment .bss
res resb 1
隐含寻址 有些指令的操作码不仅包含了操作的性质,还隐含了部分操作数的地址,如乘法指令MUL,在这条指令中只需指明乘数的地址,而被乘数以及乘积的地址是隐含且固定的。这种将一个操作数隐含在指令码中的寻址方式称为隐含寻址。
例如:
mul bl ;mul bl的功能把AL中的内容与BL的内容相乘,乘积送到AX寄存器中
;因此上面示例中我们将另一个送入al,然后会自动隐式从 al 取值
# IMUL 乘法指令
imul乘法指令支持符号运算,支持多参数
1、支持正负符号
2、支持2,3个参数,但是三个参数的最后一个必须是立即数
类似的:
; a 16 bit multiplication:
mov ax, [factor1]
mov bx, [factor2]
imul bx ; 32-bit result in DX:AX
; or imul word [factor2]
; a 32 bit multiplication:
mov eax, [factor1]
mov ebx, [factor2]
imul ebx ; 64-bit result in EDX:EAX
在386或更高版本上
mov ecx, [factor1]
imul ecx, [factor2] ; result in ecx, no other registers affected
imul ecx, ecx ; and square the result
----------------------------
movsx ecx, word [factor1]
movsx eax, word [factor2] ; sign-extend inputs to 32-bit
imul eax, ecx ; 32-bit multiply, result in EAX
imul eax, eax ; and square the result
----------------------------
imul cx, bx, 123 ; requires 186
----------------------------
imul ecx, ebx, -123 ; requires 386
# DIV / IDIV指令
# 示例:6/3=2
section .text
global _start ;must be declared for using gcc
_start: ;tell linker entry point
mov ax,'6'
sub ax, '0'
mov bl, '3'
sub bl, '0'
div bl
add ax, '0'
mov [res], ax
mov ecx,msg
mov edx, len
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov ecx,res
mov edx, 1
mov ebx,1 ;file descriptor (stdout)
mov eax,4 ;system call number (sys_write)
int 0x80 ;call kernel
mov eax,1 ;system call number (sys_exit)
int 0x80 ;call kernel
section .data
msg db "The result is:", 0xA,0xD
len equ $- msg
segment .bss
res resb 1
# DIV / IDIV (x86 - 32位)
# DIV 无符号除法
DIV 寄存器或内存(8位) DIV 寄存器或内存(16位) DIV 寄存器或内存(32位)
被除数 | 除数 | 商 | 余数 |
---|---|---|---|
AX | reg/mem8 | AL | AH |
DX:AX | reg/mem16 | AX | DX |
EDX:EAX | reg/mem32 | EAX | EDX |
比例
mov ax, 0043h ; 被除数
mov bl, 5 ; 除数
div bl ; AL = 08h, AH = O3h
⚠️
1、除数不能为0,需要判断
2、商或余数存储超过对应空间大小就会抛出异常
3、不占用的寄存器需要清空
DX 包含的是被除数的高位部分,因此在执行 DIV 指令之前,必须将其清零:
mov dx, 0 ; 清除被除数高16位
mov ax, 8003h ; 被除数的低16位
mov ex, 100h ; 除数
div ex ; AX = 0080h, DX = 0003h
# IDIV 有符号数除法
有符号除法与无符号几乎相同,但是在计算前必须对被除数进行符号扩展。
指令 | 全称 | 说明 |
---|---|---|
cbw | convert byte to word | 将AL的符号位扩展到AH |
cwd | convert word to doubleword | 将AX的符号位扩展到DX |
cdq | convert doubleword to quadword | 将EAX的符号位扩展到EDX |
.data
wordval sword -101
.code
main PROC
nop
mov dx, 0
mov ax, wordval
cwd; DX:AX=FFFF:FF9B,这样的才能保证被除数是负101,否则会被解释为正65435。
mov bx, 2
idiv bx
invoke ExitProcess,0
main ENDP
# 逻辑
常见逻辑指令 and、or、xor、not、test(测试计算数据)等等
# 逻辑指令
格式:
指令 | 格式 |
---|---|
AND | AND 操作数1,操作数2 |
OR | OR 操作数1,操作数2 |
XOR | XOR 操作数1,操作数2 |
TEST | TEST 操作数1,操作数2 |
NOT | NOT 操作数1 |
# AND 指令
实现按位与运算
1、将寄存器清空
AND AH, 00H ; 高位AH清空
2、检查奇偶数
AND AL, 01H ; 与上 0000 0001
格式示例:
and 【寄存器or内存】【寄存器or内存or立即数】 ✅ |
---|
and 【内存】【内存】 ❌ |
运算结果会修改对应的标志: |
CF,OF,PF,SF和ZF标志 |
以下程序说明了这一点-
section .text
global _start
_start:
mov ax, 6h ;设置数据6(二进制 0110 ),下面开始判断它的奇偶
and ax, 1 ;通过 0001 & 0110 = 0,此时标记位 ZF=0
jz even_f ; jz: ZF 等于 0 成立,跳转 偶数过程 even_f
;jnz odd_f jnz: ZF 不等于 0 成立,跳转 奇数过程 odd_f
jmp exit ; jmp 无条件跳转到 exit
even_f: ;偶数打印
mov eax, 4
mov ebx, 1
mov ecx, even_msg
mov edx, len1
int 0x80
odd_f: ;奇数打印
mov eax, 4
mov ebx, 1
mov ecx, odd_msg
mov edx, len2
int 0x80
exit: ;退出
mov eax,1
int 0x80
section .data
even_msg db '是偶数!'
len1 equ $ - even_msg
odd_msg db '是奇数!'
len2 equ $ - odd_msg
跳转相关的标志位: 【⚠️ 关于标志位详细介绍后面篇章有介绍】
11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|
OF | DF | IF | TF | SF | ZF | AF | PF | CF | |||
溢 出 | 方向 | 中断 | 调试 | 符 号 | 非零 | 未 用 | 辅 助 | 未 用 | 奇 偶 | 未 用 | 进 位 |
部分跳转指令和标记位关系:
程序控制类 | 无条件转移 | JMP | 不影响标志位 | |
---|---|---|---|---|
条 件 转 移 | 单个 标志 位 | JS/JNS | SF=1/0,则转移到目的地址 | |
JZ/ JNZ | ZF=1/0,则转移到目的地址 | |||
JP/JNP | PF=1/0,则转移到目的地址 | |||
JB/JNB | CF=1/0,则转移到目的地址 | |||
JO/JNO | OF=1/0,则转移到目的地址 | |||
若干 标志 位的 逻辑 组合 | JA | 两个无符号数比较,A>B |
# OR 指令
OR 指令用于通过执行按位或运算来支持逻辑表达式。
例如
4 or 3 结果 = 7
mov al, 4 ;0100
mov bl, 3 ;0011
or al, bl ;0100 | 0011 = 0111
add al, byte '0' ;格式化
# XOR 指令
异或运算:位不同为1,相同为0
例如,
4 xor 3 结果 = 7
mov al, 4 ;0100
mov bl, 3 ;0011
or al, bl ;0100 xor 0011 = 0111
add al, byte '0' ;格式化
清零操作:
将操作数与自身进行XOR会将操作数更改为0。这用于清除寄存器。
XOR EAX, EAX
# TEST 指令
TEST 指令与AND运算的工作原理相同,但与AND指令不同的是,它不会更改第一个操作数。
因此,可以使用test测试某个逻辑运算的结果:
AND AL, 01H ;会修改AL的值
TEST AL, 01H ;不会修改AL的值
# NOT 指令
NOT 指令实现按位NOT运算。NOT操作将操作数中的位取反。操作数可以在寄存器中,也可以在存储器中。
NOT 是单操作,接受一个参数
例如,
mov al, 4 ;0100
mov bl, 3 ;0011
or al, bl ;al = 0100 | 0011 = 0111
not al ;al = 0000 0111 取反 = 1111 1000
# 条件
前面示例已经介绍过一些条件指令,比如 jmp 无条件跳转
# 条件
条件令可以更改程序中的控制流,分为有条件和无条件:
无条件跳转 - JMP 就是最常见的,类似 go to
有条件跳转 - 通过条件判断是否或者如何处理控制流向
# 无条件跳转
语法格式
JMP label
JMP指令:直接跳转到指定的过程,不做任何判断
_start:
...
jmp exit ; jmp 无条件跳转到 exit
exit: ;退出
mov eax,1
int 0x80
# 条件跳转
如果在条件跳转中满足某些指定条件,则控制流将转移到目标指令。
指令 | 描述 | 标志测试 |
---|---|---|
JE/JZ | 跳转等于或跳转零 | ZF |
JNE/JNZ | 跳转不等于或跳转不为零 | ZF |
JG/JNLE | 跳转大于或跳转不小于/等于 | OF,SF,ZF |
JGE/JNL | 跳转大于/等于或不小于跳转 | OF,SF |
JL/JNGE | 跳转小于或不大于/等于 | OF,SF |
JLE/JNG | 跳少/等于或跳不大于 | OF,SF,ZF |
以下是对用于逻辑运算的无符号数据使用的条件跳转指令-
指令 | 描述 | 标志测试 |
---|---|---|
JE/JZ | 跳转等于或跳转零 | ZF |
JNE/JNZ | 跳转不等于或跳转不为零 | ZF |
JA/JNBE | 跳转向上或不低于/等于 | CF,ZF |
JAE/JNB | 高于/等于或不低于 | CF |
JB/JNAE | 跳到以下或跳到不高于/等于 | CF |
JBE/JNA | 跳到下面/等于或不跳到上方 | AF,CF |
以下条件跳转指令有特殊用途,并检查标志的值-
指令 | 描述 | 标志测试 |
---|---|---|
JXCZ | 如果CX为零则跳转 | 没有 |
JC | 如果携带则跳 | CF |
JNC | 如果不携带则跳转 | CF |
JO | 溢出时跳转 | OF |
JNO | 如果没有溢出则跳转 | OF |
JP/JPE | 跳校验或偶校验 | PF |
JNP/JPO | 跳转无奇偶校验或跳转奇偶校验 | PF |
JS | 跳跃符号(负值) | SF |
JNS | 跳转无符号(正值) | SF |
根据 CX、ECX 寄存器的值跳转:
JCXZ(CX 为 0 则跳转)、JECXZ(ECX 为 0 则跳转);
# 示例
CMP 比较是否相同的指令,通过相减等于0表示相同,否则不相同。
CMP AL, BL ;比较他们是否相同,相同 ZF=0
JE equal ;使用je:判断ZF标记等于0跳转
CMP AL, BH
JNE not_equal ;使用jne:判断ZF标记不等于0跳转
not_equal:
...
equal:
...
# 过程
可以理解为c 语言的函数
前面示例 jmp、je等条件指令 跳转的就是 过程或子例程
过程的语法:
proc_name: ;过程名称
procedure body ;过程主体
...
ret ;过程的结束由return语句指示
示例:
section .text
global _start
_start:
jmp name_1
name_2:
mov edx,4
mov ecx, names+8
mov ebx,1
mov eax,4
int 0x80
jmp exit
name_1:
mov edx,4
mov ecx, names+4
mov ebx,1
mov eax,4
int 0x80
jmp name_2
exit:
mov eax,1
int 0x80
section .data
global names
names:
DW 'Lucy', 'Andy', 'Pete'
# 循环
c语法中 while 等能很方便实现循环功能
在汇编中可以通过loop 和jmp 等实现
# 循环 loop
loop指令的格式是:loop 标号
通常我们用loop指令来实现循环功能,cx中存放循环次数。
CPU执行loop指令的时候,要进行两步操作:
1、(cx)=(cx)-1; 2、判断CX中的值,不为零则转至标号处执行程序,如果为零则向下执行。
示例:
循环判断5次,每次都对ax 加一,最后cx=0时循环结束,
mov ax,5
mov cx,5
loop_1:
inc ax
loop loop_1
1、标号:loop_1
在汇编语言中,标号代表一个地址,程序中有一个标号loop_1。它实际上标识了一个 地址,
这个地址处有一条指令:inc ax
2、loop loop_1:
CPU执行loop s的时候,要进行两步操作:
① (cx)=(cx)-1;(loop 自动计算的)
②判断cx中的值,不为0则转至标号s所标识的地址处执行,为零则执行下一条指令。
3、在这段程序中第一次执行loop loop_1前,cx=5,ax=5
第一次执行后,cx=4,ax=6
最后一次 cx=0,ax=10
4、用CX和loop指令相配合实现循环功能的3个要点:
1、在 CX中存放循环次数; 2、loop指令中的标号所标识地址要在前面; 3、要循环执行的程序段,要写在标号和loop指令的中间
# 例子
从9到0打印:
section .text
global _start
_start:
mov ecx,10 ;index 起始值
mov eax, '9' ;起始值
loop_dec:
mov [num], eax
mov eax, 4
mov ebx, 1
push ecx
mov ecx, num
mov edx, 1
int 0x80
mov eax, [num]
sub eax, '0'
dec eax ;递减
add eax, '0'
pop ecx
loop loop_dec ;循环,每次自动对 ecx 自减,直到0结束循环
mov eax,1
int 0x80
section .bss
num resb 1
结果:
9876543210