1. Install development tools
The first step is to install the corresponding development tools on the Linux virtual machine, use the following command
sudo apt install build-essential sudo apt install qemu # install QEMU# as a virtual machine sudo apt install libncurses5-dev bison flex libssl-dev libelf-dev #Dependencies required to compile the kernel --- some basic libraries
2. Download the kernel source code
Install using the following command
?wget https://mirrors.edge.kernel.org/pub/linux/kernel/v5.x/linux-5.4.34.tar.xz
3. Configure kernel options
make defconfig # Default configuration is based on 'x86_64_defconfig' make menuconfig# Open debug related options
The interface after opening is shown in the figure
Then
Kernel hacking ->
Compile-time checks and compiler options ->
[] Compile the kernel with debug info
[] Provide GDB scripts for kernel debugging
[*] Kernel debugging
Processor type and features —->
[ ] Randomize the address of the kernel image (KASLR)
4. Compile the kernel and run the kernel
make -j$(nproc) # nproc gives the number of CPU cores/threads available # Test whether the kernel can be loaded and run normally, because there will be a kernel panic without a file system qemu-system-x86_64 -kernel arch/x86/boot/bzImage
Compiled
Run kernel tests
5. Create a memory root file system
When the computer is powered on, the kernel is first loaded by the bootloader, and then the kernel needs to mount the memory root file system, which contains the necessary device drivers and tools. The bootloader loads the root file system into the memory, and the kernel will mount it to the root directory/download , and then run the init script in the root file system to perform some startup tasks, and finally mount the real disk root file system. Here we use BusyBox to build a minimal memory root file system and provide basic user mode executable programs.
axel -n 20 https://busybox.net/downloads/busybox-1.31.1.tar.bz2 tar -jxvf busybox-1.31.1.tar.bz2 cd busybox-1.31.1
compile
make menuconfig
Then compile and install
make -j$(nproc) & amp; & amp; make install
Then make a memory root file system image, the general process is as follows:
mkdir rootfs cd rootfs cp ../busybox-1.31.1/_install/* ./ -rf mkdir dev proc sys home sudo cp -a /dev/{<!-- -->null,console,tty,tty1,tty2,tty3,tty4} dev/
Next, prepare the init script file and put it in the root file system and directory (rootfs/init), add the following content to the init file: (Use touch init to create the init script file, then “vim init” to open the init file, and then press i to insert mode, paste it in, then press ESC to enter the command mode, :wq to save and exit.)
#!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Wellcome YouYouOS!" echo "--------------------" cd home /bin/sh
The mount command is to mount proc and sys.
Next, add executable permissions to the script: chmod + x init
Packaged into a memory root file system image: find . -print0 | cpio –null -ov –format=newc | gzip -9 > …/rootfs.cpio.gz
Test to mount the root file system to see if the init script is executed after the kernel boot is complete:
qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd rootfs.cpio.gz
The init script executed successfully. At this point, you can directly use gdb to debug.
gdb debugging
implement:
qemu-system-x86_64 -kernel ./arch/x86/boot/bzImage -initrd rootfs.cpio.gz -S -s
Then open another terminal and execute: gdb vmlinux
Then do a gdb test trace
(gdb) target remote:1234 //This is to establish a connection through the previously reserved tcp:1234 (gdb) b start_kernel //This is the first breakpoint set c, bt, list, next, step.... //Check the gdb command for details
VSCODE debugging
First search and install these extensions in VScode
Because the starting point of the linux kernel is the “start_kernel” function, first break the point at start_kernel: click the run and debug icon, and add a function breakpoint in the breakpoint: start_kernel
Click Run – start debugging, and start trace analysis from start_kernel:
You can see that the program execution stopped at start_kernel:
Click the single point to skip, here we see that process 0 init_task is set as the first process of the whole system (process 0 is created manually, other processes are created by process 0) when the kernel boots, init_task will be created and start, which is the starting point for all other processes.
Continue to skip, start_kernel will continue to perform some initialization operations, including initializing various important data structures, drivers, interrupt handlers, etc. At this stage, the kernel will establish some necessary core data structures, such as physical memory manager, virtual memory manager, and process scheduler.
Finally came to the end of start_kernel arch_call_reset_init(), the definition of this function is to execute the reset_init() function, so we set a function breakpoint “reset_init”, continue to click the single point to skip and enter reset_init inside the function. This function is executed by process 0.
Continue to execute. At this time, we encountered kernel_init, that is, process No. 1. It is the ancestor of all user processes. It is created by the kernel_thread function. The kernel_thread function creates a new kernel thread (actually, linux does not support threads, so it is a kernel process). This thread The entry address is the kernel_init() function.
Click on the definition of the kernel_thread function.
It is found that the kernel_thread function creates a process through the _do_fork function.
The _do_fork function mainly completes calling copy_process() to copy the parent process, calling wake_up_new_task to add the child process to the ready queue to wait for scheduling execution, etc.
Next, run the kernel_init() function in the new thread (process), click on the kernel_init() function, you can see that the run_init_process function is called in this:
In this way, process No. 1 completes the user mode initialization.
Next is the creation of No. 2 process, No. 2 process is the ancestor of all kernel processes, kernel_thread creates No. 2 process, and the function executed by the process is kthreadd:
This thread keeps checking the list for kernel threads that need to be created and creates them as needed.