《汇编语言》实验10,dtoc子程序在编译连接都正常的情况下弄崩了虚拟机
系统为WINDOS10系统,运行环境有两个:vscode上的DOSBOX扩展和的vm虚拟机的MS-DOS系统
平时就在vscode上写,发现dosbox环境上程序运行出不来END后放到虚拟机上想知道是什么原因,结果程序直接被弄崩了。
第3题的数值显示,除了dtoc函数其他都正常,希望有朋友帮我看看是怎么回事。
assume cs:codedata segment
DB 10 DUP(0)
data ends
code segment
start:
mov ax,DATA ;用ds:si指向字符串
mov ds,ax
mov ax,0B800H ;用es:bx+di指向显存
mov es,ax
mov bx,0
mov di,0
mov si,0
mov dx,000FH
mov ax,4240H
call dtoc
; mov dh,8 ;传入参数并调用函数
; mov dl,3
; mov cl,2
; call show_str
mov ax,4c00h
int 21h
DIVDW: ;函数介绍:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型,参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位),CX(除数,WORD型)
PUSH AX
MOV AX,DX ;先计算高16位
MOV DX,0
DIV CX
MOV BX,AX ;暂时用BX存放结果的高16位,这时DX要存放运算数
POP AX
DIV CX ;计算出结果低16位
MOV CX,DX ;CX存放余数
MOV DX,BX ;DX存放结果高16位
MOV BX,0
RET
DTOC: ;函数介绍:将DWORD型数据转化为十进制字符串放入DS中(从DS:0遍历到DS:X == 0结尾),参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位)
MOV CX,10
CALL DIVDW
ADD CX,30H
MOV WORD PTR DS:[SI],CX
PUSH DS:[SI]
INC SI
MOV CX,AX
JCXZ DTOC_END
JMP DTOC
DTOC_END:
MOV SI,0
DATA_REVERSE: ;当栈不为空(持续弹出到只剩下IP)时,将栈中各值顺序打入DS
POP DS:[SI]
INC SI
MOV BP,SP
MOV BX,SS:[BP]
MOV CX,BX
JCXZ DR_END
JMP DATA_REVERSE
DR_END:
MOV BX,DS:[SI-1] ;用BX存取RET用的IP
MOV CX,0
MOV [SI-1],CX
PUSH BX
MOV SI,0
RET
SHOW_STR: ;函数介绍:将DS中的字符串数据(从DS:0遍历到DS:X == 0结尾)显示在屏幕上,参数为dh(行号),dl(列号),cl(颜色)
push dx ;预处理子程序中要用的参数
push cx
mov ax,00a0h ;根据行和列计算在显存中相应的偏移地址,存入bx中
mul dh
sub ax,00a0h
mov bx,0
mov bl,dl
add bx,bx
sub bx,2
add ax,bx
mov bx,ax
mov ah,cl
mov al,ds:[si]
mov es:[bx + di],ax ;用ax装字符和属性,并经过ES写入显存中
inc si ;指向下一个字符
add di,2
mov cl,ds:[si] ;指向下一个字符后,检查是否到底(即是否为0)
mov ch,0
jcxz show_str_END
pop cx
jmp show_str
show_str_END:pop cx ;还原子程序中用到的参数
pop dx
mov si,0
mov di,0
mov bx,0
mov sp,0FFFEH
ret
code ends
end start
回答
开始研究
检查修改了下自己的程序,发现
assume cs:code data segment
DB 10 DUP(0)
data ends
code segment
start:
mov bx,DATA ;用ds:si指向字符串
mov ds,bx
; mov ax,0B800H ;用es:bx+di指向显存
; mov es,ax
mov dx,000FH
mov ax,4240H
call dtoc
mov ax,4c00h
int 21h
DTOC: ;函数介绍:将DWORD型数据转化为十进制字符串放入DS中(从DS:0遍历到DS:X == 0结尾),参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位)
MOV CX,10
CALL DIVDW
ADD CX,30H
MOV WORD PTR DS:[SI],CX
PUSH DS:[SI]
INC SI
MOV CX,AX
JCXZ DTOC_END
JMP DTOC
DTOC_END:
MOV SI,0
DATA_REVERSE: ;当栈不为空(持续弹出到只剩下IP)时,将栈中各值顺序打入DS
POP DS:[SI]
INC SI
MOV BP,SP
MOV BX,SS:[BP]
MOV CX,BX
JCXZ DR_END
JMP DATA_REVERSE
DR_END:
MOV BX,DS:[SI-1] ;用BX存取RET用的IP
MOV CX,0
MOV [SI-1],CX
PUSH BX
MOV SI,0
RET
DIVDW: ;函数介绍:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型,参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位),CX(除数,WORD型)
PUSH AX
MOV AX,DX ;先计算高16位
MOV DX,0
DIV CX
MOV BX,AX ;暂时用BX存放结果的高16位,这时DX要存放运算数
POP AX
DIV CX ;计算出结果低16位
MOV CX,DX ;CX存放余数
MOV DX,BX ;DX存放结果高16位
MOV BX,0
RET
SHOW_STR: ;函数介绍:将DS中的字符串数据(从DS:0遍历到DS:X == 0结尾)显示在屏幕上,参数为dh(行号),dl(列号),cl(颜色)
push dx ;预处理子程序中要用的参数
push cx
mov ax,00a0h ;根据行和列计算在显存中相应的偏移地址,存入bx中
mul dh
sub ax,00a0h
mov bx,0
mov bl,dl
add bx,bx
sub bx,2
add ax,bx
mov bx,ax
mov ah,cl
mov al,ds:[si]
mov es:[bx + di],ax ;用ax装字符和属性,并经过ES写入显存中
inc si ;指向下一个字符
add di,2
mov cl,ds:[si] ;指向下一个字符后,检查是否到底(即是否为0)
mov ch,0
jcxz show_str_END
pop cx
jmp show_str
show_str_END:pop cx ;还原子程序中用到的参数
pop dx
mov si,0
mov di,0
mov bx,0
mov sp,0FFFEH
ret
code ends
end start
; mov ax,0B800H ;用es:bx+di指向显存 ; mov es,ax
将这两个指令注释掉,程序在DOSBOX中能正常出END,放进虚拟机的MS-DOS系统不会弄蹦系统了,但运行仍然会卡住,原因未知,由于主要是用DOSBOX,就不深究虚拟机了,下面主要看DOSBOX的情况。
我只是操作寄存器指向了某一内存,并没有修改内存,为什么程序会卡住呢?
这两段代码主要是给后面的show_str程序使用的,检查该程序,发现只要代码中设计栈,别说pop,push,就算只mov了个sp,程序都出不了END。
开始拆分问题,先将dtoc和show_str分为两个单独的文件
分文件讨论过程
show_str.asm
show_str.asm,运行该文件代码,发现不出END。
assume cs:code data segment
db 'WELCOME TO MASM!',0
data ends
code segment
start:
mov ax,data ;用ds:si指向字符串
mov ds,ax
mov ax,0B800H ;用es:bx+di指向显存
mov es,ax
mov bx,0
mov di,0
mov si,0
mov dh,8 ;传入参数并调用函数
mov dl,3
mov cl,2
call show_str
mov ax,4c00h
int 21h
show_str:
push dx ;预处理子程序中要用的参数
push cx
mov ax,00a0h ;根据行和列计算在显存中相应的偏移地址,存入bx中
mul dh
sub ax,00a0h
mov bx,0
mov bl,dl
add bx,bx
sub bx,2
add ax,bx
mov bx,ax
mov ah,cl
mov al,ds:[si]
mov es:[bx + di],ax ;用ax装字符和属性,并经过ES写入显存中
inc si ;指向下一个字符
add di,2
mov cl,ds:[si] ;指向下一个字符后,检查是否到底(即是否为0)
mov ch,0
jcxz ok
pop cx
jmp show_str
ok:pop cx ;还原子程序中用到的参数
pop dx
mov si,0
mov di,0
mov bx,0
ret
code ends
end start
在show_str.asm中重写show_str:将代码中涉及栈的语句从:
mov ah,cl mov al,ds:[si]
mov es:[bx + di],ax ;用ax装字符和属性,并经过ES写入显存中
inc si ;指向下一个字符
add di,2
mov cl,ds:[si] ;指向下一个字符后,检查是否到底(即是否为0)
mov ch,0
jcxz show_str_END
pop cx
jmp show_str
show_str_END:pop cx ;还原子程序中用到的参数
pop dx
mov si,0
mov di,0
mov bx,0
mov sp,0FFFEH
ret
改为:
show_str: mov bx,0B800H ;用es:bx+di指向显存
mov es,bx
mov ax,00a0h ;根据行和列计算在显存中相应的偏移地址,存入bx中
mul dh
sub ax,00a0h
mov bx,0
mov bl,dl
add bx,bx
sub bx,2
add ax,bx
mov bx,ax
show_str_start:
mov ah,cl
mov al,ds:[si]
mov es:[bx + di],ax ;用ax装字符和属性,并经过ES写入显存中
inc si ;指向下一个字符
add di,2
mov [bp],cx ;等效替代push cx
mov cl,ds:[si] ;指向下一个字符后,检查是否到底(即是否为0)
mov ch,0
jcxz ok
mov cx,[bp] ;等效替代pop cx
jmp show_str_start
show_str_END: ;还原子程序中用到的参数
mov si,0
mov di,0
mov bx,0
ret
DOSBOX中能正常出END,仅仅是换了个代码,为什么编译器会看push和pop不顺眼?
dtoc.asm
但是dtoc.asm里也有push和pop,怎么运行能正常出END?
ASSUME CS:CODEDATA SEGMENT
DB 10 DUP(0)
DATA ENDS
CODE SEGMENT
START:
mov dx,0000H
mov ax,12666
call dtoc
MOV AX,4C00H
INT 21H
DTOC: ;函数介绍:将DWORD型数据转化为十进制字符串放入DS中(从DS:0遍历到DS:X == 0结尾),参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位)
MOV CX,10
CALL DIVDW
ADD CX,30H
MOV WORD PTR DS:[SI],CX
PUSH DS:[SI]
INC SI
MOV CX,AX
JCXZ DTOC_END
JMP DTOC
DTOC_END:
MOV SI,0
DATA_REVERSE: ;当栈不为空(持续弹出到只剩下IP)时,将栈中各值顺序打入DS
POP DS:[SI]
INC SI
MOV BP,SP
MOV BX,SS:[BP]
MOV CX,BX
JCXZ DR_END
JMP DATA_REVERSE
DR_END:
MOV BX,DS:[SI-1] ;用BX存取RET用的IP
MOV CX,0
MOV [SI-1],CX ;这里发现虚拟机DOS系统不允许直接向内存赋值,就改成寄存器了
PUSH BX
MOV SI,0
RET
DIVDW: ;函数介绍:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型,参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位),CX(除数,WORD型)
PUSH AX
MOV AX,DX ;先计算高16位
MOV DX,0
DIV CX
MOV BX,AX ;暂时用BX存放结果的高16位,这时DX要存放运算数
POP AX
DIV CX ;计算出结果低16位
MOV CX,DX ;CX存放余数
MOV DX,BX ;DX存放结果高16位
MOV BX,0
RET
CODE ENDS
END START
合并讨论:problem.asm
尝试将这两个程序重新合在一起,作problem.asm:
assume cs:code data segment
DB 10 DUP(0)
data ends
code segment
start:
mov ax,DATA ;用ds:si指向字符串
mov ds,ax
mov dx,0000H
mov ax,12666
call dtoc
mov dh,13 ;传入参数并调用函数
mov dl,40
mov cl,2
call show_str
MOV AX,4C00H
INT 21H
DTOC: ;函数介绍:将DWORD型数据转化为十进制字符串放入DS中(从DS:0遍历到DS:X == 0结尾),参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位)
MOV CX,10
CALL DIVDW
ADD CX,30H
MOV WORD PTR DS:[SI],CX
PUSH DS:[SI]
INC SI
MOV CX,AX
JCXZ DTOC_END
JMP DTOC
DTOC_END:
MOV SI,0
DATA_REVERSE: ;当栈不为空(持续弹出到只剩下IP)时,将栈中各值顺序打入DS
POP DS:[SI]
INC SI
MOV BP,SP
MOV BX,SS:[BP]
MOV CX,BX
JCXZ DR_END
JMP DATA_REVERSE
DR_END:
MOV BX,DS:[SI-1] ;用BX存取RET用的IP
MOV CX,0
MOV [SI-1],CX ;这里发现虚拟机DOS系统不允许直接向内存赋值,就改成寄存器了
PUSH BX
MOV SI,0
RET
DIVDW: ;函数介绍:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型,参数为DX(DWORD型数据高16位),AX(DWORD型数据低16位),CX(除数,WORD型)
PUSH AX
MOV AX,DX ;先计算高16位
MOV DX,0
DIV CX
MOV BX,AX ;暂时用BX存放结果的高16位,这时DX要存放运算数
POP AX
DIV CX ;计算出结果低16位
MOV CX,DX ;CX存放余数
MOV DX,BX ;DX存放结果高16位
MOV BX,0
RET
show_str:
mov bx,0B800H ;用es:bx+di指向显存
mov es,bx
mov ax,00a0h ;根据行和列计算在显存中相应的偏移地址,存入bx中
mul dh
sub ax,00a0h
mov bx,0
mov bl,dl
add bx,bx
sub bx,2
add ax,bx
mov bx,ax
show_str_start:
mov ah,cl
mov al,ds:[si]
mov es:[bx + di],ax ;用ax装字符和属性,并经过ES写入显存中
inc si ;指向下一个字符
add di,2
mov [bp],cx ;等效替代push cx
mov cl,ds:[si] ;指向下一个字符后,检查是否到底(即是否为0)
mov ch,0
jcxz show_str_end
mov cx,[bp] ;等效替代pop cx
jmp show_str_start
show_str_end: ;还原子程序中用到的参数
mov si,0
mov di,0
mov bx,0
ret
code ends
end start
初次运行发现不出END,对这个程序再做了一些探究,发现只要把
mov ax,DATA ;用ds:si指向字符串mov ds,ax
注释掉后,程序正常出END,且正常显示了子程序对参数的处理结果:
也就是不让ds指向data就行了,跟编译器用不了push pop什么没关系,疑似是程序不允许往已经声明好的数据段内赋值,不知道为什么。
现在才发现,课本实验题的子程序程序描述上有把ds指向data的语句,会导致卡程序,最后发现自己写的程序并用不着声明data给ds,自己就这么被课本坑了几个小时,真是无语。
以上是 《汇编语言》实验10,dtoc子程序在编译连接都正常的情况下弄崩了虚拟机 的全部内容, 来源链接: utcz.com/a/107618.html