The Linux GCC HOWTO

Daniel Barlow <daniel.barlow@linux.org>

v1.17, 28 February 1996

中野武雄 <nakano@apm.seikei.ac.jp>

v1.17j2, 29 January 1997
この文書では linux の下で GNU C コンパイラとコンパイルに必要なライブラ リを設定する方法について記しています。またこれらの環境を用いたプログラ ムのコンパイル、リンク、実行およびデバッグについても概要を述べます。こ の文書の内容の多くは Mitch D'Souza の GCC-FAQ から得たものであり、この 文書は GCC-FAQ の代わりとなるものです。また ELF-HOWTO からも多くの題材 を採用しており、同じくこの文書は ELF-HOWTO の大部分を置きかえるもので す [訳注:Daniel さんは ELF-HOWTO の著者でもあります。] この版は公式リリースとしては最初のものになります(バージョン番号からは そうは見えないかもしれませんが、これは RCS が自動的につけているものな んです)。ご意見ご感想をお待ちしています。

注意: この文書はかなり以前に書かれたものなので、 いまどきの Linux 環境にはあてはまらない箇所があります。 (JF Project)

1. はじめに

2. 入手先

3. GCC のインストールと設定

4. 移植とコンパイル

5. デバッグとプロファイリング

6. リンク

7. ダイナミックロード(Dynamic Loading)

8. 開発者に連絡を取るには

9. その他

10. 索引


1. はじめに

1.1 ELF 対 a.out

現在 Linux におけるプログラム開発環境はどんどん変化しています。 Linux で実行できるバイナリには二種類の形式が在り、システムの設定 によってこれらを使い分けることが出来ます。この HOWTO を読む前に、読者 のシステムではどちらを使っているかを調べておくと良いでしょう。

さて、どうすればわかるでしょうか?答えは `file' コマンドを使う、です (file /bin/bash のようにします)。ELF の実行ファイルなら 「ELF」 という文字列がメッセージにあらわれますし、 a.out の実行ファ イルなら「Linux/i386」という文字列を含む表示が出ます。

ELF と a.out の違いについてはこの文書の後ほどの部分で詳細に解説します。 ELF の方が新しいフォーマットであり、一般にはより良いものとされています。

1.2 この文書について

著作権などの法的な件に関しては、この文書の最後に示してあり ます。また Usenet で間抜けな質問をしたり、バグでないことをバグだと報告 して C 言語についての無知をさらしたり、ガムを噛みながら歩きまわったり することに関する法的な警告も示してあります。

訳注:言うだけ野暮ですが、後の方のは冗談です :-)

1.3 フォントについて

この文書を Posstscript、dvi、html のどれかでお読みの方は、テキスト 版を読んでいる方より少々多くのフォントを見ることになります。ファイル名、 コマンド名、コマンド出力、ソースコードなどからの引用は typewriter フォントで、その他の強調すべき内容は emphasized フォントで示します。

便利な索引も用意しました。 dvi と Postscript では index の数字は章や節 の番号です。HTML では数字は単に出てきた順番ですが、クリックによってそ の内容に飛ぶことが出来ます。テキスト版では本当に「単なる」順番になって しまいます。他の形式で見て下さい!

例には Bourne シェルを用いています。 C シェルをお使いの方は、

$ FOO=bar; export FOO
となっているところを
% setenv FOO bar
と読み変えて下さい。

プロンプトに $ でなく # を用いているところでは、その コマンドは root で実行する必要があることを示しています。もちろんこの文 書での例を実行した結果にたいする責任は私はとれません。幸運を :-)

2. 入手先

2.1 この文書

この文書は Linux HOWTO シリーズの一つですので、 Linux HOWTO が置か れているところ(例えば http://sunsite.unc.edu/pub/linux/docs/HOWTO/ )ならばどこにでもあるはずです。 HTML 版(おそらくは多少新しい版)をお望みでしたら http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html をどうぞ。

2.2 他の文書

gcc に関する公式な文書は配布ソース(後述)に texinfo 形式およ び *.info ファイルとして入っています。CD-ROM がある方、充分高 速なネットワークを利用できる(あるいは辛抱強い)方は配布パッケージを手 に入れて untar し、適宜必要なものを /usr/info にコピーするだけ です。以上のいずれも不可能な方は、 tsx-11 でもこの文書を入手できます。ただしこちらは常に最新版であるとは限りませ ん。

libc に関する情報源は二つあります。 GNU libc には info ファイルが付属 しており、これには Linux の libc の内容が(stdio を除いて)比較的正確 に記述されています。また man ページ のアーカイブは Linux 向けに書かれたもので、システムコール(セクション 2)と libc の関数(セクション 3)に関する記述がたくさんあります。

2.3 GCC

二つの選択があります。

  1. Linux 用の GCC 公式配布版が常にバイナリ形式(コンパイル済み)で ftp://tsx-11.mit.edu:/pub/linux/packages/GCC/ に置かれています。この文書を書いている時点での最新版は 2.7.2 で、 gcc-2.7.2.bin.tar.gz というファイルです。 訳注:翻訳時では 2.7.2.1 が最新版です。
  2. Free Software Foundation からの GCC ソースの最新配布版が GNU archives にあります。こちらのバージョンは常にバイナリ配布のものと同じであるとは 限りませんが、現在のところは同じ 2.7.2 です。 Linux GCC をメンテナンス してくれている人々は、最新バージョンのコンパイルを容易にするための 作業をしてくれており、 configure スクリプトですべての設定が完了し ます。 tsx-11 にも有用なパッチが置かれていることがありますので、チェックしておきましょ う。

ごく初歩的なコード以外をコンパイルするには以下のようなものも必要になり ます(実はほとんどの初歩的なコードにも必要です)。

2.4 C ライブラリとヘッダファイル

ここで必要になるものは、システムで ELF と a.out のどちらを用いてい るか、あるいは読者がどちらを用いたいかによって変わります。 libc 4 か ら 5 にアップデートするときは ELF-HOWTO を読んでおくようにして下さい。 この文書を入手したのと同じところにあるはずです。

以下に示すものも今までと同じく tsx-11 にあります。

libc-5.2.18.bin.tar.gz

ELF 共有ライブラリと静的リンクライブラリ、インクルードファイルです。 C ライブラリと math ライブラリが入っています。

libc-5.2.18.tar.gz

上記のソースです。ヘッダファイルを入手するには .bin のパッケージ も必要です。 C ライブラリを自分でコンパイルするかバイナリを使うかで悩 むかもしれませんが、ほとんどの場合はバイナリを使うのが正解です。しかし NYS か shadow password を利用したい場合は自分でライブラリを作る必要が あるかもしれません。

libc-4.7.5.bin.tar.gz

a.out 形式の共有ライブラリと静的リンク用のライブラリです。 バージョン 4.7.5 の C および関連のライブラリとなっています。このパッケージは上記 のバージョン 5 ライブラリと共存できるようになっていますが、a.out 形式 のプログラムの作成や利用をしない限りはこのパッケージは必要ありません。

2.5 関連ツール(as, ld, ar, strings 等)

今までのものと同じく、 tsx-11 から入手します。現在のバージョンは binutils-2.6.0.2.bin.tar.gz です。

訳注:翻訳時の最新版は 2.7.0.3 です。

現在 binutils は ELF 対応のものしかありません。また最新版の libc も ELF ですので、 a.out の libc は ELF の libc と一緒に使う必要があるでしょ う。現在では C ライブラリの開発はほとんど ELF ベースになってきています から、特に a.out にこだわる理由がない限り ELF を用いるようにしましょう。

3. GCC のインストールと設定

3.1 GCC のバージョン

現在利用している GCC のバージョンはシェルプロンプトから gcc -v と入力することでわかります。またこのコマンドによって現在のセットアッ プが ELF か a.out かもわかります。私のシステムでは以下のようになります。

$ gcc -v
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2

出力のうち注意すべきキーワードは以下のようなものです。

i486

この gcc が 486 プロセッサ向けにビルドされたことを示しています。読者の システムでは 386 や 586 が表示されるかもしれません。これらのシステム のどれかでコンパイルされたバイナリは他のどのチップでも実行させるこ とが出来ます。 486 gcc でコンパイルされたプログラムでは、 486 チップで 高速に動作するようなコードがあちこちに埋め込まれています。このことによっ て 386 下での実行時の性能に有害な影響が出ることはありませんが、バイナ リのサイズは少々大きくなります。

box

これはまったく重要ではありませんで、他のものに置き変わっている (slackwaredebian など)ことや、そもそもまったく現れない こともあります(つまり全体で i486-linux のようになります)。 もし自分自身で gcc をビルドする場合には、好きな値をセットして格好良く 見せることもできます(私はそうしてます :-))。

linux

これは linuxelflinuxaout となることもあ り、更に面倒なことには gcc のバージョンによってこれらの意味あいが変わっ ています。

2.7.2

バージョン番号です。

したがって私は ELF コードを出力する 2.7.2 版の gcc を使っていることに なります。そうだったのか!

3.2 インストール先のディレクトリ

gcc のインストールの時によそ見をしていた場合や、バイナリパッケージ の一部として gcc をインストールした場合、ファイルシステムのどこを探せ ば gcc があるのかを知りたいことがあるかもしれません。以下重要なものを 示します。

3.3 ヘッダファイルの在処

利用者各自が /usr/local/include にインストールするものを 除くと、 Linux で主に利用されるヘッダファイルは以下の 3 つになります。

3.4 クロスコンパイラの作成

Linux で実行するバイナリを他のホストでコンパイルする

gcc のソースコードがあればその中の INSTALL ファイルの指示に従うだ けでできるはずです。コンパイルを行うコンピュータの種類が XXX でし たら configure --target=i486-linux --host=XXX として make す れば、すべてやってくれるはずです。ただし実際のコンパイルには Linux 特 有のヘッダファイル、カーネルのヘッダファイルが必要になりますし、クロス アセンブラ、クロスリンカのソースを ftp://tsx-11.mit.edu/pub/linux/packages/GCC/ から入手してそれぞれビルドする必要があります。

MSDOS の実行バイナリを Linux でコンパイルする

うーん。 "emx" パッケージとか "go" エクステン ダなどを使うと可能になるそうです。 ftp://sunsite.unc.edu/pub/Linux/devel/msdos を見てみて下さい。

私はこれらを試したことがないので、性能について保証することはできません。

4. 移植とコンパイル

4.1 自動定義されるシンボル

手元の gcc でどんなシンボルが自動定義されているかを調べるには -v スイッチをつけて gcc を実行します。私の場合は以下のようになり ました。

$ echo 'main(){printf("hello world\n");}' | gcc -E -v -
Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs
gcc version 2.7.2
 /usr/lib/gcc-lib/i486-box-linux/2.7.2/cpp -lang-c -v -undef
-D__GNUC__=2 -D__GNUC_MINOR__=7 -D__ELF__ -Dunix -Di386 -Dlinux
-D__ELF__ -D__unix__ -D__i386__ -D__linux__ -D__unix -D__i386
-D__linux -Asystem(unix) -Asystem(posix) -Acpu(i386)
-Amachine(i386) -D__i486__ -

Linux 特有の機能に依存したコードを書いている場合はその部分を以下のよう に囲っておくと良いでしょう。

#ifdef __linux__
/* ... funky stuff ... */
#endif /* linux */

この目的には __linux__ を用います。 linux は用いるべきでは ありません。後者は POSIX 準拠ではないからです。

4.2 コンパイラへの指示

コンパイラのスイッチに関する文書は gcc の info に書かれています。 (Emacs からは C-h i として `gcc' オプションを選びます)。インス トールに用いたバイナリ配布パッケージによってはこれが入っていなかったり 古かったりすることがあります。その場合は gcc のソースアーカイブを ftp://prep.ai.mit.edu/pub/gnu やミラーサイトから入手し、その中の info をコピーして使いましょう。

gcc の man ページ gcc.1 は、一言で言ってしまうと内容が古いです。 これは man ページそのものの中でも警告されています。

コンパイラのフラグ

gcc のコマンドラインに -On をつけると出力される コードを最適化することができます。 n は整数です(省略すると 1 と みなされます)。意味のある n の値とそれぞれの値に対する実質的な効 果はコンパイラのバージョンによって変わりますが、通常は 0 (最適化なし) から 2(たくさん)あるいは 3(とてもたくさん)までが意味を持ちます。

gcc 内部ではこれらの値は -f-m オプション群に展開されます。 -O オプションのそれぞれのレベルにどのようなオプションが対応してい るかを調べるためには gcc を -v-Q オプションをつけて実行 します(後者のオプションはマニュアルには載っていません)。例えば私の場 合 -O2 に対しては以下のようになります。

enabled: -fdefer-pop -fcse-follow-jumps -fcse-skip-blocks
-fexpensive-optimizations
         -fthread-jumps -fpeephole -fforce-mem -ffunction-cse -finline
         -fcaller-saves -fpcc-struct-return -frerun-cse-after-loop
         -fcommon -fgnu-linker -m80387 -mhard-float -mno-soft-float
         -mno-386 -m486 -mieee-fp -mfp-ret-in-387

使っているコンパイラでの最も高い最適化レベルよりも高い数値を指定した場 合(例えば -O6 など)の動作は、最高レベルの最適化を指定したのと同 じになります。しかし配布するコードにこのようなやり方で最適化を指定する のは良いアイディアとは言えません。もし将来リリースされるコンパイラで更 なる最適化が導入された場合、コードがうまく動かなくなる可能性があるから です。

gcc 2.7.0 および 2.7.2 のユーザは、これらの版の -O2 にはバグがあ ることに気をつけて下さい。具体的には strength reduction が動作しないの です。 gcc を再コンパイルする場合はパッチを当てることによってこの問題 は解決できます。そうしない場合はコンパイルの際に常に -fno-strength-reduce を指定するようにして下さい。

プロセッサ固有のフラグ

-O オプションのどのレベルでも指定はされませんが、 -m は 有用なフラグ群です。その最たるものは -m386-m486 で、それ ぞれ 386 と 486 に有利なコードを出力するように指定します。これらのオプ ションを指定してコンパイルしたコードは、それぞれ他のチップでも動作しま す。 486 のコードは大きくなりますが、386 の上でも遅くなることはありま せん。

現在はまだ -mpentium あるいは -m586 というオプションは存在し ません。 Linus によれば -m486 -malign-loops=2 -malign-jumps=2 -malign-functions=2 を使うとアラインメントのための大きなギャップを作 ることなく 486 のコードを最適化できるそうです(Pentium ではそも そもアラインメントが必要とされません)。 Cygnus の Michael Meissner は こう言っています。

個人的には -mno-strength-reduce を x86 のコードに指定すると速度は 向上すると思う(strength reduction のバグのことを言っているわけではな いことに注意されたい。それはまた別の話である)。 x86 CPU ではレジスタ が不足しやすいため、 gcc で用いている手法(レジスタ群を spill レジスタとそれ以外へグループ分けする)と相性が悪いからである。 strength reduction では乗算を加算で置き換える際により多くのレジスタを 使用する。私は -fcaller-saves も同様に性能低下の原因になると考える。
もう一つ私見を。 -fomit-frame-pointer は有利に働く場合も 不利に働く場合もあると思う。このオプションは他のレジスタを割り当て可能 にする。一方 x86 が命令セットを解釈するやり方から考えて、スタック相対 アドレスはフレーム相対アドレスよりも大きなスペースを必要とする。したがっ てプログラムで利用できる I キャッシュが少なくなってしまう。同様に -fomit-frame-pointer を指定するとコンパイラはスタックポインタを命 令コールのたびに再配置するが、フレームがある場合はスタックアキュムレー タを数命令で使いきってしまうことになる。

この話題の最後は再び Linus の言葉で締めくくりましょう。

最高の性能を得るには私を信じちゃいけません。テストして下さい。 gcc コ ンパイラにはたくさんのオプションが在り、その組み合せのうちの一つがあな たにとってのベストな最適化となるはずです。

Internal compiler error: cc1 got fatal signal 11

シグナル 11 は SIGSEGV または「セグメント違反」を意味します。通常こ れはプログラム中でポインタが混乱し、プログラムで管理していないメモリ領 域に書き込みを行おうとした結果です。したがってこれは gcc のバグである 可能性もあります。

しかし gcc (のほとんどの部分)は細部までテストされた信頼すべきソフト ウェアと言えます。一方 gcc では数多くの複雑なデータ構造や無数のポイン タを用いています。つまり通常手に入る中で最も優秀な RAM のテスターであ るとも言えるのです。もしバグが再現されなければ(コンパイルを再び行なっ たときに同じところで止まるのでなければ)それは多分間違いなく使っている ハードウェア(CPU、メモリ、マザーボードまたはキャッシュ)の障害です。 システムが電源投入時のチェックをパスするからといって、あるいは Windows で問題なく動作するからといってこの障害をバグと言ってはいけません。これ らの『テスト』は一般に価値が無いとみなされているからです(正当な判断と 言えます!)。またカーネルのコンパイルがいつも `make zImage' の途 中で停止するからといって、これをバグだと言ってこないでください --- そ りゃ確かにバグかもしれませんけどね。 `make zImage' はおそらく 200 以上のファイルをコンパイルします。我々が知りたいのはもう少し小さな範囲 なのです。

もしバグが再現できたら、また(より望むらくは)バグを引き起こす短いプロ グラムがあったら、その問題に関するバグレポートを FSF か linux-gcc メー リングリストに送りましょう。 gcc の文書を良く読んで、彼らが必要とする 情報に関して理解してからにしましょう。

4.3 移植性

最近では『もし Linux に移植されていないプログラムがあったとしたら、 それはそもそも移植されるべき価値が無いのだ』とも言われています。 :-)

もう少し真面目に。しかし一般に Linux の 100% POSIX 準拠を満たすには ソースを少々変更するだけで良いはずです。行なった変更はプログラムの原著 者にフィードバックすると良いでしょう。以降は `make' だけで実行ファイ ルができるようにしてもらえるかもしれません。

BSD 系(bsd_ioctldaemon および <sgtty.h>

プログラムは -I/usr/include/bsd をつければコンパイルでき、 また -lbsd をつければリンクできます(つまり Makefile の CFLAGS-I/usr/include/bsd を加え、 LDFLAGS-lbsd を加えるわけです)。 BSD 形式のシグナルの振る舞いを用い るために -D__USE_BSD_SIGNAL を加える必要はもうありません。 -I/usr/include/bsd を加えて <signal.h> をインクルー ドすれば自動的に選択されます。

「失われた」シグナル(SIGBUS, SIGEMT, SIGIOT, SIGTRAP, SIGSYS など )

Linux は POSIX に準拠しています。これらは POSIX で定義されているシ グナルではありません。 ISO/IEC 9945-1:1990 (IEEE Std 1003.1-1990) の B.3.3.1.1 から引用します。

「SIGBUS、 SIGEMT、 SIGIOT、 SIGTRAP、 SIGSYS の各シグナルは POSIX.1 から削除されます。これらのシグナルの振舞いは実装によって異なっており、 適当な分類ができないからです。 POSIX 準拠の実装でもこれらのシグナル を発行することは許されていますが、発行される状況は文書化しなければなり ませんし、これらシグナルの発行に関するあらゆる制限を記述しておく必要が あります。」

これを回避する安直な方法はこれらのシグナルを SIGUNUSED として 再定義することです。正しい方法はこれらを扱っているコードを適当な #ifdef の組で囲うことです。

#ifdef SIGSYS
/* ... non-posix SIGSYS code here .... */
#endif

K & R のコード

GCC は ANSI のコンパイラです。しかし現在存在する C のコードはほと んどが ANSI 準拠ではありません。 K & R のコードに関して GCC ができ ることはコンパイラのフラグに -traditional を付けることぐらいです。 もう少し精密なコントロールをすることも可能ですが、これらをエミュレート するのは各種の頭痛の種になるでしょう。詳しくは gcc の info を参照して 下さい。

-traditional は gcc の文法を変えるだけでなく、副作用を生じること に注意して下さい。例えば -traditional によって -fwritable-string が有効になります。このスイッチにより文字列定数 はデータ領域に書き込まれます(スイッチがないとプログラムによる変更が行 われないテキスト領域に書き込まれます)。これにより、プログラムのメモリ 使用量が増加します。

プリプロセッサのシンボルがコード中のプロトタイプと衝突する

ありがちなのは、汎用の関数が Linux のヘッダファイルでもマクロとして定 義されているため、プリプロセッサがコード中の同様なプロトタイプ宣言を 認めなくなるという問題です。良くあるのは atoi()atol() です。

sprintf()

(特に SunOS から移植する際に)気をつけなければならないのは、 sprintf(string, fmt, ...) の戻り値は多くの Unix では string へのポインタであるのに対して、 Linux では(ANSI に従い)文字列へ書き込 まれた文字数になっていることです。

fcntl など。 FD_* の定義はどこにあるの?

<sys/time.h> で定義されています。 fcntl を用い る場合は <unistd.h> も一緒にインクルードする必要があるでしょ う。実際のプロトタイプはここで定義されています。

一般に、関数に必要な #include は man ページの SYNOPSIS セクショ ンに記述されています。

select() が一度タイムアウトするとプログラムがウェイトしなくなる

昔は select() の timeout 引数は変更されませんでした。し かし当時でもマニュアルには以下のように書かれていました。

select() は与えられた timeout から(もしあれば)残った時間を、time の 値を置き換えることによって返すべきです。これはシステムの将来のバージョ ンでインプリメントされるでしょう。従って timeout のポインタが select() の呼び出しによって変更されないことを仮定したコードを書くのは良くありま せん。

その将来が来たわけです、少なくともここでは。 select() から戻るとき、 timeout 引数には待ち時間の残りがセットさ れます。データが最後まで到着しなけ れば timeout は 0 になりますので、 timeout 構造体をそのままにしてもう 一度 select() を呼ぶと、すぐに制御が返って来てしまいうというわけ です。

この問題を修正するには select() を呼ぶ度にタイムアウトの値を timeout 構造体に代入してやれば良いのです。今までのコードが以下のような ものだとしたら、

      struct timeval timeout;
      timeout.tv_sec = 1; timeout.tv_usec = 0;
      while (some_condition)
            select(n,readfds,writefds,exceptfds,&timeout); 
このように変えて下さい。

      struct timeval timeout;
      while (some_condition) {
            timeout.tv_sec = 1; timeout.tv_usec = 0;
            select(n,readfds,writefds,exceptfds,&timeout);
      }

Mosaic のあるバージョンではこの問題が残っていたことがありました。回転 する地球のアニメーションが、ネットワークから到着するデータの速度と反比 例した速さで回転したのです!

システムコールが割り込まれる

症状

プログラムが Ctrl-Z で停止されてから再開される(あるいはシグナルを 発生する他の状況: Ctrl-C による中断や子プロセスの終了など)と、プログ ラムが "interruputed system call" や "write: unknown error" と言ったようなメッセージを出します。

問題点

POSIX のシステムでは古い UNIX よりもシグナルチェックをする局面が多 くなっています。 Linux は以下のようなシグナルハンドラを実行します。

他の OS では以下のようなシステムコールが対象になる場合もあります。 creat()close()getmsg()putmsg()msgrcv()msgsnd()recv()send()wait()waitpid()wait3()tcdrain()sigpause()semop()

もしプログラムがハンドラを持っているシグナルがシステムコールの 途中で発生すると、そのシグナルハンドラが呼び出されます。ハンドラからの 制御が(システムコールに)戻ると、システムコールは自分に対する割り込み を検知し、ただちに -1 を返すとともに errno = EINTR を セットします。プログラムはこのようなことが起こるとは思っていませんから、 異常終了します。

対処法は二つあります。

(1) 導入したシグナルハンドラごとに SA_RESTART を sigaction フラグに追加します。例えば

  signal (sig_nr, my_signal_handler);

のようなものを以下のように書き換えます。

  signal (sig_nr, my_signal_handler);
  { struct sigaction sa;
    sigaction (sig_nr, (struct sigaction *)0, &sa);
#ifdef SA_RESTART
    sa.sa_flags |= SA_RESTART;
#endif
#ifdef SA_INTERRUPT
    sa.sa_flags &= ~ SA_INTERRUPT;
#endif
    sigaction (sig_nr, &sa, (struct sigaction *)0);
  }

これはほとんどのシステムコールに適用できますが、 read()write()ioctl()select()pauseconnect() の各システムコールに対しては EINTR のチェックをプ ログラム中で行なう必要があります。以下を参考にして下さい。

(2) EINTR をプログラム中で明示的にチェックする。

read()ioctl() に対する二つの例を示します。

まず read() の場合です。

int result;
while (len > 0) { 
  result = read(fd,buffer,len);
  if (result < 0) break;
  buffer += result; len -= result;
}

のようなコードを以下のように書き換えます。

int result;
while (len > 0) { 
  result = read(fd,buffer,len);
  if (result < 0) { if (errno != EINTR) break; }
  else { buffer += result; len -= result; }
}

次は ioctl() の例です。

int result;
result = ioctl(fd,cmd,addr);

これを以下のように書き換えます。

int result;
do { result = ioctl(fd,cmd,addr); }
while ((result == -1) && (errno == EINTR));

BSD Unix のバージョンによっては、デフォルトでシステムコールをやり直す ことになっていることもあります。この場合システムコールを中断するには、 SV_INTERRUPUTSA_INTERRUPT フラグを用いる必要があります。

書き込み可能な文字列(プログラムがランダムに停止する)

GCC はユーザを信頼しており、文字列定数はあくまで定数として扱われる ものとみなしています。従って GCC では文字列定数はテキスト(コード)領 域に保持されます。ここはプログラムのディスクイメージにページングされま す(スワップ領域に take up される代わりに)ので、この文字列定数を書き 換えようとするとセグメント違反となります。これは仕様です!

古いプログラムの場合ではこれが問題になることがあります。例えば mktemp() を文字列定数を引数にして呼び出す場合などです。 mktemp() は引数を書き換えようとするためです。

修正するには二つの方法があります。 (a) -fwritable-string を付けて コンパイルして gcc に定数をデータ領域に保持するよう伝える。 (b) 問題と なる部分を定数でない文字列に strcpy して、こちらを用いる。

execl() が失敗する

間違った呼び出し方をしているからです。 execl の最初の引数は実 行したいプログラムです。2番目以降の引数は呼び出すプログラムの argv 配列になります。ここで argv[0] はプログラムの パスそのものであることに注意して下さい。従ってexecl の呼び出しは 以下のように書く必要があります。

execl("/bin/ls","ls",NULL);

単に以下のように書くのは間違いです。

execl("/bin/ls", NULL);

少なくとも a.out の場合は、引数を全くセットせずにプログラムを実行する と、依存しているダイナミックライブラリを表示するようになっています。 ELF ではまた違った動作となります。

もしこのライブラリの情報が必要でしたら、もっと簡単なインターフェースが あります。ダイナミックロードの節か ldd の man ページを見て下さい。

5. デバッグとプロファイリング

5.1 予防的メンテナンス(lint)

Linux では広く用いられている lint はありません。ほとんどの人が gcc の出す warning に満足しているからです。おそらく -Wall スイッチが もっとも役にたつでしょう。これは `Warnings, all' の意味です。しかしこ のスイッチからは、どちらかというと頭を叩き付けたくなるもの(wall)を連 想しそうですね。

パブリックドメインの lint が ftp://larch.lcs.mit.edu/pub/Larch/lclint から入手できます。どのくらい良いかについては私は知りません。

5.2 デバッグ

プログラムにデバッグ情報を埋め込むには

コンパイルとリンクの全ての段階において -g スイッチを付け、 -fomit-frame-pointer スイッチは付けない必要があります。実際には全 ての部分を再コンパイルする必要はなく、デバッグしたい部分のみをすれば OK です。

a.out の共有ライブラリは -fomit-frame-pointer を付けてコンパイル されており、これに対して gdb を用いることはできません。 -g オプショ ンをリンクのときに付けると static リンクになってしまうのはこうした理由 からです。

「libg.a が見つからない」というメッセージが出てリンカが動かない場合は、 /usr/lib/libg.a がないのです。これはデバッグが可能になっている 特殊な C ライブラリです。 libc のバイナリパッケージに入っているか、あ るいは(最近の版の C ライブラリでは) libc のソースコードを手に入れて 自分でビルドする必要があります。しかし実際にはこれは必要なく、ほとんど の場合は /usr/lib/libc.a に対するシンボリックリンクを張れば、 必要な情報は得られるはずです。

埋め込まれたデバッグ情報を除去するには

GNU のソフトウェアの多くは -g フラグがついた状態でコンパイル、 リンクされています。そのため実行ファイルは巨大(かつしばしば static リ ンク)になっています。これはあまりありがたくないですね。

プログラムに autoconf で作った configure スクリプトが付属している 場合は、./configure CFLAGS= または ./configure CFLAGS=-O2 とすることでデバッグ用のコードを付加しないようにできま す。その他 の場合は Makefile を書き換えることになるでしょう。もちろん ELF を使っ ている場合はプログラムは -g の有無に関わらずダイナミックリンクと なっていますから strip すれば良いだけです。

役に立つソフト

gdb が最も広く使われています。ソースは GNU archive sites から、バイナリは tsx-11 から入手できます。 xxgdb は X 用のフロントエンドで(したがって先 に gdb をインストールしておく必要があります) 、ソースは ftp://ftp.x.org/contrib/xxgdb-1.08.tar.gz にあります。

UPS デバッガも Rick Sladkey によって移植されています。これも X で 動作しますが、 xxgdb とは違って単なるテキストベースデバッガのフロント エンドではありません。多くの便利な機能がありますので、デバッグ作業を行 う人はチェックしてみると良いでしょう。 Linux 用のコンパイル済みバイナ リと、オリジナルの UPS のソースコードへのパッチとが ftp://sunsite.unc.edu/pub/Linux/devel/debuggers/ にあります。オリジナルのソースは ftp://ftp.x.org/contrib/ups-2.45.2.tar.Z. です。

デバッグに便利なツールをもう一つ紹介しましょう。 strace はプログ ラムが発行するシステムコールを表示してくれます。またコンパイル済みの バイナリから呼び出されるパス名を表示してくれるので、ソースがなくてもど のファイルが使われているかを知ることができますし、プログラムが引き起こ す競合状態(race condition)のレポートをしてくれるなど、いろいろ便利に 使うことができます。プログラムがどのように動作しているかの学習にも適し ています。 strace の最新バージョン(現在 3.0.8)は ftp://ftp.std.com/pub/jrs/ にあります。

訳注:現在は 3.1.0.1 のようです。

バックグラウンド(デーモン)プログラム

典型的なデーモンプログラムでは fork() を実行し、親プロセスを終 了します。するとデバッグのセッションもあっという間に終わってしまいます。

これを回避する一番簡単な方法は fork にブレークポイントをセットし、 プログラムが停止したときに 0 を返すように強制することです。

(gdb) list 
1       #include <stdio.h>
2
3       main()
4       {
5         if(fork()==0) printf("child\n");
6         else printf("parent\n");
7       }
(gdb) break fork
Breakpoint 1 at 0x80003b8
(gdb) run
Starting program: /home/dan/src/hello/./fork 
Breakpoint 1 at 0x400177c4

Breakpoint 1, 0x400177c4 in fork ()
(gdb) return 0
Make selected stack frame return now? (y or n) y
#0  0x80004a8 in main ()
    at fork.c:5
5         if(fork()==0) printf("child\n");
(gdb) next
Single stepping until exit from function fork, 
which has no line number information.
child
7       }

core ファイル

Linux はブート時の設定では core ファイルを作らない設定になっていま す。作る設定にしたい場合は、シェルの組み込みコマンドを使って再設定して ください。 C シェル系(tcsh など)では

% limit core unlimited

とします。 Bourne シェル系(sh、bash、zsh、pdksh)では次のようにします。

$ ulimit -c unlimited

core ファイルの命名法をもうちょっと便利にしたい場合(例えばそれ自身良く 落ちるようなデバッガで他のプログラムが吐いた core ファイルをデバッグす るときなど)は、カーネルを少々変更すれば可能です。 fs/binfmt_aout.cfs/binfmt_elf.c のコードの中に、 以下のような部分があるはずです(新しいカーネルの場合です。古いカーネル では少々 grep してまわる必要があるかもしれません)。

        memcpy(corefile,"core.",5);
#if 0
        memcpy(corefile+5,current->comm,sizeof(current->comm));
#else
        corefile[4] = '\0';
#endif

この 01 に変えて memcpy(corefile+5.. の行の方を有効 にしてください。

5.3 プロファイリング

プロファイリングとはプログラムのどの部分が最も多く呼ばれ、最も長い時間 を食っているのかを調べることです。無駄な時間を使っているコードを最適化 するのに良い方法です。プロファイルを行うにはタイミング情報が必要なオブ ジェクトファイルを -p をつけてコンパイルします。出力されたファイ ルから情報を得るには gprof が必要になります(binutils のパッケー ジに入っています)。詳細は gprof の man ページを見てください。

6. リンク

あらかじめお断りしておきますが、この章の構成は複雑になっています。 互換性のないバイナリの形式が二つあること、 static なリンクと共有ライブ ラリを使う場合があること、「リンク」という言葉が「コンパイルの後に行う 作業」と「コンパイルされたプログラムが呼び出されたときに行われること」 という二つの意味で用いられること(「ロード(load)」という言葉も使われま す。ただし逆の意味で) などが原因です。でも今あなたが読んだばかりの文よ りごちゃごちゃしている部分は少ないはずですから、それほど心配しないでく ださい。

多少なりとも混乱を少なくするために、実行時に行われることは「動的ロード」 と呼ぶことにして、その内容は次の章に書きます。同じ内容を「動的リンク」 と書いている文書もありますが、この文書では「動的リンク」は用いま せん。要するに、この章ではコンパイルの最終段階として行うリンクに ついてのみを扱います。

6.1 共有ライブラリ対 static なライブラリ

プログラム作成の最終段階は「リンク」と呼ばれます。全ての部品を結合 して、足りないものがないかどうか調べる作業です。「ファイルを開く」といっ たような類の作業は、多くのプログラムで行われます。したがってこのよ うな機能を持つ「部品」はライブラリの形で提供されています。普通の Linux システムでは、ライブラリは /lib/usr/lib にありま す(他の場所にあることもままありますが)。

static なライブラリを用いる場合は、リンカはプログラムが必要とする部品 を探し、出力する実行ファイルにその部品をコピーします。共有ライブラリの 場合は違った作業が行われます。リンカは出力ファイルに「このプログラムが 実行されるときには、まずこれこれのライブラリがロードされていないといけ ませんよ」といったメッセージを埋め込みます。したがって明らかに共有ライ ブラリを用いる方が実行ファイルのサイズは小さくなります。また消費するメ モリやディスク容量も小さくなります。 Linux におけるのデフォルトの振る 舞いでは、共有ライブラリがあればそちらを用い、なければ static なリンク を行います。実行ファイルを共有ライブラリ形式にしたいのに static になっ てしまった場合は、正しい位置に共有ライブラリのファイルがあって(a.out では *.sa、 ELF では *.so です)、それらが読み込み可能になっ ているかどうかをチェックしてください。

Linux では static なライブラリは libname.a といったような名前を持っ ており、共有ライブラリは libname.so.x.y.z となっています。 x.y.z はバージョン番号を示します。共有ライブラリにはリンクが張ら れることが多く、これは重要な機能を持っています。また a.out を利用する 設定では .sa という拡張子を持ったファイルもあるはずです。標準ライ ブラリは共有形式のものと static な形式の両方が含まれています。

あるプログラムがどのような共有ライブラリを必要とするかを調べるには ldd コマンド(List Dynamic Dependencies)を用います。

$ ldd /usr/bin/lynx
        libncurses.so.1 => /usr/lib/libncurses.so.1.9.6
        libc.so.5 => /lib/libc.so.5.2.18

これは WWW ブラウザのプログラム lynx が libc.so.5 (C ライブラリ) と libncurses.so.1 (端末制御ライブラリ) を用いていることを示して います。もし利用する共有ライブラリがなければ、 ldd の表示は 「statically linked]か「statically linked (ELF)」となります。

6.2 ライブラリに尋ねる(sin() はどこにいるの?)

nm libraryname とすると libraryname が参照している シンボルのリストが表示されます。 static なライブラリにも共有ライブラリ にも有効です。例えば tcgetattr() が定義されているライブラリが知り たい場合としましょう。この場合はまず以下を実行します。

$ nm libncurses.so.1 |grep tcget
         U tcgetattr

U は「定義されていない(undefined)」ことを意味します。したがって ncurses ライブラリでは tcgetattr を用いていますが、定義はしていないこ とにことになります。続いて以下のように実行します。

$ nm libc.so.5 | grep tcget
00010fe8 T __tcgetattr
00010fe8 W tcgetattr
00068718 T tcgetpgrp

W は「弱い定義(weak) 」であることを示します。すなわちこのシンボ ルは定義されてはいますが、他のライブラリの定義によって上書きされること を示しているのです。通常の定義は T で示されます(tcgetpgrp がそうです)。

ところでこの節のタイトルに対する解答は libm(so|a) です。 <math.h> で定義されている関数の本体は全て math ライブラ リにあります。したがってこのような関数を用いるには、リンクの際に -lm が必要になるわけです。

6.3 ファイルを探す

ld: Output file requires shared library `libfoo.so.1`

ld などのプログラムにおけるファイル検索の方法はバージョンによって異な ります。しかし全てのバージョンにおいて、/usr/lib は検索対象に 入っています。もしここ以外のディレクトリをライブラリ検索の対象にしたけ れば、 gcc や ld などの -L オプションを用いてください。

これで見つからなければ、正しいファイルがそのディレクトリにあるかを確認 してください。 a.out では -lfoo を指定してリンクすると、 ld はま ず libfoo.sa (共有ライブラリ)を探し、失敗すると libfoo.a (static ライブラリ)を検索します。 ELF の場合は libfoo.solibfoo.a の順に探します。 libfoo.so は通常 libfoo.so.x へのシンボリックリンクとなっています。

6.4 自分のライブラリを作る

バージョン管理

プログラムと同様、ライブラリにもバグはつきもので、時間とともに修正 されていきます。また新たな機能が追加されたり、関数の仕様が変更されたり 古いものが削除されたりもします。これはライブラリを使用するプログラムに は問題です。もし古い仕様に基づいている場合はどうすれば良いのでしょうか?

したがってライブラリにはバージョン管理を用います。ライブラリに対して行っ た変更を「小さい(minor) 」ものと「大きい(major)」ものに分けます。 minor なな変更では、そのライブラリを用いるプログラムがちゃんと動くことを保証 することにします。ライブラリのバージョンはファイル名でわかるようにしま す。(本当の事を言うとこれは ELF には当てはまりません。理由は後述。) libfoo.so.1.2 は major バージョンが 1 で、 minor バージョンが 2 であることを示します。 minor バージョンは少々違った構造を持つこともあ ります。 libc では「パッチレベル」を minor バージョンに追加し、ライブ ラリの名前を libc.so.5.2.18 のようにしています。 ASCII 端末で表示 可能な文字なら、英字でも _ でもつけてかまいません。

ELF と a.out の大きな違いの一つは、共有ライブラリの作り方にあります。 まず簡単な ELF のほうから見ることにしましょう。

ELF って結局なに?

ELF (Executable and Linking Format)はもともと USL(UNIX System Laboratories)で開発されたバイナリ形式で、現在では Solaris と System V Release 4 で用いられています。 ELF は以前 Linux で用いられていた a.out 形式よりも柔軟性に富んでいたので、 GCC と C ライブラリの開発者たちは昨 年に ELF を Linux の標準バイナリ形式としても採用することに決めました。

だからなんだって?

この節は `/news-archives/comp.sys.sun.misc' にある文書から抜粋した ものです。

ELF(Executable Linking Format)は SVR4 に導入された「進歩した最新の」 オブジェクトファイル形式です。 ELF はユーザによる拡張が可能であり、 straight COFF よりもずっと強力です。 ELF では、オブジェクト ファイルを任意の長さを持ったセクションからなるものします(決まったサ イズの要素からなる配列とはみなされません)。これらのセクションは(COFF とは異なり) 決まった場所に置く必要がなく、また順番も任意です。ユーザがオブジェクト ファイルに新たなデータを導入したければ、新しいセクションを追加するだけ で良いのです。 ELF にはこれまでのものよりずっと強力なデバッグ支援用の形式も導入されて います。これは DWARF(Debugging With Attibute Record Format)と呼ばれ ています。現在のところ linux ではこの機能は完全にはサポートされていま せん(しかし作業は着々と継続中です)。 DWARF DIE(Debugging Information Entries)のリンクリストは ELF バイナリの中の .debug という セクションに収められています。小さく、サイズも固定されたデバッグ情報と 異なり、DWARF DIE はそれぞれ任意の長さの複雑な属性を持ち、スコープに依 存したツリー形式のプログラムデータとして記述されています。 DIE は COFF の .debug セクションでは不可能であったような、巨大な情報(C++ の継承関 係リストなど)を保有することができるのです。

ELF ファイルは SVR4 (Solaris 2.0 ?)の ELF アクセスライブラリを通じて アクセスされます。このライブラリは ELF で取り扱いが厄介になっている部 分に対して、簡単で高速なインターフェースを提供しています。このライブラ リを用いれば、 ELF ファイルの実体そのものを見なくても済みます。 Elf ファ イルとしてアクセスされた UNIX のファイルは、最初に elf_open() コールを 実行すれば、後はその中身に elf_foobar() コールでアクセスすることができ ます。今まで COFF ユーザが強制されてきたように、実際のディスク上の位置 を求めてさまよう必要はもう無いのです。

ELF の有利/不利な点、および a.out のシステムを ELF システムにアップグ レードする際に必要な内容は ELF-HOWTO に記述されています。ここにカット & ペーストするつもりはありません。 ELF-HOWTO は、この文書と同じと ころにあるはずです。

ELF 共有ライブラリ

libfoo.so のような共有ライブラリを作成するための基本的な手順 は以下のようになります。

$ gcc -fPIC -c *.c
$ gcc -shared -Wl,-soname,libfoo.so.1 -o libfoo.so.1.0 *.o
$ ln -s libfoo.so.1.0 libfoo.so.1
$ ln -s libfoo.so.1 libfoo.so
$ LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH ; export LD_LIBRARY_PATH

これによって libfoo.so.1.0 という名前の共有ライブラリができ、ld のためのリンク(libfoo.so)とダイナミックローダのためのリンク (libfoo.so.1)が作成されます。テストするにはカレントディレクトリ を LD_LIBRARY_PATH に追加します。

ライブラリがちゃんと動いたら、これを移動する必要があります。 /usr/local/lib あたりが適当でしょう。上で作ったようなリンクも それぞれ作り直す必要があります。 libfoo.so.1libfoo.so.1.0 のリンクは ldconfig によって常に最新のものに更 新されます。 通常 ldconfig はブートプロセスの一部で実行されているはずです。 libfoo.so のリンクはマニュアルで更新する必要があります。几帳面な 方はライブラリの全て(ヘッダファイルなども含む)を同時にアップデートし たくなるでしょうが、その場合は libfoo.so -> libfoo.so.1 とい うリンクを張っておき、 ldconfig が両者を同時に細心にしてくれるようにし ておくのが最も簡単でしょう。そうしない人は、後にあらゆる種類 の不可思議な現象に見舞われることになるでしょう。後で「聞いてないよ」なん て言わないように!

$ su
# cp libfoo.so.1.0 /usr/local/lib
# /sbin/ldconfig
# ( cd /usr/local/lib ; ln -s libfoo.so.1 libfoo.so )

バージョンの番号付けと soname、シンボリックリンク

各々のライブラリは soname という情報を持っています。リンカがライ ブラリを検索する際にこの soname を見つけると、実行バイナリにはライブラ リのファイル名ではなく soname が埋め込まれます。するとプログラムの実行 時に、動的ローダはリンク時に用いられたファイルではなく soname で指定されるファイルを探索します。例えば libfoo.so という ライブラリが libbar.so という soname を持つことも可能で、すると libfoo.so にリンクされたプログラムは実行時に libbar.so の方 を検索します。

意味の無い機能だと思いますか?実は同じライブラリの複数バージョンをシス テムに共存させるための鍵となる機能なのです。 Linux におけるライブラリ 命名法のデファクト・スタンダードは libfoo.so.1.2 といったようなも ので、これに対する soname は libfoo.so.1 となります。このライブラ リが標準のライブラリディレクトリ(例えば /usr/lib)に置かれる と、 ldconfiglibfoo.so.1 -> libfoo.so.1.2 というシン ボリックリンクを作り、実行時に適当なファイルが利用されるようにします。 また libfoo.so -> libfoo.so.1 というリンクも必要で、これにより ld はリンク時に用いるべき正しい soname を見つけることができるようになりま す。

ライブラリのバグを修正したり新機能を追加(今までのプログラムに影響を与 えない範囲で)したりしたときには、 soname はそのままにしてファイル名を 変更するのです。ライブラリの上位互換性がなくなったときには soname の番 号を一つ増やします。この場合新しいバージョンのライブラリはファイル名が libfoo.so.2.0、 soname が libfoo.so.2 となります。 libfoo.so のリンクも新しいバージョンへ張りなおせばライブラリの更 新に伴う手続きがすべて完了したことになります。

絶対にこの規則でライブラリの命名を行わなければならないわけではありませ んが、良い慣習ですので利用する方が良いと思います。 ELF ではライブラリ の命名に関しても柔軟性がありますから、人がうんざりするほど複雑な命名ルー ルを使うことだってできますが、実際にそうするかどうかはまた別の話ですよね。

実行方法をまとめます。伝統に従い major なアップグレードは互換性が失わ れたとき、 minor なアップグレードはそうでないときということにしましょ う。この場合は以下のようにリンクしてください。

gcc -shared -Wl,-soname,libfoo.so.major -o libfoo.so.major.minor

これでうまく動くはずです。

a.out、汝古き形式よ

ライブラリが ELF へ移行した主な理由は、共有ライブラリを簡単に作れ るという点にありました。しかし a.out でも作成が不可能なわけではありません。 ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz を入手して、パッケージの中にある 20 ページのドキュメントを読みましょう。 私は一方の党派に偏るつもりはありませんが、でも今まで書いてきた文章で、 私がもう a.out を使うつもりが無いことは明らかでしょうかね :-)

ZMAGIC と QMAGIC

古い a.out の実行バイナリは ZMAGIC と呼ばれます。 QMAGIC は ZMAGIC と 似ていますが、最初のページをマップしない点が違っています。 したがって 0〜4096 番地がどこにもマッピングされてないため、NULL ポインタ から変数を参照するという間違いを見つけやすくなっています(0 番地をアク セスすると、トラップされるわけです)。 また別の利点としてバイナリが少々(1K くらい)小さくなります。

非常に古いリンカでは ZMAGIC のみに、やや古いものは両方に、最近のものは QMAGIC のみに対応しています。カーネルは両方のフォーマットを実行させる ことができますので、実際には気にする必要はありません。

プログラムが QMAGIC かどうかは、`file' コマンドを実行することによって 表示されます。

ファイルの配置

a.out の共有ライブラリは二つのファイルと一つのシンボリックリンクから構 成されます。この文書でこれまで使ってきた「foo」ライブラリを例にとりま すと、 libfoo.salibfoo.so.1.2 が実際のファイル、 libfoo.so.1libfoo.so.1.2 へのシンボリックリンクです。 これらの役割はどんなものでしょうか?

コンパイルの際に、 ldlibfoo.sa を探します。このファイル はライブラリの「stub」ファイルと呼ばれ、外から参照できるデータと 実行時リンクに必要な関数へのポインタとを保持しています。

実行時には、ダイナミックローダは libfoo.so.1 を探索します。これは 実ファイルではなくシンボリックリンクになっていて、バグフィックスなどに よるアップデートの際に、このライブラリを使っていたプログラムに問題が生 じないようにしています。新しいバージョンのライブラリ (libfoo.so.1.3 など)があれば、 ldconfig を実行することによって リンク先を変更でき、このライブラリを用いていたプログラムには影響を与え なくてすみます。

DLL ライブラリ(トートロジーだということは承知しています :-p)は static なライブラリに比べて大きくなる場合が多いです。 DLL ライブラリに は将来の拡張に備えて、「hole」という形式の領域が確保されています。この hole 領域が実際にはディスクを消費しないようにすることもできます。単に cp するか、あるいは makehole コマンドを用います。(a.out で は)アドレスは固定されているので、ライブラリ構築後に strip することも できます。なお ELF ライブラリは strip してはいけません!

「libc-lite」とは?

libc-lite は libc ライブラリの軽量版です。フロッピーでの利用に適し、 UNIX の基本的なタスクのほとんどをカバーしています。 libc-lite には curses、 dgm、 termcap などのコードは入っていません。もしお使いのシス テムの /lib/libc.so.4 がこの lite 版へのリンクでしたら、 full サイズの版に置き換えた方が良いでしょう。

リンク:よくある問題

リンクの際に障害が起こったら私に教えてください!私が手助けできることは あまり無いかもしれませんが、同じのがたくさんきたらここに載せることはで きます...

共有形式でリンクしたいのに static になってしまう

ld が共有ライブラリを検索できるよう、それぞれリンクがちゃんと張ら れているか確認してください。 ELF の場合はシンボリックリンク libfoo.so が、 a.out では libfoo.sa が実ファイルへ張られていな ければなりません。この問題は ELF binutils を 2.5 から 2.6 に更新したと きに非常に多く報告されました。以前のバージョンではライブラリの検索を 「賢く」行っており、全てのリンクがなくても動作することがありました。新 しいバージョンでは他のアーキテクチャとの整合性をとるために、この機能を 削除しました。また推測を誤ると、より深刻な問題を引き起こす可能性が生じ ることも削除された理由の一つです。

DLL のツール `mkimage が libgcc の検索に失敗します

libc.so.4.5.x 以上では、 libgcc は共有ライブラリではなくなりまし た。したがって問題が生じた行の「-lgcc」は「`gcc -print-libgcc-file-name`」に置き換えてください。バッククォート「`」を お忘れなく。

また /usr/lib/libgcc* は全て削除してください。こちらも重要で す。

__NEEDS_SHRLIB_libc_4 multiply defined というメッセージが出る

これは上と同種の問題です。

DLL を再構築するときに ``Assersion failure'' というメッセージがでる

この謎のメッセージは、恐らくジャンプテーブルの slot のうちの一つがオー バーフローしてしまったことを意味しています。オリジナルの jump.vars ファイルに予約した領域が小さすぎたことが原因と考えられ ます。問題の個所は getsize によって特定できます(getsize は tools-2.17.tar.gz パッケージにあります)。この場合残念ながら、ライブラ リの major バージョンを上げ、下位互換性をあきらめることが唯一の解決法 となるでしょう。

ld: output file needs shared library libc.so.4

これは libc 以外のライブラリ(X 関係のライブラリなど)を用いており、か つ -g をつけて -static をつけていない場合に生じます。

共有ライブラリに対応した stub ファイル(*.sa)には、通常 _NEEDS_SHRLIB_libc_4 という未定義のシンボルが含まれています。これ は libc.sa stub によって解決されています。 -g が指定されてい ると libg.a または libc.a とのリンクが行われますが、ところが これらのライブラリでは上のシンボルは解決されていないのです。したがって 表題のエラーとなります。

結局 -g をつけてコンパイルするときは -static を追加するか、 あるいはリンクの際に -g を用いないか、が解答です。リンクの際に -g を用いなくても、各々のソースを -g でコンパイルしておけば、 ほとんどの場合は充分なデバッグ情報が得られます。

7. ダイナミックロード(Dynamic Loading)

この章は今のところまだ短いです。私が ELF HOWTO の内容を消化するに連れ、 だんだん拡張されていくでしょう。

7.1 概念

前の章全部を一気に読んだ人はもう聞き飽きたかもしれませんが、 Linux は 共有ライブラリを利用しています。「名前を検索して置き換える」という動作 が、リンク時から実行時に先送りされるようになったのです。

7.2 エラーメッセージ

リンクエラーが起こったら送ってください!解決はできないかもしれませんが、 ここにまとめたいと思います...

can't load library: /lib/libxxx.so, Incompatible version

(a.out のみ)これは xxx ライブラリの major バージョンがあっていないか らです。ただシンボリックリンクを違うバージョンのライブラリに張っただけ ではだめですよ(そんなことをして segfault くらいで済んだら幸運というも のです)。新しいバージョンを入手してください。 ELF の場合、同じような 状況では以下のようなメッセージとなります。

ftp: can't load library 'libreadline.so.2'

warning using incompatible library version xxx

(a.out のみ)このメッセージを吐いたプログラムをコンパイルした人よりも minor バージョンが古いライブラリを使っている場合に出ます。多分プログ ラムはちゃんと動くと思います。でもアップグレードした方が良いでしょうね。

7.3 ダイナミックローダの動作をコントロールする

ダイナミックローダが参照する環境変数は多岐に渡っています。これらのほと んどは ldd だけが使うものなので、 ldd に対応するスイッチをつ けて実行する方が便利かもしれません。

7.4 ダイナミックロードを用いたプログラムを書く

Solaris 2.x でのダイナミックロード機能と非常に似ています(といっても Solaris ユーザにしかわかりませんね)。具体的な内容は H. J. Lu の ELF programming document および dlopen(3) の man ページで詳述されてい ます。後者は ld.so のパッケージに入っています。以下にもちょっとした例 を示します。 -ldl をつけてリンクしてください。

#include <dlfcn.h>
#include <stdio.h>

main()
{
  void *libc;
  void (*printf_call)();

  if(libc=dlopen("/lib/libc.so.5",RTLD_LAZY))
  {
    printf_call=dlsym(libc,"printf");
    (*printf_call)("hello, world\n");
  }

}

8. 開発者に連絡を取るには

8.1 バグレポートを送る

まず問題を絞り込んでください。 Linux に特有の問題か、他のシステム の gcc でも起こるか?カーネルのバージョンに固有の問題か?ライブラリの バージョンは?リンクを static にすれば解決するか?問題を起こすプログラ ムを短いデモンストレーション版に切りつめることができるか?などで す。

これが済んだら、どのプログラムにバグがいるかがはっきりすると思います。 GCC の場合は、バグレポートの手続きは info ファイルで説明されています。 ld.so や C ライブラリ、 math ライブラリの場合は linux-gcc@vger.rutgers.edu にメールを送ってください。可能ならば短 く、バグの存在をはっきりと示すプログラムを添付し、そのプログラムで想定し ていた動作と実際の動作についての説明も送ってください。

8.2 開発に参加する

GCC や C ライブラリの開発に参加したい場合は、まずメーリングリスト linux-gcc@vger.rutgers.edu に参加してください。どんなことが議論さ れているかを知りたいだけならば、リストのアーカイブが http://homer.ncm.com/linux-gcc/ にあります。その次になすべきことはあなた次第です!

9. その他

9.1 謝辞

「我々」と言う言葉を使うことができるのは、大統領と編集者、そして 体内にサナダ虫を飼っている人々のみである
(Mark Twain)

この HOWTO は Mitchum DSouza の GCC-FAQ から非常に多くの題材を取ってい ます。 GCC-FAQ の大部分の(本当に大部分の)情報は、この文書にそのまま の形で導入されています。この HOWTO の文章中、一人称(「私」)が指す実 体は我々のどちらの場合もありえます。 「私はこれらを試していません。これらを試したことによってあなたのディス クやシステムや奥さんが焼けてしまっても私のせいにしないでください」といっ た内容は、特に指定が無ければ我々の両方に当てはまります。

この文書に助力くださった人々です(ファーストネームの ASCII 順です)。 Andrew Tefft, Axel Boldt, Bill Metzenthen, Bruce Evans, Bruno Haible, Daniel Barlow, Daniel Quinlan, David Engel, Dirk Hohndel, Eric Youngdale, Fergus Henderson, H.J. Lu, Jens Schweikhardt, Kai Petzke, Michael Meissner, Mitchum DSouza, Olaf Flebbe, Paul Gortmaker, Rik Faith, Steven S. Dick, Tuomas J Lukka, そしてもちろん Linus Torvalds。彼がいなければこの文書に関連した全ての 活動は意味を持たなかったですし、そもそも行われなかったでしょう :-)

もしこの文書(HOWTO にでも FAQ にでも)助力くださった方で、リストから 漏れている方がいらっしゃいましたら、どうかお知らせくださいますようお願 いします。私にメールしていただければ修正します。

訳注:日本語訳に当たっては、 水原さん、 こやまさん、 鴨澤さん、 堀江さん、 菅原さん、 山崎さん、 伊藤さん、 をはじめとする JF メーリングリストの皆さんに有益なご指摘を頂きました。

9.2 翻訳について

現時点ではこの文書の翻訳版はありません。翻訳してみようと思う方、どうぞ お願いします。でも私に知らせてください!私が翻訳先の言葉を話せる可能性 は(残念なことに)100 対 1 以下でしょうが、それはともかく私は喜んでお 手伝いするつもりです。

9.3 フィードバック

歓迎します。私のアドレス daniel.barlow@linux.org 宛にメールしてください。 私の PGP 公開鍵(ID 5F263625)は web ページ にあります。通信の秘密を必要とする方は利用してください。

訳注:原文でのメールアドレスは dan@detached.demon.co.uk と なっていますが、 daniel.barlow@linux.org の方がアクセスが良い そうです。

9.4 法的条項

訳注:この節は原文も示します。

All trademarks used in this document are acknowledged as being owned by their respective owners.

This document is copyright (C) 1996 Daniel Barlow <dan@detached.demon.co.uk> It may be reproduced and distributed in whole or in part, in any medium physical or electronic, as long as this copyright notice is retained on all copies. Commercial redistribution is allowed and encouraged; however, the author would like to be notified of any such distributions.

All translations, derivative works, or aggregate works incorporating any Linux HOWTO documents must be covered under this copyright notice. That is, you may not produce a derivative work from a HOWTO and impose additional restrictions on its distribution. Exceptions to these rules may be granted under certain conditions; please contact the Linux HOWTO coordinator at the address given below.

In short, we wish to promote dissemination of this information through as many channels as possible. However, we do wish to retain copyright on the HOWTO documents, and would like to be notified of any plans to redistribute the HOWTOs.

If you have questions, please contact Greg Hankins, the Linux HOWTO coordinator, at gregh@sunsite.unc.edu via email.

[日本語訳]

この文書で引用している商標はそれぞれの保有者に帰するものです。

この文書の著作権は (C) 1996 Daniel Barlow <dan@detached.demon.co.uk> が保有しています。 この文書の全体あるいは一部は、物理的電子的を問わず、あらゆるメディアに 自由に複写することができます。ただしその際にはこの著作宣言を全てのコピー に付加する必要があります。商業的な配布も許可し、また奨励します。しかし その際には著者にお知らせくださるようお願いします。

Linux HOWTO の文書を含んだ翻訳、修正および編集作業の成果は、全てこの著 作権条項に従う必要があります。すなわちこの HOWTO を修正した後に、配布 条件に追加項を加えることはできません。但し適当と認められた場合は例外と することもできます。 Linux HOWTO の管理者に連絡してください。アドレス は以下に示します。

要するに我々はこれらの情報を可能な限りの方法で広めたいと思っているので す。しかし我々は HOWTO 文書に関する著作権を保持し続けることを望んでい ますし、HOWTO を再配布する計画について知らせてもらうことも望んでいます。

もし疑問点があったら、Linux HOWTO 管理者の Greg Hankins に連絡してくだ さい。電子メールのアドレスは gregh@sunsite.unc.edu です。

10. 索引

アルファベット以外の文字ではじまる言葉は ASCII 配列の順に並べてありま す。