学习汇编
这篇文章用来记录王爽版8086cpu的学习历程
两个重要的知识点
学习汇编语言,首先必须了解两个知识点:寄存器和内存模型。
先来看寄存器。CPU 本身只负责运算,不负责储存数据。数据一般都储存在内存之中,CPU 要用的时候就去内存读写数据。但是,CPU 的运算速度远高于内存的读写速度,为了避免被拖慢,CPU 都自带一级缓存和二级缓存。基本上,CPU 缓存可以看作是读写速度较快的内存。
但是,CPU 缓存还是不够快,另外数据在缓存里面的地址是不固定的,CPU 每次读写都要寻址也会拖慢速度。因此,除了缓存之外,CPU 还自带了寄存器(register),用来储存最常用的数据。也就是说,那些最频繁读写的数据(比如循环变量),都会放在寄存器里面,CPU 优先读写寄存器,再由寄存器跟内存交换数据。
寄存器不依靠地址区分数据,而依靠名称。每一个寄存器都有自己的名称,我们告诉 CPU 去具体的哪一个寄存器拿数据,这样的速度是最快的。有人比喻寄存器是 CPU 的零级缓存。
王爽8086CPU中的需要记住寄存器
指令
伪指令:没有对应的机器码,由编译器执行,计算机并不执行。
汇编指令:机器码的记助符,有对应的机器码。
CPU要工作必须向它提供指令和数据,指令和数据在存储器中存放,也就是我们平时说的内存,CPU不能使用磁盘上的数据。
CPU要从内存中读数据,首先要指定存储单元的地址。
存储器被划分为若干个存储单元从0开始顺序编码,例如一个存储器有128个存储单元,它可以存储128个Byte,编码从0~127。
最小信息单位bit(比特),也就是一个二进制位。一个存储单元可以存储8个bit,即8位二进制数。8个bit组成一个Byte,也就是一个字节。微型机存储器的存储单元可以存储一个Byte,即8个二进制位。
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。一根导线可以传送的稳定状态只有1高电平和0低电平两种,10根导线可以传送10位二进制数据,而10位二进制数可以表示2的10次方个不同的数据。一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N,这样的CPU最多可以寻找2的N次方个存储单元。
测验题
例,1个CPU的寻址能力为8KB,那么它的地址总线的宽度为13。 解析:1KB=1024B,8KB=1024B*8=2^N, N=13
例题,8080、8088、80286、80386的地址总线宽度分别位16根、20根、24根、32根,则他们的寻址能力分别为:64(KB)、1(MB)、4(MB)、4(GB)。 解析:一个内存单 元=1Byte
8根数据总线一次可以传送8位二进制数据(即一个字节)。
例,8080、8088、8086、80286、80386的数据总线宽度分别为8根、8根、16根、16根、32根,则他们一次可以传送的数据为:1B、1B、2B、2B、4B.
例,从内存中读取1024字节的数据,8086至少读512次,80386至少读256次。
解析:8086的数据总线宽度为16根(即一次传送的数据为2B) 1024B/2B=512,同理1024B/4B=256。
在存储器中指令和数据没有任何区别,都是二进制信息。
就像棋盒中的黑白棋子,只有在棋盘中展开对弈时才有意义,在棋盒中是没有实际意义的
地址总线的宽度决定了CPU的寻址能力;
数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量;
控制总线的宽度决定了CPU对系统中其他器件的控制能力。
内存地址空间的大小受CPU地址总线宽度的限制,8086CPU的地址总线宽度为20,可以传送2^20个不同的地址信息,即可以定位2^20个内存单元,则8086PC的内存地址空间大小为1MB。
mov 指令
将逗号右边的给逗号左边的
要保证寄存器与寄存器之间 数据与寄存器之间 的位数一致性
寄存器是相互独立的, al就是al ah就是ah 不会相互影响
16位寄存器进行16位运算 保存16位数据 8位寄存器进行8位运算保存8位数据
指令是有长度的,一条指令可以有多个字节组成
指令的执行过程
cpu从cs:ip 所指向的内存单元中读取指令,存放到指令存储器中
ip=ip+所读指令的长度,从而指向下一条指令
执行指令缓存器中的内容,回到第一步
在进行数据传送或运算时,要注意指令的两个操作对象的为啥应当是一致的,如下面的指令都是错误的:
mov ax,bl (在8位寄存器和16位寄存器之间传送数据)
mov bh,ax (在16位寄存器和8位寄存器之间传送数据)
mov al,2000 (8位寄存器最大可存放值位255的数据)
add al,100H (将一个高于8位的数据加到一个8位寄存器中)
CPU访问内存单元时,要给出内存单元的地址,所有的内存单元构成的存储空间是一个一维的线性空间,每个内存单元在这个空间都有唯一的地址,我们将这个唯一的地址称为物理地址。CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。
8086CPU有20位地址总线,可以传送20位地址,达到1MB的寻址能力,而8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址位16位。8086CPU采用一种在内部用两个16位地址(段地址+偏移地址)合成的方法来形成一个20位的物理地址。
物理地址=段地址SAx16+偏移地址EA 或 物理地址=基础地址+偏移地址
“段地址x16”更有一种说法是左移4位(指二进制位)。一个数据的二进制形式左移1位,相当于该数据乘以2;左移N位,相当于该数据乘以2的N次方。
左移运算的理解:例,一个数据为2H,二进制形式为10B,对其进行左移运算有:
左移位数二进制十六进制十进制
010B2H2
1100B4H4
21000B8H8
310000B10H16
4100000B20H32
段地址x16必然是16的倍数,一个段的起始地址一定是16的倍数;偏移地址为16为,16位地址的寻址能力为64KB,所以一个段的长度最大为 64KB。CPU可以用不同的段地址和偏移地址形成同一个物理地址。偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻 64KB 个内存单元。
例
给定段地址为0001H,仅通过变化偏移地址寻址,CPU 的寻址范围为0010H到1000FH。
解析:物理地址=SA16+EA,EA的变化范围为0H~ FFFFH ;
物理地址范围为(SA16+0H) ~ (SA16+ffffH),现在SA=0001H,那么寻址范围为 (0001H16+0H)’ ~ (0001 H*16+FFFFH)=0010H~1000FH
有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为1001H ,最大为2000H。当段地址给定为1001H 以下和2000H 以上, CPU无论怎么变化偏移地址都无法寻到20000H单元。
解析:物理地址= SA16+EA;20000H = SA16+EA;SA= (20000H- EA) /16=2000H-EA/16;EA取最大值时, SA=2000H-ffffH/16=1001H;SA为最小值,EA取最小值时,SA=2000H-0H/16=2000H,SA为最大
8086 cpu 设计者给了它20根地址线 表示范围 0~FFFFFH
地址线的数量决定了cpu的寻址能力
地址加法器决定地址的计算方式
段地址* 16(10H)+偏移地址 =物理地址 ,因为8086cpu有20根地址线,16位寄存器无法表示,所以采取这样的方式
段地址 *16 =基础地址
基础地址+偏移地址=物理地址
物理地址
段地址*10H +偏移地址(0~FFFFH)=物理地址
冒号前段地址 冒号后偏移地址
U指令 将某个内存地址 开始的字节 全部当做指令
D 指令 将某个内存开始的字节 全部当成数据
ip寄存器和指令有关
CPU是怎样区分指令和数据的?
8086cpu中 在任意时刻,cpu将cs ip所指向的内容 全部当成指令来执行
在内存中指令和数据是没有任何区别的,都是二进制信息,cpu只有在工作的时候才将有的信息当做指令 有的信息当做数据,cpu根据什么将内>存中的信息当做指令的话,cpu将cs ip 指向的内存单元中的内容当做指令
指令和数据在内存中有区别吗?是没有区别的
在cpu中的cs段地址寄存器和 ip这个偏移地址寄存器 组合的时候从中读取内容当做指令来执行
cs:ip 决定了cpu从哪里开始读取指令 指令是有长度的,一条指令可以由多个字节构成
指令的执行过程
cpu从cs:ip 所指向的内存单元读取指令,存放到指令缓存器中
ip=ip+所读指令的长度,从而指向下一条指令
执行指令缓存器中的内容,回到步骤一
明白ip寄存器和指令的关系
汇编指令 jmp jump的简写
这是一条转移指令,可以修改cs和ip这2个寄存器
两种写法
Jum 2000:0
Jum 寄存器
例题
假设cs=2000H ,ip=0000 写出下列指令的执行过程
a 2000:0 CS=2000H IP=0
mov ax,662H CS=2000H IP=0+3=3
jmp 1000:3 CS=2000H IP=3+5=8
mov cx,ax
a 1000:0 CS=1000H IP=3
mov ax,0123H
mov ax,0 CS=1000H IP=3+3=6
mov bx,ax CS=1000H IP=6+2=8
jmp bx CS=1000H IP=8+2=10(A)
mov cx,0
Debug 调试工具
r 可以查看和改变寄存器中的内容
d 可以查看内存中的内容
u 可以将内存中的机器指令翻译成汇编指令
a 可以以汇编指令的格式在内存中写入一条汇编指令
t 执行 当前 cs :ip 所指向的机器指令
e 可以改写内存中的内容
p 跳过loop
g 跳到指定位置
一个字型数据 存放在内存中 可以有2个连续的地址的内存单元组成
高地址内存单元存放 字型数据的高位字节
低地址 内存单元存放 字型数据的地位字节
例
0 20H
1 4EH
2 12H
3 00H
问题
地址0存放的 字节型数据是多少? 20H
地址0存放的 字型数据是多少? 4E20H 4EH是高位字节 20H是低位字节
地址2 中存放的字节型数据是多少? 12H
地址2中存放的字型数据是多少? 0012H
地址1中存放的 字型数据是多少? 124EH 12H是高位字节 4EH是低位字节
CPU 通过内存地址去访问内存
DS 段地址寄存器 访问数据用的
mov al,ds:[0]
mov移动指令 逗号左边是CPU中的al寄存器,逗号右边是内存地址,0是偏移地址,[]表示得到里面的内容.
小结:
字型数据和字节型数据 在内存中的存放
字型数据在内存中存储时,需要2个地址连续的内存单元存放,高位字节存放在高地址中,低位字节存放在低地址中
CS 和指令有关
DS 段地址寄存器 数据段地址寄存器 和数据有关
CS:IP 指令从哪里来
DS 数据从哪里来
修改寄存器中的内容 去控制cpu 进而控制计算机
在8086cpu中,在任意时刻将段地址寄存器ss和偏移地址寄存器sp所组合出来的内存地址当做栈顶标记
push ax 修改sp寄存器中的数值 sp=sp-2 将ax中字型数据 ->ss:ip所组合内存地址中
pop ss:ip 所组合出来的内存地址中的字型数据 ->bx
修改栈顶标记 sp=sp+2 成为新的栈顶标记 出栈
我们可以 决定栈顶标记在哪里
栈在哪里 (箱子在哪里) 栈的大小(箱子的大小)
栈的设置
起始地址 + 你所设定的栈的大小的字节数
一个栈 最大的空间能设置为多少?
SP寄存器的变化范围是多少?
0~FFFFH 65536/2=32768 字型数据
SS=2000 sp=0 设置一个存放32768个字型数据的箱子
栈的作用
临时性保存数据
用栈进行数据交换
保存在栈中 每执行一条t指令 就会将寄存器保存到栈中
Call ip 保存到栈中 为了让ret指令可以从栈中拿回来
我们如何让cpu按照我们的安排去访问这些内存段呢?
对于数据段来说,段地址 -> ds段寄存器 [0] [1] …… mov add sub 指令去访问这些内存单元
那么CPU就会将我们定义的数据段中的内容 当做数据来访问
对于代码段来说,我们可以通过修改 cs,ip 这2个寄存器,去指向我们定义的代码段
这样cpu就执行我们定义的代码段中的指令
对于栈段来说,我们可以通过修改,ss,sp这2个寄存器去决定栈顶标记在哪里
这样CPU在执行栈的操作时,比如pop,push就会将我们定义的栈段当做栈空间使用
不管我们如何安排,cpu将内存中某段内容当做指令 是因为cs:ip 指向那里
cpu将某段内存当做栈空间是因为 ss:sp 指向那里
数据从哪里来,临时性的数据存放到哪里去,取决于我们cpu中的地址寄存器的设置 cs ip ss sp ds 寄存器
寻址方式的思维导图
子程序
汇编中的子程序调用(对应的高级语言的部分-函数吊用) call 子程序的名字:执行到这里,cpu 会做两件事。一是保存,二是跳转
保存:ip 指令指针。如果是段内跳转。会将当前的ip 偏移值压入栈。如果是段间跳转,会将cs 段寄存器的段地址,和ip 指令指针(偏移值) 压入栈中
跳转:根据子程序的名字跳转到指定的位置
子程序的格式
名字 pro 程序入口
内容 将在这个子程序中会使用到的寄存器压入栈中(在程序结束时要将寄存器弹出栈)
在程序执行结束,子程序末尾:ret 表达式 (段内是ret 段间是retf) 子程序名字 endp 程序执行结束后,堆栈会将要入栈的cs 和ip 弹出栈。让程序返回调用处继续向下执行。 关于ret 后面的表达式 :会改变栈的指针,也就是在CS 和IP 都被弹出栈后,表达式的值就是栈的指针SP 还要移动的字节数
关于参数:
使用寄存器来传递
在调用子程序之前先将参数压入栈中
理解了汇编中子程序的调用,再返回来看高级语言中函数的调用就不难了。在高级语言中定义了函数名是函数的起始地址。所以在调用函数时,都会使用函数名,其实就是相当于跳转目的地址,在堆栈中存放的是在函数调用中的临时变量,存放的函数执行完之后继续执行的地址