6

我正在尝试按照此处的说明构建一个简单的操作系统内核: 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似乎只能工作......

4

1 回答 1

8

GRUB 支持ELF32和平面二进制文件。您的标头虽然暗示您提供的是ELF二进制文件。

将平面二进制文件与多重引导一起使用

如果您想告诉 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 文档中进行了详细定义。

当使用NASM-f bin,需要注意我们需要为我们的代码指定原点。多重引导加载程序在物理地址加载我们的内核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

将 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指令仅在使用. 多重引导加载程序将在物理地址加载我们,因此我们必须确保汇编和链接的代码是使用该起点生成的。使用时,我们在链接器(LD)命令行上指定入口点。或者,可以在链接描述文件中设置原点。-f bin0x100000-f elf32-Ttext=0x100000

使用 NASM/LD/OBJCOPY 生成平面二进制图像

可以一起使用NASM / LD / OBJCOPY来生成最终的平面二进制图像,而不是-f binNASM一起使用。如果您从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 的平面二进制可执行文件

这比仅使用NASM-f bin生成平面可执行文件init.bin的选项要复杂得多。那又何苦呢?通过上述方法,您可以告诉NASM生成可供gdb(GNU 调试器)使用的调试信息。如果您尝试在NASM中使用-g(启用调试),则不会生成任何调试信息。您可以通过以下方式更改汇编顺序来生成调试信息:-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(保留调试信息)中。平面二进制文件不包含调试信息,因为当您将objcopy-O binary. 如果您在QEMU中启用远程调试工具并使用GDB进行调试,则可以使用init.elfinit.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
  • 连接到QEMU正在监听的 localhost:1234
  • 设置您选择的调试布局
  • 在我们的内核中设置一个断点来停止(在这个例子中是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这样的 Multiboot 兼容加载器时,CPU 处于 32 位保护模式(不是 16 位实模式)。与直接从 BIOS 引导不同,您将无法使用 16 位代码,包括大多数 PC-BIOS 中断。如果您需要处于实模式,则必须手动更改回实模式,或创建 VM86 任务(后者并非易事)。

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

于 2015-11-03T07:37:34.723 回答