AsmEx - HsuJv/Note GitHub Wiki
> 程序如下, 编写code段中的代码, 将a段和b段的数据依次相加, 将结果存到d段中
assume cs : code
a segment
db 1, 2, 3, 4, 5, 6, 7, 8
a ends
b segment
db 1, 2, 3, 4, 5, 6, 7, 8
b ends
d segment
db 0, 0, 0, 0, 0, 0, 0, 0
d ends
code segment
start:
...
code ends
end start
思考: 80x86CPU中的段寄存器有且只有CS, DS, ES, SS四个, 显然CS, SS不能用来指向abd三个段中的任意一个, 则可用寄存器仅两个(DS, ES)
在不改变源数据的情况下, 则最简单的解决方案就是先通过8次循环将数据段a中的数据复制到d段中
mov cx, 8 ; loop for 8 times
s1:
xor ax, ax
mov al, es:[bx]
mov [bx], al
inc bx
loop s1 ; end loop1
; 数据段a的内容被送入d
接着, 再进行8次循环, 一次使用 add
指令将b段数据与d段数据相加, 结果保存在d段
mov cx, 8
s2:
xor ax, ax
mov al, es:[bx]
add [bx], al
inc bx
loop s2 ; end loop2
详细代码在Repository-Note中可见
> Power idea 公司从1975年成立一直到1995年的基本情况如下
>
> 年份 收入(千美元) 雇佣(人) 人均收入(千美元)
> 1975 16 3 ?
> 1976 22 7 ?
> 1977 38 9 ?
> ...
> 1995 5937000 17800 ?
>
> 下面的程序中, 已经定义好了这些数据
assume cs:codesg, ds:datasg, ss:stacksg
datasg segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995' ; 21年
dd 16, 22, 382, 1356, 2390, 8000, 16000, 24486, 50065
dd 97479, 140417, 197514, 345980, 590827, 803530, 1183000, 1843000, 2759000
dd 3753000, 4649000, 5937000 ; 21年内的总收入
dw 3, 7, 9, 12, 28, 38, 130, 220, 476
dw 778, 1001, 1442, 2258, 2793, 4037, 5635, 8226, 11542
dw 14430, 15257, 17800 ; 21年雇佣员工数
datasg ends
table segment
db 21 dup('year summ ne ?? ')
table ends
> 编程, 将data段中的数据按如下格式写入到table段中, 并计算21年中的人均收入(
> 取整), 结果也按照下面的格式保存在table段中
> | 年份(4B) | 空| 收入(4B) | 空|雇员数(2B)|空|人均收入(2B)| 空
> 行内地址 | 0 1 2 3 | 4 | 5 6 7 8 | 9| A B | C| D E | F
> 行首地址 | | | | | | | |
> table:0 | '1 9 7 5' | | 16 | | 3 | | ?? |
> table:10H | '1 9 7 6' | | 22 | | 7 | | ?? |
思考: 题目中有三个数据段要复制, 一个算术结果要保存;经观察发现, 第一段数据和第二段数据, 每两个值之间的地址偏移量都是4, 第三段数据之间的偏移是2
考虑实现: 用bx指向数据段起始, si指向收入段起始, di指向雇员人数起始, es指向目标段, bp指向当前要复制的位置, 然后开始循环21次
mov ax, datasg
mov ds, ax
mov ax, table
mov es, ax
mov ax, stacksg
mov ss, ax
mov sp, 6
mov si, 84 ; 指向总收入
mov di, 168 ; 指向总人数
mov bp, 0 ; bp = 0, 用于指向要复制到的内存单元
mov cx, 21 ; 循环21次
l1:
...
每次循环中嵌套一个循环复制年份, 进行二重循环时需要使用栈保存循环中要用到的寄存器的值, 再循环结束后返回
l2:
mov al, [bx+si]
mov es:[bp+si], al
inc si
loop l2 ; l2完成年份的复制
计算人均收入的时候, 先把si指向的目标单元低两字节通过ax转移到目标地址中, 再把高两字节用dx转移, 接着直接用div word ptr [di]
计算人均收入, 因为结果取整, 所以直接复制ax的值即可。
在循环的最后, 对bx, si, di分别增加4, 4, 2, 并把bp指向目标段下一行的起始
详细代码在Repository-Note中可见
> 分析下面程序, 在运行前思考, 这个程序可以正确返回吗?
> 运行后思考, 为什么会是这种结果?
assume cs:codesg
codesg segment
mov ax, 4c00h
int 21h
start:
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
codesg ends
end start
思考: 因为编译器在编译程序时就会根据offset给出的标号计算出偏移量, 所以 mov cs:[di], ax
复制给cs:[offset s]的开头的指令并不是 jmp short s1
而是 jmp short offset s2 - offset s1
从而导致程序跳转到s标号后, ip的值继续变小, 接着执行代码段最开始的返回指令
> 编程。在屏幕中间分别显示绿色, 绿底红色, 白底蓝色的字符串'Welcome to
> masm!'
>
> 编程所需的知识通过阅读, 分析下面的材料获得
> 80x25彩色字符模式显示缓冲区的结构:
>
> 内存地址空间中, b8000h-bffffh共32KB的空间, 为80x25彩色字符模式显示 缓冲区
> 向这个地址空间写入数据, 写入的数据, 写入的内容会立即出现在显示器上
>
> 在80x25彩色字符模式下, 显示器可以显示25行, 每行80个字符, 每个字符可以有
> 256种属性(背景色, 前景色, 闪烁, 高亮等组合信息)
>
> 这样, 一个字符在显示缓冲区中要占两个字节, 分别存放字符的ASCII码(低地址)
> 和属性(高地址), 80x25模式下, 一屏的内容在显示缓冲区共占4000个字节
>
> 显示缓冲区分为8页, 每页4KB≈4000B。显示器可以显示任意一页的内容, 一般情况
> 下, 显示第0页(b8000h-b8f9f)的内容
>
> 属性字节的格式:
> 7 6 5 4 3 2 1 0
> 含义 BL R G B I R G B
> 闪烁 背景 高亮 前景
思考: 通过es定位到b800段, 通过偏移160*12+40*2定位到第13行第41个字符的位置。用ds指向字符串, 将ah赋值为想要的颜色属性对应代码, 每次将当前字符送入al, 最后将ax送入显示缓冲区
详细代码在Repository-Note中可见
> 编写一个通用的子程序来实现显示字符串的功能
> 子程序描述:
> 功能: 在指定的位置, 用指定的颜色, 显示一个用0结束的字符串
> 参数: (dh) = 行号, (dl) = 列号
> (cl) = 颜色, ds:si指向字符串的首地址
> 返回: 无
思考: 在子程序中, 首先通过dh和dl定位到b800段中相应的偏移处
xor ax, ax ; ax置0
mov al, dh ; 得到dh的值
sub ax, 1
mov bl, 0a0h
mul bl ; 计算出第dh行0列相对b800:0的偏移
xor dh, dh
add dx, dx
add ax, dx
sub ax, 2 ;要打印的第一个字符的偏移
接着, cx置零, 开始循环复制, 在循环中不停的将源字符串的值送入cl, 再送入al, ah被送入颜色代码, 最后将ax送入显示缓冲区, 当字符串尾的0被送入cl时, cx = 0, 退出循环。
xor cx, cx
trans:
mov cl, [si]
mov al, [si]
mov es:[bx], ax
add bx, 2
inc si
inc cx
loop trans
ret
详细代码在Repository-Note中可见
> 编写一个通用的子程序来实现不会溢出的除法运算, 被除数为dword型, 除数为word
> 型, 结果为dword型
> 参数: (ax) = dword型数据的低16位
> (dx) = dword型数据的高16位
> (cx) = 除数
> 返回: (dx) = 商的高16位
> (ax) = 商的低16位
> (cx) = 余数
思考: 首先, 先对高16位进行除法运算, 此时低16位的值被保存入栈
push ax
mov ax, dx
xor dx, dx
div cx
执行完上述代码后, ax里是商的高16位, dx里是高16位里的余数, 应该和原低16合为一个新的dword数据, 分别放入dx, ax, 此次除法运算肯定不会溢出
pop bx
push ax
mov ax, bx
div cx
最后, 将余数送入cx, 用之前压入栈中的第一次除法的商覆盖dx
mov cx, dx
pop dx
详细代码在Repository-Note中可见
> 在CMOS RAM中, 存放着当前时间:
> 秒: 00h
> 分: 02h
> 时: 04h
> 日: 07h
> 月: 08h
> 年: 09h
> 这6个信息的长度都为1个字节
> 这些数据以BCD码的方式存放, 高4位存放十位上的数值, 低4位存放个位
> 70h为地址端口, 存放要访问的CMOS RAM单元的地址
> 71h为数据端口, 存放从选定的CMOS RAM单元中读取的数据或要写入的数据
思考: 这个程序主要做两部分工作
- 从CMOS RAM的8号单元读取当前月份的BCD码
mov al, 08h
out 70h, al
in al, 71h ; 获得月份的bcd码
- 将BCD码转换为对应的ASCII码
- 首先将BCD码复制ah中, 这样, ax中就存放着四位的BCD码
- 将ah的内容右移4位, 这样, ah中存放月份的十位的BCD码
- 将al和00001111b进行and操作, 这样, al中存放月份的个位的BCD码
- 最后将al和ah分别加上30h, 将BCD码转换为对应的ASCII码
- 调用21号中断进行输出
略
详细代码在Repository-Note中可见
> 在屏幕中依次显示'a' - 'z', 并可以让人看清
> 在显示过程中, 按下Esc键后改变颜色
思考: 程序需要完成三个任务:
- 依次显示'a' - 'z'
- 使显示间隔肉眼可见
- 改写int 9号中断使得60端口接收到Esc键的扫描码时更改显示颜色
简单实现:
- 定位到b800:[160*12+40*2], 使得字母显示到显示屏第13行第41列位置
- 循环26次, 用一个寄存器存放'a'的ASCII码值, 每次循环中输出改寄存器, 每次循环完自增1
- 每次循环中调用函数Delay使得延迟肉眼可见
delay:
push ax
push dx
xor ax, ax
mov dx, 1000h
de:
sub ax, 1
sbb dx, 0
cmp ax, 0
jne de
cmp dx, 0
jne de
pop dx
pop ax
ret
- 该函数通过10000h * 1000h次无意义的循环, 使得输出字符的两次循环间有长的延迟
- 将原int 9号中断的cs:ip保存, 写入当前程序的数据段
push word ptr es:[24h]
push word ptr es:[26h]
pop word ptr ds:[2]
pop word ptr ds:[0]
- 接着用自定义的newInt 9子程序的起始地址覆盖中断向量表中原int 9号中断起始地址
mov word ptr es:[24h], offset newInt9
mov word ptr es:[26h], cs
- 在自定义的newInt 9子程序中将标志寄存器中的TF, IF置0, 接着
call ds:[0]
调用原int 9号中断处理键盘输入 - 然后对端口60h的内容进行读取, 并考察是否是Esc
- 如果是Esc键的扫描码, 则将b800:[160*12+40*2+1]的值自增1, 实现颜色的改变
- 否则, 直接iret结束此次中断
newInt9:
push ax
push bx
push es
pushf
pushf
pop bx
and bh, 11111100b
push bx
popf ; 将IF, TF置0
call dword ptr ds:[0]
in al, 60h
cmp al, 1
jne newInt9re
mov ax, 0b800h
mov es, ax
inc byte ptr es:[160*12+40*2+1]
newInt9re:
pop es
pop bx
pop ax
iret
详细代码在Repository-Note中可见