繁体   English   中英

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

[英]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二进制文件。

使用Flat Binary和Multiboot

如果您希望告诉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

使用ELF和多重启动

为了将Jester的评论与原始问题联系起来,您应该能够使用ELF启动并让它工作,但它并不是因为一个小细节。 在您的示例中,您使用它来生成init.bin

nasm -f elf32 -o init.bin  init.s

当使用-f elf32NASM生成目标文件(它们不可执行),必须链接(例如,使用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来生成最终的平面二进制图像,而不是使用带有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,汇编程序等)的代码混合的更复杂内核非常有用。 对于小型玩具内核,可能不需要它,但随着内核的复杂性增加,使用链接器脚本的好处将变得更加明显。

使用GDB远程调试QEMU

如果您使用上一节中的方法在ELF可执行文件( init.elf )中生成调试信息,您可以启动QEMU并拥有它:

  • 加载QEMU环境并在启动时暂停CPU。 从手册页:

    -S不要在启动时启动CPU(必须在监视器中键入“c”)。

  • 使QEMU在localhost:1234上侦听GDB远程连接。 从手册页:

    -s -gdb tcp :: 1234的简写,即在TCP端口1234上打开gdbserver。

然后你只需启动GDB就可以了:

  • 使用带有调试符号和信息的ELF可执行文件( init.elf )启动GDB
  • 连接到本地主机: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位程序(内核)。

重要考虑因素

正如Jester所指出的,当使用像GRUB这样的多引导兼容加载器时,CPU处于32位保护模式(而不是16位实模式)。 与从BIOS启动不同,您将无法使用16位代码,包括大多数PC-BIOS中断。 如果您需要处于实模式,则必须手动更改回实模式,或创建VM86任务(后者并非无足轻重)。

这是一个重要的考虑因素,因为您在MikeOS中链接的一些代码是16位。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM