在程序集中显示时间

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

您正在尝试将 CHCLDH 寄存器中的值(时间)显示为 aschi 字符。

假设您的 CH 包含值 15h。

现在,当您将此值放入AL 并调用int 10h 时,它会显示与值15h 匹配的aschi 字符。如果你想显示 15h 的十进制数字,那么你将不得不调用一个显示例程,它会破坏寄存器中的值并提取其中的数字来显示 - 而不仅仅是该值中的 aschi 表示。

假设您的寄存器中有十进制数 85。现在您需要在屏幕“8”和“5”上打印。因此,您必须将值 85 转换为 aschi-56("8") 和 aschii-53("5")。然后依次放入寄存器,调用两次int 10显示“85”。

霍布斯的回答展示了如何做到这一点。

Here是另一个教程

请参阅 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。我们可以保存 cbwmov 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, 10cbw.

上的节省
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 之后使用 leaeax 读取在 Sandybridge 系列 CPU 上可能会更快,尤其是。 Haswell 及更高版本,但在 Intel pre-SnB 上,部分寄存器停顿将使使用具有单独的 add 和 mov 指令的纯 16 位版本更好。

当然,如果您真的关心性能,您会使用乘法逆而不是实际除以 10。而且您通常不会编写 16 位代码这也使得传统的 BIOS 调用!