はじめに
以下の記事にて、クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点を紹介しました。
- 「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」 @Qiita
- 「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点(Linux Kernel 5.x編)」 @Qiita
上記の記事で、クロスコンパイルした linux-headers-.deb をターゲット上にインストールした時に、このパッケージに含まれる modpost プログラム)が、クロスコンパイルしたホストコンピューターのアーキテクチャでビルドされたままでターゲットコンピューター上では動かない問題があることを説明しました。
また、上記の記事でこの問題の解決策として、linux-header-.deb をターゲットコンピューターにインストールする際に modpost などの各種ツールを自動的に再ビルドする方法を説明しました。
この問題が Linux Kernel 6.12 で進展がありました。linux-headers-*.deb をクロスコンパイルでビルドする際にターゲットのアーキテクチャにあわせて再ビルドするようになったのです。この記事では、その変更点、その問題点、および改善案を説明します。
Linux Kernel 6.12 での変更点
script/package/builddeb は各種 Debian Package をビルドする時に呼び出されるシェルスクリプトです。
Linux Kernel 6.12 では script/package/builddeb が次のようになっています。
:
(前略)
:
install_kernel_headers () {
pdir=debian/$1
version=${1#linux-headers-}
CC="${DEB_HOST_GNU_TYPE}-gcc" "${srctree}/scripts/package/install-extmod-build" "${pdir}/usr/src/linux-headers-${version}"
mkdir -p $pdir/lib/modules/$version/
ln -s /usr/src/linux-headers-$version $pdir/lib/modules/$version/build
}
:
(中略)
:
package=$1
case "${package}" in
*-dbg)
install_linux_image_dbg "${package}";;
linux-image-*|user-mode-linux-*)
install_linux_image "${package}";;
linux-libc-dev)
install_libc_headers "${package}";;
linux-headers-*)
install_kernel_headers "${package}";;
esac
linux-headers-*.deb をビルドする場合は、install_kernel_headers()
が呼び出されます。install_kernel_headers()
にて scripts/package/install-extmod-build を呼んでいます。
scripts/package/install-extmod-build は次のようになっています。
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
set -eu
destdir=${1}
is_enabled() {
grep -q "^$1=y" include/config/auto.conf
}
find_in_scripts() {
find scripts \
\( -name atomic -o -name dtc -o -name kconfig -o -name package \) -prune -o \
! -name unifdef -a ! -name mk_elfconfig -a \( -type f -o -type l \) -print
}
mkdir -p "${destdir}"
(
cd "${srctree}"
echo Makefile
find "arch/${SRCARCH}" -maxdepth 1 -name 'Makefile*'
find "arch/${SRCARCH}" -name generated -prune -o -name include -type d -print
find "arch/${SRCARCH}" -name Kbuild.platforms -o -name Platform
find include \( -name config -o -name generated \) -prune -o \( -type f -o -type l \) -print
find_in_scripts
) | tar -c -f - -C "${srctree}" -T - | tar -xf - -C "${destdir}"
{
if is_enabled CONFIG_OBJTOOL; then
echo tools/objtool/objtool
fi
echo Module.symvers
echo "arch/${SRCARCH}/include/generated"
echo include/config/auto.conf
echo include/config/kernel.release
echo include/generated
find_in_scripts
if is_enabled CONFIG_GCC_PLUGINS; then
find scripts/gcc-plugins -name '*.so'
fi
} | tar -c -f - -T - | tar -xf - -C "${destdir}"
# When ${CC} and ${HOSTCC} differ, rebuild host programs using ${CC}.
#
# This caters to host programs that participate in Kbuild. objtool and
# resolve_btfids are out of scope.
if [ "${CC}" != "${HOSTCC}" ]; then
echo "Rebuilding host programs with ${CC}..."
cat <<-'EOF' > "${destdir}/Kbuild"
subdir-y := scripts
EOF
# HOSTCXX is not overridden. The C++ compiler is used to build:
# - scripts/kconfig/qconf, which is unneeded for external module builds
# - GCC plugins, which will not work on the installed system even after
# being rebuilt.
#
# Use the single-target build to avoid the modpost invocation, which
# would overwrite Module.symvers.
"${MAKE}" HOSTCC="${CC}" KBUILD_EXTMOD="${destdir}" scripts/
cat <<-'EOF' > "${destdir}/scripts/Kbuild"
subdir-y := basic
hostprogs-always-y := mod/modpost
mod/modpost-objs := $(addprefix mod/, modpost.o file2alias.o sumversion.o symsearch.o)
EOF
# Run once again to rebuild scripts/basic/ and scripts/mod/modpost.
"${MAKE}" HOSTCC="${CC}" KBUILD_EXTMOD="${destdir}" scripts/
rm -f "${destdir}/Kbuild" "${destdir}/scripts/Kbuild"
fi
find "${destdir}" \( -name '.*.cmd' -o -name '*.o' \) -delete
# When ${CC} and ${HOSTCC} differ, rebuild host programs using ${CC}.
から始まる部分が、Linux Kernel 6.12 にて追加されました。どうやらここでクロスコンパイル環境ならば、script/basic と script/mod/modpost をターゲットアーキテクチャにあわせて再ビルドしているようです。
これで長い間悩んでいた問題が一件落着と思いましたが、残念ながらそうは問屋が卸さなかったようで、これはこれで新た問題が発生しました。新たな問題は次の章で説明します。
Linux Kernel 6.12 での問題点
クロスコンパイル時に script/mod/modpost をターゲットにあわせて再ビルドするようになったため、script/mod/modpost がクロスコンパイルした環境に依存するようになりました。
例えば、ホストコンピューター(arch=amd64,OS=Ubuntu24.04,cc=gcc-13.3.0) で ターゲットコンピューター(arch=arm64)用のLinux Kernel をビルドしたとします。ここでビルドした linux-headers-*.deb をターゲットコンピューター(OS=Debian12,cc=gcc-12.2.0) にインストールして、fclkcfg などの kernel module をターゲットコンピューター上でセルフビルドすると、次のようなエラーが出て失敗しました。
shell$ make
make -C /lib/modules/6.12.27-zynqmp-fpga-generic/build ARCH=arm64 CROSS_COMPILE= M=/home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg CONFIG_FCLKCFG=m modules
make[1]: Entering directory '/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: aarch64-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
You are using: gcc (Debian 12.2.0-14) 12.2.0
CC [M] /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/fclkcfg.o
MODPOST /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/Module.symvers
scripts/mod/modpost: /lib/aarch64-linux-gnu/libc.so.6: version `GLIBC_2.38' not found (required by scripts/mod/modpost)
make[3]: *** [scripts/Makefile.modpost:145: /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/Module.symvers] Error 1
make[2]: *** [/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic/Makefile:1901: modpost] Error 2
make[1]: *** [Makefile:224: __sub-make] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic'
make: *** [Makefile:35: all] Error 2
途中で C Compiler のバージョンが違うという表示はひとまずおいておくとして、問題は scripts/mod/modpost が失敗していることです。どうやら scripts/mod/modpost が参照している動的ライブラリのバージョンが異っているのが原因のようです。
Linux Kernel 6.12 での改善案
結局、従来通りに、ターゲットコンピューターにインストールする際に modpost などの各種ツールを自動的に再ビルドするようにしました。
具体的にはまず、次のような script/package/install-extmod-build-generic を用意します。
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0-only
#
# install-extmod-build(generic)
#
set -eu
script_name=$0
verbose=0
while [ $# -gt 0 ]; do
case "$1" in
-v|--verbose)
verbose=1
shift
;;
-a|--arch)
shift
[ $# -eq 0 ] && echo "Missing argument for $1" >&2 && exit 1
SRCARCH=$1
shift
;;
-s|--srctree)
shift
[ $# -eq 0 ] && echo "Missing argument for $1" >&2 && exit 1
srctree=$1
shift
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
destdir=$1
shift
;;
esac
done
test -n "${destdir}"
test -n "${srctree}"
test -n "${SRCARCH}"
if [ $verbose -gt 0 ]; then
echo "## $script_name: destdir = ${destdir}"
echo "## $script_name: srctree = ${srctree}"
echo "## $script_name: SRCARCH = ${SRCARCH}"
fi
#
# start scripts/package/install-extmod-build
#
is_enabled() {
grep -q "^$1=y" include/config/auto.conf
}
find_in_scripts() {
find scripts \
\( -name atomic -o -name dtc -o -name kconfig -o -name package \) -prune -o \
! -name unifdef -a ! -name mk_elfconfig -a \( -type f -o -type l \) -print
}
mkdir -p "${destdir}"
(
cd "${srctree}"
echo Makefile
find "arch/${SRCARCH}" -maxdepth 1 -name 'Makefile*'
find "arch/${SRCARCH}" -name generated -prune -o -name include -type d -print
find "arch/${SRCARCH}" -name Kbuild.platforms -o -name Platform
find include \( -name config -o -name generated \) -prune -o \( -type f -o -type l \) -print
find_in_scripts
) | tar -c -f - -C "${srctree}" -T - | tar -xf - -C "${destdir}"
(
cd "${srctree}"
if is_enabled CONFIG_OBJTOOL; then
echo tools/objtool/objtool
fi
echo Module.symvers
echo "arch/${SRCARCH}/include/generated"
echo include/config/auto.conf
echo include/config/kernel.release
echo include/generated
find_in_scripts
if is_enabled CONFIG_GCC_PLUGINS; then
find scripts/gcc-plugins -name '*.so'
fi
) | tar -c -f - -C "${srctree}" -T - | tar -xf - -C "${destdir}"
find "${destdir}" \( -name '.*.cmd' -o -name '*.o' \) -delete
#
# end of scripts/package/install-extmod-build
#
#
# copy files for postinst
#
(
cd "${srctree}"
find . \( -name Makefile\* -o -name Kconfig\* -o -name \*.pl \) -print
find include tools/include security/selinux/include \( -type f -o -type l \) -print
find scripts \( -type f -o -type l \) -print
find "arch/${SRCARCH}" \( -name Kbuild.platforms -o -name Platform \) -print
find $(find "arch/${SRCARCH}" -name include -o -name scripts -o -name tools -type d) -type f
) | tar -c -f - -C "${srctree}" -T - | tar -xf - -C "${destdir}"
#
# remove binary files for postinst
#
find "${destdir}" -type f -exec file {} + | grep 'ELF' | cut -d: -f1 | xargs rm -f
#
# copy .config manually to be where it's expected to be
#
cp "${srctree}/.config" "${destdir}/.config"
#
# patch Makefile for postinst
#
cat <<-'EOF' | patch -p0 -d "${destdir}"
--- Makefile 2025-07-26 15:43:33.031678200 +0900
+++ Makefile 2025-07-26 15:41:24.334781500 +0900
@@ -276,6 +276,7 @@
outputmakefile rustavailable rustfmt rustfmtcheck
no-sync-config-targets := $(no-dot-config-targets) %install modules_sign kernelrelease \
image_name
+no-sync-config-targets += linux-headers-postinst
single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.rsi %.s %.symtypes %/
config-build :=
@@ -1220,6 +1221,11 @@
$(Q)$(MAKE) $(build)=scripts/mod
$(Q)$(MAKE) $(build)=. prepare
+# linux-headers debian package postinst
+PHONY += linux-headers-postinst
+linux-headers-postinst: archscripts scripts
+ $(Q)$(MAKE) $(build)=scripts/mod
+
# All the preparing..
prepare: prepare0
ifdef CONFIG_RUST
EOF
script/package/install-extmod-build-generic は script/package/install-extmod-build を元に改良しました。
スクリプトの前半は、script/package/install-extmod-build と同じように必要なファイルを作業用のディレクトリにコピーしています。
スクリプトの中盤は、postinst(インストール後に自動的に実行する処理)に必要なファイルを作業用のディレクトリに追加し、不要なバイナリファイルを削除しています。
スクリプトの後半は、postinst で実行する make コマンドのために、Makefile に linux-headers-postinst ターゲットを追加しています。
さらに script/package/builddeb を次のように変更します。
:
(前略)
:
install_kernel_headers () {
pdir=debian/$1
version=${1#linux-headers-}
CC="${DEB_HOST_GNU_TYPE}-gcc" "${srctree}/scripts/package/install-extmod-build" "${pdir}/usr/src/linux-headers-${version}"
mkdir -p $pdir/lib/modules/$version/
ln -s /usr/src/linux-headers-$version $pdir/lib/modules/$version/build
}
install_kernel_headers_generic () {
pdir=debian/$1
version=${1#linux-headers-}
"${srctree}/scripts/package/install-extmod-build-generic" "${pdir}/usr/src/linux-headers-${version}"
mkdir -m 755 -p "$pdir/DEBIAN"
cat <<EOF >> $pdir/DEBIAN/postinst
#!/bin/sh -e
make -C /usr/src/linux-headers-$version linux-headers-postinst
EOF
chmod 755 $pdir/DEBIAN/postinst
mkdir -p $pdir/lib/modules/$version/
ln -s /usr/src/linux-headers-$version $pdir/lib/modules/$version/build
}
:
(中略)
:
package=$1
case "${package}" in
*-dbg)
install_linux_image_dbg "${package}";;
linux-image-*|user-mode-linux-*)
install_linux_image "${package}";;
linux-libc-dev)
install_libc_headers "${package}";;
linux-headers-*)
install_kernel_headers_generic "${package}";;
esac
linux-headers-*.deb をビルドする場合は、install_kernel_headers()
ではなく install_kernel_headers_generic()
を呼び出すように変更します。
install_kernel_headers_generic()
では、scripts/package/install-extmod-build-generic を呼んだ後、Debian Package がターゲットにインストールした後に自動的に実行されるスクリプト postinst を追加しています。postinst では、make -C /usr/src/linux-headers-$version linux-headers-postinst
を実行します。
さらに、scripts/package/mkdebian も少し変更します。具体的には次のように依存関係を整理します。
diff --git a/scripts/package/mkdebian b/scripts/package/mkdebian
index fc3b7fa70..8ff6607f8 100755
--- a/scripts/package/mkdebian
+++ b/scripts/package/mkdebian
@@ -201,7 +201,7 @@ Build-Depends: debhelper-compat (= 12)
Build-Depends-Arch: bc, bison, cpio, flex,
gcc-${host_gnu} <!pkg.${sourcename}.nokernelheaders>,
kmod, libelf-dev:native,
- libssl-dev:native, libssl-dev <!pkg.${sourcename}.nokernelheaders>,
+ libssl-dev:native,
rsync
Homepage: https://www.kernel.org/
このように変更した scripts/package を使ってビルドした linux-headers-*.deb をターゲットにインストールと次のように、自動的に各種ツールがターゲット用にビルドされます。
shell$ dpkg -i linux-headers-6.12.27-zynqmp-fpga-generic_6.12.27-zynqmp-fpga-3_arm64.deb
Selecting previously unselected package linux-headers-6.12.27-zynqmp-fpga-generic.
(Reading database ... 40346 files and directories currently installed.)
Preparing to unpack linux-headers-6.12.27-zynqmp-fpga-generic_6.12.27-zynqmp-fpga-3_arm64.deb ...
Unpacking linux-headers-6.12.27-zynqmp-fpga-generic (6.12.27-zynqmp-fpga-3) ...
Setting up linux-headers-6.12.27-zynqmp-fpga-generic (6.12.27-zynqmp-fpga-3) ...
make: Entering directory '/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic'
HOSTCC scripts/basic/fixdep
HOSTCC scripts/dtc/dtc.o
HOSTCC scripts/dtc/flattree.o
HOSTCC scripts/dtc/fstree.o
HOSTCC scripts/dtc/data.o
HOSTCC scripts/dtc/livetree.o
HOSTCC scripts/dtc/treesource.o
HOSTCC scripts/dtc/srcpos.o
HOSTCC scripts/dtc/checks.o
HOSTCC scripts/dtc/util.o
HOSTCC scripts/dtc/dtc-lexer.lex.o
HOSTCC scripts/dtc/dtc-parser.tab.o
HOSTLD scripts/dtc/dtc
HOSTCC scripts/dtc/libfdt/fdt.o
HOSTCC scripts/dtc/libfdt/fdt_ro.o
HOSTCC scripts/dtc/libfdt/fdt_wip.o
HOSTCC scripts/dtc/libfdt/fdt_sw.o
HOSTCC scripts/dtc/libfdt/fdt_rw.o
HOSTCC scripts/dtc/libfdt/fdt_strerror.o
HOSTCC scripts/dtc/libfdt/fdt_empty_tree.o
HOSTCC scripts/dtc/libfdt/fdt_addresses.o
HOSTCC scripts/dtc/libfdt/fdt_overlay.o
HOSTCC scripts/dtc/fdtoverlay.o
HOSTLD scripts/dtc/fdtoverlay
HOSTCC scripts/selinux/genheaders/genheaders
HOSTCC scripts/selinux/mdp/mdp
HOSTCC scripts/kallsyms
HOSTCC scripts/sorttable
HOSTCC scripts/asn1_compiler
CC scripts/mod/empty.o
HOSTCC scripts/mod/mk_elfconfig
MKELF scripts/mod/elfconfig.h
HOSTCC scripts/mod/modpost.o
CC scripts/mod/devicetable-offsets.s
HOSTCC scripts/mod/file2alias.o
HOSTCC scripts/mod/sumversion.o
HOSTCC scripts/mod/symsearch.o
HOSTLD scripts/mod/modpost
make[2]: warning: Clock skew detected. Your build may be incomplete.
make[1]: warning: Clock skew detected. Your build may be incomplete.
make: warning: Clock skew detected. Your build may be incomplete.
make: Leaving directory '/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic'
無事に Kernel Module のセルフコンパイルも成功しました。
shell$ make
make -C /lib/modules/6.12.27-zynqmp-fpga-generic/build ARCH=arm64 CROSS_COMPILE= M=/home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg CONFIG_FCLKCFG=m modules
make[1]: Entering directory '/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic'
warning: the compiler differs from the one used to build the kernel
The kernel was built by: aarch64-linux-gnu-gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
You are using: gcc (Debian 12.2.0-14) 12.2.0
CC [M] /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/fclkcfg.o
MODPOST /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/Module.symvers
CC [M] /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/fclkcfg.mod.o
CC [M] /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/.module-common.o
LD [M] /home/fpga/work/fclkcfg-kmod-dpkg/fclkcfg/fclkcfg.ko
make[1]: Leaving directory '/usr/src/linux-headers-6.12.27-zynqmp-fpga-generic'
linux Kernel 6.12.27 用のパッチファイルは以下の URL で公開しています。