在装配中显示时间 [英] Displaying Time in Assembly

查看:25
本文介绍了在装配中显示时间的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

MOV AH, 2ChINT 21hMOV AH, 0EhMOV AL, CHINT 10hMOV AL, 3AhINT 10hMOV AL, CLINT 10hMOV AL, 3AhINT 10hMOV AL, DHINT 10h退

在这里你可以看到控制台显示的内容

解决方案

查看 标签 wiki 用于指令集参考手册,以及许多很好的参考资料和链接教程.

<小时>

将整数拆分为 ASCII 数字需要足够的代码,您应该将其分解为函数.

这是@hobbs 的print2Digits 函数的优化和错误修正版本.(我还在他的回答中对版本进行了错误修正,所以它也是正确的,但留下了对这个版本的优化).

print2Digits:;;AL (0-99) 中的输入.(或者最好已经零扩展到 AX 所以我们可以省略 CBW);;破坏者 AX 和 DXcbw ;零啊.符号扩展 AL 可以完成这项工作,因为 AL 只允许为 0-99.mov dl, 10div dl ;AL 中的商(第一个(高)位),AH 中的余数(第二个(低)位)添加斧头,0x3030;同时向 al 和 ah 添加0".mov dl,啊;保存第二个数字移动啊, 0x0E ;BIOS 调用#:打印单个字符整数 0x10 ;先打印高位.不会破坏任何东西,所以 AH 仍然保留 0x0E移动, DL整数 0x10 ;打印低位第二位退

由于我们使用 div 将整数分成两个 base10 数字,因此我们需要 ah 为零.即红利在 AX 中,而不仅仅是 AL 中可能存在垃圾的 AH.我们可以保存 cbwmov ah,0 如果调用者做了 movzx ax, ch 或一些为零的东西 ah>.

(除了 8086 没有 movzx,所以你实际上想要 xor ax,ax/mov al, ch.)

有一个用于打印整个字符串的 DOS 系统调用,因此您可以将字符存储到一个小缓冲区中并一次打印它们,就像我在此 AMD64 Linux FizzBu​​zz.另见 如何在没有 c 库中的 printf 的情况下在汇编级编程中打印整数? 对于缓冲区函数中更通用的 int-> 字符串,或 x86 标签维基

<小时>

也可以使用aam将 AL(而不是 AX)除以 10,避免需要先将 AH 归零.在当前的 Intel 和 AMD CPU 上,它比 div r8 略快.然而,它把结果放在 div 的相反寄存器中,这意味着 aam 之后的额外指令.这平衡了 mov dl, 10cbw 的节省.

print2Digits:;;AL (0-99) 中的输入.(忽略 AH 因为我们使用 AAM 而不是 div);;破坏者 AX 和 DX阿姆;像 `div` 乘以 10,但输出相反,并且仅从 AL 输入;;商为AH(高位),余数为AL(低位).(与 div 相对)添加斧头,0x3030;同时向 al 和 ah 添加0".mov dl, al ;保存低位动,啊;先打印高位移动啊, 0x0E ;BIOS 调用#:打印单个字符整数 0x10 ;打印第一个数字.不会破坏任何东西,所以 AH 仍然保留 0x0E移动, DL整数 0x10 ;打印第二个数字退

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

<小时>

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

lea dx, [eax + 0x3030] ;需要 32 位寻址模式才能使用 eax 作为源 reg.将0"一次添加到两个数字,具有不同的目的地.移动, dh ;然后准备先打印高字节

lea 需要一个地址大小前缀和一个 2 字节的 mod/rm,以及一个 32 位的位移,所以它在代码大小上损失很大,但它确实节省了一条指令.

div 写入ax 之后,使用leaeax 读取数据在Sandybridge 系列CPU 上可能会更快,尤其是Haswell 及更高版本,但在 Intel pre-SnB 上,部分寄存器停顿将使使用带有单独 add 和 mov 指令的纯 16 位版本更好.

当然,如果您真的关心性能,您会使用乘法逆运算,而不是实际除以 10.而且您通常不会编写 16 位代码来进行传统 BIOS 调用要么!

Hello im trying to display the actual time hours/minutes/seconds this is my code sample:

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

Here you can se what the console is displaying

解决方案

See the tag wiki for the instruction set reference manual, and many good links to reference material and tutorials.


It takes enough code to split up an integer into ASCII digits that you should factor it out into a function.

This is an optimized and bugfixed version of @hobbs's print2Digits function. (I also bugfixed the version in his answer, so it's correct too, but left the optimizations for this one).

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

Since we used div to split an integer into two base10 digits, we need ah to be zero. i.e. for the dividend to be in AX, not just AL with possible garbage in AH. We could save the cbw or mov ah,0 if the caller did movzx ax, ch or something to zero ah.

(Except that 8086 doesn't have movzx, so you'd actually want xor ax,ax / mov al, ch.)

There's a DOS system call for printing a whole string, so you could store characters into a small buffer and print them all at once, like I do in this AMD64 Linux FizzBuzz. 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


It's also possible to use aam to divide AL (instead of AX) by 10, avoiding the need to zero AH first. It's slightly faster than div r8 on current Intel and AMD CPUs. However, it puts the results in the opposite registers from div, which means extra instructions after the aam. This balances out the saving on the mov dl, 10 and 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

Even if we wanted to store to a string (and make one call to a print-string function or system call), we'd have to swap al and ah before storing AX to memory (e.g. xchg al,ah, or more efficiently on modern hardware but requiring 186: rol ax,8). div produces them in the right order inside AX.


For 386 where 32bit address-size is available, we can save one instruction:

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

The lea needs an address-size prefix and a 2-byte mod/rm, and a 32bit displacement, so it loses badly on code-size, but it does save one instruction.

Using lea to read from eax after div writes ax will probably be faster on Sandybridge-family CPUs, esp. Haswell and later, but on Intel pre-SnB, the partial register stall will make it better to use the pure 16bit version with separate add and mov instructions.

Of course if you actually cared about performance, you'd use a multiplicative inverse instead of actually dividing by 10. And you usually wouldn't be writing 16-bit code that makes legacy BIOS calls either!

这篇关于在装配中显示时间的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆