続・玩式草子 ―戯れせんとや生まれけん―

第14回Days of WINE and Struggles[1]

Plamo-7.xでは、メンテナンスの手が回らない等の理由で、対応機種を64ビット版x86-64CPUに限定し、32ビットなx86CPUへの対応を打ち切りました。

ここ10年ほどの間に発売されたx86系のCPUは、組み込み向けや超省電力版といった特殊用途を除き、ほぼ全て64ビット版のx86-64になっているので、中古PCをPlamo Linuxで再生しよう、という場合でも、たいてい64ビット版のみで間に合います。

しかし、一部メーカのプリンタドライバなどではLinux用には32ビット版しか提供されていない場合があり、それらを使うためにはPlamo-6.xの32ビット版glibcを流用するなどの細工が必要になります。そのためPlamo-7.xが軌道に乗ったころから、基本的なツールだけでも32ビット版を用意しようか、と考えていました。

しかしながら32ビット版のバイナリを作成するにはgccやgasといった開発ツールから32/64ビット両対応にする必要があり、特定のプリンタドライバを動かすための手間としては大きすぎます。

どうせ32ビット版を作るなら、もう少し使いでのある用途が欲しいなぁ…、と思っていたところ、Ubuntuの32ビット版廃止表明に関連してWINEが32ビット環境を必要としていることを思い出し、⁠夏休みの自由研究」のつもりで、WINEを動かせる程度に32ビット版を作ってみようか、と思い立ちました。

その安直な思い付きが長きに渡る苦闘のはじまりになるのですが(苦笑⁠⁠、せっかくなので夏休みの自由研究日記風に、その過程を紹介してみようと思います。

7月XX日:Multilibな開発環境構築に着手

一般にMultilibと呼ばれている、64ビット版バイナリと32ビット版バイナリが共に動く環境を作るためには、32ビット版の各種ライブラリを用意する必要があり、32ビット版のライブラリを作るにはGCC/Binutilsが32ビット用のバイナリも生成できるようにする必要がある。

さまざまなCPU上で動くように設計されているGCC/Binutilsは、32ビットなバイナリも64ビットなバイナリも生成できるものの、アーキテクチャごとにCPU用の設定ファイル等が必要となるため、デフォルトではビルド時に"--disable-multilib"オプションを指定して64ビット版のみを生成するようにしている。そのため、まずはGCC/Binutilsのビルドからやり直す必要がある。

このあたりはビルド時に指定するオプションやビルドの順番がきわめて重要なので、CLFS(Cross Linux From Scratch)を参考にすることにした。CLFSは、⁠0から動作するLinux環境を作る」というLFSのアイデアを発展させて、異なるCPU用のバイナリを作成できる、いわゆるクロスコンパイル環境を0から構築しよう、というプロジェクトだ。

CLFSには日本語訳もあるものの、ざっと読んでみたところ、英語版ともども最終更新が2年ほど前で止まっており、扱っているソフトウェアのバージョンはだいぶ古くなっている。そのためビルドオプション等の互換性に一抹の不安を感じるものの、まずは手元に揃っているPlamo-7.x用のソフトウェアでビルドしてみることにした。

7月XY日:Glibcビルド用のコンパイル環境の構築

Linuxが採用している共有ライブラリは、ファイルサイズの削減やライブラリのみのバージョンアップなど、さまざまに便利な機能を提供してくれるものの、共有ライブラリが無いとコンパイラすら動作しないため、最初の共有ライブラリをどう作るかが重要になる。

CLFSによると、まず/mnt/clfs/cross-tools/以下に、32ビットなバイナリを出力できる最小規模のBinutils/GCCを用意し、それらを使ってGlibcの32ビット版共有ライブラリを作成した上で、その共有ライブラリを参照するように再度Binutils/GCCをビルドし直す、という手順になるようだ。

ビルド時のオプションや必要なパッチ等はCLFSに詳しいので省くものの、最小規模のGCCのconfigure時に--disable-sharedオプションを指定するのが重要らしい。というのも、このオプションを指定しないと、自身のビルド時にはまだ存在しない32ビット版の共有ライブラリ等を参照してしまい、ビルドに失敗するからだ。

前述のように、CLFSの更新が止っている過去2年ほどの間に、取りあげられているソフトウェアのほとんどがバージョンアップしているため、CLFSの記述がそのまま適用できるかは不安だったものの、とりあえずCLFSの指示のままビルド、インストールを進めてみたところ、M4のビルド時にglibcの仕様変更に由来するfreadaheadパッチが必要になった以外は特に修正する必要はなく、Binutilsは2.32(CLFSの記載は2.28⁠⁠、GCCは9.2.0(同7.1.0)でビルドでき、Glibcビルド用のツールが/mnt/clfs/cross-tools/以下に揃った。

$ ls /mnt/clfs/cross-tools/bin/
file                                 x86_64-unknown-linux-gnu-gcov
m4                                   x86_64-unknown-linux-gnu-gcov-dump
pkg-config                           x86_64-unknown-linux-gnu-gcov-tool
tic                                  x86_64-unknown-linux-gnu-gprof
x86_64-unknown-linux-gnu-addr2line   x86_64-unknown-linux-gnu-ld
x86_64-unknown-linux-gnu-ar          x86_64-unknown-linux-gnu-ld.bfd
x86_64-unknown-linux-gnu-as          x86_64-unknown-linux-gnu-ld.gold
x86_64-unknown-linux-gnu-c++filt     x86_64-unknown-linux-gnu-nm
x86_64-unknown-linux-gnu-cpp         x86_64-unknown-linux-gnu-objcopy
...

lib/gcc/以下には32ビット用のcrtbegin.oなどもインストールされているので、32ビット用のバイナリも出力できそうだ。

$ ls -F /mnt/clfs/cross-tools/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/
32/         crtbeginS.o  crtend.o   crtfastmath.o  crtprec64.o  include/	  install-tools/  libgcov.a
crtbegin.o  crtbeginT.o  crtendS.o  crtprec32.o    crtprec80.o  include-fixed/  libgcc.a        plugin/

$ ls -F /mnt/clfs/cross-tools/lib/gcc/x86_64-unknown-linux-gnu/9.2.0/32
crtbeginT.o  crtfastmath.o  crtprec80.o  install-tools  plugin
crtbegin.o   crtend.o	      crtprec32.o  include	  libgcc.a
crtbeginS.o  crtendS.o      crtprec64.o  include-fixed  libgcov.a

とりあえず32ビット版のGlibcをビルドするために必要なツールは揃ったので、今日はここまでにしておこう。

7月XZ日:32/64ビットなGlibcのビルド

先に作ったBinutils/GCCを使ってGlibc-2.30の32ビット版を作成しようとすると、さっそくエラーに引っかかった。

make[2]: Entering directory '/mnt/clfs/sources/Glibc/glibc-2.30/support'
x86_64-unknown-linux-gnu-gcc -m32 -B/cross-tools/bin/ -Wl,-rpath-link=/mnt/clfs/sources/Glibc/build32:
 ....
/mnt/clfs/cross-tools/bin/../lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../x86_64-unknown-linux-gnu/bin/ld: cannot find -lstdc++
/mnt/clfs/cross-tools/bin/../lib/gcc/x86_64-unknown-linux-gnu/9.2.0/../../../../x86_64-unknown-linux-gnu/bin/ld: cannot find -lgcc_s
collect2: error: ld returned 1 exit status
make[2]: *** [../Rules:215: /mnt/clfs/sources/Glibc/build32/support/links-dso-program] Error 1
make[2]: Leaving directory '/mnt/clfs/sources/Glibc/glibc-2.30/support'
make[1]: *** [Makefile:259: support/others] Error 2
make[1]: Leaving directory '/mnt/clfs/sources/Glibc/glibc-2.30'
make: *** [Makefile:9: all] Error 2

エラー自体は必要なライブラリが見つからない、という単純なものだが、libgcc_s.soもlibstdc++.soもまだ作っていないライブラリなので、ここで必要になるのはおかしい。

これはどうやらGlibcのバージョンに依存する問題のようなので、深く追求せずにCLFSが採用しているGlibc-2.25で試してみることにした。ところが、Glibc-2.25だとビルドの途中にこんなエラーで止まってしまう。

  ../string/bits/string2.h:84:32: error: '__builtin_strncpy' output truncated before terminating nul
     copying 4 bytes from a string of the same length [-Werror=stringop-truncation]
   84 | # define strncpy(dest, src, n) __builtin_strncpy (dest, src, n)
      |                                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
zic.c:1822:3: note: in expansion of macro 'strncpy'
 1822 |   strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
      |   ^~~~~~~
cc1: all warnings being treated as errors
make[2]: *** [../o-iterator.mk:9: /mnt/clfs/sources/Glibc/build32-2.25/timezone/zic.o] Error 1
make[2]: Leaving directory '/mnt/clfs/sources/Glibc/glibc-2.25/timezone'
make[1]: *** [Makefile:215: timezone/others] Error 2
make[1]: Leaving directory '/mnt/clfs/sources/Glibc/glibc-2.25'
make: *** [Makefile:9: all] Error 2

もっともこのエラーには見覚えがあり、GCCにチェックを厳密に行なう指示-Werrorオプションを与えると、コンパイルには支障ない"Warning"レベルの不具合も"Error"として処理を打ち切ってしまうことが原因だろう。GCCのエラーチェックはバージョンが上がるにつれて詳しくなっているので、GCC-7.1のころは問題なかった書き方でも9.2では"Warning"の対象になっているようだ。

configureオプションを調べてみると、"-Werror"を使わないようにする--disable-werrorという設定が見つかったので、このオプションを追加指定してconfigureからやり直す。すると今度は、

/tmp/ccRoISbw.s: Assembler messages:
/tmp/ccRoISbw.s: Error: `loc1@GLIBC_2.0' can't be versioned to common symbol 'loc1'
/tmp/ccRoISbw.s: Error: `loc2@GLIBC_2.0' can't be versioned to common symbol 'loc2'
/tmp/ccRoISbw.s: Error: `locs@GLIBC_2.0' can't be versioned to common symbol 'locs'
make[2]: *** [../o-iterator.mk:9: /mnt/clfs/sources/Glibc/build32-2.25/misc/regexp.os] Error 1
make[2]: *** Waiting for unfinished jobs....
make[2]: Leaving directory '/mnt/clfs/sources/Glibc/glibc-2.25/misc'
make[1]: *** [Makefile:215: misc/subdir_lib] Error 2
make[1]: Leaving directory '/mnt/clfs/sources/Glibc/glibc-2.25'
make: *** [Makefile:9: all] Error 2

と言って止まってしまった。メッセージを見る限り正規表現に関わる機能あたりでエラーになっているようだけど、アセンブラレベルのエラーだから、何が原因なのか見当も付かない。

こういう時は世間の知恵を借りた方が吉、とエラーメッセージをコピペしてググってみると、Binutils-2.29以降で修正された同名シンボルの処理に関わる問題で、パッチもすぐに見つかった。

パッチを適用すると無事ビルドは通り、make installで/tools/lib/以下に32ビット版のライブラリがインストールできた。

$ ls -F /tools/lib/libc*
/tools/lib/libc-2.25.so* /tools/lib/libc.so.6@        /tools/lib/libcidn.so@        /tools/lib/libcrypt.a
/tools/lib/libc.a        /tools/lib/libc_nonshared.a  /tools/lib/libcidn.so.1@      /tools/lib/libcrypt.so@
/tools/lib/libc.so       /tools/lib/libcidn-2.25.so*  /tools/lib/libcrypt-2.25.so*  /tools/lib/libcrypt.so.1@
$ file /tools/lib/libc-2.25.so 
/tools/lib/libc-2.25.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked,
   interpreter /tools/lib/ld-linux.so.2, for GNU/Linux 3.12.0, with debug_info, not stripped

同じソースコードを64ビット用にconfigureし直してビルド、インストールすると、64ビット版は/tools/lib64/以下にインストールされる。

$ ls -F /tools/lib64/libc*
/tools/lib64/libc-2.25.so*  /tools/lib64/libc.so.6@        /tools/lib64/libcidn.so@         /tools/lib64/libcrypt.a
/tools/lib64/libc.a         /tools/lib64/libc_nonshared.a  /tools/lib64/libcidn.so.1@      /tools/lib64/libcrypt.so@
/tools/lib64/libc.so        /tools/lib64/libcidn-2.25.so*  /tools/lib64/libcrypt-2.25.so*  /tools/lib64/libcrypt.so.1@
$ file /tools/lib64/libc-2.25.so 
/tools/lib64/libc-2.25.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked,
   interpreter /tools/lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.12.0, with debug_info, not stripped

Glibc2の32ビット版と64ビット版がビルドできたので、次にこれら共有ライブラリを参照するように、"--disable-shared"オプションを外してGCCをビルドし直す。共有ライブラリ対応以外にも、先にビルドした最小規模のGCCではlibgompやlibquadmath等の機能が無効化されており、そのままではコンパイルできないソフトウェアが出てくるから改めてビルドし直す必要があるようだ。

再構築したGCCは問題なくビルド、インストールできたので、新しいGCCでMultilibな機能が使えるかをテストをしてみる。テスト用のサンプルコードを作成し、まずはホスト側のGCCでビルドする。

$ cat > hello.c
#include <stdio.h>

void main() {
    printf("Hello World !\n");
}
^D
$ /usr/bin/gcc hello.c

当然のことながら作成されたバイナリは64ビット版で、/lib/以下のローダー(interpreter)を参照している。

$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,
    interpreter /lib/ld-linux-x86-64.so.2, BuildID[sha1]=f698ece45dc3fbab11d590f5f3c49812a291617c,
    for GNU/Linux 3.2.0, with debug_info, not stripped

次に、インストールし直した/cross-tools/bin/以下のGCCでビルドすると、ローダーは/tools/lib64/ld-linux-x86-64.so.2になっている。

$ /cross-tools/bin/x86_64-unknown-linux-gnu-gcc  hello.c
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,
    interpreter /tools/lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.12.0, with debug_info, not stripped

32ビット版を作成するには-m32オプションを追加するだけでいい。

$ /cross-tools/bin/x86_64-unknown-linux-gnu-gcc  -m32 hello.c 
$ file a.out
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked,
    interpreter /tools/lib/ld-linux.so.2, for GNU/Linux 3.12.0, with debug_info, not stripped

作成されたバイナリはIntel 80386用の32ビット版で、ローダーも32ビット版の/tools/lib/ld-linux.so.2になっている。もちろん動作には問題ない。

$ ./a.out 
Hello World!

CLFSの手順によると、こうして作った32ビット/64ビット両対応のツールを用いて、/mnt/clfs/tools/以下に独立した最小規模のLinux環境を作り、そこにchrootして最終版のLinux環境を作りあげることになっている。

このように、まず必要最小限のツールから次第に規模の大きいツールをビルドしていき、最終的に独立したLinux環境を整える、という流れはLFSと同じものの、CLFSの場合は32ビット版と64ビット版の双方をビルドすることになるため、作業量はLFSの倍かかることになる。なかなか先は長そうだ。


今回はシリーズの最初ということで、手元でクロスコンパイル環境をビルドした際の状況をかなり詳しく描写してみました。その分、出力メッセージ等が煩雑で読みづらくなってしまったことをお詫びします。

次回以降はもう少し話を端折って、トラブルが起きたあたりを中心に紹介していく予定です。

おすすめ記事

記事・ニュース一覧