引导加载程序 - 加载第二阶段 - QEMU 可以工作,真实机器不行。

5
作为一项学习练习,我编写了一个适用于x86 bios系统的16位引导程序链接1。在QEMU上似乎运行良好。我将它dd到一台旧的amd-turion电脑(x86_64)的驱动器上,当我尝试启动该计算机时,它会显示BIOS屏幕,然后我只能看到黑屏上闪烁的光标。
我的问题是,在QEMU的x86模拟器和使用BIOS而不是UEFI的真实x86(64位)计算机之间可能有什么不同?我是否必须为真实计算机编写与QEMU不同的代码?这是我复制信息到驱动器的方式吗?计算机是否采用某种硬件级别的安全措施?
我已经学到它在VirtualBox上也无法工作。
通过在真实硬件上从第一阶段成功打印字符,似乎加载第二阶段存在问题。
我的第一阶段引导程序使用这些文件中的代码:

stage_one.asm

[bits 16]
[org 0x7c00]
LOAD_ADDR: equ 0x9000   ; This is where I'm loading the 2nd stage in RAM.
start:
    xor ax, ax          ; nullify ax so we can set 
    mov ds, ax          ; ds to 0
    mov sp, bp          ; relatively out of the way
    mov bp, 0x8000      ; set up the stack
    call disk_load      ; load the new instructions
                        ; at 0x9000
    jmp LOAD_ADDR
%include "disk_load.asm"
times 510 - ($ - $$) db 0
dw 0xaa55 ;; end of bootsector

disk_load.asm

; load DH sectors to ES:BX from drive DL
disk_load :

    mov ah, 0x02            ;read from disk
    mov al, [num_sectors]   ;read sector(s)

    mov bx, 0
    mov es, bx

    mov ch, 0x00    ;track 0
    mov cl, 0x02    ;start from 2nd sector
    mov dh, 0x00    ;head 0
    mov dl, 0x80    ;HDD 1

    mov bx, LOAD_ADDR  ;Where we read to in RAM.
    int 0x13        ; BIOS interrupt

    jc disk_error   ; Jump if error ( i.e. carry flag set )

    cmp al, [num_sectors]   ; if num read != num expected
        jne disk_error      ; display error message

    mov ax, READ_SUCCESS
    call print_string
    ret

disk_error :
    mov ax, DISK_ERROR_MSG
    call print_string

    ; Get the status of the last operation.
    xor ax, ax      ; nullify ax
    mov ah, 0x01    ; status fxn
    ;mov dl, 0x80    ; 0x80 is our drive
    int 0x13        ; call fxn

    ;mov ah, 0       ; when we print ax, we only care about the status, 
                    ; which is in al. So, we probably want to nullify
                    ; 'ah' to prevent confusion.

    call print_hex  ; print resulting status msg.
    jmp $

status_error:
    mov ax, STATUS_ERROR
    call print_string
    jmp $

num_sectors: db 0x01

; Variables
DISK_ERROR_MSG: db "Disk read error: status = " , 0
STATUS_ERROR: db 'status failed.', 0
READ_SUCCESS: db 'Read success! ', 0

;AH  02h
;AL  Sectors To Read Count
;CH  Cylinder
;CL  Sector
;DH  Head
;DL  Drive
;ES:BX   Buffer Address Pointer

%include "print.asm"

print.asm

print_char:
    pusha
        mov ah, 0x0e
        int 0x10
    popa
    ret

print_string:
    pusha           ; preserve the registers 
                    ; on the stack.
    mov bx, ax
    print_string_loop:
        mov al, [bx]            ;move buffer index to al
        cmp al, 0               ;if [[ al == 0 ]]; then
        je print_string_end    ;   goto print_string_end
        inc bx                  ;else bx++
        call print_char
        jmp print_string_loop  ;   goto print_string_loop

    print_string_end:
    popa
    ret

print_hex_char:
    pusha
    print_hex_loop:

        ;print a single hex digit
        cmp al, 0x9
        jg a_thru_f
        zero_thru_nine:
            add al, '0'
            call print_char
            jmp print_hex_char_end
        a_thru_f:
            add al, 'A'-0xA
            call print_char

    print_hex_char_end:
    popa
    ret

print_hex:

    pusha

        ;note on little-endianness:
        ;   If you store 1234 in AX,
        ;   4 is the LSB, therefore:
        ;   AH = 12
        ;   AL = 34
        ;
        ;   Moral of the story --
        ;   If you print, you need to
        ;   print AH first.


        mov bl, al
        and bl, 0xF

        mov bh, al
        shr bh, 4
        and bh, 0xF

        mov cl, ah
        and cl, 0xF

        mov ch, ah
        shr ch, 4
        and ch, 0xF

        mov al, '0'
        call print_char

        mov al, 'x'
        call print_char

        mov al, ch
        call print_hex_char

        mov al, cl
        call print_hex_char

        mov al, bh
        call print_hex_char

        mov al, bl
        call print_hex_char

        mov al, ' '
        call print_char

    popa
    ret

我这样生成我的内核:
nasm -f bin -o stage1.bin stage_one.asm && \
nasm -f bin -o stage2.bin stage_two.asm && \
cat stage1.bin stage2.bin > raw.bin && \
#(mkdir cdiso || :) && \
#cp stage1.bin cdiso && cp stage2.bin cdiso && \
#mkisofs -o raw.iso -b stage1.bin cdiso/
qemu-system-x86_64 raw.bin || \
echo "COULD NOT FINISH ASSEMBLING." &>/dev/stderr

2
oldx86_64同时出现在一个句子里?如今的年轻人真是被宠坏了 :-) - paxdiablo
我已经整理了你的问题和答案。 - Michael Petch
2个回答

4
我发现了导致我的代码失败的错误。
问题出在使用int 13h从硬盘读取时。在QEMU中,磁盘的设备号始终为0x80,因此我将其硬编码到寄存器dl中,认为这是标准的硬盘指定。事实证明,BIOS方便地自动将dl寄存器设置为正在尝试引导的驱动器号,因此通过注释掉硬编码部分,我能够使它在QEMU和真实硬件上都能正常工作。
为了解决这个问题,我在disk_load.asm文件中删除(注释掉)了这一行:
mov dl, 0x80    ;HDD 1

1
我很高兴你搞定了它。我在这篇SO Answer中有一些关于bootloader的通用建议,也许对您有用,也可能没有。我注意到在您的代码中,您使用了 mov sp, bp mov bp, 0x8000。您确实需要使用一个值来设置SS段寄存器和SP寄存器。这两者是定义堆栈SS:SP的东西。_BP_通常与堆栈帧相关,在这样的bootloader中,除非BIOS调用要求将其设置为特定值,否则不需要设置它。 - Michael Petch

0

通过将控制寄存器的第0位设置为0来显式启用实模式可能会潜在地解决此问题(未经测试)。

cli
mov al, cr0
mov bl, 1
not bl
and al, bl
mov cr0, al
sli

它似乎在实模式下运行。我刚刚发现第一阶段已经成功运行。已经更新了问题。 - James M. Lay
你的代码中有一个拼写错误。将sli更改为STI - Sep Roland
为什么需要这么多指令?只需要 mov eax,cr0 and al,-2 mov cr0,eax 就可以了。 - Sep Roland
计算机始终自动以实模式启动。 - S.S. Anne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接