/ «2003-10-07 (Tue) ^ 2003-10-09 (Thu)» ?
   西田 亙の本:GNU 開発ツール -- hello.c から a.out が誕生するまで --

Categories Books | Hard | Hardware | Linux | MCU | Misc | Publish | Radio | Repository | Thoughts | Time | UNIX | Writing | プロフィール


2003-10-08 (Wed)

[Writing] GCC プログラミング工房 書籍 Vol.1 第1章

GCC プログラミング工房書籍化にあたり、第1章をどうスタートさせるか、まる1ヵ月間悩んでしまった。連載を書籍化する場合、基本的には既存原稿ベースで進めるらしいが、第1回は2年も前の原稿である。私自身もその間に成長しているので、今読み直すとあちこちに不十分な点が目について仕方がない。

ということで、わずかに第1回の痕跡を残しながら、第1章は大幅加筆することにした(ちなみに渡辺編集長は最初からお見通しだった模様・・)。結果、完成した原稿は特大ホームラン級の内容に膨らんでしまった。1章だけで、全体の1/4以上のページ数書いてどうすんだよ>我。

う〜〜ん、本当にどうなるんだろう。心配になってきた・・と思いつつ、編集長に送信。いつもいつも、すみませんです。

しかしながら、内容は自分でも納得できるレベルに仕上がった。今回重要視したのは、binutils/GCC を使ったビルドの全体像である。Cプログラムのビルドは、「前処理・コンパイル・アセンブル・リンク」の4工程で構成されるが、gcc hello.c を実行した際に、舞台裏で一体何が起きているのか?その一部始終を白日の下にさらしつつ、ひとつひとつの作業の「意味」を解説できたと思う。

4工程の中でも、リンクは最大の難所であり、連載中でもほとんど触れていなかったのだが、今回この鬼門に真っ向から挑んでみた。正直、これは非常に手強い強敵だったが、おかげで collect2 コマンドにおける、crt (C RunTime startup)、ライブラリー指定の技術背景を、初めて明らかにすることができた。

その一部を簡単にご紹介しよう。まずはお決まりの hello.c である。

#include <stdio.h>     // printf()

#define MESSAGE        "Hello, world!?n"

int main() {
  printf("%s", MESSAGE);
  return 123;
 }

どうということはないソースリストである。これを普通にビルドすると・・

$ gcc -o hello hello.c
$ ./hello ; echo $?
Hello, world!
123

となる。当たり前である。いや、「これまでは」当たり前のこととされてきた。しかし、この「当たり前」の中にこそ、開発ツールを使いこなすためのノウハウが山ほど隠されているのである。ここで、-v オプションを追加して同じコマンドを実行してほしい。

$ gcc -o hello hello.c -v
Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/specs
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man
--infodir=/usr/share/info --enable-shared --enable-threads=posix
--disable-checking --with-system-zlib --enable-__cxa_atexit
--host=i386-redhat-linux
Thread model: posix
gcc version 3.2.2 20030222 (Red Hat Linux 3.2.2-5)
 /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/cc1 -lang-c -v -D__GNUC__=3
-D__GNUC_MINOR__=2 -D__GNUC_PATCHLEVEL__=2 -D__GXX_ABI_VERSION=102
-D__ELF__ -Dunix -D__gnu_linux__ -Dlinux -D__ELF__ -D__unix__
-D__gnu_linux__ -D__linux__ -D__unix -D__linux -Asystem= posix -D__NO_INLINE__
-D__STDC_HOSTED__=1 -Acpu=i386 -Amachine=i386 -Di386 -D__i386 -D__i386__
-D__tune_i386__ hello.c -quiet -dumpbase hello.c -version -o /tmp/cctU6LlJ.s
GNU CPP version 3.2.2 20030222 (Red Hat Linux 3.2.2-5) (cpplib) (i386 Linux/ELF)
GNU C version 3.2.2 20030222 (Red Hat Linux 3.2.2-5) (i386-redhat-linux)
compiled by GNU C version 3.2.2 20030222 (Red Hat Linux 3.2.2-5).
ignoring nonexistent directory "/usr/i386-redhat-linux/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/local/include
 /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/include
 /usr/include
End of search list.
 as -V -Qy -o /tmp/cccGbXzm.o /tmp/cctU6LlJ.s
GNU assembler version 2.13.90.0.18 (i386-redhat-linux) using BFD version 2.13.90.0.18 20030206
/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/collect2 --eh-frame-hdr -m elf_i386
-dynamic-linker /lib/ld-linux.so.2 -o hello /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/../../../crt1.o
/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/../../../crti.o /usr/lib/gcc-lib/i3 86-redhat-linux/3.2.2/crtbegin.o
-L/usr/lib/gcc-lib/i386-redhat-linux/3.2.2 -L/usr/lib/gcc-lib/i386-redhat-linux/3.2.2/../../.. /tmp/cccGbXzm.o
-lgcc -lgcc_eh -lc -lgcc -lgcc_eh /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/crtend.o
/usr/lib/gcc-lib/i386-redha t-linux/3.2.2/../../../crtn.o

-v (Verbose) は連載中でも何度も登場したオプションだが、gcc ドライバーによるコマンドの実行状況を表示するためのものだ。-v を指定することで、私達は gcc の裏側で一体何が起きているのかを、初めて知ることが可能になる。

それにしても、これはまさに「呪文」である。訳が分からない・・というのが、ほとんどの人の反応だろう。だからこそ、このダンプリストに隠された「本質」が、現在に至るまで封印されてきたのかもしれない。

さて、この暗号を丁寧に解き明かしていくと、その本質は以下のようになる。

  • 前処理
$ `gcc --print-prog-name=cpp0` hello.c > hello.i
  • コンパイル
$ `gcc --print-prog-name=cc1` hello.i
  • アセンブル
$ as -o hello.o hello.s
  • リンク
$ ld -o hello /usr/lib/crt1.o /usr/lib/crti.o hello.o /usr/lib/crtn.o -lc -dynamic-linker /lib/ld-linux.so.2

驚くほどシンプルになったが、この4行で gcc が出力したコードと同じものが出力される。「ビルドって、シンプルじゃん!」本書を手にした読者の方々が、この境地に達することができるよう、さらに精進しよう。

連載フォローアップ

skyfree.org での連載フォローアップが停止したままである。申し訳ございません。tDiary の手軽さに慣れてしまうと、重戦車のような GoLive を起動して HTML を編集する生活にはもう戻れない・・。ということで、tDiary base でサポートページを再開する予定。