assembly x86 qemu: fatal: Trying to execute code o

2019-07-05 03:50发布

问题:

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

回答1:

Main problem is that your kernel.bin has grown beyond 1 sector, and you only load 1 sector.

Further problems:

  1. In os_compare_string, the call .compare_next_character should be a jmp.
  2. In os_get_user_input, stosb uses the es segment, but you have set that up to point to the video memory, so it won't store the entered text in the buffer
  3. In shell_begin when you compare the input to the help command, the code flow will always go to .command_help