uchan note

プログラミングや電子工作の話題を書きます

Windows で GCC 6.2.0 をビルドするメモ

f:id:uchan_nos:20161208235327j:plainこれは 自作 OS Advent Calendar 2016 の 8 日目の記事です。

概要

現時点で最新バージョンの GCC 6.2.0 を Windows 上でビルドするメモです。 このメモでは、i386-elf をターゲットにした GCC を作ります。 コンパイラ本体のみが対象で、標準ライブラリ等はビルドしません。 (将来、標準ライブラリも自作 OS 向けにビルドできるようになったら、また記事を書こうかと思います。っていつだよ!)

Windows で動く x86 ターゲットな GCC と言えば CygwinMinGW が有名です。 このメモでも MinGW を使って GCC をビルドします。

しかし MinGW に付属の GCC にはちょっとした問題があったりします。

  • Windows 上で動くバイナリの生成に特化していて、リンカ LD がフラットバイナリを吐けない(PE バイナリは吐ける)
  • GCC のバージョンが最新ではない

後者は我慢することもできますが、前者はいかんともしがたい問題です。 フラットバイナリ(ベタバイナリ)を生成することができないと、独自フォーマットの実行可能ファイルを生成することができません。

例えば 30 日でできる!OS 自作入門 の「はりぼて OS」で登場する .hrb 形式は完全に独自のフォーマットで、標準的には著者(川合氏)が製作した bim2hrb というソフトウェアで生成することになっています。 bim2hrb を使わずに GNU リンカ LD で .hrb 形式の実行可能ファイルを作ることもできるのですが、そのためにはフラットバイナリを生成できる LD が必要なのです。

ソースコードの入手

以下、MinGW がすでにインストールされていることを前提として話を進めます。 Windows のくせに、手順が Linux っぽいのはそのためです。

必要なのは GNU BinutilsGCC に加え、GCC が依存するライブラリ GNU MP、GNU MPFR、GNU MPC のソースコードです。 作業ディレクトリは /c/Users/uchan/gcc-6.2.0 として進めますが、任意のディレクトリで大丈夫です。

$ mkdir /c/Users/uchan/gcc-6.2.0
$ cd /c/Users/uchan/gcc-6.2.0
$ wget wget http://ftp.gnu.org/gnu/binutils/binutils-2.27.tar.bz2
$ wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-6.2.0/gcc-6.2.0.tar.bz2
$ wget http://ftp.tsukuba.wide.ad.jp/software/gcc/infrastructure/gmp-6.1.0.tar.bz2
$ wget http://ftp.tsukuba.wide.ad.jp/software/gcc/infrastructure/mpfr-3.1.4.tar.bz2
$ wget http://ftp.tsukuba.wide.ad.jp/software/gcc/infrastructure/mpc-1.0.3.tar.gz

展開と配置

ソースコードを入手したら展開して、GCC の依存ライブラリを GCC ディレクトリに移動しておきます。

$ cd /c/Users/uchan/gcc-6.2.0
$ tar xf binutils-2.27.tar.bz2
$ tar xf gcc-6.2.0.tar.bz2

特に GCC は巨大なのでかなり待たされます。コーヒーでも飲んで休憩しましょう。

GCC の依存ライブラリは、GCC のコードツリーの直下にバージョン番号を取り除いたディレクトリ名(gmp mpfr mpc)で配置しておくと、GCC のビルド時に自動的に認識されます。 Linux 等のシンボリックリンクが使える環境では、ディレクトリを移動せずリンクを張るだけでもいいです。

$ tar xf gmp-6.1.0.tar.bz2
$ mv gmp-6.1.0 gcc-6.2.0/gmp
$ tar xf mpfr-3.1.4.tar.bz2
$ mv mpfr-3.1.4 gcc-6.2.0/mpfr
$ tar xf mpc-1.0.3.tar.gz
$ mv mpc-1.0.3 gcc-6.2.0/mpc

GNU Binutils

GCC に先立って GNU Binutils をビルドしてインストールします。 次の設定でビルドすることにします。

設定項目 設定値
インストール先 /c/Users/uchan/gcc-6.2.0/i386-elf-gcc
ターゲットアーキテクチャ i386-elf
実行ファイルのプレフィクス なし(gcc.exe とか ld.exe などになる)
日本語化 しない

configure して make します。

$ export PATH=/g/MinGW/bin:$PATH
$ cd /c/Users/uchan/gcc-6.2.0
$ mkdir build-binutils
$ cd build-binutils
$ ../binutils-2.27/configure --prefix=/c/Users/uchan/gcc-6.2.0/i386-elf-gcc/ --program-prefix= --target=i386-elf --disable-nls
$ make -j4
$ make install

MinGW の MSYS 上でビルド作業をする場合、ネイティブの GCC をパスの先頭に追加しておかないとビルドがうまくいきませんでした。 そのため、先頭で export PATH=/c/MinGW/bin:$PATH を実行します。 実際のパスは皆さんの MinGW のパスに修正してくださいね。

GCC

いよいよ GCC をビルドしましょう。 インストール先などは GNU Binutils と同じ設定にします。 その他の設定は次のようにしました。

設定項目 設定値
プログラミング言語 C と C++
ターゲットシステム用のサポートライブラリ ビルドしない

configure して make します。

$ export PATH=/c/Users/uchan/gcc-6.2.0/i386-elf-gcc/bin:$PATH
$ cd /c/Users/uchan/gcc-6.2.0
$ mkdir build-gcc
$ cd build-gcc
$ ../gcc-6.2.0/configure --prefix=/c/Users/uchan/gcc-6.2.0/i386-elf-gcc/ --program-prefix= --target=i386-elf --disable-nls --enable-languages=c,c++ --disable-multilib
$ make all-gcc
$ make install-gcc

実際のところ、--disable-multilib を指定しないとビルドできないかは実験していません。 ビルドターゲットに all-gcc を指定すれば、もしかしたら --disable-multilib は不要かもしれません。

make のときに、分割ビルドのための -j オプションを付けたくなるかもしれません。 しかし、実験したところ -j オプションを付けるとビルドの途中でフリーズし、最後までビルドできませんでした。 そこで、遅くはなりますが分割ビルドはしない手順としました。

確認

さて、正しくインストールされたか確認を込めて、Hello World バイナリを作ってみます。 実験用のソースコードを用意します。

$ cat main.cpp
int main()
{
    int puts(const char*);
    [](const char* s){ puts(s); }("hello, world!\n");
}

そうしたら、先ほどインストールした GCC までパスを通します。 ビルドした GCCMinGW の DLL に依存しているので MinGW/bin にもパスを通さないと起動できません。

$ export PATH=/c/Users/uchan/gcc-6.2.0/i386-elf-gcc/bin:/c/MinGW/bin:$PATH

最後にビルドします。 GCC 6 からはデフォルトの言語規格が -std=gnu++14 ですので、オプション無しでラムダ式などの先進的な言語機能を使えます。

$ g++ -c main.cpp

readelfobjdump 等のツールも GNU Binutils をインストールしたおかげで導入されています。 バイナリの中身を見てみましょう。

$ readelf -a main.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
...

おお、ちゃんと ELF バイナリが生成できています! 逆アセンブラは動くでしょうか…?

$ objdump -d main.o

main.o:     file format elf32-i386


Disassembly of section .text:

00000000 <_ZZ4mainENKUlPKcE_clES0_>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 08                sub    $0x8,%esp
   6:   83 ec 0c                sub    $0xc,%esp
   9:   ff 75 0c                pushl  0xc(%ebp)

大丈夫ですね。完璧です。

以上で、アドベントカレンダー 8 日目は終了です!