I'm trying learn how operating systems work. This is an easy task I'm having a hard time solving: to write a simple bootloader that prompts the user for his name and prints a welcome message, like "hello, >>name<<" - after that, it does nothing.
I'm running minix 3
with qemu
, if this is of any relevance. I simply compile an asm
file and dd
its first 512 bytes to /dev/c0d0
(the virtual hard drive of minix
).
I can print the message and print what the user is typing. However, I didn't manage to print the user's name afterward.
Here is my assembly code:
[bits 16]
[org 0x7c00]
mov si, HelloString
call print_string
mov di, name
call read_name
mov si, name
call print_string
read_name:
read_char:
mov ah, 0h ; read character from keyboard
mov [di], ah ; save it in the buffer
inc di ; next char
int 0x16 ; store it in AL
cmp ah, 0x0d ; check for enter
je stop_reading
mov ah, 0eh ; display character in AL
int 0x10 ; echo it
jmp read_char ; an so on
stop_reading:
mov si, EoL
call print_string
ret
print_char:
mov ah, 0x0e ; one char
mov bh, 0x00 ; page number
mov bl, 0x07 ; font color
int 0x10
ret
print_string:
next_char:
mov al, [si]
inc si
or al, al
jz exit_function
call print_char
jmp next_char
exit_function:
ret
;data
HelloString db 'Enter your name', 0xd, 0xa, 0
name times 20 db 0
EoL db 0xd, 0xa, 0
times 510 - ($ - $$) db 0;
dw 0xaa55
What am I doing wrong?
There are a number of issues with your code. Ross and I have pointed some out in the comments. You should read my General Bootloader Tips. Although not related to your actual problem you should set up DS (and ES if you eventually need it) to 0 because you use an origin point of 0x7c00 (org 0x7c00
). You should also set up the stack somewhere you know your code won't be clobbering. I'd add this code to the top before:
mov si, HelloString
call print_string
Change it to be:
xor ax, ax ; AX=0
mov ds, ax
mov es, ax
mov ss, ax ; SS=ES=DS=0
mov sp, 0x7c00 ; Place stack before the bootloader. Grows down from 0x0000:0x7c00
mov si, HelloString
call print_string
After your code is finished running you should place the CPU in an infinite loop so that it doesn't continue by executing your functions beneath the main code. So before the label read_name:
place an infinite loop. Something like this is typical:
cli ; Turn off interrupts
endloop:
hlt ; Halt processor until next interrupt encountered
jmp endloop ; Jump back just in case we get an MNI (non-maskable interrupt)
You have some bugs in your read_char
function. One of the best places for BIOS Interrupt information is Ralph Brown's Interrupt List. Int 0x16/AH=0 is documented as:
AH = 00h Return: AH = BIOS scan code AL = ASCII character
You should be using the ASCII character in AL to store into your string buffer. You should also be comparing AL with 0x0d, not AH (Which is the keyboard scan code, NOT the ASCII character). You also are storing data into your string buffer before you read the characters with int 0x16
. You need to put them in the buffer after. When you reach stop_reading:
you will want to place a NUL (0x00) character at the end of the buffer.
Your code for read_name
could look like:
read_name:
read_char:
mov ah, 0h ; read character from keyboard
int 0x16 ; store it in AL
cmp al, 0x0d ; check for enter
je stop_reading
mov [di], al ; save it in the buffer
inc di ; next char
mov ah, 0eh ; display character in AL
int 0x10 ; echo it
jmp read_char ; an so on
stop_reading:
mov byte [di], 0x00 ; NUL terminate buffer
mov si, EoL
call print_string
ret
The revised bootloader could look like:
[bits 16]
[org 0x7c00]
xor ax, ax ; AX=0
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00 ; Place stack before the bootloader. Grows down from 0x0000:0x7c00
mov si, HelloString
call print_string
mov di, name
call read_name
mov si, name
call print_string
cli ; Turn off interrupts
endloop:
hlt ; Halt processor until next interrupt encountered
jmp endloop ; Jump back just in case we get an MNI (non-maskable interrupt)
read_name:
read_char:
mov ah, 0h ; read character from keyboard
int 0x16 ; store it in AL
cmp al, 0x0d ; check for enter
je stop_reading
mov [di], al ; save it in the buffer
inc di ; next char
mov ah, 0eh ; display character in AL
int 0x10 ; echo it
jmp read_char ; an so on
stop_reading:
mov byte [di], 0 ; NUL terminate buffer
mov si, EoL
call print_string
ret
print_char:
mov ah, 0x0e ; one char
mov bh, 0x00 ; page number
mov bl, 0x07 ; font color
int 0x10
ret
print_string:
next_char:
mov al, [si]
inc si
or al, al
jz exit_function
call print_char
jmp next_char
exit_function:
ret
;data
HelloString db 'Enter your name', 0xd, 0xa, 0
name times 20 db 0
EoL db 0xd, 0xa, 0
times 510 - ($ - $$) db 0;
dw 0xaa55
I highly recommend using BOCHS to debug bootloaders. It has a built in debugger that understands real mode and real mode addressing and is better suited for debugging bootloaders than QEMU