第一章 基础知识
- 存储器(内存)存放CPU工作的指令和数据(CPU可以直接使用的信息在内存中存放);指令和数据都是二进制数没有任何区别,由CPU决定是数据还是指令
- 内存单元:存储器被分为若干个存储单元,并从0开始编号(存储单元的地址),一个存储单元为1字节(8bit)
- CPU的读写
总线逻辑上分为:地址总线、数据总线、控制总线
- 地址总线:地址线宽度决定了CPU的寻址能力,计算方式 :2^[地址线宽](个内存单元)
- 数据总线:决定了CPU和外界的数据传输速度(8根数据总线可传送一个8位(bit)二进制数,即1个字节)
- 控制总线:是各外部器件的不同控制线的集合
CPU进行数据读写必须经过3类信息交互:
①存储单元的地址(地址信息)②器件的选择,读或写的命令(控制信息)③读或写的数据(数据信息)
读:CPU在内存中读取数据时,要先指定存储单元的地址(你要找一个地方先要确定一个地址),会把要读取的地址经过地址总线发给内存;同时CPU控制器将内存读命令通过控制总线发送给内存,并通知内存要读取数据;最后内存将被指定的内存单元中的数据经过数据总线送入CPU
写:与读类似,CPU经地址线发送要操作的内存单元的地址,CPU经控制线将内存写命令发送至内存,并通知内存要写入数据;CPU通过数据线将数据送入指定的内存单元;
内存地址空间:
- 每一个外部器件都有自己的存储芯片(如显卡有显存RAM和ROM(装有显卡BIOS)、网卡ROM(装有网卡BIOS)),CPU控制各个设备都是通过总线向各个设备的存储芯片发送命令,存储芯片根据CPU的命令控制外设进行工作
- 所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器,每个物理内存在这个逻辑内存中占有一个地址段,即一段地址空间可看出各存储芯片都和CPU的总线相连;CPU对它们的读写都通过控制线发出内存读写命令
- CPU根据读取不同的地址实现对不同设备的读写操作,这个逻辑存储器就是内存地址空间,它的容量受CPU寻址能力的限制(即地址总线宽度限制),如有20位的地址总线宽度,它的寻址能力为2^20个内存单元(一个存储单元为1字节(8位))所以该CPU的内存地址空间的大小为1MB;32位地址线宽的CPU内存地址空间最大为为4GB(CPU不行的话加内存条也没得用!64位的就不得了了,主板不够插)
1、CPU寻址能力 = 2^线宽
2、一根数据总线传一位数据,8根数据总线一次传8位数据(1Byte)
第二章 寄存器
CPU组成:运算器(信息处理)、控制器(控制各种器件进行工作)、寄存器(信息存储)
内部总线实现CPU内部器件的联系
外部总线实现CPU与主板和其他器件的联系
- AX、BX、CX、DX通常用来存放一般性数据——通用寄存器(后面是X的寄存器是16位的,现在的EAX前面加了个E是32位)
- 这四个寄存器都可以分为两个独立的八位寄存器使用(如AX可分为高位AH和低位AL),是为了向下兼容(只使用低位,高位填0)单独使用时当成一个来看,同时使用时是整体(计算方法 2^[位数] )
- 一个字=两个字节=>一个十六进制数,所以十六进制数的一位相当于二进制数的四位
- 汇编指令不分大小写
- 16位结构的CPU具备以下特征(64位同理):1、运算器一次最多可以处理16位的数据;2、寄存器的最大宽度为16位;3、寄存器和运算器之间的通路是16位的;
- 8086CPU给出物理地址的方法:
8086有20位地址总线,可传送20位地址,寻址能力为2^20 = 1M,
而8086内部为16位结构,他只能传送16位的地址,表现出的寻址能力只有64,
说明CPU不能直接传送16位数据地址到20位的总线上(资源浪费),所以需要转换成20位的地址;
8086采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址(段地址 和 偏移地址),经过地址加法器转换成20位的物理地址,示意图:
地址加法器的工作原理:物理地址 = 段地址SA*16+偏移地址EA & 物理地址 = 基础地址 + 偏移地址
相当于把段地址往高位进了一位,这里说的16位是二进制的数,CPU中存储的数据只能是二进制,而我们表示地址是用16位的数字,前面说了2和16进制的转换关系,一个十六进制数的位等于四个位的二进制数,所以段地址乘以16,把段地址后移了一位,增加了一位16进制数,二进制就得加四位,即数据左移四位(二进制位),16+4=20位,转换完成
- 一个数据的X进制形式左移1位,相当于乘以X(十进制数乘十后左移一位)
- 一个数据的二进制形式左移N位,相当于该数据乘以2的N次方
段落的概念:
内存本身并没有被分段,分段概念来自于CPU,8086CPU用 物理地址 = 段地址*16+偏移地址 的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存 用 段地址x16 定位段的起始地址,用偏移地址定位段中的内存单元注意:1、段地址X16必然是16的倍数,即一个段的起始地址也一定是16的倍数
2、偏移地址为16位,16位地址的寻址能力为64k,所以一个段的长度最大为64k
CPU访问内存单元时必须向内存提供内存单元的物理地址(有地址才知道要访问哪里) 偏移地址EA为16位,变化范围0~FFFFH,仅用偏移地址来寻址最多可寻64k个内存单元(如段地址位1000H,那么CPU寻址范围为:10000H~1FFFFH 储存单元的地址用 段地址 和 偏移地址 描述:数据在21F60中 a、数据存在内存2000:1F60单元中;b、数据存在内存的2000段中的1F60中;(可将地址连续、起始地址为16的倍数的一组内存单元定义为一个段)段寄存器:
提供段地址,8086CPU有4个段寄存器:CS(代码(指令)段寄存器)、DS(数据段寄存器)、SS(堆栈stack段寄存器)、ES(附加段寄存器) CS和IP(指令指针寄存器)是8086CPU中最关键的寄存器,存放CPU当前要读取的指令的地址在任何时候,CPU将CS为指定(存放)指令的段地址(代码段首地址),IP指定(存放)指令的偏移地址(指令从哪处代码开始执行),CS和IP经过地址加法器加工后(通过输入输出控制电路)从地址总线指向内存单元读取指令,指令再通过数据总线(经过输入输出控制电路)传递给指令缓冲器,再到执行控制器,实现执行效果
内存中被CPU执行过的代码必然被CS:IP指向过
从 CS : IP 指向内存单元读取指令,读取的指令进入指令缓冲器 IP = IP + 所读取指令的长度,从而指向下一条(即所指向的指令执行完后接着运行接着的下一条指令)
CPU加电启动或复位时,CS=FFFFH,IP=0000H,即CPU刚启动时读取并执行的第一条指令是FFFF0H
修改CS、IP值的指令:转移指令 jmp
同时修改CS、IP的内容:jmp 段地址:偏移地址 (例:jmp 2AE3:3) 只修改IP的内容:jmp 某一合法寄存器(jmp ax),用寄存器中的值修改IP代码段:
根据需要,将一组内存单元定义为一个段:把长度<=64kb的一组代码存在一组地址连续、起始地址为16的倍数的内存单元中(个人理解为偏移地址需要16位的位置),这段内存用来存放代码,从而定义了一个代码段 (代码段不能影响CPU来是否执行它,还是得看CS:IP所指向的内存单元中的内容为指令,代码段仅仅是一种编程格式) 所以要将CS:IP指向所定义的代码段中的第一条指令的首地址debug命令:
R 查看、改变CPU寄存器的内容(修改寄存器中的值:r [寄存器名]) D 查看内存中的内容 ( d 段地址:偏移地址 ) E 改写内存中的内容 U 将内存中的及其指令翻译成汇编指令 T 执行一条机器指令 A 以汇编指令的格式在内存中写入一条机器指令第三章 寄存器(内存访问)
- 字单元:存放字型数据的内存单元
一个字等于两个字节,一个字节等于8比特,则一个字型数据有16位;
因为有两个字节组成,字可分为高位和低位,图中0和1可组成一个字,1位比0位高,所以他们组成的字,高位是4E,低位是20即4E20(可以直接用字的起始地址描述它,如0地址字单元)
任何两个地址连续的内存单元,可以将其看成一个内存字单元,一个为字低位字节单元,一个为字高位字节单元
DS寄存器 和 [address]
- DS寄存器:用来存放要要访问数据的段地址(首地址),配合 [ EA ] 使用,[ ] 说明操作的是一个内存单元,EA为偏移地址,段地址为DS中存放的地址
- 数据段:将一组长度小于64k、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段;具体操作时用ds存放数据段的段地址,在根据需要用相关指令访问数据段中的具体单元
栈(stack):出入栈规则LIFO(后进先出)
- 入栈:push 出栈:pop
push 寄存器 pop 寄存器
push 段寄存器 pop 段寄存器
push 内存单元 pop 内存单元
- 内存空间是如何被当做栈使用的:
每执行一次入栈指令,都会将寄存器的内容放入栈顶单元的上方,此时段寄存器SS、寄存器SP就会存放栈顶的地址及偏移量,任何时候SS:SP都指向栈顶元素,执行push和pop时,CPU从SS和SP中获取段顶地址
入栈时:SP=SP-2 (入栈时由高位往低位走)
出栈时:SP=SP+2 (出栈时由低位往高位走)
注意:入栈时的偏移量SP的取值要比当前栈最高位+1位(如要入栈至1000E~F(即栈最底部的字单元地址000E),SP值应比F高一位0010
- 栈顶越界:需自行根据所安排的栈大小进行出入栈,避免入栈数据太多越界,也要避免空栈的时候继续出栈导致越界;越界会覆盖或修改掉可能是其他程序的内存数据或指令,需要注意!
- push执行时,CPU改变栈顶的指向,即SP=SP-2,再将数据传入SS:SP指向的字单元中;
pop执行时,CPU先读取栈顶指向的字单元数据,再改变SP,即SP=SP+2;
- 可见push、pop指令修改的只是SP,栈顶的变化范围为:0~FFFF
- 栈操作机制:SS:SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令
- Debug的 T 命令在执行修改寄存器SS的指令时,下一条指令也紧接着被执行(mov ss,ax ; mov ss,[0] ; pop ss)
- 可用段寄存器表示内存单元的段地址:
第四章 汇编程序
- 编写汇编源码----编译连接----可执行文件(包含由汇编指令翻译过来的机器码和源程序中定义的数据以及相关的描述信息,如程序大小,占用多少内存空间等)
- 伪指令:
段名 segment ;定义一个段,该段从此开始
:
段名 ends ;该段从此结束
end:汇编程序结束的标记
assume:含义为“假设”,假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联,用于说明关联的关系。格式:assume 寄存器名:段名
- 程序返回:DOS为例,可执行文件P2想要运行,必须有一个正在运行的程序P1,要CPU将P1的运行权限交给P2,P2才能执行,且执行结束后需要将权限返还给P1,用到汇编指令 mov ax,4c00 int 21 (十六进制),结束相关的概念:
- 自定义的段名标号代指了一个地址,最终被编译、连接的程序处理为一个段地址
- Link连接的作用:
- 当源程序很大时,可分为多个源程序文件来编译,各编译为目标文件后,再用连接程序将他们连接到一起,生成一个可执行文件;
- 程序中调用了某个库文件的子程序,就要把这个库文件和程序的.obj目标文件连接到一起,生成一个可执行文件;
- 源代码编译后,得到有机器码的.obj目标文件,但有些内容不能直接用来生成可执行文件,连接程序Link.exe把这些内容处理为最终的可执行信息;所以,即使只有一个原文件,且不调用库文件,还是要使用连接,生成可执行文件;
- 在DOS中运行程序时,由command根据文件名找到这个程序,并加载到内存中,设置CS:IP指向程序的入口,command暂停运行,CPU运行程序,程序运行结束后,返回command;
- DOS系统的shell(外壳)程序,就是command.com程序,负责处理各种输入:命令或程序的文件名;
- DOS中.EXE文件中程序的加载过程:
- 程序加载后,DS存放着程序所在的内存区的段地址,且偏移地址为0,即程序所在的内存区地址为DS:0
- 而这个内存区的前256个字节中存放的是PSP,DOS用来和程序进行通信,往后推256字节的空间存放的就是程序
所以,从DS中可以得到PSP的段地址SA:0,则实际的物理为SAx16+0(x16段地址后移一位),因为PSP占256(100H)字节,所以程序部分的物理地址是:SAx16+0+256 = SAx16+16x16+0 = (SA+16)x16+0
可用段地址和偏移地址表示为:SA+10H:0 (SA进位也要x16,而16x16为100H,根据上面的公式结果,各抵消一个x16(一个十六进制的进位),得出此结果)
也就是DS寄存器存放的地址SA+10H就是程序的段地址,CS:IP会指向这里(程序入口)
第五章 [BX]和loop指令
- [BX]:与[]一样,表示一个内存单元;[0]表示偏移地址为0,[BX]表示偏移地址为BX寄存器中的值
完整描述一个内存单元需要知道的两个信息:①内存单元的地址;②内存单元的长度(类型)
- loop:通过跳转实现循环(类似C语言goto语句)
程序结构:
mov cx,循环次数
s:
循环执行的程序段
loop s
- CX寄存器存放循环次数
- CPU执行loop s 时,要进行两步操作:(且按照①②的顺序)
①(CX)=(CX)- 1;
②判断CX中的值,不为0则跳转至标号标识的地址处执行,为0则执行下一条语句
标号会被自动替换成所标记的地址显示在源程序中,cx-1不为0时,“loop 地址”会把IP的值设置成这个“地址”,从而使CS:IP指向“地址”处,实现跳转
- 符号“ ( ) ” :用于描述元素或数据
“()”中的元素可以有3种类型:①寄存器名;②段寄存器名;③内存单元的物理地址(一个20位数据)
可描述的数据有两种类型:①字节;②字 (同时类型说明了数据的长度)
- 约定符号idata表示常亮
在汇编程序中,数据不能以字母开头;所以,ffffh书写时前面要加0,即0ffffh
- debug用于跳过循环的指令:p直接执行到cx为0;g 偏移地址,直接执行到偏移地址所在处,如,g 0016,表示直接执行到CS:0016处(不禁发起疑问:这就是断点?)
- Debug和汇编编译器masm对指令的不同处理:
[idata]在debug中解释为内存单元“idata”为偏移地址;而masm中解释为常量“idata”,如mov ax,[4]就为mov ax,4 不能达到预想效果;
解决办法:①用[bx],间接存储偏移地址,如 mov ax,[bx] ;②[]前面显示注明段地址所在的段寄存器(段前缀),如 mov ax,ds:[4]
第六章 包含多个段的程序
- 在代码中使用数据:
- dw 数据.1,数据2,数据3 dw为“define Word”,定义字型数据,如dw在段首定义数据,那么数据的偏移地址从0开始,即CS:0 ;
- dw 0,0,0,… 可以用于开辟内存空间,如给栈开辟内存空间
- 由于段首存放数据,所以需要指定程序入口地址:在程序结束的 end 汇编伪指令后加 start 标号,同时用start指定入口的位置,结构框架:
- 将数据、代码、栈放入不同的段,定义形式:段名 segment … 段名 ends
注意:CPU处理段中的内容完全依靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置来决定的,即把他们指向对应的内存,那么对应的内存段就被被CPU当成对应的内容,与段的命名、assume后的定义等无关(但assume后定义的段名可以代替对应的寄存器来表示寄存器的内容,它们之间有一种联系);
- 每一个段寄存器只有和对应的偏移地址寄存器配合起来才能指向对应的内存(如CS:IP指向栈顶),其他时候都可以作为一个段寄存器来使用(如 SS:[bx])
- 使用汇编指令进行内存单元的存取操作都要经过通用寄存器(如 add ds:[bx],ax),不能内存单元之间直接进行(如mov ds : [bx],ss:[bx])
第七章 更灵活的定位内存地址的方法
- ‘与’指令and(将操作对象的相应位设为0)、‘或’指令or(将操作对象的相应位设置为1);常用于改变字母大小写,大写字母ASCII码第5位为0,小写的为1
- [bx+idata]代表内存单元偏移地址,几种使用格式:ax,[200+bx]、ax,200[bx]、ax,[bx].200;还可以以数组的形式来操作:0[bx]、5[bx] (起始段地址为0和5的两组数据),为高级语言实现数组提供了便利机制(就像C语言的a[i]、b[i]);也可以直接寄存器相加得出最终偏移地址EA(如,mov ax,[bx+si]或mov ax,[bx][si])
- si和di寄存器(功能类似bx寄存器,但不能分为bh、bl两个八位寄存器)
- CPU的几种寻址方式
- [idata]用一个常量来表示地址,可用于直接定位一个内存单元
- [bx]用一个变量来表示内存地址,用于间接定位一个内存单元
- [bx+idata]用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元
- [bx+si]两个变量
- [bx+si+idata]两变量一常量
- 程序中暂存一个数据往往使用用栈(嵌套循环就需要用此暂存CX的值)