8086汇编程序设计

Published: 5/5/2024

8086

8086是Intel最早推出的X86 16位CPU之一,使用20位地址总线,寻址空间为1MB,共有14个寄存器,其中包含8个通用寄存器,4个段寄存器,1个PC寄存器,1个PSW寄存器。

寄存器

通用寄存器

通用寄存器有AX,BX,CX,DX,BP,SP,SI,DI 共8个寄存器。即使说是通用寄存器,大多数通用寄存器仍然具有使用规范,包括:

  • 只有BX和BP用作基地址寻址,因此这两寄存器又被称为基址寄存器,只是BX默认相对于DATA段,而BP默认相对于堆栈段寻址。
  • CX用于各种循环指令和重复指令中,根据PSW中的ZF标志位决定加减。
  • AX常被用于乘除法的第一个操作数,DX被用来作为AX的高位,在16x16位乘法中MUL SRC表示AX * SRC,结果为DX:AX总计32位;在32/16位除法中,DIV SRC表示DX:AX表示的32位数字除以16位SRC
  • SI与DI寄存器常被用于串指令中,用于表示串的当前位置。
  • SP寄存器表示堆栈栈顶,BP用于非叶子程序参数传递。

需要注意前面四个16位寄存器更多被称为数据寄存器,每个寄存器可以分为2个对应位置的高八位寄存器和低八位寄存器,比如AX可分为高AH和低AL寄存器;后四个16位寄存器更多被称为指针寄存器,且不可像之前那样被分割。

段寄存器

然后就是4个段寄存器SS,DS,CS,ES分别表示堆栈段,数据端,代码段和附加段;附加段主要用于串指令配合SIDI使用。需要注意在8086中每个段寄存器都需要在程序开始时显式使用指令进行初始化,需要注意段寄存器不能用立即数初始化。

PC寄存器与PSW寄存器

PC寄存器(或者说是IP寄存器,我更喜欢称为PC寄存器)用于表示当前指令地址;PSW寄存器是一个标志位寄存器,用于存储指令在执行过程中产生的各种标志,包括溢出,进位,负数,中断,异常等。

指令集

8086的指令采取OP [DST[,SRC]]的形式,并且是不定长的CISC指令集。

主要可分为 数据传送指令,逻辑指令,算数指令,串指令 ,循环控制指令以及中断相关指令。

数据传送

最常使用的是MOV DST,SRC指令,用于将数据复制到另一个地方,这里需要注意传送双方的位长匹配问题,尤其是立即数向内存之间的传递,比如MOV S,0H就应该改成MOV WORD PTR S,0H,需要确定所使用内存的字长,并且传送时也应该注意数据的大小不能超过字长。

逻辑指令

主要有AND,OR,XOR,NOT,指令格式都为OP DST,SRC

算数指令

主要有ADD,ADC,INC,DEC,SBB,SUB,NEG,其中INC <T>,DEC <T>分别表示进一和减一。ADD,SUB不带进位和借位,而ADC与SBB则带进位,也就是操作时会带上标志位CF。NEG指令用于取补码。

移位指令总共有8条,包括算数,逻辑,循环以及进位循环,分别是SHL,SAL,SHR,SAR,ROL,ROR,RCL,RCR <dst>,<count>需要注意count只能是1或者是CL寄存器,并且会将其移出的位保存在CF标志位中。

串指令

串指令主要用于操作串,指令主要有MOVS,LODS,STOS,CMPS,SCANS;

LODS用于取出DS:[SI]内存到AL或者AX寄存器中,并将SI + 1或 + 2,STOS功能类似。

MOVS用于将DS:[SI]传送到ES:[DI]中,并将SI与DI同时加1或加2.

CMPS用于比较原串与目标串,SCANS用于扫描目标串中是否出现AL或者AX中指定的字符。

上面的指令根据DF标志位,使用CLD或者STD修改。

重复指令用于配合串指令使用,有REP,REPZ,REPNZ。REP 串指令会重复执行,并且不断将CX寄存器减1,直到CX位0停止。

REPZ则直到ZF为1并且CX为0,REPNZlei’si

LEA用于加载指定地址偏移到寄存器;LDS,LES分别用于将双字内容存放到对应段寄存器和指定寄存器。

跳转与循环指令

跳转指令主要分为无条件跳转和有条件跳转。

无条件跳转使用JMP <ADDR>

有条件跳转根据PSW中的标志位决定是否跳转,主要有JZ/JE, JNE,JAE,JBE TEST。x86的一个特点是有循环指令,包括LOOP,LOOPE,LOOPNELOOP <label> 会将CX - 1,如果不为0则跳转到label处。

CALL <proc>用于调用子程序,RET <2N>用于从子程序返回并修复栈。

中断指令

通过INT <X>传入中断号进入中断,通过IRET从中断返回。功能调用通过INT 21H,通过AH传入功能调用号,常用的功能调用有:

  • 01 输入字符并回显
MOV AH,1
INT 21H

输入的符号被存储AL寄存器中。

  • 02 输出字符
MOV AH,2
MOV DL,'A'
INT 21H

需要输出的字符需要被存放在DL寄存器中。

  • 09 输出字符串,必须以$结尾
MOV DX,OFFSET STR1
MOV AH,09H
INT 21H

将字符串地址存放在DX中,使用DS段寄存器。

  • 0AH 输入字符串
MOV AH,0AH
MOV DX,OFFSET BF1
INT 21H

将缓冲区地址存放在DX中,第一个字节存放缓冲区最大长度,在返回后,第二个字节存放读取长度,之后才是读取到的字符串内容。

  • 4CH 返回命令行
MOV AX,4CXXH
INT 21H

AL表示返回值,AH传递4CH功能调用号。

寻址方式

对于数据的寻址方式,总共有6种:

  • 立即数寻址:MOV AX,01H,指令所需操作数直接出现在指令中。
  • 直接寻址: MOV AX,[01H]:操作数的偏移地址直接出现在指令中。
  • 寄存器直接寻址:MOV AX,BX,操作数是某个寄存器。
  • 基址变值寻址:基址寄存器加上变址寄存器如MOV AX,[BX][SI]
  • 寄存器间接寻址: 操作数地址位于基址寄存器或变址寄存器中,如MOV,AX,[BX]
  • 寄存器相对寻址:MOV AX,[BX + 2]

对于地址的寻址方式,总共有4种:

  • 段内直接跳转:JMP LABEL SP - 2; SS:[SP] <- 返回地址
  • 段内间接跳转JMP WORD PTR <ADDR> SP - 2; SS:[SP] <- 返回地址
  • 段间直接跳转JMP FAR LABEL SP - 2; SS:[SP] <-段地址; SP - 2 ; SS:[SP] <- 返回地址
  • 段间间接跳转JMP DWORD PTR <ADDR> (IP <- ADDR, CS<- DS:[ADDR + 2]) SS:[SP] <-段地址; SP - 2 ; SS:[SP] <- 返回地址

程序结构

一个8086汇编程序主要有代码段,数据段,堆栈段组成,类似于

DATA_S_NAME SEGMENT PARA
	T1 EQU 1H
	T2 DB 0FFH
	T3 DW 100
	DB 10H
	T4 DD 100H DUP(0)
DATA_S_NAME ENDS

STACK_S_NAME SEGMENT PARA STACK
	STACK_AREA DW 100H DUP(?)
	STACK_BTM  EQU $ - STACK_AREA
STACK_S_NAME ENDS

CODE_S_NAME SEGMENT PARA 
	ASSUME CS:XX,DS:XX,SS:XX,ES:XX
MAIN PROC FAR
	...
MAIN ENDP
CODE_S_NAME ENDS
	END MAIN
  • 使用SEGMENTENDS定义段,最后在代码中对段寄存器初始化。PARA让段能够256字节对齐,还有BYTE,PAGE这些选项。

  • 使用END MAIN来指定程序入口为MAIN子程序。

  • 通过PROCENDP定义子程序,如果使用FAR为段间定义,否则为NEAR则是段内定义。

子程序与宏定义

子程序和宏相比总的程序占用空间更小,但是宏不需要使用堆栈。

子程序

通过PROCENDP来定义子程序,通过CALL调用子程序,通过RET <2n>从子程序返回并修复堆栈。可以使用堆栈来传递参数,比如在调用前使用堆栈存放参数,在子程序中使用BP保存传入时的SP寄存器,通过BP来访问参数,最后通过RET修复堆栈,这一过程需要注意BP的保存和修复。

通过MACROENDM来定义宏,宏可以传递参数,如果定义时给定形参,则使用时必须给定实参。

结构体

使用STRUCENDS来定义结构体数据结构,使用类似于定义段内变量的方式定义结构体的成员变量,使用时使用.操作符号。

调试

使用debug进行调试

  • A <ADDRESS>: 修改对应地址的汇编

  • U <ADDRESS>:反汇编指定地址。

  • G <ADDRESS>: 执行到对应地址

  • T逐条指令追踪

  • D <ADDRESS>导出对应地址的内存

  • E <ADDRESS>修改对应地址的内存

  • R <reg>查看或修改对应寄存器的值

PSW寄存器的具体内容

  • CF: 进位标志,如果最高位产生了进位,则为1;或者移位时被移除的位。
  • PF: 用来对数据提供奇偶校验值,对低8位进行检验。
  • ZF:0标志位,如果运算结果为0则为1.
  • TF:陷阱标志位,用于调试
  • IF:中断标志位,IF为1则允许中断。
  • DF:用于控制串操作指令的方向,使用CLD和STD指令设置0和1。
  • OF:溢出标志,如果超过机器的补码范围,则为1.
  • SF:用于记录运算结果的符号,为负数则为1.、
  • AF:辅助进位标志。