Bootloader 在真机上不工作,但 qemu 工作
Bootloader is not working at real machine but qemu works
作为我的自定义内核的一部分,我编写了一个用于在 BIOS 中引导 ELF 内核的引导加载程序。在内核的第一阶段,我尝试在保护模式下通过 pio 加载我的内核。在 QEMU 中,正如预期的那样,我的引导加载程序正常工作。但是在裸机中,我的引导加载程序卡在 pio 上(在我的汇编代码中,wait_disk 部分没有按我的预期工作。)。然后我只是在黑屏上看到一个闪烁的光标。
我的问题是,我的代码有问题吗?有什么原因导致我的引导程序在 QEMU 上运行,但在真机上被阻止了吗?
这是我的bootloader代码(省略了second stage和parsing elf部分,因为真机无法成功进入stage 2)
.section .bootloader, "awx"
.global _start
.intel_syntax noprefix
.code16
_start:
cli
cld
# Setup the ds, es, ss
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7000
# ENABLE A20
seta20_1:
in al, 0x64
test al, 0x2
jnz seta20_1 # spin until not busy
mov al, 0xd1
out 0x64, al
seta20_2:
in al, 0x64
test al, 0x2
jnz seta20_2 # spin until not busy
mov al, 0xdf
out 0x60, al
# Get a820 map from bios
get_e820:
mov eax, 0xe820
mov edi, 0x7000 + 52 + 4 # E820_map + 4
xor ebx, ebx
mov edx, 0x534d4150
mov ecx, 24
int 0x15
jc fail
cmp edx, eax
jne fail
test ebx, ebx
je fail
mov ebp, 24
parse_entry:
mov [edi - 4], ecx
add edi, 24
mov eax, 0xe820
mov ecx, 24
int 0x15
jc done
add ebp, 24
test ebx, ebx
jne parse_entry
done:
mov [edi - 4], ecx
mov dword ptr [0x7000], 0x40
mov dword ptr [0x7000 + 44], ebp
mov dword ptr [0x7000 + 48], 0x7000 + 52 # E820_map
fail:
# Switch to protected mode
lgdt gdt_desc
mov eax, cr0
or eax, 1 # CR0_PE
mov cr0, eax
# Jump to the 32bit mode
lea eax, [_code32]
push 0x8
push eax
retf
.code32
_code32:
mov ax, 0x10 # PROT_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
# Load the remaining boot loaders
mov edi, 0x7c00 # addr
xor ecx, ecx # sector
load_boot_loader:
inc ecx
add edi, 0x200
lea esi, [boot_end]
cmp edi, esi
jae end
push ecx
push edi
call read_sector
pop edi
pop ecx
jmp load_boot_loader
end:
jmp _head64
hlt
boot_fail:
mov ax, 0x8A00
mov dx, 0x8A00
out dx, al
mov ax, 0x8E00
mov dx, 0x8A00
out dx, al
spin:
jmp spin
read_sector: # edi: dst, ecx: offset
call wait_disk
mov al, 1
mov edx, 0x1F2
out dx, al
mov eax, ecx
mov edx, 0x1F3
out dx, al
mov eax, ecx
shr eax, 0x8
mov edx, 0x1F4
out dx, al
mov eax, ecx
shr eax, 0x10
mov edx, 0x1F5
out dx, al
mov eax, ecx
shr eax, 0x18
or ax, 0xE0
mov edx, 0x1F6
out dx, al
mov ax, 0x20
mov edx, 0x1F7
out dx, al
call wait_disk
mov ecx, 0x80
mov edx, 0x1F0
cld
repnz ins DWORD PTR [edi], dx
ret
wait_disk: <- kernel enter this line as expected
mov edx, 0x1F7
in al, dx <- something is wrong?
and al, 0xC0
cmp al, 0x40
jne wait_disk
ret <- kernel is not returned from this line
.p2align 2
gdt:
.quad 0; # NULL SEGMENT
.quad 0xCF9A000000FFFF; # CODE SEGMENT
.quad 0xCF92000000FFFF; # DATA SEGMENT
gdt_end:
gdt_desc:
.word gdt_end - gdt # sizeof(gdt) - 1
.word gdt # addrof(gdt)
.org 510
.word 0xaa55
原来USB启动盘没有被识别为ATA接口。我可以通过在 Linux.
中列出 /sys/block 来找出此信息(ATA 端口号)
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sda -> ../devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sdb -> ../devices/pci0000:00/0000:00:1f.2/ata2/host1/target1:0:0/1:0:0:0/block/sdb
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sdc -> ../devices/pci0000:00/0000:00:1f.2/ata3/host2/target2:0:0/2:0:0:0/block/sdc
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sdd -> ../devices/pci0000:00/0000:00:1f.2/ata4/host3/target3:0:0/3:0:0:0/block/sdd
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sde -> ../devices/pci0000:00/0000:00:14.0/usb3/3-8/3-8:1.0/host6/target6:0:0/6:0:0:0/block/sde
如您所见,USB 磁盘没有分配 ATA 端口。
此外,ATA最多支持4个磁盘,使用USB作为引导加载程序,尝试通过ATA端口访问更有可能无法正常工作。我的代码中还有另一个问题,我总是尝试访问 ATA 可启动磁盘到 ata 0。我们必须指定确切的端口和驱动程序编号才能访问可启动 ATA。所以重点是,
- USB启动盘不要使用ATA
- 必须找到准确的 ATA 端口转发到引导磁盘
- ATA 最多支持 4 个磁盘,如果你使用超过 4 个,它可能无法工作
作为我的自定义内核的一部分,我编写了一个用于在 BIOS 中引导 ELF 内核的引导加载程序。在内核的第一阶段,我尝试在保护模式下通过 pio 加载我的内核。在 QEMU 中,正如预期的那样,我的引导加载程序正常工作。但是在裸机中,我的引导加载程序卡在 pio 上(在我的汇编代码中,wait_disk 部分没有按我的预期工作。)。然后我只是在黑屏上看到一个闪烁的光标。
我的问题是,我的代码有问题吗?有什么原因导致我的引导程序在 QEMU 上运行,但在真机上被阻止了吗?
这是我的bootloader代码(省略了second stage和parsing elf部分,因为真机无法成功进入stage 2)
.section .bootloader, "awx"
.global _start
.intel_syntax noprefix
.code16
_start:
cli
cld
# Setup the ds, es, ss
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7000
# ENABLE A20
seta20_1:
in al, 0x64
test al, 0x2
jnz seta20_1 # spin until not busy
mov al, 0xd1
out 0x64, al
seta20_2:
in al, 0x64
test al, 0x2
jnz seta20_2 # spin until not busy
mov al, 0xdf
out 0x60, al
# Get a820 map from bios
get_e820:
mov eax, 0xe820
mov edi, 0x7000 + 52 + 4 # E820_map + 4
xor ebx, ebx
mov edx, 0x534d4150
mov ecx, 24
int 0x15
jc fail
cmp edx, eax
jne fail
test ebx, ebx
je fail
mov ebp, 24
parse_entry:
mov [edi - 4], ecx
add edi, 24
mov eax, 0xe820
mov ecx, 24
int 0x15
jc done
add ebp, 24
test ebx, ebx
jne parse_entry
done:
mov [edi - 4], ecx
mov dword ptr [0x7000], 0x40
mov dword ptr [0x7000 + 44], ebp
mov dword ptr [0x7000 + 48], 0x7000 + 52 # E820_map
fail:
# Switch to protected mode
lgdt gdt_desc
mov eax, cr0
or eax, 1 # CR0_PE
mov cr0, eax
# Jump to the 32bit mode
lea eax, [_code32]
push 0x8
push eax
retf
.code32
_code32:
mov ax, 0x10 # PROT_DS
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
# Load the remaining boot loaders
mov edi, 0x7c00 # addr
xor ecx, ecx # sector
load_boot_loader:
inc ecx
add edi, 0x200
lea esi, [boot_end]
cmp edi, esi
jae end
push ecx
push edi
call read_sector
pop edi
pop ecx
jmp load_boot_loader
end:
jmp _head64
hlt
boot_fail:
mov ax, 0x8A00
mov dx, 0x8A00
out dx, al
mov ax, 0x8E00
mov dx, 0x8A00
out dx, al
spin:
jmp spin
read_sector: # edi: dst, ecx: offset
call wait_disk
mov al, 1
mov edx, 0x1F2
out dx, al
mov eax, ecx
mov edx, 0x1F3
out dx, al
mov eax, ecx
shr eax, 0x8
mov edx, 0x1F4
out dx, al
mov eax, ecx
shr eax, 0x10
mov edx, 0x1F5
out dx, al
mov eax, ecx
shr eax, 0x18
or ax, 0xE0
mov edx, 0x1F6
out dx, al
mov ax, 0x20
mov edx, 0x1F7
out dx, al
call wait_disk
mov ecx, 0x80
mov edx, 0x1F0
cld
repnz ins DWORD PTR [edi], dx
ret
wait_disk: <- kernel enter this line as expected
mov edx, 0x1F7
in al, dx <- something is wrong?
and al, 0xC0
cmp al, 0x40
jne wait_disk
ret <- kernel is not returned from this line
.p2align 2
gdt:
.quad 0; # NULL SEGMENT
.quad 0xCF9A000000FFFF; # CODE SEGMENT
.quad 0xCF92000000FFFF; # DATA SEGMENT
gdt_end:
gdt_desc:
.word gdt_end - gdt # sizeof(gdt) - 1
.word gdt # addrof(gdt)
.org 510
.word 0xaa55
原来USB启动盘没有被识别为ATA接口。我可以通过在 Linux.
中列出 /sys/block 来找出此信息(ATA 端口号)lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sda -> ../devices/pci0000:00/0000:00:1f.2/ata1/host0/target0:0:0/0:0:0:0/block/sda
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sdb -> ../devices/pci0000:00/0000:00:1f.2/ata2/host1/target1:0:0/1:0:0:0/block/sdb
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sdc -> ../devices/pci0000:00/0000:00:1f.2/ata3/host2/target2:0:0/2:0:0:0/block/sdc
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sdd -> ../devices/pci0000:00/0000:00:1f.2/ata4/host3/target3:0:0/3:0:0:0/block/sdd
lrwxrwxrwx 1 root root 0 Mar 29 13:49 /sys/block/sde -> ../devices/pci0000:00/0000:00:14.0/usb3/3-8/3-8:1.0/host6/target6:0:0/6:0:0:0/block/sde
如您所见,USB 磁盘没有分配 ATA 端口。
此外,ATA最多支持4个磁盘,使用USB作为引导加载程序,尝试通过ATA端口访问更有可能无法正常工作。我的代码中还有另一个问题,我总是尝试访问 ATA 可启动磁盘到 ata 0。我们必须指定确切的端口和驱动程序编号才能访问可启动 ATA。所以重点是,
- USB启动盘不要使用ATA
- 必须找到准确的 ATA 端口转发到引导磁盘
- ATA 最多支持 4 个磁盘,如果你使用超过 4 个,它可能无法工作