1

I know I can print an ASCII character if it's representation is in AL with:

DrawChar:
  MOV AL, 0x45
  MOV AH, 0x0E
  MOV BL, 0x07
  MOV BH, 0x00
  INT 0x10
  RET

Is there a way I can use INT 10H to print the actual register value? Like AL is 0x45 in the e.g. above, so it would print 45 (doesn't have to be the hex rep.). I'm doing this in a 16-bit real-mode bootloader.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
Nat
  • 890
  • 3
  • 11
  • 23
  • 1
    No, you'll have to write your own function to convert the integer to an ASCII string. If you use Bochs emulator it has an internal debugger that lets you step through instructions (even in bootloaders) and display register values, memory etc. – Michael Petch Jul 12 '16 at 00:58

1 Answers1

4

I'll give you one method to print a 16-bit register. This prints a nibble at a time (4 bits) by rotating the 16-bit register left 4 bits into the least significant bits and then isolating those 4 bits. We then print the hexadecimal digit of that value which will be 0 to 9 and A to F. We do this 4 times for each of the 4 nibbles in a 16-bit word.

There are TWO 16-bit hex printing functions provided in the examples. Choose one appropriate for your environment:

  • print_hex_word which will work on any 8086/8088 processor or later.
  • print_hex_word that is a smaller version that works on any 80186/80188 processor in 16-bit real mode.

Both variants require you to:

  • push a 2-byte value on the stack containing the page number and foreground color (graphics mode). see Ralf Brown's interrupt list for more information on Int 10h/AH=0eh
  • push the 2-byte value on the stack you wish to print.

The sample code below contains a minimal bootloader as an example:

bits 16
org 0x7c00

section .text
    global      _start

_start:
    ; Set segment registers to 0
    xor ax, ax
    mov ds, ax
    mov es, ax
    ; Set stack pointer just below bootloader
    mov ss, ax
    mov sp, 0x7c00
    ; Clear direction flag (forward movement)
    cld
    ; print_hex_word/print_hex_word_186 take a second parameter
    ; that is the page number (upper 8 bits) and foreground color
    ; in lower 8 bits. We just want 0x0000 so push it on the
    ; stack first
    push ax

    ; This test just prints SS and SP to the display
    push ss                 ; Push on stack as 1st parameter
                            ;    In this case display value in SS
    call print_hex_word     ; Print 16-bit value as hex
    add  sp, 2              ; Cleanup stack after call
    push sp                 ; Push on stack as 1st parameter
                            ;    In this case display value in SP
    call print_hex_word     ; Print 16-bit value as hex
    add  sp, 2              ; Cleanup stack after call

    ; Print value 0xaa55 to the display
    mov ax, 0xaa55
    push ax                 ; Push on stack as 1st parameter
                            ;    In this case display value in 0xAA55
    call print_hex_word     ; Print 16-bit value as hex

    cli                     ; Disable interrupts
    hlt                     ; Halt processor

; Print 16 bit value passed on stack as first parameter
; in hexadecimal. Use page number and foreground color
; passed in second parameter. This routine will work on 8086+
; processors. This code takes advantage of packed BCD to
; determine the ASCII values to print. This code could have
; used compare and branch to do the same or a translation table.

print_hex_word:
    push bp
    mov bp, sp      ; BP=SP, on 8086 can't use sp in memory operand
    push dx         ; Save all registers we clobber
    push cx
    push bx
    push ax

    mov cx, 0x0404  ; CH = number of nibbles to process = 4 (4*4=16 bits)
                    ; CL = Number of bits to rotate each iteration = 4 (a nibble)
    mov dx, [bp+4]  ; DX = word parameter on stack at [bp+4] to print
    mov bx, [bp+6]  ; BX = page / foreground attr is at [bp+6]

.loop:
    rol dx, cl      ; Roll 4 bits left. Lower nibble is value to print
    mov ax, 0x0e0f  ; AH=0E (BIOS tty print),AL=mask to get lower nibble
    and al, dl      ; AL=copy of lower nibble
    add al, 0x90    ; Work as if we are packed BCD
    daa             ; Decimal adjust after add.
                    ;    If nibble in AL was between 0 and 9, then CF=0 and
                    ;    AL=0x90 to 0x99
                    ;    If nibble in AL was between A and F, then CF=1 and
                    ;    AL=0x00 to 0x05
    adc al, 0x40    ; AL=0xD0 to 0xD9
                    ; or AL=0x41 to 0x46
    daa             ; AL=0x30 to 0x39 (ASCII '0' to '9')
                    ; or AL=0x41 to 0x46 (ASCII 'A' to 'F')
    int 0x10        ; Print ASCII character in AL
    dec ch
    jnz .loop       ; Go back if more nibbles to process

    pop ax          ; Restore registers
    pop bx
    pop cx
    pop dx
    pop bp
    ret

TIMES 510-($-$$) db 0
DW 0xaa55

The 80186+ version of print_hex_word that uses PUSHA and POPA to save and restore registers AX, CX, DX, BX, original SP, BP, SI, and DI:

; Print 16 bit value passed on stack as first parameter
; in hexadecimal. This routine will work on 80186+ processors
; Use page number and foreground color passed in second parameter

print_hex_word:
    pusha           ; Save all registers, 16 bytes total
    mov bp, sp      ; BP=SP, on 8086 can't use sp in memory operand
    mov cx, 0x0404  ; CH = number of nibbles to process = 4 (4*4=16 bits)
                    ; CL = Number of bits to rotate each iteration = 4 (a nibble)
    mov dx, [bp+18] ; DX = word parameter on stack at [bp+18] to print
    mov bx, [bp+20] ; BX = page / foreground attr is at [bp+20]

.loop:
    rol dx, cl      ; Roll 4 bits left. Lower nibble is value to print
    mov ax, 0x0e0f  ; AH=0E (BIOS tty print),AL=mask to get lower nibble
    and al, dl      ; AL=copy of lower nibble
    add al, 0x90    ; Work as if we are packed BCD
    daa             ; Decimal adjust after add.
                    ;    If nibble in AL was between 0 and 9, then CF=0 and
                    ;    AL=0x90 to 0x99
                    ;    If nibble in AL was between A and F, then CF=1 and
                    ;    AL=0x00 to 0x05
    adc al, 0x40    ; AL=0xD0 to 0xD9
                    ; or AL=0x41 to 0x46
    daa             ; AL=0x30 to 0x39 (ASCII '0' to '9')
                    ; or AL=0x41 to 0x46 (ASCII 'A' to 'F')
    int 0x10        ; Print ASCII character in AL
    dec ch
    jnz .loop       ; Go back if more nibbles to process
    popa            ; Restore all the registers
    ret

The code uses some packed Binary Coded Decimal(BCD) tricks to convert a 4 bit value to a hexadecimal digit. More on packed BCD arithmetic can be found in this tutorial in the section Processing Packed BCD Numbers.

To assemble this bootloader you could use:

nasm -f bin boot.asm -o boot.bin

It could be tested with QEMU at a Linux command line like this:

qemu-system-i386 -fda boot.bin

Debug Recommendation

If you use BOCHS you can step through your bootloader with its built-in debugger, and display the contents of the registers and memory as your code executes.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Michael Petch
  • 46,082
  • 8
  • 107
  • 198