第 2 阶段引导加载程序未正确跳转到内核条目

问题描述 投票:0回答:1

我正在使用 QEMU 在 macOS 环境中的 x86_64 NASM 中为我的 64 位操作系统开发自定义引导加载程序。引导加载程序是两阶段的,旨在从磁盘加载操作系统。我遇到了引导加载程序在将其加载到内存后无法正确跳转到内核入口点的问题。

当前行为

  1. 引导加载程序成功读取第 2 阶段并将其加载到内存中(“第 2 阶段加载成功!”)。
  2. 然后尝试跳转到位于高半地址的内核
    0xFFFFFFFF80000000
  3. 引导加载程序在尝试加载内核时会冻结

相关代码:

first_stage.asm

; First stage bootloader
[BITS 16]
[ORG 0x7C00]

start:
    ; Set up segments and stack
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0x7C00      ; Set stack just below the bootloader

    ; Store boot drive number
    mov [bootDrive], dl

    ; Load Stage 2 (3 sectors, starting from sector 2)
    mov ah, 0x02        ; BIOS read sector function
    mov al, 3           ; Number of sectors to read
    mov ch, 0           ; Cylinder number
    mov cl, 2           ; Start from sector 2
    mov dh, 0           ; Head number
    mov bx, 0x7E00      ; Load Stage 2 right after the bootloader

    int 0x13            ; BIOS interrupt to read disk
    jc disk_error       ; Jump if error

    ; Print success message
    mov si, successMsg
    call print_string

    ; Pass boot drive number to second stage and jump
    mov dl, [bootDrive] ; Load boot drive number
    jmp 0x0000:0x7E00

disk_error:
    mov si, diskErrorMsg
    call print_string
    mov ah, 0x0E        ; Print the error code in AH
    int 0x10
    cli
    hlt

print_string:
    lodsb
    or al, al
    jz done
    mov ah, 0x0E
    int 0x10
    jmp print_string
done:
    ret

diskErrorMsg db 'Disk Error!', 13, 10, 0
successMsg db 'Stage 2 loaded successfully!', 13, 10, 0
bootDrive db 0

times 510 - ($ - $$) db 0
dw 0xAA55   ; Boot signature

second_stage.asm

; Second stage bootloader
; Second stage bootloader
[BITS 16]
[ORG 0x7E00]

start:
    ; Set up segments
    xor ax, ax
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ax, 0x9000      ; Use segment 0x9000 (0x90000 physical address)
    mov ss, ax
    mov sp, 0xFFFF      ; Set stack pointer to the top of the segment

    ; Print initial success message
    mov si, stage2Msg
    call print_string_16

    ; Enable A20 line for mem access beyond 1MB
    call enable_A20

    ; Load the kernel
    call load_kernel

    ; Load GDT and switch to protected mode
    call enable_protected_mode
enable_A20:
    in al, 0x92
    or al, 2
    out 0x92, al
    ret

load_kernel:
    mov ah, 0x02        ; BIOS read sector function
    mov al, 9           ; Number of sectors to read (adjust as needed)
    mov ch, 0           ; Cylinder number
    mov cl, 5           ; Start from sector 5
    mov dh, 0           ; Head number
    ; DL already contains the boot drive number from the first stage
    mov bx, 0x8000      ; Load starting at 0x8000

    int 0x13            ; BIOS interrupt to read disk
    jc disk_error       ; Jump if error
    ret

enable_protected_mode:
    cli                 ; Disable interrupts
    lgdt [gdt_descriptor]

    ; Enable protected mode
    mov eax, cr0
    or eax, 1           ; Set PE (Protection Enable) bit
    mov cr0, eax

    ; Enable PAE
    mov eax, cr4
    or eax, 1 << 5      ; Set PAE bit
    mov cr4, eax

    ; Enter protected mode
    jmp 0x08:protected_mode_entry

[BITS 32]
protected_mode_entry:
    mov ax, 0x10        ; Data segment selector
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Set up stack for protected mode
    mov esp, 0x90000

    ; Debug print
    mov esi, protected_mode_msg
    call print_string_pm

    ; Set up paging for long mode
    call setup_paging

    ; Enable long mode
    call enable_long_mode

    ; Load 64-bit GDT
    lgdt [gdt64_descriptor]

    ; Jump to long mode entry
    jmp 0x08:long_mode_entry

setup_paging:
    ; Set up 4-level paging
    mov edi, 0x1000
    mov cr3, edi
    xor eax, eax
    mov ecx, 4096
    rep stosd
    mov edi, cr3

    mov dword [edi], 0x2003      ; PML4[0] -> PDPT
    add edi, 0x1000
    mov dword [edi], 0x3003      ; PDPT[0] -> PD
    add edi, 0x1000
    mov dword [edi], 0x4003      ; PD[0] -> PT
    add edi, 0x1000

    mov ebx, 0x00000003          ; Entry flags
    mov ecx, 512                 ; Number of entries

    .set_entry:
        mov dword [edi], ebx     ; Set entry
        add ebx, 0x1000          ; Next page
        add edi, 8               ; Next entry
        loop .set_entry

    ret

enable_long_mode:
    ; Enable long mode in IA32_EFER MSR
    mov ecx, 0xC0000080
    rdmsr
    or eax, 1 << 8      ; Set Long Mode Enable (LME) bit
    wrmsr
    ret

[BITS 64]
long_mode_entry:
    ; Set up segment registers for long mode
    mov ax, 0x10        ; Data segment selector
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    ; Set up stack for long mode
    mov rsp, 0x90000

    ; Print long mode message
    mov rsi, long_mode_msg
    call print_string_lm

    mov rax, 0xFFFFFFFF80000000   ; Kernel address
    jmp rax

[BITS 16]
print_string_16:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0E        ; BIOS teletype function
    int 0x10
    jmp print_string_16
.done:
    ret

[BITS 32]
print_string_pm:
    push eax
    push ebx
.loop:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0F        ; Text attribute for character
    mov ebx, 0xB8000    ; VGA video memory address
    mov [ebx], ax       ; Write character to VGA memory
    add ebx, 2          ; Move to next character
    jmp .loop
.done:
    pop ebx
    pop eax
    ret

[BITS 64]
print_string_lm:
    push rax
    push rbx
.loop:
    lodsb
    or al, al
    jz .done
    mov ah, 0x0F        ; Text attribute for character
    mov rbx, 0xB8000    ; VGA video memory address
    mov [rbx], ax       ; Write character to VGA memory
    add rbx, 2          ; Move to next character
    jmp .loop
.done:
    pop rbx
    pop rax
    ret

[BITS 16]
disk_error:
    mov si, diskErrorMsg
    call print_string_16
    cli
    hlt

; Data
stage2Msg db 'Entered Stage 2', 13, 10, 0
protected_mode_msg db 'Entered Protected Mode', 0
long_mode_msg db 'Entered Long Mode', 0
diskErrorMsg db 'Disk Error While Reading Kernel!', 13, 10, 0

; GDT for Protected Mode
gdt_start:
    dq 0x0000000000000000 ; Null descriptor
    dq 0x00CF9A000000FFFF ; 32-bit code descriptor
    dq 0x00CF92000000FFFF ; 32-bit data descriptor
gdt_end:

gdt_descriptor:
    dw gdt_end - gdt_start - 1
    dd gdt_start

; GDT for Long Mode
gdt64_start:
    dq 0x0000000000000000 ; Null descriptor
    dq 0x00AF9A000000FFFF ; 64-bit code descriptor
    dq 0x00AF92000000FFFF ; 64-bit data descriptor
gdt64_end:

gdt64_descriptor:
    dw gdt64_end - gdt64_start - 1
    dq gdt64_start

times 1536 - ($ - $$) db 0 ; Pad to 3 sectors

linker.ld

ENTRY(kernel_start)

KERNEL_VMA = 0xFFFFFFFF80000000;
KERNEL_LMA = 0x8000;

SECTIONS
{
    . = KERNEL_VMA;

    .text ALIGN(4K) : AT(ADDR(.text) - KERNEL_VMA + KERNEL_LMA)
    {
        *(.text*)
    }

    .rodata ALIGN(4K) : AT(ADDR(.rodata) - KERNEL_VMA + KERNEL_LMA)
    {
        *(.rodata*)
    }

    .data ALIGN(4K) : AT(ADDR(.data) - KERNEL_VMA + KERNEL_LMA)
    {
        *(.data*)
    }

    .bss ALIGN(4K) : AT(ADDR(.bss) - KERNEL_VMA + KERNEL_LMA)
    {
        *(COMMON)
        *(.bss*)
    }

    /DISCARD/ :
    {
        *(.eh_frame)
        *(.comment)
    }
}

Makefile

ARCH ?= x86_64

ASM=nasm
CC=$(ARCH)-elf-gcc
LD=$(ARCH)-elf-ld
OBJCOPY=x86_64-elf-objcopy
STRIP=x86_64-elf-strip
NASMFLAGS=-f bin
NASMFLAGS_ELF64=-f elf64
CFLAGS=-ffreestanding -nostdlib -mno-red-zone -Wall -Wextra -g -O2 -mcmodel=kernel
LDFLAGS=-T linker.ld

BUILD_DIR=bin
BOOT_DIR=boot/$(ARCH)
KERNEL_DIR=kernel
ARCH_DIR=kernel/arch/

KERNEL_BIN=$(BUILD_DIR)/kernel-$(ARCH).bin
KERNEL_ELF=$(BUILD_DIR)/kernel-$(ARCH).elf
BOOTLOADER_IMG=$(BUILD_DIR)/bootloader-$(ARCH).img

KERNEL_C=$(KERNEL_DIR)/kernel.c
KERNEL_ENTRY=$(ARCH_DIR)/entry-$(ARCH).asm

FIRST_STAGE=$(BOOT_DIR)/first_stage.asm
SECOND_STAGE=$(BOOT_DIR)/second_stage.asm

$(BUILD_DIR):
    @mkdir -p $(BUILD_DIR)

$(BUILD_DIR)/first_stage-$(ARCH).bin: $(FIRST_STAGE) | $(BUILD_DIR)
    $(ASM) $(NASMFLAGS) $< -o $@

$(BUILD_DIR)/second_stage-$(ARCH).bin: $(SECOND_STAGE) | $(BUILD_DIR)
    $(ASM) $(NASMFLAGS) $< -o $@

$(BUILD_DIR)/entry-$(ARCH).o: $(KERNEL_ENTRY) | $(BUILD_DIR)
    $(ASM) $(NASMFLAGS_ELF64) $< -o $@

$(BUILD_DIR)/kernel.o: $(KERNEL_C) | $(BUILD_DIR)
    $(CC) $(CFLAGS) -c $< -o $@

$(KERNEL_ELF): $(BUILD_DIR)/entry-$(ARCH).o $(BUILD_DIR)/kernel.o | $(BUILD_DIR)
    $(LD) $(LDFLAGS) -o $@ $^

$(KERNEL_BIN): $(KERNEL_ELF) | $(BUILD_DIR)
    $(OBJCOPY) -O binary $< $@

$(BOOTLOADER_IMG): $(BUILD_DIR)/first_stage-$(ARCH).bin $(BUILD_DIR)/second_stage-$(ARCH).bin $(KERNEL_BIN)
    dd if=/dev/zero of=$(BOOTLOADER_IMG) bs=512 count=131072
    dd if=$(BUILD_DIR)/first_stage-$(ARCH).bin of=$(BOOTLOADER_IMG) bs=512 seek=0 conv=notrunc
    dd if=$(BUILD_DIR)/second_stage-$(ARCH).bin of=$(BOOTLOADER_IMG) bs=512 seek=2 conv=notrunc
    dd if=$(KERNEL_BIN) of=$(BOOTLOADER_IMG) bs=512 seek=34 conv=notrunc

run: $(BOOTLOADER_IMG)
    qemu-system-x86_64 -drive file=bin/bootloader-$(ARCH).img,format=raw -monitor stdio

clean:
    rm -rf $(BUILD_DIR)/*.o $(BUILD_DIR)/*.bin $(BUILD_DIR)/*.img $(KERNEL_ELF)

all: $(BOOTLOADER_IMG)

rerun: clean all run

问题:我做错了什么?

PS:内核足够小,可以容纳分配的扇区(190 字节),并且它被正确分配在预期的地址中。第一阶段启动位于

0x7C00

我尝试在 VGA 中使用打印语句进行调试,但是模拟器控制台上没有任何内容。组装过程中也出现警告:

boot/x86_64/second_stage.asm:172: warning: signed dword immediate exceeds bounds [-w+number-overflow]
boot/x86_64/second_stage.asm:172: warning: dword data exceeds bounds [-w+number-overflow]

编辑1:似乎

load_kernel
被调用后没有返回并且BIOS中断导致了问题(?)。

编辑 2:寄存器看起来是正确的并且正确地跳转到 64 位。然而,内核没有正确加载到内存中,但没有抛出错误(内存无法访问

0xFFFFFFFF80000000

assembly kernel x86-64 bootloader osdev
1个回答
0
投票

我遇到了同样的问题,无法加载C内核,我尝试使用ata-pio,但这没有帮助。 这是我的第二阶段:

%ifndef KERNEL_SECTORS
%define KERNEL_SECTORS 1
%endif

[BITS 16]
[org 0x8000]

; -------------------------
; A20 Status Check
; -------------------------
a20_status_check:
    pushf               ; Save flags
    push ds             ; Save data segment
    push es             ; Save extra segment
    push di             ; Save destination index
    push si             ; Save source index

    cli                 ; Disable interrupts during A20 check
    xor ax, ax          ; Set AX to 0
    mov es, ax          ; Set extra segment (ES) to 0
    mov di, 0x0500      ; Set DI to test memory location 0x0500 in low memory

    not ax              ; Set AX to 0xFFFF (inverted 0)
    mov ds, ax          ; Set data segment (DS) to 0xFFFF
    mov si, 0x0510      ; Set SI to memory location 0x0510 (also in low memory)

    ; Save original values from memory locations
    mov al, byte [es:di] ; Load byte at ES:DI into AL
    push ax              ; Save value from ES:DI
    mov al, byte [ds:si] ; Load byte at DS:SI into AL
    push ax              ; Save value from DS:SI

    ; Modify memory locations for the test
    mov byte [es:di], 0x00 ; Set ES:DI to 0x00
    mov byte [ds:si], 0xFF ; Set DS:SI to 0xFF

    ; Check if memory write is visible at ES:DI
    cmp byte [es:di], 0xFF ; Compare ES:DI with 0xFF

    ; Restore original memory values
    pop ax               ; Restore value from stack to AX
    mov byte [ds:si], al  ; Restore original byte at DS:SI
    pop ax               ; Restore value from stack to AX
    mov byte [es:di], al  ; Restore original byte at ES:DI

    ; Determine A20 status based on comparison result
    mov ax, 0            ; Assume A20 is off
    je status_a20_off     ; Jump if A20 is off

    mov ax, 1            ; A20 is on
    jmp status_a20_on     ; Jump to A20 enabled handling

; -------------------------
; A20 Enable via BIOS Interrupt 15h
; -------------------------
status_a20_off:
    mov ax, 0x2401       ; Request to enable A20 via BIOS (INT 15h)
    int 0x15             ; BIOS interrupt call

    jc a20_error         ; If carry flag is set (error), jump to error handler
    jmp a20_status_check ; Otherwise, recheck A20 status after enabling

; -------------------------
; A20 Enable Failure Handling
; -------------------------
a20_error:
    xor ax, ax
    mov ds, ax
    mov ah, 0x0E         ; Set up for character output
    mov bh, 0x00         ; Display page number

    mov si, msg_a20_error ; Load error message into SI

print_error_loop:
    lodsb                ; Load next byte from message
    cmp al, 0            ; Check for null terminator
    je end_error_msg_a20 ; If end of string, stop printing
    int 0x10             ; Print character in AL
    jmp print_error_loop ; Loop to print the next character

end_error_msg_a20:
    hlt                  ; Halt the system

; -------------------------
; A20 Enable Success Handling
; -------------------------
status_a20_on:
    xor ax, ax
    mov ds, ax
    mov ah, 0x0E         ; Set up for character output
    mov bh, 0x00         ; Display page number

    mov si, msg_a20_enable ; Load success message into SI

print_enable_loop:
    lodsb                ; Load next byte from message
    cmp al, 0            ; Check for null terminator
    je restore_registers_a20 ; If end of string, restore registers
    int 0x10             ; Print character in AL
    jmp print_enable_loop ; Loop to print the next character

; -------------------------
; Restore Registers and Continue
; -------------------------
restore_registers_a20:
    pop si               ; Restore source index
    pop di               ; Restore destination index
    pop es               ; Restore extra segment
    pop ds               ; Restore data segment
    popf                 ; Restore flags

    sti                  ; Re-enable interrupts

    ; Now proceed to set up GDT and TSS
    jmp gdt_setup

; -------------------------
; GDT Setup and TSS Setup
; -------------------------
gdt_setup:
    cli                   ; Disable interrupts during GDT setup
    lgdt [gdt_descriptor] ; Load GDT descriptor into GDTR

    call setup_tss

; -------------------------
; GDT & TSS Success Message
; -------------------------
    xor ax, ax
    mov ds, ax
    mov ah, 0x0E         ; Set up for character output
    mov bh, 0x00         ; Display page number

    mov si, msg_gdtss_success ; Load GDT success message into SI

gdtss_print_success:
    lodsb                   ; Load next byte from message
    cmp al, 0               ; Check for null terminator
    je enter_protected_mode     ; If end of string, proceed to protected mode
    int 0x10                ; Print character in AL
    jmp gdtss_print_success   ; Loop to print the next character

; -------------------------
; Enter Protected Mode
; -------------------------
enter_protected_mode:
    cli                 ; Disable interrupts before entering protected mode

    mov ah, 0x0E
    mov al, 'B'         ; Print 'B' for before protected mode
    int 0x10

    mov ah, 0x02        ; BIOS function to read sectors
    mov al, KERNEL_SECTORS ; Number of sectors to read
    mov ch, 0x00        ; Cylinder number (0)
    mov cl, 0x03        ; Sector number (starts at sector 3)
    mov dh, 0x00        ; Head number (0)
    mov dl, 0x80        ; Drive number (0x80 = primary hard disk)
    mov bx, load_segment      ; Destination address (0x1000 segment = 0x100000)
    int 0x13            ; Call BIOS interrupt
    jc load_error       ; Jump if error

    mov eax, cr0
    or eax, 1           ; Set protected mode bit
    mov cr0, eax

    jmp 0x08:pm_start   ; Far jump to 32-bit code
load_error:
    mov ah, 0x0E        ; BIOS function to print character
    mov al, 'E'         ; Print 'E' for error
    int 0x10            ; Call BIOS interrupt to print
    hlt

; -------------------------
; Protected Mode Code Segment
; -------------------------
[BITS 32]
pm_start:

    ; Set up segment registers for protected mode
    mov ax, 0x10        ; Load GDT data segment selector (0x10)
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov esp, 0x9FC00    ; Set ESP to a safe location within the segment

    ; Load TSS
    mov ax, 0x28        ; TSS selector
    ltr ax              ; Load Task Register with TSS selector

    call run_offset
    jmp $
; -------------------------
; TSS Memory Allocation
; -------------------------
section .bss
    tss_start: resb 104       ; Reserve 104 bytes for the TSS
    tss_end:

section .text
global setup_tss
setup_tss:
    ; Get the base address of the TSS at runtime
    lea ax, [tss_start]          ; Load the effective address of TSS into AX

    ; Set ESP0 (kernel stack pointer) and SS0 (kernel stack selector)
    mov dword [tss_start + 4], esp0   ; Set the kernel stack pointer (ESP0)
    mov word [tss_start + 8], ss0     ; Set the kernel stack segment selector (SS0)

    ; Fill the TSS base address into GDT descriptor at runtime
    mov [gdt_tss_base], ax           ; Set the lower 16 bits (Base Low)
    shr ax, 16                       ; Get the upper 16 bits of the TSS base address
    mov [gdt_tss_base + 2], al       ; Set the middle 8 bits (Base Middle)
    mov [gdt_tss_base + 7], ah       ; Set the upper 8 bits (Base High)

    ret

section .data
esp0:    dd 0x9FC00        ; Define the kernel stack pointer (0x9FC00 is an example)
ss0:     dw 0x10           ; Define the kernel data segment selector (0x10, corresponding to your GDT)

; -------------------------
; GDT Definition
; -------------------------
gdt_start:

    ; Null Descriptor (Selector 0x00)
    dd 0x0
    dd 0x0

    ; Kernel Code Segment Descriptor (Selector 0x08)
    dw 0xFFFF              ; Limit Low
    dw 0x0000              ; Base Low
    db 0x00                ; Base Middle
    db 10011010b           ; Access Byte
    db 11001111b           ; Flags and Limit High
    db 0x00                ; Base High

    ; Kernel Data Segment Descriptor (Selector 0x10)
    dw 0xFFFF              ; Limit Low
    dw 0x0000              ; Base Low
    db 0x00                ; Base Middle
    db 10010010b           ; Access Byte
    db 11001111b           ; Flags and Limit High
    db 0x00                ; Base High

    ; User Code Segment Descriptor (Selector 0x18)
    dw 0xFFFF              ; Limit Low
    dw 0x0000              ; Base Low
    db 0x00                ; Base Middle
    db 11111010b           ; Access Byte
    db 11001111b           ; Flags and Limit High
    db 0x00                ; Base High

    ; User Data Segment Descriptor (Selector 0x20)
    dw 0xFFFF              ; Limit Low
    dw 0x0000              ; Base Low
    db 0x00                ; Base Middle
    db 11110010b           ; Access Byte
    db 11001111b           ; Flags and Limit High
    db 0x00                ; Base High

    ; TSS Descriptor (Selector 0x28)
gdt_tss_base:
    dw tss_end - tss_start - 1   ; Limit (Size of TSS)
    dw 0x0000                    ; Base Low (to be filled at runtime)
    db 0x00                      ; Base Middle (to be filled at runtime)
    db 0x89                      ; Access Byte (TSS descriptor, 32-bit available)
    db 0x00                      ; Flags and Limit High
    db 0x00                      ; Base High (to be filled at runtime)

gdt_end:

; -------------------------
; GDT Descriptor
; -------------------------
gdt_descriptor:
    dw gdt_end - gdt_start - 1  ; Size of the GDT (in bytes, minus 1)
    dd gdt_start                ; Linear address of the GDT

; -------------------------
; Messages
; -------------------------
msg_a20_error   db 'A20 line enable                 ->   failed ', 0x0D, 0x0A, 0
msg_a20_enable  db 'A20 line enable                 ->   successfully ', 0x0D, 0x0A, 0

msg_gdtss_success db 'GDT and TSS is configured       ->   successfully ', 0x0D, 0x0A, 0
load_segment equ 0x1000
run_offset equ 0x100000

这是我的内核输入:

[bits 32]
extern kernel_main
global _start

_start:
    call kernel_main    ; Call the C kernel entry
    hlt                 ; Halt the system if kernel_main returns
© www.soinside.com 2019 - 2024. All rights reserved.