- PR -

C言語入門者:自作ライブラリの呼び出しでSegmentation fault

投稿者投稿内容
うーたん
常連さん
会議室デビュー日: 2008/09/16
投稿数: 34
投稿日時: 2009-02-20 13:33
あんとれさん

入門者にとってまたまたありがたいご助言をいただき
大変嬉しく思います。有難うございます!!

<最初のコード>
なるほど。
 int n = 0;
 int *ret = &n;
 subpg(ret);
とするのであれば、
 int n = 0;
 subpg(&n);
でも全く同じことだということですね。
納得しました。

<2つ目のコード>
動的にメモリを獲得(malloc)すれば、
segmentaion faultにならないのですね。
使い終わったらfreeでメモリを開放。
納得しました。

勉強になりました。
有難うございました!!!
koji
常連さん
会議室デビュー日: 2004/11/04
投稿数: 36
お住まい・勤務地: 北海道
投稿日時: 2009-02-20 17:02
mallocを使えばsegmentaion faultにならないかというと
そうでもなくて不用意に使うと容易に発生するかもしれません。

mallocの後に実際に確保出来たかどうかのチェックは入れた方がいいと思いますよ
if (ret == NULL) {
/* エラー処理 */
}

あとmallocで確保した領域の内容は不定なので注意してください。
うーたん
常連さん
会議室デビュー日: 2008/09/16
投稿数: 34
投稿日時: 2009-02-20 17:51
kojiさん

ご助言有難うございます。
malloc、NULLについて調べてみました。

■malloc
mallocは必ず成功するとは限らない。利用可能な空きメモリ領域がないとき、プログラムが限界値を超えてメモリを使用しようとしたときなど、mallocは ヌルポインタを返す。(Wikipediaより引用)

■NULL
ポインタの基本的な考え方は、どこかに実体があって、それがどこにあるかという情報を保存しておこう、というものです。すなわち、ポインタはどこかにある実体を指している矢印のようなものと考えられます。
ところで、ポインタを使う場合、どこも指していないことを明確に示したいことがよくあります。このような時に使う値が NULL です。すなわち、 NULL はどの実体も指さないことが保証されています。 (C MAGAZINE 1995年8月号より引用)

勉強になりました。
有難うございました!!!
angel
ぬし
会議室デビュー日: 2005/03/17
投稿数: 711
投稿日時: 2009-02-21 11:36
アプリケーションが Segmentation fault なんてしたらコトですので、解析できるようにしておいた方が良いです。
Segmentation fault時には、coreファイルが出来ますので、デバッガ(gdb)を使って、Segmentation fault時の挙動を追うことができます。
※coreファイルができない場合は、coreファイルサイズ制限がかかっているかもしれません。“ulimit -c”で設定を見て、小さいようなら“ulimit -c 値”で増やしましょう。( unlimited でも可 )
前提としては、ライブラリやプログラムが -g オプション付でコンパイルされていること、です。

以下、実行例です。なお、デバッガが元ソースを追えるよう、core とプログラム本体と、ソースファイルを全てカレントディレクトリに置いています。
コード:
[user@server tmp]$ file core.31169

core.31169: ELF 32-bit LSB core file Intel 80386, …(中略)…, from 'mainpg'
[user@server tmp]$ gdb mainpg core.31169
…(中略)…
Core was generated by `./mainpg'.
Program terminated with signal 11, Segmentation fault.
…(中略)…
#0 0x006f56ae in subpg (ret=0x804850c) at subpg.c:4 ※1
4 *ret = -1;
(gdb) bt ※2
#0 0x006f56ae in subpg (ret=0x804850c) at subpg.c:4
#1 0x080484a7 in main () at mainpg.c:7
(gdb) p ret ※3
$1 = (int *) 0x804850c
(gdb) up ※4
#1 0x080484a7 in main () at mainpg.c:7
7 subpg(ret);
(gdb) p ret ※5
$2 = (int *) 0x804850c
(gdb) quit



gdbを起動した時点で、どこで停止したかが分かります。( ※1 )
Segmentation fault ということは、不正なメモリアクセス ( 無効なメモリを読み書きした/読み込み専用メモリに書き込んだ ) なので、この時点で ret の値が怪しいと推測できます。
なお、※2 の bt コマンドにより、関数の階層を見ることができます。この場合は、main から呼び出された subpg で止まっていることが分かります。

さて、実際に ret の値を見ているのが ※3 です。i386-Linux の場合、ポインタ変数の値はCPUが扱う仮想メモリのアドレスと一致しますから、アドレス 0x804850c へ書き込むのはマズいよね、と分かります。

なお、subpg がサードパーティ製ライブラリにあって、デバッグ情報が拾えない場合。subpg を呼び出している main 側でないと情報が拾えません。
※4 の up コマンドで関数の呼び出し階層をあがって、main 側の ret を見ているのが ※5 です。subpg に渡している ret 0x804850c がマズいというのが見て取れます。

[ メッセージ編集済み 編集者: angel 編集日時 2009-02-21 13:06 ]
angel
ぬし
会議室デビュー日: 2005/03/17
投稿数: 711
投稿日時: 2009-02-21 13:32
なお、アドレス 0x804850c がなぜマズいのか、はカンで言っている部分もありますので、メモリ割り当てと対応させて見てみます。
デバッガ(gdb)上でプログラムを実行し、途中で停止させてみます。
コード:
[user@server tmp]$ LD_LIBRARY_PATH=. gdb mainpg

…(中略)…
(gdb) b main ※1
Breakpoint 1 at 0x804849c: file mainpg.c, line 7.
(gdb) run ※2
Starting program: /home/user/tmp/mainpg

Breakpoint 1, main () at mainpg.c:7
7 subpg(ret);
(gdb) p ret
$1 = (int *) 0x804850c
(gdb) p &ret ※3
$2 = (int **) 0xbfeb07a4
(gdb)
※Ctrl-Z入力
[2]+ Stopped LD_LIBRARY_PATH=. gdb mainpg
[user@server tmp]$ pgrep mainpg ※4
18211
[user@server tmp]$ cat /proc/18211/maps ※5
00527000-0053c000 r-xp 00000000 09:00 751001 /lib/ld-2.3.2.so
0053c000-0053d000 rw-p 00015000 09:00 751001 /lib/ld-2.3.2.so
0053f000-00672000 r-xp 00000000 09:00 293867 /lib/tls/libc-2.3.2.so
00672000-00675000 rw-p 00133000 09:00 293867 /lib/tls/libc-2.3.2.so
00675000-00678000 rw-p 00000000 00:00 0
00cc8000-00cc9000 r-xp 00000000 09:04 688398 /home/user/tmp/libtest.so
00cc9000-00cca000 rw-p 00000000 09:04 688398 /home/user/tmp/libtest.so
08048000-08049000 r-xp 00000000 09:04 688406 /home/user/tmp/mainpg
08049000-0804a000 rw-p 00000000 09:04 688406 /home/user/tmp/mainpg
bf50a000-bf50b000 rw-p 00000000 00:00 0
bfeaf000-c0000000 rw-p fffa1000 00:00 0
[user@server tmp]$ fg
quit
The program is running. Exit anyway? (y or n) y



※1で、main関数をブレークポイントと設定しておいて、※2 でプログラムを実行します。すると、main関数の開始時点でプログラムが一時停止します。
この時の mainpg のPIDは 18211 でしたので(※4で調査)、※5 でLinuxからメモリ割り当て状況を見てみました。
で、0x804850c というのは mainpg を読み込み専用(r-xp)で割り当てている 08048000-08049000 の領域にありますから、そもそも書き込みができないことがわかります。
なお、この領域は、プログラムコードや読み取り専用の変数等が格納されています。
※mainpg.c(mainpg)で定義したグローバル変数であれば、08049000-0804a000 にあることが期待できます。同様に、subpg.c(libtest.so) で定義したグローバル変数であれば、00cc9000-00cca000 にあることが期待できます。

参考として、ret 自体はメモリのどこに格納されているか ※3 で見てみました。
0xbfeb07a4 は、bfeaf000-c0000000 の領域に含まれます。
ここは、局所変数が保存されるスタック領域になります。
※mallocで確保される領域(ヒープ)はまた別になります。

今回は、不正なポインタ値を経由したメモリ書き込みが直ぐに Segmentation fault を引き起こしたため、バグに気付くことができましたが、誤って読み書きしたのがどこか別の有効なメモリ領域の場合、後になって不可解な動作として現れることになります。
※破壊したのがスタック上の特定領域の場合、デバッガで挙動を追えなくなることもあります。
そのため、ポインタ周りのバグは非常にやっかいです。

[ メッセージ編集済み 編集者: angel 編集日時 2009-02-21 13:37 ]
angel
ぬし
会議室デビュー日: 2005/03/17
投稿数: 711
投稿日時: 2009-02-21 13:52
以下、余談です。

・main の型
main は void main(void) ではありません。
int main(void) もしくは、int main(int argc, char *argv[]) ( argv は char **argv でも同等 ) です。

・main の返り値
上記により、main は int型の値を返すことになります。
main から抜ける所では return 文を書きましょう。
なお、正常終了の場合は 0 を返すようにします。
※main から return するのは、exit を呼ぶのと同じ効果になります。

・プロトタイプ宣言
外部で定義している subpg のプロトタイプ宣言は、会議室に書くときに省略している、でしょうか?
mainpg.c では、subpg関数の情報が分からないので、プロトタイプ宣言 ( void subpg(int *ret); ) を書きましょう。
ただし、一般的には、プロトタイプ宣言をヘッダファイルとして分離しておいて、mainpg.c では #include により取り込むようにするものかとは思いますが。
うーたん
常連さん
会議室デビュー日: 2008/09/16
投稿数: 34
投稿日時: 2009-02-23 09:54
angelさん

デバッグ視点からのご助言ありがとうございます。
Linux,C言語の経験が浅いため、
gdb(GNUデバッガ)の存在は今回はじめて知りました。
シンボリックデバッグ機能を持ったツールなのですね。
C言語を今後扱っていく上では必須のツールであると感じました。
詳細な具体例はわかりやすく大変勉強になりました。
書込みにも多くのお時間を割いていただいたと存じます。
感謝いたします。

あと、「余談」についても
入門者としては大変勉強になります。
・void main(void)は実は会議室への書込時のパンチミスではあったのですが、あまり深く考えていませんでした。「void main(void)は駄目」とは思っていなかったのです。int main(void)もしくは、int main(int argc, char *argv[])のいずれかなのですね。勉強になりました。
・外部の関数を使う際にプロトタイプ宣言が必要とは知りませんでした。今回のmainpg.cには実際書いていませんがコンパイルが通っています。何か限定的な条件でコンパイルが通っているだけなのでしょうか。今後はプロトタイプ宣言を書くようにいたします。

本当に有難うございました!!!
うーたん
常連さん
会議室デビュー日: 2008/09/16
投稿数: 34
投稿日時: 2009-02-23 10:08
追記

先程プロトタイプ宣言をしなくてもコンパイルが通っていますと書き込みましたが、
警告: implicit declaration of function ‘subpg’
は出ていました。

プロトタイプ宣言をしたところ、
この警告は出なくなりました。

以上です。

スキルアップ/キャリアアップ(JOB@IT)