实验任务1
代码
assume cs:code, ds:data
data segment
x db 1,9,3
len1 equ $-x
y dw 1,9,3
len2 equ $-y
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,offset x
mov cx,len1
mov ah,2
s1: mov dl,ds:[si]
or dl,30h
int 21h
mov dl,' '
int 21h
inc si
loop s1
mov ah,2
mov dl,0ah
int 21h
mov si,offset y
mov cx,len2/2
mov ah,2
s2: mov dx,ds:[si]
or dl,30h
int 21h
mov dl,' '
int 21h
add si,2
loop s2
mov ah,4ch
int 21h
code ends
end start
代码功能简析
输出两行1 9 3
or 30h
是为了转换为\(ASCII\)码输出。 \(30h\) 是ASCII中 \('0'\) 的编号,其二进制形式为:\(0011 0000\) ,所以or上一个 \(30h\) 表示输出从'0'开始偏移量为1 9 3的数字
如果 or 61h
,则输出的是偏移量减去 \(1\) 的小写字母。( \(61h\) 是 \(0110 0001\) ,从1开始)
$
是预定义符号,表示当前的偏移地址,使用 jmp $
,可以进入死循环。
问题1
反汇编查看机器码,可以看到其机器码为 \(E2F2\)
\(E2\) 表示LOOP
\(F0\) 是补码形式的位移量, \(F2\) 转换为二进制为 \(11110010\)
将其转换为原码 \(1!(1110010-1) = 1!(1110001) = 10001110 = -8+-4+-2=-14\)
所以其位移量为 \(-14\) 。当前ip为 \(0x19\) ,即 \(25\) ,\(25-14=11\)
但是s1处的偏移量为 \(D\) ,即 \(13\) 。
所以整个转移的过程如下
ip指向 \(25\) 偏移处的指令,先取指令,然后ip自动 \(+2\) ,变为 \(27\) ,然后执行指令的过程中,将ip减去 \(14\) ,得到 \(13\) ,那么下一条要执行的指令就在偏移量为 \(13\) 的地方,即s1标号处。
问题2
重复问题1的操作步骤。
\(F0\) 的二进制形式为: \(11110000\)
得到其原码: \(1!(1110000-1) = 1!(1101111) = 10010000 = -16\)
为什么指令数量相同,但是位移量不同?原因出在inc
和add
的指令占字节数不同。8086汇编采用动态指令长度,所以每条指令的长度都不是相同的。所以这里会有位移量的不同。
CPU计算得到s2之后指令的地址方式和问题1一样,这里不重复描述。
实验任务2
代码简析
assume cs:code, ds:data
data segment
dw 200h,0h,230h,0h
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov word ptr ds:[0],offset s1
mov word ptr ds:[2],offset s2
mov ds:[4],cs
mov ax,stack
mov ss,ax
mov sp,16
call word ptr ds:[0]
;push ip
;jmp word ptr ds:[0]
s1: pop ax
;(ax) = stack.top() = offset line23 + size(line23) = offset s1
call dword ptr ds:[2]
;push cs
;push ip
;jmp dword ptr ds:[2]
s2: pop bx
;(bx) = stack.top() = offset line28 + size(line28) = offset s2
pop cx
;(bx) = stack.top() = cs = code
mov ah,4ch
int 21h
code ends
end start
问题1
(ax) = offset s1
(bx) = offset s2
(cx) = cs = code
问题2
进入debug进行验证。
验证AX寄存器
验证BX寄存器
验证CX寄存器
实验任务3
实现代码
assume cs:code, ds:data
data segment
x db 99, 72, 85, 63, 89, 97, 55
len equ $ - x
data ends
code segment
start:
mov ax,data
mov ds,ax
mov byte ptr ds:[len],10
mov cx,7
mov bx,0
s: mov al,ds:[bx]
mov ah,0
inc bx
call printNumber
call printSpace
loop s
mov ah,4ch
int 21h
printNumber:
div byte ptr ds:[len]
mov dx,ax
mov ah,2
or dl,30h
int 21h
mov ah,2
mov dl,dh
or dl,30h
int 21h
ret
printSpace:
mov dl,' '
mov ah,2
int 21h
ret
code ends
end start
使用call和ret实现子程序编写与调用。因为mov ah,2,int21h一次只能输出一个字符,所以采用除10将十位与个位分开输出。十位是商,个位是余数。ah保存余数,al保存商。
实现结果
实验任务4
代码
assume cs:code,ds:data
data segment
str db 'try'
len equ $ - str
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
;F00-F9F Last
;000-09F Fisrt
;cx len
;bl color
;bh line
;ds:si fist value of one string's address
mov ax,data
mov ds,ax
; mov byte ptr ds:[len],160
mov ax,stack
mov ss,ax
mov sp,16
mov bl,00000010B
mov bh,0
mov cx,3
mov si,0
call printStr
mov bl,00000100B
mov bh,24
mov cx,3
mov si,0
call printStr
mov ah,4ch
int 21h
printStr:
mov ax,0b800h;显存地址
mov es,ax
mov ax,0
mov al,bh;行号
mov dx,160
mul dx
mov di,ax
s: mov al,ds:[si]
mov es:[di],al
inc si
inc di
mov es:[di],bl
inc di
loop s
ret
code ends
end start
运行结果
实验任务5
代码
assume cs:code,ds:data
data segment
stu_no db '201983290202'
len equ $ - stu_no;学号的长度
len1 equ 40-len/2;-的长度
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax,data
mov ds,ax
mov bl,00010001B
call setBgColor
mov bl,00010111B
call printID
mov ah,4ch
int 21h
printID:
mov ax,0b800h
mov es,ax
mov ax,0
mov al,24
mov dx,0
mov dl,160
mul dl
mov di,ax
mov cx,len1
s1: mov es:[di],2dh
mov es:[di+1],bl
add di,2
loop s1
mov si,0
mov cx,len
s2: mov al,ds:[si]
mov es:[di],al
mov es:[di+1],bl
add di,2
inc si
loop s2
mov cx,len1
s3: mov es:[di],2dh
mov es:[di+1],bl
add di,2
loop s3
ret
setBgColor:
;bl,颜色
mov ax,0b800h
mov es,ax
mov di,0
mov ax,0
mov al,25
mov dx,0
mov dl,80
mul dl
mov cx,ax
mov al,20h
s:
mov es:[di],al
inc di
mov es:[di],bl
inc di
loop s
ret
code ends
end start
运行结果
实验总结
本次实验学习了跳转指令的编程以及子程序的编写,了解了loop的原理以及call和ret的原理
还学会了向显存中写入内容以格式化输出字符
一个字符占两个字节
低位字节是要输出的字符,高位字节是字符的格式