汇编语言大灰狼.docx
《汇编语言大灰狼.docx》由会员分享,可在线阅读,更多相关《汇编语言大灰狼.docx(25页珍藏版)》请在冰豆网上搜索。
![汇编语言大灰狼.docx](https://file1.bdocx.com/fileroot1/2023-2/21/23e00067-e985-45f2-9b36-f3a27f72bcf3/23e00067-e985-45f2-9b36-f3a27f72bcf31.gif)
汇编语言大灰狼
CPU的结构
CPU对设备的读写
1、程序是如何运行的?
·双击程序应用程序载入内存CPU找到应用程序在内存中的位置通过某种途径把程序传送给CPU
CPU通过一些很细的线与外围设备进行通讯,这些线叫做总线
CPU与内存通信的线叫做地址总线(通过这里将应用程序在内存中的位置的地址传递给CPU)
在找到应用程序在内存中的位置之后呢,还得想办法把应用程序的内容传递给CPU,这个途径成为数据总线
与CPU连接的除了地址总线和数据总线之外,还有一个是控制总线,即:
CPU用来控制外围设备的(如硬盘、光驱、声卡等)
2、CPU是如何传输数据的?
CPU运行起来时0101的数据,这些线只能传输两种信号,即0和1信号,高低电平分别是1和0信号。
8086CPU有20条地址总线,从CPU连到内存,则CPU的最大寻址范围就是2
=1024KB=1MB,即8086最大寻址范围是1MB
假设CPU要到内存中找数据,则CPU要把应用程序的地址传送给内存,然后内存通过地址找到相应的数据,再通过数据总线把内容传输给CPU。
其中CPU中有一个内存控制器,它会根据CPU传递过来的地址找到相应的数据内容。
控制总线用来干什么呢?
有的时候CPU要读内容,有的时候要写内容,则是通过控制总线执行什么动作。
CPU就是通过这三种总线与外围设备通讯的。
3、总线的宽度
所谓总线的宽度就是指CPU有多少根线连接到内存当中,或者说地址总线的宽度决定了CPU的最大存储范围,也可以说CPU是多少位的就是多大的寻址范围。
比如说CPU是32位的话,最大的寻址范围就是2的32次方就是4G内存,所以即便你安装了8G的内存,另外4G是无法利用起来的。
2
=1MB
4、继续探讨CPU执行程序
当内存将数据送到CPU之后,是不是立即就开始执行程序呢?
不是的,CPU是先将程序的数据放入寄存器,然后再从寄存器里取出数据来执行
寄存器概念
5、那什么事寄存器?
它有什么作用?
答:
存放CPU要执行的数据或指令
寄存器分为:
通用寄存器和段寄存器
寄存器知识补充:
1数据寄存器(或称通用寄存器)
数据寄存器包括AX,BX,CX,DX四个通用寄存器,他们可以以字16位的形式使用,也可以以字节8位的形式使用。
以字形式使用时四个通用寄存器称为AX,BX,CX,DX,以字节形式使用,高八位通用寄存器称AH,BH,CH,DH。
低八位称AL,BL,CL,DL。
这四个都是通用寄存器,又可用于专用的目的。
AX做累加器用(ACCUMALATOR)
BX在计算存储器地址时,经常用做基地址寄存器,所以又称基址寄存器。
(BASE)
CX(COUNT)可用做通用寄存器。
此外,在循环( LOOP)和串处理指令中用做隐含的计数器。
DX(DATA)在做双字长的运算时,把DX和AX组合在一起存放I/O端口地址。
2,指针及变址寄存器
他们包括SP,BP,SI,DI四个16位寄存器。
他们可以象数据寄存器一样在运算过程中存放操作数,单他们只能以字16位为单位使用。
SP(STACK POINTER)堆栈指针寄存器;
用来指示堆栈的栈顶的偏移地址,与SS堆栈段寄存器形成栈顶存储单元的物理地址。
BP(BASE POINTER)基址指针寄存器。
用来指示堆栈中某个数据区的偏移地址-----基地址。
SI(SOURCE INDEX)源变址寄存器;
DI(DESTINATION INDEX)目的变址寄存器;
3,段寄存器
包括CS,DS,SS,ES四个16位段寄存器
CS(CODE SEGMENT)代码段寄存器
SS(STACK SEGMENT)堆栈段寄存器
DS(DATA SEGMENT)数据段寄存器
ES(EXTRA SEGMENT)附加段寄存器
8086/8088采用存储空间的分段技术来解决寻址1M字节的存储空间。
这些段寄存器的内容和有效的地址偏移量(称偏移地址)一起可确定内存的存储单元的物理地址。
CS控制程序区DS和ES控制数据区,SS控制堆栈区。
4控制寄存器
分为两个16位的寄存器IP和PSW。
IP(INSTRUCTION POINTER)指令指针寄存器;他用来存放代码段中的偏移地址。
程序运行中始终指向下一条指令的首地址。
计算机就是用IP寄存器来控制指令序列的执行流程的
PSW(PROGRAM STATUS WORD)程序状态字寄存器或称标志寄存器;
由状态码标志和控制标志构成,
OF溢出标志;运算结果超出机器能表示的数值范围称溢出OF=1,否则OF=0;
SF符号标志;运算结果的符号为负时置1否则置0
ZF;零标志
CF进位标志
AF辅助进位标志
PF奇偶标志
DF方向标志 DF=1每次操作后使SI和DI减量,使串处理指令向低地址方向进行
IF中断标志
TF跟踪标志
控制标志是由系统程序或用户程序根据需要用指令来设置的。
AX寄存器(8086寄存器的一种.成为累加寄存器)
AX寄存器是通用寄存器,是专门用来存放数据的寄存器。
高8位成为AH,低8位成为AL。
它是如何组织数据的?
AX中有两个字节
字节:
用8个二进制数表示一个字节
字:
用两个字节表示一个字,即16位二进制数据
双字:
两个字表示,即32位二进制数
当然还有4字的。
CPU内部是由哪些部件构成的?
1、寄存器
2、运算器(+-*/)
3、控制器,控制各器件运行,发送一些命令(各个运算器做什么有这个决定)
4、内部总线连接各种器件,在它们之间进行数据传输
物理地址表示方法
8086主板地址线20根进行寻址必须一次传20位二进制数,但是CPU最大一次只能床16个二进制位,如何解决?
比如有两个小纸条,每张最低只能写三位数字,要求用这两张纸条来表示一个四位数字
CPU也是类似于这样进行处理的。
CPU用地址加法器完成这样的功能
如上图,234成为基地址,23成为偏移地址
CPU用基地址+偏移地址得到实际物理内存地址
内存地址的表示方法:
基地址:
偏移地址=实际内存地址
计算方法:
实际内存地址=基地址*16+便宜地址
解释:
我们人是基于十进制运算的,要想后面加一个0即加一位,所以用234*10然后加23,而8086计算机是基于16进制的,所以要乘以16然后加上偏移地址
具体例子:
1402:
100;
1402H*16(10进制,16基址的10H)+0100H=14120H
其中H表示16进制。
CPU如何对内存进行逻辑的分段处理
1000H转化为基地址:
偏移地址
1000H:
0000
寄存器分为公用寄存器和段寄存器,通用寄存器用来存放普通的数据,而段寄存器与这里的段地址是一个概念,就是存放基地址的。
段地址和偏移地址
CS和IP寄存器
主要内容:
CS、IP寄存器;debug命令的使用;内存访问
1、程序被载入内存后,CPU是从哪里开始执行程序代码的?
在C语言中main函数是第一个被执行的
那么在汇编语言中是从哪里开始的呢?
我们知道,段地址*16+偏移地址=实际物理地址
CS(codesegment)寄存器保存了要被CPU执行的代码的基地址
IP寄存器保存了要被CPU执行代码的偏移地址
IP寄存器别名为指令指针寄存器
也就是说:
CS*16+IP=实际地址
2、Debug工具
什么是debuge
Debug是DOS、Windows都提供的实模式程序调试工具可以查看CPU各种寄存器中的内容和机器码级跟踪程序的运行
Debug命令的使用
Debug的R命令用来查看和改变各个寄存器内容
Debug的D命令查看内存中的内容
屏幕上的内容有3部分:
左边显示的是用“基地址:
偏移地址”即“CS:
IP”的形式表示内存内容。
中间是用16进制的形式表示内存内容。
右边是由ASCII的形式表示内存内容
如果想查看某个寄存器的内容,可以在d的后面加相应的寄存器:
0000
然而用16进制表示的内容我们看不懂,如果希望用汇编语言显示,则可以用下面的命令
Debug的U命令将内存机器码转为汇编指令
下面用ucs:
0100表示查找cs寄存器
其中左边是基地址:
偏移地址。
中间是16进制表示的操作指令。
右边两行表示的是汇编指令
Debug的T命令跟踪代码运行
Debug的A命令以汇编指令格式在内存中写入指令
以上是把1234赋值给ax寄存器
验证:
CSIP指定的存地址中的内容就是CPU要执行的内容
第一步:
看一下CS中当前的内容
之后我们用T命令跟踪程序的执行
看出:
第一个cs=0B6FIP=0103即ES:
命令
第二个CS=0B6FIP=0108即JZ命令
第三个CS=0B6FIP=010A即CALL命令
通过相邻两个ip的差可以算出上一个指令所占用的空间是多大
下面看一个例子:
intnumber;
intmain(){
inta=10;
if(a>0)
a++;
return0;
}
程序分为:
数据段,,代码段,堆栈段
其中数据段用来存放应用程序的全局变量,如本例的intnumber
代码段:
if(a>0)a++;
堆栈段:
inta=10;局部变量
如果我们想为全局变量赋值,我们必须先找到其在内存中的地址
CPU根据什么指定内存中的哪些数据是我们定义的全局变量?
即DS寄存器(内存访问)
CPU是根据DS(DataSegment)这个寄存器和任意一个通用寄存器的值或其他数值组成数据段的物理地址如:
DS:
[0]或者DS:
[BX]
第一个表示方法是将一个内容直接保存在DS寄存器的一个地址中
第二种表示时将一个内容保存到另一个BX通用寄存器中
当然还有其他表示方式
上面两种方式表示的都是物理内存中具体的地址
因为全局变量保存在内存地址中,因此我们要访问这些物理地址
内存的表示方法:
在汇编语言中表示内存的两种写法
movds:
[13ABH],1234H
即把1234放入内存地址ds*16+13AB中
mov[13ABH],1234H
第二种更常见
堆栈
主要内容:
栈的概念ss寄存器和sp寄存器post和pop指令
栈的数据结构:
栈是一种具有特殊的访问方式的存储空间,它的特殊性就在于,最后进入这个空间的数据,最先出去。
数据出栈:
在内存中栈是从高字节向低字节存放数据
堆栈段存放的是函数的局部变量,数据段存放的是全局变量
ss、sp寄存器
同代码段和数据段一样,CPU如何知道一段内存空间被当做栈使用?
Cpu是根据ss这个段寄存器和sp这个通用寄存器来感知堆栈段的存在。
也就是说,cpu从ss中取出一个值,再从sp中取出一个值,ss*16+sp所指向的那个内存单元即是栈内存
段寄存器ss存放栈的基地址,sp存放的是栈顶的偏移地址
执行入栈和出栈的时候,如何知道哪个单元是栈顶单元?
cpu规定,任何时候ss:
sp都指向站顶元素
push指令的执行过程:
如果ax=1234h。
即ax寄存器中存放的是1234这个16进制的数,现在想把它放入栈中,假设现在是空栈,ss:
sp指针指向栈顶,此时,将34压入栈低,12放到栈顶,即先放低字节的数,再放高字节的数,最后ss:
sp指针指向12
具体过程:
pushax
(1)将ax中的内容送入ss:
sp指向的内存单元处,ss:
sp此时指向新栈顶
(2)Sp=sp-2;通过这条指令使指针往上移
(3)入栈数据是从高字节往低字节进行排列的,即最上面是高字节。
出栈则相反
Popax
将ss:
sp指向的内存单元处的内容赋值给ax寄存器,然后sp+2.,ss:
sp此时指向新的栈顶
出栈数据是从低字节往高字节排列
操作解释:
a命令,用汇编语言的形式在内存中写入指令
movax,1234将1234赋值给ax寄存器
之后分别是:
将7ba1、2213分别写入bx、cx寄存器
pushax:
将ax中的数据入栈
之后分别是:
将bx、cx入栈
popax:
将ax出栈
之后分别是:
将bx、cx出栈
由于入栈顺序和出栈顺序相反,所以将三个数据经过这样的处理之后,三个数字的顺序将反着输出。
下面用t命令跟踪程序验证:
可以发现前三步分别对ax、bx、cx赋值了,显著的变化时ax,bx,cx的值变了,而且ip的值每次加3
继续验证:
这三步分别是将ax,bx,cx入栈,入栈时会将数据放到栈中,显著的变化时sp的值每次减3
下面继续验证,出栈:
这三步进行了出栈操作,可以发现的显著变化时sp每次加3。
而且ax的值和cx的值对换了
关于栈的疑问
1、当栈满时再进行入栈会发生什么情况?
2、当栈空时再使用出栈指令会发生什么情况?
栈顶越界是危险的!
因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据和代码,这些数据或代码可能是我们自己的程序,也可能是别的程序中的
ss:
sp寄存器配合指定了栈顶位置,可是如何保证在入栈和出栈时栈顶不会超出栈空间?
8086CPU不保证对栈的操作不会越界
也即:
8086CPU只知道栈顶在何处,(由ss:
sp指示),而不知道读者安排的栈空间有多在。
第一个HelloWorld汇编程序
汇编集成开发环境
如何利用汇编语言编写一个简单的Helloworld应用程序?
1、汇编语言中变量又是如何定义的?
2、汇编语言如何把要显示的内容输出到屏幕上呢?
3、汇编语言编写的程序如何进行调试?
编写包含多段的应用程序
msg是全局变量,应该放在应用程序的数据段中
打印msg变量的是程序代码,应该放在应用程序的代码段中
4、如何让汇编语言知道我们编写的程序是有多个段组成的?
assume关键字
assume关键字表示用来假设某一段寄存器和程序中的某一个用segment…ends定义的段相关联(假设的意思)
db指令
那么如何在数据段中定义全局变量msg并赋初值?
使用db(definebyte)指令来定义字节的内容,语法:
labeldbinitalizer,initializer,initializer
label表示任选标号,相当于C语言的变量名
db表示definebyte定义个字节数据
initalizer表示初始值
msgdb“helloworld”这里写单引号也行,在汇编中都一样
上面的语句相当于C语言的:
char[]msg=“helloworld”
下面研究如何打印出上面定义的数据
观察上面的内存地址空间图,我们说:
只要我们把“helloworld”这个字符串拷贝到显存地址空间,我们的显卡就自动地把它显示到屏幕上
那么在CPU中哪一段地址空间是显存地址空间呢?
CPU已经为我们安排好了!
其中显示地址中又分为很多种显示模式
vga显存地址空间
在内存地址空间中,B8000H~BFFFFH共32的地址空间80*25(列X行)彩色字符模式(vga)的显示缓冲区,只要向该地址空间写入数据,写入的内容就立刻显示到屏幕中
8个byte可以表示一个字符,紧跟其后的是8个byte表示颜色,即用16位二进制数表示一个颜色的字符
VGA的英文全称是VideoGraphicArray,即显示绘图阵列。
VGA支持在640X480的较高分辨率下同时显示16种色彩或256种灰度,同时在320X240分辨率下可以同时显示256种颜色.
肉眼对颜色的敏感远大于分辨率,所以即使分辨率较低图像依然生动鲜明。
VGA由于良好的性能迅速开始流行,厂商们纷纷在VGA基础上加以扩充,如将显存提高至1M并使其支持更高分辨率如800X600或1024X768,这些扩充的模式就称之为VESA(VideoElectronicsStandardsAssociation,视频电子标准协会)的SuperVGA模式,简称SVGA,现在的显卡和显示器都支持SVGA模式。
不管是VGA还是SVGA,使用的连线都是15针的梯形插头,传输模拟信号
高亮的意思是粗体显示
vag显存地址空间
在80*25彩色模式下显示器可以显示25行80列。
每个字符可以有256种属性(背景色前景色闪烁等)
一个字符在显存中占两个字节,分别存放ASCII码值和属性
显示缓冲总共分为8页,每页4KB,显卡可以显示任意页内容一般情况下显示第0页内容即B8000H~B8F9FH(4K的空间)
到目前学过的8086段寄存器有
CS(CODE SEGMENT)代码段寄存器 、SS(STACK SEGMENT)堆栈段寄存器 、DS(DATA SEGMENT)数据段寄存器
这几个寄存器都有它自己特定的功能,我们不能随便给它们赋值,那怎么办呢?
CPU还为我们分配了一个ES(EXTRA SEGMENT)寄存器,叫做附加段寄存器 ,这个寄存器专门存放我们的段地址,即B800H这个段地址存放到这个ES寄存器中:
moves,0b800h
上句说明:
给es寄存器赋值B800,因为是16进制,所以后面加一个H,为了CPU能辨认这是一个16进制的数,所以前面加了一个0,当16进制数第一个数字不是字母的时候就不用加0了
但是CPU规定,我们不可以直接将一个常量直接赋值给一个ES寄存器,所以必须通过一个中间的通用寄存器中转一下:
movbx,0b800h
moves,bx
现在我们再把helloworld的一个个的拷贝到以ES为段地址,以0为偏移地址的寄存器中,现在用一个循环来做这个工作
汇编中的循环loop关键字
如何把我们定义的字符一个个的拷贝到显存的缓冲区中?
语法:
标号:
指令1
指令2
loop标号
那么循环次数怎么指定呢?
这个数字存放在CX寄存器中,循环的次数由CX寄存器中的数据来决定,循环完一句自动减一。
循环内部如何实现功能?
如何获得在数据段中的每一个内容?
这涉及到内存访问
1、我们知道helloworld属于数据段内容,那么其中每个字符都可以通过数据段地址加上偏移地址获得
2、汇编中数据段地址存放在ds寄存器中,那么要获得数据段第一个字节内容就要如下表示:
ds:
[0]或ds:
[si]
第一个表示获得段地址在ds中,偏移地址是0.
在循环中偏移地址应该是可变的,因此应放在一个寄存器中
si寄存器相当于通用寄存器
把H从数据段ds中拷贝到es寄存器中的代码如下:
moves:
[0],ds:
[0]
在loop中应该用变量替换0
movsi,0//初始化为0
movbx,0
s:
moves:
[bx],ds:
[si]
但是汇编中不支持这样的语法,因为这只指定了初始地址,没有指定结束语地址
所以也需要一个中间的寄存器导一下
s:
movax,ds[si]
moves,ax
但是一个字符是8位的,但是ax是16位的,此时我们只用其一半就可以
al和ah寄存器
设计80*86cpu时,为了能够完全兼容8088cpu(8位cpu),我们可以把通用寄存器AX,BX,CX,DX分别当作两个8位寄存器来使用
AX寄存器可以分成AH和AL两个8位寄存器,其余类似
s:
moval,ds[si]
moves,al
然后我们知道,字符的属性紧跟在字符后面,我们用红底绿字01000010b
movah,01000010b
…
moves:
[bx+1],ah
然后si加1
incsi
bx要加2
addbx,2
最后循环结束
loops
代码段和数据段我们只是人为的规定了
Assumecs:
code,ds:
data
但是编译器不知道,因此必须明确指定
现在我们为程序划分出了代码段和数据段,那么CPU是否就能够执行代码段中的指令和数据中的数据呢?
答案是否定的,因为这是我们人为给予的标记,方便我们阅读CPU却完全不知道
解决:
把标记了data的数据段地址赋给ds段寄存器,
movax,data
movds,ax
此时编译器就知道了data代表了数据段
代码段CPU可以自动获得代码段地址
CPU可以获得代码段的段地址,却不知道其偏移地址,此时可以在代码首行加入一个start关键字,在最后加一个endstart
屏幕暂停:
movax,4c00h
int21h
所有代码
assumecs:
code,ds:
data
datasegment
msgdb"helloworld"
dataends
codesegment
start:
movax,data
movds,ax
movbx,0b800h
moves,bx
movcx,11
movsi,0
movbx,0
movah,01000010b
s:
moval,ds:
[si]
moves:
[bx],al
moves:
[bx+1],ah
incsi
addbx,2
loops
movax,4c00h
int21h
codeends
endstart
中断
1、中断的概念
a)什么是中断?
任何一个通用CPU,比如8086都具备一种能力,可以执行完成当前正在执行的指令后,检测到从CPU外部或内部产生的一种特殊信息,并立即对接收的信息进行处理。
这种信息称之为中断信息
中断的意思是指:
CPU不再接着(刚刚执行的指令)向下执行,而是转去处理这个特殊信息
2、关于中断的疑问:
中断发生时CPU如何找到中断处理程序?
中断处理程序有很多种,那么每个中断程序存放在哪里?
中断处理完成之后,CPU如何继续运行之前的中断的程序?
中断向量表
中断向量表在内存中保存,其中存放着256个中断所对应的中断处理程序的入口,如下
0号中断对应的中断处理的入口地址
1号中断对应的中断处理的入口地址
2号中断对应的中断处理的入口地址
3号中断对应的中断处理的入口地址
…
那么中断向量表一般存放在哪里呢?
CPU规定了内存中的某个地址专门存放中断向量表
中断向量表一般保存在内存0000:
0000到0000:
03FE
那么在中断向量表中一表项占多少内存空间?
一个表项存放一个中断向量,也就是一个中断程序入口地址,这个入口地址包括段地址和偏移地址
因此每个表项占两个字的空间,高地址存放段地址,低地址存放偏移地址
考虑一个问题:
当CPU执行完中断处理程序后必须返回继续执行原来的命令,那么CPU根据什么能够回到原来执行代码中断执行?
遇到中断时CPU把CS和IP内存入栈暂时保存起来,等中断程序执行结束后通过出栈指令重新获得原来的CS和IP的值(这就是C语言函数调用的本质)
中断过程这些操作,CPU自动完成,不需要编写代码
0号中断:
除法中断
assumecs:
code
codesegment
start:
movax,1000h
movbh,1