汇编语言程序设计 | AI生成和翻译
以下是 第二部分:汇编语言程序设计 的全面教程,涵盖以下主题:8086 指令集(数据传送、算术、逻辑和控制流指令)、汇编语言程序设计(顺序、分支和循环结构)以及中断服务程序。本教程内容详尽、易于理解且注重实践,建立在微型计算机(例如 8086/8088 架构)的基础知识之上。它假定读者已具备 CPU 寄存器和内存寻址的基本知识。
第二部分:汇编语言程序设计
汇编语言是一种低级编程语言,它提供了对微处理器操作的直接控制。对于 Intel 8086/8088,汇编语言允许程序员编写与机器代码紧密映射的指令,从而对寄存器、内存和 I/O 设备等硬件资源进行细粒度控制。
1. 8086 指令集
8086 指令集是 CPU 能够理解的一系列命令,按其功能可分为:数据传送、算术、逻辑 和 控制流。每条指令都作用于寄存器、内存或立即数,并使用 8086 的寻址方式(例如,寄存器寻址、直接寻址、间接寻址)。
a. 数据传送指令
这些指令在寄存器、内存和立即数之间移动数据。
- MOV(传送):
- 语法:
MOV 目标, 源 - 功能:将数据从源复制到目标。
- 示例:
MOV AX, BX(将 BX 复制到 AX);MOV AX, [1234h](将内存地址 DS:1234h 处的数据复制到 AX)。 - 说明:不影响标志位;源和目标的大小必须相同(8 位或 16 位)。
- 语法:
- XCHG(交换):
- 语法:
XCHG 目标, 源 - 功能:交换源和目标的内容。
- 示例:
XCHG AX, BX(交换 AX 和 BX)。
- 语法:
- PUSH(压栈):
- 语法:
PUSH 源 - 功能:将 16 位数据压入堆栈,SP 减 2。
- 示例:
PUSH AX(将 AX 保存到堆栈)。
- 语法:
- POP(出栈):
- 语法:
POP 目标 - 功能:从堆栈弹出 16 位数据到目标,SP 加 2。
- 示例:
POP BX(从堆栈恢复 BX)。
- 语法:
- LEA(加载有效地址):
- 语法:
LEA 目标, 源 - 功能:将内存操作数的地址加载到寄存器中。
- 示例:
LEA BX, [SI+4](将 DS:SI+4 的地址加载到 BX)。
- 语法:
- IN/OUT(输入/输出):
- 语法:
IN 目标, 端口;OUT 端口, 源 - 功能:与 I/O 端口传输数据。
- 示例:
IN AL, 60h(读取键盘端口);OUT 61h, AL(写入扬声器端口)。
- 语法:
b. 算术指令
这些指令执行数学运算,并根据结果更新标志位(例如 ZF、CF、SF、OF)。
- ADD(加法):
- 语法:
ADD 目标, 源 - 功能:将源加到目标,结果存储在目标中。
- 示例:
ADD AX, BX(AX = AX + BX)。
- 语法:
- SUB(减法):
- 语法:
SUB 目标, 源 - 功能:从目标中减去源。
- 示例:
SUB CX, 10(CX = CX - 10)。
- 语法:
- INC(递增):
- 语法:
INC 目标 - 功能:将目标加 1。
- 示例:
INC BX(BX = BX + 1)。
- 语法:
- DEC(递减):
- 语法:
DEC 目标 - 功能:将目标减 1。
- 示例:
DEC CX(CX = CX - 1)。
- 语法:
- MUL(无符号乘法):
- 语法:
MUL 源 - 功能:将 AL(8 位)或 AX(16 位)与源相乘,结果存储在 AX 或 DX:AX 中。
- 示例:
MUL BX(DX:AX = AX * BX)。
- 语法:
- DIV(无符号除法):
- 语法:
DIV 源 - 功能:将 AX(8 位)或 DX:AX(16 位)除以源,商存储在 AL/AX 中,余数存储在 AH/DX 中。
- 示例:
DIV BX(AX = DX:AX / BX,DX = 余数)。
- 语法:
- ADC(带进位加法) 和 SBB(带借位减法):
- 功能:使用进位标志处理多字算术运算。
- 示例:
ADC AX, BX(AX = AX + BX + CF)。
c. 逻辑指令
这些指令执行位操作并处理二进制数据。
- AND(按位与):
- 语法:
AND 目标, 源 - 功能:执行按位与操作,结果存储在目标中。
- 示例:
AND AX, 0FFh(清除 AX 的高字节)。
- 语法:
- OR(按位或):
- 语法:
OR 目标, 源 - 功能:执行按位或操作。
- 示例:
OR BX, 1000h(设置 BX 的第 12 位)。
- 语法:
- XOR(按位异或):
- 语法:
XOR 目标, 源 - 功能:执行按位异或操作。
- 示例:
XOR AX, AX(将 AX 清零)。
- 语法:
- NOT(按位取反):
- 语法:
NOT 目标 - 功能:反转目标中的所有位。
- 示例:
NOT BX(BX = ~BX)。
- 语法:
- SHL/SHR(逻辑左移/右移):
- 语法:
SHL 目标, 计数;SHR 目标, 计数 - 功能:将位左移/右移,用 0(SHR)或符号位(SAL/SAR)填充。
- 示例:
SHL AX, 1(AX = AX * 2)。
- 语法:
- ROL/ROR(循环左移/右移):
- 功能:循环移动位,通过进位标志绕回。
- 示例:
ROL BX, 1(将 BX 循环左移 1 位)。
d. 控制流指令
这些指令改变程序的执行顺序,实现跳转、循环和子程序。
- JMP(跳转):
- 语法:
JMP 标号 - 功能:无条件跳转到标号处。
- 示例:
JMP start(跳转到标号start)。 - 变体:
- 短跳转(±127 字节)。
- 近跳转(段内)。
- 远跳转(不同段)。
- 语法:
- 条件跳转:
- 语法:
Jcc 标号(例如 JZ、JNZ、JC、JNC) - 功能:根据标志位状态跳转。
- 示例:
JZ loop_end(如果零标志置位则跳转)。JC error(如果进位标志置位则跳转)。- 常见条件:JZ(为零)、JNZ(非零)、JS(符号)、JO(溢出)。
- 语法:
- LOOP(循环):
- 语法:
LOOP 标号 - 功能:CX 减 1,如果 CX ≠ 0 则跳转到标号。
- 示例:
LOOP process(重复直到 CX = 0)。 - 变体:
LOOPE/LOOPZ:如果 CX ≠ 0 且 ZF = 1 则循环。LOOPNE/LOOPNZ:如果 CX ≠ 0 且 ZF = 0 则循环。
- 语法:
- CALL(调用子程序):
- 语法:
CALL 标号 - 功能:将返回地址压入堆栈,跳转到子程序。
- 示例:
CALL compute_sum(调用子程序)。
- 语法:
- RET(返回):
- 语法:
RET - 功能:从堆栈弹出返回地址,恢复执行。
- 示例:
RET(从子程序返回)。
- 语法:
- INT(中断):
- 语法:
INT 编号 - 功能:触发软件中断,调用中断服务程序(ISR)。
- 示例:
INT 21h(DOS 系统调用)。
- 语法:
- IRET(中断返回):
- 功能:从中断服务程序返回,恢复标志位和返回地址。
2. 汇编语言程序设计
汇编语言程序以人类可读的指令编写,然后被汇编成机器代码。8086 使用 分段内存模型,代码段、数据段和堆栈段需显式定义。
a. 程序结构
典型的 8086 汇编程序包括:
- 伪指令:给汇编器的指令(例如 NASM、MASM)。
SEGMENT:定义代码段、数据段或堆栈段。ORG:设置起始地址。DB/DW:定义字节/字数据。
- 指令:CPU 操作(例如 MOV、ADD)。
- 标号:标记跳转或数据的位置。
- 注释:解释代码(例如
; 注释)。
程序结构示例(MASM 语法):
.model small
.stack 100h
.data
message db 'Hello, World!$'
.code
main proc
mov ax, @data ; 初始化 DS
mov ds, ax
mov dx, offset message ; 加载消息地址
mov ah, 09h ; DOS 打印字符串功能
int 21h ; 调用 DOS 中断
mov ah, 4Ch ; 退出程序
int 21h
main endp
end main
b. 顺序结构
顺序代码按顺序执行指令,没有跳转或循环。
示例:两个数相加
mov ax, 5 ; AX = 5
mov bx, 10 ; BX = 10
add ax, bx ; AX = AX + BX (15)
mov [result], ax ; 将结果存储到内存
- 指令一个接一个地执行。
- 常用于简单计算或数据初始化。
c. 分支结构
分支使用条件/无条件跳转,根据条件改变程序流程。
示例:比较和分支
mov ax, 10 ; AX = 10
cmp ax, 15 ; 比较 AX 和 15
je equal ; 如果 AX == 15 则跳转
mov bx, 1 ; 否则,BX = 1
jmp done
equal:
mov bx, 0 ; 如果相等,BX = 0
done:
; 继续程序
- CMP:根据减法(AX - 15)设置标志位。
- JE:如果 ZF = 1(相等)则跳转。
- 适用于 if-then-else 逻辑。
d. 循环结构
循环重复执行指令直到满足条件,通常使用 LOOP 或条件跳转。
示例:求 1 到 10 的和
mov cx, 10 ; 循环计数器 = 10
mov ax, 0 ; 和 = 0
sum_loop:
add ax, cx ; 将 CX 加到和
loop sum_loop ; CX 减 1,如果 CX ≠ 0 则循环
; AX = 55 (1 + 2 + ... + 10)
LOOP简化了基于计数器的迭代。- 替代方案:使用
CMP和JNZ实现自定义条件。
带条件循环的示例
mov ax, 0 ; 计数器
mov bx, 100 ; 限制
count_up:
inc ax ; AX++
cmp ax, bx ; 与 100 比较
jle count_up ; 如果 AX <= 100 则跳转
- 适用于非基于计数器的循环。
e. 子程序
子程序通过 CALL 和 RET 模块化代码,实现代码复用。
示例:计算数字的平方
main:
mov ax, 4 ; 输入
call square ; 调用子程序
; AX = 16
jmp exit
square:
push bx ; 保存 BX
mov bx, ax ; 复制 AX
mul bx ; AX = AX * BX
pop bx ; 恢复 BX
ret ; 返回
exit:
; 结束程序
- PUSH/POP:保存/恢复寄存器以避免副作用。
- 堆栈自动管理返回地址。
3. 中断服务程序(ISR)
中断允许 CPU 响应外部或内部事件(例如键盘输入、定时器滴答),暂停当前程序并执行 ISR。
中断机制
- 中断向量表(IVT):
- 位于内存 0000:0000h–0000:03FFh。
- 存储 256 种中断类型(0–255)的 ISR 地址。
- 每个条目:段地址:偏移地址(4 字节)。
- 类型:
- 硬件中断:由设备触发(例如 IRQ)。
- 软件中断:由
INT指令触发(例如 INT 21h 用于 DOS)。 - 异常:CPU 错误(例如除零)。
- 过程:
- 发生中断。
- CPU 将标志位、CS 和 IP 保存到堆栈。
- 通过 IVT 跳转到 ISR。
- ISR 执行,以
IRET结束以恢复状态。
编写 ISR
ISR 必须:
- 保存寄存器(PUSH/POP)。
- 快速处理中断。
- 以
IRET结束。
示例:自定义定时器 ISR
.data
old_vec dw 2 dup(0) ; 存储旧的中断向量
.code
install_isr:
cli ; 禁用中断
mov ax, 0
mov es, ax ; ES = 0(IVT 段)
mov bx, 1Ch*4 ; 定时器中断 (1Ch)
mov ax, es:[bx] ; 保存旧向量
mov old_vec, ax
mov ax, es:[bx+2]
mov old_vec+2, ax
mov ax, offset my_isr ; 设置新向量
mov es:[bx], ax
mov ax, cs
mov es:[bx+2], ax
sti ; 启用中断
ret
my_isr:
push ax
inc word ptr [counter] ; 递增计数器
pop ax
iret ; 从中断返回
- 挂钩定时器中断(1Ch,约 18.2 Hz)。
- 递增计数器变量。
- 保存寄存器并使用
IRET。
示例:DOS 中断(INT 21h)
mov ah, 09h ; 打印字符串功能
mov dx, offset msg ; 以 '$' 结尾的字符串地址
int 21h ; 调用 DOS
- INT 21h 提供操作系统服务(例如 I/O、文件处理)。
- AH 指定功能代码。
实践注意事项
- 保存状态:ISR 必须保存所有寄存器,以免破坏主程序。
- 优先级:硬件中断可能抢占其他中断(由 PIC 管理)。
- 调试:使用 DEBUG.COM 等工具或现代模拟器(例如 DOSBox、Bochs)。
示例程序:阶乘计算
此程序使用循环和子程序计算一个数的阶乘(例如 5! = 120)。
.model small
.stack 100h
.data
num dw 5 ; 输入数字
result dw ? ; 存储结果
.code
main proc
mov ax, @data
mov ds, ax ; 初始化 DS
mov ax, num ; 加载数字
call factorial ; 计算阶乘
mov result, ax ; 存储结果
mov ah, 4Ch ; 退出
int 21h
main endp
factorial proc
push bx
mov bx, ax ; BX = n
mov ax, 1 ; AX = 结果
fact_loop:
cmp bx, 1
jle done ; 如果 BX <= 1,退出
mul bx ; AX = AX * BX
dec bx ; BX--
jmp fact_loop
done:
pop bx
ret
factorial endp
end main
- 逻辑:
- 输入:num = 5。
- 循环:AX = AX * BX,BX– 直到 BX = 1。
- 结果:AX = 5 * 4 * 3 * 2 * 1 = 120。
- 特点:
- 使用子程序实现模块化。
- 使用堆栈保存寄存器。
- 包含顺序和循环结构。
最佳实践
- 注释代码:汇编语言晦涩难懂,需解释每一步。
- 最小化寄存器使用:避免不必要的覆盖。
- 增量测试:使用调试器跟踪执行。
- 处理边界情况:检查溢出、零或负输入。
- 优化:减少指令数(例如,使用 XOR AX, AX 代替 MOV AX, 0)。
总结
- 8086 指令集:
- 数据传送:MOV、PUSH、POP 用于移动数据。
- 算术:ADD、SUB、MUL 用于计算。
- 逻辑:AND、OR、XOR 用于位操作。
- 控制流:JMP、LOOP、CALL 用于程序流程控制。
- 程序设计结构:
- 顺序:线性执行简单任务。
- 分支:条件跳转用于决策。
- 循环:LOOP 或 Jcc 用于重复。
- 子程序:CALL/RET 用于模块化代码。
- 中断:
- ISR 通过 IVT 处理事件。
- 保存状态,以 IRET 结束。
- 用于硬件(例如定时器)和软件(例如 DOS)中断。
练习题
- 编写一个程序,反转内存中的字符串。
- 实现一个子程序,检查一个数是否为质数。
- 为键盘中断(INT 09h)创建一个 ISR,用于统计按键次数。
- 解释
JMP和CALL的区别。 - 优化这段代码:
MOV AX, 0; MOV BX, AX; ADD BX, 5。
本教程涵盖了 8086 汇编程序设计的基础知识,通过示例和解释帮助您构建实践技能。如果您想探索特定指令、高级技术(例如字符串操作)或模拟工具,请告诉我!