[英]Creating a simple multiboot kernel loaded with grub2
我试图按照这里的说明构建一个简单的操作系统内核: http : //mikeos.sourceforge.net/write-your-own-os.html
除了从软盘启动,我想创建一个基于grub的ISO映像并在模拟器中启动多重启动CD。 对于多引导头,我已将以下内容添加到该页面上列出的源:
MBALIGN equ 1<<0 ; align loaded modules on page boundaries
MEMINFO equ 1<<1 ; provide memory map
FLAGS equ MBALIGN | MEMINFO ; this is the Multiboot 'flag' field
MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum of above, to prove we are multiboot
section .multiboot
align 4
dd MAGIC
dd FLAGS
dd CHECKSUM
我正在做以下事情来创建图像:
nasm -felf32 -o init.bin init.s
cp init.bin target/boot/init.bin
grub2-mkrescue -o init.iso target/
然后我运行qemu来启动它:
qemu-system-x86_64 -cdrom ./init.iso
从启动菜单中选择“myos”后,我收到错误消息
error: invalid arch-dependent ELF magic
这意味着什么,我该如何解决? 我试过搞乱精灵格式,但只有-felf32
似乎有效......
GRUB支持ELF32和扁平二进制文件。 你的标题隐含地表示你提供的是ELF二进制文件。
如果您希望告诉Multiboot加载程序(GRUB)您使用的是平面二进制文件,则必须将第16位设置为1:
MULTIBOOT_AOUT_KLUDGE equ 1 << 16
;FLAGS[16] indicates to GRUB we are not
;an ELF executable and the fields
;header address,load address,load end address,
;bss end address, and entry address will be
;available in our Multiboot header
它并不像指定这个标志那么简单。 您必须提供完整的Multiboot标头,为Multiboot加载程序提供将二进制文件加载到内存中的信息。 使用ELF格式时,此信息位于代码之前的ELF标头中,因此不必明确提供。 Multiboot标头在GRUB文档中详细定义。
使用带-f bin
NASM时 ,请务必注意我们需要为代码指定原点。 多引导加载程序在物理地址0x100000
处加载我们的内核。 我们必须在汇编程序文件中指定我们的原点是0x100000
以便在我们的最终平面二进制图像中生成适当的偏移等。
这是一个从我自己的项目中剥离和修改的示例,它提供了一个简单的标题。 对_Main
的调用在_Main
中设置为C调用,但您不必这样做。 通常我调用一个函数,在堆栈上使用几个参数(使用C调用约定)。
[BITS 32]
[global _start]
[ORG 0x100000] ;If using '-f bin' we need to specify the
;origin point for our code with ORG directive
;multiboot loaders load us at physical
;address 0x100000
MULTIBOOT_AOUT_KLUDGE equ 1 << 16
;FLAGS[16] indicates to GRUB we are not
;an ELF executable and the fields
;header address, load address, load end address;
;bss end address and entry address will be available
;in Multiboot header
MULTIBOOT_ALIGN equ 1<<0 ; align loaded modules on page boundaries
MULTIBOOT_MEMINFO equ 1<<1 ; provide memory map
MULTIBOOT_HEADER_MAGIC equ 0x1BADB002
;magic number GRUB searches for in the first 8k
;of the kernel file GRUB is told to load
MULTIBOOT_HEADER_FLAGS equ MULTIBOOT_AOUT_KLUDGE|MULTIBOOT_ALIGN|MULTIBOOT_MEMINFO
CHECKSUM equ -(MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)
KERNEL_STACK equ 0x00200000 ; Stack starts at the 2mb address & grows down
_start:
xor eax, eax ;Clear eax and ebx in the event
xor ebx, ebx ;we are not loaded by GRUB.
jmp multiboot_entry ;Jump over the multiboot header
align 4 ;Multiboot header must be 32
;bits aligned to avoid error 13
multiboot_header:
dd MULTIBOOT_HEADER_MAGIC ;magic number
dd MULTIBOOT_HEADER_FLAGS ;flags
dd CHECKSUM ;checksum
dd multiboot_header ;header address
dd _start ;load address of code entry point
;in our case _start
dd 00 ;load end address : not necessary
dd 00 ;bss end address : not necessary
dd multiboot_entry ;entry address GRUB will start at
multiboot_entry:
mov esp, KERNEL_STACK ;Setup the stack
push 0 ;Reset EFLAGS
popf
push eax ;2nd argument is magic number
push ebx ;1st argument multiboot info pointer
call _Main ;Call _Main
add esp, 8 ;Cleanup 8 bytes pushed as arguments
cli
endloop:
hlt
jmp endloop
_Main:
ret ; Do nothing
多引导加载程序( GRUB )通常在文件的前8k(无论是ELF还是平面二进制文件)中加载,在32位边界上查找Multiboot标头。 如果Multiboot标题FLAG的第16位清除,则假定您提供ELF图像。 然后它解析ELF头以检索将内核文件加载到内存中所需的信息。 如果设置了第16位,则需要一个完整的Multiboot标头,以便加载器具有将内核读入内存,执行初始化,然后调用内核的信息。
然后,您可以将init.s
汇编为平面二进制文件,例如:
nasm -f bin -o init.bin init.s
为了将Jester的评论与原始问题联系起来,您应该能够使用ELF启动并让它工作,但它并不是因为一个小细节。 在您的示例中,您使用它来生成init.bin :
nasm -f elf32 -o init.bin init.s
当使用-f elf32
, NASM生成目标文件(它们不可执行),必须链接(例如,使用LD )以生成最终的ELF (ELF32)可执行文件。 如果你用以下方法完成了汇编和链接过程,它可能会有用:
nasm -f elf32 init.s -o init.o
ld -Ttext=0x100000 -melf_i386 -o init.bin init.o
请注意,使用-f elf32
,必须从init.s中删除ORG指令。 ORG指令仅在使用-f bin
时适用。 多引导加载程序将加载物理地址0x100000
因此我们必须确保使用该原点生成汇编和链接代码。 使用-f elf32
我们在链接器( LD )命令行上使用-Ttext=0x100000
指定入口点。 或者,可以在链接描述文件中设置原点。
可以一起使用NASM / LD / OBJCOPY来生成最终的平面二进制图像,而不是使用带有NASM的 -f bin
。 如果从init.s中删除ORG指令并使用这些命令,则应该生成一个平面二进制init.bin :
nasm -f elf32 init.s -o init.o
ld -Ttext=0x100000 -melf_i386 -o init.elf init.o
objcopy -O binary init.elf init.bin
在此, NASM被告知生成ELF32对象。 我们组装init.s到名为init.o一个ELF目标文件。 然后,我们可以使用连接器(LD),产生从init.o称为init.elf ELF可执行文件。 我们使用一个名为objcopy的特殊程序来删除所有ELF头文件并生成一个名为init.bin的平面二进制可执行文件。
这比使用带有-f bin
选项的NASM生成扁平可执行文件init.bin要多得多 。 为什么打扰呢? 使用上面的方法,您可以告诉NASM生成可由gdb (GNU调试器)使用的调试信息。 如果您尝试使用-g
(启用调试)和NASM使用-f bin
不会生成调试信息。 您可以通过以下方式更改装配顺序来生成调试信息:
nasm -g3 -F dwarf -f elf32 init.s -o init.o
ld -Ttext=0x100000 -melf_i386 -o init.elf init.o
objcopy -O binary init.elf init.bin
init.o将包含将与LD链接到init.elf (保留调试信息)的调试信息(以dwarf格式)。 平面二进制文件不包含调试信息,因为当您将objcopy与-O binary
一起使用时,它们会被删除。 如果在QEMU中启用远程调试工具并使用GDB进行调试,则可以使用init.elf 。 init.elf中的此调试信息为调试器提供了信息,允许您单步执行代码,按名称访问变量和标签,查看源汇编代码等。
除了生成调试信息之外,还有另一个原因是使用NASM / LD / OBJCOPY过程来生成内核二进制文件。 LD非常适合配置。 LD允许一个人创建链接器脚本,使您可以更好地调整在最终二进制文件中布局的方式。 这对于可能包含来自不同环境(C,汇编程序等)的代码混合的更复杂内核非常有用。 对于小型玩具内核,可能不需要它,但随着内核的复杂性增加,使用链接器脚本的好处将变得更加明显。
如果您使用上一节中的方法在ELF可执行文件( init.elf )中生成调试信息,您可以启动QEMU并拥有它:
-S不要在启动时启动CPU(必须在监视器中键入“c”)。
-s -gdb tcp :: 1234的简写,即在TCP端口1234上打开gdbserver。
然后你只需启动GDB就可以了:
下面是从CD-ROM映像init.iso启动内核并启动GDB连接到它的示例:
qemu-system-x86_64 -cdrom ./init.iso -S -s &
gdb init.elf \
-ex 'target remote localhost:1234' \
-ex 'layout src' \
-ex 'layout regs' \
-ex 'break multiboot_entry' \
-ex 'continue'
您应该能够像调试普通程序一样使用GDB 。 这假设您不会调试16位程序(内核)。
正如Jester所指出的,当使用像GRUB这样的多引导兼容加载器时,CPU处于32位保护模式(而不是16位实模式)。 与从BIOS启动不同,您将无法使用16位代码,包括大多数PC-BIOS中断。 如果您需要处于实模式,则必须手动更改回实模式,或创建VM86任务(后者并非无足轻重)。
这是一个重要的考虑因素,因为您在MikeOS中链接的一些代码是16位。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.