《汇编语言》实验10,dtoc子程序在编译连接都正常的情况下弄崩了虚拟机

2021-01-25 10:41发布

问题:

系统为WINDOS10系统,运行环境有两个:vscode上的DOSBOX扩展和的vm虚拟机的MS-DOS系统
平时就在vscode上写,发现dosbox环境上程序运行出不来END后放到虚拟机上想知道是什么原因,结果程序直接被弄崩了。
第3题的数值显示,除了dtoc函数其他都正常,希望有朋友帮我看看是怎么回事。

assume cs:code 
data 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


回答1:

开始研究

检查修改了下自己的程序,发现

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:CODE
DATA 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,自己就这么被课本坑了几个小时,真是无语。