Linuxシステム開発において、core dump(コアダンプ)はプログラムがクラッシュした際にその状態を分析するための重要なツールです。プログラムがセグメントフォールトや不正命令などの理由で異常終了した場合、Linuxシステムはプロセスのメモリイメージ、プログラムカウンタ、レジスタステートなどの情報をファイルに記録します。このファイルこそがcore dumpです。
開発者にとって、core dumpファイルはプログラムクラッシュ時の現場情報を含む宝箱のようなものです。core dumpファイルを解析することで、クラッシュ時のメモリレイアウト、関数呼び出しスタック、変数値などの重要情報を得ることができます。これにより問題の原因を迅速に特定し、コードを最適化し、プログラムの堅牢性を向上させることができます。
本記事では、Linuxでのcore dumpの解析方法について説明します。具体的な例を通じてcore dump解析の実際の応用を示し、読者がこの技術をより深く理解できるよう支援します。
core dumpとは
コアダンプは、プロセスが特定のシグナルを受信して終了する際に、その時点でプロセスアドレス空間の内容やプロセス状態に関する他の情報がディスクファイルとして書き出されるものです。UNIX系システムでは、「メインメモリ」を「core」と呼び、半導体がメモリ材料として使用される以前は「core」が使われていました。「core image」とはプロセス実行時のメモリ内容を指します。プロセスがエラーを起こしたり、シグナルを受信して終了した場合、システムはそれをデバッグ用にファイルに書き出します。
core dumpが発生する原因
C/C++プログラマーがよく遭遇する問題の一つが、自身が書いたコードが実行中に意図しないcore dumpを引き起こすことです。core dumpの原因は多岐にわたりますが、代表的なものを以下にまとめます。
- 空ポインタまたは不正ポインタによるcore dump
空ポインタや不正ポインタ(ワイルドポインタ、ダングリングポインタ)によるcore dumpは最も一般的です。主な原因は以下の通りです:
- 空ポインタに対する参照操作。
- ポインタ変数を宣言した後、初期化せずに操作を行うと、高確率でcore dumpが発生します。このような未初期化のポインタを総称して「ワイルドポインタ」と呼びます。
freeまたはdelete関数を使用してポインタが指す領域を解放した後、そのポインタをNULLに再設定しなかった場合、そのポインタは「ダングリングポインタ」となります。ダングリングポインタに対して再度操作を行うとcore dumpが発生します。
- 配列またはポインタの範囲外アクセスによるcore dump
以下は配列の範囲外アクセスによってcore dumpが発生する簡単な例です。
#include <stdio.h>
int main() {
int i;
int array[6];
for (i = 0; i < 8; i++) {
array[i] = 0;
printf("Grayson Zheng\n");
}
return 0;
}
上記のコードでは、配列の要素数を超えた範囲にアクセスしようとするため、メモリオーバーフロウが発生し、プログラムがクラッシュします。
また、ポインタの範囲外アクセスによるcore dumpも存在します。このような場合、問題が発生したコード自体には問題がないこともあり、隠れた原因を見つけるのが難しいことがあります。
- データ競合によるcore dump
マルチスレッド環境でグローバル変数にアクセスする際、適切な同期保護を行わない場合、データ競合が発生し、プログラムがクラッシュすることがあります。これにより、予期せぬメモリ値の変更やセグメンテーションフォールトが引き起こされ、core dumpが生成されることがあります。
core dumpの解析方法
- core dumpの有効化
デフォルトでは、プログラムがクラッシュしてもcore dumpファイルは生成されません。これは、システムのRLIMIT_CORE(コアファイルサイズ)リソース制限がデフォルトで0に設定されているためです。
次のコマンドでcoreファイルの生成を有効にできます。
ulimit -c unlimited
echo 1 | sudo tee /proc/sys/kernel/core_uses_pid
echo "/tmp/core-%e-%s-%u-%g-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
- core dumpのトリガー
以下のシンプルなCプログラムを使ってcore dumpを発生させてみましょう。
#include <stdio.h>
void func() {
int *p = NULL;
*p = 13;
}
int main() {
func();
return 0;
}
これをコンパイルして実行すると、core dumpが発生します。
- gdbを使ったcore dumpの解析
core dumpファイルをgdbで解析する手順は以下の通りです。
gdb <プログラム名> <core_dumpファイル>
例えば、先ほどの例では次のように入力します。
gdb example /tmp/core-example-11-1000-1000-88496-1719910934
gdb内でbtコマンドを使用してスタックトレースを確認できます。また、listコマンドで該当のコード部分を表示し、printコマンドで変数の値を確認できます。