Отображение времени в сборке
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".
Ответ Хоббса показывает, как это сделать.
Вот еще один учебник