Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Displaying Time in Assembly

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

enter image description here

like image 240
Polo D. Vargas Avatar asked Dec 25 '22 05:12

Polo D. Vargas


2 Answers

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!

like image 135
Peter Cordes Avatar answered Jan 12 '23 23:01

Peter Cordes


You need a print routine to print bytes as numbers, instead of writing them directly to the screen as characters. Luckily since you only have to deal with values between 0 and 59, and since you want leading zeroes, the problem is pretty simple. Assuming value to be printed in 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
like image 29
hobbs Avatar answered Jan 12 '23 21:01

hobbs