2009-01-25

"GNU開発ツール" 西田亙 著

昨夜から、ポールジローを飲み続ける。この季節はストーブにおでんを仕込むのが慣習である。そして、ベランダから眺める雪景色はなかなかいい。寒いとはいえ、ここまで積もるのも珍しい。おまけに、深夜は晴れていて、灯りが遠くの山まで通るのが幻想的だ。今日もその余韻にひたりながら、真昼間っからブランデーにひたる。そして、一本空いてしまった。気分良くなんとなく本棚を眺めていると、一冊の本に目が留まる。重厚なハードカバーは、味わい深い洋酒の解説書を思わせる。3年ほど前に読んだ本だが、なんとなく読み返したくなった。寒い季節は、なぜか?感傷的にさせやがる。

本書は2006年に著者西田亙氏により自費出版されたもので、当時、初版を購入した。こうした個人で活動されている方の試みは、アル中ハイマーに良い(酔い)刺激を与えてくれる。本書は、アル中ハイマー世代には懐かしい香りがする。おいらが初めて買ったコンピュータはFM7、20年以上前、媒体はカセットテープだった。通常のオーディオ機器でプログラムがコピーできた時代である。録音レベルの調節を失敗すると暴走したりもした。社会人になって自己啓発と称して購入したのが98互換機だった。その時、虚しかったのは、ローンの残高が次々に登場する新型マシンの価格を追い越すことだった。記憶装置は5インチのフロッピーディスクが主流で、まだ3.5インチの信頼性に疑いを持っていた。おいらは、プロセッサの設計やCPU周辺回路の設計などに携わっていた。ターゲットCPUは、Z80系が主流で、ワンチップマイコンという言葉が流行り出した時代である。回路検証のために、リアルタイムモニタとでも言おうか、OSもどきを自作せざるを得なかった。LSIの検証のためにブレッドボードを製作していたが、今では考えられないほどの労力をかけていた。それでも、小学生が科学するような作る楽しさがあったものである。近年コンピュータの処理能力と記憶領域の進歩は凄まじいものがある。高機能化したOSや高速なネットワーク環境によって、ハードウェアやソフトウェアはますます複雑化し、もはや、システム全体を個人で把握することはできない。強固なOSは、かつてマイコンが許していた内部操作さえも拒絶する。こうした流れは、コンピュータの基礎であるプリミティブな部分を軽視する傾向へと向かわせる。社会が便利で豊かになるのは歓迎であるが、逆に、複雑化した社会では基礎を学ぶ場が難しいとも言える。著者は、入門者を基本の学べる場へと導くために、「Computer Architecture Series」を誕生させたという。本書はシリーズの第一弾で、その原点をC言語の入門書で御馴染みのhello.cに求めている。

ところで、物事の真理を探究する欲求は、よりプリミティブな方向へと向かうのだろうか?真の芸術は原点を自然に求め、文学は自らの精神を曝け出すことを鉄則とする。哲学は、人生の意義を追求した挙句、自らの存在をも疑わせる。科学の探求はあらゆる物質の基本構成へと向かわせ、数学の原理を素数に求め、アルゴリズムの基本原理をランダム性に求める。あらゆる探求がプリミティブな方向へと向かうのは、自然の理なのかもしれない。笑いの感性も例外ではなく、鉛筆が転がっただけで笑えるような感覚にこそ、本物の笑いがある。この感性を習得するには女子高生に弟子入りするのが一番だ。ということで、、アル中ハイマーは逆援助交際の相手を募集している。

本書は、GNU開発ツールを使って、ソースプログラムから生成される実行コマンドの正体を暴いていく。それは、gccが裏で何をしているかを -vプションで解明していくことである。GNU開発ツールは、gccパッケージと、binutilsパッケージから構成される。binutils由来のリンカがldである。gccの役割は、プリプロセッサ、コンパイラ、アセンブラ、リンカといった4つのビルド作業の調整役である。おいらがコンピュータに触れた頃は、こうした手順が自然と見えたものだが、今ではほとんど自動化されあまり意識する必要がない。しかし、トラブルが発生した時、こうした情報を知っていて損はない。本書で一番盛り上がるのは、静的リンクと動的リンクの違いを手動で試す場面であろう。いかにgccが便利な社会を提供しているかが実感できる。静的リンクで作成されたコマンドは、それ自体で完結しているため、ディスク障害などで一部が破壊されても、自身をメモリ上にロードできれば起動することができる。一方、動的リンクは、elfローダと共有ライブラリに依存するため、いずれにかに障害が発生すると、全てのコマンドが起動不能となる。OpenBSDでは、その危険性を考慮して、/sbin, /binに格納するコマンドは全て静的リンクで作成されているという。一方、linuxでは、initやbashが動的リンクとなっている。
ちなみに、fedora9ではこうなっている。
----------------------------------------------------
$ file /sbin/init
/sbin/init: ELF 32-bit LSB executable, ..., dynamically linked...
$ file /bin/bash
/bin/bash: ELF 32-bit LSB executable, ...., dynamically linked...
----------------------------------------------------

以下、なんとなく気になるところをメモっておこう。

1. /etc/passwdの管理
/etc/passwdの管理で、ログイン拒否やクラッキング防止方法を紹介している。シェルの起動の代わりに、Linuxでは、/bin/falseを活用する。OpenBSDでは、/sbin/nologinを活用する。

2. gcc -v hello.c
gccは、裏で6つの処理を行っていることが分かる。
(1) specsファイルの読み込み。
(2) ビルド時のconfigureオプションの表示
(3) プリプロセス(以前はcpp0が担当していたが、ver3からcc1が兼任)
(4) コンパイル(cc1)
(5) アセンブル(as)
(6) リンク(ld)
specsは、ビルド作業の自動実行を制御するための設定ファイル。ちなみに、-save-tempsオプションを使えば、中間ファイルが保存できる。fileコマンドで、オブジェクトファイル(hello.o)が、elf形式による再配置可能であることが分かる。
----------------------------------------------------
hello.c: ASCII C program text
hello.i: ISO-8859 C program text
hello.o: ELF 32-bit LSB relocatable, ...
hello.s: ASCII assembler program text
a.out: ELF 32-bit LSB executable,... dynamically linked...
----------------------------------------------------

3. プリプロセス
自前のライブラリをインクルードファイルで管理することはよくある。そして、オーバーライドなどによって、プロトタイプ宣言の微妙な違いによって、コンパイルエラーや、奇妙な動作で悩まされることがある。ヘッダファイルの探索順によっては、思惑と違うファイルを参照することもある。ローカルファイルの管理ミスで、システムと衝突したりすると頭が痛い。また、定義済みのマクロで嵌ることもある。例えば、文字型のビット長などは、未定義だと思っても正常にビルドできたりする。
次のコマンドで定義内容が確認できる。
cpp -dM < /dev/null

4. アセンブラ
asciiコードで表示(hexdump)と、実行コードからの逆アセンブル(objdump)の紹介。
objdump -j .rodata -s hello.o
# -j は解析対象セクション -sと組み合わせてセクション内容を表示。
readelf -S hello.o
# セクション一覧表示

5. 静的リンク
実行可能ファイルにはプログラムヘッダが含まれる。プログラムヘッダの内容は、readelf -l a.outで確認できる。静的Cライブラリの実体は、libc.a (Archive)。gccランタイムライブラリ(libgcc.a)も必要。
libc.aに含まれるオブジェクトファイル群の表示は以下のようにやる。
$ ar t /usr/lib/libc.a

_startシンボル未定義による致命的エラーは、以下のファイルをリンクする必要がある。5つのcrt(C Runtime start up)ファイルは、プログラム起動時に呼ばれる初期化コードやコンストラクタ、デコンストラクタを格納する。(crt1.o, crti.o, crth.o, crtbegin.o, crtend.o)

6. 動的リンク
動的リンクは、crtファイルや外部ライブラリとリンクする点は静的リンクと同じであるが、共有ライブラリを使用し、プログラム起動時にelfローダを必要とするところが違う。libcの共有ライブラリ版は libc.so (Share Object)。
----------------------------------------------------------
$ cat /usr/lib/libc.so
/* GNU ld script
Use the shared library, but some functions are only in
the static library, so try that secondarily. */
OUTPUT_FORMAT(elf32-i386)
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a AS_NEEDED ( /lib/ld-linux.so.2 ) )
----------------------------------------------------------
ライブラリ本体は、GROUPに記載されるlib/libc.so.6にある。
----------------------------------------------------------
-rw-r--r-- 1 root root 3105844 2008-07-17 07:14 /usr/lib/libc.a
lrwxrwxrwx 1 root root 11 2008-08-05 18:25 /lib/libc.so.6 -> libc-2.8.so
-rwxr-xr-x 1 root root 1758448 2008-07-17 07:30 /lib/libc-2.8.so
----------------------------------------------------------
libc.aの3MBに比べて、共有ライブラリ(libc-2.8.so)1.7MBと小さいのは、シンボル情報が削除されているため。よって、シンボル情報を得るためにnmコマンドでは解析できないので、readelf -sを使う。
$ readelf -s /lib/libc-2.8.so

リンカの-lcオプションの解釈順
-lc -> libc.so -> /usr/lib/libc.so -> /lib/libc.so.6 -> /lib/libc-2.8.so

elfローダ(ld.so/ld-linux.so)は、ダイナミック・リンカローダとも呼ばれる。elfローダを指定するには、-dynamic-linkerオプションを使用する。
ld -dynamic-linker /lib/ld-linux.so.2 ...
共有ライブラリの格納情報を記録しているキャッシュは、/etc/ld.so.cache
テキスト形式で確認するには、/sbin/ldconfig -p

0 コメント:

コメントを投稿