Skip to content

Instantly share code, notes, and snippets.

@daniruiz
Last active January 27, 2024 15:57
Show Gist options
  • Save daniruiz/5c1998a30e0da956463e20f3c7e1ef23 to your computer and use it in GitHub Desktop.
Save daniruiz/5c1998a30e0da956463e20f3c7e1ef23 to your computer and use it in GitHub Desktop.
My Linux From Scratch notes for x86_64 EFI system

My Linux From Scratch notes for x86_64 EFI system

The goal of this guide is to provide a simplified version of the Linux From Scratch project, with steps organized in short sections that give you the satisfaction of testing each progress.

One key difference of this approach is that instead of building all the packages and tools that are part of a regular Linux OS, we start with a basic system based on the kernel and BusyBox, a simple binary that provides all the required commands.

Later we will continue adding packages and configurations until we get a system similar to the original LFS project.

Reference Links:

Disk configuration

For the guide we are going to use a disk image, but you can do this with real hardware.

Create raw disk image

30GiB example:

$ dd if=/dev/zero of=disk.img bs=1G count=30 status=progress

Create disk partitions

$ fdisk disk.img
  1. Create a new GPT partition table
  2. Add a new 512MiB EFI partition
  3. Add a new partition for the root filesystem with the remaining space
  4. Write the partition table

Format the partitions:

Take note of the name assigned to the loop device (in this case /dev/loop0), as it might vary depending on the system and already used loop names, so you might need to adjust some of the later commands.

$ losetup -P -f disk.img
$ losetup -a
  /dev/loop0: [2065]:26214403 (/mnt/1TB/LFS/disk.img)
$ lsblk /dev/loop0
NAME      MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
loop0       7:0    0   30G  0 loop 
├─loop0p1 259:4    0  512M  0 part 
└─loop0p2 259:5    0 29,5G  0 part 
$ mkfs.vfat -F32 /dev/loop0p1
$ mkfs.ext4 /dev/loop0p2

Check the new partitions:

$ fdisk -l /dev/loop0
  Disk /dev/loop0: 30 GiB, 32212254720 bytes, 62914560 sectors
  Units: sectors of 1 * 512 = 512 bytes
  Sector size (logical/physical): 512 bytes / 512 bytes
  I/O size (minimum/optimal): 512 bytes / 512 bytes
  Disklabel type: gpt
  Disk identifier: 41907C5B-56E5-A64C-A89B-688589A53CC9

  Device         Start      End  Sectors  Size Type
  /dev/loop0p1    2048  1050623  1048576  512M EFI System
  /dev/loop0p2 1050624 62914526 61863903 29.5G Linux filesystem

Mount the root partition:

$ sudo mkdir -v /mnt/lfs
$ sudo mount -v /dev/loop0p2 /mnt/lfs
$ sudo chown -v $(whoami) /mnt/lfs

Building the initial root filesystem

Creating the directory layout

$ cd /mnt/lfs
$ mkdir -pv dev etc proc run sys var usr/{bin,lib,src}
$ ln -sv usr/lib lib
$ ln -sv usr/lib lib64
$ ln -sv usr/lib usr/lib64
$ ln -sv usr/bin bin
$ ln -sv usr/bin sbin
$ ln -sv usr/bin usr/sbin

Installing shared libraries (glibc)

$ cd /mnt/lfs/usr/src
$ wget -O- http://ftp.gnu.org/gnu/libc/glibc-2.34.tar.xz | xz -dc | tar -x
$ cd glibc-2.34
$ mkdir build
$ cd build
$ ../configure --prefix=/usr --enable-kernel=4.4
$ make
$ make DESTDIR=/mnt/lfs install

Getting BusyBox

BusyBox is a software suite that provides several Unix utilities in a single executable file. It was specifically created for embedded operating systems with very limited resources, as the single executable replaces basic functions of more than 300 common commands.

This makes it perfect for this project, as it allows us to have a functional system much earlier, without needing to compile all the packages for each tool.

$ cd /mnt/lfs/usr/src
$ wget -O- https://www.busybox.net/downloads/busybox-1.34.0.tar.bz2 | bzip2 -dc | tar -x
$ cd busybox-1.34.0
$ make defconfig
$ make -j

In case you want to configure the build of BusyBox, instead of make defconfig you can use make menuconfig, which shows a menu with all the settings.

Once compiled, we can now move the generated binary to the system's /bin directory and create all the links for each command it provides.

$ cp -v busybox /mnt/lfs/bin/
$ cd /mnt/lfs/bin/
$ for t in $(./busybox --list); do
    ln -sv busybox $t
  done

Configure busybox as the init program:

$ cd /mnt/lfs/
$ ln -sv bin/busybox /mnt/lfs/init

Try it with chroot

$ sudo chroot /mnt/lfs /bin/sh

Getting the Kernel

Compile the Kernel

$ cd /mnt/lfs/usr/src
$ wget -O- https://www.kernel.org/pub/linux/kernel/v5.x/linux-5.14.8.tar.xz | xz -dc | tar -x
$ cd linux-5.14.8
$ make -j x86_64_defconfig

Same as for BusyBox, if you want to customize your kernel build you can run make menuconfig.

To make the system even simpler we won't use a boot loader, for now, so one thing we do need to configure before compiling is the kernel parameters so that it knows where to find the root partition.

$ PARTUUID=$(blkid /dev/loop0p2 -s PARTUUID -o value)
$ echo "CONFIG_CMDLINE_BOOL=y" >> .config
$ echo "CONFIG_CMDLINE=\"root=PARTUUID=$PARTUUID rw rootwait\"" >> .config
$ echo "CONFIG_CMDLINE_OVERRIDE=n" >> .config
$ make -j$(nproc)

Try the kernel with Qemu

Only running the kernel in Qemu would give us a 'Kernel Panic' error, as it wouldn't find the root filesystem. But as we already have the root partition built, we can specify it to Qemu as the main disk:

$ cd /mnt/lfs/usr/src/linux-5.14.8
$ qemu-system-x86_64 -hda /dev/loop0 -kernel arch/x86/boot/bzImage

You can also use the path to the raw disk image instead of the loop device.

Making the disk image bootable

Configure the EFI boot

Mount the EFI partition:

$ mkdir -pv /mnt/lfs/boot/efi/
$ sudo mount -v /dev/loop0p1 /mnt/lfs/boot/efi/

Even though it's pretty rare to boot the system without a boot loader, it is possible to use a kernel bzImage as an EFI executable. Now we only need to copy the bzImage kernel we previously compiled to the EFI partition.

The path \EFI\Boot\bootx64.efi is the only bootloader pathname that the UEFI firmware on 64-bit X86 systems will look for without any preexisting NVRAM boot settings, so this is what we are going to use.

$ sudo mkdir -pv /mnt/lfs/boot/efi/EFI/Boot/
$ sudo cp -v /mnt/lfs/usr/src/linux-5.14.8/arch/x86/boot/bzImage /mnt/lfs/boot/efi/EFI/linux/bootx64.efi

Try the bootable disk image

To boot Qemu in EFI mode, we need a bios file, for example OVMF, which is included with most package managers, and once installed you can find it with with locate OVMF.fd (make sure to use the 64bit file)

$ qemu-system-x86_64 -hda /dev/loop0 -bios OVMF.fd

As for the previous section where we ran the kernel with Qemu, you can use the path of the raw image disk instead of the loop device.

The number of Tuxes on display is set by the number of threads, which is cool to see 😄️ (add -smp and the threads you want)

TODO

  • 7.2. Change files ownership
  • 7.6. Creating Essential Files and Symlinks
  • Mount proc, run and sys
  • Proper configuration of glibc
  • Users
  • Sign efi binary
  • Network
  • Compiler
  • Cool stuff 🤪️
@daniruiz
Copy link
Author

I have not even checked if there are any typos, but I wanted to share my progress.
If you have any advice or tricks to make it even simpler, feel free to ping me or write a comment here!

Peek.19-09-2021.23-11.mp4

@kyb3rcipher
Copy link

Super cool 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment