X86汇编程序设计笔记-2

本系列是北航计算机学院于 2024 年春季学期开设的一般专业课《X86汇编程序设计》的学习笔记,由于学习过程中掌握并不牢靠,如有错误请读者不吝赐教!

编程技巧

在编写 X86 程序时,掌握寄存器、内存存取等操作是非常重要的。因为 X86 提供的系统调用和 MIPS 相比更原始、更接近操作系统底层,而且寄存器等限制更多、数量更少,所以需要先熟练掌握后再开始编程,避免无谓的 debug。

系统调用

寄存器的特殊使用

下面介绍的是在进行指令操作时必须遵守的规则,在遵守寄存器使用规则的同时也要注意系统调用使用的寄存器可能会和当前正在执行的操作抢占相同的寄存器(比如 AX 存数据的同时向 AH 赋值进行系统调用,就破坏了原有的数据),只要是给寄存器赋值就要注意会不会影响其他数据,当成在写编译器处理寄存器就完事了

x86汇编之——8086寄存器讲解

  • AX:乘除指令存放左侧的运算数(乘数1或被除数),运算后保存结果(乘法存低 16 位、16 位除法 AH 商 & AL 余数、32 位除法商)
  • BX:间接寻址计数器,[BX] 表示 DS 段地址 + BX 偏移量的内存单元数据
  • CX:循环计数器、移位默认计数器,循环 = 0 就跳出循环
  • DX:乘除指令保存结果(32 位乘法存高 16 位、32 位除法存余数);DL 系统调用
  • 间接寻址寄存器:
    • BP、SP:基地址使用 SS 寄存器,也就是说指向的是堆栈空间
    • DI、SI:基地址使用 DS 寄存器,也就是说指向的是数据段空间,寻址操作符用寄存器表示内部偏移量时必用
  • SS:栈底/基地址寄存器,SS:SP 寄存器指向的是堆栈的栈顶元素

debug 操作

断点与 -g

虽然在 DOSBOX 中我们并没有办法直接实现在 GUI 上生成断点的操作,但是仍然可以通过 debug 程序提供的 -u + -g 指令符实现断点。具体而言可以按照以下操作:

  • 先通过 -u 标记所需断点指令的所在地与内存值
  • (怕忘的话可以把内存写在 asm 文件那一行后面当注释)
  • -g ip,直接运行标记指令处

可以说 -g 操作是为数不多的能够在 DOSBOX 中用到的比较现代的指令了()

标志位状态查看

在 debug 过程中用 -r 可以查看到当前的寄存器状态,这几行信息的右下角会有 8 个英文双字,这些符号代表的就是标志寄存器中标志位的值:

符号位名称 状态值(1) 状态值(0)
溢出标志 OF(Over flow flag) OV NX
方向标志 DF(Direction flag) DN UP
中断标志 IF(Interrupt flag) EI DI
符号标志 SF(Sign flag) NG PL
零标志 ZF(Zero flag) ZR NZ
辅助标志 AF(Auxiliary carry flag) AC NA
奇偶标志 PF(Parity flag) PE PO
进位标志 CF(Carry flag) CY NC

最常用的可能就是 CF 和 ZF 了,其他的用到的不太多

标志位与特定指令

不仅标志位的取值会影响某些指令的行为,某些指令也会反过来改变标志位的值。

  • 运算指令 & CF:ADD 和 SUB 会影响 CF 位的值(进位/借位),CF 也会影响 ADC 和 SBB 的值
  • CMP & CF:CMP 指令如果 operand1 >= operand2 置 OF = 0,否则为 1
  • MUL/IMUL & CF/OF:如果乘法得到的乘积高一半为 0,两个标志位均置 0,否则均为 1
  • AND/OR/NOR:执行后 CF、OF 置 0,PF 取决于结果操作数中 1 的个数
  • 所有的条件码转移指令都根据某个条件码的值分为两个指令: J[N]<标志位>

常用程序段

输出 ASCII 字符/字符串

; ASCII IN DATA
PRINT_CHAR PROC
MOV AH, 02H
MOV DL, DATA
INT 21H
PRINT_CHAR ENDP
; STRING IN DATA
PRINT_STRING PROC
MOV AH, 09H
MOV DL, OFFSET DATA
INT 21H
PRINT_STRING ENDP

输出寄存器中的数字

; digit in ax
PRINT_DIGIT PROC
MOV BX, 10
MOV CX, 0
calculater:
XOR DX, DX
DIV BX ; DIV 10
PUSH DX ; PUSH REMINDER
INC CX ; COUNT LENGTH
TEST AX, AX ; IF ZERO, QUIT
JNZ calculate
printer:
POP DX ; POP REMINDER
ADD DL, '0'
MOV AH, 02H
INT 21H ; SYSCALL
LOOP printer ; COUNT LENGTH

RET
PRINT_DIGIT ENDP

读入一串字符串

; READ INTO input
GET_STRING PROC
MOV AH, 0AH
LEA DX, input
INT 21H ; SYSCALL
GET_STRING ENDP

读入一个数字

; READ INTO input
GET_NUMBER PROC
MOV BX, 0
MOV AH, 0AH
LEA DX, input
INT 21H ; SYSCALL

XOR AX, AX
MOV SI, OFFSET input + 2 ; SI = START
MOV CL, BYTE PTR [SI - 1] ; CL = LENGTH

MOV CH, 0 ; CH = 0

convert_loop:
MOV DX, 10
MOV BL, BYTE PTR [SI]
SUB BL '0'
MOV BH, 0 ; BX = DIGIT[SI]

MUL DX ; AX = AX * 10
ADD AX, BX ; AX = AX + BX
INC SI ; POINTER++
LOOP convert_loop
POP BX
RET
GET_NUMBER ENDP