我正在开发一个简单而小型的64位OS。到目前为止,我只使用一个文件,并使用NASM进行编译:
nasm -f bin os.asm -o os.bin
然后使用qemu测试了.bin
文件。
现在,我需要在os.bin
文件中使用多个文件。我插入了这一行:
extern helper_func
然后在代码中将其命名。在另一个.asm文件中,我创建了此函数或过程。问题是bin格式不支持extern
,因此我尝试使用ELF格式创建.obj
文件,然后将它们与gcc链接:
gcc -m32 -nostdlib -nodefaultlibs -lgcc os.obj helper.obj -t linker.ld
使用此链接器文件:
ENTRY(_start)
SECTIONS
{
. = 0x7C00;
.text :
{
*(.text);
}
}
但是,当我尝试运行已创建的.bin
时,qemu无法识别该文件。我做错了什么?
(我之所以使用gcc,是因为我计划将来使用C代码)
实际上,我什至不知道gcc中所有标志的作用;我已经从互联网XD复制了它们。
这是我到目前为止所做的:
nasm -f elf os.asm -o os.obj
nasm -f elf helper.asm -o helper.obj
gcc -m32 -nostdlib -nodefaultlibs -lgcc os.obj helper.obj -t linker.ld -o myos.bin
objcopy --input-target=elf32-little --output-target=binary myos.bin myos.bin.new
qemu-system-x86_64 myos.bin.new
任何这些编译都没有错误。但是当我运行qemu时,我得到了:
os.asm:
[bits 16]
section .text
global _start
_start:
; Zero segment
cli
jmp 0x0000:.zero_seg
.zero_seg:
xor ax, ax
mov ss, ax
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov sp, _start
cld
sti
; Reset disk
call reset_disk
; Load disk sectors
mov al, 2 ; sectors to read
mov cl, 2 ; start sector
mov bx, second_sector ; offset to load
call read_disk
; Enable A20 line
call enable_a20
jmp second_sector
_end1:
jmp $
%include "liba/disk.asm"
%include "liba/a20.asm"
; padding and magic number
times 510-($-$$) db 0
dw 0xaa55
second_sector:
call check_long
call switch_long
_hang:
jmp $
%include "liba/long.asm"
%include "liba/gdt.asm"
[bits 64]
extern helper_func
long_mode:
jmp kernel_code
_end2:
jmp $
times 512-($-$$-512) db 0
kernel_code:
; two byte
call helper_func
helper.asm:
[bits 64]
section .text
global helper_func
helper_func:
kernel_end:
hlt
jmp .kernel_end
ret
在os.asm内部,我使用了以下库:
disk.asm:
read_disk:
pusha
mov ah, 0x02
mov dl, 0x80 ; 0x00 Floppy/FlashDrive -- 0x80 HardDisk
mov ch, 0 ; cylinder
mov dh, 0 ; head
int 0x13
jc .disk_err
popa
ret
.disk_err:
jmp $
reset_disk:
xor ax, ax
mov bx, second_sector
mov dl, 0x80
int 0x13
ret
a20.asm:
test_a20:
pusha
mov ax, [0x7dfe]
push bx
mov bx, 0xffff
mov es, bx
pop bx
mov bx, 0x7e0e
mov dx, [es:bx]
cmp ax, dx
je .cont
popa
mov ax, 1
ret
.cont:
mov ax, [0x7dff]
push bx
mov bx, 0xffff
mov es, bx
pop bx
mov bx, 0x7e0f
mov dx, [es:bx]
cmp ax, dx
je .exit
popa
mov ax, 1
ret
.exit:
popa
xor ax, ax
ret
enable_a20:
pusha
;BIOS
mov ax, 0x2401
int 0x15
call test_a20
cmp ax, 1
je .done
;Keyboard
sti
call wait_c
mov al, 0xad
out 0x64, al
call wait_c
mov al, 0xd0
out 0x64, al
call wait_d
in al, 0x60
push ax
call wait_d
mov al, 0xd1
out 0x64, al
call wait_c
pop ax
or al, 2
out 0x60, al
call wait_c
mov al, 0xae
out 0x64, al
call wait_c
sti
call test_a20
cmp ax, 1
je .done
;FastA20
in al, 0x92
or al, 2
out 0x92, al
call test_a20
cmp al, 1
je .done
jmp $
.done:
popa
ret
wait_c:
in al, 0x64
test al, 2
jnz wait_c
ret
wait_d:
in al, 0x64
test al, 1
jz wait_d
ret
long.asm:
enable_long:
cli
call check_long
mov edi, 0x1000
mov cr3, edi
xor eax, eax
mov ecx, 4096
rep stosd
mov edi, 0x1000
mov dword [edi], 0x2003
add edi, 0x1000
mov dword [edi], 0x3003
add edi, 0x1000
mov dword [edi], 0x4003
add edi, 0x1000
mov dword ebx, 3
mov ecx, 512
.setEntry:
mov dword [edi], ebx
add ebx, 0x1000
add edi, 8
loop .setEntry
mov eax, cr4
or eax, 1 << 5
mov cr4, eax
mov ecx, 0xc0000080
rdmsr
or eax, 1 << 8
wrmsr
mov eax, cr0
or eax, 1 << 31
or eax, 1 << 0
mov cr0, eax
ret
switch_long:
call enable_long
lgdt [GDT.Pointer]
jmp GDT.Code:long_mode
ret
check_long:
pusha
pushfd
pop eax
mov ecx, eax
xor eax, 1 << 21
push eax
popfd
pushfd
pop eax
xor eax, ecx
jz .done
mov eax, 0x80000000
cpuid
cmp eax, 0x80000001
jb .done
mov eax, 0x80000001
cpuid
test edx, 1 << 29
jz .done
popa
ret
.done:
popa
jmp $
gdt.asm:
GDT:
.Null: equ $ - GDT
dw 0
dw 0
db 0
db 0
db 0
db 0
.Code: equ $ - GDT
dw 0
dw 0
db 0
db 10011000b
db 00100000b
db 0
.Data: equ $ -GDT
dw 0
dw 0
db 0
db 10000000b
db 0
db 0
.Pointer:
dw $ - GDT - 1
dq GDT
答案 0 :(得分:1)
我对构建环境一无所知。我强烈建议构建一个x86-64 cross compiler。
我可以对一些可能的问题做出合理的猜测。使用GCC链接将生成一个.note.gnu.build-id
节,并将其放在二进制文件中所有其他节的前面。这样可以将磁盘启动签名(0xaa55
)移到第一个扇区中最后2个字节以外的位置。这是我认为您的磁盘不会被识别为可启动磁盘的唯一原因,在您的屏幕截图中似乎就是这种情况。您需要将-Wl,build-id=none
添加到GCC链接选项,以防止生成此部分。
要以这种方式构建自定义的64位Bootloader,您应将所有内容生成为64位对象而不是32位对象。与GCC进行编译/链接时,请使用-m64
;与NASM进行组装时,请使用-felf64
;如果与--64
GNU汇编器进行组装,请使用as
。
确保您不会使用-static
生成可重定位代码,建议您使用选项.eh_frame
来消除-fno-asynchronous-unwind-tables
部分。
可以通过让OBJCOPY参数确定源对象/可执行文件类型来简化该参数。您可以改为使用objcopy -O binary myos.bin myos.bin.new
。
我认为您应该使用的命令如下所示:
nasm -f elf64 os.asm -o os.obj
nasm -f elf64 helper.asm -o helper.obj
gcc -m64 -wl,--build-id=none -static -fno-asynchronous-unwind-tables \
-nostdlib -nodefaultlibs -lgcc os.obj helper.obj -T linker.ld -o myos.bin
objcopy -O binary myos.bin myos.bin.new
在disk.asm
中,对驱动器号进行硬编码。您可以在引导加载程序首次启动时重用/保存 DL 寄存器的值。 BIOS为您传递了 DL 中的引导驱动器号。如果您重复使用该值,则无需根据启动的驱动器类型(软盘,硬盘驱动器等)来更改代码。
我有一些General Bootloader Tips可能很有价值。
以后,当您开始编译 C 文件时,我建议使用以下选项:
gcc -m64 -ffreestanding -mcmodel=kernel -mno-red-zone \
-mno-mmx -mno-sse -mno-sse2 -c filename.c -o filename.o