程序集 – 使用自己的键盘中断`int 09h`处理程序时代码的奇怪行为(损坏的绘制)

前端之家收集整理的这篇文章主要介绍了程序集 – 使用自己的键盘中断`int 09h`处理程序时代码的奇怪行为(损坏的绘制)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我正在为univesity工作,我们需要创建一个简单的breakout / arkanoid克隆,它很顺利但是我发现了一个错误删除屏幕上的所有内容,这个bug是随机的,但我怀疑它与我的DrawPaddle功能.也许你可以发现错误或了解为什么视频内存会这样做.

游戏必须使用16位ms-dos程序集完成,我使用NASM VAL DosBox创建它,我用以下代码编译它:

nasm -f obj test.asm
val test.obj

游戏只是使用键盘箭头在固定屏幕上移动球拍,您也可以通过按下退出退出游戏.

这是一切都还好:https://puu.sh/yeKtG/affc912d4b.png,当程序溢出时它看起来像这样:http://puu.sh/yeKEy/caeef089d1.pnghttp://puu.sh/yeKJH/1106e1e823.png

我注意到奇怪的行为只发生在我移动桨时它会随机发生,例如现在我从程序中删除了几乎所有其他东西,它可能需要几次尝试来获取bug.

这是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],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,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,ax

    ;guardando el modo de video y aplicando el nuevo
    xor ax,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],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,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,9h
    int 21h
    mov al,0
    mov ah,4ch
    int 21h

谢谢阅读!

您可以在键盘中断中修改cx而不保留它.

^^^这是答案(导致你的错误的原因),而不仅仅是一些建议

这里有一些建议如下:

在中断中有任何循环(动态延迟)也是错误的,中断应该尽可能快地进行.

我不记得从头部读取键盘的0x6X端口的正确方法(我只记得它有点棘手,让它完全正确),所以我不打算检查特定的输入/输出序列及其正确性.

但是如果你将在实际当前状态中设置XXXpressed中断,并且主循环将太慢,则可能看不到非常短的按键(因为输入没有被缓冲).对于像arkanoid clone一样的简单游戏,这是可以的,我根本不会被这个打扰,听起来像是正确的行为(你需要实际上非常快地把钥匙保持得这么短).

您还可以通过在中断代码处理程序附近保留一些数据空间(在iret之后将escpressed dw 0移动到代码部分中)来避免中断中的ds设置,然后将其用作mov word [cs:escpressed],1等.如果您实际上以更有效的方式设置内存标志和短中断代码(可以简化很多),那么使用cs的代价:内部中断寻址将低于ds设置.

你有多广泛地将slow loop instruction用于所有主循环,但是在延迟子程序中你做了更快的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,al         ; set "enable kbd" bit
        mov     al,ah
        out     61h,al         ; set original value back

        mov     al,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
        ...

猜你在找的Windows相关文章