<daniel.barlow@linux.org>
<nakano@apm.seikei.ac.jp>
注意: この文書はかなり以前に書かれたものなので、 いまどきの Linux 環境にはあてはまらない箇所があります。 (JF Project)
現在 Linux におけるプログラム開発環境はどんどん変化しています。 Linux で実行できるバイナリには二種類の形式が在り、システムの設定 によってこれらを使い分けることが出来ます。この HOWTO を読む前に、読者 のシステムではどちらを使っているかを調べておくと良いでしょう。
さて、どうすればわかるでしょうか?答えは `file' コマンドを使う、です
(file /bin/bash
のようにします)。ELF の実行ファイルなら
「ELF
」 という文字列がメッセージにあらわれますし、 a.out の実行ファ
イルなら「Linux/i386
」という文字列を含む表示が出ます。
ELF と a.out の違いについてはこの文書の後ほどの部分で詳細に解説します。 ELF の方が新しいフォーマットであり、一般にはより良いものとされています。
著作権などの法的な件に関しては、この文書の最後に示してあり ます。また Usenet で間抜けな質問をしたり、バグでないことをバグだと報告 して C 言語についての無知をさらしたり、ガムを噛みながら歩きまわったり することに関する法的な警告も示してあります。
訳注:言うだけ野暮ですが、後の方のは冗談です :-)
。
この文書を Posstscript、dvi、html のどれかでお読みの方は、テキスト
版を読んでいる方より少々多くのフォントを見ることになります。ファイル名、
コマンド名、コマンド出力、ソースコードなどからの引用は typewriter
フォントで、その他の強調すべき内容は emphasized フォントで示します。
便利な索引も用意しました。 dvi と Postscript では index の数字は章や節 の番号です。HTML では数字は単に出てきた順番ですが、クリックによってそ の内容に飛ぶことが出来ます。テキスト版では本当に「単なる」順番になって しまいます。他の形式で見て下さい!
例には Bourne シェルを用いています。 C シェルをお使いの方は、
$ FOO=bar; export FOO
となっているところを
% setenv FOO bar
と読み変えて下さい。
プロンプトに $
でなく #
を用いているところでは、その
コマンドは root で実行する必要があることを示しています。もちろんこの文
書での例を実行した結果にたいする責任は私はとれません。幸運を :-)
この文書は Linux HOWTO シリーズの一つですので、 Linux HOWTO が置か れているところ(例えば http://sunsite.unc.edu/pub/linux/docs/HOWTO/ )ならばどこにでもあるはずです。 HTML 版(おそらくは多少新しい版)をお望みでしたら http://ftp.linux.org.uk/~barlow/howto/gcc-howto.html をどうぞ。
gcc に関する公式な文書は配布ソース(後述)に texinfo 形式およ
び *.info
ファイルとして入っています。CD-ROM がある方、充分高
速なネットワークを利用できる(あるいは辛抱強い)方は配布パッケージを手
に入れて untar し、適宜必要なものを /usr/info
にコピーするだけ
です。以上のいずれも不可能な方は、
tsx-11
でもこの文書を入手できます。ただしこちらは常に最新版であるとは限りませ
ん。
libc に関する情報源は二つあります。 GNU libc には info ファイルが付属 しており、これには Linux の libc の内容が(stdio を除いて)比較的正確 に記述されています。また man ページ のアーカイブは Linux 向けに書かれたもので、システムコール(セクション 2)と libc の関数(セクション 3)に関する記述がたくさんあります。
二つの選択があります。
gcc-2.7.2.bin.tar.gz
というファイルです。
訳注:翻訳時では 2.7.2.1 が最新版です。
configure
スクリプトですべての設定が完了し
ます。
tsx-11
にも有用なパッチが置かれていることがありますので、チェックしておきましょ
う。
ごく初歩的なコード以外をコンパイルするには以下のようなものも必要になり ます(実はほとんどの初歩的なコードにも必要です)。
ここで必要になるものは、システムで 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 形式 のプログラムの作成や利用をしない限りはこのパッケージは必要ありません。
今までのものと同じく、
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 を用いるようにしましょう。
現在利用している 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 のバージョンによってこれらの意味あいが変わっ
ています。
linux
は 2.7.0 以降のバージョンなら ELF、その前の場合は
a.out を意味します。linuxaout
は a.out 対応です。このエントリは linux
の
内容が 2.7.0 以降で a.out から ELF に変更されたことにより導入
されました。したがって 2.7.0 より前のバージョンではこのエントリが現れ
ることはありません。
linuxelf
というのは古い書式です。これが現れるのはほとんどの
場合 ELF バイナリを出力するように設定された 2.6.3 の gcc です。ところ
で gcc 2.6.3 は ELF コードを作成するにあたってバグがあることが知られて
います。したがってこの表示が現れた場合はアップグレードをお薦めします。2.7.2
バージョン番号です。
したがって私は ELF コードを出力する 2.7.2 版の gcc を使っていることに なります。そうだったのか!
gcc のインストールの時によそ見をしていた場合や、バイナリパッケージ の一部として gcc をインストールした場合、ファイルシステムのどこを探せ ば gcc があるのかを知りたいことがあるかもしれません。以下重要なものを 示します。
/usr/lib/gcc-lib/
target/
version/
とそのサブディレクトリにはコンパイラ本体の大部分が置かれています。コン
パイラ本体のプログラムとバージョンに特有のライブラリ、インクルードファ
イルがあります。
/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
/usr/
target/(bin|lib|include)/
。複
数のターゲットシステム(例えば a.out と ELF、あるいは各種のクロスコン
パイラなど)に応じて gcc を複数インストールした場合、Linux 以外向
けのライブラリや binutils (as
、 ld
など)、ヘッダファイルな
どはこのディレクトリ下に置かれます。一種類の gcc しかインストールして
い場合でも、まあ要するにここに関連ファイルが置かれるわけです。ここ
にない場合は /usr/(bin|lib|include)
にあります。
/lib
や /usr/lib
などは native なシステムのた
めのライブラリが置かれるディレクトリです。コンパイラ以外のアプリケーショ
ンの中には、ここに /lib/cpp
が置かれることを想定しているものが
あります(特に X でよく使われます)。ない場合は
/usr/lib/gcc-lib/
target/
version/
からコピーしてくるかシンボリックリンクを張りましょう。
利用者各自が /usr/local/include
にインストールするものを
除くと、 Linux で主に利用されるヘッダファイルは以下の 3 つになります。
/usr/include/
とそのサブディレクトリにあるヘッダファイ
ルのほとんどは H. J. Lu が提供している libc バイナリパッケージのもので
す。「ほとんど」と書いたのは、他にもここにヘッダファイルを置くパッケー
ジがあるからです。例えば curses
や dbm
などがそうです。古い
libc パッケージは curses と dbm もいっしょになっていましたが、最新のも
のでは別パッケージになっています。
/usr/include/linux
と /usr/include/asm
ディレ
クトリのファイル(これらは <linux/*.h>
、
<asm/*.h>
のようにインクルードされます)。これらのディ
レクトリはカーネルソースツリーのうちの linux/include/linux
ディ
レクトリおよび linux/include/asm
へのシンボリックリンクとなっ
ています。少々複雑なプログラムを開発する場合はインストールしておく必要
があります。これらはカーネルのコンパイルのためだけのものではないのです。
カーネルソースを展開するだけではだめで、 make config
を実行す
る必要があります。このコマンドによってはじめて作成される
<autoconf.h>
は多くのファイルによって参照されていますし、
カーネルのバージョンによっては 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 .
<float.h>
や <limits.h>
、
<varargs.h>
、 <stdarg.h>
、 <stddef.h>
といったファイルはコンパイラのバージョンによって内容が変わっています。
したがってこれらは
/usr/lib/gcc-lib/i486-box-linux/2.7.2/include/
か、あるいは似
たような名前の場所に置かれています。gcc のソースコードがあればその中の INSTALL ファイルの指示に従うだ
けでできるはずです。コンパイルを行うコンピュータの種類が XXX
でし
たら configure --target=i486-linux --host=XXX
として make
す
れば、すべてやってくれるはずです。ただし実際のコンパイルには Linux 特
有のヘッダファイル、カーネルのヘッダファイルが必要になりますし、クロス
アセンブラ、クロスリンカのソースを
ftp://tsx-11.mit.edu/pub/linux/packages/GCC/
から入手してそれぞれビルドする必要があります。
うーん。 "emx" パッケージとか "go" エクステン ダなどを使うと可能になるそうです。 ftp://sunsite.unc.edu/pub/Linux/devel/msdos を見てみて下さい。
私はこれらを試したことがないので、性能について保証することはできません。
手元の 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 準拠ではないからです。
コンパイラのスイッチに関する文書は gcc の info に書かれています。
(Emacs からは C-h i
として `gcc' オプションを選びます)。インス
トールに用いたバイナリ配布パッケージによってはこれが入っていなかったり
古かったりすることがあります。その場合は gcc のソースアーカイブを
ftp://prep.ai.mit.edu/pub/gnu
やミラーサイトから入手し、その中の info をコピーして使いましょう。
gcc の man ページ gcc.1
は、一言で言ってしまうと内容が古いです。
これは man ページそのものの中でも警告されています。
gcc のコマンドラインに -O
n をつけると出力される
コードを最適化することができます。 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 の文書を良く読んで、彼らが必要とする 情報に関して理解してからにしましょう。
最近では『もし Linux に移植されていないプログラムがあったとしたら、
それはそもそも移植されるべき価値が無いのだ』とも言われています。 :-)
もう少し真面目に。しかし一般に Linux の 100% POSIX 準拠を満たすには ソースを少々変更するだけで良いはずです。行なった変更はプログラムの原著 者にフィードバックすると良いでしょう。以降は `make' だけで実行ファイ ルができるようにしてもらえるかもしれません。
bsd_ioctl
、 daemon
および <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
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 は以下のようなシグナルハンドラを実行します。
/proc
のファイルに対する select()
、 pause()
、 connect()
、 accept()
、 read()
。write()
。open()
。ioctl()
。fcntl()
への F_SETLKE
コマンド。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
フラグを用いる必要があります。
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 ページを見て下さい。
Linux では広く用いられている lint はありません。ほとんどの人が gcc
の出す warning に満足しているからです。おそらく -Wall
スイッチが
もっとも役にたつでしょう。これは `Warnings, all' の意味です。しかしこ
のスイッチからは、どちらかというと頭を叩き付けたくなるもの(wall)を連
想しそうですね。
パブリックドメインの lint が ftp://larch.lcs.mit.edu/pub/Larch/lclint から入手できます。どのくらい良いかについては私は知りません。
コンパイルとリンクの全ての段階において -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 }
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..
の行の方を有効
にしてください。
プロファイリングとはプログラムのどの部分が最も多く呼ばれ、最も長い時間
を食っているのかを調べることです。無駄な時間を使っているコードを最適化
するのに良い方法です。プロファイルを行うにはタイミング情報が必要なオブ
ジェクトファイルを -p
をつけてコンパイルします。出力されたファイ
ルから情報を得るには gprof
が必要になります(binutils のパッケー
ジに入っています)。詳細は gprof
の man ページを見てください。
あらかじめお断りしておきますが、この章の構成は複雑になっています。 互換性のないバイナリの形式が二つあること、 static なリンクと共有ライブ ラリを使う場合があること、「リンク」という言葉が「コンパイルの後に行う 作業」と「コンパイルされたプログラムが呼び出されたときに行われること」 という二つの意味で用いられること(「ロード(load)」という言葉も使われま す。ただし逆の意味で) などが原因です。でも今あなたが読んだばかりの文よ りごちゃごちゃしている部分は少ないはずですから、それほど心配しないでく ださい。
多少なりとも混乱を少なくするために、実行時に行われることは「動的ロード」 と呼ぶことにして、その内容は次の章に書きます。同じ内容を「動的リンク」 と書いている文書もありますが、この文書では「動的リンク」は用いま せん。要するに、この章ではコンパイルの最終段階として行うリンクに ついてのみを扱います。
プログラム作成の最終段階は「リンク」と呼ばれます。全ての部品を結合
して、足りないものがないかどうか調べる作業です。「ファイルを開く」といっ
たような類の作業は、多くのプログラムで行われます。したがってこのよ
うな機能を持つ「部品」はライブラリの形で提供されています。普通の 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)
」となります。
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
が必要になるわけです。
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
へのシンボリックリンクとなっています。
プログラムと同様、ライブラリにもバグはつきもので、時間とともに修正 されていきます。また新たな機能が追加されたり、関数の仕様が変更されたり 古いものが削除されたりもします。これはライブラリを使用するプログラムに は問題です。もし古い仕様に基づいている場合はどうすれば良いのでしょうか?
したがってライブラリにはバージョン管理を用います。ライブラリに対して行っ
た変更を「小さい(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 (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 は、この文書と同じと ころにあるはずです。
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 )
各々のライブラリは 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
これでうまく動くはずです。
ライブラリが ELF へ移行した主な理由は、共有ライブラリを簡単に作れ
るという点にありました。しかし a.out でも作成が不可能なわけではありません。
ftp://tsx-11.mit.edu/pub/linux/packages/GCC/src/tools-2.17.tar.gz
を入手して、パッケージの中にある 20 ページのドキュメントを読みましょう。
私は一方の党派に偏るつもりはありませんが、でも今まで書いてきた文章で、
私がもう a.out を使うつもりが無いことは明らかでしょうかね :-)
古い a.out の実行バイナリは ZMAGIC と呼ばれます。 QMAGIC は ZMAGIC と 似ていますが、最初のページをマップしない点が違っています。 したがって 0〜4096 番地がどこにもマッピングされてないため、NULL ポインタ から変数を参照するという間違いを見つけやすくなっています(0 番地をアク セスすると、トラップされるわけです)。 また別の利点としてバイナリが少々(1K くらい)小さくなります。
非常に古いリンカでは ZMAGIC のみに、やや古いものは両方に、最近のものは QMAGIC のみに対応しています。カーネルは両方のフォーマットを実行させる ことができますので、実際には気にする必要はありません。
プログラムが QMAGIC かどうかは、`file' コマンドを実行することによって 表示されます。
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 してはいけません!
libc-lite は libc ライブラリの軽量版です。フロッピーでの利用に適し、
UNIX の基本的なタスクのほとんどをカバーしています。 libc-lite には
curses、 dgm、 termcap などのコードは入っていません。もしお使いのシス
テムの /lib/libc.so.4
がこの lite 版へのリンクでしたら、 full
サイズの版に置き換えた方が良いでしょう。
リンクの際に障害が起こったら私に教えてください!私が手助けできることは あまり無いかもしれませんが、同じのがたくさんきたらここに載せることはで きます...
ld
が共有ライブラリを検索できるよう、それぞれリンクがちゃんと張ら
れているか確認してください。 ELF の場合はシンボリックリンク
libfoo.so
が、 a.out では libfoo.sa
が実ファイルへ張られていな
ければなりません。この問題は ELF binutils を 2.5 から 2.6 に更新したと
きに非常に多く報告されました。以前のバージョンではライブラリの検索を
「賢く」行っており、全てのリンクがなくても動作することがありました。新
しいバージョンでは他のアーキテクチャとの整合性をとるために、この機能を
削除しました。また推測を誤ると、より深刻な問題を引き起こす可能性が生じ
ることも削除された理由の一つです。
libc.so.4.5.x
以上では、 libgcc は共有ライブラリではなくなりまし
た。したがって問題が生じた行の「-lgcc
」は「`gcc
-print-libgcc-file-name`
」に置き換えてください。バッククォート「`」を
お忘れなく。
また /usr/lib/libgcc*
は全て削除してください。こちらも重要で
す。
__NEEDS_SHRLIB_libc_4 multiply defined
というメッセージが出るこれは上と同種の問題です。
この謎のメッセージは、恐らくジャンプテーブルの 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
でコンパイルしておけば、
ほとんどの場合は充分なデバッグ情報が得られます。
この章は今のところまだ短いです。私が ELF HOWTO の内容を消化するに連れ、 だんだん拡張されていくでしょう。
前の章全部を一気に読んだ人はもう聞き飽きたかもしれませんが、 Linux は 共有ライブラリを利用しています。「名前を検索して置き換える」という動作 が、リンク時から実行時に先送りされるようになったのです。
リンクエラーが起こったら送ってください!解決はできないかもしれませんが、 ここにまとめたいと思います...
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 バージョンが古いライブラリを使っている場合に出ます。多分プログ ラムはちゃんと動くと思います。でもアップグレードした方が良いでしょうね。
ダイナミックローダが参照する環境変数は多岐に渡っています。これらのほと
んどは ldd
だけが使うものなので、 ldd
に対応するスイッチをつ
けて実行する方が便利かもしれません。
LD_BIND_NOW
通常ライブラリ中の関数は、呼び出されるまでアク
セスされません。このフラグを設定しておくと、ライブラリがロードされたと
きに全ての関数が確認されます( したがって時間がかかります)。このオプショ
ンはプログラムをテストするときに、全てがちゃんとリンクされているかを確
認するときに役に立ちます。
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
が同時に定義さ
れている場合は、範囲の狭い指定の方が有効になります。
LD_LIBRARY_PATH
はコロンで区切られたリストで、共有ライブラ
リを検索するディレクトリを指定します。この指定は実行時のみに用いられ、
ld
には影響しません。また setuid や setgid されたプログラムには無
効です。これにも LD_ELF_LIBRARY_PATH
と LD_AOUT_LIBRARY_PATH
があり、バイナリ形式によって異なる検索リストを指定できます。通常は
LD_LIBRARY_PATH
は必要ないでしょう。ディレクトリを
/etc/ld.so.conf
に加えて ldconfig を実行しなおす方をお勧めし
ます。
LD_NOWARN
は a.out のみに適用されます。設定されていると
(つまり LD_NOWARN=true; export LD_NOWARN
)、ローダは致命的でない
警告メッセージ(minor バージョンがあっていないなど)を出力しなくなりま
す。
LD_WARN
は ELF にのみ適用されます。設定されていると通常は
致命的エラーである ``Can't find library'' をワーニングに変えます。通常
はあまり利用されることはないでしょうが、 ldd には重要な設定です。
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
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");
}
}
まず問題を絞り込んでください。 Linux に特有の問題か、他のシステム の gcc でも起こるか?カーネルのバージョンに固有の問題か?ライブラリの バージョンは?リンクを static にすれば解決するか?問題を起こすプログラ ムを短いデモンストレーション版に切りつめることができるか?などで す。
これが済んだら、どのプログラムにバグがいるかがはっきりすると思います。
GCC の場合は、バグレポートの手続きは info ファイルで説明されています。
ld.so や C ライブラリ、 math ライブラリの場合は
linux-gcc@vger.rutgers.edu
にメールを送ってください。可能ならば短
く、バグの存在をはっきりと示すプログラムを添付し、そのプログラムで想定し
ていた動作と実際の動作についての説明も送ってください。
GCC や C ライブラリの開発に参加したい場合は、まずメーリングリスト
linux-gcc@vger.rutgers.edu
に参加してください。どんなことが議論さ
れているかを知りたいだけならば、リストのアーカイブが
http://homer.ncm.com/linux-gcc/
にあります。その次になすべきことはあなた次第です!
「我々」と言う言葉を使うことができるのは、大統領と編集者、そして 体内にサナダ虫を飼っている人々のみである(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 メーリングリストの皆さんに有益なご指摘を頂きました。
現時点ではこの文書の翻訳版はありません。翻訳してみようと思う方、どうぞ お願いします。でも私に知らせてください!私が翻訳先の言葉を話せる可能性 は(残念なことに)100 対 1 以下でしょうが、それはともかく私は喜んでお 手伝いするつもりです。
歓迎します。私のアドレス daniel.barlow@linux.org 宛にメールしてください。 私の PGP 公開鍵(ID 5F263625)は web ページ にあります。通信の秘密を必要とする方は利用してください。
訳注:原文でのメールアドレスは dan@detached.demon.co.uk
と
なっていますが、 daniel.barlow@linux.org
の方がアクセスが良い
そうです。
訳注:この節は原文も示します。
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
です。
アルファベット以外の文字ではじまる言葉は ASCII 配列の順に並べてありま す。
-fwritable-strings
39
56 ar
10 as
8 atoi()
40 atol()
41 cos()
68 dlopen()
82 dlsym()
83 execl()
57 fcntl
47 FD_CLR
44 FD_ISSET
45 FD_SET
43 FD_ZERO
46 file
2 gcc -fomit-frame-pointer
61 gcc -g
60 ld
9 LD_*
environment variables
80 libg.a
62 <math.h>
70 mktemp()
55 SIGBUS
34 SIGEMT
35 SIGIOT
36 SIGSYS
38 SIGTRAP
37 sin()
67 sprintf()
42 strings
11 <sys/time.h>
48 <unistd.h>
49