[从零学习汇编语言] - 寄存器与栈空间

前言

该系列博文基于王爽老师 <汇编语言 第四版> 一书,需要的同学链接自取:

链接:https://pan.baidu.com/s/1NAgD1Z15LtK1BuH92xmICA
提取码:xlzb

另外书中提到的DosBox软件不想去官网下载的小伙伴也可以自取:

链接:https://pan.baidu.com/s/1O6PnLb_hN-WUS2avicNpcw
提取码:xlzb

最后如果还没有计算机基础的同学,建议先补充下计算机相关的基础知识:

笔记目录总览

一、栈结构简介

1. 箱子实验

小伙伴们现在想想一个简单的场景:此时分别有语文,数学,英语三本书需要装入箱子中,那么我们装书的过程是什么样的呢?简单模拟下:
在这里插入图片描述
图中展示的就是我们将书籍放入箱子的过程,此时我们先将书籍的放入顺序进行排序:
在这里插入图片描述
我们可以很直观的发现我们最先放入的英语书被放置到了箱子的底部,反而最后放入的语文书却被放到了箱子的顶部。此时我们再从箱子中将书取出看看:
在这里插入图片描述
我们看到放到箱子顶部的语文书先被取出,而放在箱子底部的英语书则是最后被取出。此时我们可以简单的总结规律:我们最先放入箱子的书却被最后取出(先入后出)。

其实在计算机中也有一个类似箱子的容器:

2. 什么是栈?

栈(Stack)是一种具有特殊的访问方式的存储空间。它具有一种有趣的特性:最先进入这个空间的元素只能够最后取出。

由于这里有很多零基础的小伙伴,因此博主并没有再数据结构的层面进行详细定义,感兴趣的小伙伴可以看这段定义:
栈是一种运算受限的线性表,其限定仅在表尾进行插入和删除操作的线性表,表尾也被叫做栈顶。简单概括就是我们对于元素的操作只能够在栈顶进行,也造就了其先进后出的结构特性。

3. 数据与栈

为了更好的理解栈的特性,我们将上面箱子实验中的箱子换成一段连续的存储单元,而书本则是换成将要存放进去的数据:
在这里插入图片描述
这个过程中我们可以看出的是, 这种内存空间其实本质上有两种操作:将数据放入栈中和在栈中取出数据,相对应的我们管这两种操作叫做入栈出栈,而栈顶的元素总是最后入栈,需要出栈时又最先被取出的现象我们将其称为LIFO(Last In First Out,后进先出)

入栈 : 将数据压入栈中
出栈 : 从栈中弹出数据
LIFO: 首先进入栈中的数据最后才会弹出

3. 栈顶

其实栈顶的概念很简单,栈需要有一个标记来一直指示存放在栈结构中需要最先出栈的元素,如果不明白的小伙伴可以看下图:

在这里插入图片描述
准确来说,栈顶就是一个指针,它永远指向栈中需要最先出栈的元素。

二、CPU提供的栈机制

现如今的CPU中其实都有栈结构的设计,我们熟悉的8086CPU当然也不例外。8086CPU提供了一些指令让我们可以以栈的方式来访问内存空间,这意味着我们可以在编程时,通过指令将一段连续的存储单元当作栈来使用。这里我们提到的指令有两个,分别是刚刚我们概括的栈的两种行为:入栈(PUSH)出栈(POP)

1. 入栈 PUSH

入栈的最基本的指令就是push + 操作数,这里的操作数既可以是存储器或者寄存器的名称,也可以是立即数(通过段地址:偏移地址的形式)。先让我们做一个简单的演示:
在这里插入图片描述

图中命令:

  1. R指令查看当前寄存器状态
  2. A指令开启指令输入
  3. mov ax,1122 将ax寄存器赋值1122
  4. push ax 将AX寄存器的值压入栈中
  5. T指令 执行命令

这里会有小伙伴有个问题,虽然观察到PUSH AX命令被成功执行了,但是问题是并没有直观的发现入栈的数据存放到了哪里,内存发生了什么变化,CPU又是如何感知到的栈顶?

2. SS:SP

还记得我们之前讲过的CPU如何感知到接下来需要执行哪条指令吗?

不清楚或者有些遗忘的同学可以查看下述博客中关于CS:IP相关内容
[从零学习汇编语言] -寄存器详解

CPU是通过CS,IP两个寄存器存放的段地址和偏移地址来获取物理地址到存储单元中查找指令数据。聪明的同学已经猜到CPU针对栈顶应该也有对应的寄存器记录。没错,8086寄存器中有两个寄存器段地址寄存器SS以及偏移地址寄存器SP,栈顶的段地址存放在SS中,偏移地址存放在SP中,PUSH指令与性的时候从SS:SP中获取到栈顶的地址。知道这个概念后,我们先指定栈顶地址,然后完成依次PUSH观察内存的变化:
在这里插入图片描述

为了基础不是很牢的小伙伴,我们还是要逐步分析指令:

  1. D 2000:0000: 我们查看从2000:0000开始的连续的内存空间中存储的数据
  2. R SS: 修改SS寄存器中段地址数据
  3. R SP: 修改SP寄存器中偏移地址数据
  4. A 指令: 将想要运行的指令输入
  5. MOV AX,1234: 将AX寄存器的值更改为1234
  6. PUSH AX: 将AX寄存器的值压入栈中
  7. T指令: 运行输入好的命令
  8. D 2000:0000 查看2000:0000处连续的内存地址数据变化

这里我们可以观察到,当我们的寄存器指针指向2000:0020地址处时,我们push了AX寄存器中的值(1234),T指令执行后,SP偏移地址寄存器的值发生了变化,而对应的2000:0019和2000:0018两处存储单元的值也发生了变化。
在这里插入图片描述
而我们通过这个现象也可以完整的描述PUSH指令的执行过程:

  1. SS:SP指向当前栈顶前面的单元,以当前单元前面的单元作为新的栈顶。偏移地址的计算规则:SP=SP-2 。
  2. 将指定数据送入SS:SP指向的内存单元处,SS:SP此时指向新的栈顶。

这里我们引用<汇编指令第四版>的相关描述图
在这里插入图片描述

3. 出栈 POP

其实POP和PUSH基本相同,POP指令的格式是POP 寄存器其指令含义就是用一个寄存器来保存出栈的数据,因此POP指令后面不能够加立即数。让我们来演示下:
在这里插入图片描述

按照惯例,翻译下:

  1. R指令:查看当前寄存器状态
  2. A指令:录入指令
    mov ax,2134 : 将ax寄存器赋值2134
    push ax : 将ax寄存器的值压入栈中
    pop bx : 将栈顶的值赋予bx
  3. T指令: 执行命令

三、栈与内存

1. 栈顶与栈的大小

我们来思考一个小问题
如果我们将20000 - 2000F 这段空间当作一个栈
栈初始的时候是空的
此时 SS= 2000
那么我们的SP应该等于多少?

关于这个问题我们要分步分析:
第一步: 20000 到2000f 一共需要多少个存储单元?
这个不难计算,我们需要0到F(十六进制数,转换为十进制为15)共16个存储单元,即16个字节空间。
第二步:栈底是哪里?
如果说栈底这个概念还有同学不清楚,我们换个描述就是此时我们要拆入一个2字节数据,CPU会将数据放在哪里?按照我们刚刚入栈的规律总结,当我们执行PUSH操作的时候,会将数据首先放入高位存储单元。

高位存储单元: 即物理地址数字更大的单元

由此推断,第一次执行PUSH操作的时候,会将数据存放到2000F和2000E处。
第三步:栈顶指针在哪里?
之前提到过,当我们执行PUSH操作时,SP寄存器会完成SP=SP-2的操作,因此当我们插入一个数据时,栈顶指针应该指向2000E处,也就是SS = 2000 , SP = 000E 。而通过SP=SP-2反推,当我们没有存放数据的时候,SP就应该等于000E + 2 = 0010 ,所以此时SP=0010(十六进制,也可以表示为0010H)

2. 栈的越界

继续思考:
如果我们想要将 2000:0010 到 2000:0020作为栈空间
当栈存满时,我们还能存入元素吗?

很不幸,由于CPU只记录了此时栈顶的位置,它无法感知我们的操作是否越界,因此当我们提交了超过内存范围的数据时,它依旧是允许的,但是此时我们侵犯了其他区域的数据,相应的肯定会为系统带来安全风险。所以我们在编程时需要特别注意栈顶越界的问题,不仅仅是PUSH操作,也包含POP操作在内!

结语

今天我们介绍了栈空间的相关概念和特性。并且配合CPU介绍了两个新的汇编命令:PUSHPOP,小伙伴们学习后建议跟着书本再敲一敲对应的实验联系哦!
好了,今天的内容就到此结束了,有疑问的小伙伴欢迎评论区留言或者私信博主,博主会在第一时间为你解答。
码字不易,感到有收获的小伙伴记得要关注博主一键三连,不要当白嫖怪哦~
如果大家有什么意见和建议请评论区留言或私聊博主,博主会第一时间反馈的哦。

  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
汇编语言中,通常使用两种方式来清空空间,一种是使用循环结构,一种是使用REP STOS指令。以下是两种方式的代码示例: 1. 使用循环结构 ``` clear_stack: mov ecx, [esp+4] ; ecx存放要清空的字节数 xor eax, eax ; eax清,用于填充空间 mov edi, esp ; edi指向顶 rep stosb ; 重复执行将al填充到[edi]的操作,直到ecx为0 ret ``` 在上面的代码中,首先将要清空的字节数存放在ecx寄存器中,然后将eax寄存器,用于填充空间。接着,将edi寄存器指向顶,然后使用REP STOSB指令将al寄存器的值填充到[edi]中,直到ecx为0,即清空了空间。最后使用ret指令返回。 2. 使用REP STOS指令 ``` clear_stack: mov ecx, [esp+4] ; ecx存放要清空的字节数 xor eax, eax ; eax清,用于填充空间 mov edi, esp ; edi指向顶 shr ecx, 2 ; 将ecx右移2位,相当于除以4,将字节数转换为DWORD数 rep stosd ; 重复执行将eax填充到[edi]的操作,直到ecx为0 mov ecx, [esp+4] ; ecx存放要清空的字节数 and ecx, 3 ; 将ecx与3进行按位与操作,相当于取ecx除以4的余数 rep stosb ; 重复执行将al填充到[edi]的操作,直到ecx为0 ret ``` 在上面的代码中,首先将要清空的字节数存放在ecx寄存器中,然后将eax寄存器,用于填充空间。接着,将edi寄存器指向顶,将ecx右移2位,相当于将字节数转换为DWORD数,然后使用REP STOSD指令将eax填充到[edi]中,直到ecx为0。接着,重新将要清空的字节数存放在ecx寄存器中,将ecx与3进行按位与操作,相当于取ecx除以4的余数。最后,使用REP STOSB指令将al填充到[edi]中,直到ecx为0,即清空了空间。最后使用ret指令返回。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓龙oba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值