Location>code7788 >text

Booting a Homebrew Operating System with GRUB Multiboot2

Popularity:749 ℃/2024-10-08 16:55:49

Booting a Homebrew Operating System with GRUB Multiboot2

preamble

I spent a week learning the traditional BIOS boot process from the ground up. While I was amazed at the wealth of technical details behind it, I also marveled at the heavy historical baggage of the x86 architecture. After all, who would have thought that a modern CPU would need to enable an address line by manipulating a "keyboard controller register"?

In the end, I decided to go for GRUB for compatibility and functionality reasons. Besides, it's a great sense of accomplishment to have an operating system that I wrote appear in the GRUB menu along with my local Linux and boot successfully.

The full code is atappendiceportion

development environment (computer)

sports event releases
systems Windows Subsystem for Linux - Arch (2.3.24)
compilers gcc version 14.2.1 20240910 (GCC)
bootstrap program grub-install (GRUB) 2:2.12-3
virtual machine QEMU emulator version 9.1.50 / VirtualBox 7.0.6

1 Multiboot2 specification

To get GRUB to recognize and boot our homebrew OS, we need to understand the Multiboot2 specification.

Official Documentation:Multiboot2 Specification version 2.0

1.1 Multiboot2 header

An OS image must contain an additional header called Multiboot2 header, besides the headers of the format used by the OS image. The Multiboot2 header must be contained completely within the first 32768 bytes of the OS image, and must be 64-bit aligned. In general, it should come as early as possible, and may be embedded in the beginning of the text segment after the real executable header.

The general meaning of the passage is:

In the Multiboot2 specification, the first 32768 bytes of the operating system image must contain a data structure called the Multiboot2 header, and the starting address must be 64-bit aligned.

The structure of the Multiboot2 header is as follows:

Offset Type Field Name
0 u32 magic
4 u32 architecture
8 u32 header_length
12 u32 checksum
16-XX tags

1.1.1 Magic fields

The first four fields of the Multiboot2 header, between 0 and 15 bytes, are called magic fields.

magic field is an identifier for the Multiboot2 header, it must be a hexadecimal value0xE85250D6

architecture field specifies the architecture of the CPU.0 denotes the 32-bit protected mode of the i386.4 Indicates 32-bit MIPS architecture

header_length field records the length (in bytes) of the Multiboot2 header

checksum field is a 32-bit unsigned number that is used when it is combined with other fields in the magic fields (i.e., themagicarchitecture cap (a poem)header_length ) are summed, their 32-bit unsigned sums must be zero.

1.1.2 Tags

Tags consist of one tag structure after another, populated when necessary toEnsure that each tag starts at an 8-byte aligned addressTags are terminated with a tag of type = 0 and size = 8 (equivalent to '\0' at the end of a string).

It is worth noting that the official documentation gives the code andhasn't 8-byte alignment of the tag's address. This can cause misalignments when GRUB reads the tag, resulting in all sorts of strange errors (e.g. error: unsupported tag: xxx)

Each tag has the following basic structure:

        +-------------------+
u16     | type              |
u16     | flags             |
u32     | size              |
        +-------------------+

type is used to denote the type of tag, since different tags have different types in thesize There may be other data fields after that.

in the event thatflags (also known as theoptional) is 1, which means that the bootloader can ignore this tag if it lacks support for it.

size Indicates the length of the entire tag.

For example, theentry address tag The structure is as follows:

        +-------------------+
u16     | type = 3          |
u16     | flags             |
u32     | size              |
u32     | entry_addr        |
        +-------------------+

GRUB will determine that it is an entry address tag based on type = 3 and, when the preparation is complete, jump to theentry_addr The address in the field runs the operating system.

In addition to this, there are also specialized EFI bootstraps for the use ofEFI i386 entry address tag

        +-------------------+
u16     | type = 8          |
u16     | flags             |
u32     | size              |
u32     | entry_addr        |
        +-------------------+

Its structure is similar to that ofentry address tag is the same, except that type = 8. Since both specify the program entry, theBoth exist at the same timeWhat happens when you do?

In the case of EFI bootstrapping, theentry address tag will be ignoredThe bootloader jumps to theEFI i386 entry address tag Address provided.

When using a traditional BIOS boot, the boot loader will directlyreport an error error: unsupported tag: 0x08 This is because traditional bootstrapping does not support EFI-related tags. This is because traditional bootstrapping does not support EFI-related tags.flags It'll come in handy. Just take theflags Setting the lowest bit to 1 allows the bootloader to ignore this tag when it is incompatible and use it normally when EFI booting.

In this way, our startup program is compatible with multiple startup methods.

Other than that, the tag types can be found in theMultiboot2 Specification version 2.0 A detailed description can be found in Sections 3.1.3 ~ 3.1.13 of this document.

1.2 Machine status

The official documentation for the3.3 I386 machine state Describes the state of the machine when the boot loader calls the 32-bit operating system.

In addition to the usual operations of turning on Protected Mode and enabling A20 gate, there are two more important items:

  1. EAX Must contain an identifying value0x36d76289The presence of this value indicates to the operating system that it is loaded by a Multiboot2-compatible boot loader.
  2. EBX Must contain the 32-bit physical address of the Multiboot2 information structure provided by the boot loader (see theBoot information format)。

The data in these two registers will be used in subsequent kernel code.

2 Code Implementation

The official documentation provides the corresponding sample code:4.4 Example OS code

But!

As mentioned earlier, the documentation gives the Yes, there is.BUG The! Becausehasn't The 8-byte alignment of the tag's address caused GRUB to misalign the tag when reading it.

I put the complete code in theappendicePart. This section focuses on analyzing code snippets based on the Multiboot2 specification.

2.1 Defining the Multiboot header

#include ""

        .text
        
multiboot_header:
        /* Align 64 bits boundary. */
        .align 8
        /* magic */
        .long MULTIBOOT2_HEADER_MAGIC
        /* ISA: i386 */
        .long MULTIBOOT_ARCHITECTURE_I386
        /* Header length. */
        .long multiboot_header_end - multiboot_header
        /* checksum */
        .long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))

entry_address_tag_start:    
        /* everyone tag all 8 byte alignment */
        .align 8
        .short MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS
        .short MULTIBOOT_HEADER_TAG_OPTIONAL
        .long entry_address_tag_end - entry_address_tag_start
        /* entry_addr */
        .long multiboot_entry
entry_address_tag_end:

        /* terminate (law) tag indicate tags Partial closure */
end_tag_start:
        /* everyone tag all 8 byte alignment */
        .align 8
        .short MULTIBOOT_HEADER_TAG_END
        .short 0
        .long end_tag_end - end_tag_start
end_tag_end:
multiboot_header_end:

multiboot_entry:
        /* ...... */

For code readability, macro definitions are used to represent values. For details, refer to:

According to the specification, we define the Multiboot header at the beginning of the text segment to bring the data as far forward as possible.

The entry address tag is used here to indicate the address of the program entry, which is the target that GRUB jumps to when executing the kernel. However, there is another way to set up a program entry.

#include ""

        .text

        jmp multiboot_entry
        
multiboot_header:
        /* Align 64 bits boundary. */
        .align 8
        /* magic */
        .long MULTIBOOT2_HEADER_MAGIC
        /* ISA: i386 */
        .long MULTIBOOT_ARCHITECTURE_I386
        /* Header length. */
        .long multiboot_header_end - multiboot_header
        /* checksum */
        .long -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))

        /* terminate (law) tag indicate tags Partial closure */
end_tag_start:
        /* everyone tag all 8 byte alignment */
        .align 8
        .short MULTIBOOT_HEADER_TAG_END
        .short 0
        .long end_tag_end - end_tag_start
end_tag_end:
multiboot_header_end:

multiboot_entry:
        /* ...... */

As opposed to the previous code, we add the following to the beginning of the text segmentjmp multiboot_entry The same can be accomplished by removing the entry address tag.

This is because GRUB executes our operating system program directly without specifying a program entry with a tag. In this case, the first instruction that is run is the one we addedjmp multiboot_entry

2.2 Launching the portal

#include ""

#ifdef HAVE_ASM_USCORE
# define EXT_C(sym) _ ## sym
#else
# define EXT_C(sym) sym
#endif

#define STACK_SIZE 0x4000
        
        .text
        
multiboot_header:
        /* ...... */
multiboot_header_end:

        /* Program Entry Location */
multiboot_entry:
        /* Initialize the stack pointer. */
        movl $(stack + STACK_SIZE), %esp

        /* Reset EFLAGS. */
        pushl $0
        popf

        /* Push the pointer to the Multiboot information structure. */
        pushl %ebx
        /* Push the magic value. */
        pushl %eax

        /* Now enter the C main function... */
        call EXT_C(cmain)

        /* Halt. */
        pushl $halt_message
        call EXT_C(printf)
        
loop: hlt
        jmp loop

halt_message:
        .asciz "Halted."

        /* Our stack area. */
        .comm stack, STACK_SIZE

Next, to the OS entrance location.

The first thing that needs to be done is to initialize the stack pointer:

  1. #define STACK_SIZE 0x4000: Representing the size of the stack with macro definitions
  2. .comm stack, STACK_SIZE.comm instruction is used to allocate a block of uninitialized data segment memory, which here is allocatedSTACK_SIZE Bytes of space for thestack
  3. movl $(stack + STACK_SIZE), %esp: Because the stack grows down from the top, the top address of the stack is put into the stack pointer register%esp center

Then clear the EFLAGS register:

  1. pushl $0: Press immediate 0 onto the stack
  2. popf: pops the value at the top of the stack and loads it into the EFLAGS register

Once the initialization is complete, the kernel entry C functions can be called. We've done this before in the1.2 Machine statusAs mentioned inEAX respond in singingEBX The registers hold the identifier value and the address of the Multiboot2 information structure, which will be used by the kernel next. Since the kernel code is a C function, we need to press the data onto the stack in the reverse order of the function arguments, according to the C passing standard:

/* Kernel Entry Function Definitions: */
/* void cmain (unsigned long magic, unsigned long addr) */

pushl %ebx
pushl %eax

call EXT_C(cmain)

2.3 Kernel code

This part of the code is the same as the official documentation for the Consistent. The main function of this kernel is to print the Multiboot2 information structure on the screen. It is mainly used to test the Multiboot2 bootloader and as a reference example for implementing the Multiboot2 kernel.

2.4 Code Building

Need to get in the appendix first, respond in singing coding

CC = gcc
LD = ld
CFLAGS = -m32 -fno-builtin -fno-stack-protector -nostartfiles
LDFLAGS = -Ttext 0x100000 -melf_i386 -nostdlib
KERNEL_NAME = 

all: $(KERNEL_NAME)

$(KERNEL_NAME):  
	$(LD) $(LDFLAGS) $^ -o $@

: 
	$(CC) -c $(CFLAGS) $< -o $@

: 
	$(CC) -c $(CFLAGS) $< -o $@

clean:
	rm -f *.o $(KERNEL_NAME)

A brief description of the compilation options:

CFLAGS = -m32 -fno-builtin -fno-stack-protector -nostartfiles
  • -m32: Make the compiler generate 32-bit code
  • -fno-builtin: Disable the compiler's built-in optimized implementations of standard library functions (e.g. memcpy, strlen, etc.). Ensure that these functions are not replaced with compiler-optimized versions!
  • -fno-stack-protector: Disable the stack protection feature, otherwise it will compile with errors
  • -nostartfiles: Standard startup files are not used because the operating system starts up differently than regular programs
LDFLAGS = -Ttext 0x100000 -melf_i386 -nostdlib

These are options passed to the linker (e.g. ld) to control linking behavior:

  • -Ttext 0x100000: Indicates that the program's text segment will be placed at memory address 0x100000, a common location for loading the operating system.
  • -melf_i386: Specifies that the target format of the output file is the 32-bit ELF format (for the i386 architecture), which is the format recommended by the Multiboot2 specification
  • -nostdlib: Tell the linker not to link the standard library. This is necessary because the standard C libraries are not used in the kernel or bootloader, but rather they implement the required functions themselves or manipulate the hardware directly.

3 Mirroring

It is well known that modern PCs haveLegacy cap (a poem)UEFI Two boot options are available, and the next image will use theLegacy + MBR of the startup method.

optionLegacy The main reason for this is thatEFI It seems that only pixels can be output, not text directly. This can result in us not being able to view the information printed by the kernel.

See this link for the exact statement:/?f=1&t=28429

3.1 Creating mirrors

utilizationdd command creates a 64M file:

dd if=/dev/zero of= bs=1M count=64

utilizationparted Partition for the image file:

parted -s  mklabel msdos mkpart primary ext2 1MiB 100%

Mount the image file as a virtual disk and create an ext2 filesystem and mount partition

sudo losetup -P /dev/loop0 
sudo mkfs.ext2 /dev/loop0p1
sudo mount /dev/loop0p1 /mnt

3.2 Installing GRUB

mountingLegacy Start a compatible GRUB and create configuration file

sudo grub-install --target=i386-pc --boot-directory=/mnt/boot /dev/loop0

sudo mkdir -p /mnt/boot/grub
cat <<EOF > /mnt/boot/grub/
set timeout=20
set default=0

menuentry "MyOS" {
    multiboot2 /boot/
    boot
}
EOF

The startup method in the configuration file should be writtenmultiboot2 rather thanmultiboot

After the installation is complete, unmount

sudo umount /mnt
sudo losetup -d /dev/loop0

4 Virtual machine operation

4.1 QEMU

QEMU running on i386 with more than 128 M of RAM.

qemu-system-i386 -m 1G -hda 

img

img

This blue line is also drawn. If your machine and GRUB are EFI-booted, you'll notice that you can't output characters, but this blue line is still there. This is because it is achieved by setting the framebuffer pixel.

4.2 VirtualBox

To run in VirtualBox, you need to convert the image file to vdi format first.

VBoxManage convertdd   --format VDI

Next, register the hard disk and add it to the IDE controller of the virtual machine

img

img

Turn off EFI in Settings

img

That's how you start it up.

img

Appendix: Complete Code

write_grub_cfg.sh

Don't forget to add executable permissionschmod +x write_grub_cfg.sh

#!/bin/bash

KERNEL_NAME=$1
GRUB_CFG_PATH=$2

cat <<EOF > $GRUB_CFG_PATH
set timeout=20
set default=0

menuentry "MyOS" {
    multiboot2 /boot/$KERNEL_NAME
    boot
}
EOF

Makefile

CC = gcc
LD = ld
CFLAGS = -m32 -fno-builtin -fno-stack-protector -nostartfiles
LDFLAGS = -Ttext 0x100000 -melf_i386 -nostdlib
KERNEL_NAME = 
IMG_NAME = 
IMG_SIZE = 64

all: img

$(KERNEL_NAME):  
	$(LD) $(LDFLAGS) $^ -o $@

: 
	$(CC) -c $(CFLAGS) $< -o $@

: 
	$(CC) -c $(CFLAGS) $< -o $@

.PHONY: img
img: $(IMG_NAME) $(KERNEL_NAME)
	sudo losetup -P /dev/loop0 $(IMG_NAME)
	sudo mount /dev/loop0p1 /mnt
	sudo ./write_grub_cfg.sh $(KERNEL_NAME) /mnt/boot/grub/
	sudo cp $(KERNEL_NAME) /mnt/boot/
	sudo umount /mnt
	sudo losetup -d /dev/loop0


$(IMG_NAME):
	dd if=/dev/zero of=$(IMG_NAME) bs=1M count=$(IMG_SIZE)
	parted -s $(IMG_NAME) mklabel msdos mkpart primary ext2 1MiB 100%
	sudo losetup -P /dev/loop0 $(IMG_NAME)
	sudo mkfs.ext2 /dev/loop0p1
	sudo mount /dev/loop0p1 /mnt
	sudo grub-install --target=i386-pc --boot-directory=/mnt/boot /dev/loop0
	sudo mkdir -p /mnt/boot/grub
	sudo umount /mnt
	sudo losetup -d /dev/loop0

qemu: img
	qemu-system-i386 -m 1G -hda $(IMG_NAME)

clean:
	sudo umount /mnt || true
	sudo losetup -d /dev/loop0 || true
	rm -f *.o $(KERNEL_NAME) $(IMG_NAME)

/*   - bootstrap the kernel */
/*  Copyright (C) 1999, 2001, 2010  Free Software Foundation, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see </licenses/>.
 */

#define ASM_FILE        1
#include ""

/*  C symbol format. HAVE_ASM_USCORE is defined by configure. */
#ifdef HAVE_ASM_USCORE
# define EXT_C(sym)                     _ ## sym
#else
# define EXT_C(sym)                     sym
#endif

/*  The size of our stack (16KB). */
#define STACK_SIZE                      0x4000
        
        .text
        
multiboot_header:
        /*  Align 64 bits boundary. */
        .align  8
        /*  magic */
        .long   MULTIBOOT2_HEADER_MAGIC
        /*  ISA: i386 */
        .long   MULTIBOOT_ARCHITECTURE_I386
        /*  Header length. */
        .long   multiboot_header_end - multiboot_header
        /*  checksum */
        .long   -(MULTIBOOT2_HEADER_MAGIC + MULTIBOOT_ARCHITECTURE_I386 + (multiboot_header_end - multiboot_header))
entry_address_tag_start:        
        .align  8
        .short MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS
        .short MULTIBOOT_HEADER_TAG_OPTIONAL
        .long entry_address_tag_end - entry_address_tag_start
        /*  entry_addr */
        .long multiboot_entry
entry_address_tag_end:
end_tag_start:
        .align  8
        .short MULTIBOOT_HEADER_TAG_END
        .short 0
        .long end_tag_end - end_tag_start
end_tag_end:
multiboot_header_end:
multiboot_entry:
        /*  Initialize the stack pointer. */
        movl    $(stack + STACK_SIZE), %esp

        /*  Reset EFLAGS. */
        pushl   $0
        popf

        /*  Push the pointer to the Multiboot information structure. */
        pushl   %ebx
        /*  Push the magic value. */
        pushl   %eax

        /*  Now enter the C main function... */
        call    EXT_C(cmain)

        /*  Halt. */
        pushl   $halt_message
        call    EXT_C(printf)
        
loop:   hlt
        jmp     loop

halt_message:
        .asciz  "Halted."

        /*  Our stack area. */
        .comm   stack, STACK_SIZE

/*   - the C part of the kernel */
/*  Copyright (C) 1999, 2010  Free Software Foundation, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see </licenses/>.
 */

#include ""

/*  Macros. */

/*  Some screen stuff. */
/*  The number of columns. */
#define COLUMNS                 80
/*  The number of lines. */
#define LINES                   24
/*  The attribute of an character. */
#define ATTRIBUTE               7
/*  The video memory address. */
#define VIDEO                   0xB8000

/*  Variables. */
/*  Save the X position. */
static int xpos;
/*  Save the Y position. */
static int ypos;
/*  Point to the video memory. */
static volatile unsigned char *video;

/*  Forward declarations. */
void cmain (unsigned long magic, unsigned long addr);
static void cls (void);
static void itoa (char *buf, int base, int d);
static void putchar (int c);
void printf (const char *format, ...);

/*  Check if MAGIC is valid and print the Multiboot information structure
   pointed by ADDR. */
void
cmain (unsigned long magic, unsigned long addr)
{  
  struct multiboot_tag *tag;
  unsigned size;

  /*  Clear the screen. */
  cls ();

  /*  Am I booted by a Multiboot-compliant boot loader? */
  if (magic != MULTIBOOT2_BOOTLOADER_MAGIC)
    {
      printf ("Invalid magic number: 0x%x\n", (unsigned) magic);
      return;
    }

  if (addr & 7)
    {
      printf ("Unaligned mbi: 0x%x\n", addr);
      return;
    }

  size = *(unsigned *) addr;
  printf ("Announced mbi size 0x%x\n", size);
  for (tag = (struct multiboot_tag *) (addr + 8);
       tag->type != MULTIBOOT_TAG_TYPE_END;
       tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 
                                       + ((tag->size + 7) & ~7)))
    {
      printf ("Tag 0x%x, Size 0x%x\n", tag->type, tag->size);
      switch (tag->type)
        {
        case MULTIBOOT_TAG_TYPE_CMDLINE:
          printf ("Command line = %s\n",
                  ((struct multiboot_tag_string *) tag)->string);
          break;
        case MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME:
          printf ("Boot loader name = %s\n",
                  ((struct multiboot_tag_string *) tag)->string);
          break;
        case MULTIBOOT_TAG_TYPE_MODULE:
          printf ("Module at 0x%x-0x%x. Command line %s\n",
                  ((struct multiboot_tag_module *) tag)->mod_start,
                  ((struct multiboot_tag_module *) tag)->mod_end,
                  ((struct multiboot_tag_module *) tag)->cmdline);
          break;
        case MULTIBOOT_TAG_TYPE_BASIC_MEMINFO:
          printf ("mem_lower = %uKB, mem_upper = %uKB\n",
                  ((struct multiboot_tag_basic_meminfo *) tag)->mem_lower,
                  ((struct multiboot_tag_basic_meminfo *) tag)->mem_upper);
          break;
        case MULTIBOOT_TAG_TYPE_BOOTDEV:
          printf ("Boot device 0x%x,%u,%u\n",
                  ((struct multiboot_tag_bootdev *) tag)->biosdev,
                  ((struct multiboot_tag_bootdev *) tag)->slice,
                  ((struct multiboot_tag_bootdev *) tag)->part);
          break;
        case MULTIBOOT_TAG_TYPE_MMAP:
          {
            multiboot_memory_map_t *mmap;

            printf ("mmap\n");
      
            for (mmap = ((struct multiboot_tag_mmap *) tag)->entries;
                 (multiboot_uint8_t *) mmap 
                   < (multiboot_uint8_t *) tag + tag->size;
                 mmap = (multiboot_memory_map_t *) 
                   ((unsigned long) mmap
                    + ((struct multiboot_tag_mmap *) tag)->entry_size))
              printf (" base_addr = 0x%x%x,"
                      " length = 0x%x%x, type = 0x%x\n",
                      (unsigned) (mmap->addr >> 32),
                      (unsigned) (mmap->addr & 0xffffffff),
                      (unsigned) (mmap->len >> 32),
                      (unsigned) (mmap->len & 0xffffffff),
                      (unsigned) mmap->type);
          }
          break;
        case MULTIBOOT_TAG_TYPE_FRAMEBUFFER:
          {
            multiboot_uint32_t color;
            unsigned i;
            struct multiboot_tag_framebuffer *tagfb
              = (struct multiboot_tag_framebuffer *) tag;
            void *fb = (void *) (unsigned long) tagfb->common.framebuffer_addr;

            switch (tagfb->common.framebuffer_type)
              {
              case MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED:
                {
                  unsigned best_distance, distance;
                  struct multiboot_color *palette;
            
                  palette = tagfb->framebuffer_palette;

                  color = 0;
                  best_distance = 4*256*256;
            
                  for (i = 0; i < tagfb->framebuffer_palette_num_colors; i++)
                    {
                      distance = (0xff - palette[i].blue) 
                        * (0xff - palette[i].blue)
                        + palette[i].red * palette[i].red
                        + palette[i].green * palette[i].green;
                      if (distance < best_distance)
                        {
                          color = i;
                          best_distance = distance;
                        }
                    }
                }
                break;

              case MULTIBOOT_FRAMEBUFFER_TYPE_RGB:
                color = ((1 << tagfb->framebuffer_blue_mask_size) - 1) 
                  << tagfb->framebuffer_blue_field_position;
                break;

              case MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT:
                color = '\\' | 0x0100;
                break;

              default:
                color = 0xffffffff;
                break;
              }
            
            for (i = 0; i < tagfb->common.framebuffer_width
                   && i < tagfb->common.framebuffer_height; i++)
              {
                switch (tagfb->common.framebuffer_bpp)
                  {
                  case 8:
                    {
                      multiboot_uint8_t *pixel = fb
                        + tagfb->common.framebuffer_pitch * i + i;
                      *pixel = color;
                    }
                    break;
                  case 15:
                  case 16:
                    {
                      multiboot_uint16_t *pixel
                        = fb + tagfb->common.framebuffer_pitch * i + 2 * i;
                      *pixel = color;
                    }
                    break;
                  case 24:
                    {
                      multiboot_uint32_t *pixel
                        = fb + tagfb->common.framebuffer_pitch * i + 3 * i;
                      *pixel = (color & 0xffffff) | (*pixel & 0xff000000);
                    }
                    break;

                  case 32:
                    {
                      multiboot_uint32_t *pixel
                        = fb + tagfb->common.framebuffer_pitch * i + 4 * i;
                      *pixel = color;
                    }
                    break;
                  }
              }
            break;
          }

        }
    }
  tag = (struct multiboot_tag *) ((multiboot_uint8_t *) tag 
                                  + ((tag->size + 7) & ~7));
  printf ("Total mbi size 0x%x\n", (unsigned) tag - addr);
}    

/*  Clear the screen and initialize VIDEO, XPOS and YPOS. */
static void
cls (void)
{
  int i;

  video = (unsigned char *) VIDEO;
  
  for (i = 0; i < COLUMNS * LINES * 2; i++)
    *(video + i) = 0;

  xpos = 0;
  ypos = 0;
}

/*  Convert the integer D to a string and save the string in BUF. If
   BASE is equal to ’d’, interpret that D is decimal, and if BASE is
   equal to ’x’, interpret that D is hexadecimal. */
static void
itoa (char *buf, int base, int d)
{
  char *p = buf;
  char *p1, *p2;
  unsigned long ud = d;
  int divisor = 10;
  
  /*  If %d is specified and D is minus, put ‘-’ in the head. */
  if (base == 'd' && d < 0)
    {
      *p++ = '-';
      buf++;
      ud = -d;
    }
  else if (base == 'x')
    divisor = 16;

  /*  Divide UD by DIVISOR until UD == 0. */
  do
    {
      int remainder = ud % divisor;
      
      *p++ = (remainder < 10) ? remainder + '0' : remainder + 'a' - 10;
    }
  while (ud /= divisor);

  /*  Terminate BUF. */
  *p = 0;
  
  /*  Reverse BUF. */
  p1 = buf;
  p2 = p - 1;
  while (p1 < p2)
    {
      char tmp = *p1;
      *p1 = *p2;
      *p2 = tmp;
      p1++;
      p2--;
    }
}

/*  Put the character C on the screen. */
static void
putchar (int c)
{
  if (c == '\n' || c == '\r')
    {
    newline:
      xpos = 0;
      ypos++;
      if (ypos >= LINES)
        ypos = 0;
      return;
    }

  *(video + (xpos + ypos * COLUMNS) * 2) = c & 0xFF;
  *(video + (xpos + ypos * COLUMNS) * 2 + 1) = ATTRIBUTE;

  xpos++;
  if (xpos >= COLUMNS)
    goto newline;
}

/*  Format a string and print it on the screen, just like the libc
   function printf. */
void
printf (const char *format, ...)
{
  char **arg = (char **) &format;
  int c;
  char buf[20];

  arg++;
  
  while ((c = *format++) != 0)
    {
      if (c != '%')
        putchar (c);
      else
        {
          char *p, *p2;
          int pad0 = 0, pad = 0;
          
          c = *format++;
          if (c == '0')
            {
              pad0 = 1;
              c = *format++;
            }

          if (c >= '0' && c <= '9')
            {
              pad = c - '0';
              c = *format++;
            }

          switch (c)
            {
            case 'd':
            case 'u':
            case 'x':
              itoa (buf, c, *((int *) arg++));
              p = buf;
              goto string;
              break;

            case 's':
              p = *arg++;
              if (! p)
                p = "(null)";

            string:
              for (p2 = p; *p2; p2++);
              for (; p2 < p + pad; p2++)
                putchar (pad0 ? '0' : ' ');
              while (*p)
                putchar (*p++);
              break;

            default:
              putchar (*((int *) arg++));
              break;
            }
        }
    }
}

/*    - Multiboot 2 header file. */
/*   Copyright (C) 1999,2003,2007,2008,2009,2010  Free Software Foundation, Inc.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to
 *  deal in the Software without restriction, including without limitation the
 *  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 *  sell copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL ANY
 *  DEVELOPER OR DISTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 *  IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef MULTIBOOT_HEADER
#define MULTIBOOT_HEADER 1

/*  How many bytes from the start of the file we search for the header. */
#define MULTIBOOT_SEARCH                        32768
#define MULTIBOOT_HEADER_ALIGN                  8

/*  The magic field should contain this. */
#define MULTIBOOT2_HEADER_MAGIC                 0xe85250d6

/*  This should be in %eax. */
#define MULTIBOOT2_BOOTLOADER_MAGIC             0x36d76289

/*  Alignment of multiboot modules. */
#define MULTIBOOT_MOD_ALIGN                     0x00001000

/*  Alignment of the multiboot info structure. */
#define MULTIBOOT_INFO_ALIGN                    0x00000008

/*  Flags set in the ’flags’ member of the multiboot header. */

#define MULTIBOOT_TAG_ALIGN                  8
#define MULTIBOOT_TAG_TYPE_END               0
#define MULTIBOOT_TAG_TYPE_CMDLINE           1
#define MULTIBOOT_TAG_TYPE_BOOT_LOADER_NAME  2
#define MULTIBOOT_TAG_TYPE_MODULE            3
#define MULTIBOOT_TAG_TYPE_BASIC_MEMINFO     4
#define MULTIBOOT_TAG_TYPE_BOOTDEV           5
#define MULTIBOOT_TAG_TYPE_MMAP              6
#define MULTIBOOT_TAG_TYPE_VBE               7
#define MULTIBOOT_TAG_TYPE_FRAMEBUFFER       8
#define MULTIBOOT_TAG_TYPE_ELF_SECTIONS      9
#define MULTIBOOT_TAG_TYPE_APM               10
#define MULTIBOOT_TAG_TYPE_EFI32             11
#define MULTIBOOT_TAG_TYPE_EFI64             12
#define MULTIBOOT_TAG_TYPE_SMBIOS            13
#define MULTIBOOT_TAG_TYPE_ACPI_OLD          14
#define MULTIBOOT_TAG_TYPE_ACPI_NEW          15
#define MULTIBOOT_TAG_TYPE_NETWORK           16
#define MULTIBOOT_TAG_TYPE_EFI_MMAP          17
#define MULTIBOOT_TAG_TYPE_EFI_BS            18
#define MULTIBOOT_TAG_TYPE_EFI32_IH          19
#define MULTIBOOT_TAG_TYPE_EFI64_IH          20
#define MULTIBOOT_TAG_TYPE_LOAD_BASE_ADDR    21

#define MULTIBOOT_HEADER_TAG_END  0
#define MULTIBOOT_HEADER_TAG_INFORMATION_REQUEST  1
#define MULTIBOOT_HEADER_TAG_ADDRESS  2
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS  3
#define MULTIBOOT_HEADER_TAG_CONSOLE_FLAGS  4
#define MULTIBOOT_HEADER_TAG_FRAMEBUFFER  5
#define MULTIBOOT_HEADER_TAG_MODULE_ALIGN  6
#define MULTIBOOT_HEADER_TAG_EFI_BS        7
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI32  8
#define MULTIBOOT_HEADER_TAG_ENTRY_ADDRESS_EFI64  9
#define MULTIBOOT_HEADER_TAG_RELOCATABLE  10

#define MULTIBOOT_ARCHITECTURE_I386  0
#define MULTIBOOT_ARCHITECTURE_MIPS32  4
#define MULTIBOOT_HEADER_TAG_OPTIONAL 1

#define MULTIBOOT_LOAD_PREFERENCE_NONE 0
#define MULTIBOOT_LOAD_PREFERENCE_LOW 1
#define MULTIBOOT_LOAD_PREFERENCE_HIGH 2

#define MULTIBOOT_CONSOLE_FLAGS_CONSOLE_REQUIRED 1
#define MULTIBOOT_CONSOLE_FLAGS_EGA_TEXT_SUPPORTED 2

#ifndef ASM_FILE

typedef unsigned char           multiboot_uint8_t;
typedef unsigned short          multiboot_uint16_t;
typedef unsigned int            multiboot_uint32_t;
typedef unsigned long long      multiboot_uint64_t;

struct multiboot_header
{
  /*  Must be MULTIBOOT_MAGIC - see above. */
  multiboot_uint32_t magic;

  /*  ISA */
  multiboot_uint32_t architecture;

  /*  Total header length. */
  multiboot_uint32_t header_length;

  /*  The above fields plus this one must equal 0 mod 2^32. */
  multiboot_uint32_t checksum;
};

struct multiboot_header_tag
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
};

struct multiboot_header_tag_information_request
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
  multiboot_uint32_t requests[0];
};

struct multiboot_header_tag_address
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
  multiboot_uint32_t header_addr;
  multiboot_uint32_t load_addr;
  multiboot_uint32_t load_end_addr;
  multiboot_uint32_t bss_end_addr;
};

struct multiboot_header_tag_entry_address
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
  multiboot_uint32_t entry_addr;
};

struct multiboot_header_tag_console_flags
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
  multiboot_uint32_t console_flags;
};

struct multiboot_header_tag_framebuffer
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
  multiboot_uint32_t width;
  multiboot_uint32_t height;
  multiboot_uint32_t depth;
};

struct multiboot_header_tag_module_align
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
};

struct multiboot_header_tag_relocatable
{
  multiboot_uint16_t type;
  multiboot_uint16_t flags;
  multiboot_uint32_t size;
  multiboot_uint32_t min_addr;
  multiboot_uint32_t max_addr;
  multiboot_uint32_t align;
  multiboot_uint32_t preference;
};

struct multiboot_color
{
  multiboot_uint8_t red;
  multiboot_uint8_t green;
  multiboot_uint8_t blue;
};

struct multiboot_mmap_entry
{
  multiboot_uint64_t addr;
  multiboot_uint64_t len;
#define MULTIBOOT_MEMORY_AVAILABLE              1
#define MULTIBOOT_MEMORY_RESERVED               2
#define MULTIBOOT_MEMORY_ACPI_RECLAIMABLE       3
#define MULTIBOOT_MEMORY_NVS                    4
#define MULTIBOOT_MEMORY_BADRAM                 5
  multiboot_uint32_t type;
  multiboot_uint32_t zero;
};
typedef struct multiboot_mmap_entry multiboot_memory_map_t;

struct multiboot_tag
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
};

struct multiboot_tag_string
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  char string[0];
};

struct multiboot_tag_module
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t mod_start;
  multiboot_uint32_t mod_end;
  char cmdline[0];
};

struct multiboot_tag_basic_meminfo
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t mem_lower;
  multiboot_uint32_t mem_upper;
};

struct multiboot_tag_bootdev
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t biosdev;
  multiboot_uint32_t slice;
  multiboot_uint32_t part;
};

struct multiboot_tag_mmap
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t entry_size;
  multiboot_uint32_t entry_version;
  struct multiboot_mmap_entry entries[0];  
};

struct multiboot_vbe_info_block
{
  multiboot_uint8_t external_specification[512];
};

struct multiboot_vbe_mode_info_block
{
  multiboot_uint8_t external_specification[256];
};

struct multiboot_tag_vbe
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;

  multiboot_uint16_t vbe_mode;
  multiboot_uint16_t vbe_interface_seg;
  multiboot_uint16_t vbe_interface_off;
  multiboot_uint16_t vbe_interface_len;

  struct multiboot_vbe_info_block vbe_control_info;
  struct multiboot_vbe_mode_info_block vbe_mode_info;
};

struct multiboot_tag_framebuffer_common
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;

  multiboot_uint64_t framebuffer_addr;
  multiboot_uint32_t framebuffer_pitch;
  multiboot_uint32_t framebuffer_width;
  multiboot_uint32_t framebuffer_height;
  multiboot_uint8_t framebuffer_bpp;
#define MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED 0
#define MULTIBOOT_FRAMEBUFFER_TYPE_RGB     1
#define MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT     2
  multiboot_uint8_t framebuffer_type;
  multiboot_uint16_t reserved;
};

struct multiboot_tag_framebuffer
{
  struct multiboot_tag_framebuffer_common common;

  union
  {
    struct
    {
      multiboot_uint16_t framebuffer_palette_num_colors;
      struct multiboot_color framebuffer_palette[0];
    };
    struct
    {
      multiboot_uint8_t framebuffer_red_field_position;
      multiboot_uint8_t framebuffer_red_mask_size;
      multiboot_uint8_t framebuffer_green_field_position;
      multiboot_uint8_t framebuffer_green_mask_size;
      multiboot_uint8_t framebuffer_blue_field_position;
      multiboot_uint8_t framebuffer_blue_mask_size;
    };
  };
};

struct multiboot_tag_elf_sections
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t num;
  multiboot_uint32_t entsize;
  multiboot_uint32_t shndx;
  char sections[0];
};

struct multiboot_tag_apm
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint16_t version;
  multiboot_uint16_t cseg;
  multiboot_uint32_t offset;
  multiboot_uint16_t cseg_16;
  multiboot_uint16_t dseg;
  multiboot_uint16_t flags;
  multiboot_uint16_t cseg_len;
  multiboot_uint16_t cseg_16_len;
  multiboot_uint16_t dseg_len;
};

struct multiboot_tag_efi32
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t pointer;
};

struct multiboot_tag_efi64
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint64_t pointer;
};

struct multiboot_tag_smbios
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint8_t major;
  multiboot_uint8_t minor;
  multiboot_uint8_t reserved[6];
  multiboot_uint8_t tables[0];
};

struct multiboot_tag_old_acpi
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint8_t rsdp[0];
};

struct multiboot_tag_new_acpi
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint8_t rsdp[0];
};

struct multiboot_tag_network
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint8_t dhcpack[0];
};

struct multiboot_tag_efi_mmap
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t descr_size;
  multiboot_uint32_t descr_vers;
  multiboot_uint8_t efi_mmap[0];
}; 

struct multiboot_tag_efi32_ih
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t pointer;
};

struct multiboot_tag_efi64_ih
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint64_t pointer;
};

struct multiboot_tag_load_base_addr
{
  multiboot_uint32_t type;
  multiboot_uint32_t size;
  multiboot_uint32_t load_base_addr;
};

#endif /*  ! ASM_FILE */

#endif /*  ! MULTIBOOT_HEADER */

bibliography

Multiboot2 Specification version 2.0

Writing an Operating System: GRUB to multiboot -- Docking the kernel from multiboot (reprint)

[OS Principles and Implementation] Multiboot and GRUB_multiboot2-CSDN Blog

grub2 explained (translate and organize the official manual) - Stallion Golden Dragon - blogosphere

Booting your own operating system with GRUB_Switching grub for a normal system boot - CSDN Blogs

OS Implementation - 055 multiboot2 header_beep_bilibili

MBR and GPT_mbr gap-CSDN blog


This article was posted on October 8, 2023

Last modified on October 8, 2024