Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save haruue/9dff23f9b7a80ec7bdc0d69feda0dff2 to your computer and use it in GitHub Desktop.
Save haruue/9dff23f9b7a80ec7bdc0d69feda0dff2 to your computer and use it in GitHub Desktop.
使用 Nix 解决 Debian 11 上无法给 6.x 内核安装 DKMS 模块的问题

使用 Nix 解决 Debian 11 上无法给 6.x 内核安装 DKMS 模块的问题

解决方案

前提条件

服务器的网络至少要能顺畅地访问 Nix Cache(https://cache.nixos.org), 大部分用作代理的(中国境外的)服务器通常都没有问题。

步骤

  1. 如果你正在安装 tcp-brutal, 请确保先跑一遍 tcp-brutal 安装脚本(即使会失败)。
    否则, 请先手动安装好对应的 linux-headers。
  2. 给你的服务器装上 Nix。
    如果你用 Debian 11 或者更新的 Debian, 可以直接 apt install nix
    如果你的软件仓库没有 Nix, 可以参考 https://nixos.org/download/ 里的操作执行一键脚本。
    只需要安装好 Nix 即可, 我们的目标并不是将你的系统变成 NixOS。
  3. 下载这个 gist 里的 shell.nix 到你的服务器上。
  4. shell.nix 同一目录执行 nix-shell --run fix-kernel-headers, 等它跑完。
  5. 如果你是在安装 tcp-brutal, 请重新跑一遍安装脚本以编译和载入模块。
    否则, 请参考你正在安装的模块的文档继续, 通常情况下执行 dkms autoinstall 就可以重新编译模块。

如果你只是想装好内核模块, 现在你就可以关掉这个网页了。

发生什么事了?

问题的起源是我们收到了数个 tcp-brutal 用户的 Issue (#7#18#21), 这些用户在 Debian 11 或者 Ubuntu 20.04 这种几年前的系统上使用 6.x 的 xanmod 内核, 并且在尝试安装 tcp-brutal 时失败。

Building module:
cleaning build area...
make -j1 KERNELRELEASE=6.9.10-x64v3-xanmod1 KERNEL_DIR=/lib/modules/6.9.10-x64v3-xanmod1/build all...(bad exit status: 2)
Error! Bad return status for module build on kernel: 6.9.10-x64v3-xanmod1 (x86_64)
Consult /var/lib/dkms/tcp-brutal/1.0.2/build/make.log for more information.

检查 /var/lib/dkms/tcp-brutal/1.0.2/build/make.log, 可以看到如下输出:

# cat /var/lib/dkms/tcp-brutal/1.0.2/build/make.log
DKMS make.log for tcp-brutal-1.0.2 for kernel 6.9.10-x64v3-xanmod1 (x86_64)
Thu 25 Jul 2024 04:44:24 PM UTC
make -C /lib/modules/6.9.10-x64v3-xanmod1/build M=/var/lib/dkms/tcp-brutal/1.0.2/build modules
make[1]: Entering directory '/usr/src/linux-headers-6.9.10-x64v3-xanmod1'
warning: the compiler differs from the one used to build the kernel
  The kernel was built by: gcc-13 (Debian 13.3.0-1) 13.3.0
  You are using:           gcc (Debian 10.2.1-6) 10.2.1 20210110
  CC [M]  /var/lib/dkms/tcp-brutal/1.0.2/build/brutal.o
/var/lib/dkms/tcp-brutal/1.0.2/build/brutal.c:38:5: warning: no previous prototype for ‘tcp_sock_get_sec’ [-Wmissing-prototypes]
   38 | u64 tcp_sock_get_sec(const struct tcp_sock *tp)
      |     ^~~~~~~~~~~~~~~~
./tools/objtool/objtool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by ./tools/objtool/objtool)
./tools/objtool/objtool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./tools/objtool/objtool)
make[3]: *** [scripts/Makefile.build:244: /var/lib/dkms/tcp-brutal/1.0.2/build/brutal.o] Error 1
make[3]: *** Deleting file '/var/lib/dkms/tcp-brutal/1.0.2/build/brutal.o'
make[2]: *** [/usr/src/linux-headers-6.9.10-x64v3-xanmod1/Makefile:2070: /var/lib/dkms/tcp-brutal/1.0.2/build] Error 2
make[1]: *** [Makefile:240: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.9.10-x64v3-xanmod1'
make: *** [Makefile:13: all] Error 2

实际上关键的只有这两行:

./tools/objtool/objtool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by ./tools/objtool/objtool)
./tools/objtool/objtool: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./tools/objtool/objtool)

在 Linux 内核编译的 make prepare 阶段, 会先编译几个会在后续编译阶段用上的程序, 而这些编译好的 executable 也被作为 linux-headers 的一部分进行分发。 这些 executable 依赖编译机器上的 Glibc, 而 Glibc 有前向兼容问题 —— 链接到新版本 Glibc 的程序, 无法在旧版本 Glibc 的环境下执行。

#7#18 中, 遇到这个问题的用户尝试通过修改软件仓库的配置文件, 从较新版本的软件仓库更新 Glibc, 但是, 这种跨版本的「不完全更新」可能会在后续安装某个包时导致系统损坏。 我们不打算推荐这种做法。

Nix 可以在多种 Linux 发行版上运行, 并且能够提供较新的 Glibc。 除此在外, Nix 还提供了一个叫做 patchelf 的工具, 可以修改 ELF 文件的 interpreter 和 rpath。 这个工具原本是用来修改既存的 executable 使其能够在 NixOS 这种非 UNIX 兼容的环境下运行的。 但是, 我们也可以用它修改 linux-headers 中的 executable, 使其通过 Nix 的 Glibc 执行, 从而解决上面提到的问题。

于是我编写了这个 Nix 脚本使这个操作可以「一键」执行。

FAQ

为什么不是 Nix Flake

因为 Debian 11 上用 apt install nix 安装的 Nix 版本太旧不支持 Flake, 才不是因为我懒

不需要安装 gcc-13 吗?

不需要。 经过测试 gcc-10 编译的内核模块一样可以正常加载。 此外, 如果你需要 gcc-13, Nix 也可以快速地提供一个环境。

let
nixpkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/refs/tags/24.05.tar.gz") {};
pkgs = nixpkgs.pkgs;
lib = nixpkgs.lib;
script = ''
#!/usr/bin/env bash
set -e
LDSO="${pkgs.glibc}/lib/ld-linux-x86-64.so.2"
RPATH="${lib.makeLibraryPath [ pkgs.glibc pkgs.elfutils ]}"
PATCHELF="${pkgs.patchelf}/bin/patchelf"
FILE="${pkgs.file}/bin/file"
FIND="${pkgs.findutils}/bin/find"
is_executable() {
local _target="$1"
local _mimetype="$("$FILE" --mime-type --brief "$_target")"
[[ "$_mimetype" == "application"*"executable"* ]]
}
patch_kernel_dir() {
local _dir="$1"
echo "[info] patching dir $_dir"
local _saved_IFS="$IFS"
IFS="$(echo)"
"$FIND" "$_dir" -type f | while read -r f; do
if is_executable "$f"; then
patch_kernel_elf "$f"
fi
done
IFS="$_saved_IFS"
}
patch_kernel_elf() {
local _target="$1"
echo "[info] patching $_target"
"$PATCHELF" \
--set-interpreter "$LDSO" \
--set-rpath "$RPATH" \
"$_target"
}
find_kernel_dir() {
local _kernel_version="$1"
if [[ -z "$_kernel_version" ]]; then
_kernel_version="$(uname -r)"
fi
echo "/lib/modules/$_kernel_version/build"
}
show_usage() {
echo "usage: $0 [-h] [-k kernel_version] [-d kernel_dir]" >&2
}
main() {
local _kernel_version=""
local _kernel_dir=""
while getopts "hd:v:" opt; do
case $opt in
h)
show_usage
exit 0
;;
k)
_kernel_version="$OPTARG"
;;
d)
_kernel_dir="$"
esac
done
if [[ -z "$_kernel_dir" ]]; then
_kernel_dir="$(find_kernel_dir "$_kernel_version")"
fi
if [[ ! -e "$_kernel_dir" ]]; then
echo "[error] kernel dir $kernel_dir does not exist" >&2
exit 22
fi
patch_kernel_dir "$_kernel_dir/tools"
patch_kernel_dir "$_kernel_dir/scripts"
echo "done."
}
main "$@"
'';
fix-kernel-headers = { stdenv, lib, pkgs }: stdenv.mkDerivation {
pname = "fix-kernel-headers";
version = "0.0.2";
unpackPhase = "true";
installPhase = ''
mkdir -p "$out/bin"
echo '${script}' > "$out/bin/fix-kernel-headers"
chmod +x "$out/bin/fix-kernel-headers"
'';
};
fix-kernel-headers-pkg = pkgs.callPackage fix-kernel-headers {};
in nixpkgs.mkShell {
buildInputs = [ fix-kernel-headers-pkg ];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment