汇编语言

本文最后更新于 2025年3月12日 晚上

去年学的东西了,突然看到了,想了一下还是传上来,王爽的《汇编语言》第四版(第一本完全看完的跟计算机有关的书www)

一、基础知识

1.1 机器语言

1.2 汇编语言产生

汇编语言的主体是汇编指令,汇编指令是机器的助记符。

1.3 汇编语言的组成

1.汇编指令(机器码助记符)

2.伪指令(由编译器)

3.其他符号(由编译器识别)

1.4 存储器

cpu,内存,磁盘

1.5 指令和数据

1.6 存储单元

存储器分为若干个存储单元,从 0 开始编号

1.7 CPU对存储器进行读写

CPU 对数据进行读写,需要和外部器件进行信息交互:

1.存储单元地址(地址信息)

2.器件的选择,读或写命令(控制信息)

3.读或写的数据(数据信息)

连接 CPU 和其他芯片

1.8 地址总线

CPU 通过地址总线指定存储单元

地址总线能传送多少个不同的信息,CPU 就可以对多少个存储单元进行寻址。

一个 CPU 有 N 根地址总线,则可以说这个 CPU 地址总线宽度为 N,CPU 可以寻找 2 的 N 次方个内存单元。

1.9 数据总线

CPU和其他器件的数据传输,数据总线宽度决定了 CPU 和外界的数据传输速度。

1.10 控制总线

CPU 对外部器件的控制,控制总线是不同控制线的集合

小结

1)汇编指令是机器指令的助记符,同机器指令一一对应;

2)每一种 CPU 都有自己的汇编指令集;

3)CPU 可以直接使用的信息在存储器中存放;

4)在存储器中指令和数据没有任何区别,都是二进制信息;

5)存储单元从零开始存储顺序编号;

6)一个存储单元可以存储 8 个 bite(b);

7)每一个 CPU 芯片都有许多管脚,管脚和总线相连。

地址总线宽度决定了 CPU 的寻址能力;

数据总线宽度决定了 CPU 与其他器件进行数据传时的一次数据传输量;

控制总线宽度决定了 CPU 对系统中其他器件的控制能力。

1.11 主板、接口卡

1.12 各类存储器芯片

随机存储器(RAM)、只读存储器(ROM)

随机存储器 RAM

装有 BIOS 的 ROM

接口卡上的 RAM

1.13各类存储器芯片

装有 BIOS 的 ROM

BIOS:基本输入输出系统

1.15内存地址空间

存储器在物理上是独立器件,相同点:都通过 CPU 和总线相连,CPU 进行读写时都通过控制线发出内存读写命令。

二、寄存器(CPU 工作原理)

一个 16 位寄存器可以存储数据最大值为 2^16-1

2.1 通用寄存器

AX 的低八位(0位-7位)构成了 AL 寄存器,高8位(8 位-15 位)构成了 AH 寄存器。

AH 和 AL 寄存器是可以独立使用的 8 位寄存器。

2.2 汇编指令

汇编指令不区分大小写

1
2
3
4
5
mov ax,18         		将18送入AX中     					  AX=18
mov ah,78 将78送入AH AH=78
add ax,8 将寄存器中AX中的数据加上8 AX=AX+8
mov ax,bx 将BX中的数据送入AX中 AX=BX
add ax,bx 将AX,BX中的内容相加,结果存在AX中 AX=AX+BX

2.3 物理地址

CPU访问内存单元时给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间,这个唯一地址成为物理地址。

2.4 16 位结构的 CPU

16 位结构运算器一次最多可以处理 16 位数据,寄存器最大宽度为 16 位,寄存器和运算器之间的通路是 16 位的。

地址加法器工作原理

地址加法器合成物理地址的方法:物理地址=段地址x16+偏移地址

移位次数和各种形式数据关系:几进制左移就乘以几

2.5 段的概念

将若干地址连续的内存单元看成一个段

1)一个段的起始地址一定是 16 的倍数.

2)偏移地址位 16 位,16 位地址的寻址能力为 64k,所以一个段的最大长度为 64k, 2^16=64

小结

CPU 访问内存单元时,必须提供物理地址

CPU 可以通过不同的段地址和偏移地址组成物理地址

2.6 段寄存器

段寄存器是提供段地址,8086CPU 有四个段寄存器:CS,DS,SS,ES,当 8086CPU 要访问内存时,由这四个段寄存器提供内存单元的段地址。

CS 为代码段寄存器,IP 为指令指针寄存器。

2.7 CS 和 IP

CPU 将 CS 和 IP 中的内容当作指令的段地址

CS 存放指令的段地址,IP 存放指令的偏移地址

同时修改 CS、IP 的内容:

jmp 段地址:偏移地址

1
2
3
jmp 2AE3:3		#跑到2AE33

jmp 3:0B16 #跑到00B46H 3=00030

功能:用指令中给出的段地址修改 CS,偏移地址修改 IP

1
2
3
4
5
仅修改IP内容:jmp 某一合法寄存器

jmp ax(类似于mov IP,ax)

jmp bx

比如:

1
mov ax,200H      jmp ax

功能:用寄存器中的值修改 IP

2.8 代码段

将一组内存单元定义为一个段

CPU 只认被 CS:IP 指向的内存单元中的内容为指令

三、寄存器(内存访问)

3.1 内存中字的存储

低地址存低位,高地址存高位

字型考虑两个字节

两个连续的内存单元,N 号单元和 N+1 可以看成两个内存单元,也可以看成一个地址为 N 的字单元中的高位字节单元和低位字节单元。

3.2 DS 和 [address]

DS 段寄存器存放要访问数据的段地址

mov 指令能**1.将数据直接送入寄存器 **

2.将寄存器的内容送入另一个寄存器

3.将内存单元的内容传入一个寄存器

mov 指令能将一个内存单元的内容送入一个寄存器

1
2
3
4
5
6
7
mov 寄存器名,内存单元地址
e.g
mov bx,1000H
mov ds,bx
mov a1,[0] //内存单元偏移地址为 0,段地址默认放在 ds 中
mov ds,1000H //错误表达
8086CPU 不支持直接将数据送入段寄存器操作,用 bx 进行中转

数据->通用寄存器->段寄存器

内存单元到寄存器

1
mov 寄存器名,内存单元地址

寄存器到内存单元

1
mov 内存单元地址,寄存器名

将 a1 中的数据传入内存单元10000H 中

10000H->1000:0 地址1000H,偏移地址为0

1
2
3
mov bx,1000H
mov ds,bx
mov [0],a1

3.3 字的传送

1
2
3
4
mov bx,1000H
mov ds,bx
mov ax,[0] //1000:0处的字型数据送入ax
mov [0],cx //cx中的16位数据送到1000:0处

3.4 mov、add、sub 指令

1
2
3
4
5
mov/add/sub  寄存器,数据			mov ax,8
mov/add/sub 寄存器,寄存器 mov ax,bx
mov/add/sub 寄存器,内存单元 mov ax,[0]
mov/add/sub 内存单元,寄存器 mov [0],ax
mov/add/sub 段寄存器,寄存器 mov ds,ax

3.5 数据段

将 123B0H~123B9H 的内存单元定义为数据段,累加这个数据段 的前三个单元的数据

1
2
3
4
5
6
mov ax,123BH
mov ds,ax //将123BH送入ds中,作为数据段的段地址
mov a1,0 //用a1存放累加结果
add a1,[0] //将数据段第一个单元(偏移地址为0)中的数值加到a1中
add a1,[1] //以此类推
add a1,[2]

eg.写几条指令,累加数据段中的前三个字型数据

1
2
3
4
5
6
7
8
mov ax,123BH
mov ds,ax
mov ax,0
add ax,[0]
add ax,[2]
add ax,[4]

//一个字占两个单元,偏移地址为0,2,4

小结

  • 字在内存中存储时,需要两个连续的内存单元,低位字节放低地址单元中,高位放高地址中
  • mov 指令中只给出单元的偏移地址,段地址默认在 DS 寄存器中
  • 内存和寄存器传字型数据时,高地址单元和高 8 位寄存器、低地址单元和低 8 位寄存器相对应
  • mov/sub/add 有两个操作对象指令,jmp 只有一个

3.6 栈

先进后出

3. 7 CPU提供的栈机制

  • 8086CPU 入栈和出栈都是以为单位的

  • push ax 是将寄存器的数据送入栈中,pop ax 表示从栈顶取出数据送入 ax

  • 段寄存器 SS 和寄存器 SP,栈顶的段地址存放在 SS 中,偏移地址存放在SP中,CPU 从 SS 和 SP 中得到栈顶的地址

  • 任意时刻,SS:SP 指向栈顶元素

push ax 执行步骤:

​ 1)SP=SP-2,SS:SP 指向当前栈顶前面的单元,以此为新的栈顶

​ 2)将 ax 中的内容送入SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶

将 10000H~1000FH 当作栈,SS=1000H,栈空间大小为16字节。任意时刻 SS:SP 指向栈顶,当栈中只有一个元素时,SS=1000H,SP=000EH。栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2,SP 原来为 000EH,后 SP=0010H,所以栈为空时,SS=1000H,SP=10H

当栈为空时,也就不存在栈顶元素。SS:SP就只能指向最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元地址为1000:000E,所以栈空时,SP=0010H

pop ax步骤:
1
2
1) 将 SS:SP 指向的内存单元处的数据送入ax中
1) SP=SP+2,SS:SP指向当前栈下面单元,以此为栈顶

出栈后,SS:SP 指向新的栈顶 1000EH,pop 操作前的栈顶元素,1000CH 处的 2266H 依然存在。但已经不在栈中。再次执行 push 等入栈指令后,SS:SP 移至1000CH,并写入新的数据,将被覆盖

3.8 栈顶超界的问题

注意入栈出栈时超界问题

3.9 push、pop指令

1
2
push 寄存器 			//将一个寄存器的数据入栈
pop 寄存器 //用一个寄存器接收出栈的数据
1
2
3
4
push 段寄存器			//将一个段寄存器中的数据入栈
pop 段寄存器 //用段寄存器接收数据
push 内存单元 //将一个内存单元处的字入栈
pop 内存单元 //用一个内存单元接收数据
1
2
3
4
5
e.g.
mov ax,1000H
mov ds,ax
push [0] //将1000:0处的字压入栈中
pop [2] //出栈的数据放入1000:2处

指令执行时,CPU 要知道内存单元的地址,可以在 push、pop 指令中给出内存单元的偏移地址,段指令在执行时,CPU 从 ds 中获得

e.g

  1. 将 10000H~1000FH 这段空间当作栈,初始状态栈为空,将 AX,BX,DX 中的数据入栈
1
2
3
4
5
6
mov ax,1000H
mov ss,ax //设置栈的段地址,SS=1000H,不能直接向段寄存器SS中送入数据,所以用ax中转
mov sp,0010H //设置栈顶的偏移地址,栈为空,所以sp=0010H
push ax
push bx
push dx
  1. 将 10000H~1000FH 这段空间当作栈,初始状态是空的,AX=001AH,BH=001BH,将 AX,BX 的数据入栈,然后将 AX、BX 清零,从栈中恢复 AX、BX 原来的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    mov ax,1000H
    mov ss,ax
    mov sp,0010H //初始化栈

    mov ax,001AH
    mov bx,001BH

    push ax
    push bx //ax,bx入栈

    sub ax,ax //ax清零,也可以用mov ax,0
    sub bx,bx

    pop bx //恢复数据,当前栈顶为bx中的内容001BH,ax的001AH在栈顶下面,所以先pop bx 再pop ax
    pop ax

3.10 栈段

一段内存当作栈,所以为栈段

  1. 将 10000H~1000FH 中的八个字,逆序复制到 20000H-2000FH 中

1
2
3
4
5
6
7
8
9
10
11
12
13
mov ax,1000H
mov ds,ax
mov ax,2000H
mov ss,ax
mov sp,10010H
push [0]
push [2]
push [4]
push [6]
push [8]
push [A]
push [C]
push [E]

将 10000H~1000FH 中的八个字,逆序复制到 20000H-2000FH 中

1
2
3
4
5
6
7
8
9
10
11
12
13
mov ax,2000H
mov ds,ax
mov ax,1000H
mov ss,ax
mov sp,0
pop [E]
pop [C]
pop [A]
pop [8]
pop [6]
pop [4]
pop [2]
pop [0]

四、第一个程序

4.2 源程序

1. 伪指令

汇编语言中有汇编指令和伪指令。

  • 汇编指令:有对应的机器码指令,可以被编译为机器指令,最终被 CPU 所执行。
  • 伪指令:没有对应的机器指令,不被 CPU 执行。由编译器执行指令,进行相关编译工作。
1
2
3
4
5
6
7
8
9
10
assume cs:codesg		//将代码段codesg和CPU的段寄存器cs联系
codesg segment //定义一个段,段名为codesg
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codesg ends //段结束
end //程序结束

a. segment 和 ends 是成对使用的伪指令,segment 和 ends 定义一个段,格式为:

1
2
3
段名 segment
:
段名 ends

b. end 表示一个汇编程序结束

c. assume 说明某段寄存器和程序中的某一个用 segment…ends 定义的段相联

2. 源程序中的程序

3. 标号

如“codesg”,一个标号代表了一个地址,codesg 在 segment 前面,作为一个段名称,这个名称最终被编译、连接程序处理为一个段的段地址。

4. 程序的结构

编写运算2^3。

1
2
3
4
5
6
7
assume cs:abc		//将abc和cs联系起来
abc segment //定义一个程序段
mov ax,2
add ax,ax
add ax,ax
abc ends
end //程序结束

5. 程序的返回

一个程序结束后,将 CPU 的控制权交换给它得以运行的程序,这个过程称为程序返回

目的 相关指令 指令性质 指令执行者
通知编译器一个段的结束 段名 ends 伪指令 编译时编译器
通知编译器程序结束 end 伪指令 编译时由编译器
程序返回 mov ax,4cooH int 21H 伪指令 执行时由CPU

4.3 编辑源程序

DOS 中有一个程序 command.com,这个程序在 DOS 中称为命令解释器,也就是 DOS 系统的 shell

汇编程序从写出到执行过程

1
2
编程 -> 1.asm-> 编译 -> 1.obj -> 连接 -> 1.exe -> 加载 -> 内存中的程序 -> 运行
(Edit) (masm) (link) (command) (CPU)

五、[BX] 和 loop 指令

1.[bx] 和 [0] 类似,偏移地址为 0

2.[bx] 表示一个内存单元,偏移地址在 bx 中

3.loop 循环

4.”()“符号三种类型:**a)**寄存器名

​ **b)**段寄存器名

​ **c)**内存单元物理地址(一个20位数据)

e.g

1
2
3
4
5
6
7
8
ax内容位0010H,可以描述为:(ax)=0010H
2000:1000处的内容为0010H,描述为:(21000H)=0010H
mov ax,[2] 描述为(ax)=((ds)*16+2)
mov [2],ax 描述为((ds)*16+2)=ax
add ax,2 描述为(ax)=(ax)+2
add ax,bx 描述为(ax)=(ax)+(bx)
push ax 描述为 (sp)=(sp)-2 ((ss)*16+(sp))=(ax)
pop ax 描述为(ax)=((ss)*16+(sp)) (sp)=(sp)+2

5.”(X)“中表示数据有两种类型:字节和字

1
2
3
4
比如(al)、(bl)、(cl)等得到的数据为字节
(ds)、(ax)、(bx)等得到的数据为字
(al)=(20000H),则(20000H)得到的数据为字节型
(ax)=(20000H),则(20000H)得到的为字型

6.约定符号 idata 表示常量

1
2
3
mov ax,[idata]			代表 mov ax,[1]、mov ax,[2]、mov ax,[3]等
mov bx,idata 代表mov bx,1、mov bx,2、mov bx,3等
mov ds,idata 代表mov ds,1、mov ds,2等,都是非法指令

5.1 [BX]

1
2
mov ax,[bx]
bx中存放数据作为一个偏移地址EA,段地址SA默认在ds中,将ax中的数据送入内存SA:EA处,即:((ds)*16+(bx))=(ax)

e.g 写出程序执行后,21000H~21007H 单元中的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
mov ax,2000H
mov ds,ax
mov bx,1000H //ds=2000H,bx=1000H

mov ax,[bx] //将2000:1000处的字型数据送入ax中。ax=00beH

inc bx
inc bx //bx=1002H

mov [bx],ax //ds=2000H,bx=1002H,将ax内容送入2000:1002处。2000:1002内容为BE,2000:1003内容为00
inc bx
inc bx //bx=1004H

mov [bx],ax //2000:1004内容为BE,2000:1004内容为00

inc bx //bx=1005H

mov [bx],al //ds=2000H,bx=1005H,将al中的数据送入2000:1005处。2000:1005单元的内容为BE

inc bx
mov [bx],al //bx=1006H,2000:1006单元的内容为BE

5.2 Loop指令

  1. 格式:loop 标号

  2. CPU 执行指令时要进行两步操作:a. (cx)=(cx)-1 b. 判断 cx 中的值,不为零则转至标号处执行程序,如果为零则向下执行。

e,g 计算2^2,结果存在ax中

1
2
3
4
5
6
7
8
assume cs:code
code sgement
mov ax,2
add ax,ax
mov ax,4c00h
int 21h
code ends
end

e.g 计算2^12,结果存在 ax 中

1
2
3
4
5
6
7
8
9
10
11
12
13
assume cs:code
code segment
mov ax,2

mov cx,11
s: add ax,ax //用s标识了一个地址,地址处有一条指令:add ax,ax
loop s //判断(cx)=(cx)-1,不为0转到s所标识的地址处执行,如果为0就执行下一条指令,下一条为mov ax,4c00h
//执行loop s时,首先将(cx)-1,不为0就转到s处

mov ax,4c00h
int 21h
code ends
end

cx 和 loop 指令配合实现循环3要点:

​ 1)在 cx 中存放循环次数

​ 2)loop 指令中的标号所标识地址要在前面

​ 3)要循环执行的程序段,要写在标号和 loop 指令的中间

框架

1
2
3
4
	mov cx,循环次数
s:
循环执行的程序段
loop s

e.g 用加法计算123*236,结果存在ax中

1
2
3
4
5
6
7
8
9
10
assume cs:code
code segment
mov ax,0
mov cx,236
s:add ax,123
loop s
mov ax,4c00h
int 21h
code ends
end

5.5 loop 和 [bx] 的联合应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax
mov bx,0 //初始化ds:bx指向ffff:0

mov dx,0 //初始化累加寄存器dx,(dx)=0

mov cx,12 //初始化循环计数寄存器cx,(cx)=12

s: mov al,[bx]
mov ah,0
add dx,ax //间接向dx中加上((ds)*16+(bx))单元的数值
inc bx //ds:bx指向下一个单元
loop s

mov ax,4c00h
int 21h
code ends
end

5.6 段前缀

1
2
3
4
5
6
mov ax,ds:[bx]			//将一个内存单元内容送入ax,这个内存单元长度为2字节,存放一个字,偏移地址在bx中,段地址在ds中
cs:[bx]
ss:[bx]
es:[bx]
mov ax,ss:[0] //将一个内存单元内容送入ax,这个内存单元长度为2字节,存放一个字,偏移地址为0,段地址在ss中
cs:[0]

段前缀用于显示地指明内存单元的段地址,如”ds”,“cs”,“ss”,“es”等

5.7 一段安全的空间

  • 直接向一段内存写入内容
  • 这段内容不应存放系统或其他程序的数据或代码,否则写入操作可能引发错误
  • DOS 方式下,一般情况,0:200~0:2ff 空间中没有系统或其他程序的数据或代码
  • 要直接向一段内存中写入内容时,使用 0:200~0:2ff 这段空间

5.8 段前缀的使用

e.g 将内存 ffff:0~fff:b 单元处的数据复制到 0:200-0:20b 单元中

  • 0:200-0:20b 相当于 0020:0-0020:b 单元

  • 初始化:X=0 循环12次:将ffff:X单元的数据送入 0020:X X=X+1

  • 源始单元 ffff:X 和目标单元 0020:X 的偏移地址X是变量。用 bx 存放

  • 将 0:200-0:20b 用 0020:0-0020:b 描述,使目标单元的偏移地址和源始单元的偏移地址从同一数值 0 开始

    使用两个寄存器分别存放源始单元 ffff:X 和目标单元 0020:X 的段地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:code
code segment
mov ax,0ffffh
mov ds,ax //(ds)=0ffffh

mov ax,0020h
mov es,ax //(es)=0020h

mov bx,0 //(bx)=0,此时ds:bx指向0fffh:0,es:bx指向0020h:0

mov cx,12 //(cx)=12

s: mov dl,[bx] //(dl)=((ds)*16+(bx)),将ffff:bx中的数据送入dl
mov es:[bx],dl //(es)*16+(bx)=(dl),将dl中数据送入0020:bx
inc bx //(bx)=(bx)+1
loop s

mov ax,4c00h
int 21h
code ends
end

六、包含多个段的程序

6.1 在代码块中使用数据

框架

1
2
3
4
5
6
7
8
9
10
11
assume cs:code
code segment
:
数据
:
start:
:
代码
:
code ends
end start

e.g. 计算以下八个数据的和,结果存在ax寄存器中:0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h //dw定义字型数据。即“define word”
start: mov bx,0
mov ax,0

mov cx,8
s: add ax,cs:[bx]
add bx,2
loop s

mov ax,4c00h
int 21h
code ends
end start

6.2 在代码段中使用栈

利用栈,将数据0123h、0456h、0789h、0abch、0defh、0fedh、0cbah、0987h逆序存放,数据存放在cs:10-cs:2F单元中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 //用dw定义16个字型数据,在程序加载后,会取得16个字的内存空间,存放这16个数据。后面程序中将这段空间当作栈来使用

start: mov ax,cs
mov ss,ax
mov sp,30h //将设置栈顶ss:sp指向cs:30

mov bx,0
mov cx,8
s: push cs:[bx]
add bx,2
loop s //以上代码0-15单元中的8个字符数据依次入栈

mov bx,0
mov cx,8
s0: pop cs:[bx]
add bx,2
loop s0 //依次出栈8个字符型数据到代码段0-15单元中

mov ax,4c00h
int 21h
codesg ends
end start

1.e.g 下面的程序依次实现使用内存0:0-0:15单元中的内容改写程序中的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0fedh,0cbah,0987h
start: mov ax,0
mov ds,ax
mov bx,0 //ds=0,bx=0

mov cx,8
s: mov ax,[bx] //将ds:0数据送入ax
mov cs:[bx],ax //(cs)*16+(bx)=(ax),将ax数据送入cs:0处
add bx,2 //cs:2
loop s

mov ax,4c00h
int 21h
codesg ends
end start

用栈来进行数据传送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:codesg
codesg segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
start: mov ax,cs
mov ss,ax
mov sp,24h

mov ax,0
mov ds,ax
mov bx,0
mov cx,8
s: push [bx]
pop cs:[bx]
add bx,2
loop s

mov ax,4c00h
int 21h
codesg ends
end start

6.3 将数据、代码、栈放入不同的段

逆序存放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,20h //设置栈顶ss:sp指向stack:20

mov ax,data
mov ds,ax //ds指向data段

mov bx,0 //ds:bx指向data段中第一个单元

mov cx,8
s: push [bx]
add bx,2
loop s //将data段中0-15单元中的8个字符型数据依次入栈

mov bx,0

mov cx,8
s0: pop [bx]
add bx,2
loop s0 //依次出栈到data段的0-15单元中

mov ax,4c00h
int 21h
code ends
end start

七、定位内存地址的方法

7.1 and和or指令

1)and 指令:逻辑与,按位进行与运算(两个1才是1)

1
2
3
mov al,01100011B
and al,00111011B
结果 al=00100011B

2)or 指令:逻辑或,按位进行或运算(有1就为1)

1
2
3
mov al,01100011B
or al,00111011B
结果 al=01111011B

7.3 以字符形式给出的数据

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code,ds:data
data segment
db 'unIX' //相当于“db 75H,6EH,49H,58H”,db定义字节变量,类似dw
db 'foRK' //相当于“db 66H,6FH,52H,4BH”
data ends
code segment
start: mov al,'a' //mov al,61H
mov bl,'b'
mov ax,4c00h
int 21h
code ends
end start

7.4 大小写转换

判断某一位置是 0 还是 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax //设置ds指向datasg段

mov bx,0 //设置(bx)=0,ds:bx指向'BaSiC'的第一个字母

mov cx,5 //设置循环次数
s: mov al,[bx] //将ASCII码从ds:bx所指向的单元中取出
and al,11011111B //将al中的ASCII码的第五位为0,变为大写字母
mov [bx],al //转换后的ASCII码写回原单元
inc bx //ds:bx指向下一个字母
loop s

mov bx,5 //(bx)=5,ds:bx指向'iNfOrMaTiOn'的第一个字母

mov cx,11
s0: mov al,[bx]
or al,00100000B //将al中的ASCII码第5位置为1,变为小写字母
mov [bx],al
inc bx
loop s0

mov ax,4c00h
int 21h
codesg ends
end start

7.5 [bx+idata]

[bx+idata] 表示一个内存单元,偏移地址为 (bx) + idata ( bx中的数值加上 idata )

mov ax,[bx+200] 的含义:

  • 将一个内存单元送入ax,内存单元长度为 2 个字节(字单元),存放一个字,偏移地址为 bx 中的数值加上200,段地址在ds中
  • (ax)=((ds)*16+(bx)+200)
  • mov ax,[200+bx]

​ mov ax,200[bx]

​ mov ax,[bx].200

1
2
3
4
5
6
7
8
9
10
11
2000:1000 BE 00 06 00 00 00 ...
写出下面的程序执行后,ax、bx、cx中的内容
mov ax,2000H
mov ds,ax
mov bx,1000H

mov ax,[bx] //访问的段地址在ds中,(ds)=2000H;偏移地址在bx中,(bx)=1000H;指令执行后(ax)=00BEH

mov cx,[bx+1] //偏移地址=(bx)+1=1001H;后(cx)=0600H

add cx,[bx+2] //偏移地址=(bx)+2=1002H;后(cx)=0606H

7.6 用 [bx+idata] 的方式进行数组的处理

将 datasg 中定义的第一个字符串转化为大写,第二个字符串转化为小写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:codesg,ds:datasg
datasg segment
db 'BaSiC'
db 'MinIX'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0

mov cx,5
s: mov al.0[bx]
and al,11011111b
mov 0[bx],al
mov al,5[bx]([5+bx])
or al,00100000b
mov 5[bx]([5+bx]),al
inc bx
loop s
codesg ends
end start

7.7 SI和DI

si 和 di 是 8086CPU 中和 bx 功能相近的寄存器,不能分成两个 8 位寄存器来使用

下面三组实现同一功能

1
2
mov bx,0
mov ax,[bx]
1
2
mov si,0
mov ax,[si]
1
2
mov di,0
mov ax,[di]

下面三组实现同一功能

1
2
mov bx,0
mov ax,[di]
1
2
mov si,0
mov ax,[si+123]
1
2
mov di,0
mov ax,[di+123]

e.g 用 si 和 di 实现将字符串’welcome to masm!’复制到它后面的数据中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:codesg,ds:datasg
datasg segment
db 'welcome to masm!'
db '................'
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0
mov di,0
mov cx,8
s: mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s
mov ax,4c00h
int 21h
codesg ends
end start
//ds:si指向要复制的源始字符串,用ds:di指向复制的目的空间。用16位寄存器进行内存单元之间的数据传送,一次复制2个字节,循环8次

使用 [bx(si或di)+idata] 的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
codesg segment
start: mov ax,datasg
mov ds,ax
mov si,0

mov cx,8
s: mov ax,0[si]
mov 16[si],ax
add si,2
loop s

mov ax,4c00h
int 21h
codesg ends
end start

7.8 [bx+si] 和 [bx+di]

[bx+si] 表示一个内存单元,偏移地址为 (bx)+(si) 即 bx 中的数值加上 si 的数值

1
2
3
4
5
6
mov ax,[bx+si]
将一个内存单元的内容送入ax,内存单元的长度为2字节(字单元),存放一个字,偏移地址为bx中的数值加上si中的数值,段地址在ds中
(ax)=((ds)*16+(bx)+(si))

也可写为如下格式
mov ax,[bx][si]

e.g 2000:1000 BE 00 06 00 00 00 ….. ,写出ax、bx、cx中的内容

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+si] //段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1000H;指令执行后(ax)=00BEH

inc si
mov cx,[bx+si] //段地址在ds中,(ds)=2000H;偏移地址=(bx)+(si)=1001H;执行指令后(cx)=0600H

inc si
mov di,si
add cx,[bx+di] //段地址在ds中,(ds)=2000H;偏移地址=(bx)+(di)=1002H;执行指令后(cx)=0606H

7.9 [bx+si+idata] 和 [bx+di+idata]

[bx+si+idata] 表示一个单元,偏移地址为 (bx)+(si)+idata (即bx中数值加上si中的数值再加上idata)

1
2
3
4
5
6
7
8
9
10
mov ax,[bx+si+idata]
将内存单元送入ax,偏移地址为bx加上si数值再加上idata,段地址在ds中
(ax)=((ds)*16+(bx)+(si)+idata)

也有下面这些格式
mov ax,[bx+200+si]
mov ax,[200+bx+si]
mov ax,200[bx][si]
mov ax,[bx].200[si]
mov ax,[bx][si].200

e.g 2000:1000 BE 00 06 00 6A 22 ……写出ax、bx、cx内容

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+2+si] //段地址在ds中,(ds)=2000H,偏移地址=(bx)+(si)+2=1002H;(ax)=0006H

inc si
mov cx,[bx+2+si] //(ds)=2000H,偏移地址=(bx)+2+(si)=1003H;(cx)=6A00H

inc si
mov di,si
mov bx,[bx+2+di] //偏移地址=(bx)+2+(di)=1004H;(bx)=226AH

7.10 不同的寻址方式的灵活运用

  • [idata]常量表示地址,可以直接定位一个内存单元
  • [bx] 变量来表示内存地址,可以间接定位一个内存单元
  • [bx+idata] 变量和常量表示内存地址,可以在起始地址的基础上用变量间接定位一个内存单元
  • [bx+si]两个变量表示地址
  • [bx+si+idata]两个变量和一个常量表示地址

e.g 将datasg段中每个单词的头改为大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
assume cs:codesg,ds:datasg
datasg segment
db '1. file '
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,6
s: mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s
codesg ends
end start

e.g 将 datasg 段中每个单词改为大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:codesg,ds:datasg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0 //R=第一行地址

mov cx,4

s0: mov si,0 //C=第一列地址
mov cx,3

s1: mov al,[bx+si]
and al,11011111b //改变R行,C列的字母变为大写
mov [bx+si],al
inc si //下一列地址

loop s

add bx,16 //下一行地址
loop s0
codesg ends
end start

改进程序

一般情况下,需要暂存数据的时候,应该使用栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends

codesg segment
start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4

s0: push cx //将外层循环的cx值压栈
mov si,0
mov cx,3 //cx设置成内层循环的次数

s: mov al,[bx+si]
add al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
pop cx //从栈顶弹出原cx的值,恢复cx
loop s0 //外层循环的loop指令将cx中的值-1

mov ax,4c00H
int 21H
codesg ends
end start

e.g 将 datasg 段每个单词前四个字母改为大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
assume	cs:codesg,ss:stacksg,ds:datasg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
datasg ends
codesg segment
start: mov ax,datasg
mov ds,ax
mov bx,0

mov cx,4

s0: mov si,0
mov cx,4

s: mov al,[bx+si+3]
add al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
loop s0

mov ax,4c00H
int 21H
codesg ends
end start

八、数据处理的两个基本问题

reg 寄存器的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di

sreg 段寄存器集合包括:ds、ss、cs、es

8.1 bx、si、di 和 bp

1)在 8086CPU 中,只有这四个寄存器可以在“[……]”中进行内存单元的寻址

2)在[……]中,这4个寄存器可以单个出现,或只能以4种组合出现:==bx+si、bx+di、bp+si、bp+di==

3)只要在[…]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中

1
2
3
4
mov ax,[bp]						//(ax)=((ss)*16+(bp))
mov ax,[bp+idata] //(ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si] //(ax)=((ss)*16+(bp)+(si))
mov ax,[bp+si+idata] //(ax)=((ss)*16+(bp)+(si)+idata)

8.3 数据位置的表达

1)立即数 (idata)

2)寄存器

3)段地址(SA)和偏移地址(EA)

存放段地址的寄存器可以是默认的,比如以下指令,段地址默认在 ds 中

1
2
3
4
5
mov ax,[0]
mov ax,[di]
mov ax,[bx+8]
mov ax,[bx+si]
mov ax,[bx+si+8]

段地址默认在ss中

1
2
3
4
mov ax,[bp]
mov ax,[bp+8]
mov ax,[bp+si]
mov ax,[bp+si+8]

8.4 寻址方式

8.5 指令要处理得到数据有多长

1)通用寄存器指明要处理的数据的尺寸

2)没有寄存器名存在时,用操作符 X ptr 指明内存单元的长度,X 可以为 byte 或 word

3)push 不用指明访问字单元还是字节单元,push 指令只进行字操作

8.7 div指令

1)除数:有 8 位和 16 位,在一个 reg 或内存单元中

2)被除数:默认放在 AX 或 DX 和 AX 中,除数为 8 位,被除数则为 16 位,默认在 AX 中存放;除数为 16 位,被除数则为 32 位,在 DX 和 AX 中存放,DX 存放高16位,AX 存放低 16 位

3)结果:除数为 8 位,则 AL 存储除法操作的商,AH 存储除法操作的余数;

​ 除数为16位,AX 存储商,DX 存储余数

1
2
div reg
div 内存单元

用多种方法表示一个内存单元

1
2
3
div byte ptr ds:[0]
//(al)=(ax)/((ds)*16+0)的商
//(ah)=(ax)/((ds)*16+0)的余数
1
2
3
div word ptr es:[0]
//(ax)=[(dx)*10000H+(ax)]/((es)*16+0)的商
//(dx)=[(dx)*10000H+(ax)]/((es)*16+0)的余数
1
2
3
div byte ptr [bx+si+8]
//(al)=(ax)/((ds)*16+(si)+8)的商
//(ah)=(ax)/((ds)*16+(si)+8)的余数
1
2
3
div word ptr [bx+si+8]
//(ax)=[(dx)*10000H+(ax)]/((ds)+(si)+8)的商
//(dx)=[(dx)*10000H+(ax)]/((ds)+(si)+8)的余数

利用除法指令计算 100001/100

1
2
3
4
5
6
7
8
被除数100001大于65535,不能用ax寄存器存放,用dx和ax两个寄存器联合存放100001.
除数小于255,用8位寄存器存放

mov dx,1
mov ax,86A1H
mov bx,100
div bx
//(ax)=03E8H,(dx)=1

8.8 伪指令 dd

dd 用来定义 dword(double word,双字)型整数

用 div 计算 data 段中第一个数据除以第二个数据后的结果,商存在第三个数据存储单元中

1
2
3
4
5
6
7
8
9
10
11
12
13
data segment
dd 100001
dw 100
dw 0

mov ax,data
mov ds,ax
mov ax,ds:[0] //ds:0字单元中的低16位存储在ax中
mov dx,ds:[2] //ds:2中高16位存储到dx中
div word ptr ds:[4] //dx:ax中的32位数据除以ds:4字单元中的数据
mov ds:[6],ax //存储

data ends

8.9 dup

操作符,由编译器识别处理,用来进行数据的重复

1
2
3
4
5
6
7
8
db 3 dup (0)
//定义3个字节,值为0,相当于db 0,0,0

db 3 dup (0,1,2)
//定义了9个字节,0、1、2、0、1、2、0、1、2,相当于db 0,1,2,0,1,2,0,1,2

db 3 dup ('abc','ABC')
//18个字节,‘abcABCabcABCabcABC’,相当于db 'abcABCabcABCabcABC'

dup 使用格式

1
2
3
db 重复的次数 dup (重复的字节型数据)
dw 重复的次数 dup (重复的字型数据)
dd 重复的次数 dup (重复的双字型数据)

九、 转移指令的原理

可以修改 IP,或同时修改 CS 和 IP 的指令

8086CPU 转移行为分为两类:

  • 只修改IP,称为段内转移,比如:jmp 1000:0

  • 同时修改CS和IP时,称为段间转移,比如:jmp 1000:0

段间转移分为:短转移和近转移

  • 短转移 IP 修改范围为-128~127
  • 近转移 IP 的修改范围为-32768~32767

8086CPU 转移指令:

  • 无条件转移指令(如:jmp)
  • 条件转移指令
  • 循环指令(如:loop)
  • 过程
  • 中断

9.1 操作符 offset

操作符 offset 由编译器处理,取得标号的偏移地址

1
2
3
4
5
6
7
8
assume cs:codesg
codesg segment
start:mov ax,offset start //相当于mov ax,0
s: mov ax,offset s //相当于mov ax,3
codesg ends
end start
//start所标记的指令是代码段的第一条指令,偏移地址为0
//s所标记的指令为第二条,第一条指令长度为3个字节,则s的偏移地址为3

e.g 使程序在运行中将 s 处的一条指令复制到 s0 处

1
2
3
4
5
6
7
8
9
10
11
12
13
//将s处的指令复制到s0处就是将cs:offset s处数据复制到cs:offset s0

assume cs:codesg
codesg segment
s: mov ax,bx //mov ax,bx的机器码占2个字节
mov si,offset s
mov di,offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0: nop //nop占一个字节
nop
codesg ends
end s

9.2 jmp 指令

无条件转移指令

jmp 指令要给出两种信息:

  • 转移的目的地址
  • 转移的距离(段间转移、段内短转移、段内近转移)

9.3 根据位移进行转移的 jmp 指令

  • jmp short 标号(转到标号处执行指令) //段内短转移

  • CPU 在执行 jmp 指令时不需要转移的目的地址

  • “jmp short 标号”:功能:(IP)=(IP)+8 位位移

    • 8位位移=标号处的地址-jmp 指令后的第一个字节的地址
    • short 指明此处的位移为8位位移
    • 8位位移范围为-128~127,用补码表示
    • 8位位移由编译程序在编译时算出
  • “jmp near ptr 标号”:(IP)=(IP)+16 位位移

    • 16位位移=标号处的地址-jmp指令的第一个字节的地址
    • near ptr 指明此处的位移为16位位移,进行的是段内近转移
    • 16位位移的范围为-32768~32767,用补码表示
    • 16位位移由编译程序在编译时算出

9.4 转移的目的地址在指令中的 jmp 指令

“jmp far ptr 标号”实现段间转移,又称为远转移**

  • (CS)=标号所在段的段地址;
  • (IP)=标号在段中的偏移地址
  • far ptr 指明了指令用标号的段地址和偏移地址修改 CS 和 IP

9.5 转移地址在寄存器中的 jmp 指令

格式:

1
jmp 16位reg

功能:(IP)=(16位reg)

9.6 转移地址在内存中的 jmp 指令

1)jmp word ptr 内存单元地址(段内转移)

  • 从内存单元地址处开始存放一个字,是转移的目的偏移地址

  • 内存单元地址可用寻址方式的任一格式给出

  • mov ax,0123H
    mov ds:[0],ax
    jmp word ptr ds:[0]
    //执行后(IP)=0123H
    
    mov ax,0123H
    mov [bx],ax
    jmp word ptr [bx]
    //执行后(IP)=0123H
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22


    2)**jmp dword ptr 内存单元地址(段间转移)**

    - 从内存单元开始存放两个字,**高地址处的字是转移的目的段地址,低地址是偏移地址**

    - (CS)=(内存单元地址+2)

    IP)=(内存单元地址)

    - ```assembly
    mov ax,0123H
    mov ds:[0],ax
    mov word ptr ds:[2],0
    mov dword ptr ds:[0]
    //执行后(CS)=0,(IP)=0123H,CS:IP指向0000:0123

    mov ax,0123H
    mov [bx],ax
    mov word ptr [bx+2],0
    jmp dword ptr [bx]
    //执行后,(CS)=0,(IP)=0123H,CS:IP指向0000:0123

e.g. 使 jmp 指令执行后,CS:IP 指向程序的第一条指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code
data segment
dd 12345678H
data ends
code segment
start: mov ax,data
mov ds,ax
mov bx,0
mov [bx],bx //或者 mov [bx],word ptr 0或者mov [bx],offset start
//将IP设置为0
mov [bx+2],cs //或 mov [bx+2],cs 或 mov [bx+2],seg code
//将cs段地址放入内存单元
jmp dword ptr ds:[0]
code ends
end start
//jmp dword ptr ds:[0]为段间转移,(CS)=(内存单元地址+2),(IP)=(内存单元地址)
//CS:IP指向第一条指令,第一条程序地址为cs:0,设置CS:IP指向cs:0

e.g 2000:1000 BE 00 06 00 00 00 ……

1
2
3
4
mov ax,2000H
mov es,ax
jmp dword ptr es:[1000H]
//(CS)=0006H,(IP)=00BEH

9.7 jcxz 指令

jcxz 指令为有条件转移指令,都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。IP修改范围为:-128~127

格式:

1
2
jcxz  标号(如果(cx)=0),转移到标号处执行
当(cx)=0时,(IP)=(IP)+8位位移
  • 8位位移=标号处的地址 - jcxz 指令后的第一个字节的地址
  • 8位位移的范围为-128~127,用补码表示

“ jcxz 标号 ”功能相当于:

1
2
if ((cx)==0) 
jmp short 标号

e.g 利用 jcxz 指令,实现在内存 2000H 段中查找第一个值为 0 的字节,找到后将它的偏移地址存储在 dx 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code
code segment
start: mov ax,2000H
mov ds,ax

mov bx,0
s: mov ch,0 //将ch寄存器清零。ch寄存器被用作辅助寄存器,cx寄存器由ch和cl组成,用于循环计数
mov cl,[bx] //将bx指向的内存地址的内容送入cl中
jcxz ok //if(cx==0),就是ch和cl为0
inc bx
jmp short s

ok: mov dx,bx
mov ax,4c00h
int 21h
code ends
end start

9.8 loop 指令

loop 指令为循环指令,所有循环指令都是短转移

格式:

1
2
3
4
loop 标号 (cx)=(cx)-1,如果(cx)不为0,转移到标号处执行、
即:
(cx)=(cx)-1
如果(cx)不为0,(IP)=(IP)+8位位移
  • 8位位移=标号处地址 - loop 指令后的第一个字节的地址
  • 8位位移的范围为-128~127,用补码表示

“ loop ”相当于:

1
2
3
	(cx)--
if(cx)!=0)
jmp short 标号

e.g 利用 loop 指令,实现在内存 2000H 段中查找第一个值为 0 的字节,将偏移地址存储在 dx 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
start: mov ax,2000H
mov ds,ax
mov bx,0
s: mov cl,[bx]
mov ch,0
inc cx
inc bx
loop s
ok: dec bx
mov dx,bx //dec和inc相反,dec bx进行操作为:(bx)=(bx)-1
mov ax,4c00h
int 21h
code ends
end start

十、CALL和RET指令

10.1 ret 和 retf

ret 指令用栈中的数据,修改 IP 的内容,从而实现转移

retf 修改 CS 和 IP 的内容,实现转移

执行 ret 指令时 : 相当于进行:pop IP

1
2
(IP)=((ss)*16+(sp))
(sp)=(sp)+2

执行 retf 指令时:相当于进行:pop IP pop CS

1
2
3
4
(IP)=((ss)*16+(sp))
(sp)=(sp)+2
(CS)=((ss)*16+(sp))
(sp)=(sp)+2

ret 指令执行后,(IP)=0,CS:IP 指向代码段的第一条指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start

retf 指令执行后,CS:IP 指向代码段的第一条命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00h
int 21h
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push cs
push ax
mov bx,0
retf
code ends
end start

e.g 实现从内存 1000:0000 处开始执行指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,1000h
push ax
mov ax,0
push ax
retf
code ends
end start
//先将段地址CS入栈,再将偏移地址IP入栈

10.3 根据位移进行转移的 call 指令

CPU 执行 call 指令时,进行:

  • 将当前 IP 或 CS 和 IP 压入栈中
  • 转移

call 标号(将当前 IP 压栈后,转到标号处执行指令)

执行 call 指令时,进行:

1
2
3
1) (sp)=(sp)-2
((ss)*16+(sp))=(IP)
2) (IP)=(IP)+16位位移
  • 16位位移 = 标号处的地址 - call 指令后的第一个字节的地址
  • 16位位移的范围为-32768~32767

执行 “ call 标号 “时,相当于进行:

1
2
push IP
jmp near ptr 标号

10.4 转移的目的地址在指令中的 call 指令

” call far ptr 标号 “ 实现段间转移

执行时:

1
2
3
4
5
6
1) (sp)=(sp)-2
((ss)*16+(sp))=(CS)
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
2) (CS)=标号所在段的段地址
(IP)=标号在段中的偏移地址

执行” call far ptr 标号 “时,相当于:

1
2
3
push CS
push IP
jmp far ptr 标号

10.5 转移地址在寄存器中的 call 指令

格式:call 16位 reg

1
2
3
4
5
6
7
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)

//相当于
push IP
jmp 16位reg

10.6 转移地址在内存中的 call 指令

1) call word ptr 内存单元地址

​ 相当于

1
2
push IP
jmp word ptr 内存单元地址
1
2
3
4
5
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
//(IP)=0123H,(sp)=0EH

2) call dword ptr 内存单元地址

​ 相当于

1
2
3
push CS
push IP
jmp dword ptr 内存单元地址
1
2
3
4
5
6
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0] //进行两次压栈操作,sp-2-2
//(CS)=0,(IP)=0123H,(sp)=0CH

e.g 下面程序执行后,ax 和 bx 值为多少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:code
data segment
dw 8 dup (0)
data ends
code segment
start: mov ax,data //3字节
mov ss,ax //2字节
mov sp,16 //3字节
mov word ptr ss:[0],offset s //7字节
//(ss:[0]=1ah)

mov ss:[2],cs //4字节
//(ss:[2])=cs

call dword ptr ss:[0] //7字节
//cs入栈,ip=19h入栈,转到cs:1ah处执行指令,(ss:[4])=cs,(ss:[6])=ip

nop
s: mov ax,offset s //ax=1ah
sub ax,ss:[0cH]
//ax=1ah-(ss:[0ch])=1ah-19h=1

mov bx,cs //bx=cs=0c5bh
sub bx,ss:[0eH] //bx=cs-cs=0
mov ax,4c00h
int 21h
code ends
end start

10.7 call 和 ret 的配合使用

e.g bx 的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s //IP指向下一条指令,并压栈,将IP的值改变为标号为s处的偏移地址

mov bx,ax

mov ax,4c00h
int 21h
s: add ax,ax
loop s //ax=8
ret //前压入的指令偏移地址送入IP中。CS:IP指向指令mov bx,ax,从此开始执行命令。bx=8
code ends
end start

10.8 mul 指令

  • mul 是乘法指令

  • 两个相乘的数位要一样,8位默认放在 AL 中,另一个放在8位 reg 或内存字节单元中;16位放在AX中,另一个放在16位 reg 或内存字单元中。

  • 8位乘法,结果默认放在 AX 中;16位,结果高位默认在 DX 中,低位 AX 中。

格式

1
2
mul reg
mul 内存单元

内存单元可以用不同的寻址方式给出,比如

1
2
3
4
5
6
mul byte ptr ds:[0]
//(ax)=(al)*((ds)*16+0)

mul word ptr [bx+si+8]
//(ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位
//(dx)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位

e.g 计算 100 * 10

1
2
3
4
mov al,100
mov bl,10
mul bl
//(ax)=1000(03E8H)

​ 计算 100 * 10000

1
2
3
4
mov ax,100
mov bx,10000
mul bx
//(ax)=4240H,(dx)=000FH

10.10 参数和结果传递的问题

e.g 计算 data 段中第一组数据的 3 次方,结果保存在后面一组 dword 单元中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0 //ds:si 指向第一组 word 单元
mov di,16 //ds:si 指向第二组 dword 单元

mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2 //ds:si 指向下一个 word 单元
add di,4 //ds:si 指向下一个 dword 单元
loop s

mov ax,4c00h
int 21h
cube: mov ax,bx
mul bx
mul bx
ret
code ends
end start

十一、标志寄存器

标志寄存器 flag 按位起作用

11.1 ZF 标志

flag 的第 6 位是 ZF ,零标志位。如果执行相关指令后,结果为 0,zf = 1;不为 0,zf =0

11.2 PF标志

flag 第 2 位是 PF,奇偶标志位。记录相关指令执行后,结果的所有 bit 位中1的个数是否为偶数,如果是偶数,pf = 1,如果是奇数,pf = 0

1
2
3
4
5
6
7
8
mov al,1
add al,10
//00001011B,3个1,pf=0
mov al,1
or al,2
//执行后,结果为 00000011B,有 2 个1,pf=1
sub al,al
// 00000000B,0个偶数,pf=1

11.3 SF 标志

flag 第7位,符号标志位。记录执行指令后,结果是否为负。结果为负,sf =1;如果非负,sf =0

1
2
3
4
5
6
7
8
sub al,al 		ZF=1  PF=1  SF=0
mov al,1 1 1 0
push ax 1 1 0
pop bx 1 1 0
//mov,push,pop等传送指令对标志寄存器没影响
add al,bl 0 0 0
add al,10 0 1 0
mul al 0 1 0

11.4 CF 标志

flag 第 0 位是 CF,进位标志位。一般情况下,在进行无符号运算时,记录了运算结果的最高有效位向跟高位的进位值,或从更高位的借位值

1
2
3
4
5
6
7
mov al,98H
add al,al //(al)=30H,CF=1,CF记录了从最高有效位向跟高位的进位值
add al,al //(al)=60H,CF=0

mov al,97H
sub al,98H //(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al,al //(al)=0,CF=0

11.5 OF 标志

flag 第 11 位是 OF,溢出标志位。OF 记录有符号数运算的结果是否发生溢出。溢出 OF = 1,没有 OF = 0

1
2
3
4
5
6
7
mov al,0F0H
add al,88H
//执行后,CF=1,OF=1.无符号运算,0F0H+88H有进位,CF=1;有符号运算,0F0H+88H发生溢出,OF=1

mov al,0F0H
add al,78H
//CF=1,OF=0.无符号运算0F0H+78H有进位,CF=1;有符号运算,0F0H+78H不发生溢出,OF=0

e.g 下列每条指令执行后,ZF 、PF、SF、CF、OF等标志位的值

1
2
3
4
5
6
7
8
9
sub al,al				//ZF=1,PF=1,SF=0,CF=0,OF=0
mov al,10H // 1 1 0 0 0
add al,90H // 0 1 1 0 0
mov al,80H // 0 1 1 0 0
add al,80H // 1 1 0 1 1
mov al,0FCH // 1 1 0 1 1
add al,05H // 0 0 0 1 0
mov al,7DH // 0 0 0 1 0
add al,0BH // 0 1 1 0 1

11.6 adc 指令

adc 为带进位加法指令

1
2
3
4
格式:	adc 操作对象1,操作对象2
功能: 操作对象1 = 操作对象1 + 操作对象2 + CF
//指令: adc ax,bx
//(ax)=(ax)+(bx)+CF

e.g

1
2
3
4
5
mov ax,2
mov bx,1
sub bx,ax
adc ax,1
//(ax)=4,(ax)+1+CF=4
1
2
3
4
mov ax,1
add ax,ax
adc ax,3
//(ax)=2+3+0=5
1
2
3
4
mov al,98H
add al,al
adc al,3
//(al)=34H,(al)+3+CF=30H+3+1=34H

执行 adc 指令时,CF 的值如果是被 sub 指令设置的,那么就是借位值

add 指令设置时,是进位值

加法可以分两部进行:

  • 低位相加,比如 0198H 和 0183H。低位相加得 1B
  • 高位相加再加上低位相加的进位值。01+01+1=03

e.g 计算 1EF0001000H + 2010001EF0H,结果放在 ax(最高 16 位),bx(次高 16 位),cx(低 16 位)中

  • 先将低 16 位相加,然后 CF 中记录本次相加的进位值
  • 再将高 16 位和 CF 相加,然后 CF记录进位值
  • 最后高 16 位和 CF相加,CF记录进位值
1
2
3
4
5
6
mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
add bx,1000H
add ax,0020H

11.7 sbb指令

sbb 是带借位减法指令,利用 CF 位上记录的借位值

1
2
3
4
sbb 操作对象1,操作对象2
操作对象1 = 操作对象1 - 操作对象2 - CF
//sbb ax,bx
//(ax)=(ax)-(bx)-CF

e.g 计算 003E1000H - 00202000H,结果放在 ax,bx 中

1
2
3
4
mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H

11.8 cmp 指令

cmp 为比较指令,相当于减法,但不保存结果。执行后会对标志寄存器产生影响。

1
2
3
4
格式:cmp 操作对象1,操作对象2
功能:计算操作对象1 - 操作对象2,不保存结果,仅仅根据计算结果对标志寄存器进行设置
//cmp ax,ax
//结果为 0 ,指令执行后 zf=1,pf=1,sf=0,cf=0,of=0

e.g

1
2
3
4
mov ax,8
mov bx,3
cmp ax,bx
//(ax)=8,zf=0,pf=1,sf=0,cf=0,of=0

对于 cmp ax,bx,有如下分析

  • if (ax==bx),(ax)-(bx)=0,zf=1
    if (ax!=bx),(ax)-(bx)!=0,zf=0
    if (ax<bx),(ax)-(bx)<0,产生借位,cf=1
    if (ax>=bx),(ax)-(bx)>=0,cf=0
    if (ax>bx),(ax)-(bx)>0,cf=0 或 zf=0
    if (ax<=bx),(ax)-(bx)<=0,cf=1 或 zf=1
     
    if:
        zf=1,说明 (ax)=(bx)
        zf=0,(ax)!=(bx)
        cf=1,(ax)<(bx)
        cf=0,(ax)>=(bx)
        cf=0 且 zf=0,(ax)>(bx)
        cf=1 或 zf=1,(ax)<=(bx)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9


    对有符号数进行比较时

    **对于cmp ah,bh**

    ```assembly
    if (ah==bh),(ah)-(bh)=0,zf=1
    if (ah!=bh),(ah)-(bh)!=0,zf=0
  1. 如果 sf =1,of=0

    of =0,没有溢出,逻辑上真正结果的正负 = 实际结果的正负

    sf =1,实际结果为负数,所以逻辑上真正的结果为负,(ah)<(bh)

  2. 如果 sf =1,of =1

    of =1,有溢出,逻辑上真正结果的正负与实际结果的正负不相等

    sf =1,实际结果为负

    如果因为溢出导致实际结果为负,那么逻辑上真正的结果为正(ah)>(bh)

  3. 如果 sf = 0,of = 1

    sf=0,实际结果为非负,而 of =1 说明有溢出 ,结果为非 0 ,实际结果为正

    如果因为溢出导致实际结果为非负,那么逻辑上真正结果为负,(ah)<(bh)

  4. sf =0,of =0

    没有溢出,实际结果为非负,(ah)>=(bh)

11.9 检测比较结果的条件转移指令

“ 转移 ”指它能修改 IP,“ 条件 ”指它可以根据某种条件,决定是否修改 IP

指令 含义 检测的相关标志位
je 等于则转移 zf = 1
jne 不等于就转移 zf = 0
jb 低于则转移 cf =1
jnb 不低于就转移 cf = 0
ja 高于就转移 cf = 0且 zf =0
jna 不高于就转移 cf =1 或 zf = 1

e.g 统计 data 段中数值小于 8 的字节的个数,用 ax 保存统计结果

1
2
3
4
5
6
7
8
9
10
11
		mov ax,data
mov ds,ax
mov ax,0
mov bx,0
mov cx,8
s: cmp byte ptr [bx],8
jnb next
inc ax
next: inc bx
loop s
//(ax)=2

统计 F000:0 处 32 个字节中,大小在 [32,128] 的数据个数

1
2
3
4
5
6
7
8
9
10
11
12
13
	mov ax,0f000h
mov ds,ax
mov bx,0
mov dx,0
mov cx,32
s: mov al,[bx]
cmp al,32
ja s0
cmp al,128
ja s0
inc bx
s0: inc bx
loop s

11.10 DF 标志和串传送指令

flag 第 10 位,方向标志位。在串处理指令中,控制每次操作后 si ,di 的增减

df = 0 每次操作后 si、di 递增

df =1 每次操作后 si、di 递减

1
2
3
4
5
6
7
movsb
相当于:
((es)*16+(di))=((ds)*16)+si
如果 df=0: (si)=(si)+1
(di)=(di)+1
如果 df=1: (si)=(si)-1
(di)=(di)-1
1
2
movsw
将ds:si指向的内存单元中的字送入es:di中,根据标志寄存器df位的值,将si和di递增2或递减2

8086 CPU 提供两条指令对 df 位进行设置

1
2
cld 指令:将标志寄存器的df位置0
std 指令:将标志寄存器df位置1

e.g 用串传送指令,将 F000H 段中的最后 16 个字符复制到 data 段中

1
2
3
4
5
6
7
8
9
10
11
12
data segment
db 16 dup (0)
data ends
mov ax,0f000h
mov ds,ax
mov si,0ffffh //ds:si 指向 f000:ffff
mov ax,data
mov es,ax
mov di,15 //es:di 指向 data:000f
mov cx,16 //(cx)=16,rep 循环16次
std //设置 df=1,逆向传送
rep movsb

11.11 push 和 popf

pushf 是将标志寄存器的值压栈,popf 是出栈送入标志寄存器中

e.g (ax)=?

1
2
3
4
5
6
7
8
9
10
11
12
mov ax,0
push ax
popf
mov ax,0fff0h
add ax,0010h
pushf
pop ax //0 0 0 0 of df if tf sf zf 0 af 0 pf 0 cf
//0 0 0 0 0 0 * * 0 1 0 * 0 1 0 1
//ax=flag=000000**010*0101b
and al,11000101B //al=01000101b=45h
and ah,00001000B //ah=00000000b=0h
//ax=45h

11.12 标志寄存器在 Debug 中的表示

标志 1 0
of OV NV
sf NG PL
zf ZR NZ
pf PE PO
cf CY NC
df DN UP

十二、内中断

12.1 内中断的产生

对于 8086CPU ,有以下情况会产生中断

  • 除法错误,比如 div 指令产生的除法溢出
  • 单步执行
  • into 指令
  • int 指令

中断源在 8086CPU 中的中断类型

  • 除法错误:0
  • 单步执行:1
  • into 指令:4
  • int 指令,格式为 int n,n 为字节型立即数,是提供给 CPU 的中断类型码

12.3 中断向量表

对于 8086PC 机,中断向量表指定放在内存地址 0 处。从内存 0000:0000 到 0000:03FF 的 1024 个单元中存放着中断向量表。

e.g

1
2
0000:0000 68 10 A7 00 8B 01 70 00-16 00 9D 03 8B 01 70 00
//3号中断源对应的中断处理程序的入口地址为: 0070:018B

一个表项存放一个中断向量,也就是一个中断处理程序的入口地址,入口地址包括段地址和偏移地址,一个表项占两个字,高地址存放段地址,低地址存放偏移地址

1
2
存储 N 号中断源对应的中断处理程序入口的偏移地址的内存单元的地址为:4N
段地址的内存单元地址为:4N+2

12.4 中断过程

8086CPU 中断过程

  • 取得字段类型码 N
  • 标志寄存器入栈,TF=0,IF=0
  • push CS
  • push IP
  • ( IP ) = ( N * 4 ) , ( CS ) = ( N * 4 + 2 )

12.5 中断处理程序和 iret 指令

中断处理程序的编写方法

  • 保存用到的寄存器
  • 处理中断
  • 恢复用到的寄存器
  • 用 iret 指令返回
1
2
3
pop IP
pop CS
popf

在中断过程中,寄存器入栈顺序是标志寄存器,CS,IP。iret 出栈顺序是 IP,CS,标志寄存器

12.9 do0

do0 程序的主要任务是显示字符串

e.g 显示“ overflow!”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
assume cs:code

code segment
start: mov ax,cs
mov ds,ax
mov si,offset do0 //设置 ds:si 指向源地址
mov ax,0
mov es,ax
mov di,200h //设置 es:di 指向目的地址
mov cx,offset do0end-offset do0 //设置 cx 为传输长度
cld //传输方向为正
rep movsb
设置中断向量表

mov ax,4c0h
int 21h

do0: jmp short do0start //”overflow!”不是可执行代码,jmp 转移到正式 do0 程序。除法发生溢出时,CPU 执行0:200处的 jmp 指令,跳过后面字符串,转到正式 do0 程序执行
db "overflow!"
do0start: mov ax,cs
mov ds,ax
mov si,202h //ds:si指向字符串,jmp 指令占两个字节,所以偏移地址为 202h

mov ax,0b800h
mov es,ax
mov di,12*160+36*2 //es:di指向显存空间的中间位置

mov cx,9 //cx为字符串长度
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s

mov ax,4c00h
int 21h

de0end:nop
code:ends
end start
//将标号 do0 到标号 do0end 之间的内容送到 0000:0200处

12.10 设置中断向量

将 do0 的入口地址 0:200 写入中断向量表的 0 号表项中,使 do0 成为 0 号中断的中断处理程序

0 号表项的地址为 0:0,其中 0:0 字单元存放偏移地址,0:2字单元存放段地址

1
2
3
4
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0

12.11 单步中断

CPU 在执行完一条指令后,如果检测到标志寄存器的 TF 位为 1,则产生单步中断,引发中断过程。

  • 取得中断类型码为 1
  • 标志寄存器入栈,TF、IF 设置为 0
  • CS、IP 入栈
  • (IP)=(1*4),(CS)=(1*4+2)

十三、int 指令

13.1 int 指令

1
int n

13.2 编写中断例程

安装中断 7ch 的中断例程

功能:将一个全是字母,以 0 结尾的字符串,转化为大写

参数:ds:si 指向字符串的首地址

eg. 将 data 段中的字符串转化为大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code

data segment
db 'conversation',0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
int 7ch

mov ax,4c00h
int 21h
code ends
end start

安装程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
assume cs:code
code segment

start: mov ax,cs
mov ds,ax
mov si,offset capital
cld
rep movsb

mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h

capital: push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
iret
capitalend: nop
code ends
end start

13.3 对 int、iret 和栈的理解

e.g 在屏幕中显示 80 个 “ ! ”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov di,160*12

mov bx,offset s-offset se //从标号 se 到标号 s 的转移位移
mov cx,80
s: mov byte ptr es:[di],'!'
add di,2
int 7ch //(cx)!=0,转移到标号 s 处
se:nop
mov ax,4c0h
int 21h
code ends
end start

13.4 BIOS 和 DOS 所提供的中断例程

在系统板的 ROM 位置存放一套程序,BIOS (基本输入输出系统),主要包含以下内容:

  • 硬件系统的检测和初始化程序
  • 外部中断和内部中断的中断例程
  • 用于硬件设备进行 I/O 操作的中断例程
  • 其他和硬件系统相关的中断例程

//e.g 下面程序在第2、4、6、8行显示 4 句英文诗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code
code segment
s1: db 'Good,better,best,','$'
s2: db 'Never let it rest,','$'
s3: db 'Till good is better,','$'
s4: db 'And better,best.','$'
s: dw offset s1,offset s2,offset s3,offset s4
row: db 2,4,6,8

start:mov ax,cs
mov ds,ax
mov bx,offset s
mov si,offset row
mov cx,4
ok: mov bh,0
mov dh,

十四、端口

寄存器相同特点:

  • 都和 CPU 总线相连,通过它们所在的芯片进行
  • CPU 对它们进行读或写的时候都通过控制线向它们所在的芯片发出端口号读写命令

CPU 可以直接读写以下地方的数据:

  • CPU 内部的寄存器
  • 内存单元
  • 端口

14.1 端口的读写

1)访问内存

1
mov ax,ds:[8]		//假设执行前(ds)=0
  • CPU 通过地址线将地址信息 8 发出
  • CPU 通过控制线发出内存读命令,选中存储器芯片,通知他它要从中读取数据
  • 存储器将 8 号单元中的数据通过数据线送入 CPU

2)访问端口

1
in al,60h				//从 60h 号端口读入一个字节
  • CPU 通过地址线将地址信息 60h 发出
  • CPU 通过控制线发出端口命令,选中端口所在的芯片,通知他要从中读取数据
  • 端口所在的芯片将 60h 端口中的数据通过数据线送入 CPU

在 in 和 out 指令中,只能用 ax 和 al 来存放从端口中读入的数据或要发送到端口的数据。访问 8 位端口时用 al ,访问 16 位用 ax

对0~255以内端口进行读写

1
2
in al,20h		//从 20h 端口读入一个字节
out 20h,al //往 20h 端口写入一个字节

对 256~65535 的端口进行读写,端口号放在 dx 中

1
2
3
mov dx,3f8h			//将端口号 3f8h 送入 dx
in al,dx //读入
out dx,al //写入

14.2 CMOS RAM 芯片

  • 包含一个实时钟和一个有 128 个存储单元的 RAM 存储器
  • 128 个字节的 RAM 中,内部实时钟占用 0~0dh 单元来保存时间信息,其余大部分单元用于保存系统配置信息,供系统启动时 BIOS 程序读取
  • 芯片内部有两个端口,存放要访问的 CMOS RAM 单元的地址;71h 为数据端口,存放从选定的 CMOS RAM 单元中读取的数据,或要写入到其中的数据。CPU 对 CMOS RAM 的读写分两步进行,读 CMOS RAM 的 2 号单元
    • 将 2 送入端口 70h
    • 从端口 70h 读出 2 号单元的内容

14.3 shl 和 shr 指令

shl 是逻辑左移指令

  • 将一个寄存器或内存单元中的数据向左移位
  • 将最后移出的一位写入 CF 中
  • 最低位用 0 补充

指令:

1
2
3
mov al,01001000b
shl al,1 //左移 1 位
//(al)=10010000b

如果移动位数大于 1 时,必须将移动位数放在 cl 中

1
2
3
4
mov al,01010001b
mov cl,3
shl,al,cl
//执行后 (al)=10001000b,最后移出的一位是 0,所以 CF=0

shr 是逻辑右移指令

  • 向右移位
  • 最后移出的一位写入 CF 中
  • 最高位用 0 补充

指令:

1
2
3
mov al,10000001b
shr al,1 //右移
//(al)=01000000b,CF =1

e.g 用加法和移位指令计算 (ax)=(ax)*10

1
2
3
4
5
6
7
8
9
10
11
12
assume cs:code
code segment
start: mov bx,ax
shl,ax,1
mov cl,3
shl bx,cl //左移 3 位,(bx)=(ax)*8
add ax,bx //(ax)=(ax)*2+(ax)*8
mov ax,4c00h
int 21h
code ends
end start

十五、外中断

15.2 外中断信息

1)可屏蔽中断

CPU 可以不响应的外中断,如果 IF = 0,CPU 可以不响应可屏蔽中断

sti ,设置 IF =1

cli ,设置 IF =0

几乎所有由外设引发的中断都是可屏蔽中断

2)不可屏蔽中断

对于 8086CPU ,不可屏蔽中断类型码固定为2,所以中断过程中,不需要取中断类型码,过程为:

1
2
3
标志寄存器入,IF=0,TF=0
CS、IP 入栈
(IP)=(8),(CS)=(0AH0)

指令系统总结

1. 数据传送指令

mov 、sub 、push、pop、pushf、popf、xchg等都是数据传送指令,实现寄存器和内存、寄存器和寄存器之间的单个数据传送

2. 算术运算指令

add、sub、adc、sbb、inc、dec、cmp、imul、idiv、aaa等,实现寄存器和内存中的数据的算数运算,执行结果影响寄存器的 sf、zf、of、cf、pf、af位

3. 逻辑指令

add、or、not、xor、test、shl、shr、sal、sar、rol、ror、rcl、rcr等,除了 not 外,执行结果都影响标志寄存器的相关标志位

4. 转移指令

可以修改 IP ,或者同时修改 CS 和 IP

1)无条件转移指令,比如,jmp

2)条件转移指令,比如,jcxz,je,jb,ja,jnb,jna等

3)循环指令,比如,loop

4)过程,比如,call、ret、retf

5)中断,比如,int、iret

5. 处理机控制指令

cld、std、cli、sti、nop、clc、cmc、stc、hlt、wait、esc、lock等

6. 串处理指令

movsb、movsw、cmps、scas、lods、stos等,可搭配 rep、repe、repne等使用

十六、直接定址表

16.2 在其他段中使用数据标号

1
2
3
4
5
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends

标号 c 处存储的两个双字节型数据为标号 a 的偏移地址和段地址、标号 b 的偏移地址和段地址 ,相当于:

1
2
3
4
5
6
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a,seg a,offset b,seg b
data ends
//seg 操作符,功能为取得某一标号的段地址

汇编语言
http://example.com/2025/03/12/Re-汇编语言/
作者
butt3rf1y
发布于
2025年3月12日
许可协议