汇编语言笔记03内存访问

汇编语言笔记03内存访问

0x00 字的储存

​ cpu中,用16位寄存器来储存一个字,高8位存放高位字节,低8位存放低位字节,该种储存方式称为小端模式。 例如存放 4e20h和0012h

QQ截图20200528154635

内存单元是一个字节,而字单元是连续的两个内存单元,存放一个字的两个字节。

QQ截图20200528154934

QQ截图20200528155031

0x01 DS和[address]

​ cpu要读写一个内存单元时,必须先给出这个内存单元的地址,在8086cpu中由段地址和偏移地址组成。ds寄存器,用来存放要访问数据的段地址,比如要读取10000h的内容

​ mov bx,1000h

​ mov ds,bx

​ mov al,[0]

​ 即将10000h中的内容放在al中 mov可以 1将数据直接送入寄存器 2 将寄存器送入寄存器 3 将内存送入寄存器 反过来也行,mov al,[0]时自动从ds中读取段地址

​ 8086不支持直接将数据送入段寄存器cs ds es ss 都不行 所以才先送到ax或者bx 再由ax或者bx送到段寄存器

​ mov bx,1000h

​ mov ds,bx

​ mov [0],al

0x02 字的传送

​ mov bx,1000h

​ mov ds,bx

​ mov ax,[0]

​ mov [0],cx

​ 由于是ax和cx是16位寄存器,因此从内存中读写的时候也是取出或者写入2个字节一个字

0x03 mov add sub指令

​ mov有以下几种形式

QQ截图20200528163011

​ 反过来mov 寄存器,段寄存器 mov 内存单元,段寄存器 mov 段寄存器,内存单元都行

QQ截图20200528163215

​ 但是add和sub 不能对段寄存器使用

0x04 数据段

​ 在编程时,可以根据需要将一组连续的内存单元定义为一个段。我们可以将一组长度为n(n<64kb) 地址连续,起始地址为16的倍数(主要是为了让偏移地址能从0开始)的内存单元当做专门储存数据的内存空间,从而定义了一个数据段。

​ 比如用123b0h-123b9h这段空间存放数据,可以认为这段空间为一个数据段,段地址为123b,

段的长度为10个字节,那么访问这个数据段的代码如下

​ mov ax,123bh

​ mov ds,ax

​ mov al,0

​ add,al,[0]

​ add,al,[1]

​ add,al,[2]

​ 上述代码累加数据段的前三个字节,下面代码累加数据段的前三个字型

​ mov ax,123bh

​ mov ds,ax

​ mov al,0

​ add,al,[0]

​ add,al,[2]

​ add,al,[4]

QQ截图20200528164938

​ 该题解题思路 ds为1那么段的起始地址为10h(要*16),所以后面就很简单了,主要注意的是最高位进位会被舍弃,例如 add al,bl时 al为0e6h bl为26h 相加为10ch,超过了8位 1被丢弃了 也不在ah上,所以ax为0ch

0x05 栈

​ 在数据结构里,栈是一种先入后出的线性数据结构,而在内存里,栈是一种有特殊访问方式的储存空间,同样是最后进这个空间的数据,最先出去

QQ截图20200528165504

QQ截图20200528165511

​ 这种规则称为LIFO(LAST IN FIRST OUT) 队列是FIFO

​ 每一种cpu中都有栈的设计,8086也有,提供相关的指令来以栈的方式访问内存空间,push和pop push 寄存器 pop寄存器 (push和pop都是一次传输一个,而不是一个字节)

QQ截图20200528165845

​ 上面的图描述了以下代码的工作流程

​ mov ax,0123h

​ push ax

​ mov bx,2266h

​ push bx

​ mov cx,1122h

​ push cx

​ pop ax

​ pop bx

​ pop cx

值得注意的是,往栈中存放数据,是由高地址向低地址进行的,每次一个字,然而字的储存方式仍然是小端模式,即低位字节对应低地址

问题:cpu如何知道某一段内存空间被当做栈地址来使用? push pop 在执行的时候如何知道哪个是栈顶单元?

​ 这不禁让我们想到讨论过的另外一个问题,就是cpu如何知道当前要执行的指令所在位置?,用cs ip来存放段地址和偏移地址,那栈也是一样的 由两个寄存器 栈的段寄存器 ss 段的栈顶寄存器sp 任意时刻ss:sp执行栈顶元素

​ push ax 的执行实际是

​ 1sp=sp-2,即ss:sp指向当前栈顶前面的单元,以其为新的栈顶

​ 2将ax送入栈顶单元处,ss:sp指向新栈顶

​ pop ax反过来

​ 1将栈顶单元处的字送入ax

​ 2sp=sp+2,即ss:sp指向原来栈顶后面的单元,以其为新的栈顶

QQ截图20200528170903

​ 栈空时没有元素,也就不存在栈顶元素,所以ss:sp指向最底部单元下面的单元,该单元的偏移地址为最底部的字单元的偏移地址+2,栈最底部字单元的地址为1000:000e(不是1000:000f,以为一个字占2字节,最后一个字的起始地址是1000:000e),所以栈空时,sp=0010h

0x06 栈顶越界问题

​ 8086cpu并没有记录栈的大小的寄存器,从而也没有检测push pop 时会不会越界的机制,所以我们编程时,要自己操心栈顶是否越界,要根据可能用到的最大栈空间,来安排栈的大小。

0x07 push pop指令

​ push和pop是可以在寄存器和内存(栈也是内存空间,只不过访问的方式比较特殊)之间传送数据

​ push 寄存器/段寄存器 pop 寄存器/段寄存器

​ 实际上也可以在内存和内存之间传送数据

​ push 内存单元 pop 内存单元

​ 如 mov ax,1000h

​ mov ds,ax ;内存单元的段地址放在ds里

​ push [0]

​ pop [2]

QQ截图20200528172508

QQ截图20200528172811

​ 即通过push来将数据存入内存10000h处,那么该内存字单元必须设置成栈,那么ss为1000h栈顶指针-2要为0,所以sp为 02h

0x08 栈段

​ 在编程时,可以根据需要,将一组连续的内存单元定义为一个段,我们将长度为n(n<64kb)的一组地址连续、其实地址为16的倍数的内存单元,当做栈空间使用,从而定义了一个栈段。

​ 比如我们将10010h-1001fh这个段长度为16字节的内存空间当做栈来使用,以栈的方式访问,那么就可以称为一个栈段,栈地址为1001h,大小为16字节

​ 这仅仅是我们自己的安排,要执行push和pop时自动访问我们定义的栈段当做栈空间,需要哦们将ss:sp指向我们定义的栈段QQ截图20200528173502

​ 当只有一个元素时,栈顶指针指向0fffeh(因为栈的储存单元是字,因此一个元素的起始地址是0fffeh),所以出栈后栈顶指针sp+2=0000h(注意进位被丢掉了,只有进位标志被记录了)

QQ截图20200528173745

0x08 检测题

QQ截图20200528174012

要逆序复制,由已给出的部分可以看出是用栈段,而且是将1000h:0看做数据段 2000h:0看做栈段,那么只需要设置栈顶指针就行了,因为ss是段寄存器,不能直接用数据赋值,所以需要先mov到ax里,再由ax mov到ss

QQ截图20200528174749

​ 第二问发现是pop到2000h:0里,也就是2000h:0是数据段 ,1000h:0是栈段,那么也就是认为元素已经push进去了需要一个一个pop出来,所以ss设置为1000h,栈顶指针应该在最前面的元素那里,所以为0h,而不是在最底下(因为初始为满)