在程序集中显示时间
Displaying Time in Assembly
你好,我正在尝试显示实际时间hours/minutes/seconds这是我的代码示例:
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
在这里您可以看到控制台显示的内容
您需要一个打印例程来将字节作为数字打印,而不是将它们作为字符直接写入屏幕。幸运的是,因为您只需要处理 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
寄存器中的值(时间)显示为 aschi 字符。
假设您的 CH
包含值 15h。
现在,当您将此值放入AL
并调用int 10h
时,它会显示与值15h 匹配的aschi 字符。如果你想显示 15h 的十进制数字,那么你将不得不调用一个显示例程,它会破坏寄存器中的值并提取其中的数字来显示 - 而不仅仅是该值中的 aschi 表示。
假设您的寄存器中有十进制数 85。现在您需要在屏幕“8”和“5”上打印。因此,您必须将值 85 转换为 aschi-56("8") 和 aschii-53("5")。然后依次放入寄存器,调用两次int 10
显示“85”。
霍布斯的回答展示了如何做到这一点。
Here是另一个教程
请参阅 x86 tag wiki 指令集参考手册,以及许多很好的参考链接 material 和教程。
将整数拆分为 ASCII 数字需要足够的代码,您应该将其分解为一个函数。
这是@hobbs 的 print2Digits 函数的优化和错误修复版本。 (我还在他的回答中修复了版本错误,所以它也是正确的,但留下了这个的优化)。
print2Digits:
;; input in AL (0-99). (Or preferably already zero-extended to AX so we can omit CBW)
;; clobbers AX and DX
cbw ; zero AH. Sign-extending AL does the job because AL is only allowed to be 0-99.
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 ; BIOS call #: print 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
为零。即,股息在 AX 中,而不仅仅是在 AH 中可能存在垃圾的 AL。我们可以保存 cbw
或 mov ah,0
如果调用者将 movzx ax, ch
或其他内容设为零 ah
.
(除了 8086 没有 movzx
,所以你实际上想要 xor ax,ax
/ mov al, ch
。)
有一个用于打印整个字符串的 DOS 系统调用,因此您可以将字符存储到一个小缓冲区中并一次打印它们,就像我在这个 . See also How do I print an integer in Assembly Level Programming without printf from the c library? for a more general int->string in a buffer function, or other multi-digit number links in the x86 tag wiki
中所做的那样
还可以使用aam
将 AL(而不是 AX)除以 10,避免首先将 AH 归零。它比当前 Intel 和 AMD CPU 上的 div r8
稍快。但是,它将结果放在 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 ; BIOS call #: print 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
即使我们想存储到一个字符串(并调用一个打印字符串函数或系统调用),我们也必须在将 AX 存储到内存之前交换 al 和 ah(例如 xchg al,ah
,或在现代硬件上效率更高但需要 186:rol ax,8
)。 div
在 AX 中以正确的顺序生成它们。
对于 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 ; then get ready to print the high byte first
lea
需要一个地址大小前缀和一个 2 字节 mod/rm,以及一个 32 位位移,因此它在代码大小上损失严重,但它确实节省了一条指令。
在 div
写入 ax
之后使用 lea
从 eax
读取在 Sandybridge 系列 CPU 上可能会更快,尤其是。 Haswell 及更高版本,但在 Intel pre-SnB 上,部分寄存器停顿将使使用具有单独的 add 和 mov 指令的纯 16 位版本更好。
当然,如果您真的关心性能,您会使用乘法逆而不是实际除以 10。而且您通常不会编写 16 位代码这也使得传统的 BIOS 调用!
你好,我正在尝试显示实际时间hours/minutes/seconds这是我的代码示例:
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
在这里您可以看到控制台显示的内容
您需要一个打印例程来将字节作为数字打印,而不是将它们作为字符直接写入屏幕。幸运的是,因为您只需要处理 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
寄存器中的值(时间)显示为 aschi 字符。
假设您的 CH
包含值 15h。
现在,当您将此值放入AL
并调用int 10h
时,它会显示与值15h 匹配的aschi 字符。如果你想显示 15h 的十进制数字,那么你将不得不调用一个显示例程,它会破坏寄存器中的值并提取其中的数字来显示 - 而不仅仅是该值中的 aschi 表示。
假设您的寄存器中有十进制数 85。现在您需要在屏幕“8”和“5”上打印。因此,您必须将值 85 转换为 aschi-56("8") 和 aschii-53("5")。然后依次放入寄存器,调用两次int 10
显示“85”。
霍布斯的回答展示了如何做到这一点。
Here是另一个教程
请参阅 x86 tag wiki 指令集参考手册,以及许多很好的参考链接 material 和教程。
将整数拆分为 ASCII 数字需要足够的代码,您应该将其分解为一个函数。
这是@hobbs 的 print2Digits 函数的优化和错误修复版本。 (我还在他的回答中修复了版本错误,所以它也是正确的,但留下了这个的优化)。
print2Digits:
;; input in AL (0-99). (Or preferably already zero-extended to AX so we can omit CBW)
;; clobbers AX and DX
cbw ; zero AH. Sign-extending AL does the job because AL is only allowed to be 0-99.
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 ; BIOS call #: print 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
为零。即,股息在 AX 中,而不仅仅是在 AH 中可能存在垃圾的 AL。我们可以保存 cbw
或 mov ah,0
如果调用者将 movzx ax, ch
或其他内容设为零 ah
.
(除了 8086 没有 movzx
,所以你实际上想要 xor ax,ax
/ mov al, ch
。)
有一个用于打印整个字符串的 DOS 系统调用,因此您可以将字符存储到一个小缓冲区中并一次打印它们,就像我在这个
还可以使用aam
将 AL(而不是 AX)除以 10,避免首先将 AH 归零。它比当前 Intel 和 AMD CPU 上的 div r8
稍快。但是,它将结果放在 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 ; BIOS call #: print 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
即使我们想存储到一个字符串(并调用一个打印字符串函数或系统调用),我们也必须在将 AX 存储到内存之前交换 al 和 ah(例如 xchg al,ah
,或在现代硬件上效率更高但需要 186:rol ax,8
)。 div
在 AX 中以正确的顺序生成它们。
对于 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 ; then get ready to print the high byte first
lea
需要一个地址大小前缀和一个 2 字节 mod/rm,以及一个 32 位位移,因此它在代码大小上损失严重,但它确实节省了一条指令。
在 div
写入 ax
之后使用 lea
从 eax
读取在 Sandybridge 系列 CPU 上可能会更快,尤其是。 Haswell 及更高版本,但在 Intel pre-SnB 上,部分寄存器停顿将使使用具有单独的 add 和 mov 指令的纯 16 位版本更好。
当然,如果您真的关心性能,您会使用乘法逆而不是实际除以 10。而且您通常不会编写 16 位代码这也使得传统的 BIOS 调用!