- 技術 💻
Segmentation faultの原因と直し方|gdb・ASanで行を特定
結論
Segmentation fault は、プログラムが使ってはいけないメモリに触ったため、OS が SIGSEGV(シグナル11)で止めたという意味です。原因の行は -g を付けてビルドし直し、gdb の bt で1分あれば特定できます。落ちないメモリ破壊まで探すなら AddressSanitizer を使います。
この記事でわかること
- NULL・範囲外・解放後・スタックあふれなど原因7種の再現と直し方
- gdb・コアダンプ・ASan・Valgrind・dmesg で落ちた行を出した原文
- Python(ctypes・C拡張)の segfault と faulthandler、終了コード139
この記事で実際に動かしたもの:C で書いた12本の小さなプログラム(NULL・範囲外・文字列リテラル・解放後・再帰・巨大な配列・scanf・二重解放など)を Ubuntu 24.04 でわざと落とし、gdb・コアダンプ・AddressSanitizer・UBSan・Valgrind・dmesg・addr2line で原因の行を出し、直したあとの出力まで確かめました。Python では ctypes と自作の共有ライブラリで落とし、faulthandler と gdb で場所を出しました。
flowchart TD
A[Segmentation fault] --> B{自分でビルドした?}
B -->|はい| C[-g -O0 でビルドし直す]
C --> D[gdb で run と bt]
D --> E{落ちた行で原因が分かる?}
E -->|はい| F[直して再実行]
E -->|いいえ| G[-fsanitize=address]
G --> F
B -->|いいえ| H[コアダンプと dmesg]
H --> I[版を確かめて報告]
よく調べられることの答えを、手を動かす順に並べました。
| やりたいこと | いちばん早い方法 | 詳しい節 |
|---|---|---|
| Segmentation fault (core dumped) の意味を知りたい | 禁止されたメモリに触って SIGSEGV で止まった。core dumped はメモリの写しを保存した印 | Segmentation faultとは |
| 落ちた行を知りたい | gcc -g -O0 でビルドし、gdb ./a.out → run → bt |
最初の5分 |
| 原因の当たりを付けたい | NULL・範囲外・解放後・スタックあふれ・リテラル書き換えの5つを先に疑う | 原因の一覧 |
| core ファイルが見つからない | ulimit -c unlimited と /proc/sys/kernel/core_pattern を見る |
コアダンプの出し方 |
| 落ちたり落ちなかったりする | -fsanitize=address でビルドして動かす |
AddressSanitizer |
| Python で Segmentation fault | python3 -X faulthandler で Python 側の行を出す |
Pythonで出る場合 |
| 終了コード139の意味 | 128+11(SIGSEGV)。134は Abort、135は Bus error | Segmentation faultとは |
| 再帰で落ちる | ループに書き換える。一時しのぎは ulimit -s |
スタックあふれ |
確認環境:Ubuntu 24.04.5 LTS(x86_64、Linux 6.18、glibc 2.39)の上で、gcc 13.3.0・g++ 13.3.0・clang 18.1.3・GNU gdb 15.1・Valgrind 3.22.0・GNU bash 5.2.21・Python 3.13.18 を使いました。コアダンプの設定(ulimit -c)は自分のシェルの中だけで変え、ホストの /proc/sys/kernel/core_pattern(値は core)は読むだけにしました。そのため systemd-coredump と coredumpctl、macOS の lldb、Windows のアクセス違反、Docker の終了コードは、各公式ドキュメント(core(5)・coredumpctl(1)・LLDB・Microsoft Learn など)で確かめた範囲で書いています(確認日 2026年11月5日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Segmentation faultの原因と直し方|gdb・ASanで行を特定」
Segmentation fault (core dumped)とは(意味と終了コード139)
この節では、エラー文の各部分の意味と、終了コード139からほかのクラッシュと見分ける方法が分かります。
Segmentation fault は、プロセスが「割り当てられていないアドレス」や「書き込みが禁止されたアドレス」にアクセスしたため、カーネルが SIGSEGV というシグナルを送って止めた、という意味です。日本語では「セグメンテーション違反」、略して「セグフォ」とも呼ばれます。signal(7) では SIGSEGV は「Invalid memory reference(無効なメモリ参照)」で、既定の動作は「Core(終了してコアダンプ)」です。
$ ./null
start
Segmentation fault (core dumped)
$ echo $?
139
| 表示の部分 | 意味 | 誰が出しているか |
|---|---|---|
| Segmentation fault | SIGSEGV で止まった | シェル(bash がシグナル名を表示) |
| (core dumped) | メモリの写し(コアファイル)を書き出した | シェル(カーネルの結果を見て付ける) |
| 終了コード 139 | 128+11。11 は SIGSEGV の番号 | シェル(bash の決まり) |
| core ファイル | gdb で開くと落ちた瞬間の状態が見える | カーネル(core_pattern の場所へ) |
プログラム自身は何も表示していません。そのため、同じクラッシュでも、シェルや実行のしかたで見え方が変わります。手元で確かめた違いです。
- 対話のシェル:
Segmentation fault (core dumped)またはSegmentation fault - シェルスクリプトの中:
s4.sh: line 1: 5637 Segmentation fault ./null(行番号と PID が付く) - パイプの途中(
./null | cat):何も表示されなかった。${PIPESTATUS[@]}は139 0 - dash(
sh):Segmentation faultと139 - Python の
subprocess.run:returncode = -11(負の数がシグナル番号)
bash のマニュアルは、シグナル N で終わったコマンドの終了ステータスを 128+N とする、と定めています。zsh では zsh: segmentation fault、macOS では Segmentation fault: 11 のような表示になります(各ドキュメントの範囲)。
Bus error・Aborted・Floating point exception との見分け
似たクラッシュを手元でわざと起こし、対話の bash で表示と終了コードを並べました。139 なら SIGSEGV、134 なら abort()(ライブラリが自分で止めた)と読み分けます。
$ ./dfree; echo $?
free(): double free detected in tcache 2
Aborted
134
$ ./smash AAAAAAAAAAAAAAAAAAAAAAAA; echo $?
*** stack smashing detected ***: terminated
Aborted
134
$ ./fpe; echo $?
Floating point exception
136
$ ./bus; echo $?
Bus error
135
$ sleep 30 & kill -SEGV $!; wait $!; echo $?
[1]+ Segmentation fault sleep 30
139
| 表示 | シグナル(番号) | 終了コード | 手元で起こした原因 |
|---|---|---|---|
| Segmentation fault | SIGSEGV(11) | 139 | NULL 参照・範囲外・スタックあふれ |
| Aborted | SIGABRT(6) | 134 | 二重解放・スタック破壊の検出・C++ の例外が捕まらない |
| Bus error | SIGBUS(7) | 135 | 中身0バイトのファイルを mmap して読んだ |
| Floating point exception | SIGFPE(8) | 136 | 整数を0で割った |
| Killed | SIGKILL(9) | 137 | メモリ不足で OOM killer に止められた など |
最後の行で分かるとおり、kill -SEGV で外から送っても同じ表示になります。ほかのプロセスや監視ツールが送った可能性もゼロではありません。シグナルの一覧は関連記事「Linux killコマンドの使い方|シグナル一覧とkill -9・pkill」で、終了コードの読み方は関連記事「シェルスクリプトの終了ステータス $?とexitコード一覧・判定方法」で扱っています。
Segmentation faultの原因の一覧(どれに当たるか)
この節では、Segmentation fault になる代表的な原因と、手元で落ちたか・どの道具で見つかったかが一覧で分かります。
原因はほぼ「ポインタが指す先がおかしい」か「スタックを使い切った」のどちらかです。ただし、範囲外や解放後の使用は、落ちずに動き続けることがあります。手元で12本をビルドして動かした結果です(gcc 13.3、-g -O0)。
| 原因 | 典型的なコード | 手元の結果 | 見つけた道具 |
|---|---|---|---|
| NULL ポインタの参照 | u->id(u が NULL) |
139 | gdb・ASan・UBSan |
| 文字列リテラルの書き換え | char *s = "hello"; s[0] = 'H'; |
139 | gdb(si_code=2) |
| 配列の範囲外(少しだけ) | a[5](要素は5個) |
落ちず 0。でたらめな値 | ASan・UBSan |
| 配列の範囲外(大きく) | a[100000000] |
139 | gdb・dmesg |
| 解放後の使用 | free(buf) のあとで buf を読む |
落ちず 0。文字化け | ASan・Valgrind・gcc の警告 |
| 終わらない再帰 | 終了条件のない関数 | 139(深さ7,791回で) | gdb・ASan(stack-overflow) |
| 大きなローカル配列 | char big[16MB] |
139 | dmesg・ulimit -s で変わる |
| scanf の & 忘れ | scanf("%d", n) |
139(libc の中で) | gcc の警告(-Wformat) |
| C 拡張・ctypes に NULL | lib.score(None) |
139 | faulthandler・gdb |
すぐ落ちる(見つけやすい)
NULL 参照・リテラルの書き換え
大きく外れた添字・スタックあふれ
gdb の bt で、ほぼその行が出る
落ちないことがある(危ない)
1つずれた添字・解放後の使用
ほかの変数を黙って壊し、別の場所で落ちる
ASan・Valgrind でないと見つけにくい
最初の5分:Segmentation faultの行をgdbで見つける
この節では、自分でビルドしたプログラムが落ちたときに、原因の行と変数の値を最短で出す手順が分かります。
最初にすることは、デバッグ情報付きでビルドし直して gdb で動かすことです。手元の null.c(NULL を受け取る関数)で試します。
#include <stdio.h>
struct user { int id; char name[16]; };
int get_id(struct user *u) {
return u->id; /* u が NULL なら、ここで落ちる */
}
int main(void) {
struct user *u = NULL; /* 見つからなかったときの戻り値を想定 */
printf("start\n");
printf("id=%d\n", get_id(u));
return 0;
}
-g -O0 でビルドし直す
gcc -g -O0 -Wall -Wextra null.c -o null。-g で行番号と変数名が残り、-O0 で最適化による行の入れ替わりを防ぐ。警告もここで読む。
gdb の中で動かす
gdb ./null を起動し、run(引数があれば run 引数)。落ちた瞬間に止まり、関数名・ファイル名・行が出る。
bt で呼び出しの流れを見る
bt(backtrace)で、どの関数から呼ばれたかが #0・#1 の順に出る。libc の中で落ちていたら、自分のファイルが出る最初のフレームを見る。
変数を print する
print u・info locals・up(1つ上のフレームへ)で値を見る。0x0 なら NULL。
分からなければ ASan でビルド
行は分かったが値がおかしい理由が分からない、落ちる場所が毎回違う、といったときは -fsanitize=address に進む。
gdb 15.1 で動かした出力です(スレッドの情報の行を省いています)。$_siginfo で、どのアドレスに触って落ちたかも見られます。
$ gdb -q ./null
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x0000555555555179 in get_id (u=0x0) at null.c:6
6 return u->id; /* u が NULL なら、ここで落ちる */
(gdb) bt
#0 0x0000555555555179 in get_id (u=0x0) at null.c:6
#1 0x00005555555551ac in main () at null.c:12
(gdb) print u
$1 = (struct user *) 0x0
(gdb) print $_siginfo.si_code
$2 = 1
(gdb) print $_siginfo._sifields._sigfault.si_addr
$3 = (void *) 0x0
si_codeが 1 なら SEGV_MAPERR(割り当てのないアドレス)、2 なら SEGV_ACCERR(権限のない書き込みなど)です。文字列リテラルを書き換えた strlit.c では$2 = 2、アドレスは0x555555556004(読み取り専用の領域)でした。-gを付けずにビルドするとget_id ()と関数名しか出ず、print uはNo symbol "u" in current context.になりました。stripしたものは?? ()だけでした。
printf が出ないまま落ちる理由
./null | cat のように出力をパイプやファイルに向けると、落ちる前の printf("start\n") が表示されませんでした。標準出力がバッファーにたまったまま、書き出される前にプロセスが止まるためです。printf で場所を探すときは、fprintf(stderr, ...) を使うか、先頭で setvbuf(stdout, NULL, _IONBF, 0); を呼ぶと、start が出てから落ちました。gdb のほうが確実なのは、この取りこぼしが無いからです。
dmesg の segfault 行と addr2line
gdb を使えない場面でも、Linux はカーネルのログに1行残します(debug.exception-trace が 1 のとき。手元は 1)。手元の null を落としたときの行です。
$ dmesg | grep segfault
null[2782]: segfault at 0 ip 000055a09f281179 sp 00007fffa14d84c0 error 4 in null[1179,55a09f281000+1000] likely on CPU 1 (core 1, socket 0)
strlit[2640]: segfault at 5567db726004 ip 00005567db725164 sp 00007ffd203535b0 error 6 in strlit[1164,5567db725000+1000] likely on CPU 0 (core 0, socket 0)
$ addr2line -f -e null 0x1179
get_id
/dev/shm/t290/c/null.c:6
segfault at の後ろが触ったアドレス、in null[1179,…] の 1179 が実行ファイルの中での位置です。addr2line に渡すと、gdb と同じ null.c:6 が出ました。error 4 はユーザー空間からの読み込み、error 6 は書き込みで起きたことを表します(x86 のページフォルトのエラーコード)。
原因1:NULLポインタの参照(再現と直し方)
この節では、最も多い NULL ポインタの参照を、落ちないコードに直す書き方が分かります。
「見つからなかったら NULL を返す」関数の戻り値を確かめずに使うのが、典型的なパターンです。malloc・fopen・getenv・strchr なども失敗すると NULL を返します。直し方は、NULL を受け取ったときの道筋を呼ぶ側に返すことです。
#include <stdio.h>
struct user { int id; char name[16]; };
int get_id(const struct user *u, int *out) {
if (u == NULL) return -1; /* 呼ぶ側に「無かった」を返す */
*out = u->id;
return 0;
}
int main(void) {
struct user *u = NULL;
int id;
if (get_id(u, &id) != 0) {
fprintf(stderr, "user not found\n");
return 1;
}
printf("id=%d\n", id);
return 0;
}
$ ./null_fixed
user not found
$ echo $?
1
NULL への備え
見分ける問い:「NULL が来るのは正常な場合か、前の処理の失敗か」
UBSan(-fsanitize=undefined)でビルドすると、落ちる直前に null.c:6:13: runtime error: member access within null pointer of type 'struct user' と、何をしたかを文章で出しました。
原因2:配列の範囲外アクセスと文字列リテラルの書き換え
この節では、範囲外アクセスが「落ちる場合」と「落ちない場合」の違いと、文字列リテラルを書き換えて落ちる理由が分かります。
添字が少しだけはみ出しても、そのアドレスがプロセスのメモリの中なら落ちません。手元の oob.c(要素5個の配列を引数の添字で読む)で試しました。
$ ./oob 5
a[5] = 32767
$ echo $?
0
$ ./oob 100000000
Segmentation fault
$ echo $?
139
a[5] はとなりのメモリを読んで、でたらめな値(32767)を返しました。-O2 でビルドしても同じく落ちず、値だけが 32766 に変わりました。落ちないから正しい、とは言えないのが範囲外アクセスの怖さです。直し方は、添字を 0〜N-1 に限ることです。
#define N 5
if (i < 0 || i >= N) { /* 0 〜 N-1 だけ許す */
fprintf(stderr, "index %ld is out of range (0..%d)\n", i, N - 1);
return 1;
}
$ ./oob_fixed 5
index 5 is out of range (0..4)
C++ では、std::vector の v[i] は範囲を調べず、手元でも大きな添字で Segmentation fault(139)になりました。v.at(i) に変えると、std::out_of_range の例外になり、何が起きたかが文章で出ました。
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 100000000) >= this->size() (which is 3)
Aborted
文字列リテラルの書き換え
char *s = "hello"; の "hello" は、読み取り専用の領域に置かれます。s[0] = 'H'; で書き込むと、gdb の si_code は 2(SEGV_ACCERR:権限の違反)でした。配列として char s[] = "hello"; と書けば、書き換えられる領域に写されます。直したものは Hello と表示して終了コード 0 でした。
原因3:解放後の使用(use-after-free)は落ちないことがある
この節では、free したメモリを使うバグが、なぜ Segmentation fault にならずに動いてしまうのかと、見つけ方が分かります。
char *buf = malloc(32);
strcpy(buf, "order-1001");
free(buf);
printf("after free: %s\n", buf); /* 解放後の使用 */
$ ./uaf
after free: |�/d
$ echo $?
0
free したメモリは OS に返されず、malloc の管理の中に残るため、読んでも落ちません。手元では文字化けした文字列が出て、終了コードは 0 でした。あとで別の malloc がそこを使うと、ほかのデータを壊し、まったく関係のない場所で Segmentation fault になります。
gcc 13 は、このくらい単純なら -Wall で warning: pointer 'buf' used after 'free' [-Wuse-after-free] を出しました。警告を読むだけで見つかる場合があります。直し方は、使い終わってから free し、free の直後にポインタへ NULL を入れることです(NULL なら次に使ったとき確実に落ち、場所が分かります)。
原因4:スタックあふれ(再帰・大きなローカル配列)とulimit -s
この節では、終わらない再帰や大きなローカル配列で落ちる仕組みと、スタックの大きさとの関係を実測で示します。
関数を呼ぶたびにスタックを使い、上限(Linux の既定は 8MB)を超えると Segmentation fault になります。1回あたり約1KBを使う、終了条件のない再帰で、落ちた深さを ulimit -s ごとに gdb で読みました。
long depth(long n) {
char buf[1024]; /* 1回の呼び出しで約1KB使う */
buf[0] = (char)n;
return depth(n + 1) + buf[0]; /* 終了条件がない */
}
ulimit -s(KB) |
落ちた深さ | 1回あたり |
|---|---|---|
| 4096 | 3,878 | 約1.06KB |
| 8192(既定) | 7,791 | 約1.06KB |
| 16384 | 15,616 | 約1.06KB |
| 65536 | 62,567 | 約1.06KB |
深さはスタックの大きさにほぼ比例しました。gdb では depth (n=<error reading variable: Cannot access memory at address 0x7fffff7fed68>) のように、引数を読めないフレームで止まるのが、スタックあふれの目印です。gcc は warning: infinite recursion detected [-Winfinite-recursion] も出しました。
16MB の配列をローカル変数に置いた bigarr.c も、1回目の書き込みで落ちました。ulimit -s 32768 にすると動きましたが、これは一時しのぎです。
スタックあふれの直し方
ulimit -s unlimited を本番の起動スクリプトに書いて済ませる見分ける問い:「入力が10倍になっても深さは変わらないか」
$ ./stack_fixed # 1〜10,000,000 の和をループで
50000005000000
$ ./bigarr_fixed # 16MB を malloc で
1
Java の再帰で起きる同じ種類の問題(StackOverflowError と -Xss)は、関連記事「Java StackOverflowErrorの原因と対処|再帰と-Xss早見表」で扱っています。
コアダンプの出し方と読み方(ulimit -c・core_pattern・coredumpctl)
この節では、「(core dumped)」と出ない・core ファイルが見つからないときの確かめ方と、core ファイルを gdb で読む手順が分かります。
core ファイルは、プロセスの上限 ulimit -c が 0 だと作られません。Ubuntu の手元の既定は 0 でした。自分のシェルの中だけで上限を上げて、もう一度落とした出力です。
$ ulimit -c
0
$ ulimit -Hc
unlimited
$ ./null
start
Segmentation fault
$ ulimit -c unlimited
$ ./null
start
Segmentation fault (core dumped)
$ ls -l core*
-rw------- 1 root root 458752 Nov 5 13:35 core
$ file core
core: ELF 64-bit LSB core file, x86-64, version 1 (SYSV), SVR4-style, from './null', real uid: 0, effective uid: 0, real gid: 0, effective gid: 0, execfn: './null', platform: 'x86_64'
core ファイルと、それを作った実行ファイルを一緒に gdb に渡すと、落ちた瞬間をあとから調べられます。本番のサーバーで起きたクラッシュを手元で読むときの方法です。
$ gdb -q ./null core
Core was generated by `./null'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055ee89f36179 in get_id (u=0x0) at null.c:6
6 return u->id; /* u が NULL なら、ここで落ちる */
(gdb) bt
#0 0x000055ee89f36179 in get_id (u=0x0) at null.c:6
#1 0x000055ee89f361ac in main () at null.c:12
core ファイルがどこにできるか(core_pattern)
置き場所は /proc/sys/kernel/core_pattern で決まります。手元の値は core で、落ちたときのカレントディレクトリに core という名前でできました。core(5) によると、値が | で始まるときはファイルを書かず、指定したプログラムに標準入力で渡します。systemd を使うシステムでは systemd-coredump に渡され、/var/lib/systemd/coredump/ に圧縮して保存されることが多く、coredumpctl list で一覧、coredumpctl debug で gdb を開けます(このとき RLIMIT_CORE は効きません)。
| core_pattern の値 | core の場所 | 見る方法 |
|---|---|---|
core |
落ちたときのカレントディレクトリ | gdb ./prog core |
core.%e.%p など |
%e=実行ファイル名、%p=PID、%t=時刻 | gdb ./prog core.prog.1234 |
|/usr/lib/systemd/systemd-coredump … |
/var/lib/systemd/coredump/(圧縮) |
coredumpctl list・coredumpctl debug |
| で始まる別のプログラム |
そのプログラムの設定しだい | そのプログラムのドキュメント |
core_pattern の変更はホスト全体の設定(root が必要)なので、手元では読むだけにしました。変えるときは sysctl kernel.core_pattern=… で、ほかの利用者のプログラムにも影響する点に注意します。
(core dumped) と出ない6つの理由
- 1
ulimit -cが 0手元の既定。ulimit -c unlimitedを同じシェルで実行してから動かす - 2ハードの上限まで 0 にしてしまった
-S・-Hを付けないulimit -c 0は両方を下げる。手元でも、そのあとのulimit -c unlimitedがcannot modify limit: Operation not permittedになった。シェルを開き直す - 3カレントディレクトリに書き込めない
cd /procしてから落とすと、上限が unlimited でもSegmentation faultだけで core は無かった - 4core_pattern が別のプログラムに渡している
|で始まる値ならcoredumpctlなどで探す - 5AddressSanitizer でビルドしているASan は 64bit では既定で
disable_coredump=1(巨大な core を避けるため)。ASAN_OPTIONS=help=1で手元でも確かめた - 6setuid のプログラム・読み取り権限のない実行ファイルcore(5) が挙げる条件。
/proc/sys/fs/suid_dumpableで変わる
ずっと動いているプロセスの状態を、止めずに core として保存したいときは、gdb に付いてくる gcore PID が使えます。
AddressSanitizerで落ちないメモリ破壊まで見つける
この節では、AddressSanitizer(ASan)の使い方と、出力の読み方、Valgrind との使い分け、速さの実測が分かります。
AddressSanitizer は、メモリの読み書きを1回ずつ調べるコードをコンパイラーが埋め込む仕組みです。範囲外・解放後の使用・二重解放・スタックあふれ・メモリリークを、落ちる前に止めて報告します。gcc と clang に入っていて、オプションを足すだけで使えます。
gcc -g -O0 -fsanitize=address -fno-omit-frame-pointer oob.c -o oob.asan
./oob.asan 5
さきほど「落ちずに 0 で終わった」a[5] の読み込みを、ASan は止めました(Shadow bytes の部分を省いています)。
==3819==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7f70ff800034 at pc 0x55ddaea944f3 bp 0x7ffda2e1e140 sp 0x7ffda2e1e130
READ of size 4 at 0x7f70ff800034 thread T0
#0 0x55ddaea944f2 in main /dev/shm/t290/c/oob.c:6
#1 0x7f710182a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
...
Address 0x7f70ff800034 is located in stack of thread T0 at offset 52 in frame
#0 0x55ddaea94298 in main /dev/shm/t290/c/oob.c:3
This frame has 1 object(s):
[32, 52) 'a' (line 5) <== Memory access at offset 52 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow /dev/shm/t290/c/oob.c:6 in main
解放後の使用では、読んだ行・free した行・malloc した行の3か所を出しました。どこで解放され、どこでまだ使われているかが一度に分かります。
==4031==ERROR: AddressSanitizer: heap-use-after-free on address 0x503000000040 at pc 0x7f311c6a1a6a bp 0x7ffec68e70b0 sp 0x7ffec68e6828
READ of size 2 at 0x503000000040 thread T0
#0 0x7f311c6a1a69 in printf_common ...
#3 0x55e0ee442284 in main /dev/shm/t290/c/uaf.c:8
0x503000000040 is located 0 bytes inside of 32-byte region [0x503000000040,0x503000000060)
freed by thread T0 here:
#0 0x7f311c6fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
#1 0x55e0ee442269 in main /dev/shm/t290/c/uaf.c:7
previously allocated by thread T0 here:
#0 0x7f311c6fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
#1 0x55e0ee44223e in main /dev/shm/t290/c/uaf.c:5
| ASan の見出し | 意味 | 手元で出したプログラム |
|---|---|---|
| SEGV on unknown address 0x000000000000 | NULL 参照(Hint: address points to the zero page) | null.c |
| stack-buffer-overflow | ローカル配列の範囲外 | oob.c |
| heap-use-after-free | free したメモリの使用 | uaf.c |
| stack-overflow | スタックの使い切り(再帰) | stack.c |
| heap-buffer-overflow | malloc した領域の範囲外 | (Clang のドキュメントの範囲) |
ASan で止まったときの終了コードは 1 でした(139 ではありません)。CI で「テストが 139 で落ちる」ことを期待していると見逃すので、出力の ERROR: AddressSanitizer を見ます。
Valgrind(ビルドし直せないとき)
Valgrind は、ビルドし直さずに実行ファイルをそのまま調べられます。uaf をそのまま渡すと、ASan と同じく3か所を出しました。
$ valgrind -q ./uaf
==4236== Invalid read of size 1
==4236== at 0x484F226: strlen (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4236== by 0x48C81B2: printf (printf.c:33)
==4236== by 0x1091E1: main (uaf.c:8)
==4236== Address 0x4a7e040 is 0 bytes inside a block of size 32 free'd
==4236== at 0x484988F: free (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4236== by 0x1091C6: main (uaf.c:7)
==4236== Block was alloc'd at
==4236== at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==4236== by 0x10919E: main (uaf.c:5)
速さとメモリの実測
10万要素(400KB)の配列を200回確保して読み書きする小さなプログラム(-O1)を、3回動かした最短の時間で比べました。
Clang のドキュメントは ASan の典型的な遅さを「2倍」としています。手元の小さな例では約3.5倍でしたが、Valgrind よりは約9倍速く、テストのたびに動かせる速さです。開発中とCIは ASan、配布されたバイナリやビルドできないものは Valgrind、と分けるのが現実的です。
clang で -fsanitize=address を付けたところ、手元では /usr/bin/ld: cannot find /usr/lib/llvm-18/lib/clang/18/lib/linux/libclang_rt.asan-x86_64.a: No such file or directory でリンクできませんでした。Ubuntu の clang は ASan の実行時ライブラリが別のパッケージ(libclang-rt-18-dev)に分かれているためで、gcc の ASan はそのまま使えました。
PythonでSegmentation faultが出る場合(ctypes・C拡張・faulthandler)
この節では、Python のプログラムが Segmentation fault で落ちる原因と、Python の何行目から落ちたかを出す方法が分かります。
Python のコードだけで Segmentation fault になることは、ふつうはありません。落ちるのは、C で書かれた部分(ctypes で呼んだライブラリ・C 拡張のパッケージ・それらの版の食い違い)でメモリを壊したときです。手元で、文字列の長さを返す自作の共有ライブラリに None(C では NULL)を渡しました。
import ctypes, faulthandler
faulthandler.enable() # コードの中で有効にする書き方
lib = ctypes.CDLL("./libscore.so")
lib.score.argtypes = [ctypes.c_char_p]
lib.score.restype = ctypes.c_int
print(lib.score(b"rjc")) # 30
print(lib.score(None)) # None は NULL として渡る
$ python3 call.py
30
Fatal Python error: Segmentation fault
Current thread 0x00007fe7181f9740 (most recent call first):
File "/dev/shm/t290/py/call.py", line 9 in <module>
Segmentation fault
$ echo $?
139
faulthandler を有効にすると、落ちた瞬間の Python の呼び出し履歴(File・line)が出ます。有効にしないと、Segmentation fault の1行だけでした。有効にする方法は3つあり、手元ではどれも同じ出力でした。
| 方法 | 書き方 | 向いている場面 |
|---|---|---|
| コマンドのオプション | python3 -X faulthandler app.py |
手元で一度だけ調べる |
| 環境変数 | PYTHONFAULTHANDLER=1 |
pytest・Docker・systemd など起動を変えにくいとき |
| コードの中 | faulthandler.enable() |
本番でも常に残したいとき |
ctypes.string_at(0) を呼ぶ crash.py では、-X faulthandler を付けると次のように、ctypes の中から自分の関数まで4段が出ました。
Fatal Python error: Segmentation fault
Current thread 0x00007f0f1465c740 (most recent call first):
File "/usr/local/lib/python3.13/ctypes/__init__.py", line 551 in string_at
File "/dev/shm/t290/py/crash.py", line 5 in load_record
File "/dev/shm/t290/py/crash.py", line 9 in main
File "/dev/shm/t290/py/crash.py", line 11 in <module>
C の側の行まで見る(gdb で Python を動かす)
faulthandler が出すのは Python の行だけです(Python 3.14 からは C のスタックも表示できる、と公式ドキュメントにあります)。C の側の行は、Python ごと gdb で動かすと出ました。共有ライブラリを -g 付きでビルドしておくと、libscore.c:4 で name=0x0 を受け取ったことまで分かります。
$ gdb -q -batch -ex run -ex 'bt 6' --args python3 call.py
30
Program received signal SIGSEGV, Segmentation fault.
__strlen_evex () at ../sysdeps/x86_64/multiarch/strlen-evex-base.S:81
#0 __strlen_evex () at ../sysdeps/x86_64/multiarch/strlen-evex-base.S:81
#1 0x00007ffff7e4e135 in score (name=0x0) at libscore.c:4
#2 0x0000000000da9052 in ffi_call_unix64 ()
#3 0x0000000000da785a in ffi_call_int ()
#4 0x0000000000da7427 in ffi_call ()
#5 0x00000000011fd0a5 in _call_function_pointer ()
再帰の上限を上げても Python 3.13 では落ちなかった
以前の Python では、sys.setrecursionlimit() を大きくしすぎると、C のスタックがあふれて Segmentation fault になることがありました。手元の Python 3.13 で、上限を100万にして C の関数(map)を挟む再帰を動かすと、RecursionError: maximum recursion depth exceeded で止まり、落ちませんでした。Python 3.12 の What's New には、組み込み関数は再帰の上限とは別の仕組みでクラッシュから守られる、と書かれています。
1まず faulthandler
Python のどの行(どのライブラリの呼び出し)で落ちたかが分かる。PYTHONFAULTHANDLER=1 なら起動を変えずに使える。
2次に版の組み合わせ
numpy などの C 拡張は、Python・ほかのパッケージの版との組み合わせで落ちることがある。新しい venv に入れ直して同じ手順で再現するかを見る。
3ctypes は argtypes を書く
型を書かないと、整数とポインタの取り違えをそのまま C に渡してしまう。NULL を渡してよい引数かは C 側の作りしだい。
Python の通常の例外(ModuleNotFoundError など)とは調べ方がまったく違います。例外の追い方は関連記事「デバッグとは|意味・手順・方法をPython・JS・Javaの実例で解説」で扱っています。
OS・環境別のSegmentation fault(Linux・macOS・Windows・Docker)
この節では、OS や実行環境ごとに、同じクラッシュがどう表示され、何を使って調べるのかが分かります。
| 環境 | 表示 | 場所を調べる道具 | core の扱い |
|---|---|---|---|
| Linux(bash) | Segmentation fault (core dumped)・139 | gdb・ASan・Valgrind・dmesg | ulimit -c・core_pattern・coredumpctl |
| Linux(zsh) | zsh: segmentation fault | 同上 | 同上 |
| macOS | Segmentation fault: 11 | lldb(run・bt)・Xcode・ASan |
LLDB・Apple のドキュメントの範囲 |
| Windows | アクセス違反(0xC0000005) | Visual Studio のデバッガー・MSVC の ASan | ダンプファイル(Microsoft Learn の範囲) |
| Docker・Kubernetes | Exited (139) | コンテナの中で gdb、またはイメージを手元で動かす | core_pattern はホストの設定に従う |
- macOS:gdb の代わりに lldb を使います。LLDB の「GDB to LLDB command map」では、
run・bt・frame variable(gdb のinfo localsに当たる)が対応しています。 - Windows:Microsoft Learn の NTSTATUS の一覧では、0xC0000005 は STATUS_ACCESS_VIOLATION で、「The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.」という文になっています。Linux の SIGSEGV に当たります。
- Docker:終了コードの読み方は Linux と同じで、139 は 128+11 です。137(128+9)の読み方は関連記事「Docker Exited (137)の原因と直し方|OOMKilled」で扱っています。コンテナのイメージを変えたら落ちるようになった場合は、ベースイメージの libc(glibc と musl)とネイティブのライブラリの組み合わせを疑います。
C 言語そのもの(ポインタ・malloc・環境構築)は、関連記事「C言語とは|入門から環境構築・ポインタ・mallocまで実行例で解説」で扱っています。
よくあるエラー・つまずき(原文と直し方)
この節では、Segmentation fault を調べる途中で出るメッセージと、その意味が分かります。すべて手元で出した原文です。
| 出たメッセージ | 意味 | 直し方 |
|---|---|---|
bash: ulimit: core file size: cannot modify limit: Operation not permitted |
ハードの上限が 0 になっている | シェルを開き直す。下げるときは ulimit -Sc 0 |
No symbol "u" in current context. |
-g なしでビルドした |
-g -O0 でビルドし直す |
0x0000555555555179 in ?? () |
strip されて関数名も無い | strip 前のファイルか、デバッグ情報のパッケージを使う |
n=<error reading variable: Cannot access memory at address …> |
スタックを使い切った | 再帰をループにする・配列を malloc に |
warning: pointer 'buf' used after 'free' [-Wuse-after-free] |
コンパイラーが解放後の使用を見つけた | free の位置を後ろへ・直後に NULL を代入 |
warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' |
scanf の & 忘れ | scanf("%d", &n) にする |
cannot find …/libclang_rt.asan-x86_64.a |
clang の ASan の実行時ライブラリが無い | libclang-rt の開発パッケージを入れるか gcc を使う |
free(): double free detected in tcache 2 |
同じポインタを2回 free(Aborted・134) | free したら NULL を代入。持ち主を1つに決める |
*** stack smashing detected ***: terminated |
ローカル配列からあふれて書き込んだ(134) | strcpy を長さ付きの関数にする・ASan で場所を出す |
- printf を足したら直った:直ったのではなく、メモリの並びが変わって壊れ方が変わっただけのことがあります。
-O0では動き-O2で落ちる場合も同じで、ASan で確かめます。 - gdb の中では落ちない:gdb はアドレスのランダム化(ASLR)を止めて動かすため、壊れ方が変わることがあります。core ファイルか ASan で調べます。
Segmentation faultの判断表(どの道具で調べるか)
この節では、状況ごとに最初に使う道具と、次の一手が分かります。
| 状況 | 最初に使う | 次の一手 |
|---|---|---|
| 自分のコードで毎回同じ場所で落ちる | -g -O0+gdb の bt |
変数を print して原因を直す |
| 落ちたり落ちなかったりする | ASan(-fsanitize=address) |
UBSan も足す |
| 本番のサーバーで落ちた | core ファイル・coredumpctl | 同じ版のバイナリで gdb prog core |
| ビルドし直せない(配布品) | Valgrind・dmesg | 版を確かめて開発元に報告 |
| Python で落ちる | -X faulthandler |
gdb で Python ごと動かす・venv を作り直す |
| 再帰・大きな配列 | gdb で深さを見る | ループ化・malloc。ulimit -s は一時しのぎ |
| いくつものプログラムがばらばらに落ちる | dmesg の likely on CPU を見る |
メモリ・ハードウェアの検査 |
自分のコードを調べるときの順番を1本にすると、次のとおりです。
- 1警告を全部出してビルド
-Wall -Wextra。解放後の使用・scanf・無限再帰は警告だけで見つかった - 2gdb で落ちた行と変数
run・bt・print・$_siginfo - 3ASan・UBSan で落ちないバグまで範囲外・解放後の使用を、落ちる前に止める
- 4本番は core と dmesg
ulimit -c・core_pattern・addr2line - 5再発を防ぐCI に ASan のビルドを足す。C++ なら vector と at()・スマートポインタ
Segmentation faultのよくある質問
この節では、検索でよく聞かれる疑問に短く答えます。
Q. Segmentation fault (core dumped) はどういう意味ですか? A. 禁止されたメモリに触ったため SIGSEGV で止まり、メモリの写し(core)を書き出した、という意味です。表示しているのはプログラムではなくシェルです。
Q. core dumped と出たのに core ファイルが見つかりません。
A. /proc/sys/kernel/core_pattern を見ます。| で始まるなら systemd-coredump などに渡されていて、coredumpctl list で探せます。core なら、落ちたときのカレントディレクトリにあります。
Q. 終了コード139とは何ですか? A. 128+11 で、シグナル11(SIGSEGV)で終わったことを表します。Docker の Exited (139) も同じ読み方です。Python の subprocess では -11 になりました。
Q. セグメンテーション違反の「セグメント」とは何ですか? A. メモリを区切った領域のことです。いまの Linux はページという単位で管理していますが、名前だけが残っています。gdb の si_code で、割り当てのない場所(SEGV_MAPERR)か、権限のない場所(SEGV_ACCERR)かが分かります。
Q. 落ちる行は分かりましたが、そこは正しいコードに見えます。 A. 別の場所で壊されたメモリを、そこで使っただけのことがあります。範囲外の書き込みや解放後の使用が典型で、ASan でビルドすると、壊した側の行で止まります。
Q. ulimit -s を大きくすれば直りますか? A. 落ちる深さは延びますが(手元では 8MB で7,791回、64MB で62,567回)、入力が増えればまた落ちます。再帰をループにするか、大きな配列を malloc に移すのが直し方です。
Q. Python で Segmentation fault が出ました。Python のバグですか?
A. 多くは C で書かれた部分(ctypes で呼んだライブラリ・C 拡張のパッケージ)です。python3 -X faulthandler で、どの呼び出しで落ちたかを出し、そのパッケージの版を確かめます。
Q. AddressSanitizer と Valgrind はどちらを使えばよいですか? A. ビルドし直せるなら ASan です。手元の実測で Valgrind より約9倍速く、テストのたびに動かせました。ビルドし直せない実行ファイルは Valgrind で調べます。
Q. ASan でビルドしたら core が出なくなりました。
A. ASan は 64bit では既定で core を作りません(disable_coredump=1)。ASan のレポートに必要な情報が出ているので、まずそれを読みます。
Q. 本番環境で core を有効にしてもよいですか? A. core にはパスワードや個人情報などメモリの中身がそのまま入ります。置き場所の権限・保存期間・容量の上限を決めてから有効にします。
まとめ
- Segmentation fault は、禁止されたメモリに触って SIGSEGV(11)で止まったという意味。終了コードは139(128+11)、(core dumped) は core を書き出した印で、どちらもシェルが表示している。
- 最初の手は
gcc -g -O0と gdb のrun・bt・print。手元の NULL 参照はget_id (u=0x0) at null.c:6と1行で出た。 - 範囲外(1つずれ)と解放後の使用は、落ちずに終了コード0で動いた。AddressSanitizer はこれを stack-buffer-overflow・heap-use-after-free として止め、解放した行まで出した。
- core が出ないときは
ulimit -c・core_pattern・カレントディレクトリの書き込み権限・ASan の設定を順に見る。ulimit -c 0はハードの上限まで下げる。 - Python では
-X faulthandlerで Python 側の行、gdb で C 側の行を出す。原因の多くは ctypes や C 拡張に渡した値と版の組み合わせ。
参考・出典
確認日はいずれも 2026年11月5日です。
- Linux man-pages「signal(7)」:https://man7.org/linux/man-pages/man7/signal.7.html
- Linux man-pages「core(5)」:https://man7.org/linux/man-pages/man5/core.5.html
- Linux man-pages「sigaction(2)」(si_code の SEGV_MAPERR・SEGV_ACCERR):https://man7.org/linux/man-pages/man2/sigaction.2.html
- systemd「coredumpctl」:https://www.freedesktop.org/software/systemd/man/latest/coredumpctl.html
- GNU「Bash Reference Manual」Exit Status:https://www.gnu.org/software/bash/manual/bash.html#Exit-Status
- GNU「Debugging with GDB」:https://sourceware.org/gdb/current/onlinedocs/gdb.html/
- GCC「Program Instrumentation Options」(-fsanitize=address・undefined):https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
- Clang「AddressSanitizer」:https://clang.llvm.org/docs/AddressSanitizer.html
- Valgrind「Memcheck: a memory error detector」:https://valgrind.org/docs/manual/mc-manual.html
- Python「faulthandler — Dump the Python traceback」:https://docs.python.org/3/library/faulthandler.html
- Python「What's New In Python 3.12」(sys.setrecursionlimit):https://docs.python.org/3/whatsnew/3.12.html
- Python「ctypes — A foreign function library for Python」:https://docs.python.org/3/library/ctypes.html
- LLDB「GDB to LLDB command map」:https://lldb.llvm.org/use/map.html
- Microsoft Learn「NTSTATUS Values」(0xC0000005 STATUS_ACCESS_VIOLATION):https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Segmentation faultの原因と直し方|gdb・ASanで行を特定」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。