ソフト作りのこと

C programming tips

Last updated:
2002.03.19. coreを吐かせる


coreを吐かせる

普段使っているソフトが突然落ちて、 そこらじゅうにcoreを吐くのはこまりものですが、 デバッグをしたい時には、coreほど大事なものはありません。

bashでは、ulimitというコマンドでcoreの最大の大きさを制御します。 制限には、二種類あり、ulimit -Hでハード・リミットを、 ulimit -Sでソフト・リミットを操作します。 -H-Sも付けない場合は、 両方の制限が同時に操作されてしまいます。 coreの大きさそれ自身は、ソフト・リミットに制限されます。 ハード・リミットは 一度制限してしまうと、それ以上にはできないが、 ソフト・リミットは、ハード・リミットの範囲内で自由に設定できます。

ulimit -Hでハード・リミットが、 ulimit -Sでソフト・リミットが、 確認できます。 普段は.bashrculimit -S 0してcoreの生成を抑制し、 必要になったら、 ulimit -S unlimitedしてcoreを採取するのがいいかも。

ハード・リミットが制限されてしまっている場合は、bashが起動時に読む、 /etc/profileなども注意してみるといいかも。

gprof

ソフト中のどの関数がどのくらい時間を消費するかわかる。 うーむ。やっぱり微分は大変なのだ、とわかったり。

  1. -g -pgをつけてコンパイル。gcc -c -Wall -g -pg hoge.cとか。
  2. 普通にリンク。gcc -o hoge hoge.o -lmとか。
  3. 普通に実行。hogeとか。 gmon.outというファイルができる。
  4. 結果を見る。gprof hoge gmon.out | less

共有ライブラリを扱う

Labviewなんてものを使いはじめたら、自分で共有ライブラリを作るハメに。 …あー。ライブラリ中でexit()しちゃうと悲惨なことになりますな。

共有ライブラリについては、 Program Library HOWTO (川崎貴彦さんによる和訳)が詳しいです。

共有ライブラリのコンパイル

共有ライブラリは以下の方法で生成します。 so.の後は、メジャーバージョン番号.マイナーバージョン番号.…で、 メインプログラムを再コンパイルしなきゃいけないような変更のときは、 メジャーバージョン番号を大きくしてライブラリを作りなおし、 リンクを張りなおすことになります。 あ、ln -sは本当はライブラリのインストール先でやらなくちゃね。

gcc -fPIC -g -c -Wall hoge.c
gcc -shared -Wl,-soname,libhoge.so.1 -o libhoge.so.1.0.1 hoge.o -lc
ln -s libhoge.so.1.0.1 libhoge.so.1
ln -s libhoge.so.1 libhoge.so

標準のパスに無い共有ライブラリにリンクする。 -Lの後に、ライブラリを探すパスを指定します。

gcc -Wall -c fuga.c
gcc -Wall -o fuga -L`pwd` -lhoge fuga.o

LD_LIBRARY_PATH

普段は、共有ライブラリのパスは、 ldconfigが/etc/ld.so.confを参照しながら管理してくれます。 が、ちょっとだけ共有ライブラリを試したいときは、

export LD_LIBRARY_PATH=`pwd`:$LD_LIBRARY_PATH
でちょっと作った共有ライブラリの場所を、 ローダに教えてあげることもできるようです。

ldd

共有ライブラリへの依存関係を表示する。 例えば、

ldd -r /usr/bin/ssh
        libdl.so.2 => /lib/libdl.so.2 (0x40019000)
        libnsl.so.1 => /lib/libnsl.so.1 (0x4001d000)
        libz.so.1 => /usr/lib/libz.so.1 (0x40034000)
        libutil.so.1 => /lib/libutil.so.1 (0x40043000)
        libpam.so.0 => /lib/libpam.so.0 (0x40047000)
        libcrypto.so.0 => /usr/lib/libcrypto.so.0 (0x4004f000)
        libc.so.6 => /lib/libc.so.6 (0x4010a000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
で、sshコマンドが必要とする共有ライブラリがわかります。 -rオプションは、 足りないオブジェクト(ってなんだ?)を表示してくれるオプション。 ちゃんと動いているプログラムでは何も表示されませんが、
ldd -r /usr/lib/libgsl.so
        libm.so.6 => /lib/libm.so.6 (0x40134000)
        libc.so.6 => /lib/libc.so.6 (0x40152000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x80000000)
undefined symbol: gsl_blas_daxpy        (/usr/lib/libgsl.so)
undefined symbol: gsl_blas_dnrm2        (/usr/lib/libgsl.so)
undefined symbol: gsl_blas_dgemv        (/usr/lib/libgsl.so)
undefined symbol: gsl_blas_dscal        (/usr/lib/libgsl.so)
undefined symbol: gsl_blas_dtrsv        (/usr/lib/libgsl.so)
undefined symbol: gsl_blas_idamax       (/usr/lib/libgsl.so)
undefined symbol: gsl_blas_ddot (/usr/lib/libgsl.so)
とすると、gsl_blas_daxpyなどの関数がlibgsl.soでは定義されていない (使うときは他のライブラリもリンクしなくちゃいけない) ことがわかります。
nm -o /usr/lib/libgsl*.so | grep gsl_blas_daxpy$
/usr/lib/libgsl.so:         U gsl_blas_daxpy
/usr/lib/libgslblas.so:000046b0 T gsl_blas_daxpy
/usr/lib/libgslblascblas.so:00007b80 T gsl_blas_daxpy
とすると、libgslblas.soをリンクしとけばいいこともわかります。 おぉ、これは便利だ。

:p> gslは、GNUの科学技術計算ライブラリです。 詳しくは、gslのサイト を、Kondara 1.2のRPMは、 RPMを作ってみる からどうぞ。

nm

共有ライブラリに定義されている(されていない)シンボルを表示する。 例えば、

nm -D /usr/lib/libm.so | less
  :
         U sprintf
0000b6b0 W sqrt
  :
とすると、libm.soに定義されている関数が何かわかります。 -Dは動的なシンボル(ってなんだ?)だけを表示する。 表示された情報のうち、2番目のアルファベット一文字はシンボルタイプで、 大文字はグローバル、小文字はローカル、 T:コードセクション内の普通の定義、 W:weak (もしも他のライブラリもこのシンボルを定義していた場合、 その定義がオーバーライドする) U:未定義 (シンボルはライブラリによって使われているが、 ライブラリ内では定義されていない) D:初期化されたデータセクション、 B:初期化されていないデータセクション、 だそうです。

浮動小数点計算の例外処理の捕捉

gcc/Linuxで普通にソフトを走らせると、 浮動小数点演算でおかしな計算をさせても、 実行が続いてしまいます。 デバッグの時には止ってほしいものだ。 SIGFPEのハンドラを作っても、SIGFPEは来ないみたい。

そのままいっちまう例

  1. ソース zero.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    int main(void)
    {
      double a, b, c;
      a = 1;
      b = 0;
      c = a / b;
      printf("%g / %g = %g\n", a, b, c);
      return 0;
    }
    
  2. コンパイルgcc -o zero zero.c
  3. 実行
    $ ./zero
    1 / 0 = inf
    

対策

浮動小数点プロセッサのオプションを変えているのかな? http://cthulhu.ale.org/ale-archive/ale-2000-08/msg00433.html からの引用です。 Thank you very much, Mr. Justin Russell

  1. 以下のソースを用意 trapfpe.c
    #include <fpu_control.h>
    static void __attribute__ ((constructor)) trapfpe (void)
    {
      fpu_control_t cw =
        _FPU_DEFAULT & ~(_FPU_MASK_IM | _FPU_MASK_ZM | _FPU_MASK_OM);
     _FPU_SETCW(cw);
    }
    
  2. コンパイル gcc -c trapfpe.c
    これで、trapfpe.o というオブジェクトファイルができる。
  3. 実行ファイルにリンクする。

というわけで、

やってみました。まずは gdb なし。

  1. コンパイル、リンク gcc -o zero zero.c trapfpe.o
  2. 実行
    $ ./zero
    Floating point exception
    

ね?なかなかのものだ。 gdb zeroからrunすると、 main()の中で例外が起きたことがわかる。 次はどこで例外が起きたのかわかるようにしよう。 以下でも触れた、gdbを利用する。

  1. コンパイル、リンク gcc -o zero zero.c trapfpe.o -g
    -gオプションを付けるとgdbからソースの行番号がわかるのだ。
  2. gdbから実行
    $ gdb zero
    GNU gdb 5.0
    Copyright 2000 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB.  Type "show warranty" for details.
    This GDB was configured as "i586-redhat-linux"...
    (gdb) run
    Starting program: /path/to/zero 
    
    Program received signal SIGFPE, Arithmetic exception.
    0x8048408 in main () at zero.c:10
    10        c = a / b;
    (gdb) quit
    The program is running.  Exit anyway? (y or n) y
    

うむ、しあわせじゃ。 というわけで、以上の例も、 Linux ホスト名 2.2.16-1k7 #1 Sat Jun 24 00:35:43 JST 2000 i686 unknown での実行結果でした。

型とバイト数-FITSの取り扱いで時間をつぶした話

double型が4バイトと思いこんでたんですな。 以下のソースの実行結果。uname -aと一緒に。

Linux ホスト名 2.2.16-1k7 #1 Sat Jun 24 00:35:43 JST 2000 i686 unknown

char  :1 bytes
short :2 bytes
int   :4 bytes
long  :4 bytes
float :4 bytes
double:8 bytes

ソース

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
  fprintf(stdout, "char  :%ld bytes\n", sizeof(char));
  fprintf(stdout, "short :%ld bytes\n", sizeof(short));
  fprintf(stdout, "int   :%ld bytes\n", sizeof(int));
  fprintf(stdout, "long  :%ld bytes\n", sizeof(long));
  fprintf(stdout, "float :%ld bytes\n", sizeof(float));
  fprintf(stdout, "double:%ld bytes\n", sizeof(double));
  return 0;
}

モデル計算ソフトでSEGVって苦労した時のお話し

SEGVる、というのは、Segmentation fault になるということです。 man 7 signal などで意味がわかります。 結局は、同じポインタに2回freeをしていたことが原因でした。


Back to zunda.

[zunda]
zunda <zunda at freeshell.org>