我试图按照这里的说明来构建一个简单的操作系统内核:http://mikeos.sourceforge.net/write-your-own-os.html
不过,我想创建一个基于grub的ISO映像,并在模拟器中引导一个多引导CD,而不是从软盘引导。我在该页列出的源代码中添加了以下内容,用于multiboot标头:
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
这是什么意思,我该如何修复它?我试着搞乱elf格式,但只有-felf32
似乎工作…
GRUB支持ELF32和平面二进制文件。虽然您的头隐式地表示您正在提供ELF二进制文件。
在Multiboot中使用Flat Binary
如果您希望告诉Multiboot loader (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
的调用在示例中像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
Multiboot加载程序(GRUB)通常在文件的前8k处加载(无论是ELF还是平面二进制文件),在32位边界上查找Multiboot头。如果Multiboot标头FLAG的16位为空,则假定您提供的是ELF映像。然后,它解析ELF头以检索将内核文件加载到内存中所需的信息。如果设置了位16,则需要一个完整的Multiboot头文件,以便装入程序拥有将内核读入内存、执行初始化、然后调用内核的信息。
然后将init.s
组装为平面二进制文件,如:
nasm -f bin -o init.bin init.s
在Multiboot中使用ELF
将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/LD/OBJCOPY一起生成最终的平面二进制图像,而不是使用-f bin
和NASM。如果你从init中删除了ORG指令。S并使用这些命令,它应该生成一个平面二进制文件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生成一个ELF可执行文件。O调用init.elf。我们使用一个名为objcopy的特殊程序来删除所有ELF头文件,并生成一个平面二进制可执行文件init.bin。
这比仅仅使用NASM和-f bin
选项生成平面可执行文件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将包含调试信息(以dwarf格式),这些信息将与LD链接到init中。Elf(保留调试信息)。平面二进制文件不包含调试信息,因为当您在-O binary
中使用objcopy时,它们被剥离。您可以使用init。如果您启用QEMU中的远程调试功能,并使用GDB进行调试。init中的调试信息。Elf为调试器提供信息,允许您单步通过代码,按名称访问变量和标签,查看汇编程序源代码等。
除了生成调试信息之外,使用NASM/LD/OBJCOPY进程生成内核二进制文件还有另一个原因。LD是可配置的。LD允许人们创建链接器脚本,使您能够更好地调整最终二进制文件中的内容布局。这对于可能包含来自不同环境(C, Assembler等)的混合代码的更复杂的内核非常有用。对于一个小的玩具内核,它可能不需要,但是随着内核复杂性的增长,使用链接器脚本的好处将变得更加明显。
使用GDB远程调试QEMU
如果您使用上一节中的方法在ELF可执行文件中生成调试信息(init.elf),您可以启动QEMU并使其:
- 加载QEMU环境并在启动时停止CPU。从手册页:
-S不要在启动时启动CPU(你必须在监视器中输入'c')。
- 让QEMU监听localhost:1234上的GDB远程连接。从手册页:
-s -gdb tcp::1234的简写,即在tcp端口1234上打开gdbserver。
然后你只需要启动GDB使它:
- 启动GDB与我们的ELF可执行文件(init.elf)与调试符号和信息
- 连接到localhost:1234,其中QEMU正在监听
- 设置您选择的调试布局
- 在我们的内核中设置一个断点来停止(在这个例子中multiboot_entry)
下面是一个从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位程序(内核)。
<标题>重要考虑h1> 如Jester指出的那样,当使用符合Multiboot的加载程序(如GRUB)时,CPU处于32位保护模式(而不是16位实际模式)。与直接从BIOS启动不同,您将无法使用16位代码,包括大多数PC-BIOS中断。如果你需要在真实模式下,你必须手动切换回真实模式,或者创建一个VM86任务(后者不是微不足道的)。这是一个重要的考虑,因为你在MikeOS中链接的一些代码是16位的。
标题>