Limine Bare Bones: Difference between revisions

→‎Compiling the kernel on macOS: Correct make variable names
[unchecked revision][unchecked revision]
mNo edit summary
(→‎Compiling the kernel on macOS: Correct make variable names)
(31 intermediate revisions by 6 users not shown)
Line 3:
{{Template:Kernel designs}}
 
The Limine Boot Protocol is the native boot protocol provided by the [[Limine]] bootloader. Like the '''stivale''' protocols it supersedes, itIt is designed to overcome shortcomings of common boot protocols used by hobbyist OS developers, such as [[Multiboot]], and stivale itself.
 
It provides cutting edge features such as 5-level paging support, 64-bit [[Long Mode]] support, and direct higher half kernel loading.
Line 9:
The Limine boot protocol is firmware and architecture agnostic. The Limine bootloader supports x86-64, IA-32, aarch64, and riscv64.
 
This article will demonstrate how to write a small Limine-compliant x86-64 higher half Limine-compliant kernel in (GNU) [[C]], and boot it using the [[Limine]] bootloader.
 
ItAdditionally, isit alsois veryhighly recommended to check out [https://github.com/limine-bootloader/limine-barebonesc-template this template project] as it provides example buildable code to go along with this guide.
 
===Overview===
 
For this example, we will create these 2 files andto place them increate the samebasic directory tree of our project:
* kernelsrc/main.c
* linker.ld
 
As one may notice, there is no "entry point" assembly stub, as one is not necessary with the Limine protocol when using a language which can make use of a standard SysV x86 [[Calling Conventions|calling convention]].
 
Furthermore, we will download the header file '''limine.h''' which defines structures and constants that we will use to interact with the bootloader from [https://github.com/limine-bootloader/limine/raw/trunk/limine.h here], and place it in the same'''src''' directory as the other files.
 
Obviously, this is just a bare bones example, and one should always refer to the [https://github.com/limine-bootloader/limine/blob/trunk/PROTOCOL.md Limine protocol specification] for more details and information.
 
===kernelsrc/main.c===
 
This is the kernel "main".
 
<sourcesyntaxhighlight lang="c">
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <limine.h>
 
// Set the base revision to 2, this is recommended as this is the latest
// base revision described by the Limine boot protocol specification.
// See specification for further info.
 
__attribute__((used, section(".requests")))
static volatile LIMINE_BASE_REVISION(2);
 
// The Limine requests can be placed anywhere, but it is important that
// the compiler does not optimise them away, so, usually, they should
// be made volatile or equivalent., _and_ they should be accessed at least
// once or marked as used with the "used" attribute as done here.
 
__attribute__((used, section(".requests")))
static volatile struct limine_framebuffer_request framebuffer_request = {
.id = LIMINE_FRAMEBUFFER_REQUEST,
.revision = 0
};
 
// Finally, define the start and end markers for the Limine requests.
// These can also be moved anywhere, to any .c file, as seen fit.
 
__attribute__((used, section(".requests_start_marker")))
static volatile LIMINE_REQUESTS_START_MARKER;
 
__attribute__((used, section(".requests_end_marker")))
static volatile LIMINE_REQUESTS_END_MARKER;
 
// GCC and Clang reserve the right to generate calls to the following
Line 112 ⟶ 131:
// linker script accordingly.
void _start(void) {
// Ensure the bootloader actually understands our base revision (see spec).
if (LIMINE_BASE_REVISION_SUPPORTED == false) {
hcf();
}
 
// Ensure we got a framebuffer.
if (framebuffer_request.response == NULL
Line 123 ⟶ 147:
// Note: we assume the framebuffer model is RGB with 32-bit pixels.
for (size_t i = 0; i < 100; i++) {
volatile uint32_t *fb_ptr = framebuffer->address;
fb_ptr[i * (framebuffer->pitch / 4) + i] = 0xffffff;
}
Line 131 ⟶ 155:
}
 
</syntaxhighlight>
</source>
 
===linker.ld===
Line 137 ⟶ 161:
This is going to be our linker script describing where our sections will end up in memory.
 
<sourcesyntaxhighlight lang="c">
/* Tell the linker that we want an x86_64 ELF64 output file */
OUTPUT_FORMAT(elf64-x86-64)
Line 149 ⟶ 173:
PHDRS
{
text PT_LOAD FLAGS((1 << 00x05) | (1 << 2)) ; /* Execute + Read */
rodata PT_LOAD FLAGS((1 << 20x04)) ; /* Read only */
data PT_LOAD FLAGS((1 << 10x06) | (1 << 2)) ; /* Write + Read */
dynamic PT_DYNAMIC FLAGS((1 << 10x06) | (1 << 2)) ; /* Dynamic PHDR for relocations */
}
 
Line 168 ⟶ 192:
 
/* Move to the next memory page for .rodata */
. += ALIGN(CONSTANT(MAXPAGESIZE));
 
.rodata : {
Line 175 ⟶ 199:
 
/* Move to the next memory page for .data */
. += ALIGN(CONSTANT(MAXPAGESIZE));
 
.data : {
*(.data .data.*)
 
/* Place the sections that contain the Limine requests as part of the .data */
/* output section. */
KEEP(*(.requests_start_marker))
KEEP(*(.requests))
KEEP(*(.requests_end_marker))
} :data
 
Line 195 ⟶ 225:
} :data
 
/* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */
/* Also discard the program interpreter section since we do not need one. This is */
/* more or less equivalent to the --no-dynamic-linker linker flag, except that it */
/* works with ld.gold. */
/DISCARD/ : {
*(.eh_frame*)
*(.note .note.*)
*(.interp)
}
}
 
</syntaxhighlight>
</source>
 
==Building the kernel and creating an image==
Line 212 ⟶ 246:
GNU make will process it.
 
<sourcesyntaxhighlight lang="make">
# Nuke built-in rules and variables.
override MAKEFLAGS += -rR
Line 218 ⟶ 252:
# This is the name that our final kernel executable will have.
# Change as needed.
override KERNEL := myos.elf
 
# Convenience macro to reliably declare user overridable variables.
Line 230 ⟶ 264:
endef
 
# It is highly recommendedsuggested to use a custom built cross toolchain to build a kernel.
# We are only using the standard "cc" as a placeholder here., Itit may work by using
# the host system's toolchain, but this is not guaranteed.
override DEFAULT_CCDEFAULT_KCC := cc
$(eval $(call DEFAULT_VAR,CCKCC,$(DEFAULT_CCDEFAULT_KCC)))
 
# Same thing for "ld" (the linker).
override DEFAULT_LDDEFAULT_KLD := ld
$(eval $(call DEFAULT_VAR,LDKLD,$(DEFAULT_LDDEFAULT_KLD)))
 
# User controllable C flags.
override DEFAULT_CFLAGSDEFAULT_KCFLAGS := -g -O2 -pipe
$(eval $(call DEFAULT_VAR,CFLAGSKCFLAGS,$(DEFAULT_CFLAGSDEFAULT_KCFLAGS)))
 
# User controllable C preprocessor flags. We set none by default.
override DEFAULT_CPPFLAGSDEFAULT_KCPPFLAGS :=
$(eval $(call DEFAULT_VAR,CPPFLAGSKCPPFLAGS,$(DEFAULT_CPPFLAGSDEFAULT_KCPPFLAGS)))
 
# User controllable nasm flags.
override DEFAULT_NASMFLAGSDEFAULT_KNASMFLAGS := -F dwarf -g
$(eval $(call DEFAULT_VAR,NASMFLAGSKNASMFLAGS,$(DEFAULT_NASMFLAGSDEFAULT_KNASMFLAGS)))
 
# User controllable linker flags. We set none by default.
override DEFAULT_LDFLAGSDEFAULT_KLDFLAGS :=
$(eval $(call DEFAULT_VAR,LDFLAGSKLDFLAGS,$(DEFAULT_LDFLAGSDEFAULT_KLDFLAGS)))
 
# Internal C flags that should not be changed by the user.
override CFLAGSKCFLAGS += \
-Wall \
-Wextra \
Line 268 ⟶ 302:
-m64 \
-march=x86-64 \
-mabi=sysv \
-mno-80387 \
-mno-mmx \
Line 276 ⟶ 309:
 
# Internal C preprocessor flags that should not be changed by the user.
override CPPFLAGSKCPPFLAGS := \
-I. src \
$(CPPFLAGSKCPPFLAGS) \
-MMD \
-MP
 
# Internal linker flags that should not be changed by the user.
override LDFLAGSKLDFLAGS += \
-m elf_x86_64 \
-nostdlib \
-static \
-pie \
--no-dynamic-linker \
-z text \
-z max-page-size=0x1000 \
Line 294 ⟶ 325:
 
# Internal nasm flags that should not be changed by the user.
override NASMFLAGSKNASMFLAGS += \
-Wall \
-f elf64
Line 300 ⟶ 331:
# Use "find" to glob all *.c, *.S, and *.asm files in the tree and obtain the
# object and header dependency file names.
override CFILES := $(shell cd src && find -L .* -type f -name '*.c' | grep -v 'limine/')
override ASFILES := $(shell cd src && find -L .* -type f -name '*.S' | grep -v 'limine/')
override NASMFILES := $(shell cd src && find -L .* -type f -name '*.asm' | grep -v 'limine/')
override OBJ := $(addprefix obj/,$(CFILES:.c=.c.o) $(ASFILES:.S=.S.o) $(NASMFILES:.asm=.asm.o))
override HEADER_DEPS := $(addprefix obj/,$(CFILES:.c=.c.d) $(ASFILES:.S=.S.d))
 
# Default target.
.PHONY: all
all: bin/$(KERNEL)
 
# Link rules for the final kernel executable.
# The magic printf/dd command is used to force the final ELF file type to ET_DYN.
$(KERNEL): $(OBJ)
# GNU binutils, for silly reasons, forces the ELF type to ET_EXEC even for
$(LD) $(OBJ) $(LDFLAGS) -o $@
# relocatable PIEs, if the base load address is non-0.
# See https://sourceware.org/bugzilla/show_bug.cgi?id=31795 for more information.
bin/$(KERNEL): GNUmakefile linker.ld $(OBJ)
mkdir -p "$$(dirname $@)"
$(KLD) $(OBJ) $(KLDFLAGS) -o $@
printf '\003' | dd of=$@ bs=1 count=1 seek=16 conv=notrunc
 
# Include header dependencies.
Line 318 ⟶ 355:
 
# Compilation rules for *.c files.
obj/%.c.o: src/%.c GNUmakefile
mkdir -p "$$(dirname $@)"
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
$(KCC) $(KCFLAGS) $(KCPPFLAGS) -c $< -o $@
 
# Compilation rules for *.S files.
obj/%.S.o: src/%.S GNUmakefile
mkdir -p "$$(dirname $@)"
$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $@
$(KCC) $(KCFLAGS) $(KCPPFLAGS) -c $< -o $@
 
# Compilation rules for *.asm (nasm) files.
obj/%.asm.o: src/%.asm GNUmakefile
mkdir -p "$$(dirname $@)"
nasm $(NASMFLAGS) $< -o $@
nasm $(KNASMFLAGS) $< -o $@
 
# Remove object files and the final executable.
.PHONY: clean
clean:
rm -rf $(KERNEL)bin $(OBJ) $(HEADER_DEPS)obj
</syntaxhighlight>
</source>
 
===limine.cfg===
Line 339 ⟶ 379:
This file is parsed by Limine and it describes boot entries and other bootloader configuration variables. Further information [https://github.com/limine-bootloader/limine/blob/trunk/CONFIG.md here].
 
<sourcesyntaxhighlight lang="ini">
# Timeout in seconds that Limine will use before automatically booting.
TIMEOUT=5
 
# The entry name that will be displayed in the boot menu.
:myOS (KASLR on)
# We use the Limine boot protocol.
PROTOCOL=limine
 
# Disable KASLR (it is enabled by default for relocatable kernels)
KASLR=no
 
# Path to the kernel to boot. boot:/// represents the partition on which limine.cfg is located.
KERNEL_PATH=boot:///boot/myos.elf
 
# Same thing, but withoutwith KASLR.
:myOS (KASLRwith offKASLR)
PROTOCOL=limine
 
KERNEL_PATH=boot:///boot/myos
# Disable KASLR (it is enabled by default for relocatable kernels)
</syntaxhighlight>
KASLR=no
 
KERNEL_PATH=boot:///myos.elf
</source>
 
===Compiling the kernel===
 
We can now build our example kernel by running '''make'''. This command, if successful, should generate, inside the '''bin''' directory, a file called '''myos.elf''' (or the chosen kernel name). This is our Limine protocol-compliant kernel executable.
 
===Compiling the kernel on macOS===
 
''If you are not using macOS, you can skip this section.''
 
The macOS Xcode toolchain uses Mach-O binaries, and not the ELF binaries required for this Limine-compliant kernel. A solution is to build a [[GCC Cross-Compiler]], or to obtain one from [https://brew.sh homebrew] by installing the '''x86_64-elf-gcc''' package. After one of these is done, build using '''make KCC=x86_64-elf-gcc KLD=x86_64-elf-ld'''.
 
===Creating the image===
Line 377 ⟶ 423:
These are shell commands. They can also be compiled into a script or Makefile.
 
<sourcesyntaxhighlight lang="bash">
# Download the latest Limine binary release for the 7.x branch.
git clone https://github.com/limine-bootloader/limine.git --branch=v5v7.x-branch-binary --depth=1
 
# Build "limine" utility.
make -C limine
 
Line 388 ⟶ 434:
 
# Copy the relevant files over.
mkdir -p iso_root/boot
cp -v myos.elf limine.cfg limine/limine-bios.sys \
cp -v limine/limine-bios-cd.bin limine/limine-uefi-cd.binmyos iso_root/boot/
mkdir -p iso_root/boot/limine
cp -v limine.cfg limine/limine-bios.sys limine/limine-bios-cd.bin \
limine/limine-uefi-cd.bin iso_root/boot/limine/
 
# Create the EFI boot tree and copy Limine's EFI executables over.
Line 397 ⟶ 446:
 
# Create the bootable ISO.
xorriso -as mkisofs -b boot/limine/limine-bios-cd.bin \
-no-emul-boot -boot-load-size 4 -boot-info-table \
--efi-boot boot/limine/limine-uefi-cd.bin \
-efi-boot-part --efi-boot-image --protective-msdos-label \
iso_root -o image.iso
Line 405 ⟶ 454:
# Install Limine stage 1 and 2 for legacy BIOS boot.
./limine/limine bios-install image.iso
</syntaxhighlight>
</source>
 
====Creating a hard disk/USB drive image====
 
In this example, we'll create a [[GPT]] partition table using '''partedsgdisk''', containing a single FAT partition, also known as the ESP in EFI terminology, which will store our kernel, configs, and bootloader.
 
This example is more involved and is made up of more steps than creating an ISO image.
Line 415 ⟶ 464:
These are shell commands. They can also be compiled into a script or Makefile.
 
<sourcesyntaxhighlight lang="bash">
# Create an empty zeroed -out 64MiB image file.
dd if=/dev/zero bs=1M count=0 seek=64 of=image.hdd
 
# Create a GPT partition table.
parted -ssgdisk image.hdd mklabel-n 1:2048 -t gpt1:ef00
 
# Download the latest Limine binary release for the 7.x branch.
# Create an ESP partition that spans the whole disk.
git clone https://github.com/limine-bootloader/limine.git --branch=v7.x-binary --depth=1
parted -s image.hdd mkpart ESP fat32 2048s 100%
parted -s image.hdd set 1 esp on
 
# Build "limine" utility.
# Download the latest Limine binary release.
git clone https://github.com/limine-bootloader/limine.git --branch=v5.x-branch-binary --depth=1
 
# Build limine utility.
make -C limine
 
Line 435 ⟶ 480:
./limine/limine bios-install image.hdd
 
# MountFormat the loopbackimage deviceas fat32.
USED_LOOPBACK=$(sudomformat losetup -Pf --showi image.hdd)@@1M
 
# Make relevant subdirectories.
# Format the ESP partition as FAT32.
mmd -i image.hdd@@1M ::/EFI ::/EFI/BOOT ::/boot ::/boot/limine
sudo mkfs.fat -F 32 ${USED_LOOPBACK}p1
 
# Mount the partition itself.
mkdir -p img_mount
sudo mount ${USED_LOOPBACK}p1 img_mount
 
# Copy the relevant files over.
sudo mkdir -p img_mount/EFI/BOOT
sudo cp -v myos.elf limine.cfg limine/limine-bios.sys img_mount/
sudo cp -v limine/BOOTX64.EFI img_mount/EFI/BOOT/
sudo cp -v limine/BOOTIA32.EFI img_mount/EFI/BOOT/
 
# Copy over the relevant files.
# Sync system cache and unmount partition and loopback device.
mcopy -i image.hdd@@1M bin/myos ::/boot
sync
mcopy -i image.hdd@@1M limine.cfg limine/limine-bios.sys ::/boot/limine
sudo umount img_mount
mcopy -i image.hdd@@1M limine/BOOTX64.EFI ::/EFI/BOOT
sudo losetup -d ${USED_LOOPBACK}
mcopy -i image.hdd@@1M limine/BOOTIA32.EFI ::/EFI/BOOT
</source>
</syntaxhighlight>
 
==Conclusions==
Line 471 ⟶ 507:
 
* [https://github.com/limine-bootloader/limine/blob/trunk/PROTOCOL.md Limine protocol specification]
* [https://github.com/limine-bootloader/limine-barebonesc-template Buildable Limine Bareprotocol Bonesbased kernel project template in C]
 
[[Category:Bare bones tutorials]]
1

edit