引言:汇编语言实验的重要性与准备工作

汇编语言是计算机科学中连接高级语言与机器硬件的桥梁,它直接操作CPU寄存器和内存地址,是理解计算机底层运行机制的关键。IBM PC架构作为经典的x86体系结构代表,其汇编语言程序设计实验是计算机组成原理和系统编程课程的核心实践环节。本实验旨在通过实际编程操作,帮助学习者掌握DOS环境下的汇编程序开发流程、调试技巧以及基本的输入输出控制方法。

进行IBM PC汇编语言实验前,需要准备以下环境和工具:

  1. 模拟器或虚拟机:推荐使用DOSBox(跨平台)或QEMU(Windows/Linux)来模拟IBM PC的DOS环境
  2. 汇编器:MASM(Microsoft Macro Assembler)或TASM(Turbo Assembler)是IBM PC汇编的标准工具
  3. 调试器:DEBUG.EXE(DOS自带)或TD(Turbo Debugger)用于单步执行和寄存器查看
  4. 文本编辑器:任何纯文本编辑器(如Notepad++、VS Code等)用于编写.asm源文件

实验的基本流程包括:编写源代码 → 汇编(生成.obj文件) → 链接(生成.exe文件) → 调试运行。理解这个流程对于后续的实验至关重要,因为每个步骤都可能产生不同的错误信息,需要学习者具备识别和解决这些问题的能力。

实验目标与核心知识点

本实验的核心目标是掌握以下关键技能:

  1. DOS功能调用(INT 21h):学习使用DOS中断进行输入输出操作,包括字符输入(AH=01h)、字符串输出(AH=09h)等
  2. 寄存器操作:理解AX、BX、CX、DX等通用寄存器的用途,以及段寄存器(CS、DS、ES、SS)和指令指针(IP)的作用
  3. 基本指令集:掌握MOV(数据传送)、ADD(加法)、SUB(减法)、CMP(比较)、JMP(跳转)等核心指令
  4. 程序结构:理解.COM文件与.EXE文件的区别,掌握基本的程序框架(如定义数据段、代码段)
  5. 调试技巧:学会使用DEBUG或TD查看寄存器状态、内存内容和单步执行程序

这些知识点构成了IBM PC汇编语言编程的基础,后续的复杂程序设计都建立在这些基础之上。

实验环境搭建详细步骤

1. DOSBox安装与配置

DOSBox是目前最流行的DOS模拟器,支持Windows、macOS和Linux。

Windows安装步骤

  • 访问DOSBox官网下载最新版本
  • 运行安装程序,按默认路径安装(如C:\DOSBox)
  • 创建一个工作目录(如C:\DOSWork),用于存放源代码和可执行文件

配置自动挂载: 编辑DOSBox的配置文件(通常位于C:\Users\用户名\AppData\Local\DOSBox\dosbox-[版本号].conf),在末尾添加:

[autoexec]
# Lines in this section will be run every time DOSBox starts
mount c c:\DOSWork
c:

这样每次启动DOSBox时会自动挂载工作目录为C盘。

2. 汇编器安装

MASM安装

  • 下载MASM 6.15(或5.10)压缩包
  • 解压到工作目录(如C:\DOSWork\MASM)
  • 确保包含以下关键文件:MASM.EXE、LINK.EXE、EXE2BIN.EXE、DEBUG.EXE

TASM安装

  • Borland Turbo Assembler是另一个选择,功能类似
  • 解压到工作目录即可使用

3. 验证环境

在DOSBox中输入以下命令验证环境:

# 挂载目录(如果未配置autoexec)
mount c c:\DOSWork
c:

# 切换到MASM目录
cd MASM

# 检查文件
dir
# 应看到MASM.EXE、LINK.EXE等文件

# 测试汇编器
masm /?
# 应显示帮助信息

实验一:基础输入输出程序

实验内容

编写一个程序,实现以下功能:

  1. 提示用户输入一个字符
  2. 接收用户输入
  3. 显示用户输入的字符
  4. 程序结束

源代码实现

创建文件char_io.asm,内容如下:

; char_io.asm - 基础字符输入输出程序
; 功能:接收用户输入的一个字符并回显

STACK SEGMENT STACK
    DB 256 DUP(0)        ; 定义256字节的堆栈空间
STACK ENDS

DATA SEGMENT
    PROMPT DB 'Please input a character: $'  ; 提示信息,以$结尾
    OUTPUT DB 0Dh, 0Ah, 'You input: $'       ; 输出信息,0Dh=回车,0Ah=换行
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA, SS:STACK

START:
    ; 初始化数据段寄存器
    MOV AX, DATA
    MOV DS, AX
    
    ; 显示提示信息
    LEA DX, PROMPT      ; 加载提示信息地址到DX
    MOV AH, 09h         ; DOS功能调用:显示字符串
    INT 21h             ; 执行DOS中断
    
    ; 接收用户输入(带回显)
    MOV AH, 01h         ; DOS功能调用:键盘输入并回显
    INT 21h             ; AL中保存输入的字符
    
    ; 保存输入字符(可选)
    MOV BL, AL          ; 将输入字符暂存到BL
    
    ; 显示输出信息
    LEA DX, OUTPUT
    MOV AH, 09h
    INT 21h
    
    ; 显示输入的字符
    MOV DL, BL          ; 将字符送到DL
    MOV AH, 02h         ; DOS功能调用:显示字符
    INT 21h
    
    ; 程序结束
    MOV AX, 4C00h       ; DOS功能调用:程序终止
    INT 21h

CODE ENDS
END START

代码详细解析

1. 段定义

STACK SEGMENT STACK
    DB 256 DUP(0)        ; 定义256字节的堆栈空间
STACK ENDS
  • STACK SEGMENT STACK:定义堆栈段,第二个STACK告诉链接器这是程序的初始堆栈
  • DB 256 DUP(0):定义256个字节的堆栈空间,初始化为0

2. 数据段

DATA SEGMENT
    PROMPT DB 'Please input a character: $'  ; 提示信息,以$结尾
    OUTPUT DB 0Dh, 0Ah, 'You input: $'       ; 0Dh=回车,0Ah=换行
DATA ENDS
  • DB:定义字节数据
  • $:DOS字符串显示功能的结束标志
  • 0Dh, 0Ah:回车换行的ASCII码

3. 代码段

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA, SS:STACK
  • ASSUME:告诉汇编器哪个段寄存器对应哪个逻辑段,但不初始化寄存器

4. 初始化DS寄存器

MOV AX, DATA
MOV DS, AX
  • 由于不能直接向段寄存器赋值,需要通过AX中转
  • 这是每个汇编程序都必须做的第一步

5. DOS功能调用详解

显示字符串(AH=09h)

LEA DX, PROMPT      ; 加载字符串偏移地址到DX
MOV AH, 09h         ; 功能号
INT 21h             ; 调用DOS
  • 要求:DS:DX指向以’$‘结尾的字符串
  • 注意:LEA是Load Effective Address指令,获取偏移地址

单字符输入(AH=01h)

MOV AH, 01h
INT 21h
  • 功能:等待键盘输入,将字符存入AL,同时回显到屏幕
  • 返回值:AL = 输入字符的ASCII码

单字符输出(AH=02h)

MOV DL, 要显示的字符
MOV AH, 02h
INT 21h
  • 要求:DL = 要显示的字符

程序终止(AH=4Ch)

MOV AX, 4C00h       ; 4C是功能号,00是返回码
INT 21h
  • 返回码可以通过echo %ERRORLEVEL%在批处理中查看

汇编与链接过程

1. 汇编(生成.obj文件)

在DOSBox中执行:

masm char_io.asm;
  • 分号;表示接受所有默认选项(不询问目标文件、列表文件、交叉引用文件名)
  • 成功后会生成char_io.obj

常见错误

  • A2006: Undefined symbol:通常是因为忘记初始化DS或ASSUME写错
  • A2009: Syntax error:检查标点符号是否使用英文符号

2. 链接(生成.exe文件)

link char_io.obj;
  • 同样使用分号接受默认选项
  • 成功后生成char_io.exe

3. 运行

char_io.exe

程序将提示输入字符,输入后显示结果。

使用DEBUG调试程序

1. 启动DEBUG

debug char_io.exe

2. 常用DEBUG命令

查看寄存器(r命令)

-r
AX=0000  BX=0000  CX=0000  DX=0000  SP=0100  BP=0000  SI=0000  DI=0000
DS=0B8A  ES=0B8A  SS=0B8A  CS=0B8A  IP=0000   NV UP EI PL NZ NA PO NC
0B8A:0000 B8????????    MOV AX, ??????
  • CS:IP指向将要执行的下一条指令
  • SP指向栈顶

反汇编(u命令)

-u 0 20
0B8A:0000 B8A000        MOV AX, 00A0
0B8A:0003 8ED8          MOV DS, AX
0B8A:0005 BA0000        MOV DX, 0000
0B8A:0008 B409          MOV AH, 09
0B8A:000A CD21          INT 21
...
  • 显示内存中的机器码和对应的汇编指令

单步执行(t命令)

-t
AX=00A0  BX=0000  CX=0000  DX=0000  SP=0100  BP=0000  SI=0000  DI=0000
DS=0B8A  ES=0B8A  SS=0B8A  CS=0B8A  IP=0003   NV UP EI PL NZ NA PO NC
0B8A:0003 8ED8          MOV DS, AX
  • 每次执行一条指令,可观察寄存器变化

设置断点(g命令)

-g 0008
  • 执行到地址0008处暂停

查看内存(d命令)

-d ds:0
0B8A:0000  50 6C 65 61 73 65 20 69-6E 70 75 74 20 61 20 63   Please input a c
0B8A:0010  68 61 72 3A 20 24 0D 0A-59 6F 75 20 69 6E 70 75   har: $..You inpu
0B8A:0020  74 3A 20 24                                       t: $
  • 显示DS段的内存内容

修改内存(e命令)

-e ds:00 48 65 6C 6C 6F
  • 将从ds:00开始的内存修改为”Hello”的ASCII码

写入文件(w命令)

-w
  • 将修改后的程序写回文件(调试时修改内存后保存)

实验二:数字加法程序

为了巩固知识,我们再实现一个数字加法程序。

实验内容

从键盘接收两个一位数字(0-9),计算它们的和并显示结果(假设和不超过9)。

源代码

; add.asm - 数字加法程序

STACK SEGMENT STACK
    DB 256 DUP(0)
STACK ENDS

DATA SEGMENT
    PROMPT1 DB 'Input first digit (0-9): $'
    PROMPT2 DB 0Dh, 0Ah, 'Input second digit (0-9): $'
    RESULT  DB 0Dh, 0Ah, 'Sum is: $'
    SUM     DB ?          ; 存储和
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA, SS:STACK

START:
    MOV AX, DATA
    MOV DS, AX
    
    ; 第一个数字输入
    LEA DX, PROMPT1
    MOV AH, 09h
    INT 21h
    
    MOV AH, 01h         ; 输入字符
    INT 21h
    SUB AL, '0'         ; 将ASCII码转换为数字
    MOV BL, AL          ; 保存第一个数字
    
    ; 第二个数字输入
    LEA DX, PROMPT2
    MOV AH, 09h
    INT 21h
    
    MOV AH, 01h
    INT 21h
    SUB AL, '0'         ; 转换为数字
    
    ; 计算和
    ADD AL, BL          ; AL = AL + BL
    ADD AL, '0'         ; 转换为ASCII码
    MOV SUM, AL
    
    ; 显示结果
    LEA DX, RESULT
    MOV AH, 09h
    INT 21h
    
    MOV DL, SUM
    MOV AH, 02h
    INT 21h
    
    ; 结束
    MOV AX, 4C00h
    INT 21h

CODE ENDS
END START

关键指令解析

SUB AL, ‘0’

  • 将输入的ASCII字符(如’5’的ASCII是35h)转换为数字5
  • ‘5’ - ‘0’ = 35h - 30h = 05h

ADD AL, BL

  • 执行加法运算
  • 注意:如果和超过9,结果会不正确(需要处理进位)

ADD AL, ‘0’

  • 将数字转换回ASCII码以便显示

实验三:字符串比较程序

实验内容

比较两个字符串是否相等,显示比较结果。

源代码

; compare.asm - 字符串比较

STACK SEGMENT STACK
    DB 256 DUP(0)
STACK ENDS

DATA SEGMENT
    STR1 DB 'HELLO$'
    STR2 DB 'HELLO$'
    STR3 DB 'WORLD$'
    MSG_EQ DB 0Dh, 0Ah, 'Strings are equal$'
    MSG_NE DB 0Dh, 0Ah, 'Strings are NOT equal$'
DATA ENDS

CODE SEGMENT
    ASSUME CS:CODE, DS:DATA, SS:STACK

START:
    MOV AX, DATA
    MOV DS, AX
    
    ; 比较STR1和STR2
    LEA SI, STR1
    LEA DI, STR2
    CALL COMPARE
    CALL DISPLAY_RESULT
    
    ; 比较STR1和STR3
    LEA SI, STR1
    LEA DI, STR3
    CALL COMPARE
    CALL DISPLAY_RESULT
    
    MOV AX, 4C00h
    INT 21h

; 子程序:比较DS:SI和DS:DI指向的字符串
COMPARE PROC
    PUSH AX
    PUSH BX
    PUSH CX
    
COMPARE_LOOP:
    MOV AL, [SI]        ; 取STR1的字符
    MOV BL, [DI]        ; 取STR2的字符
    CMP AL, BL          ; 比较字符
    JNE NOT_EQUAL       ; 不相等则跳转
    
    CMP AL, '$'         ; 检查是否到字符串结尾
    JE EQUAL            ; 如果是'$'且前面都相等,则字符串相等
    
    INC SI              ; 指向下一个字符
    INC DI
    JMP COMPARE_LOOP
    
NOT_EQUAL:
    MOV CX, 0           ; CX=0表示不相等
    JMP COMPARE_EXIT
    
EQUAL:
    MOV CX, 1           ; CX=1表示相等
    
COMPARE_EXIT:
    POP CX
    POP BX
    POP AX
    RET
COMPARE ENDP

; 子程序:根据CX值显示结果
DISPLAY_RESULT PROC
    PUSH DX
    PUSH AX
    
    CMP CX, 1
    JE SHOW_EQ
    
    LEA DX, MSG_NE
    JMP SHOW
    
SHOW_EQ:
    LEA DX, MSG_EQ
    
SHOW:
    MOV AH, 09h
    INT 21h
    
    POP AX
    POP DX
    RET
DISPLAY_RESULT ENDP

CODE ENDS
END START

关键知识点

子程序(PROC)

  • 使用PROCENDP定义子程序
  • CALL指令调用子程序
  • RET返回主程序

寄存器保护

  • 在子程序开头用PUSH保存寄存器
  • 结束时用POP恢复,避免影响主程序

字符串比较逻辑

  • 逐个字符比较,直到遇到’$’
  • 使用JNE(Jump if Not Equal)条件跳转

实验常见问题与解决方案

1. 汇编错误

错误:error A2006: Undefined symbol

  • 原因:使用了未定义的标号或忘记初始化DS
  • 解决:检查所有标号是否正确定义,确保有MOV AX, DATA / MOV DS, AX

错误:error A2009: Syntax error

  • 原因:指令格式错误,如操作数顺序错误
  • 解决:检查指令格式,如MOV指令目标操作数在左,源操作数在右

2. 链接错误

错误:error L2002: segment start address 0000 not paragraph aligned

  • 原因:段地址未对齐到16字节边界
  • 解决:确保段定义正确,通常不需要手动调整

3. 运行时错误

程序崩溃或显示乱码

  • 原因:DS未正确初始化,或字符串未以’$‘结尾
  • 解决:使用DEBUG单步执行,检查DS值和字符串内容

输入输出不正常

  • 原因:DOS功能调用参数错误
  • 解决:查阅DOS功能调用手册,确认AH值和寄存器设置

4. 调试技巧

使用TD(Turbo Debugger)

  • 启动:td char_io.exe
  • F7:单步步入(进入子程序)
  • F8:单步跳过(不进入子程序)
  • Ctrl+F7:设置断点
  • 查看寄存器、内存、堆栈状态

使用DEBUG

  • r:查看/修改寄存器
  • t:单步执行
  • g:运行到断点
  • d:查看内存
  • u:反汇编

实验总结与扩展

通过本实验,我们掌握了:

  1. IBM PC汇编语言的基本程序结构(段定义、初始化)
  2. DOS功能调用的使用方法(输入输出、程序终止)
  3. 基本指令的应用(MOV、ADD、SUB、CMP、JMP)
  4. 子程序设计和寄存器保护
  5. 使用DEBUG进行程序调试

扩展练习建议

  1. 扩展加法程序:处理两位数加法(如15+26),显示正确结果
  2. 字符串输入:使用DOS功能调用0Ah接收用户输入的字符串
  3. 循环程序:计算1到100的和,使用LOOP指令
  4. 条件判断:根据输入数字显示不同信息(如大于5、小于5、等于5)
  5. 文件操作:使用DOS功能调用创建、读取、写入文件

进一步学习资源

  • 书籍:《汇编语言(王爽)》、《IBM PC汇编语言程序设计》
  • 在线资源:x86指令集参考、DOS功能调用大全
  • 实践项目:实现简单的计算器、文本编辑器、游戏(如猜数字)

汇编语言学习需要大量实践,建议从简单程序开始,逐步增加复杂度,同时结合DEBUG工具深入理解程序执行过程。掌握汇编语言将为理解操作系统、编译器、逆向工程等领域打下坚实基础。