引导加载程序不会跳转到内核代码

时间:2015-09-21 18:06:38

标签: assembly virtualbox nasm bootloader 16-bit

我正在编写小型操作系统 - 用于练习。我开始使用bootloader。
我想创建一个以16位实模式运行的小命令系统(现在) 我创建了重启驱动器的bootloader,然后在bootloader之后加载扇区 问题是因为jmp函数在实际上没有发生任何事情。

我试图在0x7E00加载下一个扇区(我不完全确定如何使用es:bx指向地址,这样可能会出现问题,我相信它的地址:偏移),就在之后引导程序。

这是代码:

;
; SECTOR 0x0
;

;dl is number of harddrive where is bootloader
org 0x7C00
bits 16

;reset hard drive
xor ah,ah
int 0x13
;read sectors
clc
mov bx,0x7E00
mov es,bx
xor bx,bx
mov ah,0x02 ;function
mov al,0x1  ;sectors to read
mov ch,0x0  ;tracks
mov cl,0x1  ;sector
mov dh,0x0  ;head
int 0x13
;if not readed jmp to error
jc error
;jump to 0x7E00 - executed only if loaded
jmp 0x7E00
error:
    mov si,MSGError
    .loop:
        lodsb
        or al,al
        jz .end
        mov ah,0x0E
        int 0x10
        jmp .loop
    .end:
        hlt
MSGError db "Error while booting", 0x0
times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA

;
; SECTOR 0x1
;

jmp printtest
;definitions
MSGLoaded db "Execution successful", 0x0
;
; Print function
; si - message to pring (NEED TO BE FINISHED WITH 0x0)

printtest:
    mov si,MSGLoaded
    .loop:
        lodsb
        or al,al
        jz .end
        mov ah,0x0E
        int 0x10
        jmp .loop
    .end:
        hlt

times 0x400 - ($-$$) db 0x0

我一直在使用VirtualBox测试此代码但实际上没有发生任何事情,读取错误没有显示,以及应该打印的消息。

1 个答案:

答案 0 :(得分:44)

此代码的主要问题是:

  1. ES:BX 指向错误的段:偏移以将内核加载到
  2. 错误的部门正在加载,所以内核不是预期的
  3. 第一个是在这段代码中:

    mov bx,0x7E00
    mov es,bx
    xor bx,bx
    

    问题是要将扇区从磁盘加载到0x0000:0x7E00 ES:BX )。此代码将 ES:BX 设置为0x7E00:0x0000,其解析为物理地址0x7E000((0x7E00 <&lt;&lt; 4)+ 0x0000)。我认为目的是将0x07E0加载到 ES 中,这将产生0x7E00((0x07E0 <&lt;&lt; 4)+ 0x0000)的物理地址。您可以了解有关16:16内存寻址计算的更多信息here。将段乘以16与将其向左移位4位相同。

    代码中的第二个问题是:

    mov ah,0x02 ;function
    mov al,0x1  ;sectors to read
    mov ch,0x0  ;tracks
    mov cl,0x2  ;sector number
    mov dh,0x0  ;head
    int 0x13
    

    磁盘上第二个512块扇区的编号是2,而不是1.因此,要修复上述代码,您需要相应地设置 CL

    mov cl,0x2  ;sector number
    

    Bootloader开发的一般提示

    应该解决的各种仿真器,虚拟机和真实物理硬件上运行代码的其他问题包括:

    1. 当BIOS跳转到您的代码时,您无法依赖 CS DS ES SS < / em>, SP 寄存器具有有效或预期的值。应在引导加载程序启动时正确设置它们。只能保证您的引导加载程序将从物理地址0x00007c00加载并运行,并且引导驱动器号已加载到 DL 寄存器中。
    2. SS:SP 设置为您知道不会与您自己的代码操作冲突的内存。 BIOS可能已将其默认堆栈指针放在第一兆字节的可用和可寻址RAM中的任何位置。不能保证它在哪里以及它是否适合您编写的代码。
    3. 可以设置或清除lodsbmovsb等使用的方向标记。如果方向标志设置不正确 SI / DI 寄存器可能会以错误的方向调整。使用STD / CLD将其设置为您希望的方向(CLD =向前/ STD =向后)。在这种情况下,代码假定向前移动,因此应使用CLD。有关详细信息,请参阅instruction set reference
    4. 当跳转到内核时, FAR JMP 通常是一个好主意,以便它正确地将 CS:IP 设置为预期值。这可以避免在同一段内可能绝对接近 JMP CALLs 的内核代码出现问题。
    5. 如果将引导加载程序作为适用于8086/8088处理器(及更高版本)的16位代码,请避免在汇编代码中使用32位寄存器。使用 AX / BX / CX / DX / SI / DI / SP / BP 而不是 EAX / EBX / ECX / EDX / ESI / EDI / ESP / EBP 。虽然在这个问题上不是问题,但对于其他寻求帮助的人来说,这是一个问题。 32位处理器可以在16位实模式下使用32位寄存器,但8086/8088/80286不能,因为它们是16位处理器,无法访问扩展的32位寄存器。
    6. FS GS 段寄存器已添加到80386+ CPU。如果您打算以8086/8088/80286为目标,请避免使用它们。
    7. 要解析第一个和第二个项目,可以在引导加载程序的开头附近使用此代码:

      xor ax,ax      ; We want a segment of 0 for DS for this question
      mov ds,ax      ;     Set AX to appropriate segment value for your situation
      mov es,ax      ; In this case we'll default to ES=DS
      mov bx,0x8000  ; Stack segment can be any usable memory
      
      cli            ; Disable interrupts to circumvent bug on early 8088 CPUs
      mov ss,bx      ; This places it with the top of the stack @ 0x80000.
      mov sp,ax      ; Set SP=0 so the bottom of stack will be @ 0x8FFFF
      sti            ; Re-enable interrupts
      
      cld            ; Set the direction flag to be positive direction
      

      有几点需要注意。当您更改 SS 寄存器的值(在这种情况下通过MOV)时,处理器应该关闭该指令的中断并保持关闭,直到之后 em>以下说明。通常,如果您更新 SS ,然后立即更新 SP ,则无需担心禁用中断。在早期的8088处理器中存在一个错误,这并不值得尊重,因此如果您针对最广泛的环境,明确禁用和重新启用它们是一个安全的选择。如果您不打算在有问题的8088上工作,那么可以在上面的代码中删除CLI / STI指令。我在80年代中期在家用电脑上做的工作第一手了解这个错误。

      要注意的第二件事是我如何设置堆栈。对于刚接触8088/8086 16位汇编的人来说,可以通过多种方式设置堆栈。在这种情况下,我将堆栈的顶部(内存中的最低部分)设置为0x8000 SS )。然后我将堆栈指针( SP )设置为0。当您在16位实模式下push something on the stack时,处理器首先将堆栈指针递减2,然后在该位置放置一个16位 WORD 。因此,对堆栈的第一次推送将是0x0000-2 = 0xFFFE(-2)。然后,您的 SS:SP 看起来像0x8000:0xFFFE。在这种情况下,堆栈从0x8000:0x0000运行到0x8000:0xFFFF

      当处理在8086上运行的堆栈(不适用于80286,80386+处理器)时,最好将堆栈指针( SP )设置为偶数。在原始8086上,如果将 SP 设置为奇数,则每次访问堆栈空间时都会产生4 clock cycle penalty。由于8088具有8位数据总线,因此不存在此惩罚,但在8086上加载16位需要4个时钟周期,而在8088上需要8个时钟周期(两个8个)位存储器读取。)

      最后,如果您要显式设置 CS:IP ,以便在 JMP 完成时正确设置 CS (至你的内核)然后建议做FAR JMP参见影响段寄存器的操作 / FAR Jump )。在NASM语法中,JMP将如下所示:

      jmp 0x07E0:0x0000
      

      有些(即MASM / MASM32)汇编程序没有直接支持对 FAR Jmp 进行编码,所以可以通过以下方式手动完成:

      db 0x0ea     ; Far Jump instruction
      dw 0x0000    ; Offset
      dw 0x07E0    ; Segment
      

      如果使用GNU汇编程序,它将如下所示:

      ljmpw $0x07E0,$0x0000