【问题标题】:Why does my bootloader crash after adding this line?为什么我的引导加载程序在添加此行后会崩溃?
【发布时间】:2016-09-01 21:51:33
【问题描述】:

我正在关注 Nick Blundell 的引导扇区编程教程(https://www.cs.bham.ac.uk/~exr/lectures/opsys/10_11/lectures/os-dev.pdfhttps://www.youtube.com/watch?v=YvZhgRO7hL4)。我的代码在我的 qemu 模拟器中运行良好,但是当我在物理机器上运行它时,每当我开始引用段寄存器时它就会崩溃。 我在学校的老师不熟悉低级编程,无法帮助我。这是我的引导加载程序,在这里我用字符串 CRASH 注释了导致它崩溃的行(注意:当我说崩溃时,它实际上只是继续从下一个磁盘加载我的操作系统。我正在从外部加载此代码硬盘):

[bits 16]
[org 0x7c00]        

mov bp, 0xffff
mov sp, bp
mov ax, 0x0000
mov ds, ax
;; mov es, ax ;; CRASH
;; mov ss, ax ;; CRASH

mov si, BOOT_MSG
call print_string
call print_newline

mov si, INIT_SEG_MSG
call print_string
call print_newline

;; mov dx, ds ;; CRASH
;; call print_hex
;; call print_newline

;;mov dx, cs ;; CRASH
;;call print_hex
;;call print_newline

;;mov dx, es ;; CRASH
;;call print_hex
;;call print_newline

;;mov dx, ss ;; CRASH
;;call print_hex
;;call print_newline

;; mov dl, 0x80         ;; disk where kernel is
;; mov cl, 3            ;; start sect
;; mov al, 1            ;; num sect
;; mov bx, 0x7ef0       ;; RAM addr
;; call load_kernel

;; mov si, KERN_MSG
;; call print_string
;; call print_newline

;;  call switch_to_pm

jmp $

%include "print.asm"
%include "print_hex.asm"
%include "disk.asm"
%include "pm.asm"

[bits 32]
pm :
    mov esi, PM_MSG
    call print_string_pm
    jmp 0x7ef0
    jmp $
[bits 16]

BOOT_MSG : db 'booted 16-bit to 0x7c00',0
KERN_MSG : db 'loaded kernel to es 0x7ef0',0
PM_MSG : db 'switched to 32-bit mode',0
INIT_SEG_MSG : db 'init segment registers',0

times 510-($-$$) db 0
dw 0xaa55                   `

我确信我有一个根本的误解,任何帮助将不胜感激。这是我的打印例程:

print_string :
    push ax
    _loop :
        lodsb
        cmp al, 0
        je _end

        mov ah, 0x0e
        int 0x10
        jmp _loop
    _end :
        pop ax
        ret

print_hex :
    mov si, HEX_TEMPLATE

    mov bx, dx
    shr bx, 12
    mov bx, [bx+HEXABET]
    mov [HEX_TEMPLATE+2], bl

    mov bx, dx      ;; bx -> 0x1234
    shr bx, 8       ;; bx -> 0x0012
    and bx, 0x000f  ;; bx -> 0x0002
    mov bx, [bx+HEXABET]
    mov [HEX_TEMPLATE+3], bl

    mov bx, dx      
    shr bx, 4
    and bx, 0x00f   
    mov bx, [bx+HEXABET]
    mov [HEX_TEMPLATE+4], bl

    mov bx, dx      
    and bx, 0x0f    
    mov bx, [bx+HEXABET]
    mov [HEX_TEMPLATE+5], bl

    call print_string
    ret 

    HEX_TEMPLATE : db '0x???? ',0
    HEXABET : db '0123456789abcdef'

print_newline :
    pusha
    mov ah, 0x0e
    mov al, 0x0d
    int 0x10
    mov al, 0x0a
    int 0x10
    popa
    ret

【问题讨论】:

  • mov bp, 0xffff mov sp, bp 可能是坏的。堆栈由 SS:SP 指向。你不知道 SS 设置在哪里。您应该同时设置 SS 和 SP。 PS:堆栈指针不应该是奇数地址(出于性能原因)。要将堆栈设置在引导加载程序下方,您可以使用 xor ax, ax mov ss, ax mov sp, 0x7c00xor ax,ax 会将 AX 归零(您可以使用更长的指令 mov ax, 0x0000
  • 实际mov dx, ds类型的指令不会自行崩溃。它必须是别的东西(在那个 mov 之后做了什么)。
  • 真实硬件上的处理器类型是什么? 8086? 80286? 80386?
  • 处理器是 Intel Atom(32 位)......我假设模拟器会自动设置我的段寄存器。此代码:xor ax, axmov ss, axmov ds, axmov sp, 0x7c00mov bp, spjmp $ 也会导致“崩溃”
  • 模拟器和真实硬件会在 BIOS 启动期间为自己设置一个 SS:SP 对。但是你不能像现在这样只改变一半。如果您更改 SP,则需要更改 SS,因为您不知道 BIOS 为 SS 使用了什么值。物理内存是通过(segment * 16)+offset计算的。据你所知,你的堆栈可能指向一些潜在的不可写的内存区域。您可能会很幸运,代码可以正常工作。

标签: assembly x86 bootloader osdev


【解决方案1】:

BIOS 的特殊性在于 CS 的状态是未知的。在 Bochs 和可能的 Qemu CS = 0 中,因此起源为 0x7C00 的代码将起作用。真正的硬件可能会传递 CS = 0x7C0,因此如果在代码开始时没有适当的远跳转,对近绝对函数的调用将偏移 0x7C00 字节,并且原点设置为 0x7C00。

解决方案:

    org    0x7C00

    jmp    0:Begin                   ; Far jump so CS = 0
Begin:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax

    org    0
    jmp    0x7C0:0                   ; Far jump so CS = 0x7C0

这可能是正在发生的事情,崩溃即将到来@call print_string,它实际上正在寻找代码@0xF8??。

【讨论】:

  • 他确实使用mov ax, 0x0000 mov ds, axDS 设置为零。 CS 只需要为接近绝对的跳跃或他使用跳跃表正确设置。他都没有这样做。仅当您需要设置 ES 时才需要设置。显示的代码在崩溃时不需要它(尽管磁盘读取中断需要它)。因为他已经注释掉了所有的调用,所以当它不起作用时很难说出他在使用什么。它是否包含磁盘读取代码?我不知道。 SS:SP 也是另一个问题。如果有任何 2 件事脱颖而出,那就是 SS:SP 和 ES 可能是罪魁祸首。
  • 接近绝对跳跃也适用于此类调用。代码中没有人使用该类型。
  • 另外一件缺失的事情是对正确设置方向标志的依赖。他应该明确地使用 CLD 来制定前进的方向,因为 OP 对 LODSB 的使用假设是这种情况。
  • 我已将代码剥离为仅设置内存段,因为现在已确认这是崩溃的根源...以下[bits 16] [org 0x7c00] jmp 0:begin @ 987654327@ mov ax, cs mov ss, ax mov ds, ax jmp $ times 510-($-$$) db 0 dw 0xaa55 仍然会导致计算机从下一个磁盘启动操作系统...
  • 这似乎是一个 BIOS 问题,因为它无法识别软盘、USB 或其他介质。您必须中断启动过程,以便选择设备或在设置中更改设备优先级。
【解决方案2】:

所以在 许多 硬重置之后,我找到了解决方案。问题是我认为是问题,不是问题。所以我决定拿起我的闪存驱动器并从我父亲的电脑上启动。它工作得很好。所以我继续在我自己的电脑上编写我的引导加载程序。我编写了进入PM模式的代码,运行它,完美,没有问题。然后我又做了一个改变。使用 dd 将字节复制到闪存驱动器,然后在我父亲的电脑上运行。可是等等。更改没有出现...所以我又运行 dd 几次,将驱动器归零,但仍然没有。所以我重新启动了我的计算机,再次运行 DD,它工作正常。 问题似乎是我的操作系统(Ubuntu 16,不确定是否相关)发出 dd 命令的方式。似乎 /dev/sdb 是一种实际上并没有写入磁盘的缓冲区,至少在我第一次移除闪存驱动器之后。也许这是操作系统中的错误。也许这是dd中的一个错误。也许我错过了一些东西。谁知道呢。 问题(如 cmets 中所述)是我不知道 Linux 块设备是如何工作的。见:sync。显然我所做的更改是缓存而不是写入的。无论哪种方式,我将从现在开始使用模拟器。谢谢大家!

【讨论】:

  • 在拔出 USB 驱动器之前,您是 run sudo sync 还是 eject /dev/sdb?默认情况下,Linux 块设备不是同步的。另外,您确定重新插入时所需的驱动器在 sdb 下吗?查看/dev/disk/by-id(和/或其他目录)
  • 无论如何,是的,只有在模拟硬件上工作后才能在真实硬件上进行测试。如果您只是为了好玩而这样做,或者根本不这样做。从理论上讲,当 BIOS 跳转到您的代码时,最好知道您对事物状态的假设,以确保您对所有段和寄存器正在做什么有一个可靠的处理。
  • 我假设 BIOS 会将 es、ds 和 ss(fs 和 gs?)设置到内存中的某个空闲位置。打印 [0x7c00] 的十六进制值将打印我的代码的第一个字节。 cs设置为0x7c00?由于 BIOS IVT 为 0x0000,我假设尝试在此处写入是非法的...
  • 现在唯一可以真正指望的(除了几十年前的一些设备)是 DL 将在其中传递引导驱动器号。至于其他所有内容(包括所有段寄存器),不要假设它们包含特定值。我在我的General Bootloader Tips 中介绍了这个
  • 在旁注中,我使用udisks 来分离任意类型的 USB 媒体。有一个Unix Stack Exchange 有一个合理的解释。它也适用于 USB 硬盘。
猜你喜欢
  • 2012-10-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-11
  • 2018-03-26
  • 1970-01-01
相关资源
最近更新 更多