152 人が閲覧(直近 30 日) プログラミング言語

セグメンテーションフォルト(Segmentation fault)の原因と調べ方|core dumpedの意味・gdb・ASan

セグメンテーションフォルト(Segmentation fault)の原因と調べ方|core dumpedの意味・gdb・ASan

「Segmentation fault (core dumped)」と表示されてプログラムが止まった場合、原因はほぼ必ず、プログラムが触ってはいけないメモリに触れたことです。この記事ではC/C++で起きる代表的な6つの原因を、Linux(Debian 13)とmacOSで実行した結果とともに示し、gdb・AddressSanitizer(ASan)・dmesgで原因の行を突き止める手順をまとめます。自分で書いていないソフトやPythonのライブラリが落ちた場合の調べ方も扱います。

まとめ:Segmentation faultの原因と調べ方の要点

  • Segmentation faultは、カーネルがSIGSEGV(シグナル番号11)を送ってプロセスを止めた状態です。bashでは終了コードが128+11=139になります。
  • 代表的な原因は、NULLポインタ参照・文字列リテラルの書き換え・解放済みメモリへのアクセス・配列の範囲外アクセス・深すぎる再帰・未初期化ポインタの6つです。
  • 自分のコードなら「-g -O0で再ビルド→警告を確認→gdbでbt→落ちないバグはASan」の順に調べます。
  • 範囲外アクセスと解放後の使用は、LinuxでもmacOSでも落ちずに終了コード0で終わりました。落ちなかったからといって正しいコードとは限りません。
  • 配布されているソフトが落ちたら、まず版を確認し、debuginfodでシンボル付きのバックトレースを取って報告します。毎回違うプログラム・違う場所で落ちるなら、メモリなどのハードウェアを疑います。

Segmentation faultの意味とSIGSEGV・core dumpedの読み方

OSはプロセスごとに使ってよいメモリ範囲と、その範囲での読み書きの可否を管理しています。範囲の外を読み書きしたり、読み取り専用の領域に書き込んだりすると、CPUの例外を受けたカーネルがプロセスにSIGSEGVを送ります。signal(7)では、SIGSEGVの説明は「Invalid memory reference」で、既定の動作はCore(コアダンプを出力して終了)です。シグナル番号はx86でもARMでも11です。「シグナル11で落ちた」というときのシグナル11も、このSIGSEGVです。

「Segmentation fault (core dumped)」の表示元と終了コード139

あのメッセージはプログラムが出力したものではありません。子プロセスがシグナルで終了したことを、親のシェルが終了ステータスから読み取って表示しています。bashのマニュアルには、番号Nの致命的シグナルで終了したコマンドの終了ステータスは128+Nと書かれているため、SIGSEGVなら139です。Debian 13(bash 5.2.37)で実行すると、次のように表示されました。

$ gcc -g -O0 null.c -o null
$ ./null
Segmentation fault (core dumped)
$ echo $?
139

# シェルスクリプトの中で落ちた場合は、行番号とPIDが付く
bash: line 1:  4675 Segmentation fault      (core dumped) /tmp/b/null

「(core dumped)」は、終了ステータスにコアダンプのフラグが立っていたときにシェルが付ける表示です。AlmaLinux 9でulimit -c 0にして同じプログラムを動かすと、「(core dumped)」が消えて「Segmentation fault」だけになりました。反対に、表示が出ていてもsystemd-coredumpなどに渡した場合は、保存設定によってファイルが残らないことがあるので、コアの有無は別に確認します。

日本語ロケールでは「Segmentation fault (コアダンプ)」と半分だけ日本語になります。LANG=ja_JP.UTF-8のDebian 13で実際にこう表示されました。glibcの日本語翻訳ファイル(po/ja.po)では、2.17・2.28・2.31・2.35・2.41のどの版のタグでも「Segmentation fault」の訳文が原文のままです。一方、bashの翻訳(5.2-rc1版)では「(core dumped)」が「(コアダンプ)」と訳されています。macOSのbashでは「Segmentation fault: 11」、zshでは「zsh: segmentation fault」と表記は異なりますが、どれも中身は同じSIGSEGVです。

「セグメンテーション」の意味とSEGV_MAPERR・SEGV_ACCERR

名前にある「セグメント」は、メモリの区画を指す言葉です。「割り当てられた区画の外に触れた違反」という意味で、「セグフォ」は略称、「セグメンテーション違反」「セグメントエラー」は訳語です。いずれも同じSIGSEGVを指します。

カーネルはSIGSEGVを送るとき、理由をsi_codeという値に入れて渡します。sigaction(2)の定義では、SEGV_MAPERRは「オブジェクトに対応付けられていないアドレス」、SEGV_ACCERRは「対応付けられたオブジェクトへの権限が無い」です。Debian 13でシグナルハンドラーから値を出力すると、NULLの読み取りはsi_code=1 (SEGV_MAPERR) addr=(nil)、文字列リテラルへの書き込みはsi_code=2 (SEGV_ACCERR)でした。sigaction(2)には、ほかにSEGV_BNDERRやSEGV_PKUERRなども定義されています。前者は「そもそも無い場所」、後者は「あるが書いてはいけない場所」に触れたことを表します。

Segmentation faultの主な原因6つとコード例

以下の文字列リテラル・解放後使用・範囲外アクセスのコードは、main関数内の抜粋です。解放後使用の例にはstdio.h・stdlib.h・string.hが必要です。再帰の例は、別途main関数からdepth(0)を呼び出して実行します。

下の表は、各原因の最小コードを-O0でビルドして実行したときの終了コードです。LinuxはDebian 13(gcc 14.2.0・glibc 2.41・x86_64)、macOSはmacOS 26(Apple clang 21.0.0・x86_64)で実行しました。139はSIGSEGV、138はmacOSのSIGBUS(シグナル10)、0は落ちずに正常終了したことを表します。

原因 典型的な書き方 Linux macOS
NULLポインタの参照 int *p = NULL; *p; 139 139
文字列リテラルの書き換え char *s = "hello"; s[0] = 'H'; 139 138(SIGBUS)
深すぎる再帰 終了条件のない再帰 139 139
配列の範囲外アクセス for (i = 0; i <= 5; i++) a[i] 0(落ちない) 0(落ちない)
解放済みメモリへのアクセス free(buf);のあとにbufを読む 0(落ちない) 0(落ちない)
未初期化ポインタ int *p; *p = 1; 環境次第

Linuxでは-O2でも、範囲外アクセスと解放後の読み取りは終了コード0のままでした。

NULLポインタの参照

mallocやfopen、自作関数がエラー時に返したNULLを確認せずに使ったときに起きます。C11の規格案N1570の6.5.3.2では、単項*でNULLポインタを参照したときの動作は未定義です。Linuxでは通常アドレス0付近が割り当てられていないため、多くの場合SIGSEGVで落ちます。ただし規格はこの結果を保証しておらず、最適化によって参照そのものが消えることもあります。

#include <stdio.h>
int main(void) {
    int *p = NULL;
    printf("%d\n", *p);   // NULLを参照してSIGSEGV
    return 0;
}

対策は、戻り値を使う直前にNULLかどうかを確かめることです。ポインタの基本的な扱いはポインタとは?C言語での使い方とメリット・言語別の違いをわかりやすく解説で整理しています。

文字列リテラルの書き換え

char *s = "hello";のsが指すのは文字列リテラルで、多くの環境では読み取り専用の領域に置かれます。N1570の6.4.5第7項は、文字列リテラルの配列を変更しようとしたときの動作を未定義としています。上の表のとおり、LinuxではSIGSEGV、macOSではSIGBUSで落ちました。同じバグでも、届くシグナルはOSによって変わります。

char *s = "hello";   // 読み取り専用領域を指す
s[0] = 'H';          // 書き込みで落ちる

char t[] = "hello";  // 配列にコピーされるので書き換えられる
t[0] = 'H';

解放済みメモリへのアクセス(ダングリングポインタ)

freeしたあとに同じポインタを読み書きするバグです。解放後にprintf("%s\n", buf)しても、LinuxでもmacOSでも落ちずに終了コード0で終わりました。解放された領域がまだプロセスのヒープとして割り当てられたままなので、カーネルからは正当なアクセスに見えます。しばらくして別のmallocがその領域を再利用すると、古いポインタからの読み取りは別の用途のデータを読む可能性があります。古いポインタから書き込めば、再利用先のデータを壊す可能性があります。

char *buf = malloc(16);
strcpy(buf, "issoh");
free(buf);
printf("%s\n", buf);   // 解放後の読み取り(落ちないことがある)
// 対策:free(buf); の直後に buf = NULL; を入れ、使う前に NULL かを確かめる

配列の範囲外アクセス

i <= 5のような境界の書き間違いで、要素5個の配列の6個目に書き込むバグです。実行しても落ちませんでした。書き込み先がたまたま割り当て済みのスタック領域だったため、カーネルが検知できなかったと考えられます。配列の配置によっては小さなはみ出しでも割り当てのないページに届いて落ちますし、壊れた値を後の処理が使って落ちることもあります。

int a[5] = {0};
for (int i = 0; i <= 5; i++) {   // i == 5 で a[5] に書き込む
    a[i] = i;
}

警告の出方は最適化レベルで変わりました。gcc 14.2に-Wall -Wextraを付けると、-O0では未使用変数の警告しか出ませんが、-O2ではiteration 5 invokes undefined behavior [-Waggressive-loop-optimizations]と、範囲外になる回を名指しで警告しました。clang 21は警告を出しませんでした。見つけるには、ASanや、UBSanの配列境界チェック(-fsanitize=bounds)を使います。

再帰が深すぎることによるスタックオーバーフロー

再帰呼び出しでスタックフレームが積み重なり、利用可能なスタックを使い切ると、LinuxではSIGSEGVが発生します。ただし、ローカル変数がレジスタに置かれたり、最適化で呼び出しや変数が除去されたりする場合もあります。sigaltstack(2)には、標準のスタックを使い切ったときにカーネルがそのスレッドにSIGSEGVを送ると書かれています。メインスレッドのスタック上限はRLIMIT_STACKで決まり、ulimit -sで確認できます。Debian 13のコンテナでは8192(8MiB)でしたが、どの環境でもこの値に決まっているわけではありません。

int depth(int n) {
    char buf[1024];          // 1回の呼び出しで約1KBを消費
    buf[0] = (char)n;
    return depth(n + 1) + buf[0];   // 終了条件がない
}

大きな配列をローカル変数として宣言した場合も同じように落ちます。再帰の深さの見積もり方は再帰関数とは?自分を呼ぶ関数の仕組みとスタック・末尾再帰・実務判断を解説で扱っています。

未初期化ポインタとscanfの&忘れ

#include <stdio.h>
int main(void) {
    int n;
    int *p;
    scanf("%d", n);
    *p = n;
    return 0;
}

初期化していないポインタの中身は不定なので、どこを指しているか分かりません。初心者に多いのがscanf("%d", n)のように&を付け忘れるケースで、必要なint *ではなくintを渡すため、動作は未定義です。さらに、nが未初期化なら、その値を読み出すことも問題になります。こちらはコンパイラの警告で見つかります。gcc 14.2に-Wall -Wextraを付けると、次の3件が出ました(clang 21でも同じ3か所を警告しました)。

uninit.c:5:13: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
uninit.c:5:5: warning: 'n' is used uninitialized [-Wuninitialized]
uninit.c:6:8: warning: 'p' is used uninitialized [-Wuninitialized]

落ちないメモリ破壊のほうが危ない理由

6つの原因のうち2つは、実行しても落ちませんでした。メモリの不正アクセスでSIGSEGVが届く代表例は、割り当てのないページや書き込み禁止のページに触ったときです(ほかに実行禁止領域の命令実行や、killでの明示的な送信もあります)。割り当て済みの領域の中での範囲外書き込みや、解放後もヒープに残っている領域の読み書きは、カーネルには検知できません。

この性質から、実務では次の2点を前提にしてください。

  • 落ちた行と原因の行は別のことが多い。ヒープを壊した処理はずっと前に終わっていて、壊れたデータを後から使ったmallocやfreeの中で落ちます。gdbのbtがlibcの内部を指していたら、この状況を疑います。
  • printfを足したら落ちなくなった、は直ったことにならない。コードを足すとスタックの配置や最適化の結果、実行のタイミングが変わり、壊れ方が表に出なくなっただけの可能性があります。調べようとすると消えてしまうこの種のバグは「ハイゼンバグ」と呼ばれます。-O0では動くのに-O2で落ちる場合も、最適化のバグではなく未定義動作を疑うのが先です。

こうした「落ちないバグ」を見つけるには、次の章のASanが有効です。ただしASanが検出できるのは、テストで実際に通った経路で、かつ検出対象の種類のエラーに限られます(構造体の隣のメンバーへのはみ出しなどは通常検出されません)。

Segmentation faultの原因の行を特定する手順

手順1:デバッグ情報付きの再ビルドと警告の確認

最適化を切り、デバッグ情報を付けてビルドし直します。最適化が効いていると、変数の値が<optimized out>と表示されて読めなくなります。前の章のとおり、gccの-Waggressive-loop-optimizationsのように最適化を有効にしたときだけ出る警告もあるので、警告の確認だけは-O2でも1回通します。

gcc -g -O0 -Wall -Wextra main.c -o main
gcc -O2 -Wall -Wextra -c main.c -o /dev/null   # 最適化時だけ出る警告の確認
clang --analyze main.c                          # 実行せずに解析するclangの静的解析

clang --analyzeを5つの例に当てると、NULL参照(Dereference of null pointer)と解放後使用(Use of memory after it is freed)の2件を指摘し、範囲外アクセスと文字列リテラルの書き換えは素通りしました。静的解析は実行前に拾えるパターンだけを拾うもので、見逃しがある前提で使います。仕組みとツールの選び方は静的解析とは?検出できる欠陥と静的解析ツールの選び方|Coverity・cppcheckを比較を参照してください。

手順2:gdbによるバックトレースの取得

gdbの上でプログラムを動かすと、SIGSEGVを受けた時点で止まります。Debian 13のgdb 16.3でNULL参照の例を動かした出力です。

$ gdb -q ./null
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
0x00005c958307d14d in main () at null.c:4
4	    printf("%d\n", *p);
(gdb) bt
#0  0x00005c958307d14d in main () at null.c:4
(gdb) print p
$1 = (int *) 0x0

落ちた行(null.c:4)と、pが0x0つまりNULLであることが分かります。関数の中で落ちた場合は、btで呼び出し履歴を表示し、frame 番号で自分のコードのフレームへ移ってinfo localsやprintで変数を確かめます。btの先頭がlibcの関数(strlenやmemcpyなど)でも、下にたどると自分のコードのフレームがあり、そこで渡した引数がNULLや解放済みのアドレスでないかを見ます。gdbの最新版は2026年9月25日公開のGDB 18.1です。macOSではgdbが同梱されていないので、lldb ./main→run→btと同じ手順で調べます。VS CodeのC/C++拡張でデバッグ中に「Exception has occurred. Segmentation fault」と出るのも同じ状態で、デバッグエンジンのMIEngineがgdbから受け取ったシグナルの説明(signal-meaning)を例外のメッセージとして表示しています。コールスタックの欄がbtの代わりになります。

手順3:コアダンプの取得と読み込み

再現に時間がかかるバグや本番環境で起きたクラッシュは、コアダンプから調べます。core(5)によると、RLIMIT_CORE(ulimit -c)が0だと通常のコアファイルは作られません。

ulimit -c unlimited                  # このシェルでコア出力を有効化
cat /proc/sys/kernel/core_pattern    # 出力先の設定を確認
gdb ./main core                      # コアファイルを読み込んで bt

core_patternが|で始まっている場合、コアはファイルとして保存されず、別のプログラムに渡されます。渡し先はsystemd-coredumpのほか、Apportや独自のハンドラのこともあります。systemdはkernel.core_patternを設定してsystemd-coredumpにコアを渡すので、カレントディレクトリを探しても見つかりません。渡し先がsystemd-coredumpなら、coredumpctl listで一覧を表示し、coredumpctl debugで直近のコアをgdbで開きます。

手順4:AddressSanitizerによる落ちないバグの検出

ASanはGCCとClangに組み込まれたメモリエラー検出器で、ヒープ・スタック・グローバル領域の範囲外アクセス、解放後使用、二重解放などを、実際に起きた時点で止めて報告します。

gcc -g -O0 -fsanitize=address -fno-omit-frame-pointer main.c -o main
./main

落ちなかった範囲外アクセスのコードを、GCC 14.4.0のASan付きで実行したときの出力の抜粋です。終了コードは1になりました。

==14==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7e2002a00034 ...
WRITE of size 4 at 0x7e2002a00034 thread T0
    #0 0x4012a3 in main /w/oob.c:4
  This frame has 1 object(s):
    [32, 52) 'a' (line 2) <== Memory access at offset 52 overflows this variable
SUMMARY: AddressSanitizer: stack-buffer-overflow /w/oob.c:4 in main

書き込んだ行(4行目)、はみ出した変数(2行目のa)、はみ出した位置まで分かります。[32, 52)はスタックフレーム内でaが占める範囲で、書き込み先のオフセット52は配列の先頭から20バイト先、つまり配列のすぐ後ろです。解放後使用のコードではheap-use-after-freeと、読んだ行(8行目)・freeした行(7行目)・mallocした行(5行目)の3つが報告されました。Clangの公式文書は、ASanを有効にした場合の実行時間を典型的には約2倍としています。実行時のオプションは環境変数ASAN_OPTIONSで渡し、ASAN_OPTIONS=help=1で一覧を表示できます。

手順5:再ビルドできないバイナリのValgrind Memcheck

再ビルドできないバイナリは、LinuxなどValgrindの対応環境で調べます(公式の対応範囲はmacOS 13までです)。valgrind ./mainで起動するだけで、再コンパイルせずに不正なアクセスを報告します。解放後使用の例をDebian 13のValgrind 3.24.0で動かした出力です。

==4719== Invalid read of size 1
==4719==    at 0x484DC86: strlen (vg_replace_strmem.c:505)
==4719==    by 0x48DE2D3: puts (ioputs.c:35)
==4719==    by 0x109196: main (uaf.c:8)
==4719==  Address 0x4a55040 is 0 bytes inside a block of size 16 free'd
==4719==    at 0x484787F: free (vg_replace_malloc.c:989)
==4719==    by 0x10918A: main (uaf.c:7)

先頭はlibcのstrlenですが、3行目のmain (uaf.c:8)が自分のコードです。上流の最新版は2026年5月20日公開の3.27.1で、ディストリビューションのパッケージはこれより古いことがあります。ASanより大幅に遅いので、再ビルドできるならASanを先に使ってください。

手順6:Linux x86のdmesgにあるsegfault行とerror番号の読み方

サーバー上のデーモンが黙って落ちたときは、カーネルログにSIGSEGVの記録が残っていることがあります。arch/x86/mm/fault.cのshow_signal_msg()が行を出すのは、シグナルをプログラムが処理しなかった場合だけで、短時間に大量に起きたときは間引かれます。sysctlのdebug.exception-traceが0なら出ません。カーネル文書によると既定値はx86などで1、arm64では0なので、64bit版のRaspberry Pi OSのようなarm64の環境では、既定のままだとこの行は残りません。dmesgかjournalctl -kで確認します。カーネル6.8のLinux上で、この記事の例を実行したときに残った行です。

null[703330]: segfault at 0 ip 00005b510db3814d sp 00007ffda03fb180 error 4 in null[5b510db38000+1000] likely on CPU 1 (core 1, socket 0)
literal[703337]: segfault at 5dfe03f27004 ip 00005dfe03f2613c sp 00007ffebcbe0950 error 6 in literal[5dfe03f26000+1000] likely on CPU 1 (core 1, socket 0)
recurse[703344]: segfault at 7ffd63009cbc ip 000056c1f95eb134 sp 00007ffd63009cb0 error 6 in recurse[56c1f95eb000+1000] likely on CPU 1 (core 1, socket 0)

segfault atの後ろが触ったアドレス、ipが落ちた命令のアドレス、spがスタックの位置です。segfault at 0ならNULL参照です。再帰の行のように、触ったアドレスがspのすぐ近く(ここでは12バイト差)なら、スタック枯渇を疑う手掛かりになります。ただし、近さだけでは確定できないため、バックトレースの再帰状況とスタックのマッピング・上限も確認します。

errorの後ろはページフォルトのエラーコードで、16進数で出力されます。asm/trap_pf.hの定義では、bit0が「0=ページなし/1=保護違反」、bit1が「0=読み取り/1=書き込み」、bit2が「0=カーネルモード/1=ユーザーモード」、bit4が「命令の取り出し」です。ユーザープログラムでよく見る値は次の6つです。

error 意味 疑う原因
4 読み取り・ページなし NULLや壊れたポインタの読み取り
5 読み取り・保護違反 読み取り禁止ページの読み取り
6 書き込み・ページなし NULLへの書き込み、スタック溢れ
7 書き込み・保護違反 読み取り専用領域への書き込み
14 命令取り出し・ページなし 壊れた関数ポインタや戻りアドレス
15 命令取り出し・保護違反 スタックなど実行禁止領域の実行

表は目安です。文字列リテラルへの書き込みは「読み取り専用領域への書き込み」なので7に見えそうですが、実測では6でした。書き込み先のページがまだ一度も読み込まれていなかったため、bit0が0のまま記録されたと考えられます(このときのsi_codeはSEGV_ACCERR)。6だからといって割り当てのない場所とは限りません。14と15は、0x1234への関数ポインタ呼び出しとスタック上の配列へのジャンプで、それぞれ実際に出ました。

落ちた命令をソースの行に戻すには、addr2line -e 実行ファイル アドレスを使います(デバッグ情報付きでビルドしている場合)。非PIEの実行ファイルならipの値をそのまま渡せます。PIEや共有ライブラリでは、inの後ろの括弧内の先頭はそのマッピングの開始アドレスなので、ipから引いた値はマッピング内のオフセットにすぎません。readelf -lW 実行ファイルで、実行可能(R E)なLOADセグメントの仮想アドレスを足して、ELF上のアドレスに直します。上のNULL参照の例を同じ手順で再現したところ、差分0x14dをそのまま渡すと??:0、実行可能セグメントの仮想アドレス0x1000を足した0x114dを渡すとnull.c:4が返りました。

$ readelf -lW null | grep LOAD
  LOAD  0x000000 0x0000000000000000 ... R   0x1000
  LOAD  0x001000 0x0000000000001000 ... R E 0x1000
$ addr2line -e null 0x14d
??:0
$ addr2line -e null 0x114d
/w/null.c:4

配布ソフトのSegmentation faultの切り分けと報告

配布されているツールやアプリ、ゲーム、ライブラリが落ちた場合は、ソースを直すより先に、原因の切り分けと報告に必要な情報をそろえます。

版の確認と既知の不具合の検索

アップデートの直後に落ち始めたなら、その版の不具合を最初に疑います。プロジェクトのissueやリリースノートを「segfault」と版番号で検索し、修正済みの版に上げるか、問題の出ていない以前の版に戻して固定します。自分で入れた別版のライブラリが先に読み込まれていないかは、ldd(1)で依存する共有ライブラリと読み込み先を表示して確かめます。not foundの行や、想定外のディレクトリのライブラリがあれば、LD_LIBRARY_PATHなどの設定を見直します。ldd(1)には、条件によってはlddが対象のプログラムを実行してしまうことがあるため信頼できないバイナリに使ってはいけない、という注意書きがあります。出どころの分からないバイナリなら、同じページで代わりに挙げられているobjdump -p 実行ファイル | grep NEEDEDで依存ライブラリ名だけを確認します。

debuginfodによるシンボル付きバックトレースの取得

ディストリビューションのパッケージは、デバッグ情報を取り除いた状態で配布されています。そのままコアを読むと、関数名が?? ()になって原因の場所が分かりません。debuginfodは、デバッグ情報をネットワーク経由で配るサーバーで、gdbはDEBUGINFOD_URLSに書かれたサーバーから必要なデバッグ情報を自動で取得します。Debian 13で、kill -SEGVでSIGSEGVを送って止めたsleepのコアを読み比べた結果です。

# debuginfod なし
#3  0x0000558d06883cbf in ?? ()
#4  0x0000558d06883789 in ?? ()

# DEBUGINFOD_URLS=https://debuginfod.debian.net \
#   gdb -batch -iex 'set debuginfod enabled on' -ex bt /usr/bin/sleep core
#3  0x00005bc459bcccbf in rpl_nanosleep (...) at lib/nanosleep.c:80
#4  0x00005bc459bcc789 in xnanosleep (seconds=<optimized out>) at lib/xnanosleep.c:69
#5  0x00005bc459bc8a0e in main (argc=<optimized out>, argv=<optimized out>) at src/sleep.c:141

?? ()だったフレームが、関数名とソースの行(src/sleep.c:141)に置き換わりました。GDBの文書では、debuginfodの利用は対話セッションでは確認(ask)、-batchのような非対話セッションでは無効(off)が既定なので、上の例ではset debuginfod enabled onを付けています。接続先の設定はディストリビューションで違い、Ubuntuは22.10(Kinetic)以降、GDBを入れるとdebuginfod.ubuntu.comを使う設定が自動で入ります。Fedoraは35以降、/etc/profile.dのファイルでDEBUGINFOD_URLSが設定されます。コンテナのDebian 13ではDEBUGINFOD_URLSが空だったので、上のように明示しました。バグを報告するときは、ソフトの版・OSの版・この形のバックトレース・再現手順の4点を添えると、開発者が原因の行までたどれます。

PythonのSegmentation faultとfaulthandlerによる調査

Pythonのコードだけでは、通常SIGSEGVは起きません。落ちる原因は、C言語で書かれた拡張モジュール、ctypesで呼んだネイティブ関数、あるいはライブラリ自体の不具合です。標準ライブラリのfaulthandlerを有効にすると、落ちた時点のPythonのトレースバックが表示されます。

$ python3 -X faulthandler -c "import ctypes; ctypes.string_at(0)"
Fatal Python error: Segmentation fault

Current thread 0x000076aaae4c3780 (most recent call first):
  File "/usr/lib/python3.13/ctypes/__init__.py", line 538 in string_at
  File "<string>", line 1 in <module>

Debian 13のPython 3.13で、アドレス0を読ませたときの出力です。環境変数PYTHONFAULTHANDLER=1でも有効になります。最後に呼んだ拡張モジュールの関数が、疑うべき相手です。たとえばpandas 3.0.4は2026年6月28日に公開されたあと、「Reported segfaults with datetime-related functionality」という理由でPyPIから取り下げ(yank)られました。アップデートした直後に落ち始めたら、前の節と同じく版を疑います。

不規則なクラッシュとハードウェア要因の切り分け

同じ入力で同じ行が落ちるなら、まずソフトウェアのバグを疑います。ただし、再現性だけでハードウェア要因を除外することはできません。反対に、関係のない複数のプログラムが、毎回違う場所でSegmentation faultになるなら、メモリやCPUの故障を疑います。dmesgの行の末尾にあるlikely on CPU 1 (core 1, socket 0)は、カーネルのソースに「故障したハードウェアの特定に役立つ」というコメント付きで出力されている情報です。複数の行で同じCPU番号ばかりが出るなら、そのコアの故障が疑えます。Memtest86+の公式サイトも、メモリエラーをクラッシュやフリーズの最も一般的な原因の1つに挙げています。メモリは、OSを起動しない状態で検査するMemtest86+などで確かめます。

SIGSEGVとBus error・Abortedの見分け方

メモリまわりのバグは、Segmentation fault以外の表示で落ちることもあります。表示されたメッセージから、どのシグナルが届いたかを判断します。終了コードはLinux x86での値です。

表示 シグナル 終了コード 主な原因
Segmentation fault SIGSEGV(11) 139 不正なアドレスの参照
Bus error SIGBUS(7) 135 ファイルの範囲外をmmap経由で参照など
Aborted SIGABRT(6) 134 ライブラリがメモリ破壊を検知して中断

Bus errorは、境界をそろえる必要のあるアドレスへの不正なアクセスでも起きることがあります。アライメントの仕組みはメモリアライメントとは?構造体パディング・アライメント違反とC言語での制御を実測で解説で扱っています。

free(): double free detectedやdouble free or corruption (!prev)、*** stack smashing detected ***: terminatedが表示されてからAbortedになるのは、glibcやコンパイラが挿入したチェックが破壊を検知し、自分でabort()を呼んだケースです。シグナルは違っても、原因はSegmentation faultと同じメモリ破壊なので、ASanで調べる手順はそのまま使えます。二重解放とメモリリークの関係はメモリリークとは?原因の特定方法と直し方を4言語の再現コードで解説で整理しています。

Segmentation faultの再発を防ぐビルド設定と書き方

  • テストはASan付きでビルドする。CIに-fsanitize=address,undefinedのビルドを1本追加すると、落ちない範囲外アクセスや解放後使用がテストの時点で止まります。ASanは実行して見つける動的解析の道具です。実行せずに見つける静的解析との守備範囲の違いは、静的テストと動的テストの違い|静的解析・動的解析で見つかるバグを同じコードで比較で比べています。
  • 警告を無視しない。-Wall -Wextraで出た-Wformatや-Wuninitializedは、どちらもSIGSEGVに直結する警告です。-Werrorで警告をビルドエラーにするのも有効です。
  • freeの直後にNULLを代入する。free(NULL)は何もしないので、同じ変数をもう一度freeしても二重解放になりません。ただし、同じ領域を指す別のポインタには効かないので、所有者を1つに決めることと組み合わせます。
  • C++では生ポインタと生配列を避ける。所有権はstd::unique_ptrに持たせ、配列はstd::vectorにします。境界チェックが要る箇所では、範囲外でstd::out_of_rangeを投げるat()を使います。
  • 新規開発ならRustも選択肢にする。safe Rustでは、ダングリング参照や所有権の二重解放がコンパイルエラーになります。値の置き場所の考え方はRustのスタックとヒープ|値がヒープへ脱出する条件とスタックオーバーフローの防ぎ方にまとめています。

よくある質問

Segmentation fault (core dumped) はどういう意味ですか?

プロセスがSIGSEGVで終了し、終了ステータスにコアダンプのフラグが立っていたことを示します。典型的な原因は不正なメモリアクセスですが、SIGSEGVを明示的に送信した場合にも同じ表示になります。コアファイルが実際に保存されたかどうかは、core_patternの設定によって変わるので別に確認します。表示しているのはプログラムではなくシェルで、bashでの終了コードは139です。

Segmentationとはどういう意味ですか?

メモリの区画(セグメント)のことで、Segmentation faultは「割り当てられた区画の外に触れた違反」を表す名前です。Linuxが通知する理由の代表は、割り当てのないアドレスならSEGV_MAPERR、書き込み禁止の領域への書き込みのように権限が無ければSEGV_ACCERRです。

シグナル11(SIGSEGV)の原因は何ですか?

シグナル11はx86やARMのLinuxでのSIGSEGVの番号です。代表的な原因は、NULLポインタの参照、文字列リテラルの書き換え、解放済みメモリへのアクセス、配列の範囲外アクセス、深すぎる再帰、未初期化ポインタの6つです。配布ソフトが毎回違う場所で落ちる場合は、メモリなどのハードウェアの故障も原因になります。

コアダンプ(core)ファイルはどこに出力されますか?

/proc/sys/kernel/core_patternの設定で決まります。値が|で始まるならファイルではなく別のプログラムに渡されています。渡し先がsystemd-coredumpならcoredumpctl listで探せます。ulimit -cが0の場合、通常のコアファイルは作られません。

zsh: segmentation fault と表示されたときも同じ対処で良いですか?

同じです。zshやmacOSのbash(「Segmentation fault: 11」)は表記が違うだけで、届いているのは同じSIGSEGVです。macOSではgdbの代わりにlldbでrun→btを実行し、ASanはApple clangでも-fsanitize=addressで使えます。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

RELATED POSTS 関連記事

目次