玩式草子─ソフトウェアとたわむれる日々

第16回64ビット化への遠い道程その1]

いつの間にか季節は立冬をすぎ、朝晩はめっきり冷えこむようになってきました。夜の時間も長いし、PCの発熱を気にしなくてすむこの時期は、ソフトウェアとたわむれるのに絶好の時期でしょう。

前回、新しいCore i7なマシンを開発用に自作した話を紹介しました。このCPUにはIntel64と呼ばれる64ビット処理機能が搭載されており、32ビット環境との互換性を持ちつつ、64ビットの処理を実行させることが可能です。そこでさっそくこのCPU上でPlamo Linuxの64ビット化の作業に着手しました。

もっとも、64ビット化のためには、現在の32ビット版のパッケージを64ビット用に再コンパイルしていく必要があります。Plamoは小規模なディストリビューションとは言え、収録しているパッケージは1000を越えているので、それら全てを64ビット用に再コンパイルするのはかなり時間と根気のいる作業です。64ビット版Plamo Linuxを一般に公開できるようになるのはだいぶ先の話になりそうですが、この連載では適宜進行状況を紹介していきたいと思います。今回はその第1回目として、64ビット用の開発環境を構築した話題を取り上げてみましょう。

x86-64 CPU

今回導入したCore i7もそうですが、最近のIntel/AMD製のCPUではたいていx86-64と呼ばれる64ビット化機能が組み込まれています。この機能はIntel CPUではIntel64AMD CPUではAMD64と呼んでいますが、機能的には同一なので、両者を総称してx86-64と呼ぶことにします。

表1が、x86-64 CPUのもつ動作モードです。x86-64 CPUには、x86系CPUが持っていた従来からの3つの動作モードに加えて、64ビットモード互換モードという2つのモードが追加されています。この追加された2つのモードはロングモードと総称され、それに合わせて従来からある3つの動作モードはレガシーモードと呼称されます。x86-64 CPUは、明示的にロングモードに移行しない限り、従来通りのレガシーモードで動作し、x86系CPUと互換で、32ビットバイナリだけでなく、BIOS等が利用する16ビットのリアルモードもそのまま動作します。

表1 x86-64 CPUの動作モード
動作モード必要なOSアプリ側の対応
ロングモード64ビットモード64ビット用OS(x86-64用カーネル)64ビット化が必要
互換モード32ビットのまま動作可能
レガシーモードプロテクトモード32ビット用OS(x86用カーネル)32ビット用
仮想8086モード16ビット用
リアルモード16ビット用OS(MS-DOS等)

一方、ロングモードに入るとアドレス長が64ビットに拡張され、新たに追加された64ビットの汎用レジスタも使えるようになります。また、ロングモードの中には32ビットのバイナリを動かす互換モードも用意され、このモードを使えば既存の32ビットのバイナリもそのまま動かすことができるようになっています。

こう書けばいいことづくめのように聞こえますが、そもそもロングモードに入るためにはカーネル自体がx86-64用にコンパイルされて、x86-64のロングモードのことを知っていなければいけませんし、カーネルをx86-64用にコンパイルするためには、コンパイラやアセンブラがx86-64用に拡張されたコマンド(オペコード)を生成できる必要があります。すなわちx86-64 CPUの新機能を利用するには、まずx86-64用の開発環境を用意する必要があるわけです。

x86-64用開発環境の選択

x86-64用の開発環境を作るにはいくつかの方法があります。一番簡単なのは既存のx86-64用のLinuxディストリビューションを導入することですが、それではあまり面白くないので、今回はx86環境上でx86-64用のバイナリを生成するクロスコンパイルの方法で開発環境を作りあげることにしました。

クロスコンパイルの場合、まず64ビット用のバイナリを生成できるコンパイラとアセンブラを32ビット環境上に用意し(クロスコンパイラ⁠⁠、そのコンパイラを使って64ビットネイティブな開発環境を作る、という流れになります。

この作業ではソフトウェアを構築していく順序が重要になるので、その手順について詳しく紹介している、CLFSプロジェクトのドキュメントを利用して作業することにしました。

CLFS(Cross Linux From Scratch)は、 Linux環境を全て手動でコンパイルして作りあげる方法を紹介しているLFSプロジェクトの一部で、 "Cross" の名前通り、x86環境上でMIPS用やPPC用のLinux環境をビルドする手順を詳細に紹介してくれています。

CLFSのx86-64用環境には、Pure64と呼ばれる64ビット専用の環境とmultilibと呼ばれる32ビット/64ビット兼用の環境がありますが、今回は、既存の32ビットバイナリもそのまま動かせるmultilib環境を構築することにしました。

CLFSのPure64環境とmultilib環境ではライブラリの配置等が異なり、Pure64環境では/libや/usr/libに64ビット用のライブラリを置き、32ビット用のライブラリは/lib32や/usr/lib32というディレクトリに置きます。この場合、64ビット用のバイナリはデフォルトのままビルドできますが、32ビット用のバイナリは/lib32や/usr/lib32を見に行くように設定してビルドする必要があり、既存の32ビット用のバイナリは作り直さなければなりません。

一方、multilib環境の場合、32ビット用のライブラリは従来通り/libや/usr/libに配置し、新しい64ビット用のライブラリを /lib64 や /usr/lib64 というディレクトリに置くようになっています。この場合、32ビット用のバイナリは再ビルドしなくても使えますが、新しく64ビット用のバイナリを作る際には、参照するライブラリを/lib64や/usr/lib64に指定する設定が必要になります。

32ビット用の資産を使わずに64ビット用Linuxを作りあげる場合はPure64の設定の方が合理的ですが、Plamoの場合、32ビット用のパッケージがそれなりに充実しているので、それらはそのまま流用できた方が便利です。そこでmultilibの方法で64ビット環境をビルドすることにしました。

CLFS x86-64 multilib環境の構築

CLFSの手順は、CLFSのサイトやそこで配布されているドキュメントファイルで詳しく紹介されています。ただ、それらのドキュメントは誰でも「この手順に従えばビルドできる」ことを重視していて、全体の流れ(その作業の意味)が見えにくくなっているように感じました。そこで、以下では各手順の意味に重点を置きながらドキュメントを辿り、CLFSの環境構築の大きな流れを紹介してみます。今回参照したCLFSのドキュメントはCLFS-TRUNK-SVN-20100828x86_64-Multilibで、ドキュメントの各章をII-3やIII-5として参照することにします。

大きくまとめるとCLFSの環境構築の流れは、

  1. 32ビット環境上で64ビット用バイナリをコンパイルできる環境(クロスコンパイル環境)を構築
  2. 1)のクロスコンパイル環境を使って、64ビット用開発環境を構築
  3. ブートするかchrootして 2)の環境をルートファイルシステムにした上で、64ビットなシステムを構築

という段階を踏みます。binutilsやGCCといった開発用のソフトウェアはこの各段階ごとにコンパイルし直さなければならないので、3回(GCCは最初にstatic link版を作るので計4回)コンパイルすることになります。

以下では、この各段階を CLFS のドキュメントを参照しながらもう少し詳しく見ていくことにします。

1) 32ビット環境上で64ビット用バイナリをコンパイルできる環境を構築

作業用パーティションと専用のアカウントの用意(II-2, II-4)

CLFSでは、既存のLinux環境上にCLFSの環境を構築しますが、元になるディストリビューションの影響を避けるために、専用のパーティションと必要最低限の環境変数のみを設定したユーザアカウント(clfs)を設け、構築作業はその環境で専用のユーザアカウントに su - clfs した上で実行します。以下ではこの専用パーティションを/clfsにマウントして作業を進めます。

環境構築に必要なソースコードやパッチファイルの入手先は一覧で整理されているので(II-3⁠⁠、それらをあらかじめダウンロードして用意しておきます。

32ビット用クロスコンパイル環境の構築(III-5)

まず、64ビットのバイナリを出力できる32ビット用のコンパイル環境(クロスコンパイル環境)を構築します。そのためにはbinutilsやGCCをクロスコンパイル用にビルドすることになります。この際GCCのconfigureに与えるオプションは

  --host=i586-cross-linux-gnu --build=i586-cross-linux-gnu --target=x86_64-unknown-linux-gnu

となります。この指定は、⁠32ビット環境用に」⁠--host=i586-cross-linux-gnu⁠⁠、⁠64ビット用のバイナリを出力する」⁠--target=x86_64-unknown-linux-gnu)バイナリを、⁠32ビット環境上でビルドする」⁠--build=i586-cross-linux-gnu)という意味になります。

こうやって作ったバイナリは、作業用パーティションの/cross-tools/以下に集めて、次の段階はこのディレクトリにあるバイナリで作業することになります。/clfs ディレクトリにマウントされた/cross-tools/bin/ には下記のようなコマンドがインストールされ、それらは32ビット環境で動作するようになっています。

% ls /clfs/cross-tools/bin
cloog*                               x86_64-unknown-linux-gnu-c++filt*    x86_64-unknown-linux-gnu-nm*
file*                                x86_64-unknown-linux-gnu-cpp*        x86_64-unknown-linux-gnu-objcopy*
ppl-config*                          x86_64-unknown-linux-gnu-g++*        x86_64-unknown-linux-gnu-objdump*
 ..

% file /clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc
/clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.0.0, not stripped

% ldd /clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc
        linux-gate.so.1 =>  (0xffffe000)
        libc.so.6 => /lib/libc.so.6 (0xf7577000)
        /lib/ld-linux.so.2 (0xf76ea000)

一方、このgccコマンドのターゲットはx86_64になっており、このコンパイラでソースコードをコンパイルすると64ビット用のバイナリが得られます。

% /clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc -v
Using built-in specs.
COLLECT_GCC=/clfs/cross-tools/bin/x86_64-unknown-linux-gnu-gcc
COLLECT_LTO_WRAPPER=/clfs/cross-tools/bin/../libexec/gcc/x86_64-unknown-linux-gnu/4.5.1/lto-wrapper
Target: x86_64-unknown-linux-gnu
...
Thread model: posix
gcc version 4.5.1 (GCC) 

この段階でビルドするソフトウェアは、カーネルのヘッダファイルとfileコマンド、ncursesのうちterminfo情報をコンパイルするためのticコマンド、後はアセンブラやリンカ等が含まれる binutils と C の標準ライブラリ(libc⁠⁠、CコンパイラであるGCCとGCCを作るために必要な数値演算用やループ最適化用ライブラリ群(GMP, MPFR, MPC, PPL, ClooG-PPL)だけです。

なお、この段階では64ビット用の共有ライブラリが使えないので、いったんstatic link版のGCCをビルドしておき、そのGCCで64ビット用のlibcライブラリをビルドした上で、再度dynamic link版のGCCをビルドする、という手順も入っています。

2) 1)の環境を使って、64ビットな開発専用環境を構築

64ビットな開発専用環境の構築(IV-6)

先に作ったクロスコンパイル用の環境(/clfs/cross-tools)を使って、/clfs/tools/ 以下に64ビット版の開発環境を構築していきます。この際には /clfs/cross-tools/bin 以下をPATHの先頭に指定し、GCCやAS、LDなどは /clfs/cross-tools/以下のクロスコンパイル用ツールを明示してビルドしていきます。

この過程で再度binutilsやGCCを64ビット用にビルドしますが、その際のコンパイルオプションはこうなります。

 --host=x86_64-unknown-linux-gnu  --build=i586-cross-linux-gnu --target=x86_64-unknown-linux-gnu

先のクロスコンパイラ用の指定と比べると、--host の指定がx86-64用になっていて、この指定では「64ビット環境用に(--host=x86_64-unknown-linux-gnu)⁠⁠、⁠64ビット用のバイナリを出力する(--target=x86_64-unknown-linux-gnu)」バイナリを、⁠32ビット環境上でビルドする(--build=i586-cross-linux-gnu)」という意味になります。

CLFSの手順では、この段階でbashやbisonを始め、29種類のソフトウェアをビルド、インストールするようになっており、一つ前の手順でビルドした32ビット/64ビット用のlibcと含めて、30種類のソフトウェアで必要最小限の開発環境を構成しています。この段階でインストールされるソフトウェアは以下の通りになっています。

GMP-5.0.1, MPFR-3.0.0, MPC-0.8.2, PPL-0.10.2, CLooG-PPL-0.15.9
Zlib-1.2.5, Binutils-2.20.1, GCC-4.5.1, Ncurses-5.7, Bash-4.1
Bison-2.4.2, Bzip2-1.0.5, Coreutils-8.5, Diffutils-3.0, Findutils-4.4.2
File-5.04, Flex-2.5.35, Gawk-3.1.8, Gettext-0.18.1.1, Grep-2.6.3, Gzip-1.4
M4-1.4.14, Make-3.82, Patch-2.6, Sed-4.2.1, Tar-1.23, Texinfo-4.13a, Vim-7.2
XZ Utils-4.999.9beta

この段階で作成した/clfs/tools/binにインストールされるバイナリ群は64ビット用になっていて、32ビット環境からはライブラリのリンク状況等を確認できません。

% ls /clfs/tools/bin/
[*               dd*          infocmp*          nl*             s2p*           uncompress*
a2p*             ddate*       infokey*          nm*             script*        unexpand*
addr2line*       df*          infotocap@        nohup*          scriptreplay*  uniq*
...

% file /clfs/tools/bin/df
/clfs/tools/bin/df: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked (uses shared libs), for GNU/Linux 2.6.0, not stripped

% ldd /clfs/loop/tools/bin/df
   not a dynamic executable

3) ブートするかchrootして 2)の環境をルートファイルシステムにした上で、64ビットなシステムを構築

次に、先ほど作った必要最小限の64ビット用開発環境で、実際に運用できる64ビットなLinuxシステムを構築していきます。その際には、既存の32ビット環境の影響を無くすために、新しく作った環境でブートするか、chrootでルートファイルシステムを切り替えることになります。CLFSでは両方の方法とも解説してあり、ブートする際はカーネルやsysvinitのビルド方法を(IV-7⁠⁠、chrootする際にはそのための設定方法などを詳述(IV-8)しています

カーネルを含めた必要なツールを用意してこのパーティションから開発用システムを起動すれば、既存のLinux環境から完全に独立した環境が用意できます。しかし、その環境ではコンソールしか利用できず、CLFSの手順をブラウザで表示しながら、指示されたコマンドをカット&ペーストして実行するような操作もできなくなるので、今回はchrootで環境を切り替えることにしました。

当初はPlamo-4.73のデフォルトの環境からchrootで目的のディレクトリに移動しようとしましたが、bashが実行形式エラーになってしまいます。

% sudo chroot /clfs  /tools/bin/bash
chroot: failed to run command `/tools/bin/bash': 実行形式エラー

「実行形式エラー」なんてのは見たことが無いなぁ、としばらく悩んだのですが、考えてみればこれは当然の結果で、現在動いている Plamo-4.73 のデフォルトカーネルはi586用になっているため、x86-64のバイナリは理解できません。64ビットなバイナリを実行できるロングモードに入るには、カーネル自体がx86-64用にビルドされていなければいけません。

それでは、と/usr/src/linuxにあるカーネルのソースコードに対して、ARCH=x86_64を明示してビルドしてみましたが、これもエラーになってしまいました。

# make ARCH=x86_64
scripts/kconfig/conf -s arch/x86/Kconfig
  CHK     include/linux/version.h
  CHK     include/linux/utsrelease.h
  SYMLINK include/asm -> include/asm-x86
  HOSTCC  scripts/basic/docproc
  CC      kernel/bounds.s
kernel/bounds.c:1: error: code model 'kernel' not supported in the 32 bit mode
kernel/bounds.c:1: sorry, unimplemented: 64-bit mode not compiled in
make[1]: *** [kernel/bounds.s] エラー 1
make: *** [prepare0] エラー 2

このエラーメッセージも見たことが無いものだったのでしばらく悩みましたが、x86-64用にカーネルをビルドするためには、x86-64用のバイナリを出力できる環境が必要になるわけで、カーネルソース一式を構築中のクロスコンパイル環境に移して、CLFSが指示しているようにCROSS_COMPILEオプションを指定してビルドして、無事x86-64用のカーネルが完成しました。

$ make ARCH=x86_64 CROSS_COMPILE=${CLFS_TARGET}- 
  CHK     include/linux/version.h
  CHK     include/generated/utsrelease.h
  CC      kernel/bounds.s
  GEN     include/generated/bounds.h
  CC      arch/x86/kernel/asm-offsets.s
  ...

 IHEX2FW firmware/keyspan_pda/xircom_pgs.fw
 H16TOFW firmware/vicam/firmware.fw

$ file vmlinux
vmlinux: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

このカーネルではIA32 Emulation 機能を有効にしているので、ロングモードの互換モードを使って、まだ64ビット化していない Plamo-4.73 ベースの環境でも機能するはずです。そう考えて、新しくビルドしたカーネルとモジュールドライバを元の Plamo-4.73 環境に持っていって、新しくビルドしたカーネルから起動したところ、無事64ビット用カーネルで動作し、64ビット環境に chroot できるようになりました。

$ uname -a
Linux Corei7 2.6.35.7-plamo64 #1 SMP PREEMPT Tue Oct 26 00:35:06 JST 2010 x86_64 GNU/Linux
# chroot /clfs /tools/bin/bash
bash-4.1# ls /
bin   cross-tools  etc	 lib	lost+found  mnt  proc  sbin	srv  tmp    usr
boot  dev	   home  lib64	media	    opt  root  sources	sys  tools  var

bash-4.1# file /tools/bin/bash
/tools/bin/bash: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), dynamically linked (usesshared libs), for GNU/Linux 2.6.0, not stripped
bash-4.1# ldd /tools/bin/bash
        linux-vdso.so.1 =>  (0x00007fffd416f000)
        libncurses.so.5 => /tools/lib64/libncurses.so.5 (0x00007fb3c9616000)
        libdl.so.2 => /tools/lib64/libdl.so.2 (0x00007fb3c9412000)
        libgcc_s.so.1 => /tools/lib64/libgcc_s.so.1 (0x00007fb3c91fd000)
        libc.so.6 => /tools/lib64/libc.so.6 (0x00007fb3c8e93000)
        /tools/lib64/ld-linux-x86-64.so.2 (0x00007fb3c9865000)

元の32ビット環境では表示できなかったlddの出力も、新しくビルドした/tools/lib64以下のライブラリを指すと共に、それぞれのライブラリのエントリを示すアドレスが64ビット幅に拡張されていることが分かるでしょう。

次は、この64ビットの開発環境を使って、実際に運用可能な小規模Linux環境に必要なソフトウェアをコンパイルしていくことになりますが、だいぶ長くなったので今回はこのあたりまでにしておきましょう。

おすすめ記事

記事・ニュース一覧