The Linux GCC HOWTO Daniel Barlow v1.17, 28 February 1996 中野武雄 v1.17j2, 29 January 1997 この文書では linux の下で GNU C コンパイラとコンパイルに必要なライブラ リを設定する方法について記しています。またこれらの環境を用いたプログラ ムのコンパイル、リンク、実行およびデバッグについても概要を述べます。こ の文書の内容の多くは Mitch D'Souza の GCC-FAQ から得たものであり、この 文書は GCC-FAQ の代わりとなるものです。また ELF-HOWTO からも多くの題材 を採用しており、同じくこの文書は ELF-HOWTO の大部分を置きかえるもので す[訳注:Daniel さんは ELF-HOWTO の著者でもあります。]この版は公式リ リースとしては最初のものになります(バージョン番号からはそうは見えない かもしれませんが、これは RCS が自動的につけているものなんです)。ご意 見ご感想をお待ちしています。 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 が置かれて いるところ(例えば )な らばどこにでもあるはずです。 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 公式配布版が常にバイナリ形式(コンパイル済み)で に置かれています。 この文書を書いている時点での最新版は 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 これはまったく重要ではありませんで、他のものに置き変わっている (slackware やdebian など)ことや、そもそもまったく現れないこと もあります(つまり全体で i486-linux のようになります)。もし自分 自身で gcc をビルドする場合には、好きな値をセットして格好良く見 せることもできます(私はそうしてます :-))。 linux これは linuxelf や linuxaout となることもあり、更に面倒なことに は gcc のバージョンによってこれらの意味あいが変わっています。 o linux は 2.7.0 以降のバージョンなら ELF、その前の場合は a.out を意味します。 o linuxaout は a.out 対応です。このエントリは linux の内容が 2.7.0 以降で a.out から ELF に変更されたことにより導入されま した。したがって 2.7.0 より前のバージョンではこのエントリが現 れることはありません。 o linuxelf というのは古い書式です。これが現れるのはほとんどの場 合 ELF バイナリを出力するように設定された 2.6.3 の gcc です。 ところで gcc 2.6.3 は ELF コードを作成するにあたってバグがあ ることが知られています。したがってこの表示が現れた場合はアッ プグレードをお薦めします。 2.7.2 バージョン番号です。 したがって私は ELF コードを出力する 2.7.2 版の gcc を使っていることに なります。そうだったのか! 3.2. インストール先のディレクトリ gcc のインストールの時によそ見をしていた場合や、バイナリパッケージの一 部として gcc をインストールした場合、ファイルシステムのどこを探せば gcc があるのかを知りたいことがあるかもしれません。以下重要なものを示し ます。 o /usr/lib/gcc-lib/target/version/ とそのサブディレクトリにはコンパイ ラ本体の大部分が置かれています。コンパイラ本体のプログラムとバー ジョンに特有のライブラリ、インクルードファイルがあります。 o /usr/bin/gcc はコンパイラのドライバです。コマンドラインからはこれが 実行されます。複数のバージョンのコンパイラが上記のようなディレクト リに別々にインストールされている場合、このプログラムでバージョンを 使い分けることが出来ます。デフォルトのバージョンを表示するには gcc -v とします。他のバージョンを用いる場合には gcc -V version としま す。以下に例を示します。 # gcc -v Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs gcc version 2.7.2 # gcc -V 2.6.3 -v Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.6.3/specs gcc driver version 2.7.2 executing gcc version 2.6.3 o /usr/target/(bin|lib|include)/。複数のターゲットシステム(例えば a.out と ELF、あるいは各種のクロスコンパイラなど)に応じて gcc を複 数インストールした場合、Linux 以外向けのライブラリや binutils (as、 ld など)、ヘッダファイルなどはこのディレクトリ下に置かれま す。一種類の gcc しかインストールしてい場合でも、まあ要するにここに 関連ファイルが置かれるわけです。ここにない場合は /usr/(bin|lib|include) にあります。 o /lib や /usr/lib などは native なシステムのためのライブラリが置かれ るディレクトリです。コンパイラ以外のアプリケーションの中には、ここ に /lib/cpp が置かれることを想定しているものがあります(特に X でよ く使われます)。ない場合は /usr/lib/gcc-lib/target/version/ からコ ピーしてくるかシンボリックリンクを張りましょう。 3.3. ヘッダファイルの在処 利用者各自が /usr/local/include にインストールするものを除くと、 Linux で主に利用されるヘッダファイルは以下の 3 つになります。 o /usr/include/ とそのサブディレクトリにあるヘッダファイルのほとんど は H. J. Lu が提供している libc バイナリパッケージのものです。「ほ とんど」と書いたのは、他にもここにヘッダファイルを置くパッケージが あるからです。例えば curses や dbm などがそうです。古い libc パッケ ージは curses と dbm もいっしょになっていましたが、最新のものでは別 パッケージになっています。 o /usr/include/linux と /usr/include/asm ディレクトリのファイル(これ らは のようにインクルードされます)。これら のディレクトリはカーネルソースツリーのうちの linux/include/linux ディレクトリおよび linux/include/asm へのシンボリックリンクとなって います。少々複雑なプログラムを開発する場合はインストールしておく必 要があります。これらはカーネルのコンパイルのためだけのものではない のです。 カーネルソースを展開するだけではだめで、 make config を実行する必要 があります。このコマンドによってはじめて作成される は 多くのファイルによって参照されていますし、カーネルのバージョンに よっては asm ディレクトリがシンボリックリンクになっていて、 make config のときにのみリンクが作成されるようになっています。 したがってカーネルソースを /usr/src/linux 以下に展開した後の作業は 次のようになります。 $ cd /usr/src/linux $ su # make config [質問に答えます。この後に続けてカーネルを作るのでない限り ここでの答を気にする必要はあまりありません。] # cd /usr/include # ln -s ../src/linux/include/linux . # ln -s ../src/linux/include/asm . o と いったファイルはコンパイラのバージョンによって内容が変わっていま す。したがってこれらは /usr/lib/gcc-lib/i486-box- linux/2.7.2/include/ か、あるいは似たような名前の場所に置かれていま す。 3.4. クロスコンパイラの作成 3.4.1. Linux で実行するバイナリを他のホストでコンパイルする gcc のソースコードがあればその中の INSTALL ファイルの指示に従うだけで できるはずです。コンパイルを行うコンピュータの種類が XXX でしたら configure --target=i486-linux --host=XXX として make すれば、すべて やってくれるはずです。ただし実際のコンパイルには Linux 特有のヘッダ ファイル、カーネルのヘッダファイルが必要になりますし、クロスアセンブ ラ、クロスリンカのソースを から入手してそれぞれビ ルドする必要があります。 3.4.2. MSDOS の実行バイナリを Linux でコンパイルする うーん。 "emx" パッケージとか "go" エクステンダなどを使うと可能になる そうです。 を見てみて下 さい。 私はこれらを試したことがないので、性能について保証することはできませ ん。 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 のソースアーカイブを やミラーサイトから入手し、その中の info をコピーして使いましょう。 gcc の man ページ gcc.1 は、一言で言ってしまうと内容が古いです。これは man ページそのものの中でも警告されています。 4.2.1. コンパイラのフラグ 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 を指 定するようにして下さい。 4.2.1.1. プロセッサ固有のフラグ -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 コンパイラにはたくさんのオプションが在り、その組み 合せのうちの一つがあなたにとってのベストな最適化となるはずで す。 4.2.2. 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' だけで実行ファイルが できるようにしてもらえるかもしれません。 4.3.1. BSD 系( bsd_ioctl 、 daemon および ) プログラムは -I/usr/include/bsd をつければコンパイルでき、また -lbsd をつければリンクできます(つまり Makefile の CFLAGS に -I/usr/include/bsd を加え、 LDFLAGS に -lbsd を加えるわけです)。 BSD 形式のシグナルの振る舞いを用いるために -D__USE_BSD_SIGNAL を加える必要 はもうありません。 -I/usr/include/bsd を加えて をインクルー ドすれば自動的に選択されます。 4.3.2. 「失われた」シグナル( 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 4.3.3. K & R のコード GCC は ANSI のコンパイラです。しかし現在存在する C のコードはほとんど が ANSI 準拠ではありません。 K & R のコードに関して GCC ができることは コンパイラのフラグに -traditional を付けることぐらいです。もう少し精密 なコントロールをすることも可能ですが、これらをエミュレートするのは各種 の頭痛の種になるでしょう。詳しくは gcc の info を参照して下さい。 -traditional は gcc の文法を変えるだけでなく、副作用を生じることに注意 して下さい。例えば -traditional によって -fwritable-string が有効にな ります。このスイッチにより文字列定数はデータ領域に書き込まれます(ス イッチがないとプログラムによる変更が行われないテキスト領域に書き込まれ ます)。これにより、プログラムのメモリ使用量が増加します。 4.3.4. プリプロセッサのシンボルがコード中のプロトタイプと衝突する ありがちなのは、汎用の関数が Linux のヘッダファイルでもマクロとして定 義されているため、プリプロセッサがコード中の同様なプロトタイプ宣言を認 めなくなるという問題です。良くあるのは atoi() と atol() です。 4.3.5. sprintf() (特に SunOS から移植する際に)気をつけなければならないのは、 sprintf(string, fmt, ...) の戻り値は多くの Unix では string へのポイン タであるのに対して、 Linux では(ANSI に従い)文字列へ書き込まれた文字 数になっていることです。 4.3.6. fcntl など。 FD_* の定義はどこにあるの? で定義されています。 fcntl を用いる場合は も一 緒にインクルードする必要があるでしょう。実際のプロトタイプはここで定義 されています。 一般に、関数に必要な #include は man ページの SYNOPSIS セクションに記 述されています。 4.3.7. 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 のあるバージョンではこの問題が残っていたことがありました。回転 する地球のアニメーションが、ネットワークから到着するデータの速度と反比 例した速さで回転したのです! 4.3.8. システムコールが割り込まれる 4.3.8.1. 症状 プログラムが Ctrl-Z で停止されてから再開される(あるいはシグナルを発生 する他の状況: Ctrl-C による中断や子プロセスの終了など)と、プログラム が "interruputed system call" や "write: unknown error" と言ったような メッセージを出します。 4.3.8.2. 問題点 POSIX のシステムでは古い UNIX よりもシグナルチェックをする局面が多く なっています。 Linux は以下のようなシグナルハンドラを実行します。 o 非同期なチェック(タイマ割り込みの際) o システムコールからの戻り時 o 以下のシステムコールの実行時: o ターミナル、ソケット、パイプおよび /proc のファイルに対する select()、 pause()、 connect()、 accept()、 read()。 o ターミナル、ソケット、パイプおよびラインプリンタへの write()。 o FIFO、 PTY およびシリアルラインの open()。 o ターミナルへの ioctl()。 o fcntl() への F_SETLKE コマンド。 o wait4()、 syslog()、 その他全ての TCP および NFS 動作。 他の 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()、 pause、 connect() の各システムコールに対しては 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_INTERRUPUT か SA_INTERRUPT フラグを用いる必要があります。 4.3.9. 書き込み可能な文字列(プログラムがランダムに停止する) GCC はユーザを信頼しており、文字列定数はあくまで定数として扱われるもの とみなしています。従って GCC では文字列定数はテキスト(コード)領域に 保持されます。ここはプログラムのディスクイメージにページングされます (スワップ領域に take up される代わりに)ので、この文字列定数を書き換 えようとするとセグメント違反となります。これは仕様です! 古いプログラムの場合ではこれが問題になることがあります。例えば mktemp() を文字列定数を引数にして呼び出す場合などです。 mktemp() は引 数を書き換えようとするためです。 修正するには二つの方法があります。 (a) -fwritable-string を付けてコン パイルして gcc に定数をデータ領域に保持するよう伝える。 (b) 問題となる 部分を定数でない文字列に strcpy して、こちらを用いる。 4.3.10. 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 が から入手できます。どのくら い良いかについては私は知りません。 5.2. デバッグ 5.2.1. プログラムにデバッグ情報を埋め込むには コンパイルとリンクの全ての段階において -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 に対するシンボリックリンクを張れば、必要な情報 は得られるはずです。 5.2.1.1. 埋め込まれたデバッグ情報を除去するには GNU のソフトウェアの多くは -g フラグがついた状態でコンパイル、リンクさ れています。そのため実行ファイルは巨大(かつしばしば static リンク)に なっています。これはあまりありがたくないですね。 プログラムに autoconf で作った configure スクリプトが付属している場合 は、./configure CFLAGS= または ./configure CFLAGS=-O2 とすることでデ バッグ用のコードを付加しないようにできます。その他の場合は Makefile を 書き換えることになるでしょう。もちろん ELF を使っている場合はプログラ ムは -g の有無に関わらずダイナミックリンクとなっていますから strip す れば良いだけです。 5.2.2. 役に立つソフト gdb が最も広く使われています。ソースは GNU archive sites から、バイナリは tsx-11 から入手できます。 xxgdb は X 用のフロントエンドで(したがって先に gdb をインストールしておく必 要があります) 、ソースは にあります。 UPS デバッガも Rick Sladkey によって移植されています。これも X で動作 しますが、 xxgdb とは違って単なるテキストベースデバッガのフロントエン ドではありません。多くの便利な機能がありますので、デバッグ作業を行う人 はチェックしてみると良いでしょう。 Linux 用のコンパイル済みバイナリ と、オリジナルの UPS のソースコードへのパッチとが にあります。オリジ ナルのソースは . です。 デバッグに便利なツールをもう一つ紹介しましょう。 strace はプログラムが 発行するシステムコールを表示してくれます。またコンパイル済みのバイナリ から呼び出されるパス名を表示してくれるので、ソースがなくてもどのファイ ルが使われているかを知ることができますし、プログラムが引き起こす競合状 態(race condition)のレポートをしてくれるなど、いろいろ便利に使うこと ができます。プログラムがどのように動作しているかの学習にも適していま す。 strace の最新バージョン(現在 3.0.8)は にあります。 訳注:現在は 3.1.0.1 のようです。 5.2.3. バックグラウンド(デーモン)プログラム 典型的なデーモンプログラムでは fork() を実行し、親プロセスを終了しま す。するとデバッグのセッションもあっという間に終わってしまいます。 これを回避する一番簡単な方法は fork にブレークポイントをセットし、プロ グラムが停止したときに 0 を返すように強制することです。 (gdb) list 1 #include 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 } 5.2.4. core ファイル Linux はブート時の設定では core ファイルを作らない設定になっています。 作る設定にしたい場合は、シェルの組み込みコマンドを使って再設定してくだ さい。 C シェル系(tcsh など)では % limit core unlimited とします。 Bourne シェル系(sh、bash、zsh、pdksh)では次のようにしま す。 $ ulimit -c unlimited core ファイルの命名法をもうちょっと便利にしたい場合(例えばそれ自身良 く落ちるようなデバッガで他のプログラムが吐いた core ファイルをデバッグ するときなど)は、カーネルを少々変更すれば可能です。 fs/binfmt_aout.c と fs/binfmt_elf.c のコードの中に、以下のような部分があるはずです(新 しいカーネルの場合です。古いカーネルでは少々 grep してまわる必要がある かもしれません)。 memcpy(corefile,"core.",5); #if 0 memcpy(corefile+5,current->comm,sizeof(current->comm)); #else corefile[4] = '\0'; #endif この 0 を 1 に変えて 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 ライブラリにあります。したがってこの ような関数を用いるには、リンクの際に -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.so、 libfoo.a の順に探しま す。 libfoo.so は通常 libfoo.so.x へのシンボリックリンクとなっていま す。 6.4. 自分のライブラリを作る 6.4.1. バージョン管理 プログラムと同様、ライブラリにもバグはつきもので、時間とともに修正され ていきます。また新たな機能が追加されたり、関数の仕様が変更されたり古い ものが削除されたりもします。これはライブラリを使用するプログラムには問 題です。もし古い仕様に基づいている場合はどうすれば良いのでしょうか? したがってライブラリにはバージョン管理を用います。ライブラリに対して 行った変更を「小さい(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 のほうから見ることにしましょう。 6.4.2. ELF って結局なに? ELF (Executable and Linking Format)はもともと USL(UNIX System Laboratories)で開発されたバイナリ形式で、現在では Solaris と System V Release 4 で用いられています。 ELF は以前 Linux で用いられていた a.out 形式よりも柔軟性に富んでいたので、 GCC と C ライブラリの開発者たちは昨 年に ELF を Linux の標準バイナリ形式としても採用することに決めました。 6.4.2.1. だからなんだって? この節は `/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(Debug- ging 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 は、この文書と同じところ にあるはずです。 6.4.2.2. 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.1 と libfoo.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 ) 6.4.2.3. バージョンの番号付けと 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)に置かれると、 ldconfig は libfoo.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 これでうまく動くはずです。 6.4.3. a.out、汝古き形式よ ライブラリが ELF へ移行した主な理由は、共有ライブラリを簡単に作れると いう点にありました。しかし a.out でも作成が不可能なわけではありませ ん。 を 入手して、パッケージの中にある 20 ページのドキュメントを読みましょう。 私は一方の党派に偏るつもりはありませんが、でも今まで書いてきた文章で、 私がもう a.out を使うつもりが無いことは明らかでしょうかね :-) 6.4.3.1. ZMAGIC と QMAGIC 古い a.out の実行バイナリは ZMAGIC と呼ばれます。 QMAGIC は ZMAGIC と 似ていますが、最初のページをマップしない点が違っています。したがって 0〜4096 番地がどこにもマッピングされてないため、NULL ポインタから変数 を参照するという間違いを見つけやすくなっています(0 番地をアクセスする と、トラップされるわけです)。また別の利点としてバイナリが少々(1K く らい)小さくなります。 非常に古いリンカでは ZMAGIC のみに、やや古いものは両方に、最近のものは QMAGIC のみに対応しています。カーネルは両方のフォーマットを実行させる ことができますので、実際には気にする必要はありません。 プログラムが QMAGIC かどうかは、`file' コマンドを実行することによって 表示されます。 6.4.3.2. ファイルの配置 a.out の共有ライブラリは二つのファイルと一つのシンボリックリンクから構 成されます。この文書でこれまで使ってきた「foo」ライブラリを例にとりま すと、 libfoo.sa と libfoo.so.1.2 が実際のファイル、 libfoo.so.1 が libfoo.so.1.2 へのシンボリックリンクです。これらの役割はどんなもので しょうか? コンパイルの際に、 ld は libfoo.sa を探します。このファイルはライブラ リの「stub」ファイルと呼ばれ、外から参照できるデータと実行時リンクに必 要な関数へのポインタとを保持しています。 実行時には、ダイナミックローダは libfoo.so.1 を探索します。これは実 ファイルではなくシンボリックリンクになっていて、バグフィックスなどによ るアップデートの際に、このライブラリを使っていたプログラムに問題が生じ ないようにしています。新しいバージョンのライブラリ(libfoo.so.1.3 な ど)があれば、 ldconfig を実行することによってリンク先を変更でき、この ライブラリを用いていたプログラムには影響を与えなくてすみます。 DLL ライブラリ(トートロジーだということは承知しています :-p)は static なライブラリに比べて大きくなる場合が多いです。 DLL ライブラリに は将来の拡張に備えて、「hole」という形式の領域が確保されています。この hole 領域が実際にはディスクを消費しないようにすることもできます。単に cp するか、あるいは makehole コマンドを用います。(a.out では)アドレ スは固定されているので、ライブラリ構築後に strip することもできます。 なお ELF ライブラリは strip してはいけません! 6.4.3.3. 「libc-lite」とは? libc-lite は libc ライブラリの軽量版です。フロッピーでの利用に適し、 UNIX の基本的なタスクのほとんどをカバーしています。 libc-lite には curses、 dgm、 termcap などのコードは入っていません。もしお使いのシス テムの /lib/libc.so.4 がこの lite 版へのリンクでしたら、 full サイズの 版に置き換えた方が良いでしょう。 6.4.4. リンク:よくある問題 リンクの際に障害が起こったら私に教えてください!私が手助けできることは あまり無いかもしれませんが、同じのがたくさんきたらここに載せることはで きます... 共有形式でリンクしたいのに 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 に対応するスイッチをつけて実行す る方が便利かもしれません。 o LD_BIND_NOW 通常ライブラリ中の関数は、呼び出されるまでアクセスされ ません。このフラグを設定しておくと、ライブラリがロードされたときに 全ての関数が確認されます( したがって時間がかかります)。このオプ ションはプログラムをテストするときに、全てがちゃんとリンクされてい るかを確認するときに役に立ちます。 o LD_PRELOAD を使うと関数定義を「上書き」するファイルを指定できます。 例えばメモリ割り当て関数を実装して、これを `malloc' と置き換えたい 場合には、その実装を malloc.o にコンパイルして以下のようにします。 $ LD_PRELOAD=malloc.o; export LD_PRELOAD $ some_test_program LD_ELF_PRELOAD と LD_AOUT_PRELOAD も同じような機能を持ちますが、それぞ れのバイナリ形式に対してのみ有効になります。 LD_something_PRELOAD と LD_PRELOAD が同時に定義されている場合は、範囲の狭い指定の方が有効にな ります。 o LD_LIBRARY_PATH はコロンで区切られたリストで、共有ライブラリを検索 するディレクトリを指定します。この指定は実行時のみに用いられ、 ld には影響しません。また setuid や setgid されたプログラムには無効で す。これにも LD_ELF_LIBRARY_PATH と LD_AOUT_LIBRARY_PATH があり、バ イナリ形式によって異なる検索リストを指定できます。通常は LD_LIBRARY_PATH は必要ないでしょう。ディレクトリを /etc/ld.so.conf に加えて ldconfig を実行しなおす方をお勧めします。 o LD_NOWARN は a.out のみに適用されます。設定されていると(つまり LD_NOWARN=true; export LD_NOWARN)、ローダは致命的でない警告メッセ ージ(minor バージョンがあっていないなど)を出力しなくなります。 o LD_WARN は ELF にのみ適用されます。設定されていると通常は致命的エラ ーである ``Can't find library'' をワーニングに変えます。通常はあま り利用されることはないでしょうが、 ldd には重要な設定です。 o LD_TRACE_LOADER_OBJECTS は ELF にのみ適用されます。指定すると、それ ぞれの実行プログラムは、自分が ldd の下で実行されているものと考える ようになります。 $ LD_TRACE_LOADED_OBJECTS=true /usr/bin/lynx libncurses.so.1 => /usr/lib/libncurses.so.1.9.6 libc.so.5 => /lib/libc.so.5.2.18 7.4. ダイナミックロードを用いたプログラムを書く Solaris 2.x でのダイナミックロード機能と非常に似ています(といっても Solaris ユーザにしかわかりませんね)。具体的な内容は H. J. Lu の ELF programming document および dlopen(3) の man ページで詳述されていま す。後者は ld.so のパッケージに入っています。以下にもちょっとした例を 示します。 -ldl をつけてリンクしてください。 #include #include 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 に参加してください。どんなことが議論されて いるかを知りたいだけならば、リストのアーカイブが にあります。その次になすべきことはあ なた次第です! 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 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 が保有しています。この文書の全体あるいは一部は、物理的電子的を問わず、 あらゆるメディアに自由に複写することができます。ただしその際にはこの著 作宣言を全てのコピーに付加する必要があります。商業的な配布も許可し、ま た奨励します。しかしその際には著者にお知らせくださるようお願いします。 Linux HOWTO の文書を含んだ翻訳、修正および編集作業の成果は、全てこの著 作権条項に従う必要があります。すなわちこの HOWTO を修正した後に、配布 条件に追加項を加えることはできません。但し適当と認められた場合は例外と することもできます。 Linux HOWTO の管理者に連絡してください。アドレス は以下に示します。 要するに我々はこれらの情報を可能な限りの方法で広めたいと思っているので す。しかし我々は HOWTO 文書に関する著作権を保持し続けることを望んでい ますし、HOWTO を再配布する計画について知らせてもらうことも望んでいま す。 もし疑問点があったら、Linux HOWTO 管理者の Greg Hankins に連絡してくだ さい。電子メールのアドレスは gregh@sunsite.unc.edu です。 10. 索引 アルファベット以外の文字ではじまる言葉は ASCII 配列の順に並べてありま す。 o -fwritable-strings ``39'' ``56'' o /lib/cpp ``16'' o a.out ``1'' o ar ``10'' o as ``8'' o ``19'' o atoi() ``40'' o atol() ``41'' o binaries too big ``63'' ``65'' ``77'' o chewing gum ``3'' o cos() ``68'' o debugging ``59'' o dlopen() ``82'' o dlsym() ``83'' o documentation ``4'' o EINTR ``52'' o elf ``0'' ``71'' o execl() ``57'' o fcntl ``47'' o FD_CLR ``44'' o FD_ISSET ``45'' o FD_SET ``43'' o FD_ZERO ``46'' o file ``2'' o ``20'' o gcc ``6'' o gcc -fomit-frame-pointer ``61'' o gcc -g ``60'' o gcc -v ``14'' o gcc, bugs ``15'' ``28'' ``29'' ``84'' o gcc, flags ``13'' ``25'' ``26'' o gdb ``64'' o header files ``17'' o interrupted system calls ``51'' o ld ``9'' o LD_* environment variables ``80'' o ldd ``81'' o libc ``7'' o libg.a ``62'' o libgcc ``79'' o ``21'' o lint ``58'' o ``18'' o manual pages ``5'' o ``70'' o maths ``69'' o mktemp() ``55'' o optimisation ``27'' o QMAGIC ``76'' o segmentation fault ``30'' ``54'' o segmentation fault, in GCC ``33'' o select() ``50'' o SIGBUS ``34'' o SIGEMT ``35'' o SIGIOT ``36'' o SIGSEGV ``31'' ``53'' o SIGSEGV, in gcc ``32'' o SIGSYS ``38'' o SIGTRAP ``37'' o sin() ``67'' o soname ``73'' o sprintf() ``42'' o statically linked binaries, unexpected ``66'' ``78'' o ``23'' o ``24'' o strings ``11'' o ``48'' o ``49'' o ``22'' o version numbers ``12'' ``74'' o weird things ``72'' o ZMAGIC ``75''