【问题标题】:x86 bootloader doesn't jump to proper location where kernel is loadedx86 引导加载程序不会跳转到加载内核的正确位置
【发布时间】:2014-04-23 13:16:36
【问题描述】:

我正在编写一个简单地加载内核的引导加载程序。我一直在关注一个教程并稍微调整了它的汇编代码,但是教程上的地址不再有效,因此内核没有加载。我不知道寻址是如何工作的,以及我定义的附加字节和我添加的代码如何使教程中的地址无效。 我使用的教程在这里:http://inglorion.net/documents/tutorials/x86ostut/bootsector/

我应该怎么做才能确保地址有效? 根据教程,我们应该可以在 1000h 加载内核,但这似乎没有发生。

这是我的代码:

bootloader.asm 抱歉过多的 cmets

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 - 相关部分 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

编译和运行脚本(nasm 和 qemu) # 使用 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

【问题讨论】:

  • 您确定要从气缸 1 读取数据吗?我本来期望气缸 0 扇区 2。(已经有一段时间了......)
  • 分区扇区是柱面 0,磁头 0,扇区 1。引导扇区在表中,但第一个通常是柱面 0,磁头 1,扇区 1。Microsoft 分区引导代码在 0000:7c00h 加载,将自身移动到 0000:0600h,然后在 0000:7c00h (es=0, bx=7c00h) 处读取引导扇区。如果是多引导系统,此过程可能会再次重复。
  • 我觉得缸数应该没问题。如果我在不添加我的部分的情况下编译教程中的代码,它就可以工作。啊,所以我最初不需要从 7c00h 启动,对吧?它是 7c0h 还是 7c00h?我不是在做多重引导,我只需要一些非常简单的东西
  • @nrz 用 8086 重新标记了这个问题。该标签编辑带来了一些问题,因为 8086 不支持此原始代码中的一条指令。直到将立即值推入堆栈才出现80186/80188 处理器。出于这个原因,我删除了 8086 标签。
  • @MichaelPetch 你是对的。感谢您纠正我的错误。 Push immed 在 8086 中不可用。

标签: assembly x86 operating-system bootloader


【解决方案1】:

您的bootloader.asm 可以使用以下修复:

  • int 13h; AH = 2h 扇区计数从 1 开始。您想要扇区 2 但正在加载 1。
  • 删除开头不必要的ES设置

这对我有用:

bootloader.asm

位 16;用于声明位模式的 NASM 指令。 全局启动 开始: KERNEL_BLOCK_START 等 1 ;写入内核的起始磁盘块 KERNEL_BLOCK_SIZE 等于 1 ;包含内核的块数 KERNEL_SEGMENT 等于 1000h ;内核将在 4096 段加载 调用 load_kernel ;开始操作系统 加载内核: 移动斧头,0x07c0 mov ds, ax mov si, bootloader_status_message 调用 bootloader_print_string ;开始从磁盘读取内核字节码 ;使用 AH = 2h 的中断 13h ;选项: AL = 要读取的扇区数 ; CH = 要读取的气缸,0 到 1023 ; CL = 气缸内要读取的扇区,1-63 ; DH = Head(在我们的例子中为 0) ; DL = 驱动器(也是 0) ; ES:BX = 缓冲区地址指针 动啊,2 移动,KERNEL_BLOCK_SIZE 推词KERNEL_SEGMENT 流行音乐 异或 bx, bx ;将 bx 重置为 0 mov cx, KERNEL_BLOCK_START + 1 移动 dx, 0 诠释 13h ;通话中断。将错误写入进位标志 jnc jump_to_kernel ;加载成功,进位标志没有错误 mov si, bootloader_load_failed 调用 bootloader_print_string 跳转 $ ;永远循环 jump_to_kernel : mov si, bootloader_load_success 调用 bootloader_print_string 移动 bl, 2 ;用于调试(我稍后会查看 gdb) jmp KERNEL_SEGMENT:0 bootloader_print_string: mov 啊, 0x0e ;打印功能 移动,[si]; ASCII字符 诠释 0x10 ; IO 整数 公司 cmp 字节 [si], 0 jne bootloader_print_string ret bootloader_status_message db 'bootloader: loading kernel...', 0x0D, 0x0A, 0 bootloader_load_failed db 'bootloader 致命:加载内核失败。回家吧。', 0x0D, 0x0A, 0 bootloader_load_success db 'bootloader: 读取内核成功,现在跳转...', 0x0D, 0x0A, 0 乘以 510 - ($ - $$) 分贝 0 ;循环 510 次并用空字节填充 dw 0xAA55 ;最后 2 个字节是 55h 和 0AAh

【讨论】:

  • 谢谢!像魔术一样工作。虽然你的bootloader_print_string 不适用于我。我打印了一些奇怪的字符。但如果我保持ESDS 设置,打印工作正常。
  • 是的,我使用的是链接器脚本,而不仅仅是nasm -f bin。链接器修复了bootloader_status_message 的地址。我会修正我的答案以保持DS 设置。请注意,您最初设置了两次 ES
  • @danakianfar,我确实发现使用链接器脚本并生成带有 DWARF 调试信息的 ELF 文件使得使用 GDB 更加愉快,即使对于实模式代码也是如此。有兴趣的可以看看stackoverflow.com/a/22348746/676030
  • 谢谢,一定会调查的。我刚刚开始进行汇编编程,到目前为止,我正在浏览半生不熟的教程,这些教程会导致像这个线程这样的问题。实际上,我还有另一个问题,我很迷茫。你可以帮帮我吗?我可能会打开另一篇文章,但请您事先运行整个程序(您的引导加载程序,我的内核)并告诉我您是否收到有关某些 RAM 溢出或访问越界错误的错误?当您尝试在 shell 中键入某些内容时,就会发生这种情况。我认为我在内核中设置的堆栈大小等太大了..
  • @danakianfar,我会看看,但请随时发布一个新问题,因为我可能没有时间完全调试它。
猜你喜欢
  • 1970-01-01
  • 2015-12-18
  • 2014-10-24
  • 2020-09-10
  • 2015-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多