Linux用にC言語のプログラムを作っていたろころ、実行したら
Segmentation fault (コアダンプ)
が出力されていた。
やばい雰囲気のする文言……
といことで、今回はSegmentation faultをコアダンプを使って調査する方法と、ぼくが作ったソースでSegmentation faultが発生した原因を説明していきます!
Segmentation faultとは?
ググった感じの代表的な原因が
- メモリアクセス違反
- 無限or深すぎる再帰処理
というのが原因とのこと。
一言でまとめると「よくないやーつ」ですね。
原因を調査するには、「コアダンプ」というものを使うのが定石ようです。
今回のソースコード
今回発生したのが、下記のようなコードです。
一部省略していますが
新しく作ったファイルを、前のファイルに上書きする
ということを、コマンドを作成して、system()を使って行っています。
パス名は伏せていますが、実際に使ったものを同じ長さになっています。
#include <stdio.h> #include <string.h> #define FNAME "/home/work/coredump/aaa/xxxxxxxxxxxxxxxxxxxxxxxx/data/zzzzzzzz/testfilexxxxx.txt" #define FNAME_NEW "/home/work/coredump/aaa/xxxxxxxxxxxxxxxxxxxxxxxx/data/zzzzzzzz/testfilexxxxx_new.txt" int main(void) { char cmd[128]; memset(cmd, 0, sizeof(cmd)); sprintf(cmd, "mv -f %s %s", FNAME_NEW, FNAME); system(cmd); return 0; }
コレをコンパイルして実行すると……
# ./a.out Segmentation fault (コアダンプ)
でちゃいました。
コアダンプで調べる
それではコアダンプを使って調べていきましょう。
コンソールでコアダンプを使って調査するためには、下記のような手順を踏めばようです。
- コアダンプのサイズ設定を行う
- gccの-gオプションでコンパイル(デバッグ情報付与)
- 実行→Coreファイルが生成される
- gdbでCoreファイルを読み込む
- 原因箇所を調査する
コアダンプのサイズ設定
まずはこのコマンドを実行します。
[root@localhost coredump]# ulimit -c 0 [root@localhost coredump]# ulimit -c unlimited [root@localhost coredump]# ulimit -c unlimited
厳密には、「ulimit -c」は、現在の値を調べているだけらしいので、下のコマンド「ulimit -c unlimited」だけでよさそうです。
「ulimit」は、システムリソースの上限を設定するコマンドらしい。
「-cオプション」は、コアダンプのサイズを設定。
「unlimited」、つまり上限なしにしています。
コンパイルでデバッグ情報を取得
次にコンパイルします。
いつものコンパイルに加えて「-g」をオプションにつけます。
[root@localhost coredump]# gcc -g main.c
これによって、デバッグ情報を付加することができます。
コンパイルが成功したら実行しましょう。
[root@localhost coredump]# ./a.out Segmentation fault (コアダンプ)
Segmentation faultが出ましたね。
ここでファイル一覧を表示してみると……
[root@localhost coredump]# ls -l 合計 168 -rwxr-xr-x. 1 root root 9656 4月 9 17:08 a.out drwxr-xr-x. 3 root root 38 4月 9 17:04 aaa -rw-------. 1 root root 245760 4月 9 17:08 core.3618 -rwxrwxrwx. 1 root root 404 4月 9 17:05 main.c
「core.XXXXX」というファイルができていますね。
これがコアダンプのファイルです。
gdbでコアダンプファイルを開く
gbdとはコンソールで扱うデバッガです。
このgdbを使って、コアダンプファイルを開くことで、Segmentation faultが発生した際のデバッグ情報を見ることができます。
[root@localhost coredump]# gdb a.out core.3618 GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-110.el7 Copyright (C) 2013 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>... Reading symbols from /home/work/coredump/a.out...done. [New LWP 3618] Core was generated by `./a.out'. Program terminated with signal 11, Segmentation fault. #0 0x0000000000400602 in main () at main.c:15 15 } Missing separate debuginfos, use: debuginfo-install glibc-2.17-222.el7.x86_64 (gdb)
「main.cの15行目」と言っていますね。
frameとやらを見ると方法が書いてあったので念のため見てみましょう。
frameはデータの位置的なものみたい(詳しくは理解していない)。
(gdb) bt #0 0x0000000000400602 in main () at main.c:15 (gdb) frame 0 #0 0x0000000000400602 in main () at main.c:15 15 }
今回の場合、最初の文面と特に変わりませんでした。
「q→Enter」で、gdbから抜けることができます。
コアダンプの内容を調査
コアダンプの調査手順を踏んで、内容を見てみたものの……
mainの15行目……
1 #include <stdio.h> 2 #include <string.h> 3 4 #define FNAME "/home/work/coredump/aaa/xxxxxxxxxxxxxxxxxxxxx xxx/data/zzzzzzzz/testfilexxxxx.txt" 5 6 #define FNAME_NEW "/home/work/coredump/aaa/xxxxxxxxxxxxxxxxxxxxx xxx/data/zzzzzzzz/testfilexxxxx_new.txt" 7 8 int main(void) 9 { 10 char cmd[128]; 11 memset(cmd, 0, sizeof(cmd)); 12 sprintf(cmd, "mv -f %s %s", FNAME_NEW, FNAME); 13 system(cmd); 14 return 0; 15 } // ←ここ
「}」です。
「} (閉じカッコ)」の行がNGだよ!
と言ってくる……
謎で仕方なかったが、なんやかんや見てみました……
すると原因は
用意したバッファを超えてアクセスしたこと
でした。
char cmd[128];
に対して、sprintf()作り出そうとしているコマンドは
mv -f /home/work/coredump/aaa/xxxxxxxxxxxxxxxxxxxxxxxx/data/zzzzzzzz/testfilexxxxx_new.txt /home/work/coredump/aaa/xxxxxxxxxxxxxxxxxxxxxxxx/data/zzzzzzzz/testfilexxxxx.txt
と、計171Byte。
完全にオーバーですね……
戦犯 sprintfの猛威を喰らいました……(というか完全に見落とし凡ミス)
凡凡凡ミスなわけですが、全然気づかず頭を抱えていました……つらい……
バッファのサイズを
char cmd[256];
など、増やすことで解決しましたとさ。
参考
コチラを参考にしました。ありがとうございました!
あとがき
LinuxのC言語で、Segmentation faultが発生したので、コアダンプを使って解決してみたお話でした!
結果的には凡ミスでしたが、基本的には原因が
- メモリアクセス違反
- 無限or深すぎる再帰処理
ということなので、ちょっと見つけにくいと思われる類のものです。
今回の例と同じような原因かはわかりませんが、Segmentation faultが出たらコアダンプを使って調査してみるといいと思います!