第六章 包含多个段的程序
6.0 概述
合法地通过操作系统取得的空间都是安全的
操作系统不会让多个程序之间发生空间冲突
程序向操作系统获得空间的方法
程序加载时分配
在程序中定义各种段
程序运行时分配
通过指令向操作系统获取
6.1 在代码中使用数据
在程序中定义需处理的数据,这些数据会被编译、连接后作为程序的一部分写入可执行文件中
当可执行文件中的程序被加载至内存时,数据亦被载入,此时也获得了数据所含的空间
在代码段开头定义数据
定义数据
dw,define word
db,define byte
dw 1000h, 10001h
程序入口地址
当不定义入口地址时
(CS) = (DS) + 100H
(IP) = 0
此时在代码段开头定义数据,会出现程序入口指向异常
定义地址
end label
程序入口地址为label
存储地址
可执行文件组成
程序,来自于源程序中的汇编指令和数据
描述信息,主要是编译连接时,对源程序中伪指令的处理所得
保存在描述信息中
使用地址
加载程序从可执行文件中读取地址,并设置CS:IP
程序框架
assume cs:code
code segment
// data
start:
// code
code ends
end start
6.2 在代码段中使用栈
与在代码段中使用数据类似
栈关系的是空间,不在乎开辟空间使用的占位符
6.3 将数据、代码、栈放入不同的段
将代码、数据、栈放入同一段的问题
程序混乱
三者共享一个段的地址空间,会限制三者的应用
代码、数据、栈放在不同的段中
定义段
assume cs:code, ds:data, ss:statck
code segments
code ends
data segments
data ends
statck segments
statck ends
使用段
段名,是标号,是地址常量,指向段的开始
段,是一段地址连续的内存空间,其上存有数据
段及其数据的类型
段及其数据没有类型
他是什么,取决于他干了什么
ss:stack
被当做栈用
mov ax, stack
mov ss, ax
ds:data
被当做数据用
mov ax, data
mov ss, ds
cs:code
被当做代码用
end start
6.4 编写、调试具有多个段的程序
程序中段的空间分配
因为段的首地址是16倍数
所以段以16个字节为单位进行分配
link警告
no stack segment
不理会,需手动处理SS和SP
理会,加载程序会帮忙设定SS:SP,根据可执行文件中的描述信息
assume ss:stack
stack segment stack
stack ends
一般使用bx进行正向循环,配合栈可实现反向循环
masm611中不能以"c"为标号,否则会报语法错误
第七章 更灵活的定位内存地址的方法
7.1 and和or指令
按位与
二元操作指令,格式类似于add
AND 寄存器, 数据
AND 寄存器, 寄存器
AND 寄存器, 内存
AND 内存, 寄存器
有0为0,全1为1
按位或
二元操作指令,格式类似于add
OR 寄存器, 数据
OR 寄存器, 寄存器
OR 寄存器, 内存
OR 内存, 寄存器
有1为1,全0为0
7.2 关于ASCII码
ASCII,一种字符编码方案
编码方案,一套映射规则,目标对象到数字
7.3 以字符形式给出的数据
定义数据
db 'hello'
指令常量
mov al, 'a'
单个字符会转换成相应的ASCII码(单个字节)进行存储
7.4 大小写转换的问题
将字符转换成大写形式
5号位置0
与上11011111
将字符转换成小写形式
5号位置1
或上00100000
7.5 [bx+idata]
地址的变化度分离
段地址和偏移地址的分离
段内的不同存储单元
拥有不同的偏移地址,相同的段地址
地址不同,属于同一段
基址和偏移的分离
数组类的不同存储单元
拥有不同的偏移,相同的基址
地址不同,属于同一数组
偏移地址的表示
常量 [idata] ((ds)*16+idata)
变量 [bx] ((ds)*16+bx)
变量+常量 [idata+bx] ((ds)*16+idata+bx)
格式
mov ax, [idata+bx] // idata表示基址
mov ax, [bx+idata] // bx表示基址
mov ax, idata[bx]
mov ax, [bx].idata
7.6 用[bx+idata]的方式进行数组的处理
将第一个字符串转换为大写,第二个字符串转换为小写
汇编语言
assume cs:code, ds:data
data segment
db 'BaSiC'
db 'MinIX'
data ends
code segment
start: mov ax, data
mov ds, ax
mov bx, 0
mov cx, 5
s: mov al, [bx]
and al, 11011111b
mov [bx], al
mov al, 5[bx]
or al, 00100000b
mov 5[bx], al
inc bx
loop s
mov ax, 4c00h
int 21h
code ends
end start
C语言
char a[5] = "BaSiC";
char b[5] = "MinIX";
main()
{
int i;
i = 0;
do
{
a[i] &= 0xDF;
b[i] &= 0x20;
++i;
}
while (i<5);
}
7.7 SI和DI
SI和DI寄存器(source和destination)
可用于表示偏移地址,类似于BX
不可拆分成两个8位寄存器使用
例子
mov ax, [si] // 相对段地址的单次偏移
(ax) = ((ds*16)+(si))
mov ax, [si+idata] // 相对物理地址的单次偏移
(ax) = ((ds*16)+(si)+idata)
mov ax, [di]
mov ax, [di+idata]
7.8 [bx+si]和[bx+di]
mov ax, [bx+si] // 相对段地址的双次偏移
(ax) = ((ds*16)+(bx)+(si))
也写作
mov ax, [bx][si]
7.9 [bx+si+idata]和[bx+di+idata]
mov ax, [bx+si+idata] // 相对物理地址的双次偏移
(ax) = ((ds*16)+(bx)+(si)+idata)
也写作
mov ax, idata[bx][si]
mov ax, [bx].idata[si]
mov ax, [bx][si].idata
7.10 不同的寻址方式的灵活引用
[idata] 对指定偏移地址的访问,是点的访问
[idata+bx] 对偏移序列的访问,是线的访问
将数据中的单词首字母变为大写形式
data segment
db '1. file123456789'
db '1. edit123456789'
db '1. view123456789'
data ends
步骤
bx+=16
[3+bx]
[bx+si] 对阵列的访问,是面的访问
将数据中的单词变为大写形式
data segment
db 'file 12312345678'
db 'edit 12312345678'
db 'view 12312345678'
data ends
步骤
bx+=1
si+=1
[bx+si]
注意
双重循环时,注意cs的保存,可使用寄存器、普通内存、栈进行保存
一般来讲,在需要暂存数据的时候,我们应该使用栈
代码
stack segment stack
db 0,0,0,0,0,0,0,0
code segment
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 16
mov bx, 0
mov cx, 3
s: push cx
mov si, 0
mov cx, 4
s0: mov ax, [dx+si]
and al, 11011111b
mov [dx+si], al
inc si
loop s0
add bx, 16
pop cx
loop s
mov ax, 4c00h
int 21
code ends
[bx+si+idata] 对偏移阵列的访问,是面的访问
将数据中的单词变为大写形式
data segment
db ' file 1212345678'
db ' edit 1212345678'
db ' view 1212345678'
data ends
与[bx+si]类似
第八章 数据处理的两个基本问题
8.0 概述
数据处理时的数据定位
数据的首地址
数据的长度
定义描述符号
reg,表示寄存器
ax,bx,cx,dx
ah,al,bh,bl,ch,cl,dh,dl
bp,si,di
sp
sreg,表示段寄存器
cs,ds,ss,es
8.1 bx,si,di和bp
组合形式
一个
[bx+idata]
[bp+idat]
[si+idat]
[di+idat]
两个
[bx+si+idata]
[bx+di+idata]
[bp+si+idata]
[bp+di+idata]
缺省段寄存器
使用了bp,则为ss,否则为ds
8.2 机器指令处理的数据在什么地方
CPU内部
寄存器
指令缓冲器
内存
端口
8.3 汇编语言中数据位置的表达
指令缓冲器
立即数(idata)
数据在指令中,随同指令一起被读到指令缓冲器
指令能够自动找到立即数
寄存器
给出寄存器编号
内存
物理地址
段地址SA+偏移地址EA(excursion)
段地址,保存在段寄存器中
段寄存器
可隐式给出
偏移地址中使用了bp,则为ss,否则为ds
可显示给出
sreg:[X]
偏移地址,通过寄存器和立即数组合而成
8.4 寻址方式
直接寻址 固定点(点,无变化) [idata]
寄存器间接寻址 变化点(线,一维变化) [X],X为bx,si,di,bp
寄存器相对寻址 变化偏移点 [X+idata]或[X].idata(idata相对于X变) 用于结构体
(偏移线) [idata+X]或idata[X](X相对于idata变) 用于数组
(一维或二变化) [X][idata](X和idata相对于段地址变) 用于二维数组
基址变址寻址 变化线(面,二维变化) [bx或bp][si或di](X和idata相对于段地址变) 用于二维数组
相对基址变址寻址 变化偏移线(偏移面,二维变化)[bx或bp].idata[si或di] 用于表格(结构)中的数组项
idata.[bx或bp][si或di] 用于二维数组
8.5 指令要处理的数据有多长
间接指定
通过寄存器名
如 mov [bx], ax
直接指定
word ptr
byte ptr
如 mov word ptr [bx], 1
指令指定
pop和push操作的是word
8.6 寻址方式的综合应用
数据处理
原数据
db 'DEC', 'Ken Pslen'
dw 137, 40
db 'PDP'
新数据
137 -> 38
40+70
'PDP' -> 'VAX'
数据位置的描述
段 > 数据块 > 数据项 > 数据子项
段寄存器 > bx或bp > idata > si或di
内存块 > 结构体 > 结构体成员 > 成员分量
汇编语言
assume cs:code, ds:data
data segment
db 60h dup (0)
db 'DEC', 'Ken Oslen'
dw 137, 40
db 'PDP'
data ends
code segment
start: mov ax, data
mov ds, ax
mov bx, 60h
mov word ptr [bx].12, 38
add word ptr [bx].14, 70
mov si, 0
mov byte ptr [bx].16[si], 'V'
inc si
mov byte ptr [bx].16[si], 'A'
inc si
mov byte ptr [bx].16[si], 'X'
mov ax, 4c00h
int 21h
code ends
end start
C语言
struct company {
char cn[3];
char hn[9];
int pm;
int sr;
char cp[3];
}
struct company dec = {"DEC", "Ken Oslen", 137, 40, "PDP"};
main()
{
int i;
dec.pm = 38;
dec.sr += 70;
i = 0;
dec.cp[i] = 'V';
++i;
dec.cp[i] = 'A';
++i;
dec.cp[i] = 'X';
return 0;
}
8.7 div指令
除数可以在内存中或寄存器中
除数为8位
被除数为16位,在AX中
商在AL
余数在AH
除数为16位
被除数为32位,低16位在AX中,高16位在DX中
商在AX
余数在DX
指令格式
div reg
div 内存单元
8.8 伪指令dd
dd, define double word, 4字节
dd等数据定义伪指令,如同汇编指令一样
可在段内任意地方使用
都描述了或填充了段空间的数据
数据定义伪指令,直接使用数据填充
汇编指令,使用机器码填充
8.9 伪指令dup
配合数据定义伪指令使用,表示重复多次执行数据定义伪指令
如
db 3 dup (0, 1, 2)
等价于
db 0, 1, 2, 0, 1, 2, 0, 1, 2
8.10 寻址方式在结构化数据访问中的应用
程序一
assume cs:code, ds:data, es:table
data segment
db '1975', '1976', '1977'
dd 16, 22, 382
dw 3, 7, 9
data ends
table segment
db 3 dup ('year summ ne ?? ')
table ends
code segment
start: mov ax, data
mov es, ax
mov ax, table
mov ds, ax
mov cx, 3
mov bx, 0
mov bp, 0
year: mov si, 0
mov ax, es:[bp][si]
mov [bx].0[si], ax
mov si ,2
mov ax, es:[bp][si]
mov [bx].0[si], ax
add bx, 10h
add bp, 4
loop year
mov cx, 3
mov bx, 0
mov bp, 12
summ: mov si, 0
mov ax, es:[bp][si]
mov [bx].5[si], ax
mov si, 2
mov ax, es:[bp][si]
mov [bx].5[si], ax
add bx, 10h
add bp, 4
loop summ
mov cx, 3
mov bx, 0
mov bp, 24
emp: mov ax, es:[bp]
mov [bx].10, ax
add bx, 10h
add bp, 2
loop emp
mov cx, 3
mov bx, 0
avg: mov ax, [bx].5
mov dx, [bx].7
div word ptr [bx].10
mov [bx].13, ax
add bx, 10h
loop avg
mov ax, 4c00h
int 21h
code ends
end start
程序二
assume cs:code, ds:data, es:table
data segment
db '1975', '1976', '1977'
dd 16, 22, 382
dw 3, 7, 9
data ends
table segment
db 3 dup ('year summ ne ?? ')
table ends
code segment
start: mov ax, data
mov es, ax
mov ax, table
mov ds, ax
mov cx, 3
mov si, 0
mov di, 0
mov bx, 0
s:
mov ax, es:[si]
mov [bx].0, ax
mov ax, es:[2][si]
mov [bx].2, ax
mov ax, es:[12][si]
mov [bx].5, ax
mov ax, es:[14][si]
mov [bx].7, ax
mov ax, es:[24][di]
mov [bx].10, ax
mov ax, [bx].5
mov dx, [bx].7
div word ptr [bx].10
mov [bx].13, ax
add si, 4
add di, 2
add bx, 10h
loop s
mov ax, 4c00h
int 21h
code ends
end start
第九章 转移指令的原理
9.0 概述
转移指令,更新CPU从内存取指令的位置
转移指令分类
转移行为
只修改IP,称为段内转移,远转移
IP修改的范围
-128~127 短转移
-32768~32767 近转移
同时修改CS和IP,称为段间转移
转移条件
无条件转移指令
条件转移指令
循环指令
过程
中断
9.1 操作符offset
伪指令,获取标号的偏移地址
9.2 jmp指令
无条件转移指令
同时支持、近转移、短转移
指令参数
绝对位置转移
段地址和偏移地址
相对位移转移
转移位移
9.3 相对位移转移的jmp指令
段内短转移
jmp short 标号
二字节
段内近转移
jmp near ptr 标号
三字节
注意,标号用于计算转移位移,实际指令中仅包含位移数据,无标号的偏移地址数据
行为
(IP) = 转移指令后首字节的偏移地址 + 位移
位移 = 标号的偏移地址 - 转移指令后首字节的偏移地址
编译器计算
负数,使用补码表示
正数,最高位为0,取反加一为负数
负数,最高位为1,取反加一后正数
9.4 立即数的绝对位置转移的jmp指令
段间远转移
jmp far ptr 标号
五字节
行为
(CS) = 标号的段地址
(IP) = 标号的偏移地址
jmp 标号的处理过程
编译器中有一个地址计数器(AC)
根据指令,在编译过程中不断增加
遇到标号,会做记录
向前(低地址)转移
例子
s: ...
jmp [short, near ptr, far ptr]s
编译器先遇到标号,会记录标号的地址
若转移位移属于[-128, 127]
jmp [short, near ptr, far ptr] s
转移指令,统一转化为"jmp short s"对应的机器码
若转移位移属于[-32768, 32767]
jmp [short, near ptr, far ptr] s
转移指令
jmp short s,将报错
jmp [near ptr, far ptr]s,原格式编译
jmp s,等价于 jmp near ptr s
向后(高地址)转移
例子
jmp [short, near ptr, far ptr]s
...
s: ...
编译器会先遇到jmp指令
各类转移指令,按原格式编译
参数使用nop指令填充
nop指令,一字节,无动作
jmp short s,共二字节,填充一字节
jmp near ptr s,共三字节,填充二字节
jmp far ptr s,共五字节,填充四字节
编译器后遇到标号
若转移位移属于[-128, 127]
原各类转移指令,统一转化为"jmp short s"对应的机器码
未覆盖的空间,为nop,无影响
若转移位移属于[-32768, 32767]
jmp short s,将报错
jmp [near ptr, far ptr]s,在原指令处填充参数
9.5 寄存器的绝对位置转移的jmp指令
jmp 16为reg
段内转移
(IP) = (16位reg)
9.6 内存的绝对位置转移的jmp指令
jmp word ptr 内存单元地址
段内转移
(IP) = (内存单元地址)
jmp dword ptr 内存单元地址
段间转移
(IP) = (内存单元地址)
(CS) = (内存单元地址+2)
9.7 jcxz指令
jmp when cx equal zero
jcxz指令,属于条件转移指令
条件转移指令,属于短转移
参数是一字节位移,[-128, 127]
该指令一共两个字节
jcxz指令行为
若cx为0,则转移,否则继续
jcxz,即有条件的jmp short s
if (0==cx) jmp short s;
9.8 loop指令
该指令,属于条件转移指令
条件转移指令,属于短转移
该指令一共两个字节
该指令行为
--cx
若cx不为0,则转移,否则继续
有条件的jmp short s
--cx;
if (0!=cx) jmp short s;
9.9 根据位移进行转移的意义
相对关系与绝对关系的变化度分离
便于程序段在内存中浮动装配
9.10 编译器对转移位移越界的检查
如jmp short s,支持的转移范围为[-128, 127],超过就会报错
9.11 分析一个奇怪的程序
问题,程序是否能够正常返回
程序
assume cs:code
code segment
mov ax, 4c00h
int 21h
start: mov ax, 0
s: nop
nop
mov di, offset s
mov si, offset s2
mov ax, cs:[si]
mov cs:[di], ax
s0: jmp short s
s1: mov ax, 0
int 21h
mov ax, 0
s2: jmp short s1
nop
code ends
end start
答案
可以
jmp short 不是绝对位置转移,而是相对位移转移
9.12 根据材料编程
要求
在屏幕中间分别显示绿色、绿地红色、白底蓝色的字符串'welcome to masm!'
知识
80*25彩色字符模式显示缓冲区(简称显示缓冲区)的介绍
显示缓冲区的
向显示缓冲区写入数据,写入的内容立即出现在显示器中
显示缓冲区结构
空间,[B8000H~BFFFFH],共32KB
一共分为8页,单页4KB,4096B
单页,25行,80列,共2000个字符
单个字符,两个字节,低字节为ASCII码,高字节为属性码
属性码组成
7 6 5 4 3 2 1 0
BL BR BG BB I FR FG FB
闪烁 ( 背景色 ) 高亮( 前景色 )
一般情况下,显示0号页内容
[B8000H, B9000H]
程序
11行32列 绿色字符串
首地址,B8000H+(11*80+32)*2,B8720H
绿色,00000010,02H
12行32列 绿底红色字符串
首地址,B8000H+(12*80+32)*2,B87C0H
绿底红色,00100100,24H
13行32列 白底蓝色字符串
首地址,B8000H+(13*80+32)*2,B8860H
白底蓝色,01110001,71H
代码
assume cs:code, ds:data
data segment
db 'welcome to masm!'
dl1:
data ends
code segment
start: mov ax, data
mov ds, ax
mov ax, 0b800h
mov es, ax
mov si, 0
mov di, 0
mov cx, offset dl1
s: mov al, [si]
mov es:720h.[di], al
mov es:7c0h.[di], al
mov es:860h.[di], al
inc si
inc di
mov es:720h.[di], 02h
mov es:7c0h.[di], 24h
mov es:860h.[di], 71h
inc di
loop s
mov ax, 4c00h
int 21h
code ends
end start
第十章 CALL和RET指令
10.0 概述
使用栈的转移指令
ret,使用数据
call,保存数据
10.1 ret和retf
ret
用栈数据,修改IP
等效指令 POP IP
retf
用栈数据,修改IP和CS
等效指令
POP IP
POP CS
10.2 call
行为
下一指令地址压栈
将当前的IP或CS和IP压栈
转移
转移与jmp类似,不支持近转移
10.3 相对位移转移的call指令
call 标号
等效指令
push ip
jmp near ptr 标号
10.4 立即数的绝对位置转移的call指令
call 标号
等效指令
push cs
push ip
jmp far ptr 标号
10.5 寄存器的绝对位置转移的call指令
call 16位寄存器
等效指令
push ip
jmp 16位寄存器
10.6 内存的绝对位置转移的call指令
call word ptr 内存单元地址
等效指令
push ip
jmp word ptr 内存单元地址
call dword ptr 内存单元地址
等效指令
push cs
push ip
jmp dword ptr 内存单元地址
10.7 call和ret的配合使用
配合使用实现子程序的机制
具有子程序的源程序框架
assume cs:code
code segment:
main: ...
call sub1
...
mov 4c00h,
int 21h
sub1: ...
call sub2
...
ret
sub2: ...
ret
code ends
end main
10.8 mul
格式
mul reg
mul [byte ptr|word ptr] 内存单元地址
8位乘法
被乘数,al
乘数,指令参数,reg或内存单元地址
结果,ax
16位乘法
被乘数,ax
乘数,指令参数,reg或内存单元地址
结果,低字ax,高字dx
10.9 模块化程序设计
现实的问题比较复杂,常被转化为多个相互联系、不同层次的子问题
每个子问题,对应了一个子程序
使用call和ret,可以实现多个相互联系、功能独立的子程序,来解决一个复杂问题
10.10 参数和结果传递的问题
使用寄存器来存储参数和结果的内容
调用者,将参数送入参数寄存器,从结果寄存器获得结果
子程序,从参数寄存器获得参数,将结果送入结果寄存器
例子
assume cs:code, ds:data
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax, data
mov ds, ax
mov cx, 8
mov si, 0
mov di, 16
s: mov bx, [si]
call cube
mov [di], ax
mov [di].2, dx
add si, 2
add di, 4
loop s
mov ax, 4c00h
int 21h
cube: mov ax, bx
mul bx,
mul bx
ret
code ends
end start
10.11 批量数据的传递
方法一
将批量数据转化为简单数据
传值与传址
使用寄存器来存储参数和结果的地址
例子
assume cs:code, ds:data
data segment
db 'conversation'
d1:
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
mov cx, offset d1
call capital
mov ax, 4c00h
int 21h
capital:and byte ptr [si], 11011111b
inc si
loop capital
ret
code ends
end start
方法二,用栈传递参数
参数,属于临时数据,只需暂存,可以存放在栈中
栈,可被用于暂存数据
调用者,将参数压栈
子程序,从栈中取参数
例子一
;功能:计算(a-b)^3,a和b为字型数据
;参数:进入子程序时,栈顶存放IP,后面依次是a和b
;结果:(dx:ax)=(a-b)^3
difcube:push bp
mov bp, sp
mov ax, [bp+4]
sub ax, [bp+6]
mov bp, ax
mul bp
mul bp
pop bp
ret 4
;ret n
;等效指令
; pop ip
; sub sp, n
;调用
mov ax, 1
push ax
mov ax, 3
push ax
call defcube
例子二
void add(int, int, int);
main()
{
int a=1;
int b=2;
int c=0;
add(a, b, c);
c++;
}
void add(int a, int b, int c)
{
c = a+b;
}
编译后的汇编程序
mov bp, sp
sub sp, 6
mov word ptr [bp-6], 1
mov word ptr [bp-4], 2
mov word ptr [bp-2], 0
push [bp-2]
push [bp-4]
push [bp-6]
call ADDR
add sp, 6
inc word ptr [bp-2]
ADDR: push bp
mov bp, sp
mov ax, [bp+4]
add ax, [bp+6]
mov [bp+8], ax
mov sp, bp
pop bp
ret
调用者,通过栈传入的参数,成为子程序的局部变量
10.12 寄存器冲突的问题
问题
主程序和子程序,使用了相同的寄存器
主程序的寄存器数据,被子程序覆盖
方法
程序块之间,接触寄存器使用的耦合性,不再相互依赖
子程序中,开始时保存所用寄存器,结束时恢复所用寄存器
寄存器数据,属于临时暂存数据,可使用栈保存
子程序的框架
子程序开始:
子程序所用寄存器入栈
子程序内容
子程序所用寄存器出栈
返回(ret,retf)
10.13 编写子程序
代码
assume cs:code
data segment stack
db '1975', '1976', '1977'
dd 16, 22, 382
dw 3, 7, 9
data_1:
db 80 dup (' ')
db 0
data_2:
db 65 dup (0)
data ends
table segment
db 3 dup ('year', 0, 'summ ne ?? ')
table ends
code segment
start: mov ax, data
mov es, ax
mov ax, table
mov ds, ax
mov cx, 3
mov si, 0
mov di, 0
mov bx, 0
s:
mov ax, es:[si]
mov [bx].0, ax
mov ax, es:[2][si]
mov [bx].2, ax
mov ax, es:[12][si]
mov [bx].5, ax
mov ax, es:[14][si]
mov [bx].7, ax
mov ax, es:[24][di]
mov [bx].10, ax
mov ax, [bx].5
mov dx, [bx].7
div word ptr [bx].10
mov [bx].13, ax
add si, 4
add di, 2
add bx, 10h
loop s
; display table
mov cx, 3
mov bh, 5 ; row index
mov di, 0
main_s:
push cx
mov cl, 2
; erase original display
mov dh, bh
mov dl, 0
mov ax, data
mov ds, ax
mov si, offset data_1
call show_str
; display year
mov dl, 1
mov ax, table
mov ds, ax
mov si, di
call show_str
; display summ
mov dl, 10
mov ax, data
mov ds, ax
mov ax, table
mov es, ax
mov ax, es:[di].5
mov dx, es:[di].7
mov si, data_2
call ddtoc
mov dh, bh
mov dl, 10
call show_str
; display ne
mov dl, 20
mov ax, es:[di].10
call dtoc
call show_str
; display ??
mov dl, 30
mov ax, es:[di].13
call dtoc
call show_str
pop cx
inc bh
add di, 16
loop main_s
mov ax, 4c00h
int 21h
;===================================================
; parameters
; dh, row index, 0~24
; dl, column indexx, 0~79
; cl, symbol color
; ds:si, string address
; result
; void
show_str:
push ax
push dx
push di
push es
push cx
mov al, 80
mul dh
mov dh, 0
add ax, dx
mov di, 2
mul di
mov di, ax
mov ax, 0b800h
mov es, ax
mov ah, cl
mov cx, 0
show_str_s:
mov cl, [si]
jcxz show_str_ret
mov al, [si]
mov es:[di], al
inc si
inc di
mov es:[di], ah
inc di
jmp show_str_s
show_str_ret:
pop cx
pop es
pop di
pop dx
pop ax
ret
;====================================================
; parameters
; ax, bei chu shu, low word
; dx, bei chu shu, high word
; cx, chu shu
; result
; ax, shang, low word
; dx, shang, low word
; cx, yu shu
divdw: push ax
mov ax, dx
mov dx, 0
div cx
push ax
push bp
mov bp, sp
mov ax, [bp+4]
pop bp
div cx
mov cx, dx
pop dx
add sp, 2
ret
;====================================================
; parameters
; ax, data will be converted
; ds:si, space will be store result
; result
; void
dtoc:
push ax
push si
push bx
push dx
push cx
push di
mov bx, 10
mov di, 0
dtoc_next:
mov dx, 0
div bx
add dx, '0'
push dx
inc di
mov cx, ax
jcxz dtoc_ret
jmp dtoc_next
dtoc_ret:
mov cx, di
dtoc_copy:
pop [si]
inc si
loop dtoc_copy
mov [si], 0
pop di
pop cx
pop dx
pop bx
pop si
pop ax
ret
;====================================================
; parameters
; ax, data will be converted, low word
; dx, data will be converted, high word
; ds:si, space will be store result
; result
; void
ddtoc:
push ax
push si
push dx
push cx
push di
mov di, 0
ddtoc_next:
mov cx, 10
call divdw
add cx, '0'
push cx
inc di
mov cx, ax
or cx, dx
jcxz ddtoc_ret
jmp ddtoc_next
ddtoc_ret:
mov cx, di
ddtoc_copy:
pop [si]
inc si
loop ddtoc_copy
mov [si], 0
pop di
pop cx
pop dx
pop si
pop ax
ret
;====================================================
code ends
end start
注意
定义stack段时,注意段长要保持为偶数,否则编译器会自动(或前或后)增加字节
在段头增加字节,可能影响数据的引用