Question

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
Était-ce utile?

La solution

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.
  • Remove unnecessary ES setup at the beginning

This 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top