Intel8086:从结构到指令

        本文从Intel8086产生的历史背景引入,从抽象的功能结构入手,对8086的主要运行过程进行简述,并解释了实现这一过程的寄存器与数据结构设计。最后以此为基础学习32位x86汇编的常用指令与用法。本文虽较长,但笔者希望通过本文,读者可以对8086的功能结构和x86汇编指令有一个较为全面的了解,我们文末见(*´∀`)

目录

(一)说在8086之前

(二)8086功能结构概述

1.总线接口单元(BIU)

2.执行单元(EU)

(三)寄存器

1.冯·诺依曼计算机

2.8086访问内存的过程:

(1)什么是段:

(2)什么是栈:

(3)我们为什么需要栈:

(4)栈帧:

(5)存储器分段:

(6)逻辑地址与物理地址:

(7)段基址与段内偏移量:

(8)物理地址的生成算法:

3.通用寄存器

(1)数据寄存器(AX,BX,CX,DX)

(2)地址寄存器(SP,BP,SI,DI)

4.段寄存器(DS,ES,SS,CS)

(1)8086段寄存器的使用约定

5.控制寄存器

(1)指令指针寄存器(IP)

(2)程序状态寄存器/标志寄存器(PSW/FLAG)

6.纪要

(四)x86汇编指令

0.x86指令编码

1.指令分类:

(1)根据地址码格式:

(2)根据操作类型:

(3)必须掌握的一些汇编指令:

2.寻址模式和内存分配

(1)寻址模式

(2)规定数据类型长度(DB,DW,DD,PTR)

3.常用指令

(1)数据传送指令

mov 指令

MOVZX/MOVSX指令

XCHG指令

LEA指令

push 指令

pop 指令

(2)算术和逻辑运算指令

add/sub 指令

inc/dec 指令

imul 指令

idiv 指令

and/or/xor 指令

not 指令

neg 指令

shl/shr 指令

(3)控制流指令

jmp 指令

LOOP指令

jcondition 指令(系列)

cmp 指令

test指令

call/ret 指令

(五)8086的die

(六)参考


(一)说在8086之前

在8086微处理器出现之前,英特尔公司曾生产了一系列的微处理器。

(@EE_Archeology 大佬的头像就是4004娘,可爱捏)

英特尔4004是英特尔推出的第一款商用微处理器,于1971年发布。它是一款4位处理器,主要用于计算器和其他嵌入式设备。

英特尔8008微处理器,于1972年发布。它是一款8位处理器,具备更高的性能和更大的寻址空间(2^14=16KB),性能两倍于4004。

英特尔8080微处理器,于1974年发布。同样是8位的处理器,8080在计算性能和功能上有了显著的提升,逐渐成为个人计算机时代的标志。

(笔者可爱的虎皮8086和NE555)

在1975年改进8080处理器(8085)后,英特尔决定研制一款高性能处理器8800,通过她高性能的设计和对高级语言的硬件支持,以期成为80年代的标杆。然而,支持过多的新特性使之难产(最终作为iAPX 432在1981年发布)。作为权宜之计,英特尔决定在空档期生产一款简单、兼容8080的16位处理器。这就是8086微处理器,作为备胎的她于1978年投产,却成为有史以来最有影响力的芯片之一,开创了属于复杂指令集(CISC)x86架构的时代。架构良好的可扩展性使得基于她的处理器:80286、80386以及后来的x86处理器都能沿用,逐渐发展为现代个人计算机的基石。这一架构沿用至今,应用广泛,您正在显示本文的设备CPU很可能就是她的子嗣(X86-64)!

(二)8086功能结构概述

8086CPU的结构从功能上来说分成两大部分:
        总线接口单元 BIU(Bus Interface Unit)
        执行单元 EU (Execution Unit)

1.总线接口单元(BIU)

作用:负责与存储器的接口,即8086CPU与存储器之间的信息传送,是由BIU进行的

功能:负责与 M、I/O 端口传送数据。即:

  • 总线接口部件要从内存取指令送到指令队列;
  • 在CPU执行指令时,要配合执行部件从指定的内存单元或者外设端口中取数据,将数据传送给执行部件;
  • 把执行部件的操作结果传送给指定的内存或I/O口。

组成:

  • 4个段地址寄存器(CS,DS,ES,SS)1个16位指令指针寄存器(IP)

  • 20位的地址加法器(段地址*16+偏移地址=物理地址)

    寻址1M字节(2^20=1048576=1M)

  • 6字节(8086)的指令队列

    会在执行指令的同时从内存中取下一条或几条指令,取来的指令放在指令队列中,使 BIU 具有预取指令的功能,是一种先进先出(FIFO)的数据结构。

  • 总线控制电路

    8086CPU与外界总线(内存)间联系的转接电路:

    有三组总线:地址总线(20位,传递物理地址),数据总线(16位),控制总线(8位,双向)。

指令执行顺序:

  • 顺序指令执行:指令队列存放紧接在执行指令后面的那一条指令。
  • 执行转移指令:BIU 清除指令队列中的内容,从新的地址取入指令,立即送往执行单元,然后再从新单元开始重新填满队列。

2.执行单元(EU)

作用:负责指令的执行

组成:

  • 4个通用寄存器(AX、BX、CX、DX)与4个专用寄存器(BP、SP、SI、DI)

  • 标志寄存器(PSW)

    9个标志位,其中6个条件标志位(OF,SF,ZF,AF,PF,CF)用于存放结果状态

  • 算术逻辑单元(ALU)

    16 位加法器,用于对寄存器和指令操作数进行算术或逻辑运算

  • EU 控制系统

    接受从总线接口单元的指令队列中取来的指令代码,对其译码和向 EU 内各有关部分发出时序命令信号协调执行指令规定的操作


由此可见,8086取指部分与执行部分是分开的

取指和执行可以重迭,从而减少等待取指所需时间,重迭操作技术一方面提高CPU的利用率,一方面降低了与之相配的存储器的存取速度的要求。

  • 在一条指令的执行过程中可以取出下一条(或多条)指令,指令在指令队列中排队;
  • 在一条指令执行完成后,就可以立即执行下一条指令,减少CPU为取指令而等待的时间,提高CPU的利用率和整个运行速度。

(三)寄存器

1.冯·诺依曼计算机

冯·诺依曼体系的运行流程是:用户输入的数据先放到内存当中,CPU读取数据时就直接从内存中读取,CPU处理完数据后又写回内存当中,然后内存再将数据输出到输出设备中,最后由输出设备进行输出显示。

在这一体系下,储存器默认是内存(Memory)这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备),外设(输入或输出设备)要输入/输出数据,也只能写入内存或者从内存中读取。总的来说,所有设备都只能直接和内存打交道。

2.8086访问内存的过程:

当 CPU 需要访问一个内存单元时,需要给出内存单元的地址,每一个内存单元在物理内存空间中都有一个唯一的地址,通过这个地址定位到内存单元,这个地址就是物理地址。CPU 通过地址总线将一个内存单元的物理地址送入存储器,而后 CPU 便可以通过这个物理地址来访问所指的内存单元了。那么这个物理地址在 CPU 中是如何形成的呢?

首先,我们知道 8086 CPU 的地址总线是 20 根,即 20 位,故寻址大小有 220 (1MB),但是 8086 CPU 的寄存器只有 16 位,也就是说在 8086 内部,一次处理,传输,暂存的地址都只能是 16 位, 8086 不能完整的保存下一个物理地址(物理地址为 20 位),如果仅以最简单的方式(直接用 16 位寄存器来保存物理地址)的话,那么,寻址能力只有 216 (64KB),这太小了。8086 在这里采取了一些措施从而使其寻址能力达到 1MB。

8086通过合成两个 16 位的地址来形成一个 20 位的物理地址,由此,8086 CPU 的寻址能力便可以达到 1MB。那么 8086 是如何合成地址的?当 CPU 在访问内存时,其会使用一个 16 位的基地址,然后再使用一个 16 位的偏移地址,通过将基地址和偏移地址传入地址加法器中合成即可构造出 20 位的物理地址。

合成的方式如下:基地址是通过一个 16 位的段地址来形成的,将一个段地址左移 4 位即形成了基地址,而偏移地址是16 位的,通过将基地址和偏移地址相加便形成了 20 位的物理地址。

(1)什么是段:

在内存的物理结构中并没有段这一概念,事实上,段的概念来自于 CPU 中寄存器的大小局限。我们在编程过程中将若干个地址连续的内存单元看做是一个段,通过基地址来定位这个段的起始地址,再通过偏移地址才能精确定位到段中的内存单元。由于段的起始地址(基地址)是一个段地址左移 4 位,所以段的起始地址肯定是 16 的倍数,而且由于在同一个段内,只能通过偏移地址(16位)来定位,所以一个段的长度是 216 即 64KB 的大小。在编程时,可以说一段内存被定义成为一个段,而这里,我们又可以引出数据段,代码段,栈段这三种类型的段 。

何为数据段?数据段是我们自定义一段内存(当然段起始地址肯定是 16 的倍数,且段长度 <= 64KB),然后在这个段里头存放我们所需要使用的数据,这就是数据段,段地址存放在 DS (Data Segment)寄存器中;

何为代码段?代码段是我们在编程时定义的一段内存,这段内存用来存放我们的代码(也就是指令),故称之为代码段,其段地址存放在 CS (Code Segment)寄存器中;

何为栈段?接触过数据结构的同学应该是清楚栈的,这里我们在内存中分配出一个段,将这个段当做栈来使用,栈段的段地址存放在 SS (Stack Segment)寄存器中 。对于栈的介绍,参见下文;

(2)什么是栈:

栈(stack)其实就是一个段,再说白一点,也就是一块连续的内存。故我们可以用使用段的方式使用栈,当然,栈还提供了特殊的访问方式(如果没有区别还需要栈干吗)即先进者后出类型的数据结构,可以通过 ”PUSH“ 指令将数据压入栈中,然后再通过 ”POP“ 指令将栈顶的元素取出来。

如图,我们每次只能对栈顶的元素进行操作,如入栈或出栈。向空栈中压入10,10就作为栈顶元素存在,而在压入新元素20后栈顶元素就变成了20。用来指示这一栈顶元素的指针就是后文会遇到的堆栈指针寄存器(SP)。

这样的一种运算受限的线性表就是栈。

需要注意的是,在x86架构中,栈是从高地址(上方)向低地址(下方)增长的,因此栈是从上往下压的。栈的最高地址用于存储栈的底部,而栈的最低地址用于存储栈的顶部。栈指针(SP)寄存器用于指示当前栈顶的位置,而栈的增长是由SP的减小而实现的。当数据被推送到栈上时,SP减小,栈向低地址方向增长。而当数据被从栈上弹出时,SP增加,栈向高地址方向增长。我们在实际运用时的方向和图中是相反的。

(3)我们为什么需要栈:

虽然从栈中读取数据是随便的,但栈的运算是受限的,每次只能对栈顶元素操作,看起来很不方便,所以我们为什么需要栈呢?

由于在实际程序运行时会有许多变量需要存储(如LOOP循环次数),它们的数量远超数据寄存器的4个(何况其中一些寄存器还要储存运算结果),在不使用栈的情况下,我们只能把这些变量存放在内存中,之后再从内存中取用。然而,取用这些变量需要地址,存放这些变量的地址(与变量地址的地址开始套娃)是一笔很大的开销。为了解决这一很麻烦的麻烦,我们创造一个一般(而且有时候更方便)的“麻烦”去解决它,那就是栈。

由于栈的存储是有顺序的,数据顺序的规则省去了对数据地址的储存,只需储存栈地址和栈顶地址,每次操作指针自增/自减即可(CPU会自动增减CS:IP)。而且,函数调用的规则也遵循后进者先出的顺序,这让栈的使用在实际上非常便利(见下动图演示计算1+2*4-6)。

cpu内部寄存器数量有限;栈开辟了一个内存空间用做栈段,通过push和pop操作便捷地对临时性数据进行暂存,连续的数据储存提高了IO与命中率;解放了程序猿需要记忆和寻找数据内存地址的痛苦过程。这就是为什么我们需要栈这样的数据结构!

(4)栈帧:

栈帧(stack frame)是在函数调用过程中动态创建的内存区域,存储局部变量、参数、返回地址、上个栈的EBP以支持函数执行和返回。在函数调用时生成,返回时销毁。嵌套函数调用会使栈上形成多个栈帧的链式结构

如图可见,EBP是栈帧基指针,指示当前栈帧的基地址;ESP是堆栈指针寄存器,始终指向栈顶。

下图显示了在函数调用过程中栈帧的创建和销毁过程。

(5)存储器分段:
  • 8086CPU寄存器为16位,内部ALU只能进行16位运算,但若对地址只能进行16位运算,寻址范围仅2^16=65536(64K)字节。故引入“分段”概念,以获得20位地址,2^20=1048576(1M)字节的寻址范围,可以配置1MB的存储器,地址编号为00000H~FFFFFH
  • 一个是存储器的一个逻辑单位,其长度可达64KB(但可以有64k=2^16个段地址),每个段都由连续的存储单元构成,是存储器中独立的可分别寻址的单位
  • 每段第一个字节的位置称为“段起始地址”,可由软件指定
  • 段起始地址:必须能被16整除(即XXXX0H)
  • 几个段可以相互重叠,也可指向同一个空间
  • 指令、字节数据和字数据可以自由地存放在任何字节地址中
(6)逻辑地址与物理地址:
  • 在具有地址变换机构的计算机中,有两种存储器地址:
    逻辑地址(16位):允许在程序中编排的地址
    物理地址(20位):信息在存储器中实际存放地址

在8086系统中每个存储单元也都认为有这两类地址。

(7)段基址与段内偏移量:
  • 8086与存储器之间所有信息交换都要使用20位的物理地址,而程序中所涉及的地址都是16位的逻辑地址,对所给定的任一存储单元而言有两部分逻辑地址:

    段基址:决定了该段第一个字节的位置,存放在段寄存器(CS、SS、DS、ES)

    段内偏移量:该存储单元相对于该段起点字节的距离,由SP、BP、SI、DI、IP以及相应寄存器的组合而组成

(8)物理地址的生成算法:

存储单元的20位物理地址是通过将16位的段基址左移4位再加上16位的偏移地址而生成的,即:
物理地址=段基址×10H+段内偏移量

如,6000:0280的物理地址为60280H

8086CPU中BIU单元的加法器(图右上角Σ)可用来完成物理地址的计算。

3.通用寄存器

它们都可以用来保存算术结果或暂存数据

(1)数据寄存器(AX,BX,CX,DX)

这四个寄存器作为通用寄存器,用来暂存计算过程中所用到的操作数,结果或其它信息。其中每个寄存器可以分为高八位(_H)和低八位(_L),这两个八位可以单独作为两个寄存器使用(只有这四个可以8位)。访问形式: 可以用16位访问;或者可以用字节(8位)形式访问。

  • AX (Accumulator):累加寄存器,也称之为累加器,是算术运算的主要寄存器;与DX一起在乘法与除法中做32位寄存与结果寄存;所有I/O指令都使用这一寄存器与外部设备交换数据;
IN     AL  ,   20H ;从20端口读取8位二进制数据放入AL
OUT    30H ,    AX ;向I/O端口地址30H写字数据AX
  • BX (Base):基地址寄存器,在计算内存储器地址时,常用来存放基地址,[bx]表示对bx中存放内容作为地址的内存单元进行访问;
MOV     AX,   [BX+03H]	;设置 AX 的值为偏移地址为 BX+03H 的内存中的值
  • CX (Count):计数器寄存器,例如jcxz(条件转移指令),loop(循环指令),rep(重复前缀指令)都是根据CX寄存器的值进行判断然后决定是否进行跳转;在LOOP指令和串处理指令中用作隐含计数器,每循环一次递减;
MOV AX,0B800H
MOV DS,AX		;使用 80x25 彩色字符模式,内存地址 0xB8000 - 0xBFFFFF
MOV BX,0		;从 0xB8000 开始
MOV CX,5H		;循环 5 次
MOV DX,41H		;A 的16 进制为 41H
MOV AX,01110001B	;显示白底蓝字
s:  MOV [BX],DX	;显示 ASCII 字符
    ADD BX,1
    MOV [BX],AX	;设置字符显示属性
    ADD BX,1
LOOP s
->AAAAA(白底蓝字)
  • DX (Data):数据寄存器,在进行32位的乘除法操作(双字长乘除法运算)时,用它存放被除数的高16位或余数。对某些I/O操作,DX可用来存放I/O的端口地址(口地址>=256);
MOV AH, FFh
MOV AL, 00h
->AX=FF00
(2)地址寄存器(SP,BP,SI,DI)

SP、BP、SI、DI四个16位寄存器,以字为单位在运算过程中存放操作数,常用以在段内寻址时提供偏移地址,用来保存当前CPU所访问的内存单元的地址。

地址指针寄存器(SP 、 BP)

  • SP (Stack Pointer):堆栈指针寄存器,它指定栈顶(SS)的段偏移地址,与SS决定的栈段地址一起决定了栈顶元素的地址;
  • BP (Base Pointer):基址指针寄存器,指示当前栈帧的基地址,与SS寄存器联合使用来确定堆栈段中某一存储器单元地址。用于给出堆栈中数据区基地址(BX)的偏移(栈底指针,指向栈底);

在 8086 CPU 中,只有 4 个寄存器能以 […] 的方式使用,分别是 BX,SI,DI,BP。当以 […] 的方式访问内存单元,且在 […] 中使用了寄存器 BP ,那么如果指令中未明确给出段地址时,段地址默认使用 SS 寄存器中的值(BX,SI,DI 会默认使用 DS 段寄存器)。

MOV BP,0			;将BP寄存器的内容设置为0
MOV AX,[BP]         ;将 SS:[BP] 代表的内存单元移入 AX 中
MOV AX,CS:[BP]      ;将 CS:[BP] 代表的内存单元移入 AX 中

变址寄存器(SI 、DI):用于变址寻址;SI , DI具有自动增量自动减量功能。

  • SI (Source Index):源变址寄存器,在串处理指令中,SI作为隐含的源变址寄存器与DS联用,以确定数据段(DS)中某一存储单元地址
  • DI (Destination Index):目的变址寄存器,在串处理指令中,DI和附加段寄存器ES联用,以达到在附加段(ES)中寻址的目的;
;以下列举一些可行代码,其中SI可换为DI
MOV SI,0		;初始化偏移地址为 0
MOV AX,[SI]		;将段地址为 DS 偏移地址为 SI 的内存单元中的值移入 AX 中
MOV AX,DS:[SI]		;将段地址为 DS 偏移地址为 SI 的内存单元中的值移入 AX 中
MOV AX,SS:[SI]		;将段地址为 SS 偏移地址为 SI 的内存单元中的值移入 AX 中

在串处理指令中,SI、DI作为隐含的源变址和目的变址寄存器分别达到在数据段和附加段中寻址的目的。

4.段寄存器(DS,ES,SS,CS)

变址寄存器和上面介绍的指针寄存器(也就是 BP 和 SP)类似,都是用于存放某个存储单元地址的偏移,或用于某组存储单元开始地址的偏移,即存储器指针;当然,由于变址寄存器和指针寄存器都属于通用寄存器,所以它们也可以保存算术结果或者暂存数据。

  • DS (Data Segment):数据段寄存器,存放当前执行的程序所用数据段的段地址

  • ES (Extra Segment):附加段寄存器,存放当前执行程序中一个辅助数据段(附加段)的段地址(其他寄存器不够用了就用它吧);

  • SS (Stack Segment):堆栈段寄存器,存放当前执行的程序所用堆栈段的段地址

  • CS (Code Segment):代码段寄存器,存放当前执行的程序的段地址(见下CS:IP)(程序一般放在代码段);

    当一个可执行文件加载到内存中以后,CS:IP 两个寄存器便指向了这个可执行文件的起始地址,然后 CPU 就可以从这个起始地址开始往下读取指令,当读取完指令后,CS:IP 将会自动的改变,一般是改变 IP ,从而指向下一条要读取的指令,这样就可以执行这个可执行文件了 。

8086的CPU内部总线是16位,但是地址总线是20位,为了达到20位的寻址能力,就用了两个16位的地址去合成一个20位的总线。例如一个20位的地址ABCDE,可以表示为ABCD,000E,那么ABCDE =ABCD*16 + E;

或者 ABC0 + 00DE等,那么ABCDE = ABC0* 16 + DE;

从上面可以看出对于8086CPU的一个段的最大长度就是64kb了。段寄存器的作用就可想而知,是为了提供20位地址中的段地址

(1)8086段寄存器的使用约定

5.控制寄存器

(1)指令指针寄存器(IP)
  • IP (Instruction Pointer):指令指针寄存器,用来存储代码段中的偏移地址。和CS一起决定了指令物理地址CS:IP;程序运行过程中IP始终指向下一次要取出的指令偏移地址。你想让 CPU 执行哪行指令,你就让 CS:IP 指向保存有指令的那块内存即可。
(2)程序状态寄存器/标志寄存器(PSW/FLAG)

PSW (Program Status Word):程序状态寄存器,在有些机器中称为标志寄存器(Flag Register / FLAG)。

按位起作用,每个一位,用来存放两类信息:

一类是状态标志,由CPU根据计算结果自动设置的,用来记录程序中运行结果的状态信息,作为后续条件转移指令的转移控制条件。故也称为条件码。如有无借位进位(CY位)、有无溢出(OF位)、结果正负(SF位)、结果是否为零(ZF位)、奇偶标志位(PF位)、辅助进位标志(AF位)。同时机器也提供了设置状态信息指令,必要时程序员可以用这些指令来建立状态信息;

另一类是控制标志,由系统程序或用户程序根据需要用指令来设置的,设置后对其后的操作起控制作用。如允许中断(IF位),跟踪标志(TF位),方向标志(DF)。

(#控制标志)(状态标志)序数.标志位名称(1/0)(全称):描述

  • 11st. OF(OV/NV)(OverFlow FLag):溢出标志,其通常记录了有符号数运算的结果是否发生了溢出。

    OF=1:在运算过程中,如操作数超过了机器表示的范围称为溢出。
    OF=0:在运算过程中,如操作数未超过了机器能表示的范围称为不溢出。

    字节允许范围:-128+127,
    字运算范围: -32768+32767

  • #10th.DF(DN/UP)(Direction FLag):方向标志,其用于在串处理指令中,用来控制每次操作后 SI 和 DI 是自增还是自减。

    DF=1,每次串处理操作后使变址寄存器SI和DI减量,使串处理从高地址向低地址方向处理。
    DF=0,每次串处理操作后使变址寄存器SI和DI增量, 使串处理从低地址向高地址方向处理。

  • #9th.IF(EI/DI)(Interrupt-Enable FLag):中断允许标志,其决定 CPU 是否能够响应外部可屏蔽中断请求(以后会做详细介绍)。

    IF=1, 允许外部可屏蔽中断。CPU可以响应可屏蔽中断请求。
    IF=0, 关闭中断。CPU禁止响应可屏蔽中断请求。

    IF的状态对不可屏蔽中断和内部软中断没有影响。

  • #8th.TF()(Trap Flag):跟踪(陷阱)标志,高位使CPU处于单步执行指令工作方式,便于进行程序调试,用户能检查程序。

    TF=1 ,每执行一条指令后,自动产生一次内部中断,使CPU处于单步执行指令工作方式,便于进行程序调试,用户能检查程序。
    TF=0, CPU正常工作,不产生陷阱。

  • 7th.SF(NG/PL)(Sign Flag):符号标志,其记录相关指令执行完以后,其结果是否为负数。

    SF=1:记录运算结果的符号为负。
    SF=0:记录运算结果的符号为正。

  • 6th.ZF(ZR/NZ)(Zero Flag):零标志,记录相关的指令执行完毕后,其执行的结果是否为0。

    ZF=1:运算结果为0。
    ZF=0:运算结果不为0。

  • 4th.AF(AC/NA)(Auxiliary Carry Flag):辅助进位标志,字(字节)操作中发生低(半个)字节向高(半个)字节借位或者进位。

    AF=1:记录运算时第3位(半个字节)产生进位值。
    AF=0:记录运算时第3位(半个字节)不产生进位值。

  • 2nd.PF(PE/PO)(Parity FLag):奇偶标志,用来记录相关指令执行后,其结果的所有的 Bit 位中 1 的个数是否为偶数。

    PF=1:结果操作数低8位中有偶数个1。
    PF=0:结果操作数低8位中有奇数个1。

  • 0th.CF(CY/NC)(Carry FLag):进位标志,用来反映计算时是否产生了由低位向高位的进位,或者产生了从高位到低位的借位。

    CF=1:记录运算时从最高有效位产生进位值。
    CF=0:记录运算时从最高有效位不产生进值。

6.纪要

在 8086 CPU 中,只有 4 个寄存器可以以 […] 的方式使用,分别是 BX,SI,DI,BP。

任何时刻,SS:SP 都是指向栈顶元素,而BP指向栈底。

程序运行过程中 CS:IP 始终指向下一次要取出的指令地址。

8086不支持直接将一个数据送入段寄存器中,只能将其先送入寄存器。

在loop指令中,ECX自动成为循环计数器,且循环一次自减1,当ECX==0时停止跳转,不为0时跳转到符号内容中。

(四)x86汇编指令

上新32位处理器!全32位总线!Intel 80386@12.5MHz(1985)

|——16位——|—8位—|—8位—|

                       |    AH    |    AL    |

                       |           AX           |

|                   EAX                       |

进入32位时代,我们在原来的寄存器名前加“E”(Extend)表示32位寄存器(四个字节),如:AX(EAX的低16位)->EAX(32位)

除 EBP 和 ESP 外,其他几个寄存器的用途是比较任意的

指令集体系架构 ISA(Instruction-Set Architecture)

0.x86指令编码

基本指令,由以下组成:

  • Instruction Prefixes(指令前缀),可选,最多四个前缀,每个前缀1字节,用于指示紧随其后的指令应该如何被解释。
  • Opcode(操作码),必须,1或2字节。定义了操作的方向、位移的大小、寄存器编码、条件码或符号扩展。
  • ModR/M与SIB(地址形式说明符),可选,由ModR/M字节和SIB(Scale-Index-Base)字节组成。Mod R/M 字段指定寻址模式和操作数,“R/M” (register/memory) 代表寄存器和模式。伸缩索引字节(scale index byte, SIB)用于计算数组索引偏移量。
  • Displacement(地址位移),可选,地址位移为1、2、4字节或无。地址位移字段保存了操作数的偏移量,在 “基址-偏移量” 或 “基址-变址-偏移量” 寻址模式中,该字段还可以与 “基址或变址寄存器” 相加。
  • Immediate(立即数),可选,立即数为1、2、4字节或无。立即数字段保存了常量操作数。立即数(Immediate)是直接在指令中给出的常数或者立即值。这些值直接嵌入到指令中,无需从内存中读取,也不需要计算。

1.指令分类:

支持操作码扩展技术,可将不使用的地址码字段扩展为操作码

(1)根据地址码格式:

以下用于操作数的标记分别表示寄存器、内存和常数:

<reg>:表示任意寄存器,若其后带有数字,则指定其位数,
			<reg32> 表示 32 位寄存器(EAX,EBX,ECX,EDX,ESI,EDI,ESP 或 EBP);
			<reg16> 表示 16 位寄存器(AX, BX, CX 或 DX);
			<reg8> 表示 8 位寄存器(AH、AL、BH、BL、CH、CL、DH、DL)。
<mem>:表示内存地址
<con>:表示 8 位、16 位或 32 位常数,<con8>表示 8 位常数。

X86 中的指令机器码长度位 1 字节,对同一指令的不同用途有多种编码方式,比如 mov 指令就有 28 种机内编码,用于不同操作数类型或用于特定寄存器,例如:

	mov ax, <con16>;    机器码为 B8H
	mov al, <con8>;    机器码为 B0H
	mov <reg16>/<mem16>, <reg16>;   机器码为 89H
	mov <reg8>, <reg8>/<mem8>;    机器码为 8AH
	mov <reg16>, <reg16>/<mem16>;  机器码为 8BH
(2)根据操作类型:

主要有数据传送指令、逻辑计算指令和控制流指令等。

(3)必须掌握的一些汇编指令:

MOV 数据传送指令

PUSH,POP 堆栈指令

CMP 比较指令

LEA 取地址指令

XOR 异或指令

JE,JZ,JMP...(所有的转移指令)

2.寻址模式和内存分配

(1)寻址模式

X86 提供了一种灵活的内存寻址方式,以 mov 为例,mov 用于在内存和寄存器之间移动数据,

它有两个参数:第一个是目的地地址,第二个是源地址,示例如下:

	mov eax, [ebx]                ;将 ebx 值指示的内存地址中的 4 字节传送到 eax
	mov [var], ebx                ;将 ebx 值传送到 var 的值指示的内存地址中
	mov eax, [esi-4]              ;将 esi-4 值指示的内存地址中的 4 字节传送到 eax
	mov [esi+eax], cl;
	mov edx, [esi+4*ebx];

注:最多只能利用两个 32 位寄存器和一个 32 位的有符号常数(偏移)相加计算出一个内存地址。

(2)规定数据类型长度(DB,DW,DD,PTR)

汇编语言中声明内存大小时,一般显式的使用 DB(D 表示 Data,B 表示 Byte)、DW(W 表示 Word,2Bytes)和DD(第二个字母 D 表示 Double World,4Bytes),这样就能指导编译器分配内存空间,但是对于:mov [ebx], 2;​​若无特殊标记,则不确定常数 2 是单字节、双字节还是双字,为此,X86 提供了三个指示规则标记:BYTE PTR, WORD PTR 和 DWORD PTR​​,则上述例子可写成

	mov byte ptr [ebx], 2;      将 2 以单字节形式传输到 ebx 值指示的内存地址中
	mov word ptr [ebx], 2;      将 2 以双字节的形式传送到 ebx 值指示的内存地址中
	mov dword ptr [ebx], 2;     将 2 以四字节的形式传送到 ebx 值指示的内存地址中

3.常用指令

(1)数据传送指令
mov 指令

mov 指令将第二个操作数(寄存器的内容、内存中的内容或常数值)复制到第一个操作数(寄存器或内存),但不能用于直接从内存复制到内存,语法如下:

	mov <reg>, <reg>
	mov <reg>, <mem>
	mov <mem>, <reg>
	mov <reg>, <con>
	mov <mem>, <con>
	example:
	mov eax, ebx;
	mov byte ptr [var], 5;

注:

  • 两个操作数必须是同样的大小,MOV指令不能直接将较小的操作数复制到较大的操作数中。

  • 两个操作数不能同时为内存操作数。

  • 指令指针寄存器(IP、EIP 或 RIP)不能作为目标操作数。

  • MOV对于变量,加不加[]都表示取值;

    对于寄存器而言,无[]表示取值,有[]表示取地址。

MOVZX/MOVSX指令

MOVZX 指令(进行全零扩展并传送)将源操作数复制到目的操作数,并把目的操作数 0 扩展到 16 位或 32 位。这条指令只用于无符号整数,有三种不同的形式;

MOVZX reg32,reg/mem8
MOVZX reg32,reg/mem16
MOVZX reg16,reg/mem8

MOVSX 指令(进行符号扩展并传送)将源操作数内容复制到目的操作数,并把目的操作数符号扩展到 16 位或 32 位。这条指令只用于有符号整数,有三种不同的形式;

MOVSX reg32, reg/mem8
MOVSX reg32, reg/mem16
MOVSX reg16, reg/mem8
XCHG指令

交换两个操作数内容,但不能直接交换两个内存操作数。

XCHG reg, reg
XCHG reg, mem
XCHG mem, reg
LEA指令

LEA是mov指令的变种,据说lea指令是x86体系结构中最古老但又最神奇的指令。取自英语Load effect address(取有效地址),即取偏移地址。在微机8086/8088中有20位物理地址,由16位段基址向左偏移4位再与偏移地址之和得到。LEA指令取源操作数的偏移地址,并把它传送到目的操作数(逗号前的寄存器)所在的单元。

lea可以进行比较复杂的计算,比如lea eax,[esi+ebx4],把ebx的值4,加上esi的值,存入eax中;而mov就不行。

指令格式:LEA 目的,源

(原操作数必须是存储单元,而且目的操作数必须是一个除段寄存器之外的16位或32位寄存器。当目的操作数是16位通用寄存器时,则只装入有效地址的低16位。)

lea   eax,[ebp]		;将ebp的值放入eax寄存器
lea eax,[401000h]	;将值401000h写入eax寄存器中
LEA   BX,[BX+200]	;将BX的值+200放入BX寄存器
lea eax,dword ptr [ebx]	;将ebx的值赋值给eax
lea eax,c	;其中c为int型变量,该条语句把c的地址赋值给eax

注:

  • LEA对于变量,其后面的有无[]皆可,都表示取变量地址,相当于指针。

    对于寄存器而言,无[]表示取地址,有[]表示取值。

push 指令

push 指令将操作数压入内存的栈,常用于函数调用。ESP 是栈顶,压栈前先将 ESP 值减 4,因为栈增长方向与内存地址增长方向相反,然后将操作数压入 ESP 指示的地址;栈中元素固定为 32 位。语法:

	push <reg32>
	push <mem>
	push <con32>
	example:
	push eax;
	push [var];    将 var 指示的内存地址的 4 字节值压入栈
pop 指令

pop 指令执行出栈工作,出栈前先将 ESP 指示的地址种内容取出栈,然后将 ESP 值加 4,语法如下:

	pop edi;    弹出栈顶元素送到 edi
	pop [ebx];   弹出栈顶元素送到 ebx 值指示的内存地址的 4 字节中
(2)算术和逻辑运算指令
add/sub 指令

add 指令将两个操作数相加,相加的结果保存到第一个操作数中。sub 指令用于两个操作数相减,相减的结果保存到第一个操作数中,语法格式如下:

	add <reg>, <reg>
	add <reg>, <mem>
	add <mem>, <reg>
	add <reg>, <con>
	add <mem>, <con>
	sub <reg>, <reg>
	sub <reg>, <mem>
	sub <mem>, <reg>
	sub <reg>, <con>
	sub <mem>, <con>
	example:
	sub eax, 10     ;eax ←eax-10
	add byte ptr [var], 10    ;10 与 var 值指示的内存地址的一字节值相加,并将结果保存在与 var 值指示的内存地址的字节中
inc/dec 指令

inc、dec 指令分别表示操作数自加 1或自减 1,其语法格式如下:

	inc <reg>
	inc <mem>
	dec <reg>
	dec <mem>
	example:
	dec eax   ;eax 值自减 1
	inc dword ptr [var]   ;var 值指示的内存地址的 4 字节值自加 1
imul 指令

带符号整数乘法指令,有两种格式:

1)两个操作数,将两个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器;

2)三个操作数,将第二个和第三个操作数相乘,并将结果保存在第一个操作数中,第一个操作数必须为寄存器;

语法格式如下:

	imul <reg32>,<reg32>
	imul <reg32>, <mem>
	imul <reg32>, <reg32>, <con>
	imul <reg32>, <mem>, <con>
	example:
	imul eax, [var]   ;eax←eax * [var]
	imul esi, edi, 25   ; esi←edi * 25
idiv 指令

带符号整数除法指令,只有一个操作数,即除数,而被除数则为 edx:eax 中的内容,是64 位整数;操作结果分为两部分:商和余数,商送到 eax,余数送到 edx;语法格式如下:

	idiv <reg32>
	idiv <mem>
	example:
	idiv ebx
	idiv dword ptr [var]
and/or/xor 指令

逻辑与、逻辑或、逻辑异或操作指令,用于操作数的位操作,操作结果放在第一个操作数中;语法格式如下,由于格式类似,只需将前面的指令改一下就可以,所以这里以 and 为例:

	and <reg>, <reg>
	and <reg>, <mem>
	and <reg>, <con>
	and <mem> ,<con>
	example:
	and eax, ofH    ;将 eax 中的前28 位全部置零,最后 4 位不变
	xor edx, edx    ;置 edx 中的内容为 
not 指令

位翻转指令(非),将操作数的每一位翻转,即0⟶1,1⟶0;语法格式如下:

	not <reg>
	not <mem>
	example:
	not byte ptr [var]   ;将 var 值指示的内存地址的一字节的所有位翻转。
neg 指令

取负指令,语法格式如下:

	neg <reg>
	neg <mem>
	example:
	neg eax
shl/shr 指令

逻辑位移指令,shr 为逻辑右移,shl 表示逻辑左移,第一个操作数表示被操作数,第二个操作数指示移位的位数;语法格式如下:

	shl <reg>, <con8>
	shl <mem>, <con8>
	shl <reg>, <cl>
	shl <mem>, <cl> 
	; 上面的 shl 改成 shr 就是逻辑右移的格式
	example
	shl eax, 1    ;将 eax 的值左移 1 位,相当乘于 2
	shr ebx, cl   ;将 ebx 值右移 n 位,n 为 cl 中的值,相当于除于 2^n
(3)控制流指令

X86 处理器维持这一个指示当前执行指令的指令指针(IP),当执行一条指令后,此指针自动指向下一条指令。IP 寄存器不能直接操作,但可以用控制流指令更新。通常用标签(label)指示程序中的指令地址,在 X86 汇编代码中,可在任何指令前加入标签,例如:

		mov esi, [edp+8]
begin:  xor ecx, ecx
		mov eax, [esi]

这样就用 begin 指示了第二条指令,控制流指令通过标签可以实现程序的跳转。

jmp 指令

控制 IP 转移到 label 所指示的地址,从 label 中取出指令执行;语法格式如下:

	jmp <label>
	example:
	jmp begin
	example2:	;无限循环
	top:
    .
    .
    jmp top
LOOP指令

LOOP 指令,正式称为按照 ECX 计数器循环,将程序块重复特定次数。ECX 自动成为计数器,每循环一次计数值自减 1。
循环目标必须距离当前地址计数器 -128 到 +127 字节范围内。LOOP 指令的执行有两个步骤:

  • 第一步,ECX 减 1。
  • 第二步,将 ECX 与 0 比较。

当ECX不等于0,则跳转到符号内容中; 若ECX等于0,则不发生跳转。

    ; 每次循环将AX加1,当循环结束,AX=5,ECX=0
    MOV AX, 0
    MOV ECX, 5
L1:
    INC AX
    LOOP L1
jcondition 指令(系列)

条件转移指令,它依据处理机状态字中的一系列条件状态转移。处理机状态字包括指示最后一个算术运算结果是否为 0,运算结果是否为负数等;

以下是用于算术运算的有符号数据的条件跳转指令:

指令描述标志测试
JE/JZ跳转等于或跳转零ZF
JNE/JNZ跳转不等于或跳转不为零ZF
JG/JNLE跳转大于或跳转不小于/等于OF, SF, ZF
JGE/JNL跳转大于/等于或不小于跳转OF, SF
JL/JNGE跳转小于或不大于/等于OF, SF
JLE/JNG跳小/等于或跳不大于OF, SF, ZF

以下是对用于逻辑运算的无符号数据使用的条件跳转指令:

指令描述标志测试
JE/JZ跳转等于或跳转零ZF
JNE/JNZ跳转不等于或跳转不为零ZF
JA/JNBE跳转向上或不低于/等于CF, ZF
JAE/JNB高于/等于或不低于CF
JB/JNAE跳到以下或跳到不高于/等于CF
JBE/JNA跳到下面/等于或不跳到上方AF, CF

以下条件跳转指令有特殊用途,并检查标志的值:

指令描述标志测试
JXCZ如果 CX 为零则跳转none
JC如果进位则跳转CF
JNC如果不进位则跳转CF
JOJump If OverflowOF
JNO如果没有溢出则跳转OF
JP/JPE跳校验或偶校验PF
JNP/JPO跳转无奇偶校验或跳转奇偶校验PF
JS跳跃符号(负值)SF
JNS跳转无符号(正值)SF

J <condition> 指令集的语法如下:

	je <label> (jump when equal)
	jne <label>(jump when not equal)
	jg <label>(jump when greater than)
	jge <label>(jump when greater than or equal to)
	example:
	cmp eax, ebx
	jle done   ;如果 eax 的值小于等于 ebx 值,跳转到 done 所指示的指令执行,否则执行下一条指令
cmp 指令

比较两个操作数的值,并根据比较结果设置处理机状态字中的条件码;语法格式如下:

	cmp <reg>,<reg>
	cmp <reg>, <mem>
	cmp <mem>, <reg>
	cmp <reg>, <con>

通常与 jcondition 指令搭配使用:

	cmp dword ptr [var], 10
	jne loop   ;如果 var 指示的内存地址的 4 字节内容等于 10,则继续执行下一条指令;否则跳转到 loop 指示的指令执行。
test指令

将两个操作数进行逻辑与运算(测试),并根据运算结果设置相关的标志位。类似于cmp但Test命令的两个操作数不会被改变。运算结果在设置过相关标记位后会被丢弃。

test eax 01H ;测试eax奇偶(或改为00H)
call/ret 指令

这两条指令实现子程序(过程、函数等)的调用及返回。call 指令首先将当前指令地址入栈,然后无条件转移到由标签指示的指令。与其他简单的跳转指令不同,call 指令保存调用之前的地址信息,当 call 指令结束后,返回调用之前的地址。ret 指令实现子程序的返回机制,ret 指令弹出栈中保存的指令地址,然后无条件转移到保存的指令地址执行。call 和 ret 是函数调用中最关键的两条指令,其语法格式如下:

	call <label>
...
	ret

(五)8086的die

机魂不悦?来看硬件涩图!

凿开陶瓷上盖,与现代CPU不同,8086的芯片只占封装的一小部分,40个引脚通过飞线与芯片连接

金相显微镜下,金属导电层,导线和晶体管清晰可见

用酸腐蚀掉金属结构,再小心磨掉上层多晶硅,底部的晶体管(约29000个)显露出来,主要模块已标记在图上。

左侧是芯片的算术逻辑单元,16位寄存器与加法器。ALU和下寄存器构成了执行单元(EU);加法器和上寄存器构成了总线接口单元(BIU)。上寄存器还包含6字节的指令预取缓冲区和程序计数器。8086的指令队列有6个字节,当指令队列出现2个空字节,BIU就自动执行一次取指令周期,将下一条要执行的指令从内存单元读入指令队列。它们采用“先进先出”原则按顺序存放,并按顺序取到EU中去执行。

右侧是芯片的控制电路,指令解码电路和用来控制每条指令的微码(Microcode)。微码告诉处理器的每个部分如何执行每条指令,而不是使用复杂的逻辑门电路来构建控制逻辑。这样8086能够实现复杂的指令而不会使电路变得更加复杂。

尽管本文的目的不是学习硬件实现,但笔者认为,学习抽象功能的同时尊重硬件架构,以期对所学概念有所锚定,一定会让机魂大悦的吧(


看完本文,相信你已经对8086的功能结构和x86汇编指令有一个较为全面的了解……吧?

还有疑问也不要紧,下面有一些参考和教程链接供您继续精进~(∠・ω< )⌒☆

(六)参考

  • A look at the die of the 8086 processor

    作者:Ken Shirriff

    链接:https://www.righto.com/2020/06/a-look-at-die-of-8086-processor.html

  • 冯·诺依曼体系结构介绍

    版权声明:本文为CSDN博主「awofe」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/awofe/article/details/128478906

  • The 8086 Family Users Manual -October1979

    链接:http://bitsavers.org/components/intel/8086/9800722-03_The_8086_Family_Users_Manual_Oct79.pdf

  • 8086汇编寄存器及指令汇总

    作者:luckyone906

    链接:https://blog.csdn.net/u011555996/article/details/80153141

  • 第二节 8088/8086微处理器-吉林大学

    链接:http://dec.jlu.edu.cn/baozi/courseware/011/tpl_course_0905u442.html

  • 【计算机组成原理】x86指令系统

    版权声明:本文为CSDN博主「命运on-9」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/phoenixFlyzzz/article/details/131125905

  • X86指令基本格式

    版权声明:本文为CSDN博主「百里杨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/zyhse/article/details/133639825

  • 计算机组成原理学习笔记——常用X86 汇编指令

    版权声明:本文为CSDN博主「御承扬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_42896653/article/details/105016916

  • 汇编语言 教程

    链接:https://cankaoshouce.com/assembly/assembly-course.html

  • X86架构基本汇编指令详解
    版权声明:本文为CSDN博主「Descosmos」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_41028985/article/details/119408236

  • 8
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值