Отображение времени в сборке

Здравствуйте, я пытаюсь отобразить фактическое время часов / минут / секунд, это мой пример кода:

MOV AH, 2Ch
INT 21h

MOV AH, 0Eh

MOV AL, CH
INT 10h

MOV AL, 3Ah
INT 10h

MOV AL, CL
INT 10h

MOV AL, 3Ah
INT 10h

MOV AL, DH
INT 10h

ret

Здесь вы можете увидеть, что отображает консоль

3 ответа

Решение

См. Вики-тег x86 для справочного руководства по набору инструкций и множество хороших ссылок на справочные материалы и учебные пособия.


Требуется достаточно кода, чтобы разбить целое число на цифры ASCII, чтобы вы могли выделить его в функцию.

Это оптимизированная и исправленная версия функции print2Digits @ hobbs. (Я также исправил версию в его ответе, так что это тоже правильно, но оставил оптимизацию для этого).

print2Digits:
    ;; input in AL (0-99).
    ;; clobbers AX and DX
    cbw             ; zero AH.  Sign-extending AL does the job because AL is only allowed to be 0-99.
                    ; omit this step if you can easily have the caller use the full AX, i.e. zero AH for us

    mov   dl, 10
    div   dl        ; quotient in AL(first (high) digit), remainder in AH(second (low) digit)

    add   ax, 0x3030  ; add '0' to al and ah at the same time.
    mov   dl, ah      ; save the 2nd digit

    mov   ah, 0x0E ; DOS system call number for printing a single character
    int   0x10     ; print high digit first.  Doesn't clobber anything, so AH still holds 0x0E after
    mov   al, dl
    int   0x10     ; print the low digit 2nd
    ret

Так как мы использовали div чтобы разбить целое число на две цифры base10, нам нужно ah быть нулем. Мы могли бы сохранить cbw если звонил movzx ax, ch или что-то, к нулю ah,

(За исключением того, что 8086 не имеет movzx так что вам придется на самом деле xor ax,ax / mov al, ch.)

Существует системный вызов DOS для печати всей строки, так что вы можете хранить символы в небольшом буфере и печатать их все сразу, как я делаю в этом AMD64 Linux FizzBuzz


Также возможно использовать aam разделить AL (вместо AX) на 10, избегая необходимости сначала обнулять AH. Это немного быстрее, чем div r8 на текущих процессорах Intel и AMD. Тем не менее, он помещает результаты в противоположные регистры от div, что означает дополнительные инструкции после aam, Это уравновешивает экономию на mov dl, 10 а также cbw

print2Digits:
    ;; input in AL (0-99).  (Ignores AH because we use AAM instead of div)
    ;; clobbers AX and DX
        aam           ; like `div` by 10, but with the outputs reversed, and input from AL only
                      ; quotient in AH (high digit), remainder in AL(low digit).  (Opposite to div)

    add   ax, 0x3030  ; add '0' to al and ah at the same time.
    mov   dl, al      ; save the low digit
    mov   al, ah      ; print high digit first

    mov   ah, 0x0E    ; DOS system call number for printing a single character
    int   0x10        ; print first digit.  Doesn't clobber anything, so AH still holds 0x0E after
    mov   al, dl
    int   0x10        ; print second digit
    ret

Даже если бы мы хотели сохранить в строку (и сделать один вызов функции print-string или системного вызова), нам пришлось бы поменять местами al и ah перед сохранением AX в памяти (например, xchg al,ah или более эффективно на современном оборудовании, rol ax,8). div производит их в правильном порядке.


Для 386, где доступен 32-битный размер адреса, мы можем сохранить одну инструкцию:

lea   dx, [eax + 0x3030] ; need a 32bit addressing mode to use eax as a source reg.  Adds '0' to both digits at once, with a different destination.
mov   al, dh

lea нужен префикс размера адреса и 2-байтовый mod/rm, а также 32-битное смещение, поэтому он сильно проигрывает по размеру кода, но сохраняет одну инструкцию.

С помощью lea читать с eax после div пишет ax вероятно будет быстрее на процессорах семейства Sandybridge, особенно Haswell и более поздние версии, но на Intel pre-SnB частичная остановка регистров позволит лучше использовать чисто 16-битную версию с отдельными инструкциями add и mov.

Вам нужна процедура печати, чтобы печатать байты в виде чисел, а не записывать их прямо на экран в виде символов. К счастью, поскольку вам приходится иметь дело только со значениями от 0 до 59, а поскольку вам нужны ведущие нули, проблема довольно проста. Предполагая значение для печати в AX:

print2Digits:
    ;; input in AX (0-99)
    ;; clobbers AX and DX, save them if needed
    MOV   DL, 0Ah ; divide by: 10
    DIV   DL      ; first digit in AL (quotient), second digit in AH (remainder)
    MOV   DX, AX  ; save the digits
    ADD   AL, 30h ; ASCII '0'
    MOV   AH, 0Eh ; set up print
    INT   10h     ; print first digit.
    MOV   AL, DH  ; retrieve second digit
    ADD   AL, 30h
    INT   10h     ; print it
    RET

Вы пытаетесь отобразить значения (время) в CH, CL а также DH регистрируется как аши персонажи.

Допустим, ваш CH содержит значение 15ч.

Теперь, когда вы кладете это значение в AL и позвонить int 10h, он отображает символ aschi, который соответствует значению 15h. Если вы хотите показывать десятичные цифры в течение 15 часов, то вам нужно будет вызвать процедуру отображения, которая разбивала бы значение в регистре и извлекала бы из него цифры для отображения, а не только представление aschi в этом значении.

Допустим, у вас есть десятичное 85 в регистре. Теперь вам нужно распечатать на экране "8" и "5". Таким образом, вы должны преобразовать значение 85 в в aschi-56("8") и aschii-53("5"). Затем поместите их в регистры один за другим и позвоните int 10 дважды, чтобы отобразить "85".

Ответ Хоббса показывает, как это сделать.

Вот еще один учебник

Другие вопросы по тегам