汇编语言复习整理

参考教材:《汇编语言》王爽

机器语言与汇编语言

汇编语言的核心是汇编指令,它决定了汇编语言的特性。

机器语言是机器指令的集合,机器指令展开来讲就是一台机器可以正确执行的命令。

汇编语言的主体是汇编指令,汇编指令和机器指令的差别在于指令的表示方法上。

汇编指令是机器指令便于记忆的书写格式。

汇编语言由以下3类组成:

  1. 汇编指令(机器码的助记符)
  2. 伪指令 (由编译器执行)
  3. 其它符号(由编译器识别)

指令与数据

指令和数据是应用上的概念,在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。

储存单元

存储单元从零开始顺序编号。

可以存一个Byte,即8个二进制位。

总线

逻辑上分为3类:地址总线、控制总线、数据总线。

地址总线

CPU是通过地址总线来指定存储单元的。

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

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

数据总线

CPU与内存或其它器件之间的数据传送是通过数据总线来进行的。

数据总线的宽度决定了CPU和外界的数据传送速度。

8086有16根数据线,可以一次传输16位数据。

控制总线

控制总线是个总称,控制总线是一些不同控制线的集合。

控制总线的宽度决定了CPU对外部器件的控制能力。

内存地址空间

将各各类存储器看作一个逻辑存储器。

所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器;

每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间;

CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。

内存地址空间的大小受CPU地址总线宽度的限制。

如8086的地址总线宽度为20,可以定位 $2^{20}$ 个内存单元,所以内存地址空间大小为1MB。

寄存器(CPU工作原理)

8086的14个寄存器(16位)

AX、BX、CX、DX、SI、DI、SP、BP、 IP、CS、SS、DS、ES、PSW

通用寄存器 AX、BX、CX、DX

用来存放一般性数据,可分为2个独立的8位寄存器使用,如 AH,AL

字在寄存器中的储存

8086可以一次性处理下面两种尺寸的数据:

  • 字节:byte,1 byte = 8 bit
  • 字:word, 两个字节组成一个字

汇编指令 mov、add

汇编指令不区分大小写

指令 含义
mov ax,001AH 赋值 ax = 001AH
add al,85H 相加 al += 85H

在进行数据传送或运算时要注意指令的两个操作对象的位数应当是一致的

物理地址

物理地址是内存单元的唯一地址

8086是16位CPU,地址总线宽度为20,即在内部用2个16位地址合成一个20位地址:
$$
物理地址 = 段地址 \times 16 + 偏移地址
$$

CPU对内存进行划分,便于管理

一个段的长度最大为:64K(16位偏移地址)

段寄存器 CS、DS、SS、ES

add,sub不能对段寄存器进行操作。

CS和IP

8086中最关键的两个寄存器,指示CPU当前读取指令的地址

CS为代码段寄存器

IP为指令指针寄存器

任意时刻8086将CS:IP当作指令执行

初始状态CS=FFFFH,IP=0000H,即第一条指令在 FFFF0H

mov指令不能用于设置CS、IP的值

1
2
3
4
#jmp 段地址:偏移地址 同时修改
jmp 1111:1111
#jmp 某寄存器 修改IP
jmp ax

寄存器(内存访问)

字单元

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

DS和[address]

[…]作为偏移地址时,8086自动取DS作为段地址。

不支持将数据直接送入段寄存器,需要用通用寄存器中转。

push、pop以 为单位进行

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

push ax

SP = SP - 2,将ax中的内容送入,SS:SP指向新栈顶元素,栈顶从高地址向低地址方向增长。

pop ax

将栈顶内容送入ax,SP = SP + 2,SS:SP指向新栈顶元素。

栈顶超界

栈满或栈空时进行push或者pop,会发生栈顶超界的问题

8086设计上无法保证栈的操作不超界

栈段

编程时的安排,与push,pop等无关。

栈满时push,便从栈底循环重新开始;栈空时pop同理。

源程序

编写,编译,连接,执行

可执行文件包含两个信息:程序和数据、相关描述信息。

伪指令

两种指令:

  • 汇编指令 -> CPU执行
  • 伪指令 -> 编译器执行

segment和ends,定义一个段

1
2
3
codesg segment
#code
codesg ends

end 结束程序

assume 假设,定义某种联系

1
2
assume cs:codesg
#将用作代码段的段codesg与CPU的段寄存器cs联系起来

程序返回

汇编指令,由CPU执行

1
2
mov ax,4c00H
int 21H

示例

求 $2^3$

1
2
3
4
5
6
7
8
9
10
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax

mov ax,4c00H
int 21H
abc ends
end

编辑

进入DOS,运行edit,将文件保存为 1.asm

编译

masm 文件名 生成目标文件(.obj)

连接

link 文件名 生成可执行文件(.exe)

[BX]和loop指令

[BX]

[bx]表示偏移地址在bx中,类似[0]表示偏移地址为0;

段地址都在ds中。

在debug与masm中对[0]有不同的解释:

  • debug中代表偏移地址
  • masm中代表常量 0

若想在masm中表示偏移地址,可以用 ds : [0] 这种加上段地址的形式或者**[bx]**。

Loop

判断cx的值是否为 0,跳转至标号处。

计算$2^{12}$:

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
loop s

mov ax,4c00h
int 21h

code ends
end

Debug

在汇编源程序中,数据不能以字母开头,要在前面加 0

比如

9138h -> 9138h

A000h -> 0A000h

使用p命令代替t命令完成循环(step over)

段前缀

出现在访问内存单元的指令中,用于显式地指明内存空间的段地址的

“ds:” “cs:” “ss:” “es:” 在汇编语言中被称为段前缀

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#将"mov ax,4c00h"之前的指令复制到0:200处
assume cs:code
code segment

mov ax,cs
mov ds,ax
mov ax,0020h
mov es,ax
mov bx,0
mov cx,23

s: mov al,[bx]
mov es:[bx],al
inc bx
loop s

mov ax,4c00h
int 21h
code ends
end

包含多个段的程序

在代码段中使用数据

定义字型数据

1
dw 0123h,0456h,0789h

用 end 标号 的形式指明程序的入口

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

code segment

#数据

start:

#代码

code ends

end start

在代码段中使用栈

1
2
3
dw 0,0,0,0,0
mov ax,cs
mov ss,ax

实验

  • 定义一个segment,当segment中的数据不满16字节的倍数时,占用整倍数(向上取整)。

  • 编写code segment使a segment与b segment中的数据依次相加,将结果存到c segment中

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
a segment
db 1,2,3,4,5,6,7,8
a ends
b segment
db 1,2,3,4,5,6,7,8
b ends
c segment
db 0,0,0,0,0,0,0,0
c ends

code segment
#ans
start: mov bx,0
mov ax,a
mov ds,ax
mov cx,8
s: mov ax,b
mov es,ax
mov dl,0
add dl,[bx]
add dl,es:[bx]
mov ax,c
mov es,ax
add es:[bx],dl
inc bx
loop s

mov ax,4c00h
int 21h
#ans
code ends
end start
  • 使用push指令将a segment中的前8个字型数据储存到b segment中
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
assume cs:code
a segment
dw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh
a ends
b segment
db 0,0,0,0,0,0,0,0
b ends
code segment
#ans
start: mov bx,0
mov ax,a
mov ds,ax
mov cx,8

s0: push [bx]
add bx,2
loop s0

mov ax,b
mov ds,ax
mov cx,8
mov bx,0

s1: pop [bx]
add bx,2
loop s1

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

更灵活的定位内存地址的方法

and 按位与

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

可以用and al,11110111B等指令使特定位置取0

or 按位或

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

可以用or al,00001000B等指令使特定位置取1

以字符形式给出数据

1
2
#用单引号,自动转换为ASCII码
db 'unIX' ;相当于db 75H,6EH,49H,58H

大小写转换

大小写字母的二进制ASCII码在第5位(0开始)不同,大写为0小写为1

用and,or指令使该位取特定值即可

[bx+idata]

类似数组的定位方式

1
2
3
4
#不同写法
mov ax,[200+bx]
mov ax,200[bx]
mov ax,[bx].200

SI和DI

与bx相似,但不能拆分成两个8位寄存器来使用

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

1
2
3
4
5
#不同写法
mov ax,[bx+si+200]
mov ax,200[bx][si]
mov ax,[bx][si].200
mov ax,[bx].200[si]

数据处理的两个基本问题

  • 处理的数据在什么地方
  • 要处理的数据有多长

处理的数据在什么地方

bx si di bp

  1. 只有这四个寄存器可以在[…]中进行内存单元的寻址
  2. 在[…]中,只能单个出现或以 bx/bp+si/di 四种固定形式出现
  3. 只要使用bp而没有给出段地址,则默认为ss

汇编语言中数据位置的表达

  1. 立即数(idata)

    直接包含在机器指令内的数据

  2. 寄存器

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

寻址方式

名称 常用格式
直接寻址 [idata]
寄存器间接寻址 [bx]
寄存器相对寻址 结构体 [bx].idata,数组 idata[si],二维数组 [bx] [idata]
基址变址寻址 二维数组 [bx] [si]
相对基址变址寻址 表格中的数组 [bx].idata[si],二维数组 idata[bx] [si]

指令要处理的数据有多长

  1. 通过寄存器名来指定

  2. 没有寄存器名存在的情况下,用操作符 X ptr 指明内存单元的长度,X为byte/word

    1
    2
    3
    mov word ptr ds:[0],1
    inc word ptr [bx]
    add byte ptr [bx],2
  3. 其他方法

    有些指令默认了长度,如push只进行操作

寻址方式的综合应用

使用[bx+idata+si]的方式为结构化数据的处理提供了方便

用bx来定位结构体,用idata来定位结构体中的某一个数据项,用si定位数据项中的每个元素

div指令

  • 除数:有8位和16位两种,存放在一个寄存器或内存单元中

  • 被除数:存放位置

    除数8位,被除数16位,AX

    除数16位,被除数32位,DX高16位,AX低16位

  • 结果:

    除数8位,AH余数,AL商

    除数16位,DX余数,AX商

1
2
3
#格式
div reg
div 内存单元
  • 示例
1
2
3
4
5
6
#100001/100, 100001=186A1H
mov dx,1
mov ax,86A1H
mov bx,100
div bx
#执行后,(ax)=03E8H=1000, (dx)=1

伪指令 dd

define dword, 同dw,db,占4个字节

dup

用来配合dw,db,dd进行数据的重复

1
2
3
db 3 dup (0)    ;相当于 db 0,0,0
db 3 dup (0,1,2) ;相当于 db 0,1,2,0,1,2,0,1,2
#db 重复次数 dup (重复的数据)

转移指令的原理

可以修改IP,或同时修改CS和IP的指令统称为转移指令。

转移指令就是可以控制CPU执行内存某处代码的指令。

8086CPU的转移行为有以下几种:

  • 段内转移:只修改IP,jmp ax
  • 段间转移:同时修改CS和IP,jmp 1000:0

根据段内转移指令对IP的修改范围,又分为:

  • 短转移:1个字节,-128 ~ 127
  • 近转移:2个字节,-32768 ~ 32767

操作符 offset

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

1
2
3
4
5
6
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

jmp 指令

jmp为无条件转移指令,需要给出俩种信息:

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

依据位移进行转移的jmp指令

指令 说明
jmp short 标号 段内短转移(8位)
jmp near ptr 标号 段内近转移(16位)

“jmp near ptr 标号”的功能为:

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

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

“jmp far ptr 标号” 实现的是段间转移,又称远转移。

根据指明的标号修改CS和IP。

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

“jmp reg(16位)” 修改IP位寄存器中的值。

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

指令 说明
jmp word ptr 内存单元地址(IP) 段内转移
jmp dword ptr 内存单元地址(CS:IP) 段间转移

jcxz 指令

有条件转移指令,所有的条件转移指令都是短转移。即在对应的机器码中包含转移的位移,而不是目的地址。

格式:

jcxz 标号(8位)(如果(cx) = 0,转移到标号处执行)

loop 指令

同条件转移,所有的循环指令都是短转移

格式:

loop 标号(8位)((cx)=(cx) - 1,如果(cx) != 0,转移到标号处执行)


未完待续