1、分析一个奇怪的程序(课本实验8)
题目:分析下面的程序,在运行前思考:这个程序可以正确返回吗?
运行后再思考:为什么是这种结果?
通过这个程序加深对相关内容的理解。
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
解:先遵循正规的套路,先从start开始:
由start一直往下:
直到:
mov di, offset s
mov si, offset s2
mov ax, cs:[si]
mov cs:[di], ax
这个的意思是将s2处的第一个字节开始的代码(直接理解为那一行的代码)移动到s处。
cs:[di]就得到了ax的信息,就是说标号s处将得到ax的信息。
下面就执行到:s0: jmp short s
这时候,s处得到的值已经不再是以前的值了。
同时,也不再是跳回到s1处的意思了。
jmp short si ; 这句 jmp short s1 , 根据我们之前的分析 , 指令是用相对偏移来表示的
; 因此执行的操作并不是真的跳转到 s1 这个标号 , 而是跳转编译时确定的 该指令到 s1 标号的偏移
; 所以我们要分析接下来程序的流程的话 , 就必须先编译程序 , 然后要知道到底偏移是多少
; 然后再根据这个偏移确定程序下一步应该执行哪里的指令
课本之前说过,jmp指令对应的机器码前两位表示的是向前还是向后,后两句代表的是位移(位移=标号处的偏移地址-jmp后第一个字节的偏移地址),所以ax得到的不是jmp short s1这句代码,而是得到的一个四位(两个字节)的数据,表示的意义是 “向前"的”[s1]-[s2]"
查看机器码,
也可看出:
偏移是 : EB F6
其中 EB 表示的是跳转 , F6 表示偏移
F6 怎么理解呢 ?
1111 0110 (使用补码来表示)
补码转换成原码 , 符号位不变 , 其他位取反 , 然后加 1
1000 1001
1000 1010 ; 也就是 -10
也就是上面我们分析的让 (ip) = (ip) - 0x0A
然后 , 这句指令被复制到 s 标号的开头处
由于 nop 只占一个字节 , 因此两个 nop 被完全替代
然后程序执行到 s0 , 又跳转到 s 开始的地方
这个时候就要执行 : (这个时候 ip = 8)
EB F6
首先读取这条指令到指令缓存器里
接下来 , (ip) = (ip) + len(EB F6) = (ip) + 8 = 10
然后执行这条指令 , 即为 (ip) = (ip) - 10 = 0
这样 ip 就回到了 code segment 的起始处
这样继续执行
mov ax, 4C00H
int 21H
就实现了程序的正常返回
- 根据材料编程(课本实验9)
题目:
解答:
首先书中介绍了 80 x 25 彩色字符模式的显示缓冲区。
题目的要求是在第据此,若想大致的将字符显示在显存中间,需要稍微进行计算:
行:一共25行,25 / 2 = 12行(向下取整)
列:要显示的字符串 ‘welcome to masm!’ 一共16字节,每一行可以显示160字节,所以第一个元素 w 的位置: 第(160 - 16) / 2 = 72字节。
可以计算得到首个字符在显示区中的偏移:
第 12 * 160 + 72 = 1992 = 07C8H 字节处。
下面为源代码:
assume cs:code, ds:data
data segment
db 'welcome to masm!'
;将待打印的字符串放到数据段,为后续拷贝到显存处做准备
data ends
code segment
start: mov ax, data
mov ds, ax ;ds字符串区
mov ax, 0b800h ;显存地址
mov es, ax ;显存段基址
;在汇编中表示地址不可以以字母起始,所以需要添上一个0 -> 0B800H。
mov cx, 16 ;16个字母,拷贝十六次
mov si, 0 ;显存偏移
mov di, 12 * 160 + 80;显存首字符放置处
s: mov al, ds:[si]
mov ah, 01110001B ;白底蓝字
mov es:[di], ax
inc si
add di, 2
loop s
mov cx, 16 ;循环拷贝16次
mov si, 0 ;字符串区偏移
mov di, 13 * 160 + 80 ;找到中间位置
s0: mov al, ds:[si]
mov ah, 11000010B ;设置字体属性: 白底蓝字
mov es:[di], ax ;写入到显存
inc si ;字符串向后偏移1
add di, 2 ;显存写入位置向后偏移两个字节
loop s0
mov cx, 16 ;后面开始循环
mov si, 0
mov di, 14 * 160 + 80
s1: mov al, ds:[si]
mov ah, 00000111B
mov es:[di], ax
inc si
add di, 2
loop s1
mov ax, 4c00h
int 21h
code ends
end start
运行效果:
3、有个sc1的字节数组,长度为32,请统计其中0的个数,并将统计结果存入zero中。
sc1 db 12,18,45,0,56, ……
Zero db ?
解:DATAS SEGMENT
sc1 db 12,18,45,0,56,9 dup(0,1,0) ;统计19个0
Zero db 0
DATAS ENDS
STACKS SEGMENT
db 16 dup(0) ;空出栈段
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AX,DATAS
MOV DS,AX
mov ax,stacks
mov ss,ax
mov sp,17 ;设置栈段和数据段
mov bx,0
mov ax,0 ;用ax来计数
mov cx,32 ;数组长度32字节
s:push cx
mov ch,0 ;cx高位置0
mov cl,[bx]
inc bx
jcxz s1 ;找到字节为0就跳转s1,否则弹出循环次数并减一
s2:pop cx
loop s
mov word ptr [Zero],ax
MOV AH,4CH
INT 21H
s1:inc ax ;找到0要计数加1,跳转s2
jmp s2
CODES ENDS
END START