I'm working on a very basic shell where the only command currently is 'help'. If you type something wrong, you're informed that the command isnt recognized. Somewhere in the segment and stack setup I have a bug that causes the shell to spit out some nonsense after I type anything and then freeze completely.
Error I get in terminal
qemu: fatal: Trying to execute code outside RAM or ROM at 0xff0fe990
EAX=0000ffe0 EBX=0000ffff ECX=ff00e990 EDX=0000e000
ESI=000001a4 EDI=0000011e EBP=00000019 ESP=0000ffdc
EIP=ff00e990 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =b000 000b0000 0000ffff 00009300
CS =f000 000f0000 0000ffff 00009b00
SS =e000 000e0000 0000ffff 00009300
DS =e000 000e0000 0000ffff 00009300
FS =0000 00000000 0000ffff 00009300
GS =0000 00000000 0000ffff 00009300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT= 000f6688 00000037
IDT= 00000000 000003ff
CR0=00000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000018 CCD=0000ffe0 CCO=SUBL
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000
compile-and-run.sh: line 18: 17015 Abort trap: 6 qemu-system-i386 -s -fda boot-disk.bin -boot a
I'm very new to assembly so I'd really appreciate guides on how to understand my mistake! The code is placed below. I run it with this script, please do the same.
nasm bootloader.asm -o bootloader.bin
dd if=/dev/zero of=boot-disk.bin bs=512 count=2880
dd if=bootloader.bin of=boot-disk.bin conv=notrunc
nasm kernel.asm -o kernel.bin
dd if=kernel.bin of=boot-disk.bin conv=notrunc bs=512 seek=1
qemu-system-i386 -s -fda boot-disk.bin -boot a
bootloader.asm
BITS 16 ; NASM directive for declaring the bit-mode.
global start
start:
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)
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
; 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
jmp KERNEL_SEGMENT: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
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
kernel.asm
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 sp, STACK_SEGMENT
mov ss, sp
mov sp, STACK_SIZE
push cs
pop ds
push word SCREEN_SEGMENT
pop es
mov al, 0xf
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
; ---------------
; Shell + commands
; ---------------
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 strings
; ---------------
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
; ---------------
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
Fixes suggested by @Jester
;bootloader sector problem
KERNEL_BLOCK_SIZE equ 2
1. obvious, just change call to jmp
2. mov es, buffer ;(ignore SCREEN_SEGMENT as its not even used)
3. change shell logic
shell_begin:
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
jnc .wrong_input_error
.command_help:
mov si, os_action_help
call os_print_string
jmp shell_begin ; reset shell
.wrong_input_error:
mov si, shell_error_wrong_command
call os_print_string
jmp shell_begin
jmp shell_begin