Reputation: 187
I'm writing a bootloader which simply loads a kernel. I've been following a tutorial and have adapted its assembly code a bit, but the addresses on the tutorial no longer work, and so the kernel isn't loaded. I don't know how the addressing works and how the additional bytes that I've defined and the code that I've added invalidates the addresses from the tutorial. The tutorial I used is here: http://inglorion.net/documents/tutorials/x86ostut/bootsector/
What should I do to make sure that the addresses are valid? According to the tutorial, we it should be fine loading the kernel at 1000h but that doesn't seem to happen.
Here is my code:
bootloader.asm Sorry for the excessive comments
BITS 16 ; NASM directive for declaring the bit-mode.
ORG 0 ; Tells NASM that all locations we give
; will be based off of zero
bootloader_initialize_environment:
mov ax, 0x07C0 ; 07C0 = 1984, the location where BIOS looks for the
; bootloader on the floppy disk
mov ds, ax ; sets up data segment (ds)
mov es, ax ; sets up extra segment (es)
KERNEL_BLOCK_START equ 1 ; Starting disk block where kernel is written
KERNEL_BLOCK_SIZE equ 1 ; Number of blocks containing kernel
KERNEL_SEGMENT equ 1000h ; Kernel will be loaded at segment 4096
call load_kernel ; begin OS
load_kernel:
mov si, bootloader_status_message
call bootloader_print_string ; print status message
; begin reading kernel byte code from disk
; Uses interrupt 13h with AH = 2h
; Options: AL = sectors to read count
; CH = Cylinder to read
; CL = Sector within Cylinder to read
; DH = Head (0 in our case)
; DL = Drive (also 0)
; ES:BX = Buffer address pointer
mov ah, 2h
mov al, KERNEL_BLOCK_SIZE
push word KERNEL_SEGMENT
pop es
xor bx, bx ; reset bx to 0
mov ch, KERNEL_BLOCK_START
mov cl, 1h
mov dx, 0
int 13h ; call interrupt. Writes error to Carry flag
jnc jump_to_kernel ; loading success, no error in carry flag
mov si, bootloader_load_failed
call bootloader_print_string
jmp $ ; loop forever
jump_to_kernel :
mov si, bootloader_load_success
call bootloader_print_string
mov bl, 2 ; for debugging (I later look in gdb)
jmp KERNEL_SEGMENT:0
bootloader_status_message db 'bootloader: loading kernel...', 0x0D, 0x0A, 0
bootloader_load_failed db 'bootloader fatal: loading kernel failed. Go home.', 0x0D, 0x0A, 0
bootloader_load_success db 'bootloader: reading kernel success, jumping now...', 0x0D, 0x0A, 0
bootloader_print_string:
lodsb ; Takes one byte from SI and puts it to AL (maintains pointer to next byte)
or al, al ; All strings end with zero to indicate that the string has finished
; we use that to know when to stop printing characters from SI
; takes logical OR of AL by itself. Result is store in Carry Flag
jz .finish ; checks if the carry flag is zero and if so jumps to finish subroutine
; continue printing below
mov ah, 0x0E ; BIOS directive for Teletype printing
int 10h ; BIOS interrupt for video services. (AH=Teletype & AL=character)
jmp bootloader_print_string ; recursive call until all characters are printed
.finish: ; finished printing all characters
ret ; return to where this routine was called from
times 510 - ($ - $$) db 0 ; loop 510 times and pad with empty bytes
dw 0xAA55 ; last 2 bytes are 55h and 0AAh
kernel.asm - Relevant parts os_initialize_environment:
STACK_SEGMENT equ 09000h ; top of Conventional memory, 36864
STACK_SIZE equ 0ffffh ; stack length: 64K-1 bytes
SCREEN_SEGMENT equ 0b800h ; segment of memory where BIOS writes display data
SCREEN_SIZE_COLUMNS equ 80 ; 80 width
SCREEN_SIZE_ROWS equ 25 ; 25 height
mov bl, 1 ; debugging with gdb
mov sp, STACK_SEGMENT
mov ss, sp
mov sp, STACK_SIZE
push cs
pop ds
push word SCREEN_SEGMENT
pop es
mov al, fh
mov si, os_kernel_read_signal
call os_print_string
call os_start ; begin OS
; ----------------------------------------------------------
; Start of main program
; Available routines: os_print_string, os_get_user_input, os_compare_string
; ----------------------------------------------------------
os_start:
mov si, os_welcome_message ; move welcome message to input
call os_print_string
mov si, os_alive_signal ; move welcome message to input
call os_print_string
jmp shell_begin
; ---------------
; data
; ---------------
shell_cursor db '> ', 0
shell_command_help db 'help', 0
shell_error_wrong_command db 'Wrong input. Type help for help.', 0x0D, 0x0A, 0
os_welcome_message db 'SsOS is a Simple Operating System. Keep expectations low. The pessimist is never disappointed.', 0x0D, 0x0A, 0
os_alive_signal db 'Command prompt ready', 0x0D, 0x0A, 0
os_kernel_read_signal db 'Kernel reached from bootloader', 0x0D, 0x0A, 0
os_action_help db 'available commands: help', 0x0D, 0x0A, 0
os_waiting_for_input db 'Please provide input below', 0x0D, 0x0A, 0
buffer times 128 db 0
; ----------------------------------------------------------
; Routine: Begins shell
; ----------------------------------------------------------
shell_begin:
mov si, shell_cursor ; print > cursor
call os_print_string
mov di, buffer ; move buffer to destination output
call os_get_user_input ; wait for user input
mov si, buffer ; copy user input to SI
mov di, shell_command_help
call os_compare_string ; checks if user typed help command
jc .command_help
; command help (shell_command_help) selected
.command_help:
mov si, os_action_help
call os_print_string
jmp shell_begin ; reset shell
; wrong user input (command not recognized)
.wrong_input_error:
mov si, shell_error_wrong_command
call os_print_string
jmp shell_begin
; ----------------------------------------------------------
; Routine: Print String in SI
; Input 1. SI: string to be printed must be copied to SI
; ----------------------------------------------------------
os_print_string:
lodsb ; Takes one byte from SI and puts it to AL (maintains pointer to next byte)
; All strings end with zero to indicate that the string has finished
; we use that to know when to stop printing characters from SI
or al, al ; takes logical OR of AL by itself. Result is store in Carry Flag
jz .finish ; checks if the carry flag is zero and if so jumps to finish subroutine
; continue printing below
mov ah, 0x0E ; BIOS directive for Teletype printing
int 10h ; BIOS interrupt for video services. (AH=Teletype & AL=character)
jmp os_print_string ; recursive call until all characters are printed
.finish: ; finished printing all characters
ret ; return to where this routine was called from
; ----------------------------------------------------------
; Routine: Get String from User
; Waits for a complete string of user input and puts it in buffer.
; Sensitive for backspace and Enter buttons
; Input 1. Buffer in DI
; Output 2. Input char in buffer
; ----------------------------------------------------------
os_get_user_input:
xor cl, cl ; CL will be our counter that keeps track of the number of characters the user has entered.
; XORing cl by itself will set it to zero.
mov si, os_waiting_for_input
call os_print_string
.get_char_and_add_to_buffer:
mov ah, 0 ; We use bios interrupt 16h to capture user input.
; AH=0 is an option for 16h that tells the interrupt to read the user input character
int 16h ; call interrupt. Stores read character in AL
; backspace button listener
cmp al, 0x08 ; compares user input to the backspace button, stores result in Carry Flag
je .backspace_pressed ; if the results of the compare is 1, go to subroutine .backspace_pressed
; enter button listener
cmp al, 0x0D ; compares user input to enter button
je .enter_pressed ; go to appropriate subroutine for enter button
; input counter
cmp cl, 0x80 ; Has the user entered 128 bytes yet? (buffer limit is 128)
je .buffer_overflow
; User input is normal character
; print input
mov ah, 0x0E ; Teletype mode
int 10h ; Print interrupt
stosb ; puts character in buffer
inc cl ; increment counter
jmp .get_char_and_add_to_buffer ; recurse
; // Subroutines
.backspace_pressed:
cmp cl, 0 ; no point erasing anything if no input has been entered
je .get_char_and_add_to_buffer ; ignore backspace press
; Delete last input character from buffer
; When you use stosb, movsb or similar functions, the system implicitly uses the SI and DI registers.
dec di ; Therefore we need to decrement di to get to the last input character and erase it.
mov byte[di],0 ; Erases the byte at location [di]
dec cl ; decrement our counter
; Erase character from display
mov ah, 0x0E ; Teletype mode again
mov al, 0x08 ; Backspace character
int 10h
mov al, ' ' ; Empty character to print
int 10h
mov al, 0x08
int 10h
jmp .get_char_and_add_to_buffer ; go back to main routine
; enter button pressed. Jump to exit
.enter_pressed:
jmp .exit_routine
; buffer overflow (buffer is full). Don't accept any more chars and exit routine.
.buffer_overflow:
jmp .exit_routine
.exit_routine:
mov al, 0 ; end of user input signal
stosb
mov ah, 0x0E
mov al, 0x0D ; new line
int 0x10
mov al, 0x0A
int 0x10
ret ; exit entire routine
; ----------------------------------------------------------
; Routine: Compare equality of two strings
; Waits for a complete string of user input and puts it in buffer.
; Sensitive for backspace and Enter buttons
; Input 1. String1 in SI 2. String2 in DI
; Output 1. result in carry flag
; ----------------------------------------------------------
os_compare_string:
.compare_next_character: ; a loop that goes character by character
mov al, [si] ; focus on next byte in si
mov bl, [di] ; focus on next byte in di
cmp al, bl
jne .conclude_not_equal ; if not equal, conclude and exit
; we know: two bytes are equal
cmp al, 0 ; did we just compare two zeros?
je .conclude_equal ; if yes, we've reached the end of the strings. They are equal.
; increment counters for next loop
inc di
inc si
call .compare_next_character
.conclude_equal:
stc ; sets the carry flag (meaning that they ARE equal)
jmp .done
.conclude_not_equal:
clc ; clears the carry flag (meaning that they ARE NOT equal)
jmp .done
.done:
ret
Script to compile and run (nasm and qemu) # Compile bootloader assembly to binary with NASM nasm bootloader.asm -o bootloader.bin
# Next, create a floppy disk image
dd if=/dev/zero of=boot-disk.bin bs=512 count=2880
# Write the boot sector to the disk image
dd if=bootloader.bin of=boot-disk.bin conv=notrunc
# Time to put in the kernel
# Compile kernel with NASM
nasm kernel.asm -o kernel.bin
# Write Kernel to disk in appropriate place
dd if=kernel.bin of=boot-disk.bin conv=notrunc bs=512 seek=1
# Emulator
qemu-system-i386 -s -fda boot-disk.bin -boot a
# GDB Debugger
#gdb target remote localhost:1234
Upvotes: 1
Views: 1437
Reputation: 7228
Your bootloader.asm
can be made to work with the following fixes:
int 13h; AH = 2h
sector counts start from 1. You want sector 2 but were loading 1.ES
setup at the beginningThis works for me:
bootloader.asm
BITS 16 ; NASM directive for declaring the bit-mode. global start start: KERNEL_BLOCK_START equ 1 ; Starting disk block where kernel is written KERNEL_BLOCK_SIZE equ 1 ; Number of blocks containing kernel KERNEL_SEGMENT equ 1000h ; Kernel will be loaded at segment 4096 call load_kernel ; begin OS load_kernel: mov ax, 0x07c0 mov ds, ax mov si, bootloader_status_message call bootloader_print_string ; begin reading kernel byte code from disk ; Uses interrupt 13h with AH = 2h ; Options: AL = sectors to read count ; CH = Cylinder to read, 0 to 1023 ; CL = Sector within Cylinder to read, 1-63 ; DH = Head (0 in our case) ; DL = Drive (also 0) ; ES:BX = Buffer address pointer mov ah, 2 mov al, KERNEL_BLOCK_SIZE push word KERNEL_SEGMENT pop es xor bx, bx ; reset bx to 0 mov cx, KERNEL_BLOCK_START + 1 mov dx, 0 int 13h ; call interrupt. Writes error to Carry flag jnc jump_to_kernel ; loading success, no error in carry flag mov si, bootloader_load_failed call bootloader_print_string jmp $ ; loop forever jump_to_kernel : mov si, bootloader_load_success call bootloader_print_string mov bl, 2 ; for debugging (I later look in gdb) jmp KERNEL_SEGMENT:0 bootloader_print_string: mov ah, 0x0e ; print function mov al, [si] ; ascii char int 0x10 ; IO int inc si cmp byte [si], 0 jne bootloader_print_string ret bootloader_status_message db 'bootloader: loading kernel...', 0x0D, 0x0A, 0 bootloader_load_failed db 'bootloader fatal: loading kernel failed. Go home.', 0x0D, 0x0A, 0 bootloader_load_success db 'bootloader: reading kernel success, jumping now...', 0x0D, 0x0A, 0 times 510 - ($ - $$) db 0 ; loop 510 times and pad with empty bytes dw 0xAA55 ; last 2 bytes are 55h and 0AAh
Upvotes: 1