C++
 Computer >> コンピューター >  >> プログラミング >> C++

gccでコンパイルしたC++プログラムがクラッシュした時、スタックトレースを自動的に出力する方法

概要

Linux環境では、gccを使ってC/C++のコードをコンパイルするのが一般的です。gccはglibcライブラリを利用しており、execinfo.hヘッダーに用意されているbacktrace()関数を使うことで、エラー発生時点の呼び出しスタック(スタックトレース)を取得できます。

この仕組みを応用すると、セグメンテーション違反(Segmentation Fault)などの致命的なエラーが発生した瞬間に、シグナルハンドラ経由で自動的にスタックトレースを出力させることが可能です。ここでは、その具体的な実装方法を紹介します。

サンプルコード

#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <cstdlib>
#include <unistd.h>
using namespace std;

void error_handler(int sig) {
    void *array[10];
    size_t size;
    size = backtrace(array, 10); // 全エントリのポインタを取得
    cout << "Error: signal " << sig << ":\n"; // エラーの種類を表示
    backtrace_symbols_fd(array, size, STDERR_FILENO); // 標準エラー出力へ書き出し
    exit(1);
}

void invalid_index() {
    int *ptr = (int*) - 1;
    cout << *ptr << endl; // セグメンテーション違反を引き起こす
}

void func1() {
    invalid_index();
}

void func2() {
    func1();
}

int main(int argc, char **argv) {
    signal(SIGSEGV, error_handler); // SIGSEGV発生時にハンドラを実行
    func2(); // 関数呼び出しの連鎖でエラーを発生させる
}

実行結果

Error: signal 11:
./a.out(+0x825)[0x5579a31d7825]
/lib/x86_64-linux-gnu/libc.so.6(+0x3ef20)[0x7f7689009f20]
./a.out(+0x880)[0x5579a31d7880]
./a.out(+0x8a1)[0x5579a31d78a1]
./a.out(+0x8ad)[0x5579a31d78ad]
./a.out(+0x8d5)[0x5579a31d78d5]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7)[0x7f7688fecb97]
./a.out(+0x71a)[0x5579a31d771a]

コードの解説

このプログラムでは、main()内でsignal(SIGSEGV, error_handler)により、セグメンテーション違反(シグナル番号11)が発生した際のハンドラを登録しています。

invalid_index()関数内で無効なアドレス(-1)にアクセスすることで、意図的にセグメンテーション違反を発生させています。エラーが起きるとerror_handler()が呼び出され、次のような流れで処理が行われます。

  • backtrace(): 現在の呼び出しスタック上の戻りアドレスを最大10件まで配列に取得します。
  • backtrace_symbols_fd(): 取得したアドレスを人間が読める形式の文字列に変換し、指定したファイルディスクリプタ(ここでは標準エラー出力)へ直接書き出します。

なお、ハンドラ内でbacktrace_symbols()ではなくbacktrace_symbols_fd()を使用しているのは、後者がmalloc()を呼び出さずシグナルセーフなためです。ヒープが破損している状況でも比較的安全に出力できます。

補足:より読みやすいトレースを得るには

上記の出力例ではアドレスのみが表示されています。コンパイル時に-gおよび-rdynamicオプションを付与すると、関数名付きのシンボル情報が出力されるようになります。

g++ -g -rdynamic main.cpp

さらに、出力されたアドレスに対してaddr2lineコマンドを使用すれば、クラッシュした正確なソースファイル名と行番号を特定することもできます。これらを組み合わせることで、デバッグ効率を大幅に向上させられます。

  1. C++プログラムから外部アプリケーション(メモ帳など)を起動する方法

    この記事では、C++プログラムを使ってメモ帳(Notepad)などのサードパーティ製アプリケーションを起動する方法を解説します。実装は非常にシンプルで、コマンドプロンプトで使うコマンドをそのままC++から呼び出すだけで実現できます。ポイントとなるのは、標準ライブラリの system() 関数です。この関数の引数にアプリケーション名(コマンド)を文字列として渡すと、OSがそのコマンドを実行し、対応するアプリケーションが起動します。サンプルコード#include <iostream> using namespace std; int main() { cout <<

  2. C++で簡単なプログラムを作成する方法【初心者向けステップ解説】

    C++で最初の簡単なプログラムを作成するには、まず開発環境を整え、その上でプログラムを書いて実行します。この記事では、非常にシンプルな「Hello World」プログラムを例に、C++プログラミングを始めるための手順をわかりやすく解説します。ステップ1:C++コンパイラを入手するC++の学習を始める前に、まず必要になるのがコンパイラです。主要なOSプラットフォーム向けに、優れた無料のC++コンパイラが多数公開されています。自分の環境に合ったものをダウンロードするか、tutorialspoint.comが提供するオンラインコンパイラ(https://www.tutorialspoint.com/