本文是对基于IBM-PC汇编语言程序设计的一些笔记. 内容主要是汇编程序设计的基础知识和示例代码.
基本知识介绍
本文使用在dos环境下运行的MASM6.0. 为了需要运行dos环境, 首先需要下载dos的虚拟机. 这里推荐dosbox, 这是一个跨平台的dos虚拟机, 在其官网上可以下载到各个平台的程序.
下载了MASM6.0后直接解压, 可以看到如下的几个程序, 程序名和作用如下表所示
程序名 | 作用 |
---|---|
MASM.exe | 汇编主程序 |
Link.exe | 链接器 |
ML.exe | 汇编和链接 |
LIB.exe | 相关库 |
DEBUG.exe | 调试程序 |
其中, DEBUG.exe在前期使用较为频繁, 其相关指令较多, 因此下面给出DEBUG.exe的常见指令
命令名 | 作用 | 示例 |
---|---|---|
r(Register) | 显示或修改寄存器数 | r / r ax |
d(Dump) | 显示指定位置的内存数据 | d 1000:0004/d:1000:0004 38 |
e(Enter) | 向指定位置写入数据 | e 1000:0000 1 2 3 4 5 6 |
u(Unassmble) | 显示指定位置对应的汇编代码 | u 1000:0000 |
t(Trace) | 执行一步 | t |
a(Assemble) | 向指定位置输入汇编代码 | a |
q(quit) | 退出程序 | q |
在DEBUG.exe输入r指令, 会显示所有寄存器的当前数值, 下面给出各个寄存器的含义
名称 | 作用 | 名称 | 作用 |
---|---|---|---|
AX | 累加器 | CS | 代码段 |
BX | 基址变址 | DS | 数据段 |
CX | 计数 | ES | 附加段 |
DX | 数据 | SS | 堆栈段 |
SP | 堆栈指针 | DI | 目的变址 |
BP | 基址指针 | SI | 源变址 |
IP | 指令指针 |
段的几点说明
- ax,bx,cx,dx除了作为16bit寄存器使用以外, 均可分为两个8bit的寄存器
- 由于偏移地址是一个16bit的寄存器, 所以一个段最多有64K的空间
- 一个段要求至少有16Byte的空间
- 段的起始位置和容量必须是16的整数倍
- 段实际地址 = 段地址x16+偏移地址
- 注意:不能使用立即数对段寄存器赋值
数据和内存
数据在内存中的存放
对于大部分机器, 其内存结构均采用小端序, 即先存放低位数据, 再存放高位数据,例如数据0x1234
在内存中的实际存放顺序是34 12
内存寻址和ds寄存器
在汇编代码中, 可以使用如下的格式引用内存中的数据
1 | mov ax [0] |
上述代码实际是默认段寄存器为ds寄存器. 汇编代码中访问内存时, 总是默认从ds指定的位置开始读取数据
注意: 不可以使用mov指令在两个内存单元中直接移动数据
内存中的栈
在内存中, 有ss段寄存器和sp堆栈指针寄存器两者共同维护程序堆栈. 由于堆栈从高位开始, 向低位扩展, 所以当选择一块区域作为栈时, ss指向这一区域的开始位置, 而sp指向这一区域的结束位置.
在使用栈的过程中, sp始终指向当前的栈顶位置. 执行Push操作时, sp先减2, 然后写入数据. 执行Pop操作时, 先读取sp指向的位置的数据, 之后sp加2
注意事项
- 设置堆栈段寄存器和寄存器的时候, 两条指令必须连续执行
- si和di只能作为16bit寄存器使用
- 不能直接在两个内存单元中传递数据
mov al [6]
在asm文件中, 汇编器会将其汇编成mov al 6
, 此时应该先将偏移地址放在某个寄存器中, 如bx, 使用mov al [bx]
获得数据, 但是在DEBUG.exe程序中使用a指令逐行汇编时并没有这种问题
汇编程序结构
在使用汇编语言编写代码时, 会设计到两类指令, 第一类是汇编指令, 此类指令对应具体的机器代码, 在汇编后转换为CPU可以执行的二进制代码. 第二类是伪操作(伪指令), 此类指令由汇编器进行处理, 相当于汇编器提供的一些方便的函数调用.
一个完成的汇编程序的结构如下所示:
1 | assume cs:code,ds:data,ss:stack ;关联自定义段, 将CS段与自定义的code段关联, 将DS段与自定义的data段关联 |
注意:
- 每个段结尾的
ends
是end segment
的含义 - 语句结束后没有分号, 在汇编代码中, 分号表示注释
数据声明方式
基本声明
数据声明的基本格式为
1 | <标号> <数据类型> <数据1> [, <数据2>, <数据n>] |
其中<标号>
是一个标识符, 表示这段数据在内存中的位置, 后续指令可通过标号引用数据. <数据类型>
有三种取值, 分别是db(data byte), dw(data word), dd(data double word). 最后的数据部分可以填入若干数据.
1 | DATA_BYTE db 10,4,10H |
字符串
定义字符串与定义基本数据的格式相同, 数据部分可直接写ASCII字符. 在定义数据时可使用?
进行占位, 占据的空间与数据类型需要的空间相同.
1 | message1 db 'HELLO' |
注意: 如果给定的数据类型足够存储字符串, 则按照小端序存储, 否则按照输入的顺序存储. 对于字符串而言, 通常仅希望其按照给定顺序存储, 因此声明为db类型
标号和地址
标号可以作为数据存入, 此时存入内存的是这个标号对应的内存地址. 一个标号等价于一个16bit的偏移地址, 和一个16bit的段地址.
1 | PAR dw 100,200 |
如果被赋值的数据类型足够大, 则将一个地址赋给变量时, 偏移地址在数据低位, 段地址在数据高位. 如果被赋值的数据类型不够大, 则变量中只有偏移地址
大量数据分配
1 | array db 100 dup(10) |
- 第一行表示填充100个数据, 每个数据是10
- 第二行是嵌套表示, 外部表示总体重复两次, 内部的表示将1,2重复2次
- 即最后的序列为 0 1 2 1 2 3
类型转换
1 | OPER1 db 1,2 |
- 使用ptr关键字可以进行类型转化
- 具体格式为 type ptr variable, 其中type可以为byte, word, dword
- 先进行计算, 计算完成后, 将结果按照制定的格式进行转换
- 其中的变量+1操作等于对应的内存地址+1
多类型
1 | byte_array label byte |
- 使用label关键字指定类型
- 具体格式为 name label type, 其中type可选项与上一节相同
- 相当于给一个内存指定了两个名字, 在代码中可以任意的使用
数据寻址方式
数据表达方式
汇编语言中数据有3中表达方式
- 立即数
- 数据由字面值给出, 数据实际编码在机器指令中, 执行时, 保存在译码电路中
- 寄存器
- 数据存放在寄存器中
- 段地址:偏移地址
- 数据在内存中
寄存器间接寻址
使用形如[bx]
的形式进行寄存器间接寻址, 表示将指定的寄存器的内容作为内存的地址, 取出相应地址上的数据
寄存器相对寻址
使用形如[bx+idata]
的形式进行寄存器相对寻址, 表示将计算结果作为内存的地址, 取出相应地址上的数据
相对寻址的特点在于每次执行的时候idata是不变的立即数, 而每次改变寄存器的值, 从而对一组相对位置不变的数据操作
基址变址寻址
使用形如[si+bx]
的形式进行基址变址寻址
相对基址变址寻址
使用形如[bx+si+idata]
的形式进行相对基址变址寻址
一些限制
- 在8086CPU中, 只有bx, si, di, bp可以用于[…]
- 只能以bx+si,bx+di,bp+si,bp+di的形式出现, 其他组合都是非法的形式
- 如果在[…]中使用bp, 且没有指定段寄存器, 则默认段寄存器为ss, 即
[bp+di+5] <=> (ss)x16+(bp)+(di)+5
循环程序设计
LOOP指令
- 指令格式
LOOP 标号
- 执行步骤
(CX) = (CX) - 1
- 判断CX的值, 如果不为零, 则转至标号处执行, 否在继续向下执行
循环指令的例子
1 | assume cs:codeseg |
- 类似于高级语言中的循环结构, 汇编语言中的循环结构基本按照上述形式固定不变
- 可以使用si寄存器和di寄存器作为一定数据的辅助段寄存器
分支程序设计
offset操作
offset是一个伪操作, 属于数值回送操作符, 作用是获得标号的偏移地址, 以下面的代码为例
1 | assume sc:code |
指令分类
8086CPU的跳转指令可以分成如下的几类
指令 | 效果 |
---|---|
loop | 循环 |
jmp | 无条件跳转 |
jcxz | 有条件跳转 |
call/ret | 子程序调用 |
int | 中断 |
无条件转移
接下来介绍几种常见的无条件转移指令
名称 | 指令格式 | 特点 |
---|---|---|
段内短转移 | jmp short 标号 |
指令中使用8bit保存IP的偏移量 |
段内近转移 | jmp near ptr 标号 |
指令中使用16bit保存IP的偏移量 |
段间直接远转移 | jmp far ptr 标号 |
使用标号所在的段和偏移地址修改CS和SP |
段内间接近转移 | jmp word ptr 内存单元 |
使用指定内存单元的字修改IP |
段间间接远转移 | jmp dword ptr 内存单元 |
使用指定内存的两个字, 低位字修改IP, 高位字修改CS |
寄存器转移 | jmp 寄存器 |
使用指定寄存器的值修改IP |
几点补充
- 在执行跳转指令时, IP以及指向下一条指令, 因此所有的偏移都是相对于下一条指令的开始位置
- 因为段内短转移使用8bit保存偏移量, 所以只能向前跳转128字节或向后跳转127字节
- 因为段内近转移使用16bit保存偏移量, 所以只能向前跳转32768字节, 或者向后跳转32767字节
有条件转移
有条件转移根据之前的cmp指令计算结果决定是否转移, 且所有的转移都是短转移, 即只能在当前位置, 相对的跳转大约128个字节
有条件指令结构
有条件转移指令都是j开头, 根据转移条件不同跟上不同的后续符号, 后续符号可以分成如下几种情况
类型 | 无符号 | 有符号 |
---|---|---|
相等 | e(equal) | e(equal) |
大于 | a(abve) | l(less) |
小于 | b(below) | g(greater) |
否定 | n(not) | n(not) |
例如, 无符号的大于指令是ja
有符号的大于指令是jg
有符号的小于等于指令是jle
或者jng
jcxz指令
从名字可以知道, 此指令是比较cx寄存器是否为0, 所以当cx为0跳转到标号, 否则指向下一条指令. 此指令跳转条件与loop正好相反
子程序设计
ret指令
利用栈中的数据, 修改ip寄存器的内容, 从而实现近转移, 等价于如下的代码
1 | (ip) = ((ss)*16+sp) |
retf指令
使用栈中的两个数据修改IP寄存器和CS寄存器, 用于实现远转移, 等价于如下的代码
1 | (ip) = ((ss)*16+(sp) |
注:
- 实际上所有的入栈操作时, 都是先压入段寄存器, 在压入偏移地址寄存器, 所以出栈操作正好相反
- retf即return far
call指令
将当前的IP或CS和IP压入栈中, 并根据指令格式中的目的地址进行转移, 各指令格式与等价操作如下所示
入栈操作
call 标号 | call far ptr 标号 | call 寄存器 | call dword ptr 内存单元 |
---|---|---|---|
(SP) = (SP) - 2 | (SP) = (SP) - 2 | (SP) = (SP) - 2 | (SP) = (SP) - 2 |
((SS)*16+(SP)) = (IP) | ((SS)*16+(SP)) = (CS) | ((SS)*16+(SP)) = (IP) | ((SS)*16+(SP)) = (CS) |
|(SP) = (SP) - 2 | |(SP) = (SP) - 2
|((SS)*16+(SP)) = (IP) | |((SS)*16+(SP)) = (IP)
跳转操作
call 标号 | call far ptr 标号 | call 寄存器 | call dword ptr 内存单元 |
---|---|---|---|
(IP) = (IP) + 16bit位移 | (IP) = 目标标号所在段的偏移地址 | (IP) = (16bit寄存器) | (IP) = 内存单元地址 |
|(CS) = 目标标号所在段的段地址 | |(CS) = 内存单元地址+2
注:
call 标号
不能实现短转移, 因为是否为短转移是按照偏移量长度区分call 标号
与jmp指令相同, call指令的二进制代码中保存的是标号相对于当前IP的偏移量, 而不是绝对地址call far ptr 标号
类似于远转移指令, 但是在跳转前分别压入CS寄存器IP寄存器的值- 所有CS和IP同时出现的地方(内存地址和栈),都是IP在低位,CS在高位
MUL指令
指令格式为:mul 寄存器/mul 内存单元
两个8bit数据或两个16bit数据相乘
- 8bit数据使用al的值和指定的值相乘, 存放在ax中
- 16bit数据使用ax的值和指定的值相乘, 高位存放在dx, 低位存放在ax
参数传递方式
- 利用寄存器传递少量参数
- 在子程序的调用过程中, 如果不对寄存器做任何处理, 则寄存器中的值可以之间传递到子程序中
- 但是寄存器数量有限, 不能大量传递数据
- 使用内存单元
- 可以批量存放数据
- 对于需要批量返回的结果, 也可以使用此方法
寄存器冲突
在调用子程序的时候, 由于寄存器数量有限, 因此当前程序和子程序可能使用了相同的寄存器. 可以在子程序中可以很使用如下的框架来解决寄存器冲突
1 | 子程序入口:子程序中用到的寄存器入栈 |
标志位寄存器
标志位说明
标志名 | 解释 | 选项1 | 选项2 | 含义 | 针对数据类型 |
---|---|---|---|---|---|
OF | 溢出标志位 | NV(未溢出) | OV(溢出) | 记录运算结果是否溢出 | 有符号数 |
DF | 方向标志位 | UP(递增) | DN(递减) | 控制串传送的增减方式 | 无关 |
IF | 允许中断标志位 | DI(禁止) | EI(许可) | …… | …… |
SF | 符号标志位 | PL(正) | NG(负) | 运算结果的符号状态 | 有符号数 |
ZF | 零标志位 | NZ(不等于零) | ZF(等于零) | 运算结果是否为零 | 全部类型 |
AF | 辅助进位标志位 | NA(无进位) | AC(进位) | …… | …… |
PF | 奇偶标志位 | PO(奇) | PE(偶) | 当前二进制数据1的个数 | 全部类型 |
CF | 进位标志位 | NC(无进位) | CY(进位) | 记录运算结果的最高有效位进位或借位 | 无符号数 |
注:
- 选项一对应为0,选项二对应为1
- 只有算数运算置标志位,数据移动运算不置标志位
CF与OF比较
- CF是Carry Flag, 即进位标志位, 只针对无符号数
- OF是Overflow Flag, 即溢出标志位, 只针对有符号数
- 由于表示范围不一致,因此可以出现溢出但不进位
- 例如对于有符号两个较大的数相加
- 但无符号比有符号大一倍,没有进位
- 由于对于正负的认识不同,因此也可能出现进位但不溢出
- 例如有符号是负数+整数
- 对于无符号就是两个正数相加
- 此时无符号进位,有符号没有溢出
DF标志位
- DF指示在进行串传递的时候, 每次执行si或di的变化
- 置为递增
- 指令格式
cld
- 将DF置为0
- 指令格式
- 置为递减
- 指令格式
std
- 将DF置为1
- 指令格式
串传送指令
字节传送
- 指令格式
movsb
- 以字节为单位传送指令, 将ds:si指向的内存单元的数据传输到es:di执行的内存单元
- 执行过程如下
((es)*16+di) = ((ds)*16+si)
- 如果DF=0,
(si) = (si) + 1
,(di) = (di) + 1
- 如果DF=1,
(si) = (si) - 1
,(di) = (di) - 1
字传送
- 指令格式
movsw
- 以字为单位传送指令, 将ds:si指向的内存单元的数据传输到es:di执行的内存单元
- 执行过程如下
((es)*16+di) = ((ds)*16+si)
- 如果DF=0,
(si) = (si) + 2
,(di) = (di) + 2
- 如果DF=1,
(si) = (si) - 2
,(di) = (di) - 2
串传送
- 指令格式
rep movsb
或rep movsw
- rep指令与movsb/movsw指令结合使用可用于串传送
- rep movsb指令等价于
1
2s: movsb
loop s - 将这样两条指令配合使用, 通过cx即可实现一段数据的传输
标志寄存器与栈操作
入栈操作
- 指令格式:
pushf
- 将标志寄存器的值入栈
出栈操作
- 指令格式:
popf
- 将标志寄存器的值出栈
中断程序设计
内中断与外中断
- 由外设控制器或协处理器引起的中断称为硬件中断或外中断
- 由程序安排的中断指令INT产生的中断称为软件中断或内中断
内中断的产生原因
- CPU内部错误, 如除数为零等
- 为调试程序设置的中断
- 执行into指令
- 执行int指令
常见中断类型
中断号 | 名称 | 作用 |
---|---|---|
0 | 除法错误中断 | 执行除法指令时, 如果除数为零或者商操作寄存器范围, 立即产生此中断 |
1 | 单步执行中断 | 调试程序时, 使用此中断, 使得程序每次一条指令后立即中断 |
3 | 断点中断 | 当程序需要加入断点时, 使用此中断, 产生一个断点 |
4 | 溢出中断 | 程序产生溢出时, 产生此中断 |
注:
- 使用断点中断实际上就是在需要断点的地方插入一条
int 3
指令 - 产生溢出后, 可以使用into指令, 转入溢出中断处理, 如果没有溢出, 则into指令没有任何效果
中断向量表
- 80x86系统可以处理256种中断类型
- 中断向量表存放在内存单元0000:0000-0000:03FF的1024个内存单元中
- 一个表项占两个字(4个字节), 其中低位存放偏移地址, 高位存放段地址
中断过程
根据中断类型码, 在中断向量表中获得中断向量并设置CS与IP称为中断过程, 此过程有硬件自动完成, 不能通过程序修改, 执行过程如下
- 获得中断类型码N
- 标志位寄存器入栈
- CS寄存器入栈
- IP寄存器入栈
- 设置TF标志位和IF标志位为0
- 从中断向量表获得数据, 设置CS和IP寄存器
- 跳转至中断程序
中断程序设计
由于中断随时都有可能发生, 因此中断处理程序必须存放内存中的特定位置. 中断程序步骤与子程序类似, 有如下几步
- 保存用到的寄存器
- 处理中断
- 恢复用到的寄存器
- 用iret返回
iret指令
在调用中断程序之前, 标志位寄存器, CS寄存器, IP寄存器依次入栈, 因此iret执行相反的出栈操作即可恢复到中断以前的状态, 即iret等价于以下代码
1 | pop ip |
DIV指令为
- 指令格式:
div 寄存器
/DIV 内存单元
- 指令含义
- 8bit除法, 被除数16bit存放在ax中, 计算后al保存商, ah保存余数
- 16bit除法, 被除数为32bit, 高位存放在dx, 低位存放在ax, 计算后ax保存商, dx保存余数
- 如果商大于al或ax的保存范围则产生除法溢出
安装程序结构
编写一个安装程序可以分成如下的几个步骤
- 编写要被安装的程序, 并将代码置于安装程序中
- 设置ds:si指向被安装程序在安装程序中的位置, 将es:di执行被安装程序需要存在的位置
- 使用传输指令, 复制被安装程序
- 设置中断向量表
以下是一个安装程序的示例说明:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37assume cs:codeseg
codeseg segment
start: mov ax, cs
mov ds, ax
mov si, offset dd0
mov ax, 0
mov es, ax
mov di, 200h
mov cx, offset dd0end - offset dd0
cld
rep movsb
mov ax, 0
mov es, ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax, 4C00H
int 21H
dd0: jmp short dd0start
db "overflow!"
dd0start:
mov ax, cs
mov ds, ax
mov si, 202h
mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2
mov cx, 9
s: mov al, [si]
mov es:[di], al
inc si
add di, 2
loop s
mov ax, 4c00h
int 21h
dd0end: nop
codeseg ends
end start - 上述代码中, 从start标号开始, 到dd0标号之前, 是安装程序, 可以看到此部分程序严格按照上述顺序完成了安装操作
- 从dd0标号到dd0end标号之间的代码是被安装程序
- 上述示例中, 将程序安装到了200H的位置, 此处通常为空, 但是正式程序中不建议这么使用
mov cx, offset dd0end - offset dd0
指令中, 使用两个标号的运算实现了被安装程序长度的可扩展性, 后续修改被安装程序的时候, 不需要修改安装程序- 在dd0end标号对应的地方, 使用了一条nop指令占位, 从而可以添加一个标号, 之后用于计算程序长度
端口和外中断
IO设备的数据传输方式
- 程序控制方式(查询方式)
- 在PC系统中, 除存储器外, 和CPU通过总线连接的各种输入输出设备
- 每种IO设备都要通过一个硬件接口或控制器芯片和CPU相连
- 这些接口或控制器芯片都能支持输入输出指令与外部设备交换信息
- 中断方式
- DMA方式
端口
- 在各种硬件接口或控制器芯片中, 有一组可由CPU读写的寄存器
- CPU将这些寄存器作为端口, 对它们统一进行编制
- CPU对它们进行读写控制都是通过控制总线向芯片发出端口的读写指令
端口地址空间
- 80x86系统允许设置64K个8bit端口或32K个16bit端口
- 对端口的读写需要使用IN和OUT指令进行信息传输
端口指令
- 格式
in al 60h
- CPU通过地址总线将地址信息60H发出
- CUP通过控制总线发出端口读指令
- 端口所在芯片将60h端口中的数据通过数据总线送入CPU
端口指令的一些限制
- 对于端口号在0-255之间的端口, 可以直接访问
- 对于端口号在236-65535之间的端口, 端口地址需要放在dx寄存器中
- 访问8bit端口时, 数据只能存入al, 访问16bit端口时, 只能使用ax
通过端口访问CMOS RAM
- 芯片由电池供电
- 包含128个存储单元的RAM存储器
- 内部实时时钟占用0-d单元保存系统时间
- 内部有两个端口70H和71H, 通过这两个端口进行读写
- 70H端口为地址端口
- 71H端口为数据端口, 可以使用此端口读取或者写入数据
1
2
3
4
5
6
7
8
9
10
11; 读取CMOS RAM 2号单元
assume cs:code
code segment
start:
mov al, 2
out 70h, al
in al,71h
mov ax,4C00h
int 21h
code ends
end start
移位操作
- SHL指令
- shl 寄存器,n / shl 内存单元,n
- 逻辑左移
- 最后移出的位置CF标志位
- 如果移动次数大于1,移动次数存在cl中
- SHR指令
- shr 寄存器,n / shr 内存单元,n
- 逻辑右移
- 最后移出的位置CF标志位
- 如果移动次数大于1,移动次数存在cl中
- SAL指令
- sal 寄存器,n / sal 内存单元,n
- 算数左移
- 最后移出的位置CF标志位
- 如果移动次数大于1,移动次数存在cl中
- SAR指令
- sar 寄存器,n / sar 内存单元,n
- 算数右移
- 使用符号位补全高位
- 最后移出的位置CF标志位
- 如果移动次数大于1,移动次数存在cl中
可屏蔽中断
- 如果IF等于0,则不响应可屏蔽中断
- 在中断处理过程中,将IF置为0,即可屏蔽其他可屏蔽中断
- 几乎所有的外设引起的外中断都是可屏蔽中断
不可屏蔽中断
- 是CPU必须响应的外中断
- 中断码固定为2, 中断过程不需要取中断类型码
- 是系统有必须处理的紧急情况发生时,用于通知CPU的中断信息
标号与直接定址表
地址标号
- 地址标号代表一个内存单元地址
- 地址标号后跟上一个冒号
- 只能在代码段使用地址标号
数据标号
数据标号直接跟上数据,不加冒号
此标号除了代表内存地址以外,还隐含了此处数据的类型
在使用此标号代表内存单元时,会进行检查数据类型是否匹配
计算偏移的时候,类型信息没有影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17assume cs:codesg, ds:datasg // 此信息仅用于编译,对程序不可见
datasg segment
a db 1,2,3,4
b dw 0
datasg segment
codesg segment
start:
mov ax,datasg // 声明了数据段关联,但还是需要手动为ds赋值
mov ds, ax
mov si,0
mov cx,4
s: mov ax, a[si]
mov ah, 0
add b, ax
inc si
loop s数据标号可以作为数据被定义,标号表示此标号所表示的地址
1 | datasg segment datasg segment |
1 | datasg segment datasg segment |
- 使用dw时,只保存标号的偏移地址,使用dd时,保存偏移地址和标号所在段的段地址
直接定址表
- 用查表的方法的编程技巧
数值映射
0-9 数值+30h
10-15 数值+37h
使用直接定址表实现映射
其实就是类似数组的操作
可以提高算法的简洁性
由于可以查表,从而提升了运算速度
例子: 通过查表计算sin(x)
1 | table db ag0 ag30 ag60 ag90 |
- 使用两步查询获得数据
1
2
3mov bx, 2
mov bx, table[bx] ; 先取出偏移地址
mov ah, cs:[bx] ; 再从偏移地址取出实际内容
例子:清屏程序
- 清屏
- 设置前景色
- 设置背景色
- 向上滚动一行
- 先编写四个子函数,通过直接定址的方法,通过给定一个序号来指定调用的功能
- 清屏:将显存中当前屏幕字符设置为空格
- 设置前景色:设置属性字节(奇数字节)的0,1,2位
- 设置背景色:设置属性字节(奇数字节)的4,5,6位
- 向上滚动一行:依次将n+1行的内容复制到第n行,最后一行置为空
宏指令
宏定义
1 | macro_name macro [dummy parameter list] |
- dummy parameter list 相当于是高级语言中的形式参数列表
- 由local引导的是本地标签,在不同的地方调用时,本地标号会被展开成不同的唯一标号
- 标号必须是macro定义体的第一行,严格来说,之间包括注释也不能插入
宏调用
1 | macro_name [actual parameter list] |
宏展开
- 宏定义必须出现在调用之前
- 汇编过程中,将宏展开编程实际的代码
宏汇编操作符
&
- 拼接指令,与C语言宏的##类似
;;
- 在宏中使用的注释
%
- 计算表达式
- 在汇编过程中,计算%后的表达式,并将计算结果加入后续运算
1 | strg macro string |
宏库的建立和调用
宏库
macro.mac
调用
include macro.mac
- 引入macro,mac文件中全部的宏
排除
purge macroA …
- 制定宏名,剔除不需要的宏
rept expression
… ;重复块
endm
- 根据表达式的计算结果,重复制定次数
irp x, <1,2,3,4,5,6,7,8,9,10>
db x
endm
- x会依次带入后面的值
- 可以是数字或者寄存器,字符串等
显存操作
显存结构
- 显存分为8页,每页4KB
- 显示器规格是每屏25行,每行80个字符
- 每个字符占2B,低位是对应ASCII码,高位是显示属性
- 每屏实际占用4000B,剩余96字节无效果
- 显存地址空间为B8000H-BFFFFH
字符属性
位数 | 含义 | 含义 |
---|---|---|
7 | BL | 闪烁 |
6 | R | 背景色 |
5 | G | 背景色 |
4 | B | 背景色 |
3 | I | 高亮 |
2 | R | 前景色 |
1 | G | 前景色 |
0 | B | 前景色 |
最后更新: 2024年05月21日 23:27
版权声明:本文为原创文章,转载请注明出处
原始链接: https://lizec.top/2017/12/05/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80%E7%AC%94%E8%AE%B0/