目录
第1章 基础知识
1.1 存取器
cpu是计算机的核心部件,它控制着整个计算机的运作进行计算。要想让一个cpu工作,就必须向他提供指令和数据。指令和数据在存储器中存放,就是内存。
1.2 指令与数据
在内存所磁盘中,指令和数据没有任何区别,都是二进制信息。
列如:内存中的二进制信息1000100111011000,计算机可以把它看作大小为89D8H的数据来处理,也可以将其看作指令mov ax,bx来执行
1000100111011000->89D8H(数据)
1000100111011000->mov ax,bx (程序)
1.3 存储单元
存储器被划分成若干个存储单元,每个存储单元从0开始顺序编号,列如一个存储器有128个存储单元,编号0~127
计算机最小信息为bit,也就是一个二进制位
8 bit=1 Byte(一个字节)
一个存储器有128个存储单元,可存放128个Byte
1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024G
1.4 cpu对存储器的读写
存储器被划分成多个存储单元,存储单元从零开始顺序编号。这些编号可以看作存储单元在存储器中的地址。
cpu想要从内存中读取数据,首先要指定存储单元的地址。所以cpu在读写数据时还要指明,它要对那个器件进行操作,进行那种操作,是从中读取数据,还是写入
cpu想要进行数据的读写,必须和外部器件(芯片)进行信息交互
- 存储单元的地址(地址信息)
- 器件的选择,读或写的命令(控制信息)
- 读或写的数据(数据信息)
cpu从3号单元读取数据过程:
(1)cpu通过地址线将地址信息3发出。
(2)cpu通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中读取数据。
(3)存储器将3号单元的数据08通过数据线送入cpu
对应汇编指令:mov ax,[3]
写入数据也类似的
1.5 地址总线
cpu通过地址总线来指定存储器单元的,所以地址总线上能传送多少个不同的信息,cpu就可以对多少个存储单元进行寻址
例如:一个cpu有10根地址总线,那么10根导线可以传送10个二进制数据,那么10个二进制数表示多少数据呢?2的10次方个。最小为0,最大为1023.
一个cpu有N根地址总线,则可以说这个cpu的地址总线宽度为N。这样的cpu最多可以寻2的N次方给单元
1.9 数据总线
数据总线的宽度决定了cpu和外界的数据传送速度。8根数据总线一次可以传送一个8位二进制数(1个字节)。16根则为2个字节
1.10 控制总线
其宽度决定了cpu对系统中其他器件的控制能力
第2章 寄存器
寄存器对信息进行存储,不同cpu寄存器个数,结构都不相同。
8086cpu有14个寄存器
寄存器:
AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW
2.1通用寄存器
AX,BX,CX,DX这四个寄存器通常用来存放一般性数据
以AX为例:
一个16位寄存器可以存放16位数据
在8086cpu中寄存器可以分为两个独立使用的8位寄存器来用:
- AX可分为:AH和AL
- BX可分为:BH和BL
- CX可分为:CH和CL
- DX可分为:DH和DL
例如:
数据:18
二进制表示:10010
在AX中的存储:
AX的低8位构成了AL寄存器,高8位构成了AH寄存器,两者可以独立使用
十六进制数的一位相当于二进制的四位,
如0100111000100000可以表示为:4(0100),E(1110),2(0010),0(0000)
4E20H(H表示为这数为十六进制数)
2.2基本汇编指令
汇编指令
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
mov ax,4E20H ax=4E20H bx=0000H
add ax,1406H ax=6226H bx=0000H
mov bx,2000H ax=6226H bx=2000H
add bx,ax ax=8226H bx=2000H
mov bx,ax ax=8226H bx=8226H
若add ax,bx ax为5412H,bx为6123H相加是什么结果呢
相加后ax数据为11535H,但ax寄存器只能存放16位,所以要将高位舍去(并不是真正丢弃了),所以ax中数据位1535H
2.3 物理地址
cpu访问访问内存空间时,要给出内存单元地址。每个内存单元在这个空间中都有唯一的地址,我们称为物理地址。
2.4 8086cpu给出2物理地址的方法
8086cpu有20位地址总线,可以传送20位地址,达到1MB寻址能力。其结构为16位结构,在内部一次性处理,传输,暂时存储的地址为16位。
所以cpu采用了在内部用;两个16位地址合成的方法来形成一个20位的物理地址
- cpu提供了两个16位的地址,一个称为段地址,另一个被称为偏移地址
- 段地址和偏移地址一起送入称为地址加法器的部件
- 将两个16位地址合成一个20位的物理地址
地址加法器:
物理地址=段地址*16+偏移地址
段地址*16的意思:
常用说法左移4位。
数据为2H,十进制为10B
左移位数 二进制 十六进制 十进制
0 10B 2H 2
1 100B 4H 4
2 1000B 8H 8
3 10000B 10H 16
4 100000B 20H 32
十六进制数2H左移4位为2*10=20H
段地址*16+偏移地址=物理地址的本质含义:
cpu在访问内存时,用一个基础地址(段地址*16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址
例如:段地址为200H,偏移地址为826H,则物理地址为2826H
2.5 段
内存并没有分段,段的划分来源于cpu,用“段地址*16+偏移地址=物理地址”的方法给出内存单元的物理地址,可以使得我们用分段的方式管理内存
图上地址定义为10000H~100FFH组成一个段,该段的段地址为1000H,或者分成两个段一个段地址为1000H,另一个为1008H
注意:用段地址*16定位段的起始地址2,用偏移地址定位段中的内存单元
因为偏移地址为16位,16位地址的寻址能力位64KB,所以一个段的长度最大位64KB
2.6 段寄存器
CS,DS,SS,ES
- cs代码段
- ds数据段
- ss堆栈段
- es附加段
CS和IP
CS可以看作为段地址,IP看作为偏移地址 ,CS*16+IP,访问对应CS段中的IP位置的内存数据
修改CS和IP地址的指令
jmp 段地址:偏移地址
jmp 2AE3:3,执行后cs=2AE3H,ip=0003H,cpu将从2AE33H处读取数据
若只想修改ip的内容
jmp 寄存器
jmp ax 执行前ax=1000H,ip=0003H,执行后ip=1000H
第3章 debug的使用
Debug是一个调试工具,通常是操作系统的一部分,用于帮助程序员找出并修复代码中的问题。它提供了一组功能,允许程序员逐步执行程序、观察寄存器和内存中的数据,以及检查代码的执行路径。Debug通常包括命令行界面,允许程序员输入各种命令以控制程序的执行
https://pan.baidu.com/s/1P76bEvHup1QjZoHs34OMsg?pwd=ac12 提取码: ac12
本章节要多动手实践操作
需要的软件:debug软件和DOSBox,建议在虚拟机中安装
进入DOSBox,输入指令
mount c c:\masm的文件目录
c:
进入debug
- R命令查看,改变cpu寄存器内容
- D命令查看内存中的内容
- E命令改写内存中的内容
- U命令将内存中的机器指令翻译成汇编指令
- T命令执行一条机器指令
- A命令以汇编指令的格式在内存中写入一条机器指令
3.1 R命令
内容均是以16进制来表示的
查看寄存器的内容
修改寄存器内容,指定寄存器ax改为1000H
3.2 D命令
查看内存中的内容
d 段地址:偏移地址
查看cs:ip中的内容
可指定D命令查看的内容范围
d 段地址:偏移地址 结尾偏移地址
3.3 E命令
可以逐个修改某一地址开始的内存单元中的内容,以1000:10单元开始为例
输入后光标将会停在00.处提示输入想要写入的数据,有两种选择
- 输入数据,然后空格,即用输入的数据改写当前的内存单元
- 不输入数据,直接按空格键,则不对当前内存单元进行改写
可以使用 E命令向内存中写入字符“a”,数值2,字符“b”,数值3,字符“d”
字符a,b,c是以ASCII码形式写入内存单元中
也可以写入字符串,字符串“a+b”等
3.4 U指令 与 T指令
用e命令向地址1000:0开始写入数据,并用d命令查看1000:0到1000:000f的数据
如何查看写入的或内存中原有的机器码所对应的汇编指令?
T执行一条汇编指令
先使用r命令改变cs:ip的地址,使它指向对应的1000:0地址处
注意:cs为代码段
使用t命令执行汇编语句
注意ax的变化
B90200为汇编指令mov cx,0002的机器代码
3.5 A命令
以汇编指令的形式在内存中写入机器指令
在查看1000:0处的代码,并用t命令执行,注意多观察寄存器变化
第4章 寄存器(内存访问)
4.1 内存中字的存储
cpu中,用16位寄存器来存储一个字。高8位存放高位字节,低8位存放低位字节。在内存中存储时,由于内存单元时字节单元(一个单元存放一个字节),则要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高字节在高地址中,例如有个数据为4E20H,高位为4E,低位为20,所以4E存放在偏移地址为0处,20存放在偏移地址为1处,构成了一个字单元
- 0地址单元中存放的字节型数据为20H
- 0地址字单元中存放的字节型数据为4E20H
4.2 DS和[address]
CPU要读写一个内存单元时,必须先给出这个内存单元的地址,在8080CPU中,内存地址由段地址和偏移地址组成的。8086CPU中有一个DS寄存器,通常用来存放数据的段地址。
mov ax,1000H
mov ds,ax
mov bx,[0]
上面代码中将1000H送入ax寄存器中,并将再将ax的数据送入ds段地址中,此时ds的段地址为1000H,[0]为内存单元的偏移地址,将1000:0处的数据输入到bx中
注意:ds:1000H 写法是错误的,不能直接写入,需要通过通用寄存器间接传入
将ax的数据0023H送到ds段中偏移地址为0(1000:0)处
mov ax,1000H
mov ds,ax
mov ax,0023H
mov [0],ax
我们直接进入debug中验证
最后成功查找到ds段中数据
4.3 字的传送
使用mov指令在寄存器和内存之间进行字节型数据的传送。因为8086CPU是16位结构,有16根数据线,所以一次性传送16位的数据,即一次性传送一个字
mov bx,1000H
mov ds,bx
mov ax,[0] # 1000:0处的字型数据送入ax
mov [0],cx # cx中的16位数据送到1000:0处
例如:右图为内存情况
指令 执行后寄存器的内容 说明
mov ax,1000H ax=1000H 前两条命令目的是将ds设置为1000H
mov ds,ax ds=1000H
mov ax,[0] ax=1123H 1000:0处存放的字型数据送入ax
1000:1单元存放字型数据的高8位: 11H
1000:0单元存放字型数据的低8位: 23H
所以1000:0处存放字型数据为1123H
指令执行时,字型数据的高8位送入 ah,
低8位送入 al 中
mov bx,[2] bx=6622H 原理同上
mov cx,[1] cx=2211H
add bx,[1] bx=8833H
add cx,[2] cx=8833H
4.4 栈
在这里,我们对栈的研究仅限于这个角度:栈是一种具有特殊的访问方式的存储空间,特殊在于,最后进入这个空间的数据,最先进入这个空间的数据,最先出去
我们可以用一个盒子和3本书来描述这个栈的基本操作方式:
将三本书以高等数学,c语言,软件工程的顺序放入
那么如何取出呢?显然,如果要取出数据,那么就要从最上面开始依次取出
以程序化的角度来看,应该有一个标记这个标记一直指示着盒子最边上的书
如果说上面的例子中盒子就是一个栈,我们可以看出,栈有两个基本操作:入栈和出栈,入栈就是将一个新的元素放到栈顶,出栈就是从栈顶取出这一个元素(先进后出)
4.5 CPU 提供的栈机制
在8086cpu编程时候,可以将一段内存当作栈使用
它提供了入栈和出栈的指令,最基本的是PUSH(入栈),POP(出栈)
push ax 表示将寄存器ax中的数据送入栈中
pop ax 表示从栈顶取出数据送入ax
出栈和入栈操作都是以字为基本单位进行的
下面举例说明,我们可以将10000H~1000FH这段内存当作栈来使用
其指令是: (注意:字型数据用两个单元存放,高地址单元存高8位,低地址单元存放低8位)
mov ax,0123H
push ax
mov bx,2266H
push bx
mov cx,1122H
push cx
pop ax
pop bx
pop cx
那么CPU如何知道当前要执行的指令所在的位置呢?
答案是:CS:IP 中存放着段地址和偏移地址
问题是CPU如何知道栈顶的位置呢,显然也应当有相应的寄存器来存放栈顶的地址
段寄存器SS 和 寄存器 SP,栈顶的段地址存放在SS中,偏移地址存放在SP中
在任意时刻,SS:SP指向栈顶元素。push指令和pop指令执行时,CPU从SS和SP中得到栈顶地址
push ax的执行,由以下两步完成。
(1) SP=SP-2,SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶
(2) 将ax中的内容送入 SS:SP 指向的内存单元处,SS:SP 此时指向新栈顶
从图中我们可以看出,入栈时,栈顶从高地址向低地址方向增长
如果将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时,
SS=1000H,SP=0010H
将10000H~10000F这段空间当作栈段,SS=1000H,栈空间的大小为16字节,栈最底部的字单元地址为1000:000E(注意:这里是字单元,一个字单元存放两个字节)任意时刻,SP:SP指向栈顶,当栈中只有一个元素时候,SS=1000H,SP=000E。栈为空,相当于栈中唯一的元素出栈,出栈后,SP=SP+2,SP原来为000EH,加 2 后 SP=10H,当栈为空时,SS=1000H,SP=0010H
换角度看,任意时刻,SS:SP指向栈顶元素,当栈为空时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址 + 2 ,栈最底部字单元的地址为1000:00)E,所以栈空时,SP=0010H
接下来我们来描述pop指令的功能,例如pop ax
pop ax 与 push ax执行过程恰好相反
(1) 将SS:SP指向的内存单元处的数据送入ax中
(2) SP=SP+2, SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新栈顶
4.6 push,pop指令
push 寄存器 ;将一个寄存器中的数据送入栈
pop 寄存器 ;出栈,用一个寄存器接收出栈的数据
push 段寄存器 ;将一个段寄存器中的数据送入栈
pop 段寄存器 ;出栈,用一个段寄存器接收出栈的数据
push 内存单元 ;将一个内存字单元处的字送入栈
pop 内存单元 ;出栈,用一个内存字单元接受出栈的数据
比如:
mov ax,1000H
mov ds,ax ; 内存单元的段地址要存放在ds中
push [0] ; 将1000:0处的字压入栈中
pop [2] ; 出栈,出栈的数据送入1000:2处
第5章 第一个程序
5.1 基础:
我们来分析此段代码:
assume cs:code
code segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
code ends
end
1,伪指令
(1)
在汇编语言源程序中,包含两种指令,一种是汇编指令,一种是伪指令。
汇编指令前面我们了解过了,那么伪指令是什么呢?
XXX segment
...........
XXX ends
segment 和 ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时必须要用到的一对伪指令。segment 和 ends 的功能是定义一个段,segment说明一个段的开始,ends 说明一个段的结束。一个段必须有一个名称来标识格式为(段的名称可以自己命名):
段名 segment
...........
段名 ends
在上面的程序中我们定义了一个段,段名称为" code" ,从这个段开始,到code ends 结束
(2)end
end是一个汇编程序的结束标记,编译器在编译汇编程序时,如果碰到了伪指令end,就结束了对源程序的编译,注意不要混淆了end 和 ends
(3)assume
含义为“假设”,它假设某一段寄存器和程序中的某一个用segment.....ends 定义的段相关联,使编译器可以将段寄存器和某一个具体的段相关联
(4) 程序的返回
回过头看上面程序中的两条指令
mov ax,4c00H
int 21H
在目前阶段,不必去理解int 21H指令的含义,和为什么要在这条指令前面加上指令mov ax,4c00H,只需要知道,在程序的末尾使用这两条指令可以实现程序的返回
5.2 编译源程序
我们需要用到 masm 汇编编译器,在第3章提供了本章节所用的工具