这里假定读者已经拥有一个宿主 GNU/Linux 发行版作为宿主系统, 具体细节不再这里赘述了, 读者可以参考下面的一些内容来配置基础 linux 环境
您可以前往VMware官网下载其最新版
license key:
MC60H-DWHD5-H80U9-6V85M-8280D
2024.6 更新: VMware 免费了!
创建虚拟机的过程比较简单这里就不赘述了, 磁盘空间给大一些40GB, 编译内核大概就需要 20GB 的中间文件, 交换区需要调整一下给8GB
这台虚拟机只是用于辅助编译的, 并不需要特别复杂的配置, 我的个人习惯是使用Vscode+SSH连接到这台虚拟机,在Windows中工作. 这部分的配置相对来说比较繁琐, 做不做都可以, 笔者这里贴出个人的一些笔记以供参考
接下来就可以正式进入编译阶段了!
The Linux Kernel Archives 提供了所有Linux内核的历史版本,你可以使用git下载一个巨大的仓库,但笔者建议使用HTTP协议选择一个Linux版本使用即可
以6.6版本Linux内核源代码为例, 可以在 Linux Kernel 找到v6.x版本, 搜索6.6.gz
即可
本文以6.6为例, 您可以使用wget下载
wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.gz
tar xf linux-6.6.tar.gz
cd linux-6.6
使用 v6.6 版本的内核有如下的几个原因
配合编译需要的软件程序很多, 而且特定版本的内核也可能需要特定版本的软件, 新的编译器版本也可能会带来一系列问题
另外安装软件的时候也很可能会出现依赖冲突等等问题, 内核编译的时候也会因为各种配置带来各种问题的报错, 解决起来相当之麻烦, 尤其是老版本内核...
如果实在解决不了, 建议换 Ubuntu 的版本, 换源, 换 Linux 版本
下面的过程在 wsl2(ubuntu22.04) 和 linux6.6 上是没有问题的, 大概
sudo apt update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison vim
make mrproper
该命令确保内核源代码树绝对干净,内核开发组建议在每次编译内核前运行该命令.尽管内核源代码树在解压后应该是干净的,但这并不完全可靠
make defconfig
make defconfig
命令根据当前平台的默认配置生成一个内核配置文件(通常是 .config 文件).这个默认配置文件包含了内核开发者认为适用于大多数用户的默认选项. 每种架构(例如 x86、arm 等)都有其特定的默认配置文件.这些默认配置文件为不同的硬件平台提供了一个合理的起点,使得用户在编译内核时不需要从头开始配置所有选项. 对于希望快速编译内核的用户,make defconfig 提供了一个简便的方法,可以立即生成一个基本的配置文件,而不需要手动配置每一个内核选项.这对新手尤其有用,因为手动配置内核选项可能会非常繁琐和复杂.
使用 make defconfig
生成默认配置文件后,用户可以进一步使用 make menuconfig
、make xconfig
或 make nconfig
等命令对配置文件进行定制,以满足特定的需求.
make menuconfig
这里会打开一个图形化的界面方便控制选择配置, 需要做一些修改以便后面的调试
menuconfig 中有一个比较方便的搜索功能, 输入 /
进入一个搜索页面, 可以在里面搜索关键字找到对应的配置位置
例如搜索 GDB 可以得到如下的一些结果, 根据结果中 Location 的信息找到对应的配置位置, 再进行修改. 这里按下 4 就可以直接跳转到这个选项所在的位置来进行修改
或者对于一些配置选项比如 CONFIG_VIRTIO_PCI
可以直接搜索找到相关的选项并打开, 不建议直接改动 .config
文件因为可能 CONFIG 选项之间有关联关系, 最好在 menuconfig 中修改
Kernel hacking --->
Compile-time checks and compiler options --->
[*] Compile the kernel with debug info
[*] Provide GDB scripts for kernel debugging
[*] Kernel debugging
这里的 debug info 在 Linux 6.6 中默认应该是没有开启的, 选第二个
然后再退出来就可以看到这个 Provide GDB scripts for kernel debugging
选项了, 使用空格选中
Processor type and features
[*] Build a relocatable kernel
[ ] Randomize the address of the kernel image
找到对应位置取消勾选, 如果不禁用随机地址 gdb 调试时没有办法准确定位
MEMCG
)General setup --->
[*] Control Group support ---> [CGROUPS]
[*] Memory controller [MEMCG]
如果后期修改了内核编译出来的内核版本号带有dirty后缀, 这是编译内核的时候自动添加的. 可以通过如下选项关闭
General setup --->
[ ] Automatically append version information to the version string
修改完成之后选择 save
保存退出即可, 得到 .config
配置文件
make -j`nproc`
默认编译的内核同宿主机处理器架构, 如果要编译一个其他架构的内核(比如说 i386), 可以使用
ARCH
指定make -j`nproc` ARCH=i386
编译时间不确定, 短则几分钟, 长则十几几十分钟, 取决于你的配置和开的进程数, 笔者 16 核基本几分钟就可以编译完成
可以得到 vmlinux
和 arch/x86/boot/bzImage
vmlinux和bzImage都是Linux内核编译生成的可执行文件,它们的主要区别在于它们的文件格式和用途.
- vmlinux 是Linux内核编译生成的未压缩的内核镜像文件,它包含了整个内核的代码和数据,可以用来调试内核.它通常比较大,因为它没有> 被压缩.
- bzImage 是Linux内核编译生成的压缩的内核镜像文件,它是用来引导启动Linux操作系统的.它是通过将vmlinux压缩成一个单独的文件,并添加一些引导代码和头部信息来生成的.
当系统引导时,bzImage首先会被加载到内存中,然后被解压缩成vmlinux形式的内核映像.因为vmlinux文件比较大,而且内核启动过程中需要加载和解压缩文件,所以bzImage文件通常比vmlinux文件小很多.
简而言之, vmlinux主要用于内核开发和调试,而bzImage用于实际的Linux操作系统启动.
如果编译出错了那么可以执行如下命令清除重试
sudo make mrproper
sudo make distclean
可以选择安装内核和所有的模块, 这一步不是必须的
sudo make modules_install
sudo make install
install 不会替换掉原有的内核, 只会将内核以及符号表等放到
/boot
目录下, 并更新/boot/grub/grub.cfg
sudo update-grub
然后可以使用新内核启动: VMware虚拟安装Ubuntu,然后切换Ubuntu内核版本
或者额外新建两个目录, 分别用于保存安装内核所需的文件和所有的模块
mkdir install
mkdir modules
make install INSTALL_PATH=install/
make modules_install INSTALL_MOD_PATH=modules/
其中 install/ 下保存着内核文件 vmlinuz-6.6.0
, 系统符号表 System.map-6.6.0
(并非 Linux 启动所必须的) 和编译内核的配置文件 config-6.6.0
modules/ 下保存着所有的模块, 它们可以在内核启动之后通过 insmod 动态的加载到内核中
如果要使用新的内核只需要重启电脑然后进入 GRUB 中选择新的内核版本即可
如果使用真机的话长按 F12, 如果是 Vmware 的话长按 shift 进入 grub, 对于 Ubuntu 来说选择第二个
Advanced options for Ubuntu
按照上述说明编译后的内核可能会很大, 笔者编译的 linux6.6 已经有 300+ MB 的 vmlinux 和 12MB 的 bzImage 了. 但大部分情况下我们并不需要 kernel 全部的功能, 因此可以在基础之上做一些内核的裁剪
这一步并不是必要的, 视个人情况而定, 很难讲怎么样是最好的
内核配置信息比较复杂, 笔者这里按经验总结了一些 linux6.6 内核配置文件, 适用于 linux6.6 版本
读者可以根据需要直接下载对应的文件, 即跳过前面的 make menuconfig 的部分, 直接使用现成的 .config 配置文件. 可以关闭一些诸如 文件系统支持, 设备驱动, 无线网络支持, USB 支持, 图形支持, 声音等. 具体见 realease 中的信息
如果希望给你的内核起一个名字, 可以修改 CONFIG_LOCALVERSION, 该内容会加在内核版本之后
CONFIG_LOCALVERSION=""
大部分编译的内核由于没有驱动等支持, 所以只能在虚拟机上启动, 没有办法在真机启动 ubuntu. 如果希望在真机(ubuntu)启动可以下载提供 linux6.6 内核配置文件 中的 ubuntu.config 并打包为 deb
打包完成后会在上级目录生成一些文件, 启动 *.deb 文件是我们需要的, 安装 headers 和 image
sudo dpkg -i linux-headers-6.6.0+_6.6.0-ga472b7d4a578-12_amd64.deb
sudo dpkg -i linux-image-6.6.0+_6.6.0-ga472b7d4a578-12_amd64.deb
此时会将 vmlinuz initrd.img config 等安装到 /boot 下, 可以使用如下 switch_kernel.sh 脚本替换内核, 可以输入需要选择内核, 此脚本将会自动修改 grub 配置并将该内核设为默认启动项
#!/bin/bash
# Check if the script is run as root
if [ "$(id -u)" -ne "0" ]; then
echo "This script must be run as root" 1>&2
exit 1
fi
# Define the directory where the kernel images are stored
KERNEL_DIR="/boot"
# List available kernel versions and assign a number to each
echo "Available kernel versions:"
kernels=($(ls ${KERNEL_DIR}/vmlinuz-*))
count=0
for kernel in "${kernels[@]}"; do
kernel_version=$(echo $kernel | sed 's/.*\/vmlinuz-//')
echo "[$count]: $kernel_version"
((count++))
done
echo ""
# Prompt the user to select a kernel version by number
read -p "Enter the number of the kernel version you want to switch to: " kernel_number
# Check if the input is a number and within the range
if ! [[ "$kernel_number" =~ ^[0-9]+$ ]] || [ "$kernel_number" -lt "0" ] || [ "$kernel_number" -ge "$count" ]; then
echo "Invalid selection"
exit 1
fi
# Get the kernel version based on the number
kernel_version=$(echo ${kernels[$kernel_number]} | sed 's/.*\/vmlinuz-//')
echo "Switching to kernel version: $kernel_version"
# Check if the selected kernel version exists
if [ ! -e "$KERNEL_DIR/vmlinuz-$kernel_version" ]; then
echo "Kernel version $kernel_version does not exist"
exit 1
fi
# Extract the menu entry for the default kernel
MID=`awk '/Advanced options.*/{print $(NF-1)}' /boot/grub/grub.cfg`
MID="${MID//\'/}"
KID=`awk -v kern="with Linux $kernel_version" '$0 ~ kern && !/recovery/ { print $(NF - 1) }' /boot/grub/grub.cfg`
KID="${KID//\'/}"
# Update GRUB configuration
sed -i "s/GRUB_DEFAULT=.*/GRUB_DEFAULT=\"$MID>$KID\"/" /etc/default/grub
update-grub
echo -e "\e[31mPlease reboot machine\e[0m"
当然, 编译过程并不都是一帆风顺的, 遇到错误在所难免. 下面是笔者遇到的一些错误和解决措施, 读者可以修改后重新执行上述 make 编译
.config 中的 CONFIG_SYSTEM_TRUSTED_KEY
改为 ""
或者直接执行如下命令, 效果相同
scripts/config --disable SYSTEM_TRUSTED_KEYS
不要在 .bashrc 中改 CPATH, 可能是因为与其他软件(比如QEMU)源代码中的符号冲突了
BTF: .tmp_vmlinux.btf: pahole version v1.15 is too old, need at least v1.16
Failed to generate BTF for vmlinux
Try to disable CONFIG_DEBUG_INFO_BTF
make: *** [Makefile:1199: vmlinux] Error 1
在 .config 把 CONFIG_DEBUG_INFO_BTF
注释掉
./include/linux/compiler.h:350:45: error: call to '__compiletime_assert_653' declared with attribute error: BUILD_BUG_ON failed: (((0x0ffULL) + (1ULL << (__builtin_ffsll(0x0ffULL) - 1))) & (((0x0ffULL) + (1ULL << (__builtin_ffsll(0x0ffULL) - 1))) - 1)) != 0
350 | _compiletime_assert(condition, msg, __compiletime_assert_, __LINE__)
linux kernel build compliler assert error
高版本 gcc 编译有 bug, 使用 gcc-9
sudo apt install gcc-9
切换 gcc 版本, 假设当前系统中有 gcc-11 和 gcc-9
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 80
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 50
手动切换 gcc 版本, 重新编译
sudo update-alternatives --config gcc
这个问题通常是因为使用较新的软件包编译旧版本的内核, 例如编译 5.8.5 内核. 但是系统中的 binutils 2.36太新,无法编译
reddit compiling_older_versions_of_the_kernel_using
可以应用此 Patch
From 1d489151e9f9d1647110277ff77282fe4d96d09b Mon Sep 17 00:00:00 2001
From: Josh Poimboeuf <jpoimboe@redhat.com>
Date: Thu, 14 Jan 2021 16:14:01 -0600
Subject: [PATCH] objtool: Don't fail on missing symbol table
Thanks to a recent binutils change which doesn't generate unused
symbols, it's now possible for thunk_64.o be completely empty without
CONFIG_PREEMPTION: no text, no data, no symbols.
We could edit the Makefile to only build that file when
CONFIG_PREEMPTION is enabled, but that will likely create confusion
if/when the thunks end up getting used by some other code again.
Just ignore it and move on.
Reported-by: Nathan Chancellor <natechancellor@gmail.com>
Reviewed-by: Nathan Chancellor <natechancellor@gmail.com>
Reviewed-by: Miroslav Benes <mbenes@suse.cz>
Tested-by: Nathan Chancellor <natechancellor@gmail.com>
Link: https://github.com/ClangBuiltLinux/linux/issues/1254
Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com>
---
tools/objtool/elf.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c
index f9682db33ccabb..d8421e1d06bed3 100644
--- a/tools/objtool/elf.c
+++ b/tools/objtool/elf.c
@@ -380,8 +380,11 @@ static int read_symbols(struct elf *elf)
symtab = find_section_by_name(elf, ".symtab");
if (!symtab) {
- WARN("missing symbol table");
- return -1;
+ /*
+ * A missing symbol table is actually possible if it's an empty
+ * .o file. This can happen for thunk_64.o.
+ */
+ return 0;
}
symtab_shndx = find_section_by_name(elf, ".symtab_shndx");
patch -p1 < a.patch