Ensamblaje 8086 en DOSBox: ¿Error con la instrucción idiv?
Estaba ayudando a un amigo mío a depurar su programa y lo redujimos a un problema que ocurre incluso aquí:
.MODEL small
.STACK 16
.CODE
start:
mov ax, 044c0h
mov bl, 85
idiv bl
exit:
mov ax, 4c00h
int 21h
end start
Después de ensamblarlo con tasm 4.1 y ejecutarlo en DOSBox 0.74, entra en un bucle infinito. Al inspeccionarlo con turbo debugger se puede ver que sucede después de la idiv
instrucción, que por alguna razón modifica los registros cs
y ip
, y luego de dos instrucciones aparentemente aleatorias los restaura para que apunten a la idiv
línea, ejecutándolo nuevamente hasta el infinito.
¿Alguien tiene alguna explicación para esto?
Esta pregunta es una variación de otras fallas relacionadas con la división. La x86
etiqueta wiki tiene algunos enlaces adicionales:
idiv
/div
problemas : ceroedx
primero o firmar y extendereax
. . 32 bitsdiv
falla si el cociente 64b/32b => 32b en realidad no cabe en 32b.
El código aparentemente aleatorio al que parece saltar su depurador es el controlador de excepciones aritméticas (también el mismo que Divide por cero). Lo que sucede es que su código está experimentando un error Division Overflow
. Estás haciendo un IDIV de 16 bits/8 bits . De la documentación:
Con signo divide AX por r/m8, con el resultado almacenado en: AL ← Cociente, AH ← Resto.
Notarás que para la división con un divisor de 8 bits (en tu caso BL ), el rango del cociente es de -128 a +127. 044c0h IDIV 85 es 207 (decimal). 207 no cabe en un registro de 8 bits firmado, por lo que se produce un desbordamiento de división y la causa del problema inesperado.
Para resolver esto, puede subir a un divisor de 16 bits. Entonces puedes colocar tu divisor en BX (registro de 16 bits). Eso sería mov bx, 85
. Desgraciadamente no es tan sencillo. Cuando se utiliza un divisor de 16 bits, el procesador asume que el dividendo es de 32 bits, con 16 bits altos en DX y 16 bits bajos en AX .
Divida con signo DX:AX por r/m16, con el resultado almacenado en AX ← Cociente, DX ← Resto.
Para resolver esto, debe firmar y ampliar el valor de 16 bits en AX . Esto es simple ya que solo necesita usar la instrucción CWD después de colocar el valor en AX . De la referencia del conjunto de instrucciones
DX:AX ← signo-extensión de AX.
Efectivamente, si el bit más significativo (MSB) de AX es 0, DX se convertirá en 0. Si el MSB es 1, entonces DX se convertirá en 0ffffh (todos los bits establecidos en uno). El bit de signo de un número es el MSB.
Con todo esto en mente, su código de división podría ajustarse para tomar un divisor de 16 bits:
mov ax, 044c0h
cwd ; Sign extend AX into DX (DX:AX = 32-bit dividend)
mov bx, 85 ; Divisor is 85
idiv bx ; Signed divide of DX:AX by BX