在装配中显示时间

时间:2016-05-10 05:16:55

标签: assembly time x86-16 emu8086

您好我试图显示实际时间小时/分钟/秒这是我的代码示例:

'Leicester City'
'Tottenham Hotspur'
'Arsenal FC'
'Manchester City'
'Manchester United'
'Southampton FC'
'West Ham United'
'Liverpool FC'
'Chelsea FC'
'Stoke City'
'Swansea City'
'Everton FC'
'Watford FC'
'Crystal Palace'
'West Bromwich Albion'
'AFC Bournemouth'
'Sunderland AFC'
'Newcastle United'
'Norwich City'
'Aston Villa'
'Channel Boleyn emotion'
'Channel Boleyn emotion'

您可以在这里查看控制台正在显示的内容

enter image description here

3 个答案:

答案 0 :(得分:3)

您需要一个打印例程来将字节打印为数字,而不是将它们作为字符直接写入屏幕。幸运的是,因为您只需处理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

答案 1 :(得分:2)

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

假设您的CH包含值15h。

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

假设您在注册表中有小数85。现在你需要在屏幕上打印" 8"和" 5"。因此,您必须将值85转换为aschi-56(" 8")和aschii-53(" 5")。然后将它们一个接一个地放入寄存器中并拨打int 10两次以显示" 85"。

hobbs的答案显示了如何做到这一点。

Here是另一个教程

答案 2 :(得分:2)

请参阅标签wiki以获取指令集参考手册,以及许多指向参考资料和教程的良好链接。

需要足够的代码将整数分成ASCII数字,你应该把它分解成函数。

这是@ hobbs的print2Digits功能的优化和修复版本。 (我也在他的答案中修改了版本,所以它也是正确的,但保留了这个版本的优化。)

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归零。它在当前的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    ; 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

即使我们想要存储到一个字符串(并调用一个打印字符串函数或系统调用),我们必须在将AX存储到内存之前交换al和ah(例如{{1}在现代硬件上更有效,xchg al,ah)。 rol ax,8以正确的顺序生成它们。

对于32位地址大小可用的386 ,我们可以保存一条指令:

div

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 需要一个地址大小的前缀和一个2字节的mod / rm,以及一个32位的位移,因此在代码大小上会丢失很多,但它会保存一条指令。

lealea之后eax使用div来读取Sandybridge系列CPU可能会更快,尤其是Haswell和后来,但在英特尔pre-SnB上,部分寄存器停止将使得使用纯16bit版本以及单独的add和mov指令更好。