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.
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:
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
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.