4.8086汇编

140 阅读8分钟

寄存器

对程序员来说,CPU中最主要部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制 不同的CPU,寄存器的个数、结构是不相同的(8086是16位结构的CPU) 8086有14个寄存器,都是16位的寄存器,可以存放2个字节。

image.png

上面我们可以注意到其中AX,BX,CX,DX可以一分为两半,这样我们就得到了AH,AL类似这样的两块,如果我们遇到只存储八位的数据就可以用AL或者AH,上一代8086的寄存器都是8位的,为了保证兼容, AX、BX、CX、DX都可分为2个独立的8位寄存器来使用

一些特殊的点

  • AX寄存器常常用来做返回值
  • SP和BP使用在栈里面,BP一般用在栈底,而SP用在栈顶。
  • IP一般和CS一块用,IP表示cpu要执行的下一条指令的地址
  • FLAG是用在条件判断,if这些一句都是根据FLAG来判断的

通用寄存器

image.png

段寄存器

image.png

CS和IP

image.png

DS和[address]

image.png

字型数据的传递(2个字节)

image.png

大小端问题

image.png

段的总结

  • 我们可以用一个段存放数据,将它定义为“数据段”;我们可以用一个段存放代码,将它定义为“代码段”;我们可以用一个段当作栈,将它定义为“栈段”
  • 对于数据段,将它的段地址放在 DS 中,用 mov、add、sub 等访问内存单元的指令时,CPU 就将我们定义的数据段中的内容当作数据来访问;
  • 对于代码段,将它的段地址放在 CS 中,将段中第一条指令的偏移地址放在 IP 中,这样 CPU 就将执行我们定义的代码段中的指令;
  • 对于栈段,将它的段地址放在 SS中,将栈顶单元的偏移地址放在 SP 中,这样 CPU 在需要进行栈操作的时候,比如执行 push、pop 指令等,就将我们定义的栈段当作栈空间来用。

指令

mov 指令

image.png

MOV AX, 1234H    ; 将立即数1234H传送到AX寄存器
MOV BX, AX       ; 将AX的值传送到BX
MOV [BX], AX     ; 将AX的值传送到BX指向的内存地址
  • 指令大小写都可以
  • 8086 指令一般是将后面的操作数放到前面的寄存器或者内存中,和arm64的相反
  • 但是,mov指令不能用于设置CS、IP的值,8086没有提供这样的功能

add和sub指令

image.png

jump 指令

8086提供了另外的指令来修改CS、IP的值,这些指令统称为转移指令,最简单的是jmp指令

image.png

image.png

push 和 pop指令

push 和 pop 指令的格式可以是如下形式: 在8086中,push、pop操作的数据都是2个字节的

push 寄存器    ;将一个寄存器中的数据入栈
Pop 寄存器     ;出栈,用一个寄存器接收出栈的数据
push 段寄存器  ;将一个段寄存器中的数据入栈
pop 段寄存器   ;出栈,用一个段寄存器接收出栈的数据
push 内存单元  ;将一个内存字单元处的字入栈(注意:栈操作都是以字为单位)
pop 内存单元   ;出栈,用一个内存字单元接收出栈的数据
mov ax,1000H  
mov ds,ax     ;内存单元的段地址要放在 ds 中
push [0]      ;将 1000:0 处的字压入栈中
pop [2]       ;出栈,出栈的数据送入1000:2处

栈:是一种具有特殊的访问方式的存储空间(后进先出,Last In Out First,LIFO)

image.png

  1. 8086会将CS作为代码段的段地址,将CS:IP指向的指令作为下一条需要取出执行的指令
  2. 8086会将DS作为数据段的段地址,mov ax,[ax] 就是取出DS:address的内存数据放到ax寄存器中
  3. 8086会将SS作为栈段的段地址,任意时刻的,SS:SP指向栈顶元素 8086 提供了PUSH(入栈)和POP(出栈)指令来操作栈段的数据,比方说push ax是将ax的数据入栈,pop ax是将栈顶的数据送入ax

push ax

image.png

pop ax

image.png

栈越界(栈溢出)

image.png

image.png

上面描述了执行 push、pop 指令时,发生的栈顶超界问题。可以看到,当栈满的时候再使用 push 指令入栈,或栈空的时候再使用 pop 指令出栈,都将发生栈顶超界问题。 栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的(毕竟一个计算机系统中并不是只有我们自己的程序在运行)。但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。我们当然希望 CPU 可以帮我们解决这个问题,比如说在 CPU 中有记录栈顶上限和栈底的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU在执行push 指令的时候靠检测栈顶上限寄存器、在执行 pop 指令的时候靠检测栈底寄存器保证不会超界。

不过,对于 8086CPU,这只是我们的一个设想(我们当然可以这样设想,如果 CPU 是我们设计的话,这也就不仅仅是一个设想)。实际的情况是,8086CPU 中并没有这样的寄存器。 8086CPU 不保证我们对栈的操作不会超界。这也就是说,8086CPU 只知道栈顶在何处(由 SS:SP 指示),而不知道我们安排的栈空间有多大。这点就好像 CPU 只知道当前要执行的指令在何处(由 CS:IP 指示),而不知道要执行的指令有多少。从这两点上我们可以看出 8086CPU 的工作机理,它只考虑当前的情况:当前的栈顶在何处、当前要执行的指令是哪一条。 我们在编程的时候要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。

loop 指令

image.png

cx常用来做循环次数,而高级语言里面的for,while都是通过它去实现的

call 和ret指令

call指令:

  • 将下一条指令的偏移地址入栈后
  • 转到标号处执行指令

ret:将栈顶的值出栈,赋值给ip

ca11和ret联合使用的作用其实就是高级语言中的函数调用,要考虑以下几种情况

  • 有无参数
  • 有无返回值
  • 现场保护
  • 局部变量
  • 堆栈平衡

其他知识点

8086 特殊的寻址方式

image.png

image.png

image.png

上图说明了一个问题不同的段地址和不同的偏移地址可以组成同一个物理地址,其实就是二元一次方程有多个解一样

汇编中的单位 字和字节

image.png

指令和数据

指令和数据本质都是0101这种数据,看你怎么读,你觉他他是数据,他就是数据,你觉得他是指令就是指令。但是会存在一个问题如果原来是数据,你按照指令去执行,很可能会出错。如果是指令,你按照数据去读取,同样可能会出问题。
image.png

伪指令

image.png

中断

image.png

image.png

image.png

段前缀

段前缀 “mov ax,[bx]”中bx的值是偏移地址,段地址默认在ds中。我们也可以明确地标明段地址,比如

mov ax, ds:[bx]
mov ax, cs:[bx]
mov ax, ss:[bx]
mov ax, es:[bx]
  • 上面的“ds:”“cs:“ss.“es:”称为段前缀

不同的寻址方式

image.png

image.png

指令要处理的数据长度

8086指令能处理2种尺寸的数据:byte、word 思考:“mov [0],20H”指令是否正确?

mov byte ptr [0],20H ;将20H放入0位置内存的字节单元,占用1个字节
mov word ptr [0],20H ;将20H放入0位置内存的字单元,占用2个字节
很多指令都可以通过“byte ptr”或者“word ptr”来指明所需要操作内存的数据长度
> inc byte ptr [0]
> add word ptr [0],2

有些指令有默认的操作数据长度,比如push[0]、pop [0]的操作数据长度只能是2个字节