汇编
单位说明
在计算机中最小的信息单位,称之为位(bit,又称比特)。 存储器中所包含存储单元的数量称为存储容量,其计量基本单位是字节(Byte。简称B)
- 数据传输是以大多是以“位”(bit,又名“比特”)为单位。一个位就代表一个0或1 。 1bit = 1b
- 数据存储是以“字节”(Byte)为单位。1B = 8bit = 8b
- 1KB = 1024B = 1024 * 8b = 8Kb
总线 (8086处理器16bit CPU)
- 地址总线: 它的宽度决定了CPU的寻址能力。8086的地址总线宽度是20,所以寻址能力是1MB。N根地址总线,那么它可以寻2^N个内存单元,即1总线宽度 = 2*1B = 2B( 2^20B = 1024B * 1024B = 1024KB = 1MB)

- 数据总线: 它的宽度决定了CPU的单次数据传送量,也就是数据传送速度,8086的数据总线宽度是16,所以单次最大传递2个字节的数据
- 控制总线: 它的宽度决定了CPU对其他器件的控制能力、能有多少种控制
寄存器
寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。
-
8086有14个寄存器,每个寄存器都是16位的,每个存放2个字节
8086内部寄存器
通用寄存器
-
AX、BX、CX、DX这4个寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
-
通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
-
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
- CPU首先会将红色内存空间的值放到AX寄存器中:mov ax,红色内存空间
- 然后让AX寄存器与1相加:add ax,1
- 最后将值赋值给内存空间:mov 蓝色内存空间,ax
-
AX、BX、CX、DX这4个通用寄存器都是16位的,如下图所示

- 上一代8086的寄存器都是8位的,为了保证兼容, AX、BX、CX、DX都可分为2个独立的8位寄存器来使用H代表高位寄存器
L代表低位寄存器


字和字节
-
在汇编的数据存储中,有2个比较常用的单位
- 字节:byte,1个字节由8bit组成,可以存储在8位寄存器中
- 字:word,1个字由2个字节组成,这2个字节分别称为字的高字节和低字节
-
比如数据20000(4E20H,0100111000100000B),高字节的值是78,低字节的值是32
-
1个字可以存在1个16位寄存器中,这个字的高字节、低字节分别存储在这个寄存器的高8位寄存器、低8位寄存器中
8086寻址方式
- CPU访问内存单元时,要通过内存单元的地址,所有的内存单元都有唯一的地址,叫做物理地址
- 8086有20位地址总线,可以传送20位的地址,1MB的寻址能力
- 但它又是16位结构(数据总线)的CPU,它内部能够一次性处理、传输、暂时存储的地址为16位。如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出来的寻址能力只有64KB
- 存在的问题:CPU的寻址能力是1MB,但数组总线是16位,一次性能处理的数据为64KB
-
解决方案: 8086采用一种在内部用2个16位地址合成的方法来生成1个20位的物理地址

物理地址 | 段地址 | 偏移地址 |
---|---|---|
0x32AF1 | ||
0x32AF1 | 0x3200 | 0x0AF1 |
0x32AF1 | 0x32AF | 0x0001 |
0x32AF1 | 0x32A0 | 0x00F1 |
0x32AF1 | 0x3000 | 0x2AF1 |
内存分段管理
-
8086是用“基础地址(段地址×16) + 偏移地址 = 物理地址”的方式给出物理地址
-
为了开发方便,我们可以采取分段的方法来管理内存,比如:
- 地址10000H~100FFH的内存单元组成一个段,该段的起始地址(基础地址)为10000H,段地址为1000H,大小为100H
- 地址10000H1007FH、10080H100FFH的内存单元组成2个段,它们的起始地址(基础地址)为:10000H和10080H,段地址为1000H和1008H,大小都为80H
-
在编程时可以根据需要,将若干连续地址的内存单元看做一个段,用段地址×16定为段的起始地址(基础地址),用偏移地址定位段中的内存单元
- 段地址×16必然是16的倍数,所以一个段的起始地址(基础地址)也一定是16的倍数
- 偏移地址为16位,16位地址的寻址能力为64KB,所以一个段的长度最大为64KB
段寄存器
- 8086在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址
- 段地址在8086的段寄存器中存放
- 8086有4个段寄存器:CS、DS、SS、ES,当CPU需要访问内存时由这4个段寄存器提供内存单元的段地址
- CS (Code Segment):代码段寄存器
- DS (Data Segment):数据段寄存器
- SS (Stack Segment):堆栈段寄存器
- ES (Extra Segment):附加段寄存器
CS和IP
- CS为代码段寄存器,IP为指令指针寄存器。它们指示了CPU当前要读取指令的地址
- 任意时刻,8086CPU都会将CS:IP指向的指令作为下一条需要执行的指令
- 8086CPU的工作过程
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲寄存器
- IP = IP + 上一条指令的长度,从而指向下一条指令
- 执行指令。转向步骤1,重复整个过程
JMP指令
- CPU从何处执行指令是由CS、IP中的内容决定的。我们可以通过改变CS、IP的内容来控制CPU执行目标指令
- mov(传送指令)可以用来修改大部分寄存器的值,但不能用于设置CS、IP的值
- 8086提供了另外的指令来修改CS、IP的值,这些指令统称为转移指令,最简单的是jmp指令
- 若想同时修改CS和IP的内容,使用
jmp 段地址: 偏移地址
来完成
jmp 2AE3:3
CS: 0x2AE3
IP: 0x0003
则从内存地址0x2AE33处读取指令
jmp 3:0B16
CS: 0x0003
IP: 0x0B16
则从内存地址0x00B46 (CS*16+IP) 处读取指令
- 若指向修改IP的内容,使用
jmp 某一合法寄存器
来完成、
jmp ax -> 执行前: ax = 1000H, CS = 2000H, IP = 0003H
执行后: ax = 1000H, CS = 2000H, IP = 1000H
jmp bx -> 执行前: bx = 1003H, CS = 2000H, IP = 1000H
执行后: bx = 1003H, CS = 2000H, IP = 1003H
DS和[address]
- CPU要读写一个内存单元时,必须要先给出这个内存单元的地址,在8086中,内存地址由段地址和偏移地址组成
- 8086中有一个DS段寄存器,通常用来存放要访问数据的段地址
mov bx,1000H mov ds,bx mov al,[0]
- 上面3条指令的作用是将内存中10000H(1000:0)的数据,赋值给al的过程
- mov al:[address] 作用是将DS:[address]中的值给al
- al是8位的寄存器,所以将一个字节的数据给al
- 8086不支持将数据直接送入段寄存器中,mov ds,1000H是错误的
- 写几条指令,将al中的数据送入内存单元1000H中
mov bx, 1000H mov ds, bx mov [0], al
- 8086CPU在编程时,可以将一组内存单元定义为一个段。我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16倍数的内存单元当做专门存储数据的内存空间,称为数据段。比如用123B0H123B9H这段内存空间来存放数据,我们就可以认为123B0H123B9H是一个数据段,它的段地址为123BH,长度为10字节。用DS存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元
完整的汇编
assume cs:code
code segment
mov ax, 1122h
mov bx, 3344h
add ax, bx
;退出程序
mov ah, 4ch
int 21h
code ends
end
- 汇编语言由2类指令组成
- 汇编指令,如mov、add、sub等。有对应的机器指令,可以被编译为机器指令,最终被CPU执行
- 伪指令,如assume、 segment、ends、end等。没有对应的机器指令,由编译器解析,最终不被CPU执行
- db(define byte) 自定义字节; dw(define word)自定义字
- 注释以分号开头
- segment和ends的作用是定义一个段,segment代表一个段的开始,ends代表一个段的结束
- 一个有意义的汇编程序中,至少要有一个段作为代码段存放代码
- assume用作代码段的code段和CPU中的cs寄存器关联起来
- 编译器遇到end时,就结束对源程序的编译
中断
从本质上来讲,中断是一种电信号,当设备有某种事件发生时,它就会产生中断,通过总线把电信号发送给中断控制器。如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,跳到中断处理程序的入口点,进行中断处理
- 中断是由于软件的或硬件的信号,使得CPU暂停当前的任务,转而去执行另一段子程序。也就是说,在程序运行过程中,系统出现了一个必须由CPU立即处理的情况,此时,CPU暂时中止当前程序的执行转而处理这个新情况的过程就叫做中断
- 中断的分类
- 硬中断(外中断),由外部设备(比如网卡、硬盘)随机引发的,比如当网卡收到数据包的时候,就会发出一个中断
- 软中断(内中断),由执行中断指令产生的,可以通过程序控制触发
- 可以通过指令int n产生中断
- n是中断码,内存中有一张中断向量表,用来存放中断码对应中断处理程序的入口地址
- CPU在接收到中断信号后,暂停当前正在执行的程序,跳转到中断码对应的中断向量表地址处,去执行中断处理程序
- 常见中断
- int 10h用于执行BIOS中断
- int 3是“断点中断”,用于调试程序
- int 21h用于执行DOS系统功能调用,AH寄存器存储功能号
;hello world! 字符串的输出
;寄存器关联:代码段code与代码段寄存器CS关联,
;数据段data与数据段寄存器DS关联。
assume CS:code,DS:data ;注意:assume是伪指令,在扫描编译时不翻译
;data数据段定义
data segment
string db 'Hello world$' ;切忌串结束符$, string:声明,使用可去掉offset
data ends
;代码段定义
code segment
;程序开始
start:
mov ax,data ;将数据段段地址装入AX寄存器
mov ds,ax ;将数据段段地址通过通用寄存器AX装入DS
mov dx,offset string ;将串的段内地址装入DX,
mov ah,09h ;调用DOS的09H号功能,传入参数DS:DX=串地址,'$'结束字符串
int 21h
mov ah,4ch ;调用DOS的4CH号功能,带返回码结束,返回码存在于AL
int 21h
code ends ;代码段定义结束
end start ;程序结束
栈
- 栈:是一种具有特殊的访问方式的存储空间(后进先出, Last In Out Firt,LIFO)
- 8086会将CS作为代码段的段地址,将CS:IP指向的指令作为下一条需要取出执行的指令
- 8086会将DS作为数据段的段地址,mov ax,[address]就是取出DS:address的内存数据放到ax寄存器中
- 8086会将SS作为栈段的段地址,任意时刻,SS:SP指向栈顶元素
- 8086提供了PUSH(入栈)和POP(出栈)指令来操作栈段的数据。比如push ax是将ax的数据入栈,pop ax是将栈顶的数据送入ax
push ax
- SP = SP - 2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;
-
将ax中的内容送入 SS:SP 指向的内存单元处,SS:SP此时指向新栈顶
pop ax
- 将SS:SP指向的内存单元处的数据送入ax中
-
SP=SP+2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶
通过栈交换ax, bx内容
assume cs:code
code segment
start:
mov ax, 1000h
mov ss, ax
mov sp, 0010h
mov ax, 1122h
mov bx, 2233h
;通过栈的特性交换
push ax
push bx
pop ax
pop bx
;exit
mov ah, 4ch
int 21h
code ends
end start
栈段
- 对于8086来说,在编程时,可以根据需要,将一组内存单元定义为一个段
- 我们可以将一组长度为N(N<=64KB)、地址连续、起始地址为16倍数的内存单元,当做栈空间来使用,称为栈段。比如用10010H1001FH这段内存空间当做栈来使用,我们就可以认为10010H1001FH是一个栈段,它的段地址为1001H,长度为16字节
- 用SS存放栈段的段地址,用SP存放栈顶的偏移地址
assume cs:code,ss:stack,ds:data
;栈段(存放数据,比如高级语言中的局部变量)
stack segment
db 20 dup(1) ;定义一个20个字节的空栈,默认值为1
stack ends
;数据段(存放数据,比如高级语言中的全局变量)
data segment
db 20 dup(0)
str db "Hello World!$"
data ends
code segment
start:
;栈初始化
mov ax, stack
mov ss, ax
;exit
mov ax, 0x004ch
int 21h
code ends
end start
栈顶指针,指向0x07114内存空间

Loop指令
- loop指令和cx寄存器配合使用,用于循环操作类似高级语言的for,while
- 使用格式:
mov cx,循环次数 标号: 循环执行的程序代码 loop 标号
- loop指令执行流程
- 先将cx寄存器的值 - 1, cx = cx - 1
- 判断cx 的值; 如果不为零执行标号的代码,又执行步骤1;如果为零执行loop后面的代码
;通过loop求2的6次方的和64
assume cs:code
code segment
start:
mov ax, 2
mov cx, 0x05h ;设置循环次数
s: add ax, ax
loop s
; exit
mov ah, 4ch
int 21h
code ends
end start
结果: ax: 0x0040h = 64

Call和ret指令
- 函数栈平衡:保证函数调用前后的栈顶是一致的
- 外平栈:由函数外部保持栈平衡
- 内平栈:由函数内部保持栈平衡
Call
- call标号
- 将下一条指令的偏移地址入栈
- 跳转到定位的地址执行指令
ret
- ret指令就是将栈顶的值POP给IP
示例1
assume cs:code,ds:data
data segment
str db 'Hello world!$'
data ends
code segment
start:
mov ax, data
mov ds, ax
;调用打印函数
call print
;exit
mov ah, 4ch
int 21h
print:
mov dx, offset str
mov ah, 09h
int 21h
ret
code ends
end start
call print 之后,将下一条指令地址(07118h-07119h)的偏移地址0008h入栈, 根据print标号指向的内存地址的偏移地址赋值给IP(0x000C),然后执行print函数

print 函数结束后,调用ret,将下一条指令偏移地址出栈,赋值给IP

示例2:外平栈
函数执行前后, SP不变
assume cs:code,ss:stack
stack segment
db 20 dup(0)
stack ends
code segment
start:
mov ax, stack
mov ss, ax
push 1
push 2
push 3
call sum
add sp, 6 ;外平栈操作 SP恢复为默认值0x0014
push 1
push 2
push 3
call sum
add sp, 6 ;外平栈操作 SP恢复为默认值0x0014
push 1
push 2
push 3
call sum
add sp, 6 ;外平栈操作 SP恢复为默认值0x0014
;exit
mov ah, 4ch
int 21h
;参数:传递两个字型参数,参数分别用bx,dx存放
;返回值:返回值存放在ax中
sum:
mov bp, sp ;sp不能加减,bp可以
mov ax, ss:[bp+2]
add ax, ss:[bp+4]
add ax, ss:[bp+6]
ret
code ends
end start
示例3:内平栈
函数执行前后, SP不变
assume cs:code,ss:stack
stack segment
db 20 dup(0)
stack ends
code segment
start:
mov ax, stack
mov ss, ax
push 1
push 2
push 3
call sum
push 1
push 2
push 3
call sum
push 1
push 2
push 3
call sum
;exit
mov ah, 4ch
int 21h
;参数:传递两个字型参数,参数分别用bx,dx存放
;返回值:返回值存放在ax中
sum:
mov bp, sp ;sp不能加减,bp可以
mov ax, ss:[bp+2]
add ax, ss:[bp+4]
add ax, ss:[bp+6]
ret 6 ;SP恢复为默认值0x0014
code ends
end start
示例4(重要):完整函数调用过程,对bp进行现场保护
完整函数调用过程:
- push参数(64位cpu 任性使用寄存器)
- call指令调用(将下一条指令地址入栈)
- 保护bp寄存器,将sp赋值给bp
- 提升sp指针,作为局部变量空间(sp 减去值)
- 保护寄存器
- 业务逻辑
- 恢复寄存器
- 恢复sp(sp指向bp/sp 加上值)
- 恢复bp(pop bp)
- 返回(ret)
C语言:
int sum(int a, int b)
{
int c = 1;
int d = 2;
return a + b + c + d;
}
sum(3,4)
汇编:
assume cs:code,ss:stack
stack segment
db 40 dup(0)
stack ends
code segment
start:
mov ax, stack
mov ss, ax
;假设初值
mov bp, 1002h
mov bx, 1003h
mov cx, 1004h
mov dx, 1005h
;执行求和函数
push 03h ;传递参数
push 02h ;传递参数
call sum ;调用函数
;exit
mov ah, 4ch
int 21h
sum:
push bp ;现场保护,防止多个函数调用改变bp
mov bp, sp
sub sp, 20 ;20字节留作局部变量
;保护寄存器
push bx
push cx
push dx
;*业务逻辑代码*
;申明局部变量
mov ss:[bp-2], 1h
mov ss:[bp-4], 2h
;有可能修改寄存器
mov bx,2h
mov cx,3h
mov dx,4h
;计算结果
mov ax, [bp+4] ; bp 指向call 下条指令地址,取形参要加4
add ax, [bp+6]
add ax, [bp-2] ;局部变量
add ax, [bp-4]
;sp指向寄存器保护位置
pop dx
pop cx
pop bx
;清空局部变量
mov sp,bp
pop bp; 现场保护恢复
ret 4 ;栈平衡
code ends
end start


SP | BP | BX | CX | DX | |
---|---|---|---|---|---|
调用函数前 | 0x0028 | 0x1002 | 0x1003 | 0x1004 | 0x1005 |
调用函数后 | 0x0028 | 0x1002 | 0x1003 | 0x1004 | 0x1005 |