0

I was writing an x86 assembly program to output a number in hexadecimal. The program was assembled using nasm and the image file ran by qemu. The behavior of the program confused me a lot. As the working program below suggests, I wouldn't have to add 0x30 to a digit to get it to print the character of that digit.

    ; Boot sector code offset: 0x7c00
[org 0x7c00]

    mov dx, 0x1fb6 ; The hexadecimal to be printed
    call print_hex ; call the function
    jmp $ ; jump infinitely

%include "print_string.asm" ; Include the print_string function

print_hex:
    pusha ; push all registers to stack
    mov ax, 0x4 ; rotate through the number four times
print_hex_loop:
    cmp ax, 0x0 ; compare the counter with 0
    jle print_hex_end ; if it is zero then jump to the end  
    mov cx, dx ; move dx to cx
    and cx, 0x000F ; take the lower four binary digits of cx
    cmp cx, 0xa ;compare the digits with 0xa
    jge print_hex_letter ; if it is larger than a, jump to printing character
    add cx, 0x0 ; otherwise print the ascii of a number
    jmp print_hex_modify_string ; jump to routine for modifing the template
print_hex_letter:
    add cx, 0x7 ; print the ascii of a letter
print_hex_modify_string:
    mov bx, HEX_OUT ; bring the address of HEX_OUT into dx
    add bx, 0x1 ; skip the 0x
    add bx, ax ; add the bias
    add byte [bx], cl ; move the character into its position
    shr dx, 4 ; shift right 4 bits
    sub ax, 0x1 ; subtract 1 from the counter
    jmp print_hex_loop ; jump back to the start of the function
print_hex_end:
    mov bx, HEX_OUT ; move the address of HEX_OUT to bx
    call print_string ; call the function print_string
    popa ; pop all registers from stack
    ret ; return to calling function

HEX_OUT:
    db '0x0000',0 ; The template string for printing

    times 510-($-$$) db 0 ; fill zeros
    dw 0xaa55 ; MAGIC_FLAG for boot

boot_sect.asm

print_string:
    pusha
    mov ah, 0x0e
    mov al, [bx]
print_string_loop:
    cmp al, 0x0
    je print_string_end
    int 0x10
    add bx, 0x1
    mov al, [bx]
    jmp print_string_loop
print_string_end:
    popa
    ret

print_string.asm

The output of this program is what I expected, but when I tried to add 0x30 on the numerals to get the ASCII code of the digits, the output was gibberish. Is there some trick to it or am I missing some key points here?

Thanks!

2
  • 1
    Because HEX_OUT: db '0x0000' is a string that represents the ASCII characters for '0' already. Don't need to add 0x30 to values that were initialized to ASCII 0. Also note that calling print_hex more than once won't work because the way this code was written, it was expected that HEX_OUT is initialized to the string 0x0000 . Commented Nov 18, 2016 at 7:26
  • Because of add byte [bx], cl .. if you would do mov byte [bx], cl, you would have to add '0' or 'A'-10 to it first. Commented Nov 18, 2016 at 10:52

1 Answer 1

1

The answer to your original question:

Because you do add byte [bx], cl to write digit into buffer, and the buffer already contains '0', so the first time it will work correctly. Calling print_hex second time will produce gibberish again, as the HEX_OUT content is already modified (trivia: which hex number printed as first would allow also some second value to be printed correctly?).


Now just for fun I'm adding how I would probably do print_hex for myself. Maybe it will give you additional ideas for your x86 ASM programming, I tried to comment it a lot to explain why I'm doing things in a way I'm doing them:

First I would separate formatting function, so I could eventually reuse it elsewhere, so input is both number and target buffer pointer. I'm using LUT (look up table) for ASCII conversion, as the code is simpler. If you care about size, it's possible to do it in code with branching in less bytes and use the slower pusha/popa to save registers.

format_hex:
    ; dx = number, di = 4B output buffer for "%04X" format of number.
    push    bx              ; used as temporary to calculate digits ASCII
    push    si              ; used as pointer to buffer for writing chars
    push    dx
    lea     si,[di+4]       ; buffer.end() pointer
format_hex_loop:    
    mov     bx,dx           ; bx = temporary to extract single digit
    dec     si              ; si = where to write next digit
    and     bx,0x000F       ; separate last digit (needs whole bx for LUT indexing)
    shr     dx,4            ; shift original number one hex-digit (4 bits) to right
    mov     bl,[format_hex_ascii_lut+bx]    ; convert digit 0-15 value to ASCII
    mov     [si],bl         ; write it into buffer
    cmp     di,si           ; compare buffer.begin() with pointer-to-write
    jb      format_hex_loop ; loop till first digit was written
    pop     dx              ; restore original values of all modified regs
    pop     si
    pop     bx
    ret
format_hex_ascii_lut:       ; LUT for 0-15 to ASCII conversion
    db      '0123456789ABCDEF'

Then for convenience a print_hex function may be added too, providing its own buffer for formatting with "0x" and nul terminator:

print_hex:
    ; dx = number to print
    push    di
    push    bx
    ; format the number
    mov     di,HEX_OUT+2
    call    format_hex
    ; print the result to screen
    lea     bx,[di-2]       ; bx = HEX_OUT
    ; HEX_OUT was already set with "0x" and nul-terminator, otherwise I would do:
    ; mov       word [bx],'0x'
    ; mov       byte [bx+6],0
    call    print_string
    pop     bx
    pop     di
    ret
HEX_OUT:
    db      '0x1234',0      ; The template string for printing

And finally example usage from the boot code:

    mov     dx,0x1fb6       ; The hexadecimal to be printed
    call    print_hex
    mov     dx,ax           ; works also when called second time
    call    print_hex       ; (but would be nicer to print some space between them)

    jmp     $               ; loop infinitely

(I did verify this code to some extend (that it will compile and run), although only by separate parts of it and in 32b environment (patching few lines to make it 32b), so some bug may have slipped in. I don't have 16b environment to verify it as complete boot code.)

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.