LABORATORY - TechniqueBox

メインメニューDIARYINFORMATIONLABORATORYLINK


ソフトウェアプログラミングデータベース


INDEX
  1. OS 依存のコアダンプ
  2. Java でクラスの相互参照ができない!?
  3. VNC を VineLinux 2.1.5 で使う
  4. ライブラリが足りない? メモリが足りない?
  5. Windows を終了できない…

#1: OS 依存のコアダンプ
記述日時不明
 Windows や Linux ではちゃんと動作する (ように思われる) のに、FreeBSD へ持っていくと突然コアダンプをするようになってしまったプログラムがありました。与えられた文章を構文解析して文に対する確率を計算する、というプログラムだったのですが、ある特定のデータ以外ならばちゃんと計算することができ、その計算結果も正しいものになります。特定のデータに対してのみ、コアダンプしてしまうのです。
 さて、これはいったいどういうことなのでしょうか?

 最初は『FreeBSD のカーネルにバグ (もしくはメモリを扱う上での癖など) があるのでは?』と思ったのですが、そう簡単に人のせい、しかもハッカーというものすごい方々のせいにしてしまうのは恐れ多いことです。そんなわけで、printf を使ってコアダンプの発生している部分をせっせと探します。
(どうして gdb を使わない? というと、単にわたしが使い方を 知らなかったから です…(^^;)
 printf をいろいろなところに埋め込んで、少しずつ問題の場所を特定していきます。printf を入れてもコアダンプが止まるわけでもありませんから、何回も何回も 60MB 近くあるコアダンプが生成されることに。かなり間抜けなことをしていたと思います。ちなみに、この作業は FreeBSD 上で行っていたため、コアダンプが発生しても OS が落ちるようなことがありませんでした。嗚呼、Win9x でなくて良かった……。
 さて、ひたすら、printf を埋め込んで実行して……を繰り返し、ついに問題を巻き起こしているソース中の行を特定することができました。その行には compprev、そして item という三つの変数が使われています。すべてポインタですから、おそらく、
  • 指しているメモリが解放されている
  • まったく関係ない部分を指している
というような理由のために、コアダンプしてしまっていたのでしょう。

 まずは、関係ない部分を示していないか、ポインタ値を表示させて確認してみます (また printf で…)。すると、それぞれ他のデータと似通ったようなアドレスになっており、とくに際だった問題があるようには感じられません。偶然にも似通ったアドレスになっている、という可能性もありますが、それだったら他のデータでももっと激しくコアダンプするはずです。ということで、この可能性はとりあえず除外しておきます。
 次は、指しているメモリが有効なものであるかどうかです。が、これもまた同じ理由で可能性を除外しました。めんどくさくなってき ポインタの付け替えやメモリの確保、解放に欠陥があると思えなかったというのもあります。
 って、ちょっと待った。それではいったい、何が原因だというのでしょうか? そこで、事態をさらに詳細に調べるため、上に示した三つの変数のうち、いったいどれがおかしくなっているのかというのを特定します。これは簡単で、参照先の値を自分に書き戻すコードを追加するだけです。さらに、その前後に printf を入れ、止まるかどうかを確かめる、と。
 …繰り返しますが、gdb の使い方がわかっていれば、こんなことはしません よ(笑)。デバッガ上でできることでしょうしね。いちおう、使ってもみたのですが、いまいちうまくいかなくて。
 さて、この方法によって問題を特定すると『item というポインタの指している領域がおかしい』ということが判明しました。このポインタ、キューからもらってきたポインタです。そのキューというのがちょっとトリッキーなことをしていて、返すデータの実体をキューとして確保しているメモリの最後尾に置いておき、そこへのポインタを返すという仕組みになっているのです。
 つまり、キューとして確保されているメモリが[A, B, C, *, *](* は空) となっていたら、キューをシフトして値を取り出すとバッファの内部は[B, C, *, *, A]となり、最後尾の A へのポインタが返されるというわけです。
 さて、この item ですが、よ〜くソースを見ると、その直前にある if ブロックの内部で、キューへのプッシュが行われています。
 …。
これか!!
 あはは……。なんという事はなく、移動してしまった後のメモリを参照していたのです。それじゃ、確かに動かんわな。
 キューへデータをプッシュしたとき、キューのサイズが足りなくなってしまうことがあります。そのときは仕方がないので realloc することで対処するのですが、realloc はメモリが移動するという可能性を潜ませています。item は移動前のキューを参照していたのでしょうが、新しいデータがキューにプッシュされたときにキューの拡張が行われ、同時にメモリも移動したとすれば…、すべてが解決します。
 そこで、キューのシフトにおいてポインタを受け渡しするのではなく、ポインタの示す先のデータ (構造体でした) を直接コピーするように変更を加えてみると、まるで何事もなかったかのようにスイスイ動作してくれました。やはり、メモリの移動が原因だったようです。
 では、どうして Linux などではうまく動いていたのでしょうか? それは、OS の確保するメモリ領域サイズが異なるためだと考えられます。Windows などでは多めにメモリを確保しているため、realloc が発生してもメモリが移動せず、結果としてポインタは正当なものとなっていたのでしょう。
 FreeBSD は厳格なメモリ管理を行っているのかもしれませんね。

#2: Java でクラスの相互参照ができない!?
記述日時不明
 Java には、あるクラスが他のクラスを import しようとしたとき、ソースファイルのタイムスタンプがクラスファイルのものより新しくなっていると自動的にそれをコンパイルしてくれるという機能があります。また、import しようとしたクラスファイルが存在しなかったときも同様のことを行ってくれます。
 では、クラス A とクラス B がお互いに import しているときはどうなるでしょうか。クラス AB.class を検索し、存在しなかった場合は B.java を探してそれをコンパイルするようにします。しかし B.javaA.class を必要としていて、A.class を作るためには A.java をコンパイルしなければなりません。ところがところが、A.java をコンパイルするには B.class が必要になって…と、ぐるぐる回り始めてしまうような気がしますよね。
 実際にはそんなことなどなく、ちゃんとコンパイルできます。
 しかし、package を指定したときは、若干話が変わってくるのです。今回の失敗は、それが原因でした。

 package x を指定して作成したクラス AB があったとします。また、A.java には
import x.B;
とあり、B.java には
import x.A;
とあります。では次に、これをコンパイルしてみましょう〜。まず A.java をコンパイルすると、import のところで x/B.class が検索されます。しかし、そんなクラスは存在しませんというエラーが…。あれ? クラスファイルがカレントディレクトリに置かれている…。
 というわけで、まず第一ポイント。「package を使ったときは javac に -d オプションを忘れずに」
 出力先ディレクトリを指定したから、今度は x/B.class も作られるぞ…と思いきや、今度は「x/B.class が見つからない」というエラーが。おいおい、それはあんたがコンパイルしてくれるのと違うのん? ということで仕方なく B.java をコンパイル。すると「x/A.class が見つからない」というエラーに。おいおいおいおい〜、それを作れないからこっちをコンパイルしとるんじゃボケぇー!
 はてさて、これはどうしたものかと、いろいろなドキュメントを手繰ってみます。すると「ソースパスの指定が必要」というところを発見。なるほど、これを指定すればいいのか〜、ということで -sourcepath を指定。

「x/B.class が見つからない」

… … …
ウキャー!

 さらにドキュメントを調べてみると、「ソースの検索位置 = ソースパス + パッケージ名」(クラスファイルも同様) という規則があることを発見。つまり? ソースファイルは ./x に、クラスファイルも ./x に置けばいいということか? で、./x/A.java などが検索されればいいから…、えーと、こうすればいいのかな?
 まず、./x/A.java./x/B.java を作成。で、コンパイル時はカレントディレクトリを ./x にして、javac -sourcepath .. -classpath .. -d .. とする。
 どうだ!?
 おおっ、成功!
 というわけで、ようやく package 内でのクラス相互参照ができるようになりました。第二ポイント。「ソースの検索位置 = -sourcepath の引数 + パッケージ名」
 では最後に、今回の教訓。便利なことをやってくれるソフトウェアを使うときには、それが「どんな仕組みで動いているのか?」というのを知っておくようにしましょう。そうでなければ、何かが起こったときに困ってしまいますから。

#3: VNC を VineLinux 2.1.5 で使う
記述日時不明
 まず、VNC 自体をインストールしなければなりません。VNC っていったい何? という方は、 google などで検索してください。とてもたくさんのページが見つかるはずです。以降では、VNC を知っていること、UNIX の基礎知識があるものとして話を進めます。

 さて、Linux にインストールする VNC は 3.3.3r2 以降にしましょう。これは、コンパイルなしに inetd で動作させるために必要です。それ以前のバージョンの場合は、ソース展開後にパッチ当てと再コンパイルが必要になります。バイナリを VNC のページからダウンロードしてきたら、適当な場所に展開します。すると、いくつかのバイナリと README などが展開されますので、README を読みつつ、必要なバイナリを /usr/local/bin へコピーしてください。
 Windows にインストールする VNC はどんなものでも構いません。できるだけ、最新のものを突っ込んでおきましょう。こちらは展開すると InstallShield のインストーラができます。あとはそれを実行するだけです。簡単ですね。

 次は Linux 側で /etc/inetd.conf/etc/services を編集します。これに関しては ゼロ円でできる X サーバ という記事に詳細が書かれていますので、そちらを参照してください。編集が終わったら、inetd に HUP を送るのを忘れないようにしてくださいね。もしくは、PC 本体の再起動を行います。/etc/rc.d/init.d にあるスクリプトを実行してもいいでしょう。
 さて、これで VNC 経由での接続ができるようになりました。しかし、このままでは文字化けや色化けが起こる可能性があります。少なくとも、わたしの環境ではその両方が発生しました。
 色化けはウィンドウマネージャに Sawfish を使っている場合、発生するようです。わたしが使用したときはビットマップの大半が色化けを起こしていました。もしかするとこれはウィンドウマネージャのプログラムにバグがあるのかもしれません。そこで WindowMaker を使うようにすると、まったく何の問題もなく動作してくれました。8 ビットカラーで使用すれば Sawfish でも問題ないのですが、それはちょっと嫌ですよね? 結局、色化けする事になってしまうし。

 文字化けの方は、ちょっとばかり複雑です。通常、VineLinux 2.1.5 では xfs という X 用のフォントサーバが使われています。このサーバを通してデータを持ってくるようにすると、文字化けが解消されます。
 /etc/inetd.conf に書かれた Xvnc の行を次のようにします。
vnc stream tcp nowait nobody /usr/local/bin/Xvnc Xvnc -inetd -query ホスト -once -geometry 800x600 -depth 8 -fp unix/:-1 -deferglyphs 16 -co /usr/X11R6/lib/X11/rgb
 -co に関しては、無くても何とかなるかもしれません。-deferglyphs は、なぜ必要なのかわかりません(笑)。インターネットで調べていたら見つかっただけですので。
 ともかく、上のようにしてフォントパスを設定すれば動くはずです。
 もしそれで動かなければ、-fp tcp/localhost:7100 などとすれば動くかもしれません。

 こちらのページ にある PDF には、かなり詳しく、いろいろなことが書かれています。設定に詰まったら、こちらも参照してみてください。
 ちなみに、VineLinux 2.1 では、inetd 経由でログインすると「5」キーが「BS」になってしまうそうな。これの原因は不明です。VineLinux 2.0 では起こらなかったということなので、アップデートの際に紛れ込んだバグなのでしょうか。
 強引にキーマップを変更するという手段によって対処することができます。ただし、「5」が入力できない状態なのに「5」を打たなければならないという矛盾した状況になっていますので、予めエイリアスを張っておくなどしておく必要があります。

#4: ライブラリが足りない? メモリが足りない?
記述日時不明
ELF interpreter /usr/libexec/ld-elf.so.1 not found
 このメッセージがすべての幕開けでした。

 研究に使うためのプログラム (以下では研究用プログラムと書きますね) をコンパイルし、実行すると直後に上のようなエラーが発生して、まったくプログラムが動作しないのです。わたしは「/usr/libexec/ld-elf.so.1 というファイルが見つからないのかな」と考え、上のエラーメッセージで WWW から同じような症例、および、ld-elf.so.1 を探し始めました。
 が、よくよく調べてみると、/usr/libexec の下にはちゃんと ld-elf.so.1 というファイルが置かれています。
「…存在するファイルが見つからない?」
 ldconfig も実行してみました。…ダメ。
 他の FreeBSD から ld-elf.so.1 をコピーしてきました。…ダメ。むしろ悪化(笑)。
 次はほかの FreeBSD マシンにソースを移して、そっちでコンパイルしてみました。…これまたダメ。
 ところが、SunOS では同じプログラムがちゃんと動作するのです。いったいこれはなぜ?
 研究室の他の人に手伝ってもらいながら原因究明を進めていると、「このエラーメッセージはもしかして、『ELF interpreter /usr/libexec/ld-elf.so.1』が『not found』と言っている (エラーメッセージを出力している) という意味なのではないか?」という仮説が。……だとすると? どうなるのでしょう(笑)。まあ、少なくとも、ライブラリが無い、というわけではないみたいです。ELF リンカはちゃんと認識されている、と。
 もしやすると gcc が原因か? というわけで、Hello World の単純なプログラムをコンパイルしてみると……ちゃんと動きます。

 もう残るはソースしか無い! ということで、ソースを眺めてみます。最初の「ライブラリが足りない (インストールされていない) のかな?」という疑問から、かなり遠ざかってしまいましたよね…。ソースを眺めていると、上記のエラーの原因になりそうな部分より、ソースの汚さの方が目についてしまったりして、なかなか思うように作業が進みません(笑)。
 うーん、ということで、これまでは FreeBSD 4.x で作業していたのですが、これを FreeBSD 2.2 で動いているマシンに持って行ってみます。こっちは ELF など無い環境なので、きっと違ったことになるだろう、と。コンパイル、さあ実行!
      Cannot allocate memory.
メモリが足りないと怒られてしまいました。今度はライブラリ絡みのエラーは出なかったぞ、ということで、ちょっとだけ進展した感じ。じゃあ、このメッセージは研究用プログラムのどこで出力されているんだ? と grep をかけてみますが…何も見つからない。どこにも該当するエラーメッセージを出力する部分がないのです。ということは、システムがメッセージを出しているのですよね。
 今度は Linux に持っていってみます。すると、コアダンプ(笑)。
 ソースを見ても、とくにおかしいような部分は無い…というか、SunOS では動いているのだからロジック的におかしいというような事はあり得ない。じゃあ、やっぱりライブラリが足りないのか? しかしそれでは 2.2 系で出てきたエラーメッセージの説明がつきません。

 ん? 待てよ? …もしかして、メモリが足りないのか?

 ソースを書き換え、while ループでプログラム開始時に止まるようにし、top で使用メモリを調べてみると…500MB もくっている! メモリ説の気配が一気に濃くなります。
 ならばということで、静的に確保している領域を大幅に減らし、コンパイルしてみます。
 今度は…動く! 動くぞ!
 次は FreeBSD のカーネルを書き換えて、利用できるメモリの領域を増やしてみます。メモリが 1G 近く載せてある PC があるのですけれど、その PC で使われている FreeBSD のカーネルはユーザー一人あたり最大で 500MB ほどしか使用できないようにコンパイルされていたのでした。リミットを書き換え、カーネルコンパイル、そして再起動!
 …カーネルパニック!? というのも当然で、載せられたメモリのギリギリまで確保してしまったのです。これではカーネルを置く場所がなく、カーネルパニックになるのも当然です。今度はちょっと控えめな値にして、再起動。今度はちゃんとうまく行きました。limit で使用できる最大メモリを確認すると、ちゃんと 1GB になっている。
 今度こそ! ということで実行すると…やった! 動いた!

 つまり、今回の事件の背景は次のようなものでした。
 まず、バイナリが ELF 形式なのでリンカの ld-elf.so.1 が呼び出されます。が、静的なデータ領域に 500MB 以上も要求されてしまったので、リンカは「そんなのできねー」とエラーを発生させます。ところがどっこい、そのエラーを捕獲するところがうまくできていないらしく、エラーメッセージとして「〜 not found」というものが表示されてしまっていたのです。
 つまり、まったくライブラリが原因に関与していないのに、表面に出てくるエラーメッセージにはライブラリの痕跡しか見つけられなかったため、混乱してしまったのです。

 なかなか遭遇しないような事態かもしれませんけれども、もしかすると、という事があるかも。

#5: Windows を終了できない…
記述日時不明
 Windows2000 系の話題です。おそらく WindowsXP でも通用する話だと思いますけれど。
 たとえば CD-R への書き込みソフトのようなハードウェアを扱うようなプログラム (カーネルモードでの動作がほとんどというプログラム) では、ひとたびハングアップしてしまうとどうにもできなくなってしまい、Windows の終了すらできない (ように見える) 場合があります。タスクマネージャでプロセスを強制終了させようとしても「できません」と却下されてしまいますし、「Windows の終了」を選択してもまったく変化無し、どうにもできないような場合です。

 が、実は後ろではシステムがプロセスの終了を待っていまして、一分ほど 待っていればちゃんとプロセスを殺して終了してくれます。また、ログアウト時にも終了していないプロセスを待つことがあり、そこでもかなりの時間がかかってしまう可能性があります。もしかすると、プロセスは終了しておらず、残したままシステムをシャットダウンしているという可能性もありますけれど。

 というわけで、「やべっ、終了しないっ!」という場合でも、しばらく待ってみましょう。辛抱強く、辛抱強く。アニメーションが起こっていたり、マウスカーソルを動かせる場合、Windows のシステムはまだ生きています。
 ただ、Windows95 などにおいては、ハングアップしたらほぼ絶望的なので、リセットしかないですね。

メインメニューDIARYINFORMATIONLABORATORYLINK