Search code examples
assemblyx86bootloaderosdevminix

Write a simple bootloader that reads user's name


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?


Solution

  • 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