3.1内存种字的存储
1.一个内存单元的大小是1B,任何两个地址连续的内存单元,N号单元和N+1号单元,可以将他们看成两个内存单元,也可以看出一个地址为N的字单元中的高位字节单元和低位字节单元
**两个十六进制位是一个字节
1 2 3 4
| 0地址单元中存放的字节型数据:20H 0地址单元中存放的字型数据:4E20H
后面的是高位
|
3.2DS和[address]
1.CPU要读取一个内存单元的时候,必须先给出 这个单元的地址;
2.在8086CPU中,内存地址由段地址和偏移地址组成
3.8086CPU中由一个DS寄存器,通常用来存放要访问的数据的段地址
4.例子:我们要读取1000H单元的内容,可以用如下的程序进行
1 2 3
| mov bx,1000H //这里就是在bx寄存器中放入了1000H这个数据 mov ds,bx //把bx寄存器中的1000H数据放到DS寄存器中 mov al,[0] //[0],这里是偏移地址的意思,CPU在用到偏移地址的时候,会先去找到段地址,在第二部已经把段地址设置成1000H了,所以这句话的意思就是把地址位1000:0的数据放入al寄存器中【这个al的寄存器是ax下的一个low低位寄存器】
|
1 2 3
| 注意: 这里不能mov dx,1000H 只能通过通用寄存器导过来
|
5.mov指令的功能
1 2 3 4 5 6 7 8 9
| 1.将数据之间送入寄存器;【不能直接放入段寄存器】 mov ax,2 2.将一个寄存器中的内容送入另一个寄存器中 mov ax,bx 【通过这种方式来送入段寄存器】 3.将一个内存单元中的内容送入一个寄存器 mov ax,[0] 某个内存单元中的内容,需要用DS[address]指向这个地址后,才会送入另外一个寄存器 也就是说,在有[]的时候是去直接读取DS和address[偏移地址]找到这个地址里面的内容,放入ax寄存器中
|
1 2 3
| mov ax,bx ==>寄存器寻址 mov ax,1000H ==>立即寻址 mov ax,[0] ==>直接寻址
|
1 2 3 4 5
| 在修改DS段寄存器中的内容的时候是:利用了通用寄存器结合mov实现的
但是修改CS段的寄存器的时候是直接使用jmp命令修改的 jmp DS:IP --> 这个就是DS和IP一起修改 jmp ax -->这个就是DS不变,IP变成AX寄存器中的值
|
6.写几条指令,将al中的数据送入内存单元10000H?
1 2 3 4
| mov BX,1000H mov DS,BX mov [0],AL 这里是把数据放入某单元
|
3.3字的传送
1.因为8086CPU是16位结构,有16根数据线,所以一次性传入16位的数据,也就是一次性传送一个字
1 2 3 4 5
| 比如: mov bx,1000H mov ds,bx //前面俩个是为了确定段地址 mov ax,[0] //1000:0处的字型数据送入ax中 mov [],cx //cx中16位数据送到1000:0处
|
小结一下【实验】P14
首先CS:IP 和 DS[偏移地址]是密不可分的【好像有点奇怪,现在姑且这样理解】
CS:IP确定了指向命令的地址 【在我看来就是地址总线】
1
| 好像也不是很对,emmmm,反正就是说在CS:IP的地址中的内容可以放入命令,然后进行执行
|
1 2
| 首先在地址中写入要执行命令的汇编指令, 然后用CS:IP指向这些地址,在运行的时候,这些地址里面的内容就会指向命令
|
通过运行CS:IP地址中的内容就可以去改变DS中的内容,然后读取DS地址中的内容放入或者运算入AX BX CX DX通用寄存器中
DS确定了读取数据的地址 【在我看来就是数据总线】
1 2
| 反正就是说运行的命令,都是在CS:IP地址中的内容,如果CS:IP中的某些命令需要用到DS,则就会去调用DS[]地址中的值。 差不多就是这个意思
|
当通用寄存器用的是X的时候【16位的时候】就需要把该地址和该地址+1的内容当作字来看
但是如果使用的是L/H的时候,就需要当作字节来看
1 2 3 4 5 6
| 因为,这个传入的内容不能大于放入的寄存器 mov ax,[0] //这样就是当作字来看,因为ax寄存器中可以存16位【一个字】 mov al,[0] //这样就要当作字节来看,因为al寄存器中只能放入8位【一个字节】
寄存器有多大,就放多少,在选中的该地址往后加就好了 以寄存器为标准,内存按照寄存器的大小对寄存器放入值
|
3.4mov,add, sub指令
1.mov
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 已知: mov 寄存器,数据 mov ax,100 mov 寄存器,寄存器 mov ax,bx mov 寄存器,内存单元 //内存单元这里就是DS[address]指向的位置 mov ax,[0] mov 内存单元,寄存器 mov [0],ax mov 段寄存器,寄存器 mov ds,ax 新的: mov 寄存器,段寄存器 mov ax,ds
|
2.add【➕】和sub【➖】
1 2 3 4 5 6 7 8 9 10 11 12 13
| add 寄存器,数据 add ax,3 add 寄存器,寄存器 add ax,bx add 寄存器,内存单元 add ax,[0] add 内存单元,寄存器 add [0],ax 注意:!!! add不能对段寄存器进行操作 比如:add ds,ax ==> 这样是错误的!!【段寄存器不是这么用的,所以没有设置这个功能】 ##然后sub和add完全一样
|
3.5数据段
1.前面讲过,对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(这个段,可以是代码段,也可以是数据段)
2.我们可以将一组长度为N(N<=64K),地址连续,起始地址是16的倍数的内存单元当作专门存储数据的内存空间,这个空间就是定义的数据段
3.比如我们用123BOH~123B9H这段空间来存放数据:
1 2
| 段地址:123BH ==>这里会经过加工厂*16=123BOH 长度是10字节【123B0~123B9有10个存储单元【一个存储单元就是1B】】
|
4.如何访问你数据段的中数据?
1
| 题目1:我们将123BOH~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据
|
1 2 3 4 5 6 7
| 解: mov ax,123BH mov ds,ax mov al,0 //因为数题目是前三个单元中的数据,每一个存储单元的大小是1B也就是8位,所以这里需要使用低位的al 而不是 使用ax,如果使用ax的话,就会把数据放入字中【16位】这样所求得的就不是前三个单元中的数据的累加,而是前三个字中数据的累加 add al,[0] //计算机都是从0开始哦 add al,[1] add al,[2]
|
1
| 题目2:我们将123BOH~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个字型中的数据
|
1 2 3 4 5
| 这里就是把al改成ax就好啦,当然偏移地址也要累加2【懂的都懂,不懂的就别学了】 add ax,[0] //为了放入ax中其实这里是[0]放入低位[1]放入高 add ax,[2] add ax,[4] 注意:一个字型数据占2个单元,所以偏移地址是0,2,4
|
3.1~3.5小结一下
1**.字在内存中存储的时候,要用俩个地址连续的内存单元来存放,字的低位字节【al】存放在低地址单元【0】中,高位字节【ah】存放在高地址单元【1】中
2.用mov指令要访问内存单元,可以在mov指令中只给处单元的偏移地址,此时,段地址默认在DS寄存器中
3.[address]表示一个偏移地址位address的内存单元
4.在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器,低地址单元和低8位寄存器相对应
5.mov add sub 是具有两个操作对象的指令。jmp是具有一个操作对象的指令
6.可以根据自己的推测,在debug中实验指令的新格式
1
| 根据弹幕: mov ax,1000:[0] ==> 这样不error的【我也不确定】
|
3.6栈
1.我们研究栈的角度:
1 2 3
| 首先:栈是内存空间的一部分
栈是一种具有特殊的访问方式的存储空间,它的特殊性就在于,最后进入这个空间的数据,最先出去【迟到早退】
|
2.栈有俩个基本的操作:入栈和出栈
1 2
| 入栈:将一个新的元素放到栈顶 出栈:从栈顶取出一个元素
|
3.栈顶的元素总是最后入栈,需要出栈的时候,又是最先被从栈中取出
4.栈的操作规则:LIFO
3.7CPU提供的栈机制
1.如今的CPU中都有栈的设计
2.8086CPU提供相关的指令来以栈的方式访问内存空间
3.这意味着,我们在基于8086CPU编程的时候,可以将一段内存当作栈来使用
4.8086CPU提供入栈和出栈指令:
1 2 3 4 5
| 最基本: PUSH:(入栈)【入就入到最底部】 POP(出栈)【出就是从顶部出】 push ax:将寄存器ax中的数据送入栈中 pop ax:从栈顶取出数据放入ax
|
5.8086CPU的入栈和出栈操作都是以字【16位】为单位进行【这个是因为8086CPU内部是以16位为单位运算的,现在都是32/64位咯】
6.CPU如何知道一段内存空间被当作栈使用?
1 2 3 4
| 首先我们知道 段寄存器CI:IP所指向的就是指令 段寄存器DS所指向的就是数据 所以,以此类推,肯定有一个栈寄存器
|
1 2 3 4
| 在8086CPU中,有俩个寄存器: 段寄存器SS :存放栈顶的段地址 寄存器SP : 存放栈顶的偏移地址 任意时刻,SS:SP指向栈顶元素
|
1
| 这里我们要注意的是:栈顶不是某个栈的顶部,而是某栈存放了数据,然后栈顶就是最后放入数据的位置
|
7.在执行PUSH和POP的时候,如何知道那个单元是栈顶单元
1
| 在指向PUSH和POP的时候,栈顶的位置是动态变化的,那么它是如何知道的呢?
|
push ax
1 2 3 4
| 这个步骤在计算机中其实是两步 1.SP=SP-2 //这里是-2而不是+2的原因是:在push的时候,数据会首先放入栈的最底部【也就是偏移地址最大的时候,高地址】 //这个就类似于IP=IP+所读指令长度 2.将ax中的内容送入SS:SP指向的内存单元出【字】,SS:SP此时指向新栈顶
|
pop ax
1 2 3 4 5 6
| 就是push的逆运算 1.将SS:SP指向的字中的内容放入ax后 2.SP=SP+2 在使用POP之后,取出来原来地址上的内容还是存在的,【可以理解成,SP变了,只是把内容copy出去了】 内存中的数据是不会被删除,只能一次一次的被覆盖被覆盖
|
8.问题:如果我们将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时SS=1000H,那SP=?
1 2 3 4 5
| 当然是不是000FH啦 **是最高单元的下一个地址 SP=0010 因为只有指向最高单元的下一个地址 在push 的时候,整出俩个地址才会对应上哦
|
换个角度看看:
1 2 3
| 1.任何时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就步存在栈顶元素 2.所以在栈为空的时候,SS:SP就只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地址+2【这里是字单元,如过是字节的话,就是+1】 3.栈最底部子单元地址是1000:000E,所以栈为空的时候,SP=0010H
|
3.8栈顶超界的问题
1.SS和SP只记录了栈顶的地址,依靠SS和SP可以保证在入栈和出栈的时候找到栈顶。
2.但是,如何能够保证在入栈和出栈的时候,栈顶不会超出栈空间
3.当栈满的时候在使用PUSH指令入栈,栈空的时候在使用POP指令出栈,都会发生栈顶出界的问题
1 2 3
| 哟~出现了溢出 向上的溢出--push 向下的溢出--pop
|
4.栈顶越界是危险的
1 2 3
| 因为我们既然将一段空间安排为栈,那么在占空间之外的空间里面很可能存放了具有其他用途的数据,代码。这些数据,代码可能是我们自己程序中的,也可能是贝格程序中的(毕竟一个计算机系统并不是只有我们自己程序在运行)
相当于在溢出的时候,可以用PUSH或POP把栈之外的内容给搞出来,然后就会出现一些泄露或者,覆盖掉其他的东西。【类似吧】
|
5.但是由于我们在入栈和出栈时候的不小心(故意)加上一些其他工具【乱说的,可能可以把这些内容收集起来,就可以无限可能】
6.8086CPU不保证对栈的操作不会越界
1 2 3
| 8086CPU只知道栈顶在何处 因为栈的大小是我们自己定义的,CPU不知道我们定义了多大,不知上线和下线,所以CPU就会很SB的以为还在栈里面【但是这个时候已经跑出去了】 CPU只知道要执行的栈顶在何处,但是不知道要栈空间的大小是多少
|
7.总的一句话,我要想办法使他溢出【哈哈】
3.9PUSH和POP指令
1.首先PUSH和POP是可以在寄存器和内存之间传送数据的
1
| 1.栈空间是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间
|
2.看看POP和PUSH指令的格式把
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 通用寄存器: 1.PUSH 寄存器:将一个寄存器中的数据入栈 2.POP 寄存器:出栈,用一个寄存器接受出栈的数据 段寄存器: 1.PUSH 段寄存器:将一个段寄存器中的数据入栈 2.POP 段寄存器:出栈,用一个段寄存器接受出栈的数据 【也就是说,PUSH和POP可以对段寄存器使用】 【补充,ADD和SUB不能对段寄存器使用】 内存单元: 1.PUSH 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位) 2.POP内存单元:出栈,用一个内存字单元接收出栈的数据 PUSH [0] //字型的数据 POP [2]
指令执行的时候,CPU要知道内存单元的地址,可以在PUSH POP 指令中给处内存单元的偏移地址,段地址在指令执行的时候,CPU从DS取得
数据的段地址在ds中获得 用[]来偏移地址 代码的段地址在cs中获得 用ip来偏移地址 栈的段地址在ss中获得 用sp来偏移地址
|
问题1:
1 2
| 编程将10000H~1000FH这段空间当作栈,初始状态是空的,将AX,BX,DS中的数据入栈 入栈就是push
|
解答
1 2 3 4 5 6 7 8 9
| 1.mov ax,1000H
2.mov ss,ax //这里之前说过,段地址不能直接用数据,必须得用通用寄存器来过度。所以这里先把1000H存入ax,后放入ss
3.mov sp,0010H //这个sp不是段寄存器,段寄存器【CS,DS,SS,ES】.这里为什么是0010H呢?因为栈是到1000FH所以要指向这个的下一个
4.push ax 5.push bx 6.push ds
|
问题2:
1 2 3 4 5
| 1.将10000H~1000FH这段空间当作栈,初始状态是空的 2.设置AX=001AH,BX=001BH 3.将AX BX 中的数据入栈 4.然后将AX BX 清零 5.从栈中恢复AX BX 原来的内容
|
解答:
1 2 3 4
| 这个很简单啦,要注意的是 最后进入的最先出来的 清零是 sub ax,ax或者mov ax,0 或者xor ax,ax[这个是异或]
|
问题3
1 2 3
| 1.将10000H~1000FH这段空间当作栈,初始状态是空的 2.设置AX=002AH,BX=002BX 3.利用栈,交换AX和BX中的数据
|
解答
1 2 3 4
| push ax push bx pop ax //把原来bx的值放入ax pop bx //把原来ax的值放入bx
|
问题4
1 2 3 4 5 6
| 如果要在10000H处写入字型数据2266H这么搞? mov ax,1000H mov ds,ax // 这步就是要让ds段寄存器指向1000H mov ax,2266H mov [0],ax //把2266H的值放入10000H 相当于100000H是66;100001H是22
|
1 2 3 4 5 6 7 8
| 现在进阶 1.________ 2.________ 3.________ mov ax,2266H push ax
完成代码,实现上诉的结果
|
解:
1 2 3 4 5 6 7 8 9 10 11
| 分析一波,我们看哈,最后俩个是: mov ax,2266H push ax 读一下:这里是把2266H放入ax寄存器中,然后通过栈的PUSH把ax寄存器中的内容push进10000H处 所以:我们需要在这个之前需要把栈的指针指向10000H处 所以: 1.mov ax,1000H 2.mov ss,ax 3.mov sp,2 //这里要想明白:sp的步骤是什么先-2后入栈,这里后面push ax,指针是在10002H的位置,然后push ax 也就是说在10001H的位置会存入22,10000H的位置会存入66
这个指针是sp-2是往上【低】走
|
总的分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 我想问一下:我这样的分析是不是对的 mov ax,1000H mov ds,ax mov ax,2266H mov [0],ax
如果cpu运行了这段命令,然后在地址10000H处和10001H处10002H处的内容是什么? 【我本身想实验的,但是在win11下没有debuf,然后虚拟机我没有下xp,由于急于想知道答案所以没有下】【附加上我不知道还有什么调试器可以用-目前只知道debug】 我感觉像是:,然后在10000H中存入的是66 在10001H中是22 【因为电子书--王爽的汇编在3.1~3.5的小结里面有字在内存中存储的时候,需要用2个连续的内存单元来存放,字的低位字节存放在低地址中[这里我认为是al]高位字节放入高地址中[这个我认为是ah]】
然后在3.9中的问题3.10中有一个
mov ax,1000H mov ss,ax mov ap,2 mov ax,2266H push ax 这样的代码是等价于上面的代码【书上这么说的】
分析这个命令:栈的段地址是1000H,然后偏移地址是0002,所以组合在一起这个地址是10002H 然后在push,ax的时候【这个ax里面是2266】 在高地址中存入的是22【10001H】低地址存入的是66【10000H】
|
问题得出的结论
1.PUSH和POP实质上是一种内存传送指令,可以在寄存器和内存之间传送数据,和mov指令不同的是,push和pop指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的
2.同时,push和pop指令还会改变sp的内容
3.10栈段
1.段–>就是一组内存单元
2.我们可以将长度为N(<=64K)的一组地址连续,起始地址为16倍数的内存单元,当作栈来用,从而定义了一个栈段
3.将内存当作栈段,只是我们在编程时候的一种安排,CPU并不会知道这个空间,CPU只知道SS:SP。
问题1
如果我们将10000H~1FFFFH这段当作栈段是,初始状态是空的,此时SS=1000H那么SP是多少呢?
解:
1 2 3
| 猜测的是:超出了啊不知道了FFFF已经是64KB了
这里emmm SP=0
|
换一个角度看看
1
| 任何时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素,所以SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址是栈最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:FFFE,所以栈空的时候,SP=0000H
|
栈的作用
总结
1.我们可以将一段内存定义为一个段,用一个段地址来指示段,用偏移地址访问段内的单元。【这完全是我们的安排】
2.我们可以用一个段存放数据,将它定义成“数据段”
1 2 3
| DS:[] CPU就将我们定义的数据段中的内容当作数据来访问 比如mov add sub
|
我们可以用一个段存放代码,将它定义成“代码段”
1 2
| CS:IP CPU就将执行我们定义的代码段中的指令
|
我们可以用一个段当作 栈,将它定义成“栈段”
1 2
| SS:SP CPU在需要进行栈操作的时候,执行PUSH POP 指令的时候,就将我们定义的栈段当作栈空间来使用
|