| title | 汇编语言 | |
|---|---|---|
| tags |
|
|
| categories |
|
|
| date | 2024-07-01 15:00:00 -0700 | |
| excerpt | 汇编语言的学习与分享总结! |
CPU 想要进行数据的读写,必须和外部器件进行三类信息的交互:
- 存储单元的地址(地址信息)
- 器件的选择,读或写命令(控制信息)
- 读或写的数据(数据信息)
- 定义:CPU 地址总线宽度为
$N$ ,寻址空间为$2^{N}$ 字节。 - 举例:8086 CPU 的地址总线宽度为 20,那么可以寻址的内存单位个数为
$2^{20}$ =1 MB,称其内存地址空间为 1 MB。 - 将各类存储器看作一个逻辑存储器(统一编址)
- 所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器;
- 每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。
- 举例:8086 PC 机的内存地址空间分配基本情况:
一个典型的CPU由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。
区分内部总线与外部总线:
- 内部总线实现CPU内部各个器件之间的联系。
- 外部总线实现CPU和主板上其他器件的联系。
简单而言,在CPU中:
- 运算器进行信息处理;
- 寄存器进行信息存储;
- 控制器控制各种器件进行工作;
- 内部总线连接各种器件,在它们之间进行数据传送;
其中,寄存器是 CPU 中程序员可以用指令读写的部件,可以通过改变各种寄存器中的内容来实现对 CPU 的控制。
8086CPU中所有寄存器都是16位的,可以存放两个字节的数据。
存放一般性数据的寄存器成为通用寄存器,有AX、BX、CX、DX四个通用寄存器。
为了保证与上一代的8位寄存器兼容,8086CPU中的通用寄存器AX、BX、CX、DX都可以分为两个可以独立使用的8位寄存器。
- AX可分为AH和AL;
- BX可分为BH和BL;
- CX可分为CH和CL;
- DX可分为DH和DL;
-
8086是16位CPU
- 8086的字长(word size)为16bit
- 字节:记为Byte,一个字节由8个bit组成,可以存在8位寄存器中;
- 字:记为Word,对于16位的8086CPU而言,一个字由两个字节组成,这两个字节分别称为这个字的高位字节和低位字节。
-
需要特别注意字的定义:
- 计算机进行数据处理时,一次存取、加工和传送的数据长度称为字。然而,这可能会根据系统的不同而变化。例如,在
32位的系统中,一个字由32位组成。在64位的系统中,一个字由64位组成。所以,虽然16位通常被称为一个字,但这可能会根据具体的计算机系统而有所不同。
- 计算机进行数据处理时,一次存取、加工和传送的数据长度称为字。然而,这可能会根据系统的不同而变化。例如,在
- 通过汇编指令控制 CPU 进行工作,下表有常见的几条指令:
- 注意:汇编语言不区分大小写
- CPU 访问内存单元时要给出内存单元的地址。
- 所有的内存单元构成的存储空间是一个一维的线性空间。
- 每一个内存单元在这个空间中都有唯一的地址,这个唯一的地址称为物理地址。
- 8086 CPU 有
20位地址总线,可传送20位地址,寻址能力为1M。 - 8086 是
16位结构的 CPU- 运算器一次最多可以处理
16位的数据,寄存器的最大宽度为16位。 - 在 8086 CPU 内部处理的、传输、暂存的地址也是
16位,寻址能力也只有64KB!
- 运算器一次最多可以处理
- 方法:物理地址=段地址×16+偏移地址
- 要解决的问题:
- 用两个 16 位的地址(段地址、偏移地址),相加得到一个
20位的物理地址。
- 用两个 16 位的地址(段地址、偏移地址),相加得到一个
- 本质含义:
- CPU 在访问内存时,用一个基础地址(段地址×16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。
- 需要注意的是,内存并没有分段,段的划分来自于 CPU。
- 由上述章节我们知道,8086 CPU 在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址。
- 其中段地址在 8086 CPU 的
4个段寄存器中存放,分别是:CS、DS、SS、ES,当 8086 CPU 要访问内存时由这 4 个段寄存器提供内存单元的段地址。
CS和IP寄存器是 8086 CPU 中两个最关键的寄存器,它们指示了 CPU 当前要读取指令的地址。CS为代码段寄存器,IP为指令指针寄存器。
- 在 8086 CPU 加电启动或者复位后,
CS和IP被设置为CS=FFFFH,IP=0000H,即在 8086 PC 刚启动时,CPU 从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令是 8086 PC 开机后执行的第一条指令。 - 简而言之,
CS和IP寄存器提供了 CPU 要执行指令的地址。 - 我们知道,在内存中,指令和数据没有任何区别,都是二进制信息,CPU 在工作时的时候把有的信息看作指令,有的信息看作数据。
- 那么应该如何区分指令和数据呢?
- 可以看到的是,CPU 将
CS和IP寄存器指向的内存单元中的内容看作指令,因为在任何时候,CS和IP寄存器中的内容分别代表指令的段地址和偏移地址,使用它们合成指令的物理地址,通过物理地址到内存中读取指令码并执行。 - 那么,可以认为如果内存中的一段信息曾被 CPU 执行过的话,那么它所在的内存单元必然被
CS和IP寄存器指向过。
- 事实:执行何处的指令,取决于
CS和IP寄存器。 - 应用:可以通过改变
CS和IP寄存器中的内容,来控制 CPU 要执行的目标指令。
- 例题:
- 分析:
- 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。
- 用
mov指令要访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。 [address]表示一个偏移地址为address的内存单元。- 在内存和寄存器之间传送字型数据时,高地址单元和高 8 位寄存器、 低地址单元和低 8 位寄存器相对应。
mov、add、sub是具有两个操作对象的指令,访问内存中的数据段(对照:jmp是具有一个操作对象的指令,对应内存中的代码段)。- 可以根据自己的推测,在
Debug模式中实验指令的新格式。
- 问题:
- CPU 如何知道一段内存空间被当作栈来使用?
- 执行
push和pop的时候,如何知道哪个单元是栈顶单元?
- 回答:
- 8086 CPU 中,有两个与栈相关的寄存器:
- 栈段寄存器
SS- 存放栈顶的段地址; - 栈顶指针寄存器
SP- 存放栈顶的偏移地址; - 任意时刻,
SS:SP指向栈顶元素。
- 栈段寄存器
- 8086 CPU 中,有两个与栈相关的寄存器:
- 8086 CPU 不保证对栈的操作不会超界。
- 8086 CPU 只知道栈顶在何处(由
SS:SP指示),不知道程序安排的栈空间有多大。 - 我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小。
- 防止入栈的数据太多而导致的超界;
- 防止出栈时栈空了仍然继续出栈而导致的超界。
push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。- 执行
push和pop指令时,SP中的内容自动改变。 - 8086 CPU 提供的栈操作机制:
- 在
SS,SP中存放栈顶的段地址和偏移地址,入栈和出栈指令根据SS:SP指示的地址,按照栈的方式访问内存单元。 push指令的执行步骤:- 1)
SP=SP-2; - 2)向
SS:SP指向的字单元中送入数据。
- 1)
pop指令的执行步骤:- 1)从
SS: SP指向的字单元中读取数据; - 2)
SP=SP+2。
- 1)从
- 在
- 第一步:编写汇编源程序。
- 采用文本编辑器(如 Edit、记事本等)
- 第二步:对源程序进行编译产生目标文件(存储机器码),再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件。可执行文件包含以下两部分内容:
- 程序(从源程序中的汇编指令翻译过来的机器码)和数据(源程序中定义的数据)
- 相关的描述信息(比如,程序有多大、要占用多少内存空间等)
- 第三步:执行可执行文件中的程序。
- 操作系统依照可执行文件中的描述信息,将可执行文件的机器码和数据加载入内存,并进行相关的初始化(比如设置
CS:IP指向第一条要执行的指令),然后由 CPU 执行程序。
- 操作系统依照可执行文件中的描述信息,将可执行文件的机器码和数据加载入内存,并进行相关的初始化(比如设置
- 汇编程序:包含汇编指令和伪指令的文本。
- 汇编指令:有对应机器码的指令,可以被编译为机器指令,最终为 CPU 所执行。
- 伪指令没有对应的机器指令,最终不被 CPU 所执行。伪指令是由编译器来执行的指令,编译器根据伪指令来进行相关的编译工作。
- 汇编程序示例如下:
assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00h
int 21h
codesg ends
endmount c e:\masm
*********************************************************
c:
- 将下面程序保存为
t1.asm文件,将其生成可执行文件t1.exe文件。
assume cs:codesg
codesg segment
mov ax,2000H
mov ss,ax
mov sp,0
add sp,10 ;开辟栈空间
pop ax
pop bx
push ax
push bx
pop ax
pop bx
mov ax,4c00h
int 21h
codesg ends
end- 用 debug 跟踪
t1.exe文件的执行过程,写出每一步执行后,相关寄存器中的内容和栈顶的内容。
PSP的头两个字节是CD 20,用 debug 加载t1.exe文件,查看PSP的内容。
- 例如:
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... (但都是非法指令)
- 标号
s,用于标识地址,在程序写入内存中会直接转换为地址。
- 现象:
- 对策:
- 在
[idata]前显式地写上段寄存器,例mov al,ds:[bx]。
- 在
- 这些出现在访问内存单元的指令中,用于显式地指明内存单元的段地址的
ds:、cs:、ss:或es:”,在汇编语言中称为段前缀。
- 问题:计算
ffff:0~ffff:b字节单元中的数据的和,结果存储在dx中。 - 分析:
- 运算后的结果是否会超出
dx所能存储的范围?ffff:0~ffff:b内存单元中的数据是字节型数据,范围在0~255之间,12个这样的数据相加,结果不会大于65535,可以在dx中存放下。
- 是否可以将
ffff:0~ffff:b中的数据直接累加到dx中?add dx,ds:[addr];(dx)=(dx)+?- 期望:取出内存中的 8 位数据进行相加;实际:取出的是内存中的 16 位数据。
- 是否可以将
ffff:0~ffff:b中的数据直接累加到dl中?add dl,ds:[addr];(dl)=(dl)+?- 期望:取出内存中的 8 位数据相加;实际:取出的是内存中的 8 位数据,但很有可能造成进位丢失。
- 运算后的结果是否会超出
- 对策:取出 8 位数据,加到 16 位的寄存器
mov al,ds:[addr]
mov ah,0
add dx,ax- 代码:
- 问题:将内存
ffff:0~ffff:b中的数据拷贝到0:200~0:20b单元中。
- 代码:
- 说明:
- 使用
es存放目标空间0:200~0:20b的段地址,用ds存放原始空间ffff:0~ffff:b的段地址。 - 在访问内存单元的指令
mov es:[bx],al中,显式地用附加段前缀es:给出单元的段地址,这样就不必在循环中重复设置ds。
- 使用
- 将数据直接写在代码段中:
- 定义一个标号
start,指示代码开始的位置。
- 编程,向内存
0:200~0:23F依次传送数据0~63(3FH),程序中只能使用 9 条指令,包括mov ax,4c00h和int 21h。
assume cs:codesg
codesg segment
mov ax,0000h
mov es,ax
mov bx,0200h
mov cx,0040h
s: mov es:[bx],bl
inc bx
loop s
mov ax,4c00h
int 21h
codesg ends
end- 特点:数据、栈和代码都在一个段。
- 问题:
- 程序显得混乱,编程和阅读时都要注意何处是数据,何处是栈,何处是代码。
- 只应用于要处理的数据很少,用到的栈空间也小,加上没有多长的代码。
- 对策:数据、栈和代码放在不同段。
- 在汇编程序中,用
'……'的方式指明数据是以字符的形式给出的,编译器将把它们转化为相对应的ASCII码。 - 代码:
assume cs:code,ds:data
data segment
db 'yuGin'
db 'Chui'
data ends
code segment
start: mov al,'a'
mov bl,'b'
mov ax,4c00h
int 21h
code ends
end start- 编译为可执行文件,并用
Debug加载查看data段中的内容:
- 说明:
- 先用
r命令分析一下data段的地址,因为DS=075A,需要加上一个256字节称为程序段前缀(PSP)的数据区,从256字节处向后的空间存放的是程序。 - 用
d命令查看data段,Debug以十六进制数码和ASCII码字符形式展示其中的内容。
- 先用
[bx+idata]是表示一个内存单元,它的偏移地址为(bx)+idata(bx中的数值加上idata)。mov ax,[bx+200]/mov ax, [200+bx]的含义:- 将一个内存单元的内容送入
ax, - 这个内存单元的长度为
2字节(字单元),存放一个字, - 内存单元的段地址在
ds中,偏移地址为200加上bx中的数值, - 数学化的描述为:
(ax)=((ds)*16+200+(bx));
- 将一个内存单元的内容送入
- 指令
mov ax,[bx+200]的其他写法(常用):mov ax,[200+bx],mov ax,200[bx],mov ax,[bx].200;
SI寄存器和DI寄存器是 8086 CPU 中和BX功能相近的寄存器- 区别:
SI寄存器和DI寄存器不能够分成两个 8 位寄存器来使用。 BX寄存器:通用寄存器,在计算存储器地址时,常作为基址寄存器来使用,SI寄存器:source index,源变址寄存器,DI寄存器:destination index,目标变址寄存器;
- 区别:































































































