一、汇编基础知识
1.机器语言(二进制语言)
1.1 机器语言是机器指令的集合2.汇编语言的产生
3.汇编语言的组成
1、汇编指令(机器码的助记符)2、伪指令 (由编译器执行)
3、其它符号(由编译器识别)
4.存储器
4.1 CPU 是计算机的核心部件.它控制整个计算机的运作并进行运算,要想让一个CPU 工作,就必须向它提供指令和数据。
- 指令告诉CPU怎么运算,数据就是告诉CPU运算什么
指令和数据在存储器中存放,也就是平时所说的内存。
4.2 离开了内存,性能再好的CPU也无法工作。
4.3 磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,
就无法被CPU 使用。
硬盘 -> 内存 -> CPU
5.指令和数据
5.1 指令和数据是应用上的概念。5.2 在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
5.3 二进制数据
1000 1001 1101 1000
─> 8 9 D 8 H (数据)
hex 十六进制 123456789ABCDEF 0xff 8D8H
binary 二进制
1111 2^0 + 2^1 + 2^2 + 2^3 = 15 (8+4+2+1) 0~15
1 2 4 8
2^4-1
总公式: 2^n - 1 4字节 2^32 - 1 = 4G
练习: 1011 8+2+1 = 11
1000100111011000
─> MOV AX,BX (程序)
十进制D [一般习惯都不加]
二进制B
八进制Q 111
十六进制H
6.存储单元(Byte字节)
1KB=1024B1MB=1024KB 1024*1024
1GB=1024MB
1TB=1024GB
7.CPU对存储器和读写
CPU要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行三类信息的交互:
存储单元的地址 (地址信息)(硬盘地址,内存地址,显卡显存地址)
器件的选择,读或写命令(控制信息)
读或写的数据 (数据信息)
地址总线 传输 地址信息
数据总线 传输 数据信息
控制总线 传输 控制信息
CPU是通过地址总线来指定存储单元的。
地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。
CPU 系统 软件程序
64 64 64 =>真正的64位的运算速度
8. 地址总线
一个CPU有N根地址总线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
2^n
1Byte = 8 bit 2^8 - 1 = 256 - 1 (0~255)
64位 8字节
9. 数据总线(高速公路)
CPU与内存或其它器件之间的数据传送是通过数据总线来进行的。数据总线的宽度决定了CPU和外界的数据传送速度。(传输量)
10. 控制总线
CPU对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。
有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。
11.内存地址空间的概念
二、寄存器(CPU工作原理)
2.1CPU概述
1.1 一个典型的CPU由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。1.2 内部总线实现CPU内部各个器件之间的联系。
1.3 外部总线实现CPU和主板上其它器件的联系。
2.2 寄存器的概述
2.11 8086CPU有14个寄存器 它们的名称为:AX、BX、CX、DX、SI、DI、SP、BP、
IP、CS、SS、DS、ES、PSW。
这些寄存器以后会陆续介绍
2.12 通用寄存器
2.2.1 8086CPU所有的寄存器都是16位的,可以存放两个字节(即一个字)。
word
2.2.2 AX、BX、CX、DX 通常用来存放一般性数据被称为通用寄存器。
2.2.3 8086上一代CPU中的寄存器都是8位的;
为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
AX可以分为AH和AL;
BX可以分为BH和BL;
CX可以分为CH和CL;
DX可以分为DH和DL。
2.3 字在寄存器中存储
word = 2Byte2.4 几条汇编指令
汇编指令不区分大小写AX = 8226H BX = 8226H
ADD AX,BX => AX = AX + BX
1 044C = 8226H + 8226H 044c (溢出部分将舍弃)
注:AL的溢出不会进入AH中
2.5 检测点2.1
(1)mov ax,62627 AX=F4A3H
mov ah,31H AX=31A3H
mov al,23H AX=3123H
add ax,ax AX=6246H
mov bx,826CH BX=826CH
mov cx,ax CX=6246H
mov bx,ax AX=826CH
add ax,bx AX=04D8H ax = ax + bx
mov al,bh AX=0482H
mov ah,bl AX=6C82H
add ah,ah AX=D882H
add al,6 AX=D888H
add al,al AX=D810H
mov ax,cx AX=6246H
(2)
mov ax,2 AX=2
add ax,ax AX=4
add ax,ax AX=8
add ax,ax AX=16
2.6 物理地址
CPU访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。
每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址。
2.7 16位结构的CPU
概括的讲,16位结构描述了一个CPU具有以下几个方面特征:- 运算器一次最多可以处理16位的数据。
- 寄存器的最大宽度为16位。
- 寄存器和运算器之间的通路是16位的。
2^10 = 1M = 1024K 64K
2.8 8086 CPU给出物理地址的方法
2.8.1 8086有20位地址总线,可传送20位地址,寻址能力为1M。2.8.2 8086内部为16位结构,它只能传送16位的地址,
表现出的寻址能力却只有64K。
2.8.3 8086CPU采用一种在内部用两个16位地址合成的方法来形成
一个20位的物理地址。
2.8.4 地址加法器合成物理地址的方法: (血汗工厂)
物理地址 = 段地址×16 + 偏移地址
20 16+4=20 16
1111 1111 1111 1111
1111 1111 1111 1111 0000
2.8.5 由段地址X16引发的讨论
(1)一个数据的二进制形式左移1位,相当于该数据乘以2;
(2)一个数据的二进制形式左移N位,相当于该数据乘以2的N次方;
(3)地址加法器如何完成段地址×16的运算?
以二进制形式存放的段地址左移4位。
0111 7
1110 14 7*2 乘以2相当于左移1位
0111 0000 112 7*16 乘以16相当于左移4位
一个数据的八进制形式左移1位,相当于该数据乘以8
一个数据的十进制形式左移1位,相当于该数据乘以10
一个数据的十六进制形式左移1位,相当于该数据乘以16
2.9 段的概念
错误认识:内存被划分成了一个一个的段,每一个段有一个段地址。
其实:
内存并没有分段,段的划分来自于CPU,由于8086CPU用
“(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,
使得我们可以用分段的方式来管理内存。
以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,
用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。
(1)段地址×16 必然是 16的倍数,所以一个段的起始地址也一定是16的倍数;
(2)偏移地址为16位,16 位地址的寻址能力为 64K,所以一个段的长度最大为64K。
检测点2.2
物理地址 = 段地址*16 + 偏移地址
偏移量:0 ~ FFFFH
(1) MAX : 0001H * 16 + FFFFH = 0010H + FFFFH = 1000FH
MIN : 0001H * 16 + 0 = 0010H + 0 = 10H
10H ~ 1000FH
(2) 物理地址: 20000H ( y = a*16 + b ) (y = 20000H 0<= b <= FFFFH 求a的范围?)
偏移量为0 偏移量位FFFFH
段地址 2000H 1001H
最小为:1001H 最大为:2000H
2.10 段寄存器
2.10.1 段寄存器就是提供段地址的。8086CPU有4个段寄存器:
CS、DS、SS、ES
Code segment Data segment
Stack segment Extract segment
当8086CPU要访问内存时,由这4个段寄存
器提供内存单元的段地址。
2.11 CS和IP寄存器 (8086都是16位)
CS和IP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。- CS为代码段寄存器;
- IP为指令指针寄存器
2.12 8086 PC工作的过程
2.12.1 8086 PC读取和执行指令相关流程
2.12.2 8086 PC工作过的的描述
(1)从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
(2)IP = IP + 所读取指令的长度,从而指向下一条指令;
(3)执行指令。 转到步骤 (1),重复这个过程。
在 8086CPU 加电启动或复位后( 即 CPU刚开始工作时)CS和IP被设置
为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元
中读取指令执行,FFFF0H单元中的指令是8086PC机开机后执行的第一条指令。
改变CS和IP的值就可以控制CPU的执行的指令
2.12.3 CPU根据什么将内存中的信息看作指令?
CPU将CS:IP指向的内存单元中的内容看作指令。
2.13 修改CS和IP的指令
在CPU中,程序员能够用指令读写的部件只有寄存器,
程序员可以通过改变寄存器中的内容实现对CPU的控制。
CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通
过改变CS、IP中的内容来控制CPU执行目标指令。
我们如何改变CS、IP的值呢?
(1)同时修改CS、IP的内容:
jmp 段地址:偏移地址
jmp 2AE3:3 =>2AE3H * 16 + 3 = 2AE33H
jmp 3:0B16 =>3*16 + 0B16 = 0B46H
功能:用指令中给出的段地址修改CS,偏移地址修改IP。
(2) 仅修改IP的内容:
jmp 某一合法寄存器
jmp ax (类似于 mov IP,ax)
jmp bx
功能:用寄存器中的值修改IP。
2.14 问题分析
(1)mov ax,6622H
(2) jmp 1000:3
(3) mov ax,0000
(4) mov bx,ax
(5) jmp bx
(6) mov ax,0123H
(7) 转到第(3)步执行
2.15 代码段
对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。
可以将长度为 N( N≤64KB )的一组代码,存在一组地址连续、起始地址为 16的倍数的内存单元中,
这段内存是用来存放代码的,从而定义了一个代码段。
如何使得代码段中的指令被执行呢?
将一段内存当作代码段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,
就自动地将我们定义得代码段中的指令当作指令来执行。
CPU 只认被 CS:IP 指向的内存单元中的内容为指令。
所以要将CS:IP指向所定义的代码段中的第一条指令的首地址。
CS = 123BH,IP = 0000H。
2.16 检测点3
修改4次:
第一次在CPU读取"mov ax,bx"后
第二次在CPU读取"sub ax,ax"后
第三次在CPU读取"jmp ax"后
第四次在CPU执行完"jmp ax"后;
IP最后的值为0
2.17 实验1
r 查看和修改寄存器的值
r 查看寄存器的值
r ax(cs ip等)修改寄存器ax(cs ip等)的值
d 查看内存中内容
d 1000:0 1f (1f表示范围)
e 修改内存的内容
e 1000:0 30 31 32 33 34 35 36 37 (一次性修改)
e 1000:0 'a' 'b' 'c'
e 1000:0 (询问式修改)
u 以汇编指令的形式查看机器码
u 1000:0 1f (1f表示范围)
写入指令:
方式1: e 1000:0 b8 01 00 b9 02 00 01 08
方式2: a 1000:0 以汇编指令的形式写入
t 执行下一条指令(执行之前可以通过r命令修改cs:ip的值)
三、寄存器(内存访问)
1.1 内存中字的存储
20000 0x4E 20a) Little - Endian (小端)就是低位字节放在内存的低地址端,高位字节放在内存的高地址端
b) Big - Endian (大端)就是高位字节放在内存的低地址端,低位字节放在内存的高地址端
网络字节顺序(也就是大端模式)
int a = 20000; 0x00004e20
int* p = &a;
printf("p=0x%p\n",p);
p
0x004CFE44 20
0x004CFE45 4e
0x004CFE46 56
0x004CFE47 43
注意:字节单元:一个内存单元存放一个字节。字单元:存放一个字型数据(16)
的内存单元。由两个地址连续的内存单元组成
1.2 DS和[address]
1.2.1 内存单元地址- CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址;
- 在8086PC中,内存地址由段地址和偏移地址组成。
- 8086CPU中有一个 DS寄存器,通常用来存放要访问的数据的段地址。
1.2.2 mov指令传送功能
(1)将数据直接送入寄存器;
(2)将一个寄存器中的内容送入另一个寄存器中。
(3)mov 指令 还可以将一个内存单元中的内容送入一个寄存器,
或者将寄存器中的数据写入内存单元
1.2.3 字的传送(练习)
(1)
1000:0 10000H 1123H
ax = 1123H
1000:2 10002H 6622H
bx = 6622H
1000:1 10001H 2211H
cx = 2211H
1000:1 10001H 2211H
add bx,[1] => add bx,2211H
bx = bx + 2211H = 6622H + 2211H = 8833H
1000:2 10002H 6622H
add cx,[2] = add cx,6622H
cx = cx + 6622H = 2211H + 6622H = 8833H
1.2.4 mov、add、sub指令
1.2.5 数据段
将一段内存当作数据段,是我们在编程时的一种安排,
我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,
再根据需要,用相关指令访问数据段中的具体单元。
1.2.6 检测点3.1
(1)
ds = 1
MOV AX,[0000]
1:0 1*16 + 0 = 10H AX = 2662H
MOV BX,[0001]
1:1 1*16 + 1 = 11H BX = E626H
MOV AX,BX AX = E626H
MOV AX,[0000] AX = 2662H
MOV BX,[0002]
1:2 1*16 + 2 = 12H BX = D6E6H
ADD AX,BX AX = FD48H
ADD AX,[0004]
1:4 14H AX = FD48H + 2ECCH = 2C14H 舍去溢出部分
mov ax,0 ax = 0
mov al,[0002]
1:2 12H al = e6H ax = 00e6H
mov bx,0 bx = 0
mov bl,[000c] bl = 26H BX = 0026H
add al,bl al = 0cH ax = 000CH
(2)
MOV AX,6622H CS=2000H IP=0 DS=1000H AX=6622H BX=0
JMP 0FF0:0100 CS=0FF0 IP=100H DS=1000H AX=6622H BX=0
MOV AX,2000H CS=0FF0 IP=103H DS=1000H AX=2000H BX=0
MOV DS,AX CS=0FF0 IP=105H DS=2000H AX=2000H BX=0
MOV AX,[0008] CS=0FF0 IP=108H DS=2000H AX=C389H BX=0
MOV AX,[0002] CS=0FF0 IP=10BH DS=2000H AX=EA66H BX=0
1.3 栈
1.3.1 栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。
1.3.2 栈有两个基本的操作:入栈和出栈。
入栈:将一个新的元素放到栈顶;
出栈:从栈顶取出一个元素。
栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
栈的操作规则:LIFO(Last In First Out,后进先出)
1.3.3 CPU提供的栈机制
(1) 8086CPU提供入栈和出栈指令: (最基本的)
PUSH(入栈)
POP (出栈)
push ax:将寄存器ax中的数据送入栈中;
pop ax :从栈顶取出数据送入ax。
8086CPU的入栈和出栈操作都是以字为单位进行的。
(2) 8086CPU中,有两个寄存器:
段寄存器SS 存放栈顶的段地址
寄存器SP 存放栈顶的偏移地址
任意时刻,SS:SP指向栈顶元素。
1.3.4 push 指令的执行过程
push ax
(1)SP=SP–2;
(2)将ax中的内容送入SS:SP指向的内存单元处,
SS:SP此时指向新栈顶。
任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,
也就不存在栈顶元素,所以SS:SP 只能指向栈的最底部单元下面的单元,
.该单元的偏移地址为栈最底部的字单元的偏移地址+2,栈最底部字单元
的地址为1000:000E,所以栈空时,SP=0010H。
1.3.4 pop 指令的执行过程
(1) pop ax
(1)将SS:SP指向的内存单元处的数据送入ax中;
(2)SP = SP+2,SS:SP指向当前栈顶下面的单元,
以当前栈顶下面的单元为新的栈顶。
注意:
出栈后,SS:SP指向新的栈顶 1000EH,pop操作前的栈顶元素,
1000CH 处的2266H 依然存在 ,但是,它已不在栈中。
当再次执行push等入栈指令后,SS:SP移至1000CH,并在里面写入新的数据,
它将被覆盖。
1.3.5 栈顶超界
(1) 当栈满的时候再使用push指令入栈,
栈空的时候再使用pop指令出栈,
都将发生栈顶超界问题。
(2) 栈顶超界是危险的。
(3) 8086CPU的工作机理,只考虑当前的情况:
当前栈顶在何处;
当前要执行的指令是哪一条。
1.3.6 栈与内存
栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。
1.3.7 push、pop指令
push和pop指令的格式(1)
push 寄存器:将一个寄存器中的数据入栈
pop寄存器:出栈,用一个寄存器接收出栈的数据
例如:push ax
pop bx
push和pop指令的格式(2)
push 段寄存器:将一个段寄存器中的数据入栈
pop段寄存器:出栈,用一个段寄存器接收出栈的数据
例如:push ds
pop es
push和pop指令的格式(3)
push内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
pop 内存单元:出栈,用一个内存字单元接收出栈的数据
例如:push [0]
pop [2]
1.3.9 问题分析
(1) 3.7 10000H ~ 1000FH 16字节
mov ax,1000H
mov ss,ax
mov sp,0010H
push ax
push bx
push ds
(2) 3.8 10000H ~ 1000FH ss:1000H sp=?
mov ax,1000H
mov ss,ax
mov sp,0010H
mov ax,001AH
mov bx,001BH
push ax
push bx
sub ax,ax
sub bx,bx
pop bx
pop ax
结论:
从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时 ,
出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶 ,
所以在恢复时,要最先出栈。
(3) 3.9
mov ax,1000H
mov ss,ax
mov sp,0010H
mov ax,001AH
mov bx,001BH
push ax
push bx
sub ax,ax
sub bx,bx
pop ax
pop bx
(4)3.10
栈内存示意:
10000H 66
10001H 22
10002H <- sp
mov ax,1000H
mov ss,ax
mov sp,0002H
mov ax,2266H
push ax
1.3.11 栈段
我们可以将长度为 N(N ≤64K )的一组地址连续、起始地址为16的倍数的内存单元,
当作栈来用,从而定义了一个栈段。
怎么算出来的
1.3.13 段的综述
我们可以将一段内存定义为一个段,用一个段地址指示段,
用偏移地址访问段内的单元。这完全是我们自己的安排。
我们可以用一个段存放数据,将它定义为“数据段”;
我们可以用一个段存放代码,将它定义为“代码段”;
我们可以用一个段当作栈,将它定义为“栈段”;
我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:
- 对于数据段,将它的段地址放在 DS中,用mov、add、sub等访
问内存单元的指令时,CPU就将我们定义的数据段中的内容当作
数据段来访问;
- 对于代码段,将它的段地址放在 CS中,将段中第一条指令的偏移地址放在IP中,
这样CPU就将执行我们定义的代码段中的指令;
- 对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地置放在 SP 中,
这样CPU在需要进行栈操作的时候,比如执行 push、pop 指令等,就将我们
定义的栈段当作栈空间来用。
总结:
一段内存,可以既是代码的存储空间,又是数据的存储空间,还可以是栈空间,
也可以什么也不是。关键在于CPU中寄存器的设置,即:CS、IP、SS、SP、DS的指向。
检测点3.2:
(1)
把2000H安排为栈段
mov ax,2000H
mov ss,ax
mov sp,0010H
(2)
把1000H安排为栈段
mov ax,1000H
mov ss,ax
mov sp,0H
实验任务:
mov ax,[0] ;ax = C0EAH
add ax,[2] ;ax = C0FCH
mov bx,[4] ;bx = 31F0H
mov bx,[6] ;bx = 30F0H
push ax ;sp = 00FEH 修改的内存单元地址是2200:00FE 内容为C0FCH
push bx ;sp = 00FCH,修改的内存单元地址是2200:00FC 内容为6021H
pop ax ;sp = 00FCH, ax = 6021H
pop bx ;sp = 00FEH, bx = COFCH
push [4] ;sp = 00FEH ,修改的内存单元地址是2200:00FE 内容为2F31H
push [6] ;sp = 00FCH ,修改的内存单元地址是2200:00FC 内容为30F0H
四、汇编程序
1.一个源程序从写出到执行的过程
一个汇编语言程序从写出到最终执行的简要过程:
编写-->编译-->链接-->执行
2.可执行文件
可执行文件中包含两部分内容:
- 程序(从原程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如:程序有多大、要占多少内存空间等)
3.执行可执行文件中的程序
- 在操作系统中,执行可执行文件中的程序。
- 操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,
并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序。
4.源程序
4.1 汇编指令
有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行。
4.2 伪指令
没有对应的机器码的指令,最终不被CPU所执行。
4.3 谁来执行伪指令呢?
伪指令是由编译器来执行的指令,编译器根据伪指令来进行
相关的编译工作。
4.4 源程序
4.4.1 源程序中的“程序”
汇编源程序:
伪指令 (编译器处理)
汇编指令 (编译为机器码)
程序:源程序中最终由计算机执行、处理的指令或数据。
4.4.2 我们可以将源程序文件中的所有内容称为源程序,
将源程序中最终由计算机执行处理的指令或数据 ,成为程序。
4.4.3 程序最先以汇编指令的形式存在源程序中,经编译、
连接后转变为机器码,存储在可执行文件中,
4.4.4 标号
一个标号指代了一个地址。
4.4.5 程序返回
应该在程序的末尾添加返回的程序段。
mov ax,4c00H
int 21H
用T命令担不执行程序中的每一条指令,
并观察每条指令的执行结果,到了 int 21,
我们要用P命令执行:
这两条指令所实现的功能就是程序返回
4.5 编译
配置环境变量 PATH = xxxx/masm6.15/
masm 查看是否配置成功
masm 1.asm 编译程序,产生1.obj中间文件
4.6 链接
link 1.obj 链接程序,生成1.exe执行文件
4.7 执行程序
4.8 可执行文件中的程序装入内存并运行的原理
edit -> masm -> link -> command -> CPU
ml 1.asm
4.9 程序执行过程的跟踪
>debug 1.exe
-u cs:0 查看指令
五、[bx]和loop指令
1.[bx]和内存单元的描述
1.1 [bx]是什么呢?
和[0]有些类似,[0]表示内存单元,它的偏移地址是0。
1.2 我们要完整地描述一个内存单元,需要两种信息:
(1)内存单元的地址;
(2)内存单元的长度(类型)。
我们用[0]表示一个内存单元时,0 表示单元的偏移地址,
段地址默认在ds中,单元的长度(类型)可以由具体指令中的
其他操作对象(比如说寄存器)指出。
1.3 [bx]同样也表示一个内存单元,它的偏移地址在bx中,
比如下面的指令:
mov ax,[bx]
mov al,[bx]
2.loop指令(循环指令)
2.1 指令的格式是:loop 标号,CPU 执行loop指令的时候,要进行两步操作:
① (cx)=(cx)-1;
② 判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行。
2.2 从上面的描述中,我们可以看到,cx中的值影响着loop指令的执行结果。
通常(注意,我们说的是通常)我们用loop指令来实现循环功能,
cx 中存放循环次数。
2.3 从上面的过程中,我们可以总结出用cx和loop 指令相配合实现循环
功能的三个要点:
(1)在cx中存放循环次数;
(2)loop 指令中的标号所标识地址要在前面;
(3)要循环执行的程序段,要写在标号和loop 指令的中间。
2.4 用cx和loop指令相配合实现循环功能的程序框架如下:
mov cx,循环次数
s:循环执行的程序段
loop s
问题分析:5.2
//循环了236次
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
//循环了123次
assume cs:code
code segment
mov ax,0
mov cx,123
s:add ax,236
loop s
mov ax,4c00h
int 21h
code ends
end
3.loop和[bx]的联合应用
在实际编程中,经常会遇到,用同一种方法处理地址连续的
内存单元中的数据的问题。
我们需要用循环来解决这类问题,同时我们必须能够在每次循环
的时候按照同一种方法来改变要访问的内存单元的地址。
这时,我们就不能用常量来给出内存单元的地址
(比如[0]、[1]、[2]中,0、1、2是常量),而应用变量。
“mov al,[bx]”中的 bx就可以看作一个代表内存单元地址的变量,
我们可以不写新的指令,仅通过改变bx中的数值,改变指令访问的内存单元。
4.段前缀
指令“mov ax,[bx]”中,内存单元的偏移地址由bx给出,
而段地址默认在ds中。
我们可以在访问内存单元的指令中显式地给出内存单元
的段地址所在的段寄存器。
这些出现在访问内存单元的指令中,用于显式地指明内存单元的段
地址的“ds:”、“cs:”、“ss:”或“es:”,在汇编语言中称为段前缀。
六、更灵活的定位内存地址的方法
1 and和or指令
(1)and 指令:逻辑与指令,按位进行与运算。
如 mov al, 01100011B
and al, 00111011B
执行后 al = 00100011B
通过该指令可将操作对象的相应位设为0,其他位不变。
例如: al = 1111 1111B
将al的第6位设为0: and al,1011 1111B
将al的第7位设为0: and al,0111 1111B
将al的第0位设为0: and al,1111 1110B
同C语言里面的"&"
(2)or 指令:逻辑或指令,按位进行或运算
如 mov al, 01100011B
or al, 00111011B
执行后 al = 01111011B
通过该指令可将操作对象的相应位设为1,其他位不变。
例如:al = 0000 0000B
将al的第6位设为1: and al,0100 0000B
将al的第7位设为1: and al,1000 0000B
将al的第0位设为1: and al,0000 0001B
同C语言里面的"|"
2.[bx+idata]
在前面,我们可以用[bx]的方式来指明一个内存单元,
我们还可以用一种更为灵活的方式来指明内存单元:
[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata
(bx中的数值加上idata)。
我们看一下指令mov ax,[bx+200]的含义:
将一个内存单元的内容送入ax,这个内存单元的长度为2字节(字单元),
存放一个字,偏移地址为bx中的数值加上200,段地址在ds中。
数学化的描述为: (ax)=((ds)*16+(bx)+200)
3.SI和DI
SI和DI是8086CPU中和bx功能相近的寄存器,
SI和DI不能够分成两个8 位寄存器来使用。
下面的三组指令实现了相同的功能:
(1) mov bx,0
mov ax,[bx]
(2) mov si,0
mov ax,[si]
(3) mov di,0
mov ax,[di]
(4) mov bx,0
mov ax,[bx+123]
(5) mov si,0
mov ax,[si+123]
(6) mov di,0
mov ax,[di+123]
4. [bx+si]和[bx+di]
在前面,我们用[bx(si或di)]和[bx(si或di)+idata] 的方式来指明
一个内存单元,我们还可以用更灵活的方式:
[bx+si]
[bx+di]
[bx+si]表示一个内存单元,它的偏移地址为(bx)+(si)(即bx中的数值加上si中的数值)。
5. [bx+si+idata]和[bx+di+idata]
[bx+si+idata]表示一个内存单元
它的偏移地址为(bx)+(si)+idata。
(即bx中的数值加上si中的数值再加上idata)
6.如果我们比较一下前而用到的几种定位内存地址的方法(可称为寻址方式)
就可以发现有以下几种方式:
(1)[iata] 用一个常量来表示地址,可用于直接定位一个内存单元;
(2)[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
(3)[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元;
(4)[bx+si]用两个变量表示地址;
(5)[bx+si+idata] 用两个变量和一个常量表示地址。
直接寻址
寄存器间接寻址
寄存器相对寻址
基址变址寻址
相对基址变址寻址
七、转移指令的原理
8086CPU的转移指令分为以下几类:
无条件转移指令 (如:jmp)
条件转移指令
循环指令(如:loop)
过程 (函数调用)
中断
1.jmp 指令
jmp为无条件转移,可以只修改IP,也可以同时修改CS和IP;
jmp指令要给出两种信息:
转移的目的地址
转移的距离(段间转移、段内短转移,段内近转移)
(1)jmp short 标号(转到标号处执行指令)
这种格式的 jmp 指令实现的是段内短转移,它对IP的修改范围为
-128~127,也就是说,它向前转移时可以最多越过128个字节,
向后转移可以最多越过127个字节。
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
左面的程序执行后, ax中的值为 1 ,因为执行 jmp short s 后 ,
越过了add ax,1 ,IP 指向了标号 s处的 inc ax。也就是说,
程序只进行了一次ax加1操作。
(2)依据位移进行转移的jmp指令
jmp short s指令的读取和执行过程:
(1)(CS)=0BBDH,(IP)=0006,CS:IP指向EB 03(jmp short s的机器码);
(2)读取指令码EB 03进入指令缓冲器;
(3)(IP)=(IP)+所读取指令的长度=(IP)+2=0008,CS:IP指向add ax,1;
(4)CPU指行指令缓冲器中的指令EB 03;
(5)指令EB 03执行后,(IP)=000BH,CS:IP指向inc ax。
注意: 要转移的目的地址是CS:000B,而CPU 执行 EB 03时,当前的(IP)=0008,
如果将当前的IP值加3,使(IP)=000BH,CS:IP就可以指向目标指令。
在转移指令EB 03中并没有告诉CPU要转移的目的地址,却告诉了
CPU 要转移的位移,即将当前的IP向后移动3个字节。
补码:
-9的补码怎么算出来的
+9 原码: 0000 1001 =>09H
反码: 1111 0110
+ 1
——————————————
-9补码: 1111 0111 =>F7H
结论:
CPU执行 jmp short 标号 指令时并不需要转移的目的地址,
只需要知道转移的位移就行了。
(3) 实际上,指令“jmp short 标号”的功能为(IP)=(IP)+8位位移。
(1)8位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
(2)short指明此处的位移为8位位移;
(3)8位位移的范围为-128~127,用补码表示
(4)8位位移由编译程序在编译时算出。
(4) 指令“jmp near ptr 标号”的说明:
(1)16位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
(2)near ptr指明此处的位移为16位位移,进行的是段内近转移;
(3)16位位移的范围为
-32769~32767,用补码表示;
(4)16位位移由编译程序在编译时算出。
(5)jmp near ptr 标号
它实现的时段内近转移。
指令“jmp near ptr 标号”的功能为:
(IP)=(IP)+16位位移。
指令“jmp near ptr 标号”的说明:
(1)16位位移=“标号”处的地址-jmp指令后的第一个字节的地址;
(2)near ptr指明此处的位移为16位位移,进行的是段内近转移;
(3)16位位移的范围为
-32769~32767,用补码表示;
(4)16位位移由编译程序在编译时算出。
(6)指令 “jmp far ptr 标号”
实现的是段间转移,又称为远转移。
指令 “jmp far ptr 标号” 功能如下:
(CS)=标号所在段的段地址;
(IP)=标号所在段中的偏移地址。
far ptr指明了指令用标号的段地址和偏移地址修改CS和IP。
(7) 指令格式:jmp 16位寄存器
功能:IP =(16位寄存器)
这种指令我们在前面的课程(参见2.11节)中已经讲过,这里就不再详述。
(8)转移地址在内存中的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
2) jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址处开始存放着两个字,高地址处的字是转移的目的段地址,
低地址处是转移的目的偏移地址。
(CS)=(内存单元地址+2)
(IP)=(内存单元地址)
内存单元地址可用寻址方式的任一格式给出。
mov ax,0123H
mov ds:[0],ax
mov word ptr ds:[2],0
jmp 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。
2. jcxz指令
(1)jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中
包含转移的位移,而不是目的地址。对IP的修改范围都为-128~127。
指令格式:jcxz 标号
(如果(cx)=0,则转移到标号处执行。)
(2)jcxz 标号 指令操作:
- 当(cx)=0时,(IP)=(IP)+8位位移)
8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)=0时,什么也不做(程序向下执行)。
- jcxz 标号 指令操作:
当(cx)=0时,(IP)=(IP)+8位位移)
8位位移=“标号”处的地址-jcxz指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)!=0时,不进行跳转(程序向下执行)。
我们从 jcxz的功能中可以看出,指令“jcxz 标号”的功能相当于:
if((cx)==0)
jmp short 标号;
(这种用C语言和汇编语言进行的综合描述,或许能使你对有条
件指令理解得更加清楚。)
检测点9.2
s:mov ch,0
mov cl,[bx]
jcxz ok ;当cx=0时,CS:IP指向ok
inc bx
3.loop指令
(1) loop指令为循环指令,所有的循环指令都是短转移,在对应的机
器码中包含转移的位移,而不是目的地址。对IP的修改范围
都为-128~127。
指令格式:loop 标号
((cx))=(cx)-1,如果(cx)≠0,转移到标号处执行。
(2)loop 标号 指令操作:
(1)(cx)=(cx)-1;
(2)如果(cx)≠0,(IP)=(IP)+8位位移。
8位位移 = “标号”处的地址 - loop指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
当(cx)=0,什么也不做(程序向下执行)
我们从loop的功能中可以看出,指令“loop 标号”的功能相当于:
(cx)--;
if((cx)≠0)
jmp short 标号
检点的9.3
inc cx
4. 根据位移进行转移的意义
前面我们讲到:
jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号
等几种汇编指令,它们对 IP的修改是根据转移目的
地址和转移起始地址之间的位移来进行的。在它们
对应的机器码中不包含转移的目的地址,而包含的起
始地址到目的地址的位移。
这样设计,方便了程序段在内存中的浮动装配。
5.编译器对转移位移超界的检测
注意,根据位移进行转移的指令,它们的转移范围受到转移位移的限制,
如果在源程序中出现了转移范围超界的问题,在编译的时候,编译器将
报错。
八、 call 和 ret 指令
1.ret 和 retf
(1)ret指令用栈中的数据,修改IP的内容,从而实现近转移;
CPU执行ret指令时,进行下面两步操作:
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
(2)retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移;
CPU执行retf指令时,进行下面四步操作:
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
(3)(CS)=((ss)*16+(sp))
(4)(sp)=(sp)+2
可以看出,如果我们用汇编语法来解释ret和retf指令,则:
CPU执行ret指令时,相当于进行:
pop IP
CPU执行retf指令时,相当于进行:
pop IP
pop CS
检测点10.1
分析:retf执行后 CS = 1000H IP:0000H
mov ax,1000H ;段地址cs入栈
mov ax,0000H ;偏移地址ip入栈
执行retf指令时,相当于进行:
pop IP
pop CS
根据栈先进后出原则,应先将段地址CS入栈,
再将偏移地址ip入栈
2. call 指令
(1) CPU执行call指令,进行两步操作:
(1)将当前的 IP 或 CS和IP 压入栈中;
(2)转移。
call 指令不能实现短转移,除此之外,call指令实现转移的方法
和 jmp 指令的原理相同,下面的几个小节中 ,我们以给出转移目
的地址的不同方法为主线,讲解call指令的主要应用格式。
(2)call 标号(将当前的 IP 压栈后,转到标号处执行指令)
(3)CPU执行此种格式的call指令时,进行如下的操作:
(1) (sp) = (sp) – 2
((ss)*16+(sp)) = (IP)
(2) (IP) = (IP) + 16位位移
3. 依据位移进行转移的call指令
(1)call 标号
16位位移 = “标号”处的地址 - call指令后的第一个字节的地址;
16位位移的范围为 -32768~32767,用补码表示;
16位位移由编译程序在编译时算出。
(2)如果我们用汇编语法来解释此种格式的 call指令,则:
CPU 执行指令“call 标号”时,相当于进行:
push IP
jmp near ptr 标号
检测点10.2
mov ax,0 ;ax=0,ip指向1000:3
call s ;ax=0,
;读取call指令后ip=6, push ip
;jmp near ptr s
inc ax ;被跳过的代码
s:pop ax ;ax = 6
答案:ax = 6
4. 转移的目的地址在指令中的call指令
前面讲解的call指令,其对应的机器指令中并没有转移的目的地址 ,
而是相对于当前IP的转移位移。
指令“call far ptr 标号”实现的是段间转移。
CPU执行“call far ptr 标号”这种格式的call指令时的操作:
(1) (sp) = (sp) – 2
((ss) ×16+(sp)) = (CS)
(sp) = (sp) – 2
((ss) ×16+(sp)) = (IP)
(2) (CS) = 标号所在的段地址
(IP) = 标号所在的偏移地址
从上面的描述中可以看出,如果我们用汇编语法来解释此种格式的 call 指令,则:
CPU 执行指令 “call far ptr 标号” 时,相当于进行:
push CS
push IP
jmp far ptr 标号
检测点10.3
mov ax,0 ;ax=0, ip指向1000:3
call far ptr s ;读取call之后,ip=8, push cs push ip,ip指向1000:8
;执行call后, ip=9 cs=1000H
inc ax ;被跳过的指令
s:pop ax ;ax=8H
add ax,ax ;ax=10H
pop bx ;bx = 1000H
add ax,bx ;ax = 1010H
答案:ax = 1010H
5. 转移地址在寄存器中的call指令
(1)指令格式:call 16位寄存器
功能:
(sp) = (sp) – 2
((ss)*16+(sp)) = (IP)
(IP) = (16位寄存器)
(2)指令格式:call 16位寄存器
汇编语法解释此种格式的 call 指令,CPU执行call 16位reg时,相当于进行:
push IP
jmp 16位寄存器
检测点10.4
mov ax,6 ;ax=6,ip指向1000:3
call ax ;读取call指令后,ip=5,push ip
;执行call指令后,ip=6
inc ax ;被跳过的代码
mov bp,sp ;将得到栈顶指针赋值给bp
add ax,[bp] ;ax = 6 + 5 = 0bH
"call ax" 是先将该指令后的第一个字节偏移地址ip入栈,
再转到偏移地址为ax出执行指令
6. 转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式:
(1) call word ptr 内存单元地址
(2) call dword ptr 内存单元地址
(1) call word ptr 内存单元地址
汇编语法解释:
IP
jmp word ptr 内存单元地址
call word ptr 内存单元地址(示例)
比如下面的指令:
mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]
执行后,(IP)=0123H,(sp)=0EH
(2) call dword ptr 内存单元地址
汇编语法解释:
push CS
push IP
jmp dword ptr 内存单元地址
call dword ptr 内存单元地址(示例)
比如,下面的指令:
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
执行后,(CS)=0,(IP)=0123H,(sp)=0CH