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

vak*_*kus 15 assembly virtualbox nasm bootloader x86-16

我正在编写小型操作系统 - 用于练习.我从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
Run Code Online (Sandbox Code Playgroud)

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

Mic*_*tch 49

这段代码的主要问题是:

  1. ES:BX指向错误的段:偏移以加载内核
  2. 错误的部门正在被加载,因此内核不是预期的

第一个是在这段代码中:

mov bx,0x7E00
mov es,bx
xor bx,bx
Run Code Online (Sandbox Code Playgroud)

问题是要将磁盘扇区加载到0x0000:0x7E00(ES:BX).此代码将ES:BX设置0x7E00:0x0000解析为0x7E000((0x7E00 << 4)+ 0x0000)的物理地址.我认为目的是加载0x07E0ES中,这将产生0x7E00((0x07E0 << 4)+ 0x0000)的物理地址.您可以在此处了解有关16:16内存寻址计算的更多信息.将段乘以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
Run Code Online (Sandbox Code Playgroud)

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

mov cl,0x2  ;sector number
Run Code Online (Sandbox Code Playgroud)

Bootloader开发的一般提示

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

  1. 当BIOS跳转到您的代码时,您不能依赖具有有效或预期值的CS,DS,ES,SS,SP寄存器.应在引导加载程序启动时正确设置它们.您只能保证将从物理地址0x00007c00加载并运行引导加载程序,并将引导驱动器号加载到DL寄存器中.
  2. SS:SP设置为您知道不会与您自己的代码的操作冲突的内存.BIOS可能已将其默认堆栈指针放在第一兆字节的可用和可寻址RAM中的任何位置.无法保证它在哪里以及它是否适合您编写的代码.
  3. 通过所使用的方向标志lodsb,movsb等等可以被设置或清除.如果方向标志设置不正确,可能会在错误的方向上调整SI/DI寄存器.使用STD/ CLD将其设置为您希望的方向(CLD =正向/ STD =向后).在这种情况下,代码假定向前移动,因此应该使用CLD.有关详细信息,请参阅指令集参考
  4. 当跳转到内核时,通常最好对它进行FAR JMP,以便将CS:IP正确设置为预期值.这可避免与内核代码,做题绝对附近 的JMP的CALL相同段内.
  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. FSGS段寄存器被添加到80386+ CPU.如果您打算以8086/8088/80286为目标,请避免使用它们.

要解析第一个和第二个项目,可以在引导加载程序的开头附近使用此代码:

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

最后,如果要显式设置CS:IP以便在JMP完成(到内核)时正确设置CS,则建议执行FAR JMP(请参阅影响段寄存器的操作/FAR跳转).在NASM语法中,它看起来像这样:JMP

jmp 0x07E0:0x0000
Run Code Online (Sandbox Code Playgroud)

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

db 0x0ea     ; Far Jump instruction
dw 0x0000    ; Offset
dw 0x07E0    ; Segment
Run Code Online (Sandbox Code Playgroud)

如果使用GNU汇编程序,它看起来像:

ljmpw $0x07E0,$0x0000
Run Code Online (Sandbox Code Playgroud)

  • 当人类使用GNU汇编程序为x86时,它看起来像`.intel_syntax noprefix``jmp 0x07E0:0x0000` (3认同)
  • @supmethods:这是个好问题。我确实有一个关于这个主题的问题和答案:/sf/ask/2418382781/。您只需要在引导加载程序的开头设置 CS **如果**它使用与 CS 相关的指令。许多引导加载程序代码不要求 CS 为特定值。问答链接显示了可能是这种情况的一些场景。如果您编写引导加载程序来避免它们,那就没问题了。如果您不确定可以在引导加载程序的开头附近执行 FAR JMP 来设置 CS。 (3认同)