微机原理与接口技术: 4 - 汇编语言程序设计

汇编语言程序设计

Goal:

  1. 了解汇编语言源程序的结构.
  2. 深入理解伪指令系统.
  3. 深入理解DOS功能调用
  4. 掌握汇编语言源程序的设计方法.

4.1 汇编语言源程序

4.1.1 汇编语言源程序的结构

一个完整的汇编语言源程序由若干个逻辑段(Logic Segment)组成, 包括DS, CS, SS, ES.

  • 每个逻辑段以 SEGMENT语句开始
  • 以ENDS语句结束
  • 整个源程序用END语句结尾

下面给出一个完整的ASM程序的结构框架:

请添加图片描述

下面以一个具体的例子来说明一个完整ASM语言的结构.

E.g. 编写一个两个字相加的程序:

请添加图片描述

4.1.2 ASM语句类型及格式

可分为两大类: 指令性语句指示性语句.

指令性语句 - Instruction Statement : 由指令助记符(Ch.3学到的哪些)等组成的 可被CPU执行的语句.

一般格式:

[标号:] [前缀] 助记符 [操作数[,操作数]][;注释]
[label:] [prefix] mnemonic [operands],[operands] [;comment]

指示性语句 - Directive Statement : 用于告诉asm程序如何对程序进行汇编, 是CPU不执行的指令. 又称 伪操作语句 或 伪指令.

一般格式:

[名字] 伪操作 操作数 [,操作数,...] [;注释]
[name] mnemonic(directive) operands [,operands,…] [;comment]

其中+[]的是可选项.

对于一个语句Statement的构成要素(constituent elements) :

  1. 标号 - Label : 标号表示指令的符号地址, 要有:
  2. 名字 - Name :通常表示 变量名, 段名, 和 过程 名, 不要有:
  3. 助记符 - Mnemonic Instruction :
  4. 操作数 - Operand :
    • 指令性语句 的操作数 : 0, 1, 2
    • 指示性语句 的操作数 : 不限
      操作数以 “,” 隔开.
  5. 注释 - Comment : 从;开始, 注释不参加程序汇编, 不生成目标程序, 只是为程序员阅读程序提供方便.

4.1.3 数据项及表达式

1 - 常量 - Constant

包括 数字常量 和 字符串常量

2 - 标号 - label

标号具有3种属性 : 段值, 偏移量 和 类型.

  1. 段值 : 标号所在段的短地址, 当程序种引用的一个标号时, 该标号应在代码段CS.

  2. 偏移量 : 标号所在段的段首 to 定义该表好的地址之间的字节数(EA) -> 16bits unsigned.

  3. 类型 : 两种, NEAR 和 FAR, NEAR 为 近标号, 只能在段内被引用, 地址指针为2个字节; FAR 为远标号, 可在其他段被引用, 地址指针为 4 个字节.

3 - 变量 Variable

与标号同样具有3种属性 : 段值, 偏移量 和 类型

变量的类型 : BYTE(字节), WORD(字), DWORD(双字), QWORD(4字), TBYTE(十字节).

变量是存储器种某个数据区的名字.

使用时 :

  1. 变量类型 与 指令的要求必须相符.
  2. 在定义变量时, 变量名对应的时数据区的首地址, 例如:

请添加图片描述

4 - 表达式 - Expression

Expression 本身不是指令, 本身不能执行. 在我们的程序进行汇编时, 汇编程序对表达式进行相应的运算, 得出一个确定的值.

算术运算符 +,-,*,/和MOD

表达式汇编结果就是一个数值, 具体比如说 :

请添加图片描述

E.g.

请添加图片描述

逻辑运算符 AND, OR, NOT, XOR

逻辑运算符只用于数值表达式, 用来对数值进行按位逻辑运算, 并得到一个数值结果.

请添加图片描述

关系运算符 EQ(=), NE(!=), LT(<), GT(>), LE(<=), GE(>=)

参与关系匀速那的必须为2个数值或同一段 中的两个存储单元地址.

运算结果为一个逻辑值 :

  1. 关系为真 -> 0
  2. 关系为假 -> 0FFFFH
取值运算符 OFFSET, SEG & 属性运算符 PTR
  1. OFFSET : 获取 一个标号Label/变量Var偏移地址.

    MOV SI, OFFSET DATA1 ;将 变量DATA1 的EA送SI

这条指令等价于

LEA     SI, DATA1           ;取 DATA1 的EA送SI
  1. SEG : 得到 一个标号Label/变量Var段地址.

    MOV AX, SEG ;将 变量DATA的短地址送AX
    MOV DS, AAX ;DS <- AX

  2. PTR : 指定 位于其后的 存储器操作数 的类型

    CALL DWORD PTR[BX] ;说明存储器操作数为4个字节长
    MOV AL, PTR[SI] ;将 SI 指向的一个字节数送 AL

    如果变量已经定义为了字变量, 可以用PTR修改属性.

    MOV AL, VAR ;指令非法, VAR为WORD, AL为Byte, 两操作数不等长.
    MOV AL, BYTE PTR VAR ;指令合法,
    BYTE PTR VAR强制将VAR变为字节操作数.

4.2 伪指令

提供非机器指令的功能,用于定义变量、段、存储器等。

常用伪指令 : ASSUME、DB(定义字节), DW(定义字)等.

4.2.1 数据定义伪指令

1 - 格式
[变量名] 伪操作 操作数 [,操作数 ...]

常用 的 伪操作指令 如下:

DB(Define Byte) : 定义变量为字节类型。DB伪指令也常用来定义字符串

DW(Define Word) : 定义变量为字类型。DW 伪指令后面的每个操作数都占用2个字节。在内存中存放时,低字节在低地址,高字节在高地址。

DD(Define Double Word) : 用来定义双字类型的变量。DD 伪指令后面的每个操作数都占用4个字节。在内存中存放时,同样是低字节在低地址,高字节在高地址。

DQ(Define Quad Word) : 定义四字(QWORD,8个字节)类型的变量。在内存中存放时,低字节在低地址,高字节在高地址。

(5)(TBYTE)类型的变量。DT伪操作后面的每个操作数都为10个字节的压缩BCD数。

请添加图片描述

2 - 操作数

可为常数, 表达式 or 字符串.

一个数据定义伪指令可以定义多分数据元素, 但 每个数据元素的值 不能超过 由伪操作 所定义的数据类型限定的范围.

E.g.

请添加图片描述

3 - 重复操作符 DUP

格式 :

[变量名]    数据定义伪操作 n DUP(,初值 ...)
  • 圆括号中 -> 重复的内容
  • n -> 重复次数

E.g.

DATA1 DB 20 DUP(?)
DATA3 DB 20 DUP(30H)

重复操作符 主要用于需要预留存储区域且不关心初始值 的场合.

4.2.2 符号定义伪指令 - EQU

给一条表达式赋予一个名字.

格式 :

名字 EQU 表达式

请添加图片描述

E.g. 下面的表达式左右等价.

CR      EQU     0DH
TEN     EQU     0AH
VAR     EQU     TEN*2+1024
ADR     EQU     ES:[BP+DI+5]

用EQU可以用一个名字定义一个数值 or 用一个名字定义另一个名字. 但不能重复定义

=> 若要重复定义, 用"="伪指令. E.g.

FACTOR = 10H    ;FACTOR 代表了数值 10H
...
FACTOR = 25H    ;从现在开始, FACTOR 代表 25H

4.2.3 段定义伪指令 - SEGMENT

段名 SEGMENT [定位类型] [组合类型] ['类别']
.
.
.
段名 ENDS

源程序中 每个逻辑段由 SEGMENT语句开始,到ENDS语句结束. 对于DS, ES, SS来说, 段体一般为变量, 符号定义等伪指令; 对CS则存放代码.

1 - 定位类型 - Align

告诉汇编程序如何确定逻辑段的地址边界.

  1. PARA - Paragraph : 逻辑段从一个节(16bits)的边界开始. -> 段起始物理地址为 XXXX0H.

  2. BYTE : 从字节边界开始.

  3. WORD : 从字边界开始. -> 段起始物理地址为 偶数

  4. PAGE : 从页边界开始. 256字节为一页. -> 段起始物理地址为 XXXX00H.

2 - 组合类型 - Combine

用在具有多个模块的程序中. 告诉汇编程序, 当一个逻辑段装入存储器时 它与其他段如何进行组合.

  1. NONE(default) : 本段与其他段不组合 -> 即对不同程序模块, 对不同程序模块中的逻辑段, 即使具有相同的段名, 也分别作为不同的逻辑段装入内存.

  2. PUBLIC : 不同程序模块中用PUBLIC说明的同名逻辑段, 汇编时会将其组合构成一个大的逻辑段.

  3. STACK : 含义与 PUBLIC 基本一致, 但仅作为堆栈的逻辑段使用 -> 在汇编时 将不同的逻辑段集中在一个大的堆栈段, 由各模块共享.

  4. COMMON : 连接时从同一个地址开始装入 -> 各个逻辑段重叠在一起.

  5. MEMORY : 当几个逻辑段连接时, 本逻辑段定位在地址最高大的地方. 如果存在多个MEMORY -> 只将首先遇到的段作为MEMORY段, 其余的视作COMMON.

3 - 类别 - Class

以单引号’‘括起来的字符串, e.g. 代码段’CODE’, 堆栈段’STACK’…

作用为 :当几个程序模块连接时, 同CLASS的逻辑段装入连续的内存区内, 按出现的先后顺序排列.

E.g.

请添加图片描述

汇编成功后, 存储器中的分配情况:

请添加图片描述

4.2.4 设定段寄存器伪指令 - ASSUME

用于向ASM程序说明所定义的逻辑段属于何种类型的逻辑段.

ASSUME 段寄存器 : 段名 [,段寄存器 : 段名 [, ...]]

8088的存储器采用分段结构, 每个逻辑段最大为64KB, 可以有多个逻辑段. 但每个程序模块最多只允许有4个逻辑段(CS, DS, SS, ES).

=> ASSUME 用来告诉ASM程序当前正在使用的各段的名字 -> 告诉ASM程序用SEGMENT伪操作定义过的段的段地址将要存放在那个段寄存器中.

E.g.

请添加图片描述

4.2.5 过程定义伪指令 - CALL & RET

过程 <=> 子程序 <=> 函数 <=> 方法

一般格式 :

过程名  PROG    [NEAR/FAR]
        .       ;  ↓
        .       ;→ 过程体
        .       ;  ↑
        RET     ; 返回指令(也算过程体)
过程名  ENDP

过程名实际上是过程入口的符号地址. 过程体内必须要有一条返回指令RET -> 使在程序调用结束后能返回源地址. 过程可以嵌套, 也可以递归.

过程体可以是 :

  • 近地值 - NEAR(default) : 与调用程序在同一个代码段
  • 原地址 - FAR : 在不同的代码块中

E.g. 编写一个10ms延时的子程序

DELAY   PROC            ;定义一个过程
        PUSH    BX      ;保护 BX 原来的内容
        PUSH    CX      ;保护CX原来的内容
        MOV     BL, 2   ;外循环次数, 2
NEXT:   MOV     CX, 4167;内循环次数(实现延时5ms)
W10MS:  LOOP    W10MS   ;CX != 则循环
        DEC     BL      ;修改外循环计数值
        JNZ     NEXT    ;
        RET             ;过程返回
DELAY   PROC            ;过程结束

4.2.6 宏命令伪指令 - MACRO

将程序段定义为一个宏指令, 然后每次需要时 -> 简单用宏指令名来代替 - 宏调用

格式:

宏命令名 MACRO [形式参数, ...]
        (宏定义体)
        ENDM

汇编语言的宏定义 与 C语言基本没差, 下面给个例子以供简单理解

请添加图片描述

宏调用 与 过程调用的区别:

  1. MACRO 由宏汇编程序MASM在汇编过程中进行处理, 在每个MASM都用其对应的宏定义体替换
  2. CALL 与 RET 则是CPU指令, 执行CALL指令时, CPU使程序的控制转移到子程序的入口地址.

4.2.7 模块定义 与 连接伪指令

在编写较大的汇编语言程序时, 通常将其划分为几个独立的源程序(/模块), 分别汇编生成各自的目标程序, 最后将其连接成为一个完整的可执行程序.

1 - NAME

格式 :

NAME 模块名

NAME 伪指令用于给汇编后得到的目标程序一个名字. NAME前不允许加标号, 如下的语句非法:

BEGIN : NAME 模块名
2 - TITLE

格式:

TITLE 标题名

标题名最多允许60个字符.

  • 如果程序中无 NAME -> 汇编程序将TITLE伪指令后面的 “标题名” 中的前6个字符作为模块名.
  • 无 NAME 也无 TITLE -> 将源程序的文件名作为目标程序的模块名.
3 - END

-> 表示源程序停指, 指示汇编程序停止汇编.

END [标号]

只有主模块的END语句允许使用标号.

E.g. 求从TABLE开始的10个unsigned字节数的和, 结果放在SUM字单元中.

DATA    SEGMENT     ;
TABLE   DB 12H, 23H, 34H, 45H, 56H
        DB 67H, 78H, 89H, 9AH, 0FDH
SUM     DW ?
DATA    ENDS
;
CODE    SEGMENT
ASSUME  CS:CODE, DS:DATA, ES:DATA
START:  MOV     AX, DATA
        MOV     DS, AX
        MOV     ES, DATA    ;DS, ES段初始化
        LEA     SI, TABLE   ;
        MOV     CX, 10
        XOR     AX, AX
NEXT:   ADD     AL, [SI]
        ADC     AH, 0
        INC     SI
        LOOP    NEXT
        MOV     SUM, AAX
        HLT
CODE    ENDS
        END START           ;汇编结束, 起始运行地址为START

4.3 BIOS 和 DOS 功能调用 (主要是DOS)

系统软件中提供的调用功能有两种 :

  • 低级调用 - BIOS / Basic Input and output System : 是被固化在计算机主机板上Flash ROM型芯片中的一组程序, 与系统硬件有直接依赖.

  • 高级调用 - DOS / Disk Operation System : 负责管理系统的所有资源, 协调微机的操作, 其中包括大量的可供用户调用的服务程序. DOS功能调用不依赖于具体的硬件系统

在调用 BIOS/DOS的系统服务程序时, 不适用CALL命令, 而是采用软中断指令INT n来实现(又称BIOS or DOS 中断), n 表示 中断类型码 -> 对应不同的中断模块.

4.3.1 BIOS 功能调用 - 考试不涉及, 不看

4.3.2 DOS 功能调用

所有的DOS系统功能调用都是利用 软中断指令 INT 21H 来实现.

可实现的子功能可分为: 设备管理Equipment ,Management, 目录管理Directory Management, 文件管理File Management 和 其他Other.

DOS 系统功能调用的使用方法如下:

  1. AH <- 功能号(Function Number)
  2. 在指定寄存器中放入该功能所要求的入口参数
  3. 执行 INT 21H
  4. 分析出口参数

下面介绍 INT 21H 的几个常用功能:

  1. 键盘输入 - Keyboard Input
  2. 显示器输出 - Display Output
1 - 键盘输入

按键分 3 种 :

  1. 字符键 - 字母, 数字
  2. 功能键 - Del, Enter
  3. 组合键 - Shift, Alt

DOS系统功能通过调用字符输入子功能, 可以接收从键盘上输入的字符, 输入的字符将以对应的ASCII码的形式存放.

1. 单字符输入

功能号1, 7, 8都可以接收键盘输入的单字符 -> 接收的字符以ASCII码存于累加器AL.

E.g. 从键盘输入一个"Y"或"N"

...
KEY :   MOV AH, 1
        INT 21H
        CMP AL, 'Y'
        JE  YES
        CMP AL, 'N'
        JE  NOT
        JMP KEY
YES :
    ...
NO  :
    ...
2. 字符串输入

调用 0AH号 功能来实现. 该功能要求用户指定一个输入缓冲区来存放输入的字符串. 缓冲区一般定义在数据段, 定义格式严格要求:

请添加图片描述

  1. 首字节为 用户定义的缓冲区长度
  2. 2nd字节为 实际输入的字符串(不包括Enter)个数, 由0AH号功能自动填入
  3. 3rd字节开始 存放输入的字符, 在调用本功能前, 应该把输入缓冲区的起始偏移地址预置入DX寄存器.

E.g. 从键盘上输入字符串"HELLO", 并在串尾+‘$’

DATA    SEGMENT
STRING  DB 10, 0, 10 DUP(?)
DATA    ENDS
;
CODE    SEGMENT
ASSUME  CS:CODE DS:DATA
START:  MOV     AX, DATA
        MOV     DS, DATA        ;初始化
        LEA     DX, STRING      ;缓冲区偏移地址 预送入 DX
        MOV     AH, 0AH         ;功能号21H送AH
        INT     21H             ;执行功能
        MOV     CL, STRING+1    ;实际输入的字符个数送CL -> CL = 5
        XOR     CH, CH          ;清零CH -> 构成完整的 CX 寄存器
        ADD     DX, CX          ;缓冲区起始地址DX加上输入字符数 -> DX = STRING + 5
        MOV     BX, DX          ; BXL = STRING + 5 -> 指向第二个'L' (STRING, STRING+1 = 缓冲区长度n,实际读入的字符个数)
        MOV     BYTE PTR[BX+2],'$'  ;插入串结束符'$', 串尾位置 = STRING+n+2 = STRING + 7 = BX +2
        MOV     AH, 4CH         ;设置功能号4CH -> 程序退出功能
        INT     21H             ;程序退出
        CODE    ENDS
        END     START
2 - 显示器输出 - Display Output

显示器上显示的内容都为 字符形式ASCII码.

将一字符串送到显示器显示 -> 可调用 DOS 功能

  • 2, 6 - 单字符显示
  • 9 - 字符串显示
单字符显示

功能2 :

...
MOV     DL, <要显示的字符>  ;注意为ASCII码
MOV     AH, 2              ;功能号送AH
INT     21H                ;执行
...

功能6 :

...
MOV     DL, <要显示的字符>  ;注意为ASCII码, 不可以为0FFH
MOV     AH, 6              ;功能号送AH
INT     21H                ;执行
...

E.g. 用单字符显示功能实现 依次显示123ABC 6个字符

DATA    SEGMENT
STRING  DB  '123ABC'
DATA    ENDS

CODE    SEGMENT
ASSUME  CS:CODE, DS:DATA
START:  MOV     AX, DATA
        MOV     DS, AX
        LEA     BX, STRING
        MOV     CX, 6
LPP:    MOV     DL, [BX]
        MOV     AH, 2
        INT     21H
        LOOP    LPP
        MOV     AH, 04CH
        INT     21H
CODE    ENDS
        END     START
字符串显示 - 功能号 9

要求被现实的字符串必须以’$'字符作为结束符, 否则会引起屏幕混乱.

E.g. 在屏幕上显示欢迎字符串"Hello, World!"

DSEG    SEGMENT
STRING  DB 'Hello, World!', ODH, OAH, '$'
DSEG    ENDS

CSEG    SEGMENT
        ASSUME  CS:CSEG, DS:DSEG
START:  MOV     AX, DSEG
        MOV     DS, AX
        LEA     DX, STRING  ;获取要显示的字符串的首地址
        MOV     AH, 9       ;调用字符串显示功能
        INT     21H         ;执行
        MOV     AH, 04CH    
        INT     21H
CSEG    ENDS
        END     START

E.g.2 从键盘输入一串字符, 在字符串尾插入’$',并显示该字符串.

;定义一个缓冲区接收字符串, 收了之后+个’$'再输出

DATA    SEGMENT
BUFSIZE DB 50
INLEN   DB ?
CHARS   DB 50 DUP(?)
DATA    ENDS
;
CODE    SEGMENT
        ASSUME  CS:CODE,DS:DATA
START:  MOV     DX, DATA
        MOV     DS, DX
        XOR     DX, DX
        LEA     CX, BUFSIZE         ;
        MOV     AH, 0AH             
        INT     21H                 ;执行读入

        MOV     AX, INLEN
        LEA     BX, CHARS
        ADD     BX, AX              ;BX = CHARS + n = BUFSIZE字符串尾
        MOV     BYTE, PTR[BX], '$'  ;
        ;Output
        XOR     DX, DX
        LEA     DX, CX - DX
        MOV     AH, 09H
        INT     21H
        MOV     AH, 04CH
        INT     21H
        HLT
CODE:   ENDS
        END     START
3 - 返回到 DOS - 功能号 04CH
MOV     AH, 04CH
INT     21H

4.4 汇编语言设计基础

4.4.1 程序设计概述

一个好的程序不仅应满足设计要求, 实现预先设定的功能并能正常运行, 还应具备以下的一些特性 :
和 .

衡量标准:

  1. 可理解性, 可维护性 - programme easy to understand, easy commissioning and maintenance
  2. 高效率 - high execution speed
  3. 低空间占用率 - small occupied memory space
程序设计的一般步骤 - Methodology
  1. 通过对实际问题的分析抽象出系统数学模型, 简历系统的模块结构图. Abstract a mathematical model based on the actual problem, and then determine the algorithm
  2. 确定各程序模块的数据结构及算法.
  3. 画程序流程图… Draw a block diagram (flow chart)
  4. 用指令或伪指令为数据和程序代码分配内存单元和寄存器. Allocate memory work units and registers
  5. 编写源程序并报错, 形成.ASM源程序文件. According to the block diagram program the source program, save as.ASM file
  6. 通过汇编生成.OBJ目标代码文件, 同时完成静态的语法检查. Programming source codes are compiled, the generation of.OBJ target files
  7. 链接生成.EXE可执行文件. Link the.OBJ file to the.EXE execution file
  8. 程序调试和测试. Operation and debugging
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值