【问题标题】:Boot loader doesn't jump to kernel code引导加载程序不会跳转到内核代码
【发布时间】:2015-12-18 13:04:23
【问题描述】:

我正在编写小型操作系统 - 用于练习。我从引导加载程序开始。
我想创建在 16 位实模式下运行的小型命令系统(目前)。
我创建了用于重置驱动器的引导加载程序,然后在引导加载程序之后加载扇区。
问题是因为在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 测试这段代码,但实际上什么也没发生,读取错误没有显示,以及应该打印的消息。

【问题讨论】:

  • 在发出任何 BIOS 中断之前,您需要设置堆栈(SSSP 寄存器)。在使用 lodsb 和相关功能之前,您还需要调用CLDSTD。您的代码假定自动递增,因此在设置堆栈调用CLD 之后。您似乎也没有使用适当的段设置 DS 寄存器(否则 lodsb 可能无法正常工作)。从您所呈现的内核映像的原点设置到的内容中,尚不清楚。您用于链接、编译、创建磁盘映像的 makefile 或命令将对您的问题有益。
  • mov bx,0x7E00; mov es,bx,xor bx, bx 似乎可能是错误的。您的代码建议您打算从磁盘加载扇区0x0000:0x7E00=物理地址0x7E00,但您将ES:BX段设置为0x7E00BX为零,从而产生物理地址0x7E000x7E000 。我认为您的意思是将 ES 设置为 0x07E0 ,这将产生 0x07E00x7E00 的物理地址。您基本上将内核加载到内存中的位置比您预期的要高得多。尝试将“mov bx,0x7E00”更改为mov bx,0x07E0
  • 我的内核的起源是扇区 0x1。我使用 nasm bootloader.asm -o bootloader.img' (bootloader.asm is code that is in main thread). then setting up bootloader.img as floppy in VirtualBox. about lodsb` 进行编译,而 jc 我使用 jnc 在读取实际成功完成时显示错误消息。
  • 你说什么都没打印,我认为这主要是因为你没有设置lodsb 需要的DS 寄存器。在启动引导加载程序时,您应该手动设置DS,在这种情况下,它应该设置为0。所以xor ax, axmov ds, ax(或任何相当于将0 移动到DS 的东西)肯定会有所帮助。我确实相信您没有看到任何打印的事实很可能是因为 DS 错误(未设置)。您不能依靠 BIOS 跳转到具有正确 DS、ES、SS 和 SP 值的引导加载程序
  • 你的扇区号是对的,我已经更改了它并且内核正常打印了消息。我不知道为什么它之前印有“Ä┼╝”,但它现在可以工作了。我真的很感激。

标签: assembly virtualbox nasm x86-16 bootloader


【解决方案1】:

这段代码的主要问题是:

  1. ES:BX 指向了错误的段:将内核加载到的偏移量
  2. 加载了错误的扇区,因此内核不是预期的

第一个在这段代码中:

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

问题想将扇区从磁盘加载到0x0000:0x7E00(ES:BX)。此代码将 ES:BX 设置为 0x7E00:0x0000,它解析为物理地址 0x7E000 ((0x7E000x07E0 加载到ES 中,这将产生0x7E00 ((0x07E0here。将段乘以 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

引导加载程序开发的一般提示

其他可能导致各种模拟器、虚拟机和真实物理硬件上的运行代码出错的问题包括:

  1. 当 BIOS 跳转到您的代码时,您不能依赖 CSDSESSS,SP 寄存器具有有效或预期值。当您的引导加载程序启动时,它们应该被适当地设置。您只能保证您的引导加载程序将从物理地址 0x00007c00 加载和运行,并且引导驱动器号加载到 DL 寄存器中。
  2. SS:SP 设置为您知道不会与您自己的代码操作发生冲突的内存。 BIOS 可能已将其默认堆栈指针放置在可用和可寻址 RAM 的第一兆字节中的任何位置。无法保证它在哪里以及它是否适合您编写的代码。
  3. lodsbmovsb 等使用的方向标志可以设置或清除。如果方向标志设置不当SI/DI 寄存器可能会在错误的方向上调整。使用STD/CLD 将其设置为您希望的方向(CLD=forward/STD=backwards)。在这种情况下,代码假定向前移动,因此应该使用CLD。更多信息请参见instruction set reference
  4. 当跳转到内核时,通常最好对其进行 FAR JMP,以便正确地将 CS:IP 设置为预期值。这可以避免内核代码可能在同一段内执行 absolute near JMPsCALLs 的问题。
  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

有几点需要注意。当您更改 SS 寄存器的值时(在本例中通过 MOV),处理器应该关闭该指令的中断并保持关闭直到 以下说明。通常,如果您更新 SS,然后立即更新 SP,则无需担心禁用中断。在早期的 8088 处理器中存在一个错误,该错误未得到遵守,因此,如果您的目标是尽可能广泛的环境,则可以安全地明确禁用和重新启用它们。如果您不打算在错误的 8088 上工作,那么可以在上面的代码中删除 CLI/STI 指令。我在 80 年代中期在家用 PC 上进行的工作中第一手了解了这个错误。

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

处理在 8086 上运行的堆栈时(不适用于 80286、80386+ 处理器),最好将堆栈指针 (SP) 设置为偶数。在原始 8086 上,如果您将 SP 设置为奇数,则每次访问堆栈空间都会产生 4 clock cycle penalty。由于 8088 有一个 8 位数据总线,因此不存在这种惩罚,但是在 8086 上加载一个 16 位 word 需要 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

【讨论】:

  • 当人类使用 GNU 汇编器用于 x86 时,它看起来像 .intel_syntax noprefix jmp 0x07E0:0x0000
  • 在你写的“解决这个代码的第一项和第二项”的地方,代码 sn-p 初始化寄存器 DS、ES、SS 和 SP。 CS 寄存器呢,我们应该让这个寄存器保持原样吗?或者你会推荐什么改变?
  • @supmethods :这是个好问题。我确实有一个关于这个主题的问题和答案:stackoverflow.com/questions/34548325/…。您只需要在引导加载程序的开头设置 CS如果它使用与 CS 相关的指令。许多引导加载程序代码不需要 CS 是特定值。问答链接显示了可能出现这种情况的一些场景。如果您编写引导加载程序来避免它们,那就没问题了。如果您不确定是否可以在引导加载程序的开头附近执行 FAR JMP 以设置 CS。
  • 抱歉,发表评论时我没有阅读 cmets 和周围的文字。一个坏习惯,是的。
猜你喜欢
  • 1970-01-01
  • 2014-10-24
  • 2014-04-23
  • 2020-09-10
  • 1970-01-01
  • 1970-01-01
  • 2019-10-24
  • 2013-06-13
  • 1970-01-01
相关资源
最近更新 更多