汇编语言读书笔记
汇编语言
汇编语言是直接在硬件上工作的编程语言,首先要了解硬件系统的结构,才能有效的应用汇编语言对其进行编程
机器语言
机器语言
机器语言是机器指令的集合
机器指令展开来说就是一台机器可以正确执行的命令
汇编语言的产生
汇编语言的主体是汇编指令
汇编语言和机器指令的差别在于指令的表示方法上。汇编语言是机器指令便于记忆的书写格式
汇编语言是机器指令的助记符

寄存器
CPU中可以存储数据的器件,一个CPU中有多个寄存器
汇编语言的组成
汇编语言有以下三类组成
汇编指令 机器码的助记符(mov push)有对应的机器码
伪指令 没有对应的机器码,由编译器执行,计算机不执行
其他符号 + - * / 由编译器识别,没有对应的机器码
存储器
CPU是计算机的核心部件,他控制整个计算机的运作并进行计算,要想让一个CPU工作,就必须向他提供指令和数据
指令和数据在存储器中存放,也就是平时说的内存
在一台PC中内存的作用仅次于CPU
离开了内存,性能再好的CPU也无法工作
磁盘不同于内存,磁盘上的数据和程序如果不读到内存中,就无法被CPU使用
指令和数据
指令和数据是应用上的概念
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息
存储单元
存储区被划分为若干个存储单元,每个存储单元从0开始顺序编号
CPU对存储器的读写
CPU要想进行数据的读写,必须和外部器件()进行三类信息的交互
存储单元的地址(地址信息)
期间的选择,读或写的命令(控制信息)
读或写的数据(数据信息)
在计算机中专门链接CPU与其他芯片的导线,通常称为总线
物理上:一根根导线的集合
逻辑上划分为:
地址总线
数据总线
控制总线

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

一个CPU有N根地址总线,则可以说这个CPU的地址总线的宽度为N
这样的CPU最多可以寻找2的N次方个内存单元
数据总线
CPU与内存或其它器件之间的数据传送是通过数据总线来进行的
数据总线的宽度决定了CPU和外界的数据传送速度

控制总线
CPU对外部器件的控制是通过控制总线来进行的,控制总线是一些不同控制线的集合
有多少跟控制总线,即意味着CPU提供了对外部器件的多少种控制
控制总线的宽度决定了CPU对外部器件的控制能力
控制总线上发布的控制信息

内存读或写命令由几根控制总线综合发出
其中有一根名为读信号输出控制总线负责由CPU向外传送读信号,CPU向该控制总线上输出低电平表示将要读取数据
有一根名为写信号输出控制总线负责由CPU向外传送写信号
小结
汇编指令是机器指令的助记符,同机器指令一一对应
每一种CPU都有自己的汇编指令集
CPU都可以直接使用的信息在存储器中存放
在存储器中指令和数据没有任何区别,都是二进制信息
每一个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可以引出三种总线的宽度标志了这个CPU的不同方面的性能
地址总线的宽度决定了CPU的寻址能力
数据总线的宽度绝地过了CPU与其他器件进行数据传送时的一次数据传送量
控制总线宽度决定了CPU对系统中其他器件的控制能力
内存地址空间
主板
在每一台PC机中,都有一个主板,主板上与核心器件和一些主要器件
这些器件通过总线(地址总线、数据总线、控制总线)相连
接口卡
计算机系统中,所有可用程序控制其工作做的设备,必须受到CPU的控制
CPU对外部设备不能直接控制,如显示器、音箱、打印机等。直接控制这些设备进行工作的时插在扩展插槽上的接口卡
各类存储器芯片
从读写属性上看分为两类:
随机存储器RAM和只读存储器ROM
从功能和连接上分类
随机存储器RAM
装有BIOS的ROM
接口卡上的RAM
BIOS是由主板和各类接口卡厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入输出。在主板和某些接口上都插有存储相应BIOS的ROM

上述存储器在物理上是独立的器件
但在逻辑上是相同的
都和CPU的总线相连
CPU对他们进行读写的时候都通过控制总线发出内存读写命令
CPU将各类存储器看作一个逻辑存储器
所有的物理存储器被看作一个由若干存储单元构成的逻辑存储器
每个物理存储器在这个逻辑存储器中占有一个地址段,及一段地址空间
CPU在这段地址空间中读写数据,实际上就是在相对的物理存储器中读取数据

最终运行程序的是CPU,我们用汇编编程的时候,必须要从CPU角度考虑问题 *
对CPU来讲,系统中所有的存储器中的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU寻址能力的限制。这个逻辑存储单元即使我们所说的内存地址空间
寄存器(CPU工作原理)
概述
一个典型的CPU由运算器、控制器、寄存器等器件自称,这些器件靠内部总线相连
区别:
内部连线实现CPU内部各个器件之间的联系
外部总线实现CPU和主板上其他器件的联系
通用寄存器
8086所有的寄存器都是16位的,可以存放两个字节(一个字)
AX,BX,CX,DX通常用来存放一般性数据被称为通用寄存器

8086为保证兼容性,16位通用寄存器可以分为两个独立的8位寄存器使用
AX可以分为AL和AH(低八位和高八位)
字在寄存器中的存储
一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高八位和低八位寄存器中
汇编指令
汇编语言不区分大小写

mov指令可以改变8086CPU大部分寄存器的值,被称为传送指令
已知的mov指令可完成的两种传送功能
将数据直接送入寄存器
将一个寄存器中的内容送入另一个寄存器
除此之外,mov指令还可以将一个内存单元中的内容送入另一个寄存器
mov al,[0] 将地址0当中的数据读入al
mov 指令不能用来设置CS、IP寄存器的值
同时修改CS、IP的指令
jmp 段地址 :偏移地址
功能:用指令中给出的段地址修改CS,偏移地址修改IP
仅修改IP的内容
jmp某一合法寄存器
jmp ax (类似于mov IP,ax)
功能:用寄存器中的值修改IP
物理地址
CPU访问内存但愿是要给出内存单元的地址,所有的内存单元构成的空间是一个一维的线性空间,我们将这个一维的线性空间称为物理地址
概括地讲,16位结构描述了一个CPU具有以下几个方面特征 *
运算器一次最多可以处理16位的数据 *
寄存器的最大宽度为16位 *
寄存器和运算器之间的通路是16位的
8086CPU读写内存时,发生了这么一件事
CPU中的相关部件提供了两个16位的地址,一个称为段地址,另一个称为偏移地址
段地址和偏移地址通过内部总线送入一个称为地址加法器的部件
地址加法器将两个16位地址合并成一个20位的地址

地址加法器合成物理地址的方法:
物理地址=段地址*16+偏移地址 *
我们通过观察移位次数和各种形式数据的关系:
一个数据的二进制形式左移一位,相当于该数据乘以2
一个数据的二进制形式左移N位,相当于该数据乘以2的N次方
段的概念
错误认知:内存被划分成了一个一个的段,每一个段有一个段地址
其实:内存并没有分段,端的划分来自于CPU,由于8086CPU用(段地址*16)+偏移地址=物理地址的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存
CPU访问内存单元时,必须向内存提供内存单元的物理地址
8086CPU在内部用段地址和偏移地址移位相加的方法形成最终的物理地址
CPU可以通过不同的段地址和偏移地址形成同一个物理地址

段寄存器
8086CPU有四个寄存器
CS 代码寄存器
DS 数据寄存器
SS 堆栈寄存器
ES 附加段寄存器
CS和IP
CS和IP是8086CPU中最关键的寄存器,他们指示了CPU当前要读取指令的地址
CS为代码寄存器
IP为指令指针寄存器

8086PC工过程的简要描述
从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器
IP=IP+所读取指令的长度,从而指向下一条指令
执行指令。转到步骤一,重复执行这个过程
8086CPU加电启动过程举例
在8086加电启动或复位后(即CPU刚刚开始工作时)CS和IP被设置为CS=XXXXH,IP=XXXXH
即在8086PC机刚启动时,CPU从内存XXXX0H单元中读取指令执行
FFFF0H单元中的指令是8086PC机开机后执行的第一条指令
在任何时候,CPU将CS、IP中的内容当作指令的段地址和偏移地址,用他们合成指令的物理地址,到内存中读取指令码,执行
如果说,内存中的一段信息曾被CPU执行过的话,那么,他所在的内存单元必然被CS:IP指向过
代码段
对于8086来说,在编程时,可以根据需要,将一组内存单元定义为一个段
可以将长度为N的一组代码,存放在一组地址连续、起始地址是16的倍数的内存单元中,这段内存是用来存放代码的,从而定义了一个代码段
将一段代码当作代码段,仅仅是我们在编程的时候的一种安排,CPU并不会由于这种安排,就自动地讲我们定义的代码段中的指定当作指令来执行
CPU只认识被CS:IP指向的内存单元中的内容为指令
所以要将CS:IP指向要执行代码的首地址
小结
在8086PC机中。存储单元的地址用两个元素来描述,即段地址和偏移地址
两种描述
数据在内存 段地址:偏移地址 中
数据在内存 段地址中的 偏移地址 中
可根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段
段地址在8086CPU的寄存器中存放。当80086CPU要访问内存时,由段寄存器提供内存单元的段地址
80086CPU有四个段寄存器,其中CS用来存放段地址,IP存放指令的偏移地址
8086机中,任意时刻,CPU将CS:IP指向内容当作指令执行
寄存器(内存访问)
内存中字的存储
任何两个地址连续的内存单元,N单元和N+1单元,可以将它们看成两个内存单元,也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元
DS和【address】
CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址
在8086CPU中,内存地址有段地址和偏移地址组成
8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址
如何使用mov指令从10000H中读取数据?
10000H表示为1000:0(段地址:偏移地址)
将段地址1000H放入ds
用mov al,[0]完成传送(mov指令中的【】说明操作对象是一个内存单元,【】中的0说明这个内单元的偏移地址是0,它的段地址默认放在ds中)
8086不支持将数据直接送入段寄存器的操作,ds是一个段寄存器
数据--通用寄存器--段寄存器

字的传送
因为8086CPU是16位结构,有16根数据线,所以可以一次性传送16位的数据,也就是一次性传送一个字

mov、add、sub指令
mov指令
已知mov指令的几种形式
mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器
mov 段寄存器,寄存器
add、sub指令
add指令和 sub指令同mov一样,都有两个操作对象

数据段
前面讲过,对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等)
当段地址存储在CS中是,获取的是代码段,当段地址存储在DS中时,获取的是数据段
我们可以将一组长度位N、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存地址,从而定义了一个数据段
如何访问数据段中的数据?
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候,用ds存放数据段的段地址,在根据需要,用相关指令访问数据段中的具体单元
栈
可以理解成弹夹(先进后出 First In Last Out)
栈有两个基本操作:入栈和出栈
入栈:将一个新元素放到栈顶
出栈:从栈顶取出一个元素
栈顶的元素总是最后入栈,需要出栈的时候,又最先被从栈中取出
CPU提供的栈机制
8086CPU提供相关的指令来以栈的方式访问内存空间,这意味着我们在基于8086CPU编程时,可以将一段内存当作栈来使用
8086提供入栈和出栈的指令:
PUSH(入栈)
push ax :将寄存器ax中的数据送入栈中
POP(出栈)
pop ax :从栈顶取出数据送入ax
8086CPU的入栈和出栈操作都是以字为单位进行的
CPU如何知道一段内存空间被当作栈使用?
8086CPU中,有两个寄存器:
段寄存器SS 存放栈顶的段地址
寄存器SP 存放栈顶的偏移地址
任意时刻,SS:SP指向栈顶元素
因此,被SS指向的地址被当作栈
执行push和pop的时候,如何知道哪个单元是栈顶单元?
push指令的执行过程
push ax
SP=SP-2;
将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶

pop指令执行过程
pop执行指令与push相反,执行时先将数据移入寄存器,然后SP+2
pop ax
将SS:SP中=指定的内存单元中的内容送入ax中
SP=SP+2,SS:SP此时指向新栈顶
在执行完pop指令后,之间存储在栈空间中的值仍然存在,不会消失,之后更新时被直接覆盖,因此数据可以被恢复
如果将10000H~1000FH这段空间当作栈,初始状态和空栈状态下,SS=10000H,SP=0010H
任意时刻,SS:SP指向栈顶元素,当栈为空时,栈中没有元素,也就不存在栈顶元素
所以SS:SP只能指向栈的最底层字单元下面的地址,该单元的偏移地址为栈最底部的字单元的偏移地址+2
站最底部字单元的地址为1000:000E,所以栈空时,SP=0010H
栈顶超界的问题
SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈时找到栈顶,但无法判断是否发生栈超界
当栈满的时候再使用再使用push指令入栈或栈空的时候再使用pop指令出栈,都将发生栈顶超界的问题
栈顶超界是危险的 *
在内存中将一段空间安排为栈,那么在栈空间之外可能存放了具有其他用途的数据、代码等,某些数据被覆盖会引发严重的后果
CPU不保证对栈的操作不会越界,其只知道栈顶在何处,而不知道读者安排的栈空间有多大
CPU的工作机理,只考虑当前的情况
当前栈顶在何处
当前要执行的指令是那一条
至于执行结果和是否合理,CPU无从得知
栈与内存
栈空间也是内存空间的一部分,他只是一段可以以一种特殊的方式进行访问的内存空间
push、pop实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov指令不同的是,push和pop指令访问的地址不是在指令中给出的,而是由SS:SP指出的
结论
我们在编程的时候要小心栈顶超界的问题,要根据可能用到的最大空间来安排栈的大小,防止入栈的数据太多而导致的超界
执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的越界
push、pop等栈操作指令,修改的只是SP。也就是说,栈顶的变化范围最大为64k
SS、SP指示栈顶;gaibianSP后写内存的入栈指令;都内存后改变SP的出栈指令
小结
字在内存单元中存储要占用两个连续的存储单元,高位存储在高字节存储单元,低字节存储在低地址单元
用mov指令要访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认存储在DS寄存器中
[address]表示一个偏移地址位address的内存单元
在内存和寄存器之间之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应
mov、add、sub是具有两个操作对象的指令。jmp时具有一个操作对象的指令
指令在执行时,CPU要知道内存单元的地址,可以在push、pop指令中给出给内存单元的偏移地址,段地址在指令执行的时候,CPU从ds中获得
8086CPU的栈操作机制
在SS,SP中存放栈顶的段地址和偏移地址,提供入栈和出栈指令,他们根据SS:IP知识的地址,按照栈的方式访问存储单元
任意时刻,SS:SP指向栈顶元素
8086CPU只记录栈顶,栈空间的大小我们自己管理
用栈来暂存以后需要恢复的寄存器的内容是,寄存器出栈的顺序要和入栈的顺序相反
对于段我们可以自己形成逻辑,但如果让CPU按照我们的安排来访问这些段,就要:
对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据段访问
对于代码段,将他的段地址放在CS中,将段中的第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令
对于栈段,将他的段地址放在SS中,将栈顶单元的拍内衣地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用
可见,不管我们如何安排,CPU将内存中的某段内存当作代码,是因为CS:IP指向了指向了那里;CPU将某种内存当作栈,是因为SS:IP指向了那里
第一个源程序
一个源程序从写出到执行的过程
编写
使用文本编辑器生成源文件 .asm
编译连接
使用汇编语言编译程序对源程序文件中的原程序进行编译,产生目标文件 .obj
再用连接程序对目标文件进行连接,生成可在操作系统中直接运行的可执行文件 .exe
执行
在操作系统中,执行可执行文件中的程序
操作系统依照可执行文件中的描述信息,将可执行文件中的机器码和数据加载入内存,并进行相关的初始化(比如:设置CS:IP指向第一条要执行的指令),然后由CPU执行程序

源程序
汇编指令
有对应的机器码的指令,可以被编译为机器指令,最终为CPU所执行
伪指令
没有对应的机器码,最终不被CPU所执行
伪指令是由编译器执行的指令,编译器根据伪指令进行相关的编译工作
segment和ends是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令
segment和ends的功能是定义一个段,segment说明一个段的开始,ends说明一个段结束
一个汇编程序是由多个段组成的,这些段被用来存放代码、数据或当作栈空间来使用
一个有意义的汇编程序中至少要有一个段,这个段用来存放代码
End是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令end,就结束对源程序的编译
assume:含义为“假设”
它假设某一段寄存器和程序中的某一个用segment。。。ends定义的段相关联
通过assume说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系

程序的结构


assume cs:codesg 由于CS是指令段寄存器,因此该步骤指定codesg是一个代码段
codesg segment 定义一个段,codeseg为段名
段结束、程序结束、程序返回

关于编译和链接
链接的作用有以下几个
当源程序很大时,可以将它分成多个源程序文件来编译,每个源程序编译成为缪表文件后,再用连接程序将他们连接到一起,生成一个可执行文件
程序调用了某个库文件中的子程序,需要将这个库文件和该程序生成的目标文件连接在一起,生成一个可执行文件
一个源程序编译后,得到了存有机器码的目标文件,目标文件中的有些内容还不能直接用来生成可执行文件,连接程序将这些内容处理为最终的可执行信息
所以,在只有一个源程序文件,而又不需要调用某个库中的子程序的情况下,也必须用连接程序对目标文件进行处理,生成可执行文件
汇编程序从写出到执行的过程

可执行文件
可执行文件中包含两部分内容
程序:从源程序中的汇编指令翻译过来的机器码
数据:源程序中定义的数据
相关的描述信息:比如程序有多大、要占用多少内存等
PE文件的全称时Portable Executable,以为可以指的可执行文件,常见的EXE,DLL、OCX、SYS、COM都是PE文件,PE文件时微软Windows操作系统上的程序文件
什么将可执行文件中的程序装载入内存并使它运行
操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,用户使用这个程序来对计算机进行操作
系统启动时,先完成重要的初始化工作,然后运行shell,shell运行后,执行完其他相关的人物,就开始等待用户的输入
如果用户要执行一个程序,则输入该程序的可执行文件的名称,shell首先根据文件名找到可执行文件,然后将这个可执行文件中的程序加载入内存,设置CS:IP指向程序的入口。然后shell停止运行,CPU运行程序。程序结束运行后,返回到shell,等待下一次输入
EXE文件中的程序的加载过程

从图中可得知一下信息
程序加载后,ds中存放这程序所在内存区的短地址,这个内存区的拍偏移地址为0,则程序所在的内存区的地址为ds:0
这个内存区的前256个字节中存放的是PSP,shell用来与程序进行通讯,从256字节处向后的空间存放的是程序
因此假设DS=SA,则CS=DS+10H
[BX]和loop指令
[BX]和内存单元的描述
[BX]是什么
和[0]有点相似,[0]表示内存单元,他的偏移地址是0
我们要完整的描述一个内存单元,需要两种信息
内存单元的地址
内存单元的长度
我们用[0]表示一个内存单元时,0表示单元的偏移地址,段地址默认在ds中,单元的长度可以由具体指令中的其它操作对象(比如寄存器)指出
mov ax,[bx]
功能:bx中存放的数据作为一个偏移地址EA,段地址SA默认存放在ds中,将SA:EA中数据送入ax中
mov [bx],ax
功能:bx中存放的数据哦为一个偏移地址EA,段地址SA默认在ds中,,将ax中的数据送入内存SA:EA中
loop
指令的格式:loop标号,CPUzhixingloop指令的时候,要进行两步操作:
[cx]=[cx]-1
判断cx中的值,不为零则转至标号处执行程序,如果为零则向下执行
cx的值影响着loop指令的执行结果
通常我们用loop指令来实现循环功能,cx中存放循环次数

程序分析:
在汇编语言中,标号代表一个地址,此程序中有一个标号s,它实际上表示了一个地址,这个地址处有一条指令:add ax,ax
loop s
CPU在执行loop s是,要进行两步操作
1.
cx=cx-1;
2.
判断cx中的值,不为0则是转至标号s所标识的地址处执行,如果为零则执行下一条指令
用cx和loop指令相配合实现循环功能的程序框架如下:
mov cx,循环次数
s:
循环执行的程序段
loop s

一段安全的空间
在实际中,随意向一段内存空间中写入内容是很文献的,因为这段空间可能存放着重要的系统数据或代码
我们在操作系统的环境中工作,其管理所有的资源,包括内存,有需要的话要使用操作系统给我们分配的空间,而不是直接用地址任意地址内存单元
小结
[bx]的作用:作为偏移地址与ds配合
loop和cx合作完成循环
含有多个段的程序
程序实例
在操作系统的环境中,合法的通过操作系统取得的空间都是安全的,因为操作系统不会让一个程序所使用的空间和其他程序所用的空间以及系统自己的空间冲突。在操作系统允许的情况下,程序可取得任意容量的空间
系统取得所需空间的方法有两种
在加载程序的时候为程序分配
程序执行的过程中向系统申请
从规范的角度来讲,我们是不能自己随便决定那段空间可以使用的,应该让系统为我们分配。我们可以在程序中,定义我们希望处理的数据,这些数据会被编译、连接程序作为程序的一部分写到可执行文件中。当可执行文件中的程序被加载入内存时,这些数据也同时被加载入内存中。与此同时,我们要处理的数据也就自然而然地获得了存储空间

程序第一行的 dw 的含义是定义字型数据,dw 即define word
end的作用
end除了通知编译器程序结束外,还可以通知编译器程序入口在什么地方



在源程序中为这三个段起了具有含义的名称,但我们这样命名,仅仅只是为了程序便于阅读,这些名称仅在源程序中出现,CPU并不知道他们
我们在源程序中使用assume将段与寄存器相连,但assume是伪指令,由编译器执行,也是仅在源程序中存在的信息,CPU并不知道他们
若要让CPU按照我们的安排行事,就要用机器指令控制它,源程序中的汇编指令是CPU要执行的内容。
我们在源程序的最后一行用“end start”说明了程序的入口,这个入口将被写入可执行文件的描述信息,可执行文件中的程序被加载入内存后,CPU的CS:IP被设置指向这个入口,从而开始执行程序的第一条指令。标号“start” 在code 段中,因此CPU将code段当作指令来执行
我们在code段中使用指令设置ss指向stack,CPU执行这些指令后,将把stack段当作栈空间来用,CPU要访问呢data段中的数据,则可用ds指向data段,用其它的寄存器来存放data段中的偏移地址
总之,CPU如何执行我们段中的内容,是当作指令、数据还是栈空间,完全是靠程序中具体的汇编指令,和汇编指令对CS:IP、SS:SP、DS等寄存器的设置决定的 * *


定义一个段的方法和前面所讲的定义代码段的方法没有区别,只是对于不同的段,要有不同的段名
在程序中,段名就相当于一个标号,它代表了段地址。一个段的段地址可有段名代表,偏移地址看他在段中的位置
8086CPU不允许将一个数值直接送入段寄存器中。
程序中对段名的引用,将被编译成一个表示段地址的数值
更灵活的定位内存地址的方案
and和or指令
and指令:逻辑与指令,按位进行与运算

or指令:逻辑或指令,按位进行运算

ASCII码(美国信息交换标准代码)
世界上有很多编码方案,有种方案叫做ASCII编码,是在计算机系统中通常被采用的
简单来说,所谓编码方案,就是一套规则,他约定了用什么杨的信息来表示现实对象
一个文本编辑过程中,就包含着按照ASCII编码规则进行的编码和解码
我们按下键盘上的a键,这个按键的信息被送入计算机,计算机用ASCII码规则对其进行编码,将其转化为61H,存储在内存的指定空间中,文本编辑软件从内存中取出61H,将其送到显卡的显存中;工作在文本模式下的显卡,用ASCII码的规则解释显存中的信息,61H被当作字符a,显卡驱动显示器,将字符a的图像画在屏幕上。我们可以看到,显卡在处理文本信息的时候,是按照ASCII码的规则进行的,这也就是说,如果我们要想在显示器上看到a,就要给显卡提供a的ASCII码,即将其写入显存中
大小写转换
方法一
* *
方法二
* * 


SI和DI
SI和DI是8086CPU中和bx功能相近的寄存器,但是SI和DI不能分成两个8位寄存器来使用
示例一
* * 


示例二
不同寻址方式的灵活运用
如果我们比较一下i前面用到的几种定位内存地址的方法(寻址方式),就可以发现有以下几种方式
[idata] 用一个常量表示地址,可用于直接定位一个内存单元
[bx] 用一个变量来表示内存地址,可用于直接定位一个内存单元
[bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上间接定位一个内存单元
[bx+si+idata]用两个变量和一个常量表示地址
举例
将字符串中首字母由小写变成大写
* * * 


将字符串中所有小写字母变成大写
* * *


将一个寄存器的工作交由其他寄存器来进行始终是不好的(如cx记录循环次数,上例中将循环次数交由dx执行),因为该寄存器也有可能正在使用或将要使用,这种情况下应该使用栈,将外层循环次数压入栈,待内层循环执行完后再将外层循环数取出 * * *



数据处理的两个基本问题
我们知道,计算机是进行数据处理、运算的机器,那么有两个基本的问题就包含在其中:
处理的数据在什么地方
要处理的数据有多长
这两个问题,在机器指令中必须给已说明,否则计算机无法工作
在8086CPU中,只有这四个寄存器(bx,bp,si,di)可以用在【】中进行内存单元的寻址
只要在【】中使用寄存器bp,而指令中没有显性的给出段地址,段地址就默认在ss中
处理的数据在什么地方
绝大部分机器指令都是进行数据处理的指令,处理大致可分为三类
读取
写入
运算
在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,他要处理的数据所在的位置
指令在执行前,所要处理的数据可以在三个地方:CPU内部、内存、端口

如何表达数据的位置
在汇编语言中用三个概念来表达数据的位置
立即数(idata)
对于直接包含在机器指令中的数据(执行前在CPU的指令缓冲器中),在汇编语言中称为:立即数,在汇编指令中直接给出
mov ax,1
寄存器
指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名
mov ax,bx
段地址SA和偏移地址EA
指令要处理的数据在内存中,在汇编指令中可用【】的格式给出EA,SA在某个段寄存器中
存放段地址的寄存器可以是默认的
存放短地址的寄存器也可以显式地给出
寻址方式
当数据存放在内存中的时候,我们可以用多种方式来给定这个内存单元的偏移地址,这种定位内存单元的方法一般被称为寻址方式

指令要处理的数据有多长
通过寄存器名指明要处理的数据的尺寸(ax,al)
在没有寄存器名存在的情况下,用操作符 X ptr 指明内存单元的长度,X在汇编指令中可以理解为word或byte mov word ptr[bx],38
div指令
div指令是除法指令,使用div作除法的时候:
除数:8位或16位,在寄存器或内存单元中
被除数:默认放在AX或DX和AX中
div指令格式:
div reg
div 内存单元
div byte ptr ds:[0]
(al)=(ax)/((ds)*16+0)的商
(ah)=(ax)/((ds)*16+0)的余数
div word ptr es:[0]
(ax)=[(dx)10000H+(ax)]/((es) 16+0)的商
(dx)=[(dx)10000H+(ax)]/((es) 16+0)的余数
16位的除法实例

8位的除法实例

伪指令dd
dd是用来定义dword(double word)型数据的

第一个数据为01H,在data:0处,占一个字节
第二个数据为0001H,在data:1处,占一个字
第三个数据为00000001H,在data:3处,占两个字
dup
dup是一个操作符,在汇编语言中同db、dw、dd等一样,月是由编译器识别处理的符号。
他是和db、dw、dd等数据定义伪指令配合使用的,用来进行数据的重复
dup的使用格式:db 重复的次数 dup(重复的字节型数据)
dup示例
db 3 dup(0)
定义了三个字节,它们的值都是0
相当于db 0,0,0
db 3 dup(0,1,2)
定义了9个字节,相当于0,1,2,0,1,2,0,1,2
db 3 dup(‘abc’,’ABC’)
定义了18个字节
转移指令的原理
8086CPU的转移指令分为以下几类
无条件跳转指令 jmp
条件转移指令
循环指令 loop
过程
中断
操作符offset
操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址

jmp指令
jmp指令为无条件跳转,可以只修改IP,也可以同时修改CS和IP
jmp指令要给出两种信息
转移的目的地址
转移的距离(段间转移、段内短转移、段内近转移
依据位移进行转移的jmp指令
jmp short 标号 (转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,它对IP的修改范围是-128~127,也就是说,他向前转移时卒子多可以越过128个字节,向后转移可以最多越过127个字节

jmp指令中的标号是代码段中的标号,指明了指令要转移的目的地,转移指令结束后,CS:IP应该指向标号处的指令
在一般的汇编指令中,汇编指令中的idata,不论它是一个数据还是内存单元的偏移地址,都会在对应的机器指令中出现,因为CPU执行的是机器指令,它必须要处理这些数据或地址
CPU在执行jmp指令的时候并不需要转移的目的地址,而包含的是转移的位移。这个位移,是编译器根据汇编指令中的“”标号“计算出来的,
编译程序根据此两点的距离算出位移量
jmp near ptr 标号 *
实现段内近转移,转移范围为-32768~32767
转移的目的地址在指令中的jmp指令
jmp far ptr 标号 *
实现段间转移/远转移
转移地址在寄存器中的jmp指令
jmp 16位reg
转移地址在内存中的jmp指令
转移地址在内存中的jmp指令有两种格式
jmp word ptr 内存单元地址 (段内转移)
从内存单元地址处开始存放一个字,是转移的目的偏移地址
*
* *
jmp dword ptr 内存单元地址 (段间转移)
从内存单元地址处开始存放着两个字,高地址的字是转移的目的段地址,低地址时转移的目的偏移地址
*
jcxz指令
jcxz指令位有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址,对IP的修改范围都为-128~127
指令格式:jcxz 标号 *
如果cx=0,则跳转到标号处执行
*
loop指令
loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址
对IP的修改范围为-128~127
指令格式 loop 标号
如果cx!=0 转移到标号处执行
根据位移进行转移的意义
如果loop s的机器码中包含的是s的地址,则对程序段在内存中的偏移地址有了严格的限制,反之,使用偏移量可以保证程序正常执行的同时保证扩展性、可移植性和复用性
call指令和ret指令
call指令和ret指令都是转移指令,他们都修改IP,或同时修改CS和IP
ret和retf
ret
ret指令用栈中的数据,修改IP的内容,从而实现近转移
retf
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移
可以看出,如果我们用汇编语法来解释ret和retf指令,则
CPU执行ret指令时,相当于进行:pop IP
CPU执行retf指令时,相当于进行 POP IP POP CS
call指令
call指令经常跟ret指令配合使用,因此CPU执行call指令,进行两步操作
将当前的IP或CS和IP压入栈中
转移
call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同
依据位移进行转移的call指令
call 标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此种格式的call指令时,进行如下的操作:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(IP)+16位位移
转移地址在指令中的call指令
CPU执行 call far ptr 标号时,相当于进行:
push CS
push IP
jmp far ptr 标号
转移地址在寄存器中的call指令
call word ptr 内存单元 *
call dword ptr 内存单元 *
call和ret的配合使用
CPU执行这个程序的主要过程
CPu将call s指令的机器码读入,IP指向了call s后的指令mov ax,4c00h,然后CPu执行call s指令,将当前的IP值压栈,并将IP的值改编为标号s处的偏移地址
CPU从标号s处开始执行指令,loop循环完毕,(ax)=8
CPU将ret指令的机器码读入,IP指向了ret指令后的内存单元,然后CPUzhixingret指令,从栈中弹出一个值(即call先前压入的mov指令的偏移地址)送入IP中。则CS:IP指向mov ax,4c00H
CPU执行mov指令,直至完成
执行完子程序后们如何让CPU接着call指令向下执行
call指令后面的指令的地址将存储在栈中,所以可以在子程序的后面使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行
call和ret指令对模块化程序设计提供了实现上的支持,利用call和ret指令,我们可以用简介的方法,实现多个互相联系、功能独立的子程序来解决一个复杂的问题
mul指令
乘法指令,使用时,相乘的两个数,要么都是8位,要么都是16位
8位:AL中和8位寄存器或内存字节单元中,结果放在AX中
16位:AX中和16位寄存器或内存字单元中 结果放在DX(高位)和AX(低位)中
示例
mul byte ptr ds:[0]
结果存储在ax中
mul word ptr [bx+si+8]
ax存储结果的低16位
dx存储结果的高16位
字符串显示
标志寄存器
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字
flag
flag和其他寄存器不一样,其他寄存器是用来存放数据的,都是整个寄存器具有一个含义
二flag寄存器是按位起作用的,也就是说,他的每一位都有专门的含义,记录特定的信息
8086CPU的flag寄存器的结构
* *
flag的某些位不具有任何意义,而有些位具有特殊的含义
flag的第六位是ZF,零标志位,他记录相关指令执行后 *
结果为0,ZF=1
结果不为0,ZF=0
对于ZF的值,我们可以认为ZF标记指令相关的计算结果是否为0,如果为0,则在ZF记录下信息
flag的第二位是PF,奇偶标志位 *
他记录指令执行后,结果所有的二进制中为1的个数
为偶数,PF=1
为基数,PF=0
flag的第七位是SG,符号标志位,他记录指令执行后结果的正负 *
结果为负,SF=1
结果为正,SF=0
