使用自己的键盘中断 `int 09h` 处理程序时代码的奇怪行为(损坏的绘图)
weird behaviour of code (corrupted draw) when using own keyboard interrupt `int 09h` handler
我正在为大学做一项作业,我们需要创建一个简单的 breakout/arkanoid 克隆,一切顺利,但我发现了一个会删除屏幕上所有内容的错误,这个错误是随机但我怀疑它与我的 DrawPaddle 函数有关。也许您可以发现错误或了解视频内存为什么会这样。
游戏必须使用 16 位 ms-dos 程序集来完成,我正在使用 NASM + VAL + Dosbox 来创建它,我编译它:
nasm -f obj test.asm
val test.obj
游戏只是使用键盘箭头在固定屏幕上移动球拍,您也可以按退出键退出游戏。
这是一切都还好的时候:https://puu.sh/yeKtG/affc912d4b.png and it looks like this when the program overflows: http://puu.sh/yeKEy/caeef089d1.png or http://puu.sh/yeKJH/1106e1e823.png
我注意到奇怪的行为只在我移动桨时发生,并且它会随机发生,例如,现在我从程序中删除了几乎所有其他东西,它可能需要几次尝试才能找到错误。
这是 DrawPaddle 代码:
DrawPaddle:
push di
mov di, [paddleposition]
mov cx, 5 ;the paddle will be 5 pixels tall
.p0:
push cx
mov cx, paddlesize
.p1:
mov byte [es:di], bl
inc di
loop .p1
add di, screenweight - paddlesize
pop cx
loop .p0
pop di
ret
这是完整的代码,它使用键盘处理程序读取输入并将使用 320x200x256 直接写入显存。
BITS 16
stacksize EQU 0200h
;Constantes
;Direccion de inicio de la memoria de video
videobase EQU 0a000h
;Definicion de colores
black EQU 0
green EQU 00110000b
;Screen data
screenweight EQU 320
;Paddle data
startx EQU 140
starty EQU 170
paddlesize EQU 40
paddlecolor EQU 00101010b
;Paddle movement limits
leftlimit EQU starty * screenweight + 1 + 10 + 1
rightlimit EQU ((starty + 1) * screenweight) - paddlesize - 10 - 1
segment mystack stack
resb stacksize
stacktop:
segment mydata data
;Variables
escpressed dw 0
leftpressed dw 0
rightpressed dw 0
oldintseg resw 1
oldintoff resw 1
originalVideoMode resb 1
paddleposition resw 1
segment mycode code
;Subrutinas
KeybInt:
push ds ;guardamos ds:ax
push ax
mov ax, mydata ;los re-inicializamos
mov ds, ax
cli
.getstatus:
in al, 64h
test al, 02h
loopnz .getstatus ;esperando a que el puerto esté listo
in al,60h ;obtenemos el codigo make o break de la tecla leida
cmp al, 01h ;revisamos si es escape
jne .revEsc
mov word [escpressed], 1
jmp .kbread
.revEsc:
cmp al, 81h ;revisamos si el escape fue soltado
jne .revIzq
mov word [escpressed], 0
jmp .kbread
.revIzq:
cmp al, 4bh ;revisamos si es la flecha izquierda
jne .revDer
mov word [leftpressed], 1
jmp .kbread
.revDer:
cmp al, 4dh ;revisamos si es la flecha derecha
jne .revIzq2
mov word [rightpressed], 1
jmp .kbread
.revIzq2:
cmp al, 0cbh ;si se solto la flecha izquierda
jne .revDer2
mov word [leftpressed], 0
jmp .kbread
.revDer2:
cmp al, 0cdh ;o la derecha
jne .kbread
mov word [rightpressed], 0
jmp .kbread
.kbread:
in al, 61h
or al, 10000000b
out 61h, al
and al, 01111111b
out 61h, al
mov al, 20h
out 20h, al
sti
pop ax ;recuperamos ds:ax
pop ds
iret
DrawStage:
push di
push bx
;movemos el cursor a la posicion 10,10
;que seria en realidad 10*320+10
mov di, (10 * screenweight) + 10
;ahora repetiremos esto 320-20 veces
mov cx, 300
.h1:
mov byte [es:di], green
inc di
loop .h1
mov di, (190 * screenweight) + 10
;ahora repetiremos esto 320-20 veces
mov cx, 301
.h2:
mov byte [es:di], green
inc di
loop .h2
;ahora volveremos al primer punto
;y dibujaremos hacia abajo
mov di, (10 * screenweight) + 10
;y lo repetiremos 200-20 veces
mov cx, 180
.v1:
mov byte [es:di], green
add di, screenweight
loop .v1
mov di, (10 * screenweight) + 310
mov cx, 180
.v2:
mov byte [es:di], green
add di, screenweight
loop .v2
pop bx
pop di
ret
;Rutina para dibujar el palo
;Recibe en bl el color del mismo
DrawPaddle:
push di
mov di, [paddleposition]
mov cx, 5 ;the paddle will be 5 pixels tall
.p0:
push cx
mov cx, paddlesize
.p1:
mov byte [es:di], bl
inc di
loop .p1
add di, screenweight - paddlesize
pop cx
loop .p0
pop di
ret
Delay1:
mov dx, 4
sub dx, 3
.pause1:
mov cx, 6000
.pause2:
dec cx
jne .pause2
dec dx
jne .pause1
ret
..start:
mov ax, mydata
mov ds, ax
mov ax, mystack
mov ss, ax
mov sp, stacktop
;guardando el manejador actual
mov ah, 35h
mov al, 9h
int 21h
mov [oldintseg], es
mov [oldintoff], bx
;instalando el manejador nuevo
mov ax, mycode
mov es, ax
mov dx, KeybInt
mov ax, cs
mov ds, ax
mov ah, 25h
mov al, 9h
int 21h
;restaurando el segmento de datos
mov ax, mydata
mov ds, ax
;guardando el modo de video y aplicando el nuevo
xor ax, ax
mov ah, 0fh
int 10h
mov [originalVideoMode], al
mov ah, 00h
mov al, 13h
int 10h
;coordenada de inicio para el palo
mov ax, (screenweight * starty) + startx
mov word [paddleposition], ax
mov ax, videobase
mov es, ax
call DrawStage
mov bl, paddlecolor
call DrawPaddle
jmp .main
.main:
call Delay1
;leemos las entradas
cmp word [escpressed], 1
je .dosexit
cmp word [rightpressed], 1
je .movRight
cmp word [leftpressed], 1
je .movLeft
jmp .main
.movRight:
mov bl, black
call DrawPaddle
cmp word [paddleposition], rightlimit
je .ending
inc word [paddleposition]
jmp .ending
.movLeft:
mov bl, black
call DrawPaddle
cmp word [paddleposition], leftlimit
je .ending
dec word [paddleposition]
jmp .ending
.ending:
mov bl, paddlecolor
call DrawPaddle
jmp .main
.dosexit:
;restaurando el modo de video original
mov ah, 00h
mov byte al, [originalVideoMode]
int 10h
;restaurando el manejador de teclado original
mov dx, [oldintoff]
mov ax, [oldintseg]
mov ds, ax
mov ah, 25h
mov al, 9h
int 21h
mov al, 0
mov ah, 4ch
int 21h
感谢阅读!
您在键盘中断中修改了 cx
而没有保留它。
^^^ 这是 ANSWER(导致您的错误的原因),而不仅仅是一些 建议
这里有一些建议如下:
而且中断中有任何循环(动态延迟)感觉不对,中断应尽快进行。
我记不清读取键盘 0x6X 端口的正确方法是什么(我只记得 有点棘手,要让它完全正确),所以我不会检查特定的 in/out
序列及其正确性。
但是如果你将 XXXpressed
设置为当前状态中断,主循环会太慢,它可能看不到很短的按键(因为输入没有缓冲)。对于像打砖块克隆这样的简单游戏来说,这是可以的,而且我根本不会为此感到困扰,在我看来这是正确的行为(实际上你需要非常快才能保持这么短的键)。
您还可以通过在中断代码处理程序附近保留一些数据 space 来避免 ds
在中断中设置(将 escpressed dw 0
移动到 iret
之后的代码部分),然后在任何地方都使用它作为 mov word [cs:escpressed], 1
,等等。如果您实际上以更有效的方式设置内存标志,那么在中断内部使用 cs:
寻址的总惩罚将低于 ds
设置和简短的中断代码(可以简化很多)。
有趣的是,您在所有主循环中广泛使用 slow loop
instruction,但随后在 delay
子例程中,您使用更快的 dec cx
jnz ...
替代方法。
最后我确实检查了如何编写 DOS 键盘处理程序,所以这是我的建议(不幸的是我没有测试它,如果它有效):
segment mycode code
escpressed db 0
leftpressed db 0
rightpressed db 0
KeybInt:
cli
push ax ;guardamos ax
; when IRQ1 is fired, int 9 is called to handle it and the input
; already waits on port 0x60, no need to validate IBF flag on 0x64
in al,60h ;obtenemos el codigo make o break de la tecla leida
mov ah,al
and al,0x7F ; AL = scan code without pressed/released flag
shr ah,7
xor ah,1 ; AH = 1/0 pressed/released
cmp al, 01h ;revisamos si es escape
jne .checkLeft
mov [cs:escpressed], ah
jmp .kbread
.checkLeft:
cmp al, 4bh ;revisamos si es la flecha izquierda
jne .checkRight
mov [cs:leftpressed], ah
jmp .kbread
.checkRight:
cmp al, 4dh ;revisamos si es la flecha derecha
jne .kbread
mov [cs:rightpressed], ah
.kbread:
in al, 61h
mov ah, al ; store original value
or al, 10000000b
out 61h, al ; set "enable kbd" bit
mov al, ah
out 61h, al ; set original value back
mov al, 20h
out 20h, al ; send end-of-interrupt signal to 8259 IC
pop ax ;recuperamos ax
sti ; not needed in real x86 real mode, IRET restores flags
iret ; but explicit STI paired with CLI may help some VMs
...然后在游戏代码中,要检查键的状态,您也必须使用 cs
:
...
cmp byte [cs:escpressed], 1
...
我正在为大学做一项作业,我们需要创建一个简单的 breakout/arkanoid 克隆,一切顺利,但我发现了一个会删除屏幕上所有内容的错误,这个错误是随机但我怀疑它与我的 DrawPaddle 函数有关。也许您可以发现错误或了解视频内存为什么会这样。
游戏必须使用 16 位 ms-dos 程序集来完成,我正在使用 NASM + VAL + Dosbox 来创建它,我编译它:
nasm -f obj test.asm
val test.obj
游戏只是使用键盘箭头在固定屏幕上移动球拍,您也可以按退出键退出游戏。
这是一切都还好的时候:https://puu.sh/yeKtG/affc912d4b.png and it looks like this when the program overflows: http://puu.sh/yeKEy/caeef089d1.png or http://puu.sh/yeKJH/1106e1e823.png
我注意到奇怪的行为只在我移动桨时发生,并且它会随机发生,例如,现在我从程序中删除了几乎所有其他东西,它可能需要几次尝试才能找到错误。
这是 DrawPaddle 代码:
DrawPaddle:
push di
mov di, [paddleposition]
mov cx, 5 ;the paddle will be 5 pixels tall
.p0:
push cx
mov cx, paddlesize
.p1:
mov byte [es:di], bl
inc di
loop .p1
add di, screenweight - paddlesize
pop cx
loop .p0
pop di
ret
这是完整的代码,它使用键盘处理程序读取输入并将使用 320x200x256 直接写入显存。
BITS 16
stacksize EQU 0200h
;Constantes
;Direccion de inicio de la memoria de video
videobase EQU 0a000h
;Definicion de colores
black EQU 0
green EQU 00110000b
;Screen data
screenweight EQU 320
;Paddle data
startx EQU 140
starty EQU 170
paddlesize EQU 40
paddlecolor EQU 00101010b
;Paddle movement limits
leftlimit EQU starty * screenweight + 1 + 10 + 1
rightlimit EQU ((starty + 1) * screenweight) - paddlesize - 10 - 1
segment mystack stack
resb stacksize
stacktop:
segment mydata data
;Variables
escpressed dw 0
leftpressed dw 0
rightpressed dw 0
oldintseg resw 1
oldintoff resw 1
originalVideoMode resb 1
paddleposition resw 1
segment mycode code
;Subrutinas
KeybInt:
push ds ;guardamos ds:ax
push ax
mov ax, mydata ;los re-inicializamos
mov ds, ax
cli
.getstatus:
in al, 64h
test al, 02h
loopnz .getstatus ;esperando a que el puerto esté listo
in al,60h ;obtenemos el codigo make o break de la tecla leida
cmp al, 01h ;revisamos si es escape
jne .revEsc
mov word [escpressed], 1
jmp .kbread
.revEsc:
cmp al, 81h ;revisamos si el escape fue soltado
jne .revIzq
mov word [escpressed], 0
jmp .kbread
.revIzq:
cmp al, 4bh ;revisamos si es la flecha izquierda
jne .revDer
mov word [leftpressed], 1
jmp .kbread
.revDer:
cmp al, 4dh ;revisamos si es la flecha derecha
jne .revIzq2
mov word [rightpressed], 1
jmp .kbread
.revIzq2:
cmp al, 0cbh ;si se solto la flecha izquierda
jne .revDer2
mov word [leftpressed], 0
jmp .kbread
.revDer2:
cmp al, 0cdh ;o la derecha
jne .kbread
mov word [rightpressed], 0
jmp .kbread
.kbread:
in al, 61h
or al, 10000000b
out 61h, al
and al, 01111111b
out 61h, al
mov al, 20h
out 20h, al
sti
pop ax ;recuperamos ds:ax
pop ds
iret
DrawStage:
push di
push bx
;movemos el cursor a la posicion 10,10
;que seria en realidad 10*320+10
mov di, (10 * screenweight) + 10
;ahora repetiremos esto 320-20 veces
mov cx, 300
.h1:
mov byte [es:di], green
inc di
loop .h1
mov di, (190 * screenweight) + 10
;ahora repetiremos esto 320-20 veces
mov cx, 301
.h2:
mov byte [es:di], green
inc di
loop .h2
;ahora volveremos al primer punto
;y dibujaremos hacia abajo
mov di, (10 * screenweight) + 10
;y lo repetiremos 200-20 veces
mov cx, 180
.v1:
mov byte [es:di], green
add di, screenweight
loop .v1
mov di, (10 * screenweight) + 310
mov cx, 180
.v2:
mov byte [es:di], green
add di, screenweight
loop .v2
pop bx
pop di
ret
;Rutina para dibujar el palo
;Recibe en bl el color del mismo
DrawPaddle:
push di
mov di, [paddleposition]
mov cx, 5 ;the paddle will be 5 pixels tall
.p0:
push cx
mov cx, paddlesize
.p1:
mov byte [es:di], bl
inc di
loop .p1
add di, screenweight - paddlesize
pop cx
loop .p0
pop di
ret
Delay1:
mov dx, 4
sub dx, 3
.pause1:
mov cx, 6000
.pause2:
dec cx
jne .pause2
dec dx
jne .pause1
ret
..start:
mov ax, mydata
mov ds, ax
mov ax, mystack
mov ss, ax
mov sp, stacktop
;guardando el manejador actual
mov ah, 35h
mov al, 9h
int 21h
mov [oldintseg], es
mov [oldintoff], bx
;instalando el manejador nuevo
mov ax, mycode
mov es, ax
mov dx, KeybInt
mov ax, cs
mov ds, ax
mov ah, 25h
mov al, 9h
int 21h
;restaurando el segmento de datos
mov ax, mydata
mov ds, ax
;guardando el modo de video y aplicando el nuevo
xor ax, ax
mov ah, 0fh
int 10h
mov [originalVideoMode], al
mov ah, 00h
mov al, 13h
int 10h
;coordenada de inicio para el palo
mov ax, (screenweight * starty) + startx
mov word [paddleposition], ax
mov ax, videobase
mov es, ax
call DrawStage
mov bl, paddlecolor
call DrawPaddle
jmp .main
.main:
call Delay1
;leemos las entradas
cmp word [escpressed], 1
je .dosexit
cmp word [rightpressed], 1
je .movRight
cmp word [leftpressed], 1
je .movLeft
jmp .main
.movRight:
mov bl, black
call DrawPaddle
cmp word [paddleposition], rightlimit
je .ending
inc word [paddleposition]
jmp .ending
.movLeft:
mov bl, black
call DrawPaddle
cmp word [paddleposition], leftlimit
je .ending
dec word [paddleposition]
jmp .ending
.ending:
mov bl, paddlecolor
call DrawPaddle
jmp .main
.dosexit:
;restaurando el modo de video original
mov ah, 00h
mov byte al, [originalVideoMode]
int 10h
;restaurando el manejador de teclado original
mov dx, [oldintoff]
mov ax, [oldintseg]
mov ds, ax
mov ah, 25h
mov al, 9h
int 21h
mov al, 0
mov ah, 4ch
int 21h
感谢阅读!
您在键盘中断中修改了 cx
而没有保留它。
^^^ 这是 ANSWER(导致您的错误的原因),而不仅仅是一些 建议
这里有一些建议如下:
而且中断中有任何循环(动态延迟)感觉不对,中断应尽快进行。
我记不清读取键盘 0x6X 端口的正确方法是什么(我只记得 有点棘手,要让它完全正确),所以我不会检查特定的 in/out
序列及其正确性。
但是如果你将 XXXpressed
设置为当前状态中断,主循环会太慢,它可能看不到很短的按键(因为输入没有缓冲)。对于像打砖块克隆这样的简单游戏来说,这是可以的,而且我根本不会为此感到困扰,在我看来这是正确的行为(实际上你需要非常快才能保持这么短的键)。
您还可以通过在中断代码处理程序附近保留一些数据 space 来避免 ds
在中断中设置(将 escpressed dw 0
移动到 iret
之后的代码部分),然后在任何地方都使用它作为 mov word [cs:escpressed], 1
,等等。如果您实际上以更有效的方式设置内存标志,那么在中断内部使用 cs:
寻址的总惩罚将低于 ds
设置和简短的中断代码(可以简化很多)。
有趣的是,您在所有主循环中广泛使用 slow loop
instruction,但随后在 delay
子例程中,您使用更快的 dec cx
jnz ...
替代方法。
最后我确实检查了如何编写 DOS 键盘处理程序,所以这是我的建议(不幸的是我没有测试它,如果它有效):
segment mycode code
escpressed db 0
leftpressed db 0
rightpressed db 0
KeybInt:
cli
push ax ;guardamos ax
; when IRQ1 is fired, int 9 is called to handle it and the input
; already waits on port 0x60, no need to validate IBF flag on 0x64
in al,60h ;obtenemos el codigo make o break de la tecla leida
mov ah,al
and al,0x7F ; AL = scan code without pressed/released flag
shr ah,7
xor ah,1 ; AH = 1/0 pressed/released
cmp al, 01h ;revisamos si es escape
jne .checkLeft
mov [cs:escpressed], ah
jmp .kbread
.checkLeft:
cmp al, 4bh ;revisamos si es la flecha izquierda
jne .checkRight
mov [cs:leftpressed], ah
jmp .kbread
.checkRight:
cmp al, 4dh ;revisamos si es la flecha derecha
jne .kbread
mov [cs:rightpressed], ah
.kbread:
in al, 61h
mov ah, al ; store original value
or al, 10000000b
out 61h, al ; set "enable kbd" bit
mov al, ah
out 61h, al ; set original value back
mov al, 20h
out 20h, al ; send end-of-interrupt signal to 8259 IC
pop ax ;recuperamos ax
sti ; not needed in real x86 real mode, IRET restores flags
iret ; but explicit STI paired with CLI may help some VMs
...然后在游戏代码中,要检查键的状态,您也必须使用 cs
:
...
cmp byte [cs:escpressed], 1
...