汇编语言分位数和标准,位数是根据cpu寄存器的位数发展而变化,而标准主要有两类:
- AT&T 标准, 主要用于UNIX和 类UNIX系统
- Intel 标准
本文是基于16位因特尔标准
汇编语言基础
三种基本的汇编语言成员:
-
指令:
[name:] mnemonic [[dest][,src]][;comment]
-
伪指令
[name] directive [[operand][,operand,...]][;comment]
macros 宏
命名规则和基本的c/c++命名规则差不多,主要关注以下可以使用哪些符号
- ? _ @ $ 可以使用
- 长度不要超过31个字符。 第一个字符不能是数字。
数据类型:
类型 | 简写 | 大小 |
---|---|---|
BYTE | DB | 1B |
WORD | DW | 2B |
DWORD | DD | 4B |
FWORD | DF | 6B |
QWORD | DQ | 8B |
TBYTE | DT | 10B |
REAL4 | DD | 4B |
REAL8 | DQ | 8B |
REAL10 | DT | 10B |
汇编语言编译过程
src.asm ---(汇编器)---- c.obj ---(连接器)--- xx.exe
伪指令
变量声明
label WORD [值,‘?’]
算了,还是直接举例子把:
a BYTE 'A'
b WORD 1111H
C BYTE 128
D BYTE 1,2,3,4,5 ; 数组
E BYTE "i LOVE U",'$' ;字符串结尾必须有'$'
F BYTE 'I LOVE "U..zcdsadfasdft" '
DUP
a BYTE 30 DUP(5) ;重复 30个5
OFFSET 和 SEG
这是两个比较常用的,
MOV AX, OFFSET VAR1
MOV BX, SEG VAR1
一个是取偏移地址,一个是取段地址。
算数运算
+,-,*,/ 。。。 比较弱的功能,好像只支持 常数之间的运算和变量之间的地址运算,如:
MOV DX, BLOCK +(6-1)*2
MOV CX, (VAR1-VAR2)/2
PTR
常用,用于类型转换
MOV AL, BYTE PTR var
MOV BYTE PTR [BX],10
SHORT
后面跳转会用到,不常用
EQU 和 =
都是复制,只不过EQU 类似于 c系语言的宏定义,不可改变, =类似于=,可以改变。
ORG
常用,用于将当前程序指针指向某一个位置,与$搭配使用。
ORG expression
ORG $ + expression ; $ 代表当前地址,
ORG $+1 ; 意味着跳转到下一行, 常用于 对齐存储方式。。。。。
汇编代码框架
.8086
.model small
.stack
.data
;你的变量声明
.code
main:
;...
end main
.Other 段
汇编指令
转移指令
MOV 指令
MOV DST, SRC
将数据从 SRC 拷贝到 DST。MOV的使用有以下限制:
- SRC 和 DST 不能都在主存或者都是段寄存器。
- 不能将立即数移动到段寄存器(可以用通用寄存器间接来)
- 不能对 FLAG 寄存器做此操作。
- 不能将任何数据移动到CS和IP寄存器。
- 不能再不同位长的寄存器之间移动。
PUSH 和 POP
PUSH SRC
将数据压入栈,操作步骤一般为:
- SP上移,即 SP ← SP -2
- ((SP)+1,(SP) ) ← (SRC) 这里是小端对齐, 高位存在高地址。
另外,不要忘记, 可以将 PUSH 的对象可以为: 立即数,内存数,寄存器数(包括FLAG寄存器)都可以。
POP DST
将栈顶元素弹出到DST。操作顺序:
- DST ← ((SP+1),(SP))
- SP ← (SP) + 2
需要注意的是,DST不可以是CS寄存器。
加载地址指令
加载有效地址
LEA reg16/32, mem8/16/32
LEA: load effective address 加载有效地址到 寄存器。 有效地址就是偏移地址。例如:
if (DS) = 2000H, (BX) = 1234H. 则 LEA DI, [BX] 的执行结果为:
DI ← 1234H
if Symbol variable BUFFER is in 2000H:1234H. 则 LEA DI, BUFFER 的执行结果为:
DI ← 1234H
加载段地址 + 有效地址
LDS reg, mem
LES reg, mem
LFS reg, mem
LGS reg, mem
LSS reg, mem
这是 LEA 的 升级版本,但是, 千万要注意的是, 这些指令不会做像 LEA 一样的翻译的效果,而是直接将 mem内的数据载入 段寄存器和reg。例如:
if (DS) = 2000H, (BX) = 1000H。 内存中 (21000H,21001H,21002H,21003H)= (45H,D6H,00H,50H)
那么, LDS DI,[BX] 的效果为:
DI ← D645H
DS ← 5000H
交换指令
XCHG A , B
交换两者内部的值,值得注意的是:
- 两者长度要一致。
- 两者不能同时是段寄存器或者同时为内存数。
LAHF 和 SAHF
LAHF
Transfers Flag寄存器最右面的8位到AH寄存器。
SAHF
Transfers AH寄存器到Flag寄存器的最右面8位。
查表指令
XLAT
将AL 寄存器的值转换成 一个表里面的值。
表的偏移地址必须事先存在 BX 寄存器中。并且表元素的类型是byte。例如:
MOV BX, OFFSET TABLE
MOV AL, 4
XLAT
执行结果: AL ← 34H 34H 是表中第四个元素,表从0开始计数。
I/O 指令
IN AL/AX , I/O DEV
OUT I/O DEV, AL/AX
与I/O 设备的交互。 IO设备的寻址有两种方式存在:
- 固定端口寻址。 有的IO端口有固定的8bits的 端口地址。例如 IN AL, 52H
- 变动端口寻址。 端口地址存在DX。 例如 OUT DX, AL
算数指令
加法指令
ADD DST, SRC
DST ← (DST) + (SRC) 需要注意的是:
- 两个操作数位数要相同。
- 可以两个寄存器,也可以是寄存器和立即数,寄存器和内存数,内存数和立即数。 但不能是两个内存数。
- 更值得注意的是: 它的执行会影响:CF, SF, OF, PF ,ZF 和 AF。
ADC DST, SRC
DST ← (DST) + (SRC) + Carry。 考虑进位的加法。 同样会影响FLAG寄存器。
INC DST
DST ← (DST) + 1
需要注意:
- 也可以是内存数。
- 会对FLAG产生影响,但是! 不会对CF产生影响!!!!!!!!
减法指令
SUB DST, SRC
DST ← (DST) - (SRC) 需要注意的和 ADD 相同。
SBB DST, SRC
DST ← (DST) - (SRC) - Carry。
DEC DST
自减。 不会对CF产生影响。
NEG DST
DST ← 0 - (DST) 效果, 取反并加一。 对于负数来说, 就是求补。
CMP DST, SRC
通过 (DST) - (SRC) 来比较 两个操作数。 但是, DST 不会被改变!!!!!!!!! 通过对FLAG的影响来得到结果。
无符号数结果的判断:
- ZF = 1. 相等。
- CF = 0. DST 大于等于 SRC。
- CF = 1. DST 小于 SRC。
有符号数结果的判断:
- ZF = 1. 相等。
- SF = OF, DST 大于等于 SRC。
- SF != OF, DST < SRC。
乘除法指令
MUL SRC ; 无符号数
IMUL SRC ; 有符号数
- SRC 可以是 寄存器数也可以是内存数。 被乘数存在 AX/AL寄存器当中。
- 对于8位数。 AX ← AL * SRC
- 对于16位数。DX : AX ← AX * SRC
需要注意的是:
- 结果的位数总是操作数的两倍
- 这个指令对 CF 和 OF 有影响。
- CF = OF = 1. 表明 结果的 高一半不是0
- CF = OF = 0. 表明 结果的 高一半 是 0
- 不会改变其他寄存器。
IMUL REG, SRC
IMUL REG, SRC, IMM
REG ← (REG) * (SRC) - (IMM) 注意, 在这里, 它们的尺寸是相同的。 16/32
DIV SRC
IDIV SRC
同MUL。被除数在AX里面。需要注意:
- 不要除0
- 商太大放不下。
- 会改变FLAG的值
- 商的位数是被除数位数的一半。 商存在低一半。 余数存在高一半。
还有就是不能乘除立即数。可以加减立即数。
符号扩展指令
CBW ; AL to AX
CWD ; AX to DX:AX
CWDE ; AX to EAX
CDQ ; EAX to EDX : EAX
BCD 码的调节
- 加法结束后,1010B ~ 1111B 对于BCD码来说是没有意义的。因此需要调节。 即进位。
AAA
例子:
MOV AL, 9H
MOV BL, 4H
ADD AL, BL ; overflow
AAA ; adjust to AH
所以最后, 整个结果扩展到 AH:AL。 并且改变 CF 和 AF 【= 1】(adjust flag 第三位有无进位)的值。
- 减法结束后,的调节。
AAS
同上, AF = CF = 1. 且 将正确结果整合到了AL。
- BCD 乘法结束后的调节
AAM ; 和AAA 差不多
- 除法结束后调节
AAD ; 和 AAM 差不多
以上都是unpacked BCD 码的调节。 packed BCD 码如有需要,自查资料。
逻辑指令
与或非
AND DST,SRC
OR DST,SRC
XOR DST,SRC ; 异或
需要注意如下:
- 结果存在DST当中
- DST和SRC 都可以是MEM, 但不可同时是MEM。
它们的使用技巧:
- AND 转换 ASCII 到 digit integer
- OR 转换 digit integer 到 ASCII
- XOR 某一位取反 例如 : XOR CL, 00100000B . 因为0/1 与0异或不会改变。 0/1 异或会取反。
TEST 指令
和 AND 的效果一样,但是不会改变 DST 的值。
注意:
- AND, OR , XOR, TEST 会改变 FLAG 的值。
- CF = 0
- OF = 0
- AF undefined
- PF, SF, ZF 和结果有关。
NOT 指令
求反指令
MOV AL, 1
NOT AL 各个位 取反
INC AL ; 取反加一?? 还记不记得 NEG
SHIFT 和 ROTATE
SHL DST, CNT ; 逻辑左移
SHR DST, CNT
SAL DST, CNT ; 算数左移
SAR DST, CNT
值得注意的是:
- 逻辑左移和算数左移没有什么区别
- 逻辑右移左面补0,算数右移左面补符(最高位)。
- DST 可以是MEM数或者寄存器。 CNT 是8位立即数,1,或者CL
- SHIFT 操作将会改变 CF, OF ZF ,PF, AF。 注意OF, 当shift 一位的时候,如果结果的符号和原来一样,OF = 0. 否则为1.
ROL DST, CNT
ROR DST, CNT
RCL DST, CNT ; 通过 CF 来移位
RCR DST, CNT
注意:
- ROL, ROR 只是将出的位,保留到CF一份, CF不会对结果产生影响
- RCL, RCR 是移动到CF, 在将原来CF的值填到另一端。
- 只会影响CF, OF(至对于一位ROTATE,如果符号变化,OF=1)
跳转指令
无条件跳转
JMP SHORT OPR ; OPR 可以是一个label 例如
JMP SHORT ADDT
.....
ADDT: MOV AL , AH
SHORT 的范围是同一个段的8位, +127 ~ -128
JMP OPR ; 16位。 +32767~-32768 当前段
JMP FAR PTR OPR ; can be another 段 32bits
SO....
JMP WORD PTR OPR ; 16 near jmp
JMP DWORD PTR OPR ; 32 FAR JMP
JMP REG16/32
条件跳转
跳转指令 | 跳转条件 |
---|---|
JE / JNE | 是否相等(其实考察的是ZF) |
JAE/ JGE | A |
JBE/ JLE | A <= B |
JC/JZ/JS/JO/JP | 当相应的CF, ZF, SF, OF, PF 寄存器为1时跳转 |
跳转表
传言,switch 于 else if 的区别就是, switch 会被编译器编译成跳转表。跳转表的概念比较好理解但难解释,直接上实例吧!
.8086⇠
.MODEL small⇠
.STACK⇠
.DATA⇠
TABLE DW DAY_1⇠
DW DAY_2⇠
DW DAY_3⇠
DW DAY_4⇠
DW DAY_5⇠
DW DAY_6⇠
DW DAY_7⇠ ; 请忽略, 是vim 特效
.CODE
....
....
DAY_1:⇠
MOV AX, OFFSET MONDAY⇠
PUSH AX⇠
CALL PRINT⇠
JMP EXIT⇠
DAY_2:⇠
MOV AX, OFFSET TUESDAY⇠
PUSH AX⇠
CALL PRINT⇠
JMP EXIT⇠
DAY_3:⇠
MOV AX, OFFSET WEDNESDAY⇠
PUSH AX⇠
CALL PRINT⇠
JMP EXIT⇠
DAY_4:⇠
MOV AX, OFFSET THURSDAY⇠
PUSH AX⇠
CALL PRINT⇠
JMP EXIT⇠
....
意思就是指,数据段把相应的cluster 的名字写入,而具体的实现写在下面,跳转的时候:
JMP TABLE[DI]⇠
就会跳转到表中该项对应的簇!
常用标志位的置位与复位
指令 | 作用 |
---|---|
CLC | Let CF = 0 |
CMC | Let CF = ~CF |
STC | Let CF = 1 |
CLD | Let DF = 0 |
STD | Let DF = 1 |
CLI | Let IF = 0 |
STI | Let IF = 1 |
Loop
普通Loop
MOV CX, [circles]
AGAIN:
XXXX
LOOP AGAIN
特殊Loop
- 当相等的时候就LOOP
LOOPZ/E
- 当不相等的时候就LOOP
LOOPNZ
串操作**
DI 和 SI 以及串操作思想
串操作是对一些列字节进行一次性操作,涉及到以下三个寄存器:
- SI: 串操作源地址, 段地址参考 DS
- DI: 串操作目的地址 , 段地址参考 ES
- DF: 串操作的方向, DF = 0 , DI , SI 执行完一次会++, DF=1, DI,SI执行完会--
常用的串指令
LODS 指令, 从内存加载到寄存器AL,AX,EAX。。。 有LODSB, LODSW, LODSD, 最后一位是Byte,Word,DWord。 决定了执行完一次后,DI,SI加/减多少个Byte。
-
STOS 指令, 将AL,AX 存到DI 指向的内存空间。
的指令都是默认执行一次,如果要重复,就需要:
LEA DI, BUFF ; BUFF is a array
MOV CX, 1000
MOV AL, 0
REP STOSB ; init BUFF with 0
MOVS 常用,将 DS:SI 地址内容 移动(复制)到 ES:DI对应的地址, 同样需要配合 CX, 以及 REP指令
INS 与 OUTS 主要用于与IO设备的串交互,同样需要配合 CX, 以及 REP指令
SCAS 指令 串比较,将ES:DI指向的内容与AL,AX,EAX内容比较。 配合 REPNE(相等就停止) / REPE (不想等就停止),当然硬性条件是cx!=0.
-
CMPS 串指令, 将SI,DI指向的内容进行比较,和SCAS 类型,配合RExxP使用。
令在汇编中作用很大,妥善使用能省去很多麻烦, 实验中有一个回文串的判断,我是用循环进行判断,十分麻烦,现在看来,利用STOSB将会非常简单,具体思路可能是:
MOVSB ,DF = 1, 将内容反向复制, 在利用CMPS 进行判断即可!
汇编过程的编写
LABEL PROC [NEAR/FAR,far常用于中断处理函数]
PUSH XX
....
POP XX
RET
LABEL ENDP
....
CALL LABEL / FAR PTR LABEL
; CALL reg/mem16 间接调用,不常用/ call dword ptr mem32/48
REP 指令
所有过程都需要,一般没有参数,直接调用即可,但是当函数有返回值时,就需要了解下面的用法了:
REP EXP
这个指令做了些啥呢,
IP <--- (SP+1,SP)
SP <--- SP+2
SP <--- SP + EXP
EXP 可以自己想办法用, 反正效果就是让SP多向下移动了EXP个字节而已。
函数传参
- 通过寄存器传参,这个比较好弄
- 通过堆栈传参,这个比较常用,需要掌握以下。 假如要传递两个个参数:
PUSH AX ;PARAM1
PUSH BX ; param 2
CALL LABEL
...
LABEL PROC NEAR
PUSH BP
MOV BP, SP ; 利用BP进行临时的堆栈操作
PUSH XXXXX.... ;保留现场
MOV SI , [BP+6]; 第一个参数,想想为什么时+6呢, 因为BP下面的参数内容为:
;REG1 <--- SP
;REG2
...
;原来BP内容 <--- BP
;IP BP+2
;PARAM2 BP+4
;PARAM1 BP+6
现在明白了把, 具体情况还要根据函数类型来呀,因为有些函数可不止默认保留IP,还有CS什么的。。
总结
我所学到的有关汇编的全部内容就到这里啦, 有疑问可以联系作者[email protected]