创建一个加载了grub2的简单多引导内核



我试图按照这里的说明来构建一个简单的操作系统内核: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 binNASM。如果你从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.oELF对象文件中。然后我们可以使用链接器(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位的。

相关内容

  • 没有找到相关文章

最新更新