8086 на DOSBox: ошибка с инструкцией idiv?

Я помогал моему другу отлаживать свою программу, и мы сузили ее до проблемы, которая возникает даже здесь:

.MODEL small
.STACK 16
.CODE
start:
    mov ax, 044c0h
    mov bl, 85
    idiv bl
exit:
    mov ax, 4c00h
    int 21h

end start

После сборки с tasm 4.1 и запуска его на DOSBox 0.74, он переходит в бесконечный цикл. Когда вы проверяете его с помощью отладочного турбонаддува, можно увидеть, что это происходит после инструкции idiv, которая по какой-то причине изменяет регистры cs и ip, и после двух кажущихся случайными инструкций восстанавливает их, указывая на строку idiv выполняя его снова до бесконечности.

Есть ли у кого-нибудь объяснения?

Ответ 1

Этот вопрос является вариацией на другие неудачи, связанные с делением. В x86 теге wiki есть дополнительные ссылки:


По-видимому, случайный код, к которому, по-видимому, переключается ваш отладчик, - это обработчик арифметических исключений (также тот же, что и Divide by Zero). Случается, что ваш код испытывает Division Overflow. Вы делаете 16-битный/8-битный IDIV. Из документации:

Подписанное разделение AX на r/m8, с результатом, хранящимся в: AL ← Quotient, AH ← Remainder.

введите описание изображения здесь

Вы заметите, что для деления с 8-битным делителем (в вашем случае BL) диапазон для частного составляет от -128 до +127. 044c0h IDIV 85 - 207 (десятичный). 207 не вписывается в подписанный 8-разрядный регистр, поэтому вы получаете переполнение деления и причину своей неожиданной проблемы.

Чтобы решить эту проблему, вы можете перейти к 16-разрядному делителю. Таким образом, вы можете поместить свой делитель в BX (16-разрядный регистр). Это будет mov bx, 85. К сожалению, это не так просто. При использовании 16-разрядного делителя процессор предполагает, что дивиденд составляет 32 бита с высокими 16-бит в DX и младшими 16-разрядными в AX.

Подписанное разделение DX: AX на r/m16, с результатом, хранящимся в AX ← Quotient, DX ← Remainder.

Чтобы решить эту проблему, вы должны подписать расширение 16-битного значения в AX. Это просто, поскольку вам нужно использовать команду CWD после размещения значения в AX. Из ссылки на набор инструкций

DX: AX ← sign-extend от AX.

Эффективно, если самый значительный бит (MSB) AX равен 0 DX, станет 0. Если MSB равен 1, то DX станет 0ffffh (все биты установлены на единицу). Битом знака числа является MSB.

Учитывая это, ваш код разделения можно настроить так, чтобы он принял 16-разрядный делитель:

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