我正在使用 QEMU 在 macOS 环境中的 x86_64 NASM 中为我的 64 位操作系统开发自定义引导加载程序。引导加载程序是两阶段的,旨在从磁盘加载操作系统。我遇到了引导加载程序在将其加载到内存后无法正确跳转到内核入口点的问题。
当前行为
0xFFFFFFFF80000000
相关代码:
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
。
我遇到了同样的问题,无法加载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