小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)

时间:2022-03-24 06:36:37
     这章涉及了一个重点概念——栈(还好有栈,让系统有了那么多的漏洞)。
 
1、内存中字的存储
     1个字=2个字节,如从0开始存放20000(4E20H),20、4E分别表示1个字节,内存中字的存储如下图所示:
      小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)

 

     这里,0号单元对应的是低地址单元,1号单元对应的是高地址单元。
     任何两个地址连续的内存单元(0、1、2......),N号单元和N+1号单元,可以看成两个内存单元。比如说上图中的0 内存单元(字节单元),存放的是字节型数据,就是20H(十进制的32);0地址 字单元,存放字型数据,就是4E20H。
     任何两个地址连续的内存单元,N号单元和N+1号单元,也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元。比如上图中1 字单元中,所代表的字是124EH,高位字节单元是 2内存单元——12H,低位字节单元是 1内存单元——4EH。
 
2、DS和[address]
     CPU读取内存单元时,必须先读取到这个内存单元的地址。8086CPU中,内存地址是由段地址和偏移地址组成(第二章中有介绍),并且有一个DS寄存器,用来存放要访问的数据的段地址。地址很重要,就像生活当中,要前往一个地方,我们必须要知道这个地方的地址,才能够决定我们如何去这个地方。
     8086CPU中,通常有一个DS寄存器,用来存放要访问的数据的段地址。
     例子:读取10000H单元的内容,可以用这样的一段代码来实现:
          mov bx,1000H
          mov ds,bx
          mov al,[0]
     解释:上面的代码是将10000H(1000:0)中的数据读取到al中。这里为什么不直接mov 10000H到ds中呢?这是因为8086CPU的设         计问题,其实没有什么原因,工程师就是这样设计的,不必深究(感觉废话连篇,哈哈)。
               mov al,[0] 这一段代码又是什么意思呢?首先我们要知道,mov指令,就目前而言我们所知道的功能有两种:第一种就是直接将数据放到寄存器中(mov ax,2);第二种,将一个寄存器中的内容传递到另一个寄存器当中(mov bx,ax)。除去这两种功能,mov指令还能够将一个内存单元中的内容送入一个寄存器。那么mov al,[0] 这句代码就好理解了,就是将内存单元中,偏移地址为0的内存单元中的内容放入到ax通用寄存器中的低八位al(8位)中。一个段地址对应一个偏移地址,上面的代码中,段地址为1000,对应的偏移地址为0。CPU是如何自己找到段地址对应的偏移地址的呢?CPU中有其自身的索引机制,感兴趣的同学可以自行去百度或者google一下。
               综上所述,上面的代码就可以理解为cpu从内存单元10000H(1000:0)中读取到数据,然后把值传递给ax寄存器中的al。
 
3、如何将1000H送入到DS(段寄存器)中?
     我们既然可以 "mov ax,1000" ,那么可以“mov ds,1000H”吗?
     答案是NO。8086CPU不支持直接将数据放入段寄存器中,ds是一个段寄存器。至于为什么不能,这在上面也说了是设计上的问题 。所以CPU从内存中读取内容,就是这样一个过程:数据--->通用寄存器--->段寄存器。
 
4、如何将数据从寄存器送入到内存中呢?
     比如要把al中的数据送入到内存单元10000H中,我们可以通过以下的代码完成:
          mov bx,1000H
          mov ds,bx
          mov [0],al
     这里就是一个逆向思维的体现。
 
5、字的传送
     8086CPU是16位结构,有16根数据线,所以一次性可以传送16位的数据,也就是一次性传送1个字(1个字=2个字节=16位)。
     如下代码:
          mov bx,1000H
          mov ds,bx
          mov ax,[0] ---->这里是将1000:0处的字型数据(1个字,16位)送入到ax(16位)当中
          mov [0],cx ---->这里是将cx(16位)中的数据放入到1000:0(16位)内存单元中。
 
6、已知mov指令的几种形式
     mov 寄存器,数据 ---->mov ax,2C3B
     mov 寄存器,寄存器 ---->mov bx,ax
     mov 寄存器,内存单元 ---->mov ax,[0]
     mov 内存单元,寄存器 ----->mov [0],bx
     mov 段寄存器,寄存器 ---->mov ds,ax
     
     既然内存单元和寄存器之间可以互相赋值,那么寄存器和段寄存器之间可以互相赋值吗?(也就是可以执行 mov 寄存器,段寄存器吗?)答案是YES。同学们可以自行debug一下。
 
小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
     这么多指令,没必要去背,多敲一敲就记住了。
     这里能不能像mov指令一样,对段寄存器进行操作呢?(比如 add 寄存器,段寄存器)答案是no。这个也可以自行debug一下。我电脑上这样做,是会报错的:
小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
 
7、数据段
     前面一章说过,对于8086CPU,我们可以根据需要,将一组内存单元定义为一个段(可以是代码段,也可以是数据段)
     我们可以将一组长度为N(N<=64)、地址连续、起始地址为16的倍数的内存单元用于存放数据的内存空间,这样我们就定义了一个数据段。比如我们使用内存单元123B0H--123B9H这样一个连续内存单元来存放数据,它的段地址就是:123BH,偏移地址是0H到9H之间,长度就是10个字节。
     那么我们应该如何来读取数据段呢?将一段内存当成数据段,这是在编程时候的一种安排,在具体操作的时候,我们可以使用ds来存储数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。
     这里我们定义123B0H到123BAH为数据段,现在要累加数据段中的前3个单元中的数据,应该这样写代码:
     mov ax,123BH ---->把段地址存入到ax中
     mov ds,ax ---->通过ax这个中介,将段地址123BH转交给ds段寄存器中
     mov al,0 ---->使用al来存放累加结果(为什么用al?因为问题中说的是单元,没有说前3个字,默认字节,所以用al(占8位))
     mov al,[0] ---->将数据段第一个单元(偏移地址为0)加到al中
     mov al,[1] ---->将数据段第二个单元(偏移地址为1)加到al中
     mov al,[2] ---->将数据段第三个单元(偏移地址为2)加到al中
 
     上面的问题假如修改为累加数据段中的前3个字型数据数据呢?代码应该如何修改?其实很简单,只需要将al修改为ax即可,字型数据占16位嘛,ax寄存器也就是16位的嘛,对应一下,替换一下就ok了。还有就是将偏移地址修改为[0]、[2]、[4],一个字占两个单元嘛。
 
8、小结
小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
 
9、栈的概念
     栈是具有特殊访问方式的存储空间,特殊性就在于其中的数据是后进先出(LIFO)。
     两个基本操作:入栈(将一个新元素放到栈顶)和出栈(从栈顶取出一个元素)。
     栈顶的元素总是最后入栈,需要出栈时,又最先被取出来。
     现今的CPU中,都有栈的设计,8086CPU提供相关指令来以栈的方式访问内存,这意味着,我们在基于8086CPU编程的时候,可以将一段内存当做栈来使用。
     入栈指令:push     出栈指令:pop
     push ax:就是将寄存器ax中的数据存入栈中     pop ax:就是将数据从栈中取出,放到ax寄存器中。8086CPU对栈的操作,数据都是以字为单位的(因为是16位)。
     示例代码:
     mov ax,0123H
     push ax
     mov bx,2266H
     push bx
     mov cx,1122H
     push cx
     pop ax
     pop bx
     pop cx
 
10、两个问题
     CPU怎么知道一段空间被当做栈来使用呢?执行push和pop指令时,如何知道哪个单元是栈顶呢?
     首先我们回顾一下CPU是如何指导当前要执行的指令的位置。寄存器CS和IP中存放着当前要执行的指令的段地址和偏移地址。
     在8086CPU中,有两个寄存器,一个是 段寄存器SS,用来存放栈顶的段地址;一个是 寄存器SP,存放栈顶的偏移地址。 任何时刻,SS:SP指向栈顶元素。
     在push一个元素时,首先 SP会减2,然后再将ax中的数据送入到SS:SP指向的内存单元处,SS:SP指向新的栈顶。如下图所示:
      小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
     此时,我们思考一个问题,当栈为空的时候,SS:SP又会指向哪里呢?SS:SP指向最高地址的下一个单元,在执行完第一个push命令后,SS:SP指向栈中的第一个元素。如下图所示:
      小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
 
     pop的操作则是相反的,SS:SP就是加2。如下图所示:
      小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
     注意,pop之后的数据并不是真的从栈中移除掉,pop之后的数据仍然存在于栈中,只不过SS:SP不再指向被pop的那个内存单元。栈中的数据只是在不断的被新的数据所覆盖。
 
11、栈顶越界的问题
     如何保证入栈、出栈不越界呢?(从另外一个角度来看,就是黑客经常使用的攻击手段——内存溢出攻击)
     8086CPU不保证我们的栈操作不越界,所以要我们自己来操心栈顶越界的问题。
 
12、push和pop指令可以在寄存器和内存之间传送数据
      小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
     push和pop分别可以进行寄存器的数据入栈和出栈,也可以进行段寄存器的数据入栈和出栈,亦可以进行内存单元之间的数据传递。
     目前我们十分清楚的是,push和pop指令和mov指令不同,CPU执行mov指令只需要一个步骤,执行push和pop需要两步:
     执行push时,首先sp加2,后向ss:sp处传送;执行pop时,先读取ss:sp处的数据,后改变sp(sp-2)。push和pop等栈的操作指令,修改的只是sp,也就是说对于16位的CPU而言,栈顶的变化范围在0~FFFFH之间。
 
13、栈段
     前面一章说过,对于8086CPU,我们可以根据需要,将一组内存单元定义为一个段(可以是代码段,也可以是数据段)
     我们可以将一组长度为N(N<=64)、地址连续、起始地址为16的倍数的内存单元当作栈来使用,这样我们就定义了一个栈段。将一段内存当作栈段来使用,仅仅是我们编程时的一种设置,CPU并不会由于这种设置,就在执行push、pop等栈操作指令时,就自动将我们定义的栈段当作栈空间来访问。CPU只认识SS:SP指向的栈顶,不会知道栈空间是从哪里到哪里。
     一个栈段最大可以设置为多少呢?答案是2^16,即64KB(对于16位的8086CPU而言)。
 
14、段的综述
小甲鱼零基础汇编语言学习笔记第三章之寄存器(内存访问)
     要如何定义一个段,这个段用来干什么,完全是编程人员的想法,CPU只认识地址。