diff --git a/docs/linux_kernel_development/1.empty.md b/docs/linux_kernel_development/1.empty.md index 8df13dc..e2a3adc 100644 --- a/docs/linux_kernel_development/1.empty.md +++ b/docs/linux_kernel_development/1.empty.md @@ -1,5 +1,8 @@ # Empty Module +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + The first exercise is to write an empty module. The module will print a message when it loads and a message when it is unloaded. @@ -241,6 +244,10 @@ Every time we change the module, we have to perform the following steps: We can use a `run.sh` script like the following placed in the module's folder to automate this: + + + + ```bash #!/bin/sh @@ -278,13 +285,76 @@ echo "Compressing initramfs" (cd "$INIT_RAM_FS" && find . -print0 | cpio --null -ov --format=newc | gzip -9 > "$BUILD_DIR/initramfs.cpio.gz") echo "Running QEMU" -qemu-system-x86_64 -kernel "$KDIR/arch/x86_64/boot/bzImage" --initrd build/initramfs.cpio.gz -nographic -append "console=ttyS0" -s +qemu-system-x86_64 -nographic \ + -kernel "$KDIR/arch/x86_64/boot/bzImage" \ + -initrd build/initramfs.cpio.gz \ + -append "console=ttyS0" \ + -s ``` + + + + +```bash +#!/bin/sh + +MODULE=empty.ko +BUILD_DIR="$(pwd)/build" + +set -e + +if [ -z $KDIR ]; then + echo "Kernel folder not set, use export KDIR=..." + exit 1 +fi + +if [ -z $INIT_RAM_FS ]; then + echo "initramfs folder not set, use export INIT_RAM_FS=..." + ecit 1 +fi + +echo "Building module" +make + +echo "Kernel folder $KDIR" +echo "initramfs folder $INIT_RAM_FS" + +KVERSION=$(cd "$KDIR" && make kernelversion) + +echo "Kernel version $KVERSION" + +echo "Copying driver" +MODULES_DIR="$INIT_RAM_FS/lib/modules/$KVERSION" +mkdir -p "$MODULES_DIR" +cp build/empty.ko "$MODULES_DIR" + +echo "Compressing initramfs" +(cd "$INIT_RAM_FS" && find . -print0 | cpio --null -ov --format=newc | gzip -9 > "$BUILD_DIR/initramfs.cpio.gz") + +echo "Running QEMU" +qemu-system-aarch64 -machine virt \ + -cpu cortex-a53 \ + -smp 1 \ + -m 512M \ + -nographic \ + -kernel "$KDIR/arch/arm64/boot/Image" \ + -initrd build/initramfs.cpio.gz \ + -append "earlyprintk=serial,ttyAMA0 console=ttyAMA0 debug" \ + -s +``` + + + + + :::note Make sure to export both `$KDIR` and `$INIT_RAM_FS` variables before running the script. ::: +The script will place the module in `/lib/modules//` folder so that we +can use `modprobe module_name` to load the module. + ## Exercises 1. Modify the `Module::init` function to return an `Error`. Try loading the module with different errors and see what the kernel prints. diff --git a/docs/linux_kernel_development/2.debug.mdx b/docs/linux_kernel_development/2.debug.mdx index bda69ec..28c924a 100644 --- a/docs/linux_kernel_development/2.debug.mdx +++ b/docs/linux_kernel_development/2.debug.mdx @@ -18,25 +18,50 @@ We have to run `make menuconfig` and select the following components: ```text - Kernel hacking + - Kernel Debugging + - Debug information -> Rely on the toolchain's implicit default DWARF version - Compile-time checks and compiler options - Provide GDB scripts for kernel debugging - Compile the kernel with frame pointers - - Reduice debugging infromation (DISABLE) - - Kernel Debuggng + - Reduce debugging information (DISABLE) ``` ## Run QEMU with debug server We use the `-s` parameter to enable QEMU's `gdb` server. This allows `gdb` to connect to it and debug the running kernel. + + + + +```bash +$ qemu-system-x86_64 -nographic \ + -kernel "$KDIR/arch/x86_64/boot/bzImage" \ + -initrd build/initramfs.cpio.gz \ + -append "earlyprintk=serial,ttyS0 console=ttyS0" \ + -s +``` + + + + + ```bash -$ qemu-system-x86_64 -kernel "$KDIR/arch/x86_64/boot/bzImage" \ - --initrd build/initramfs.cpio.gz \ - -nographic \ - -append "earlyprintk=serial,ttyS0 console=ttyS0" \ - -s +$ qemu-system-aarch64 -machine virt \ + -cpu cortex-a53 \ + -smp 1 \ + -m 512M \ + -nographic \ + -kernel arch/arm64/boot/Image \ + -initrd build/initramfs.cpio.gz \ + -append "earlyprintk=serial,ttyAMA0 console=ttyAMA0 debug" + -s ``` + + + + QEMU will start the `gdb` server on `127.0.0.1:1234`. @@ -99,6 +124,11 @@ scripts to `~/.config/gdb/gdbinit`. Edit the `.vscode/launch.json` file. The `program` field points to the `vmlinux` file in the kernel folder. +:::danger +Please make sure to replace `KDIR` with the actual path of the kernel. Visual Studio Code's `launch.json` +does not recognize the `$KDIR` environment variable. +::: + ```json { "version": "0.2.0", @@ -107,7 +137,7 @@ Edit the `.vscode/launch.json` file. The `program` field points to the `vmlinux` "name": "Debug Linux Kernel with GDB", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/../linux-6.18-rc4/vmlinux", + "program": "$KDIR/vmlinux", "cwd": "${workspaceFolder}", "miDebuggerPath": "/usr/bin/gdb", "MIMode": "gdb", @@ -126,13 +156,18 @@ After the debugger starts, run `-exec lx-symbols` in the console to ask `gdb` to Edit the `.zed/debug.json` file. The `program` filed points to the `vmlinux` file in the kernel folder. +:::danger +Please make sure to replace `KDIR` with the actual path of the kernel. Zed's `debug.json` +does not recognize the `$KDIR` environment variable. +::: + ```json [ { "label": "Attach GDB", "adapter": "GDB", "request": "attach", - "program": "$ZED_WORKTREE_ROOT/../linux-6.18-rc4/vmlinux", + "program": "$KDIR/linux-6.18-rc4/vmlinux", "cwd": "$ZED_WORKTREE_ROOT", "target": "127.0.0.1:1234" } diff --git a/docs/linux_kernel_development/3.reset.md b/docs/linux_kernel_development/3.poweroff/1.poweroff_x86.md similarity index 89% rename from docs/linux_kernel_development/3.reset.md rename to docs/linux_kernel_development/3.poweroff/1.poweroff_x86.md index 4aa993b..b37a41b 100644 --- a/docs/linux_kernel_development/3.reset.md +++ b/docs/linux_kernel_development/3.poweroff/1.poweroff_x86.md @@ -1,10 +1,14 @@ -# Power Off Driver +# x86_64 -We will try to build a driver that powers off QEMU. +:::info +This driver works only on x86_64. +::: + +We will build a driver that powers off QEMU. -## ISA Debug Exit +## Power Off -QEMU provides a virual peripheral called ISA Debug Exit that maps an IO port. Writing a numeric value to this port +QEMU provides a virtual peripheral called ISA Debug Exit that maps an IO port. Writing a numeric value to this port will power off QEMU and return the value as an error code. Attaching this device to QEMU is done by adding `-device isa-debug-exit,iobase=0x10f4,iosize=0x04` to the command line. @@ -49,12 +53,13 @@ unsafe { outb %al, %dx "; in("dx") port as u16, - in("al") value as u8 + in("al") value as u8, + options(noreturn) ); } ``` -#### Intel Sytnax +#### Intel Syntax ```rust // core API @@ -64,7 +69,8 @@ unsafe { out dx, al ", in("dx") port as u16, - in("al") value as u8 + in("al") value as u8, + options(intel_syntax, noreturn) ); } ``` @@ -75,7 +81,7 @@ as we might override another driver's ports. if we cannot reserve a port, we jus and fail to load the driver returning a `EBUSY` error. ::: -### Realeasing the port reagion +### Releasing the port region If we have reserved a port region, we must make sure to release it when we do not need it anymore or when we unload the driver. @@ -86,7 +92,7 @@ unsafe { bindings::release_region(start, len); } ## Exercises -1. Write an empty module called `Reset` +1. Write an empty module called `PowerOff` 2. Reserve the `0x10f4` port with 4 bytes long in the `Module::init` function. - Print the contents of `/proc/ioports` to see if your port has been reserved - Try reserving a port that already exists and fail to initialize the module diff --git a/docs/linux_kernel_development/3.poweroff/2.poweroff_arm64.md b/docs/linux_kernel_development/3.poweroff/2.poweroff_arm64.md new file mode 100644 index 0000000..31d6362 --- /dev/null +++ b/docs/linux_kernel_development/3.poweroff/2.poweroff_arm64.md @@ -0,0 +1,56 @@ +# ARM64 + +:::info +This driver works only on ARM64. +::: + +We will build a driver that powers off QEMU. + +## Power Off + +QEMU provides the `semihosting` interface and the [Power State Coordination Interface](https://developer.arm.com/documentation/den0022/latest/) (PSCI) interface. Both can be used to power off QEMU, the first one is returning a value to QEMU. + +A simple driver that we can write is one that powers off QEMU when it is loaded. + +### Using `semihosting` + +This is used for debugging ARM devices, it basically traps the `hlt` instruction with the `0xf000` argument +and uses them similar to hypervisor calls. It executes actions based on the values found in the registers. +- register `x0` stores the command `SYS_*` +- register `x1` stores a reference (pointer) to the exit reason + +We have to enable `semihosting` in QEMU by appending `-semihosting` to the command line. + +```rust +unsafe { + asm!(" + hlt #0xF000 + "; + in("x0") 0x18_u64, // SYS_EXIT + in("x1") &0x20026u64, // Reason ApplicationExit + options(noreturn) + ); +}; +``` + +### Using PCSI + +We need to use the `PSCI_SYSTEM_OFF` hypervisor call. + +We use the `hvc` instruction in inline assembly. + +```rust +// kernel API (use kernel::asm) +unsafe { + asm!( + "hvc #0"; + in("x0") 0x8400_0008_u64, // PSCI SYSTEM_OFF (SMC32 ID) + options(noreturn) + ); +}; +``` + +## Exercises + +1. Write an empty module called `PowerOff` +2. Use the `hvc` instruction with `x0` set to `0x8400_0008`. diff --git a/docs/linux_kernel_development/3.poweroff/3.bonus.md b/docs/linux_kernel_development/3.poweroff/3.bonus.md new file mode 100644 index 0000000..22e26ee --- /dev/null +++ b/docs/linux_kernel_development/3.poweroff/3.bonus.md @@ -0,0 +1,53 @@ +# Bonus + +Use a `Task` to schedule a power off after a certain amount of time defined in a module parameter. Use +a [workqueue](https://rust.docs.kernel.org/next/kernel/workqueue/index.html). + +```rust +/// Stores all the possible tasks (Work) +#[pin_data] +struct DeferredWork { + module: &'static ThisModule, + #[pin] + work: Work, +} + +/// Initializes the possible tasks +impl DeferredWork { + fn new(module: &'static ThisModule) -> Result> { + Arc::pin_init( + pin_init!(DeferredWork { + module, + work <- new_work!("DeferredWork::work"), + }), + GFP_KERNEL, + ) + } +} + +/// Must be implemented +impl_has_work! { + impl HasWork for DeferredWork { self.work } +} + +/// Implement the task +impl WorkItem for DeferredWork { + type Pointer = Arc; + + fn run(_this: Self::Pointer) { + // ... + } +} + +/// Setup the work +fn setup_work() { + match DeferredWork::new(module) { + Ok(work) => { + let _ = workqueue::system().enqueue(work); + } + Err(err) => { + pr_warn!("Failed to setup work: {:?}", err); + } + } +} +``` diff --git a/docs/linux_kernel_development/3.poweroff/index.md b/docs/linux_kernel_development/3.poweroff/index.md new file mode 100644 index 0000000..3a69886 --- /dev/null +++ b/docs/linux_kernel_development/3.poweroff/index.md @@ -0,0 +1,3 @@ +# Power Off Module + +Powering off QEMU is done differently for [x86_64](poweroff_x86) and for [ARM64](poweroff_arm64). Please select one of the platform. diff --git a/docs/linux_kernel_development/4.misc.md b/docs/linux_kernel_development/4.misc.md index 0ea057d..70a054d 100644 --- a/docs/linux_kernel_development/4.misc.md +++ b/docs/linux_kernel_development/4.misc.md @@ -1,5 +1,9 @@ # Misc Driver +:::info +This is still work in progress and will be updated. +::: + We want to control the power off driver from user space. ## Exercises diff --git a/docs/linux_kernel_development/index.mdx b/docs/linux_kernel_development/index.mdx index d986784..26bebdc 100644 --- a/docs/linux_kernel_development/index.mdx +++ b/docs/linux_kernel_development/index.mdx @@ -9,46 +9,66 @@ import TabItem from '@theme/TabItem'; ## Prerequisits -- Linux x86 64bit - - We recommend Ubuntu LTS or Fedora +- Linux x86 64bit or ARM64 + - We recommend Ubuntu 24.04 LTS or Fedora 43 - GCC Compiler - LLVM Compiler - QEMU - Rust 1.90 - Code Editor with [rust-analyzer](https://rust-analyzer.github.io/) support +### Instal Rust + +```shell +$ rustup +$ rustup components add rust-src +``` + +### Packages - + ```shell # Update the package library $ sudo apt-get # Kernel Compilation -$ sudo apt-get install -y build-essential git libncurses-dev clang flex bison lld libelf-dev +$ sudo apt-get install -y build-essential git libncurses-dev clang flex bison lld libelf-dev bindgen-cli + +# Debug +$ sudo apt-get install -y gdb -# Install QEMU -$ sudo apt-get install -y qemu-system +# Install QEMU for x86 +$ sudo apt-get install -y qemu-system-x86_64 + +# Install QEMU for ARM64 +$ $ sudo apt-get install -y qemu-system-aarch64 ``` - + ```shell # Update the package library -$ dnf update +$ sudo dnf update # Kernel Compilation -$ dnf install -y make clang ncurses-devel flex bison lld llvm elfutils-libelf-devel glibc-static +$ sudo dnf install -y make clang ncurses-devel flex bison lld llvm elfutils-libelf-devel glibc-static bindgen-cli # BusyBox Compilation -$ dnf install -y glibc-static +$ sudo dnf install -y glibc-static + +# Debug +$ sudo dnf install -y gdb + +# Install QEMU x86 +$ sudo dnf install -y qemu-system-x86_64 -# Install QEMU -$ dnf install -y qemu-system-x86_64 +#Install QEMU for ARM64 +$ sudo dnf install -y qemu-system-aarch64 ``` @@ -119,6 +139,11 @@ $ make LLVM=1 menuconfig Make sure the following components are selected: +:::warning +Please make sure you select all the module components (displayed with \< \>) using **\<*\>** and not \. We want all the modules +compiled into the kernel and as not seperate modules. +::: + ``` - 64bit Kernel (no Rust otherwise) - General setup @@ -141,17 +166,46 @@ Make sure the following components are selected: - Automount devtmpfs at /dev, after kernel mounted the rootfs - Character devices - Enable tty - - Serial drivers - - 8250/16550 and compatible serial support - - Console on 8250/16550 and compatible serial port - File systems - Pseudo filesystems - /proc file system support - - Sysctl support (/proc/sys) - - sysfs file system support + - Sysctl support (/proc/sys) <-- might not be available + - sysfs file system support <-- might not be available - Userspace-driven configuration filesystem ``` +Depending on the platform that we have, we have to select the propoer serial port. + + + + + +``` +- Device Drivers + - Character devices + - Serial drivers + - 8250/16550 and compatible serial support + - Console on 8250/16550 and compatible serial port +``` + + + + + +``` +- Device Drivers + - Character devices + - Serial drivers + - ARM AMBA PL010 serial port support + - Support for console on AMBA serial port + - ARM AMBA PL011 serial port support + - Support for console on AMBA serial port +``` + + + + + ### Build the kernel Now let's build the kernel. @@ -174,8 +228,14 @@ We will use QEMU to run a machine and boot our kernel. Instead of using a bootlo provides a minimal bootloader that can load a `multiboot v1` compatible kernel that is supplied using the `-kernel` argument. The Linux kernel is compatible. + + + + ```bash -$ qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0 debug" +$ qemu-system-x86_64 -nographic \ + -kernel arch/x86_64/boot/bzImage \ + -append "earlyprintk=serial,ttyS0 console=ttyS0 debug" ``` Running QEMU should print an output similar to: @@ -308,10 +368,135 @@ Kernel Offset: disabled ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on "" or unknown-block(0,0) ]--- ``` + + + + +```bash +$ qemu-system-aarch64 -machine virt \ + -cpu cortex-a53 \ + -smp 1 \ + -m 512M \ + -nographic \ + -kernel arch/arm64/boot/Image \ + -append "earlyprintk=serial,ttyAMA0 console=ttyAMA0 debug" +``` + +We use a virtual Cortex-A53 with one core and 512 MB of RAM. + +Running QEMU should print an output similar to: + +```text +Booting Linux on physical CPU 0x0000000000 [0x410fd034] +Linux version 6.18.0-rc5 (alexandru@localhost.localdomain) (clang version 21.1.5 (Fedora 21.1.5-1.fc43), LLD 21.1.5) #1 SMP Sun Nov 16 12:39:05 EET 2025 +random: crng init done +Machine model: linux,dummy-virt +OF: reserved mem: Reserved memory: No reserved-memory node in the DT +Zone ranges: + DMA [mem 0x0000000040000000-0x000000005fffffff] + DMA32 empty + Normal empty +Movable zone start for each node +Early memory node ranges + node 0: [mem 0x0000000040000000-0x000000005fffffff] +Initmem setup node 0 [mem 0x0000000040000000-0x000000005fffffff] +psci: probing for conduit method from DT. +psci: PSCIv1.1 detected in firmware. +psci: Using standard PSCI v0.2 function IDs +psci: Trusted OS migration not required +psci: SMC Calling Convention v1.0 +percpu: Embedded 15 pages/cpu s24480 r8192 d28768 u61440 +Detected VIPT I-cache on CPU0 +alternatives: applying boot alternatives +Kernel command line: +printk: log buffer data + meta data: 131072 + 458752 = 589824 bytes +Dentry cache hash table entries: 65536 (order: 7, 524288 bytes, linear) +Inode-cache hash table entries: 32768 (order: 6, 262144 bytes, linear) +software IO TLB: SWIOTLB bounce buffer size adjusted to 0MB +software IO TLB: area num 1. +software IO TLB: mapped [mem 0x000000005f580000-0x000000005f600000] (0MB) +Built 1 zonelists, mobility grouping on. Total pages: 131072 +mem auto-init: stack:all(zero), heap alloc:off, heap free:off +SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=1, Nodes=1 +rcu: Hierarchical RCU implementation. +rcu: RCU restricting CPUs from NR_CPUS=512 to nr_cpu_ids=1. +rcu: RCU calculated value of scheduler-enlistment delay is 25 jiffies. +rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=1 +NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0 +Root IRQ handler: gic_handle_irq +rcu: srcu_init: Setting srcu_struct sizes based on contention. +arch_timer: cp15 timer running at 62.50MHz (virt). +clocksource: arch_sys_counter: mask: 0x1ffffffffffffff max_cycles: 0x1cd42e208c, max_idle_ns: 881590405314 ns +sched_clock: 57 bits at 63MHz, resolution 16ns, wraps every 4398046511096ns +Console: colour dummy device 80x25 +printk: legacy console [tty0] enabled +Calibrating delay loop (skipped), value calculated using timer frequency.. 125.00 BogoMIPS (lpj=250000) +pid_max: default: 32768 minimum: 301 +Mount-cache hash table entries: 1024 (order: 1, 8192 bytes, linear) +Mountpoint-cache hash table entries: 1024 (order: 1, 8192 bytes, linear) +cacheinfo: Unable to detect cache hierarchy for CPU 0 +rcu: Hierarchical SRCU implementation. +rcu: Max phase no-delay instances is 1000. +smp: Bringing up secondary CPUs ... +smp: Brought up 1 node, 1 CPU +SMP: Total of 1 processors activated. +CPU: All CPU(s) started at EL1 +CPU features: detected: 32-bit EL0 Support +CPU features: detected: CRC32 instructions +alternatives: applying system-wide alternatives +Memory: 504944K/524288K available (3008K kernel code, 658K rwdata, 1140K rodata, 576K init, 373K bss, 17664K reserved, 0K cma-reserved) +devtmpfs: initialized +clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 7645041785100000 ns +posixtimers hash table entries: 512 (order: 1, 8192 bytes, linear) +futex hash table entries: 256 (16384 bytes on 1 NUMA nodes, total 16 KiB, linear). +31280 pages in range for non-PLT usage +522800 pages in range for PLT usage +DMA: preallocated 128 KiB GFP_KERNEL pool for atomic allocations +DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA pool for atomic allocations +DMA: preallocated 128 KiB GFP_KERNEL|GFP_DMA32 pool for atomic allocations +ASID allocator initialised with 65536 entries +Serial: AMBA PL011 UART driver +9000000.pl011: ttyAMA0 at MMIO 0x9000000 (irq = 13, base_baud = 0) is a PL011 rev1 +printk: console [ttyAMA0] enabled +clocksource: Switched to clocksource arch_sys_counter +workingset: timestamp_bits=62 max_order=17 bucket_order=0 +Serial: AMBA driver +clk: Disabling unused clocks +check access for rdinit=/init failed: -2, ignoring +List of all partitions: +No filesystem could mount root, tried: + +Kernel panic - not syncing: VFS: Unable to mount root fs on "" or unknown-block(0,0) +CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 6.18.0-rc5 #1 NONE +Hardware name: linux,dummy-virt (DT) +Call trace: + show_stack+0x14/0x1c (C) + __dump_stack+0x24/0x30 + dump_stack_lvl+0x20/0x64 + dump_stack+0x14/0x1c + vpanic+0x120/0x2e0 + vpanic+0x0/0x2e0 + mount_root_generic+0x194/0x2cc + mount_block_root+0x4c/0x58 + mount_root+0x70/0x7c + prepare_namespace+0x7c/0xb8 + kernel_init_freeable+0xc0/0xdc + kernel_init+0x1c/0x11c + ret_from_fork+0x10/0x20 +Kernel Offset: disabled +CPU features: 0x000000,00000000,00000000,0400400b +Memory Limit: none +---[ end Kernel panic - not syncing: VFS: Unable to mount root fs on "" or unknown-block(0,0) ]--- +``` + + + + + **The kernel panicked!** :::tip -Press `CTRL`+`a` folloed by `x` to exit QEMU. +Press `CTRL`+`a` followed by `x` to exit QEMU. ::: This is normal, let's take a look at why it panicked. @@ -351,12 +536,38 @@ $ find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.g ### Boot the kernel with the RAM disk -We have to add the `--initrd` argument to QEMU. +We have to add the `-initrd` argument to QEMU. + + + + + +```bash +$ qemu-system-x86_64 -nographic \ + -kernel arch/x86_64/boot/bzImage \ + -append "earlyprintk=serial,ttyS0 \ console=ttyS0 debug" \ + -initrd initramfs.cpio.gz +``` + + + + ```bash -$ qemu-system-x86_64 -kernel arch/x86_64/boot/bzImage -nographic -append "earlyprintk=serial,ttyS0 console=ttyS0 debug" --initrd initramfs.cpio.gz +$ qemu-system-aarch64 -machine virt \ + -cpu cortex-a53 \ + -smp 1 \ + -m 512M \ + -nographic \ + -kernel arch/arm64/boot/Image \ + -append "earlyprintk=serial,ttyAMA0 console=ttyAMA0 debug" \ + -initrd initramfs.cpio.gz ``` + + + + **The kernel boots, but it seems to show use the same panic!** This is strange, as we have supplied a root file system. The hint is the following line: @@ -443,6 +654,10 @@ Kernel panic - not syncing: No working init found. Try passing init= option to As we can see, the kernel found the `init` executable, tried to run and failed with `Failed to execute /init (error -2)`. We compiled the `init` executable for Linux, which means it requires Linux libraries. Running `ldd` on the `init` executable will write: + + + + ```bash $ ldd target/debug/init linux-vdso.so.1 (0x00007f45a855e000) @@ -469,8 +684,8 @@ $ rustup target add x86_64-unknown-linux-musl ``` ::: -The static binary will be placved in `target/x86_64-unknown-linux-musl/debug/init`. Running `ldd` in this file will print `statically linked` -and this is what we axctually want. We can now copy our `init` executable to `$INIT_RAM_FS` and [rebuild it](#build-ram-disk). +The static binary will be placated in `target/x86_64-unknown-linux-musl/debug/init`. Running `ldd` in this file will print `statically linked` +and this is what we actually want. We can now copy our `init` executable to `$INIT_RAM_FS` and [rebuild it](#build-ram-disk). :::tip @@ -484,12 +699,71 @@ target = "x86_64-unknown-linux-musl" You instruct cargo to install all the target and components that you need before the build using the `rust-toolchain.toml` file. ```toml -# toolchain +channel = "1.91.1" +components = [ "rustfmt", "clippy", "rust-analyzer", "llvm-tools", "rust-src" ] +targets = [ "x86_64-unknown-linux-musl"] +``` + +::: + + + + + +```bash +$ ldd target/debug/init + linux-vdso.so.1 (0x0000ffffadf7c000) + libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x0000ffffade90000) + libc.so.6 => /lib64/libc.so.6 (0x0000ffffadcc0000) + /lib/ld-linux-aarch64.so.1 (0x0000ffffadf40000) ``` +This means that our `init` depends on these libraries. As we have an empty file system, the executable cannot be loaded. We have +to build `init` as a **static executable**. + +Rust provides the `aarch64-unknown-linux-musl` target for building static x86 64 bit Linux executables. We have to ask `cargo` to +use this target. + +```shell +$ cargo build --target aarch64-unknown-linux-musl +``` + +:::note +If the build fails, you might have to install the `aarch64-unknown-linux-musl` target using: + +```shell +$ rustup target add aarch64-unknown-linux-musl +``` ::: -Rumnning the kernel still panics 🤨, but with a different error: +The static binary will be placated in `target/aarch64-unknown-linux-musl/debug/init`. Running `ldd` in this file will print `statically linked` +and this is what we actually want. We can now copy our `init` executable to `$INIT_RAM_FS` and [rebuild it](#build-ram-disk). + +:::tip + +To avoid using the `--target` argument with cargo at every build, we can specify the target a `.cargo/config.toml` file. + +```toml +[build] +target = "aarch64-unknown-linux-musl" +``` + +You instruct cargo to install all the target and components that you need before the build using the `rust-toolchain.toml` file. + +```toml +[toolchain] +channel = "1.91.1" +components = [ "rustfmt", "clippy", "rust-analyzer", "llvm-tools", "rust-src" ] +targets = [ "aarch64-unknown-linux-musl"] +``` + +::: + + + + + +Ruminating the kernel still panics 🤨, but with a different error: ```text Run /init as init process @@ -549,29 +823,44 @@ $ make clean $ make menuconfig ``` -:::warning +:::danger BusyBox's build script has a bug when it checks for `ncurses-devel`. Instead of checking if `lx-dialog` exists, it check for a compilation error. If you get this error, you have to patch the `scripts/kconfig/lxdialog/check-lxdialog.sh` file to make sure it writes `int main` in the `check` function. -```sh -# Check if we can link to ncurses -check() { - $cc -x c - -o $tmp 2>/dev/null <<'EOF' -#include CURSES_LOC -int main() {} -EOF - if [ $? != 0 ]; then - echo " *** Unable to find the ncurses libraries or the" 1>&2 - echo " *** required header files." 1>&2 - echo " *** 'make menuconfig' requires the ncurses libraries." 1>&2 - echo " *** " 1>&2 - echo " *** Install ncurses (ncurses-devel) and try again." 1>&2 - echo " *** " 1>&2 - exit 1 - fi -} +```diff showLineNumbers=46 + # Check if we can link to ncurses + check() { + $cc -x c - -o $tmp 2>/dev/null <<'EOF' + #include CURSES_LOC +-main() {} ++int main() {} + EOF + if [ $? != 0 ]; then + echo " *** Unable to find the ncurses libraries or the" 1>&2 + echo " *** required header files." 1>&2 + echo " *** 'make menuconfig' requires the ncurses libraries." 1>&2 + echo " *** " 1>&2 + echo " *** Install ncurses (ncurses-devel) and try again." 1>&2 + echo " *** " 1>&2 + exit 1 + fi +``` +::: +:::danger +BusyBox has a bug on ARM64, we have to patch the `libbb/hasn_md5_sha.c` file at line 1316 + +```diff showLineNumbers=1313 + hash_size = 8; + if (ctx->process_block == sha1_process_block64 + #if ENABLE_SHA1_HWACCEL ++# if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + || ctx->process_block == sha1_process_block64_shaNI ++# endif + #endif + ) { + hash_size = 5; ``` ::: @@ -649,15 +938,6 @@ $ mkdir -p bin sbin etc proc dev sys usr/bin usr/sbin We only really need to copy the `_install/bin/busybox` executable to `$INIT_RAM_FS`. -:::note -We will instruct BusyBox to install all the links at boot time. If you want to avoid installing -them at boot, you can copy all the links using - -```shell -$ cp -r _install/* $INIT_RAM_FS/ -``` -::: - ### The `init` script BusyBox provides a shell interpreter which means we can now use write and `init` shell script. @@ -709,3 +989,13 @@ initramfs We have to [rebuild the RAM disk](#build-ram-disk) and boot the kernel with the new RAM disk. We should have access to a full shell now. + +:::note +We instructed BusyBox to install all the links at boot time using the `/bin/busybox --install -s` command in the `init` script. +BusyBox's `make install` command generates in `_install` all the folders and links that `busybox --install -s` does at +runtime. To avoid the runtime generation, we can copy all the files from `_install` to `$INIT_RAM_FS`. + +```shell +$ cp -r _install/* $INIT_RAM_FS/ +``` +::: diff --git a/docusaurus.config.ts b/docusaurus.config.ts index a290c95..bbedea3 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -139,7 +139,7 @@ const config: Config = { prism: { theme: prismThemes.github, darkTheme: prismThemes.dracula, - additionalLanguages: ["bash", "toml", "makefile"], + additionalLanguages: ["bash", "toml", "makefile", "diff"], }, } satisfies Preset.ThemeConfig, }; diff --git a/package-lock.json b/package-lock.json index 0781631..284e50a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8565,10 +8565,9 @@ } }, "node_modules/gray-matter/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "license": "MIT", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -9752,10 +9751,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dependencies": { "argparse": "^2.0.1" },