在装配中显示时间 [英] Displaying Time in Assembly
问题描述
你好,我试图显示实际时间小时/分钟/秒,这是我的代码示例:
MOV AH, 2ChINT 21hMOV AH, 0EhMOV AL, CHINT 10hMOV AL, 3AhINT 10hMOV AL, CLINT 10hMOV AL, 3AhINT 10hMOV AL, DHINT 10h退
在这里你可以看到控制台显示的内容
查看 x86 标签 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.我们可以保存 cbw
或 mov ah,0
如果调用者做了 movzx ax, ch
或一些为零的东西 ah
>.
(除了 8086 没有 movzx
,所以你实际上想要 xor ax,ax
/mov al, ch
.)
有一个用于打印整个字符串的 DOS 系统调用,因此您可以将字符存储到一个小缓冲区中并一次打印它们,就像我在此 AMD64 Linux FizzBuzz.另见 如何在没有 c 库中的 printf 的情况下在汇编级编程中打印整数? 对于缓冲区函数中更通用的 int-> 字符串,或 x86 标签维基
<小时>也可以使用aam
将 AL(而不是 AX)除以 10,避免需要先将 AH 归零.在当前的 Intel 和 AMD CPU 上,它比 div r8
略快.然而,它把结果放在 div
的相反寄存器中,这意味着 aam
之后的额外指令.这平衡了 mov dl, 10
和 cbw
的节省.
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
之后,使用lea
从eax
读取数据在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 x86 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屋!