クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点(Linux Kernel 5.x編)

## はじめに

以下の記事にて、クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点を紹介しました。

 * [「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」 @Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c)

上の記事は Linux Kernel 4.x を対象にしたものでしたが、Linux Kernel 5.x では新たな問題が発生しました。この記事では、Linux Kernel 5.x にてデバイスドライバをセルフコンパイル(ターゲットシステムの上でコンパイル)しようとしたときに遭遇した問題点と(暫定的な)解決方法について説明します。

## セルフコンパイルの失敗例

### ダウンロード

ここで dtbocfg (Device Tree Blob Overlay Configuration File System)を例としてコンパイルしてみます。

```console
root@debian-fpga:~/work# git clone https://github.com/ikwzm/dtbocfg.git
Cloning into 'dtbocfg'...
remote: Enumerating objects: 59, done.
remote: Total 59 (delta 0), reused 0 (delta 0), pack-reused 59
Unpacking objects: 100% (59/59), done.
root@debian-fpga:~/work# cd dtbocfg/
```

### 失敗時のログ

すると、次のようにビルドに失敗します。

```console
root@debian-fpga:~/work/dtbocfg# make
make -C /lib/modules/5.0.21-armv7-fpga/build ARCH=arm CROSS_COMPILE= M=/root/work/dtbocfg modules
make[1]: Entering directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
  CC [M]  /root/work/dtbocfg/dtbocfg.o
  Building modules, stage 2.
  MODPOST 1 modules
/bin/sh: 1: scripts/mod/modpost: Exec format error
make[2]: *** [scripts/Makefile.modpost:92: __modpost] Error 2
make[1]: *** [Makefile:1581: modules] Error 2
make[1]: Leaving directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
make: *** [Makefile:19: all] Error 2
```

### 失敗の理由

ログを見ると、どうやら ```scripts/mod/modpost``` の実行フォーマットが違うと言っているようです。```scripts/mod/modpost``` は ```/usr/src/linux-headers-*/scripts/mod/modpost``` にあります。ファイルの型を確認すると、ホストコンピュータのアーキテクチャ(ここでは x86-64)のバイナリファイルになっています。

```console
root@debian-fpga:~/work/dtbocfg# file /usr/src/linux-headers-5.0.21-armv7-fpga/scripts/mod/modpost
/usr/src/linux-headers-5.0.21-armv7-fpga/scripts/mod/modpost: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b5044d5ffd526059755d33a6d8a8ab6c16e25293, not stripped
```

[「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」 @Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c) で紹介した方法で linux-headers の Debian package を作ると、Debian package をターゲットシステムにインストールした時に自動的に ```make scripts``` が走ってアーキテクチャに依存する各種実行ファイルを再ビルドします。

そこで linux-headers の Debian package をインストールした時のログを確認してみると、何故か、```scripts/mod/modpost``` の再ビルドが行われていませんでした。

```console
root@debian-fpga:/home/fpga/debian# dpkg -i linux-headers-5.0.21-armv7-fpga_5.0.21-armv7-fpga-0_armhf-old.deb
Selecting previously unselected package linux-headers-5.0.21-armv7-fpga.
(Reading database ... 134399 files and directories currently installed.)
Preparing to unpack linux-headers-5.0.21-armv7-fpga_5.0.21-armv7-fpga-0_armhf-old.deb ...
Unpacking linux-headers-5.0.21-armv7-fpga (5.0.21-armv7-fpga-0) ...
Setting up linux-headers-5.0.21-armv7-fpga (5.0.21-armv7-fpga-0) ...
make: Entering directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
  HOSTCC  scripts/basic/fixdep
  HOSTLD  scripts/kconfig/conf
scripts/kconfig/conf  --syncconfig Kconfig
  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/genksyms/genksyms.o
  HOSTCC  scripts/genksyms/parse.tab.o
  HOSTCC  scripts/genksyms/lex.lex.o
  HOSTLD  scripts/genksyms/genksyms
  HOSTCC  scripts/bin2c
  HOSTCC  scripts/kallsyms
  HOSTCC  scripts/conmakehash
  HOSTCC  scripts/sortextable
  HOSTCC  scripts/asn1_compiler
  HOSTCC  scripts/extract-cert
make: Leaving directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
```

どうやら、Linux Kernel 5.x になって ```scripts/mod/modpost``` のビルド手順が変更になり、```make scripts``` では ```scripts/mod/modpost``` のビルドが行われなくなったようです。scripts/Makefile を Linux Kernel 5.0.21 と 4.19.57 とで比較してみると、mod まわりに変更が加えられていました。

```diff
--- linux-5.0.21-armv7-fpga/scripts/Makefile	2019-10-17 13:21:30.586261800 +0900
+++ linux-4.19.57-armv7-fpga/scripts/Makefile	2019-07-10 11:25:25.002216800 +0900
@@ -36,10 +36,11 @@ PHONY += build_unifdef
 build_unifdef: $(obj)/unifdef
 	@:
 
-subdir-$(CONFIG_GCC_PLUGINS) += gcc-plugins
 subdir-$(CONFIG_MODVERSIONS) += genksyms
+subdir-y                     += mod
 subdir-$(CONFIG_SECURITY_SELINUX) += selinux
+subdir-$(CONFIG_DTC)         += dtc
 subdir-$(CONFIG_GDB_SCRIPTS) += gdb
 
 # Let clean descend into subdirs
-subdir-	+= basic dtc kconfig mod package
+subdir-	+= basic kconfig package gcc-plugins
```

Linux Kernel 5.x では ```scripts/mod/modpost``` のビルドは トップレベルの Makefile の prepare0 で定義されていました。

```Makefile:Makefile
    :
  (前略)
    :
# prepare2 creates a makefile if using a separate output directory.
# From this point forward, .config has been reprocessed, so any rules
# that need to depend on updated CONFIG_* values can be checked here.
prepare2: prepare3 outputmakefile asm-generic

prepare1: prepare2 $(version_h) $(autoksyms_h) include/generated/utsrelease.h
	$(cmd_crmodverdir)

archprepare: archheaders archscripts prepare1 scripts

prepare0: archprepare
	$(Q)$(MAKE) $(build)=scripts/mod
	$(Q)$(MAKE) $(build)=.

# All the preparing..
prepare: prepare0 prepare-objtool
    :
  (後略)
    :
```

そこでターゲットシステム上の ```/usr/src/linux-headers-*/``` で ```make prepare0``` を実行してみました。

```console
oot@debian-fpga:~/work/dtbocfg# cd /usr/src/linux-headers-5.0.21-armv7-fpga/
root@debian-fpga:/usr/src/linux-headers-5.0.21-armv7-fpga# make prepare0
make[1]: *** No rule to make target 'arch/arm/tools/syscall.tbl', needed by 'arch/arm/include/generated/uapi/asm/unistd-common.h'.  Stop.
make: *** [arch/arm/Makefile:323: archheaders] Error 2
```

今度は ```arch/arm/tools/syscall.tbl``` が無いと言われました。

## ヘッダーパッケージの修正

本来ならばターゲットアーキテクチャ用にコンパイルした実行ファイルをヘッダーパッケージに含めるようにするのが正しい方法だと思います。しかし、私の調べた範囲では、このようにヘッダーパッケージをビルドする方法が判りませんでした。どなたか詳しい方がいらっしゃれば、教えてくださると有難いです。

次善の策として、ターゲットにヘッダーパッケージをインストールした際に自動的に各種スクリプトを再ビルドするように、ヘッダーパッケージを作り直します。具体的には、ヘッダーパッケージを生成するスクリプトに次のようなパッチを当てます。

```diff
diff --git a/scripts/package/builddeb b/scripts/package/builddeb
index 8ac25d10a..00ad59866 100755
--- a/scripts/package/builddeb
+++ b/scripts/package/builddeb
@@ -155,9 +155,9 @@ done
 
 # Build kernel header package
 (cd $srctree; find . -name Makefile\* -o -name Kconfig\* -o -name \*.pl) > "$objtree/debian/hdrsrcfiles"
-(cd $srctree; find arch/*/include include scripts -type f -o -type l) >> "$objtree/debian/hdrsrcfiles"
+(cd $srctree; find arch/*/include include tools/include scripts -type f -o -type l) >> "$objtree/debian/hdrsrcfiles"
 (cd $srctree; find arch/$SRCARCH -name module.lds -o -name Kbuild.platforms -o -name Platform) >> "$objtree/debian/hdrsrcfiles"
-(cd $srctree; find $(find arch/$SRCARCH -name include -o -name scripts -type d) -type f) >> "$objtree/debian/hdrsrcfiles"
+(cd $srctree; find $(find arch/$SRCARCH -name include -o -name scripts -o -name tools -type d) -type f) >> "$objtree/debian/hdrsrcfiles"
 if grep -q '^CONFIG_STACK_VALIDATION=y' $KCONFIG_CONFIG ; then
 	(cd $objtree; find tools/objtool -type f -executable) >> "$objtree/debian/hdrobjfiles"
 fi
@@ -172,6 +172,15 @@ mkdir -p "$destdir"
 (cd $objtree; cp $KCONFIG_CONFIG $destdir/.config) # copy .config manually to be where it's expected to be
 ln -sf "/usr/src/linux-headers-$version" "$kernel_headers_dir/lib/modules/$version/build"
 rm -f "$objtree/debian/hdrsrcfiles" "$objtree/debian/hdrobjfiles"
+mkdir -m 755 -p "$kernel_headers_dir/DEBIAN"
+cat <<EOF >> $kernel_headers_dir/DEBIAN/postinst
+#!/bin/sh -e
+
+make -C /usr/src/linux-headers-$version prepare0
+
+EOF
+
+chmod 755 $kernel_headers_dir/DEBIAN/postinst
 
 if [ "$ARCH" != "um" ]; then
 	create_package "$kernel_headers_packagename" "$kernel_headers_dir"
```

このパッチファイルは builddeb に対して、```tools/include``` 以下のファイルを含めるようにしています。これにより ```scripts/sortextable``` のビルドが正常に終了します（[「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」 @Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c)参照）。

さらに builddeb に対して ```arch/$SRCARCH/tools``` 以下のファイルを含めるようにしています。これにより ```scripts/mod/modpost``` のビルドが正常に終了します。

そして、スクリプトをターゲット用に再ビルドするための postinst スクリプトをヘッダーパッケージに追加しています。postinst スクリプトで ```make prepare0``` を実行するようにしています。これにより、ヘッダーパッケージをインストールした際、自動的にスクリプトを再ビルドします。

## 修正したヘッダーパッケージのインストール

前節で説明した方法でビルドしたヘッダーパッケージをターゲットシステムにインストールします。

```console
root@debian-fpga:/home/fpga/debian# dpkg -i linux-headers-5.0.21-armv7-fpga_5.0.21-armv7-fpga-0_armhf.deb
Selecting previously unselected package linux-headers-5.0.21-armv7-fpga.
(Reading database ... 134399 files and directories currently installed.)
Preparing to unpack linux-headers-5.0.21-armv7-fpga_5.0.21-armv7-fpga-0_armhf.deb ...
Unpacking linux-headers-5.0.21-armv7-fpga (5.0.21-armv7-fpga-0) ...
Setting up linux-headers-5.0.21-armv7-fpga (5.0.21-armv7-fpga-0) ...
make: Entering directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  HOSTCC  scripts/kconfig/confdata.o
  HOSTCC  scripts/kconfig/expr.o
  HOSTCC  scripts/kconfig/symbol.o
  HOSTCC  scripts/kconfig/preprocess.o
  LEX     scripts/kconfig/zconf.lex.c
  YACC    scripts/kconfig/zconf.tab.h
  HOSTCC  scripts/kconfig/zconf.lex.o
  YACC    scripts/kconfig/zconf.tab.c
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/conf
scripts/kconfig/conf  --syncconfig Kconfig
  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/genksyms/genksyms.o
  HOSTCC  scripts/genksyms/parse.tab.o
  HOSTCC  scripts/genksyms/lex.lex.o
  HOSTLD  scripts/genksyms/genksyms
  HOSTCC  scripts/bin2c
  HOSTCC  scripts/kallsyms
  HOSTCC  scripts/conmakehash
  HOSTCC  scripts/sortextable
  HOSTCC  scripts/asn1_compiler
  HOSTCC  scripts/extract-cert
  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
  HOSTLD  scripts/mod/modpost
make: Leaving directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
```

無事に ```scripts/mod/modpost``` が再ビルドされています。

dtbocfg をコンパイルしてみましょう。

```console
root@debian-fpga:~/work/dtbocfg# make
make -C /lib/modules/5.0.21-armv7-fpga/build ARCH=arm CROSS_COMPILE= M=/root/work/dtbocfg modules
make[1]: Entering directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
  CC [M]  /root/work/dtbocfg/dtbocfg.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /root/work/dtbocfg/dtbocfg.mod.o
  LD [M]  /root/work/dtbocfg/dtbocfg.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.0.21-armv7-fpga'
```

今度は無事にコンパイル出来ました。

```console
root@debian-fpga:~/work/dtbocfg# insmod dtbocfg.ko
[32165.177417] dtbocfg_module_init
[32165.180623] dtbocfg_module_init: OK
```

コンパイルした dtbocfg もちゃんとロード出来ているようです。

## 参考

 * [https://github.com/ikwzm/FPGA-SoC-Linux](https://github.com/ikwzm/FPGA-SoC-Linux)
 * [「クロスコンパイルした Debian GNU/Linux でデバイスドライバをセルフコンパイルする際の注意点」 @Qiita](https://qiita.com/ikwzm/items/e31ecb099d0d50cb027c)