创建一个简单的多重内核加载GRUB2 [英] Creating a simple multiboot kernel loaded with grub2

查看:490
本文介绍了创建一个简单的多重内核加载GRUB2的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图按照指示在这里建立一个简单的OS内核: http://mikeos.sourceforge.net/write-your-own-os.html

除,而不是从软盘启动,我想创建一个基于GRUB的ISO映像和引导模拟器多重引导的CD。我已经添加了以下在该页面中列出,对于多重包头中的源:

  MBALIGN EQU 1 LT;℃的;对齐页面边界上加载的模块
meminfo的EQU 1 LT;< 1;提供内存映射
FLAGS EQU MBALIGN | meminfo中;这是多引导标志字段
MAGIC EQU 0x1BADB002; 幻数让引导程序找到头
CHECKSUM EQU - (MAGIC +标志);以上校验,以证明我们是多重
部分.multiboot
4对齐
    DD MAGIC
    DD FLAGS
    DD校验

和我做下面创建映像:

  NASM -felf32 -o init.bin init.s
CP init.bin目标的/ boot / init.bin
GRUB2-mkrescue -o init.iso目标/

然后我跑QEMU来引导它:

 的qemu-系统x86_64的-cdrom ./init.iso

从启动菜单中选择myos'后,我得到的错误

 错误:无效的拱依赖ELF魔

这是什么意思,以及如何解决?我试着用ELF格式搞乱,但只有 -felf32 似乎工作...


解决方案

GRUB支持的 ELF32 的平坦二进制文件。你的头,虽然含蓄地说,你是提供的 ELF 的二进制文件。

使用扁平二进制多重引导

如果您想告诉您正在使用一台二进制必须设置多重引导装载程序(GRUB)的第16位的为1:

  MULTIBOOT_AOUT_KLUDGE EQU 1 LT;< 16
                              ; FLAGS [16]指示GRUB我们不
                              ; ELF可执行文件和田野
                              ;头地址,加载地址,负载端地址,
                              ; BSS结束地址,以及入口地址将是
                              ;在我们的多重引导头可用

这是不是只是指定这个标志一样简单。你必须提供完整的多重引导头,提供了多重引导加载程序的信息,我们的二进制加载到内存中。当使用的 ELF 的格式信息是在 ELF 的标题是precedes我们code所以没有被明确规定。多重引导头在 GRUB文档定义很详细。

在使用的 NASM 的有 -f斌要注意的是,我们需要指定我们的code原点是很重要的。多重引导装载程序来载入我们的物理地址的0x100000 内核。我们必须在我们的汇编文件中指定我们的原点是的0x100000 ,以便适当偏移等将在我们的最终平二值图像获取生成。

这是剥离,从我自己的项目之一,它提供了一个简单的头修饰的例子。到主程序的呼叫建立比如上例中的C调用,但你不必这样做的。通常我调入,是以堆栈上的几个参数的函数(使用C调用约定)。

  [BITS 32]
[全球_start]
[ORG的0x100000];如果使用-f本',我们需要指定
                              ;原点为我们code。与ORG指令
                              ;多重装载机加载在我们的身体
                              ;地址0x100000处MULTIBOOT_AOUT_KLUDGE EQU 1 LT;< 16
                              ; FLAGS [16]指示GRUB我们不
                              ; ELF可执行文件和田野
                              ;头地址,加载地址,负载端地址;
                              ; BSS结束地址和入口地址将可用
                              ;在多重引导头
MULTIBOOT_ALIGN EQU 1所述;℃下;对齐页面边界上加载的模块
MULTIBOOT_MEMINFO EQU 1所述;&所述; 1;提供内存映射MULTIBOOT_HEADER_MAGIC EQU 0x1BADB002
                              ;在第一个8K的幻数GRUB搜索
                              ;内核文件GRUB被告知要加载MULTIBOOT_HEADER_FLAGS EQU MULTIBOOT_AOUT_KLUDGE | MULTIBOOT_ALIGN | MULTIBOOT_MEMINFO
CHECKSUM EQU - (MULTIBOOT_HEADER_MAGIC + MULTIBOOT_HEADER_FLAGS)KERNEL_STACK EQU 0x00200000;堆栈开始在2MB地址和放大器;向下增长_开始:
        XOR EAX,EAX;清EAX和EBX事件
        XOR EBX,EBX;我们不被GRUB加载。
        JMP multiboot_entry;跃过多重引导头
        调整4;多重引导头必须是32
                                       ;位对齐,以避免错误13
multiboot_header:
        DD MULTIBOOT_HEADER_MAGIC;幻数
        DD MULTIBOOT_HEADER_FLAGS;标志
        DD校验,校验
        DD multiboot_header;头地址
        DD _start code入口点的加载地址
                                       ;在我们的例子_start
        DD 00;负载端地址:没有必要
        DD 00; BSS结束地址:没有必要
        DD multiboot_entry;入口地址GRUB将开始multiboot_entry:
        MOV ESP,KERNEL_STACK;设置堆​​栈
        推0;复位EFLAGS
        POPF        推EAX;第二个参数是神奇的数字
        推EBX;第一个参数多重信息指针
        调用_MAIN;调用_MAIN
        ADD ESP,8;清理8个字节推作为参数        CLI
ENDLOOP:
        HLT
        JMP ENDLOOP_主要:
        RET;没做什么

多重引导装载程序(的 GRUB 的)一般加载在你的文件的第一个8K(是否 ELF 的或扁平二进制),查找多重引导头中的32位边界上。如果的多重引导头标志的位16 的是明确的,它假定您提供的 ELF 的形象。然后它分析的 ELF 的标题检索,它需要你的内核文件加载到内存中的信息。如果的第16位的设置则需要一个完整的多重引导头,使装载机具有信息读取内核到内存,进行初始化,然后调用到你的内核。

您会再组装你的 init.s 来的东西像一个扁平二进制文件:

  NASM -f -o init.bin init.s斌

使用ELF与多重

要在小丑的言论系在你原来的问题,您应该已经能够用的 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斌时适用。多重引导加载器将在物理地址的0x100000 加载我们,我们必须确保组装和链接code与该原点产生。当使用 -f ELF32 我们指定与 -Ttext =的0x100000 的入口点链接器( LD 的)命令行。另外原点可以在链接脚本进行设置。

使用NASM / LD / objcopy把要生成平二值图像

有可能使用的 NASM / LD / objcopy把的在一起,产生最终的扁平二进制图像,而不是使用 -f斌 NASM 的。如果您的 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二进制init.elf init.bin

在此, NASM 的被告知产生的 ELF32 的对象。我们集合 init.s 的成的 ELF 的对象调用的 init.o 的文件。然后我们可以使用连接器( LD 的)来生成的 init.o 的一个 ELF 的可执行文件名为 init.elf 的。我们使用一个名为 objcopy把的剥离特别节目所有的 ELF 的头关,并产生所谓的扁平二进制可执行文件的 init.bin 的。

这是一个很大的不仅仅是使用的 NASM 的使用 -f斌选项生成可执行平更复杂的 init.bin 的。何苦呢?与上面的方法,你可以告诉的 NASM 的生成可以通过的 GDB 的(GNU调试器)被用来调试信息。如果您尝试使用 -g (启用调试)用的 NASM 的使用 -f斌没有调试信息获取生成。您可以通过改变装配顺序这种方式产生的调试信息:

  NASM -g3 -F矮-f ELF32 init.s -o init.o
LD -Ttext = 0x100000处-melf_i386 -o init.elf init.o
objcopy把-O二进制init.elf init.bin

init.o 的将包含调试信息(侏儒的格式),将与的 LD 的成的 init.elf <链接/ em>的(它保留调试信息)。扁平的二进制文件不包含调试信息,因为它们被剥去当您使用的 objcopy把的有 -O二进制。您可以使用的 init.elf 的,如果你启用的 QEMU 的远程调试设备,并使用的 GDB 的调试。在此调试信息的 init.elf 的提供信息,让您单步通过你的code,访问变量和标签的名字调试器,看源汇编code等。

除了生成调试信息,还有另外一个理由使用的 NASM / LD / objcopy把的过程中生成一个内核二进制文件。的 LD 的是多的配置。的 LD 的允许一个人创建连接器脚本,让您更好地调整得到的东西怎么在最终的二进制布局。这可以是更复杂的内核可能包含来自不同的环境(C,汇编等)code的混合物是有用的。对于一个小玩具内核它可能不被需要的,但作为内核生长在复杂性使用链接脚本将变得更加明显。

与QEMU GDB远程调试

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


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

      

    -S不要在启动时开始CPU(必须输入在监视'C')



  • 请的 QEMU 的侦听的 GDB 的本地主机上远程连接:1234。从手册页:

      

    -s速记-gdb TCP :: 1234,即打开TCP端口1234上gdbserver的。



然后你只需要推出的 GDB 的,这样吧:


  • 推出的 GDB 的与我们的 ELF 的可执行文件( init.elf 的)与调试符号和信息

  • 连接到本地主机:1234,其中的 QEMU 的是听

  • 设置你的选择
  • 的调试布局
  • 设置(在这个例子中的 multiboot_entry 的)一个破发点,停止在我们的内核

下面是从CD-ROM映像推出我们的核心的一个例子的 init.iso 的,并推出的 GDB 的连接到它:

 的qemu-系统x86_64的-cdrom ./init.iso -S -s&安培;
GDB init.elf \\
        -ex目标远程本地主机:1234'\\
        -ex布局的src'\\
        -ex布局暂存器'\\
        -ex破multiboot_entry'\\
        -ex'继续'

您应该能够使用的 GDB 的在大致相同的方式调试正常的程序。这是假设你将不会被调试16位程序(内核)。

重要事项

由于小丑指出,采用多重标准的装载机喜欢的当GRUB 的,CPU处于32位保护模式(不是16位实模式)。不像从BIOS引导正确,你将无法使用16位code在内的大部分PC机的BIOS中断。如果您需要在实模式下,你将不得不手动改回实模式,或者创建一个任务VM86(后者是不平凡的)。

这是一个重要的考虑因素,因为一些你在MikeOS链接到$ C $的c是16位。

I'm trying to follow the instructions here to build a simple OS kernel: http://mikeos.sourceforge.net/write-your-own-os.html

Except, instead of booting from a floppy, I want to create a grub-based ISO image and boot a multiboot CD in the emulator. I've added the following to the source listed at that page, for the multiboot header:

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

and I'm doing the following to create the image:

nasm -felf32 -o init.bin  init.s
cp init.bin target/boot/init.bin
grub2-mkrescue -o init.iso target/

Then I run qemu to boot it:

qemu-system-x86_64 -cdrom ./init.iso 

After selecting 'myos' from the boot menu, I get the error

error: invalid arch-dependent ELF magic

What does that mean, and how can I fix it? I tried messing with the elf format, but only -felf32 seems to work...

解决方案

GRUB supports ELF32 and flat binaries. Your header though implicitly says that you are providing an ELF binary.

Using Flat Binary with Multiboot

If you wish to tell the Multiboot loader (GRUB) that you are using a flat binary you must set bit 16 to 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

It isn't as simple as just specifying this flag. You must provide a complete Multiboot header that provides the Multiboot loader the information to load our binary into memory. When using ELF format this information is in the ELF header that precedes our code so didn't have to be explicitly provided. The Multiboot header is defined in the GRUB documentation in great detail.

When using NASM with -f bin it is important to note that we need to specify the origin point for our code. Multiboot loaders load our kernel at physical address 0x100000. We must specify in our assembler file that our origin point is 0x100000 so that proper offsets etc. will get generated in our final flat binary image.

This is an example stripped and modified from one of my own projects that provides a simple header. The call to _Main is set up like a C call in the example, but you don't have to do it that way. Usually I call into a function that takes a couple parameters on the stack (using C calling convention).

[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

The Multiboot loader (GRUB) generally loads in the first 8k of your file (whether ELF or flat binary), looks for the Multiboot header on a 32 bit boundary. If bit 16 of the Multiboot header FLAG is clear, it assumes you are providing an ELF image. It then parses the ELF header to retrieve the information it needs to load your kernel file into memory. If bit 16 is set then a complete Multiboot header is required so that the loader has the information to read your kernel into memory, perform initialization,and then call into your kernel.

You would then assemble your init.s to a flat binary with something like:

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

Using ELF with Multiboot

To tie in Jester's comments to your original question, you should have been able to boot with ELF and have it work, but it didn't because of one small detail. In your example you used this to make init.bin:

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

When using -f elf32, NASM generates object files (they aren't executable), that must be linked (with LD for example) to generate a final ELF(ELF32) executable. It would have probably worked if you had done the assemble and link processes with something like:

nasm -f elf32 init.s -o init.o 
ld -Ttext=0x100000 -melf_i386 -o init.bin init.o

Please note that when using -f elf32 you must remove the ORG directive from init.s. The ORG directive only applies when using -f bin. Multiboot loaders will load us at physical address 0x100000 so we must make sure that the assembled and linked code are generated with that origin point. When using -f elf32 we specify the entry point with -Ttext=0x100000 on the linker (LD) command line. Alternatively the origin point can be set in a linker script.

Using NASM/LD/OBJCOPY to Generate Flat Binary Images

It is possible to use NASM/LD/OBJCOPY together to produce a final flat binary image rather than using -f bin with NASM. If you remove the ORG directive from init.s and use these commands it should generate a flat binary 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 

In this, NASM is told to generate ELF32 objects. We assemble init.s into an ELF object file called init.o. We can then use the linker (LD) to generate an ELF executable from init.o called init.elf. We use a special program called objcopy to strip all the ELF headers off and generate a flat binary executable called init.bin.

This is a lot more involved than just using NASM with the -f bin option to generate the flat executable init.bin. Why bother then? With the method above you can tell NASM to generate debug information that can be utilized by gdb (the GNU debugger). If you attempt to use -g(enable debugging) with NASM using -f bin no debugging information gets generated. You can generate debug information by altering the assembly sequence this way:

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 will contain debug information (in dwarf format) that will be linked with LD into init.elf (which retains the debug information). Flat binaries don't contain debug information because they are stripped off when you use objcopy with -O binary. You can use init.elf if you enable the remote debugging facility in QEMU and use GDB for debugging. This debug info in init.elf provides information to the debugger that allows you to single step through your code, access variables and labels by name, see the source assembler code etc.

Besides generating debug information, there is another reason to use the NASM/LD/OBJCOPY process to generate a kernel binary. LD is much for configurable. LD allows a person to create linker scripts that allow you to better tune how things get laid out in the final binary. This can be useful for more complex kernels that may contain a mixture of code from different environments (C, Assembler etc). For a small toy kernel it may not be needed, but as a kernel grows in complexity the benefits of using a linker script will become more evident.

Remote debugging of QEMU with GDB

If you use the method in the previous section to generate debugging information inside an ELF executable (init.elf) you can launch QEMU and have it:

  • Load the QEMU environment and halt the CPU at startup. From man page:

    -S Do not start CPU at startup (you must type 'c' in the monitor).

  • Make QEMU listen for a GDB remote connection on localhost:1234 . From man page:

    -s Shorthand for -gdb tcp::1234, i.e. open a gdbserver on TCP port 1234.

Then you just have to launch GDB so that it:

  • Launches GDB with our ELF executable (init.elf) with debug symbols and information
  • Connects to localhost:1234 where QEMU is listening
  • Sets up the debug layout of your choice
  • Sets a break point to stop in our kernel (in this example multiboot_entry)

Here is an example of launching our kernel from the CD-ROM image init.iso, and launching GDB to connect to it:

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'

You should be able to use GDB in much the same way as debugging a normal program. This assumes you will not be debugging a 16-bit program (kernel).

Important Considerations

As Jester points out, when using Multiboot compliant loaders like GRUB, the CPU is in 32-bit protected mode (not 16-bit real mode). Unlike booting right from the BIOS, you won't be able to use 16-bit code including most of the PC-BIOS interrupts. If you need to be in real mode you would have to change back to real mode manually, or create a VM86 task (the latter isn't trivial).

This is an important consideration since some of the code you linked to in MikeOS is 16-bit.

这篇关于创建一个简单的多重内核加载GRUB2的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆