An Embedded Engineer’s Blog

とある組み込みエンジニアの備忘録的なブログです。

C++でスタックトレース(関数コール履歴)の取得

まえがき

今回は、前回に引き続き、C++で例外発生箇所と状況を特定するために、スタックトレースを取得する方法を実装します。

前提条件

今回実装したソースは以下の環境でビルド、動作確認しています。

OS Ver Compiler Remarks
Windows 10 Visual Studio 2019
macOS 10.15.6 Clang 11.0
Ubuntu 18.04 GCC 7.5.0


※ 今回実装するスタックトレースの取得方法は、OSやコンパイラといった環境に大きく左右されます。同じOS・コンパイラであってもバージョンによって異なる可能性もありますので、ご了承ください。

スタックトレースの取得

インタフェース

まずは、スタックトレースとして取得する情報をまとめた構造体StackTraceを定義します。

/* アドレス型 */
using address_t = void*;

/* スタックトレース情報 */
struct StackTrace
{
    /* トレース数 */
    uint32_t trace_size;
    /* トレースアドレスリスト */
    std::vector<address_t> traces;
    /* トレースシンボルリスト */
    std::vector<std::string> symbols;
};


StackTrace構造体には、トレースした関数のアドレスリスト、関数のアドレスに対応するシンボル(関数)名、トレースした関数の数を保持します。

次に、StackTrace構造体を取得するクラスStackTracerを定義します。

/* Stack Tracerクラス宣言 */
class StackTracer
{
public:
    /* スタックトレース情報取得 */
    static const StackTrace GetStackTrace();
};


スタックトレースの取得方法は、OSやコンパイラによって異なるため、ターゲットごとに実装していきます。

Windows

Windowsの場合は、ImageHlpライブラリを使うことで、スタックトレースおよび、シンボル情報を取得することが出来ます。

#include <Windows.h>
#include <ImageHlp.h>

#include <string>

#pragma comment(lib, "imagehlp.lib")

/* スタックトレース情報取得 */
const StackTrace StackTracer::GetStackTrace()
{
    /* 最大トレースサイズ */
    constexpr size_t MaxSize = 256;
    /* トレースリスト */
    void* traces[MaxSize] = {};

    /* 現在のプロセスを取得 */
    HANDLE process = GetCurrentProcess();

    /* シンボルハンドラの初期化 */
    SymInitialize(process, NULL, TRUE);

    /* スタックトレースの取得 */
    uint16_t trace_size = CaptureStackBackTrace(0, MaxSize, traces, NULL);

    /* シンボル名最大サイズをセット */
    constexpr size_t MaxNameSize = 255;
    /* シンボル情報サイズを算出 */
    constexpr size_t SymbolInfoSize = sizeof(SYMBOL_INFO) + ((MaxNameSize + 1) * sizeof(char));

    /* シンボル情報のメモリ確保 */
    SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(SymbolInfoSize, 1);

    /* スタックトレース情報生成 */
    StackTrace stack_trace;

    /* シンボル情報メモリ確保成功 */
    if (symbol != nullptr)
    {
        /* シンボル名最大サイズをセット */
        symbol->MaxNameLen = MaxNameSize;
        /* シンボル情報サイズをセット */
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

        /* トレースサイズをセット */
        stack_trace.trace_size = (uint32_t)trace_size;
        /* トレースリストのメモリ確保 */
        stack_trace.traces.reserve((size_t)trace_size);
        /* シンボルリストのメモリ確保 */
        stack_trace.symbols.reserve((size_t)trace_size);

        /* トレースサイズ分ループ */
        for (uint16_t i = 0; i < trace_size; i++)
        {
            /* トレースアドレスからシンボル情報を取得 */
            SymFromAddr(process, (DWORD64)(traces[i]), 0, symbol);

            /* トレースアドレスをトレースリストに追加 */
            stack_trace.traces.push_back(traces[i]);

            /* シンボル名をシンボルリストに追加 */
            stack_trace.symbols.push_back(std::string(symbol->Name));
        }

        /* シンボル情報のメモリ解放 */
        free(symbol);
    }
    else
    {
        /* Nothing to do */
    }

    return stack_trace;
}


Mac / Linux

MacLinuxの場合には、execinfoライブラリを使うことでスタックトレースおよび、シンボル情報を取得することが出来ます。

#include <execinfo.h>
#include <string>
#include <iostream>

/* スタックトレース情報取得 */
const StackTrace StackTracer::GetStackTrace()
{
    /* 最大トレースサイズ */
    constexpr size_t MaxSize = 256;
    /* トレースリスト */
    void* traces[MaxSize] = {};

    /* スタックトレース取得 */
    int trace_size = backtrace(traces, MaxSize);
    /* シンボルリスト取得 */
    char** symbols = backtrace_symbols(traces, trace_size);

    /* スタックトレース情報生成 */
    StackTrace stack_trace;

    /* トレースサイズ  */
    stack_trace.trace_size = (uint32_t)trace_size;

    /* トレースリストメモリ確保 */
    stack_trace.traces.reserve(trace_size);
    /* シンボルリストメモリ確保 */
    stack_trace.symbols.reserve(trace_size);

    /* トレースサイズ分ループ */
    for (int i = 0; i < trace_size; i++)
    {
        /* トレースアドレスをリストに追加 */
        stack_trace.traces.push_back(traces[i]);

        /* シンボル情報をシンボルリストに追加 */
        stack_trace.symbols.push_back(symbols[i]);
    }

    /* シンボルリスト解放 */
    free(symbols);

    return stack_trace;
}


未対応OS版

未対応OSの場合は、空のStackTraceを返すようにします。

/* スタックトレース情報取得 */
const StackTrace StackTracer::GetStackTrace()
{
    /* 空のスタックトレース情報生成 */
    StackTrace stack_trace;
    stack_trace.trace_size = 0;
    staci_trace.traces.clear();
    stack_trace.symbols.clear();

    return stack_trace;
}


シンボル情報のデマングル

Mac / Linux版で取得したシンボル情報は、そのままだと以下のように出力されます(※コンパイラのバージョンなどにより異なる場合があります)。

Mac版(Apple Clang 11.0)】

0   Test.out                            0x0000000104f20271 _ZN11StackTracer13GetStackTraceEv + 97
1   Test.out                            0x0000000104f1a2a7 _ZN9exception12AppException20GenerateErrorMessageEv + 55
2   Test.out                            0x0000000104f1a841 _ZN9exception12AppExceptionC2ERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_S9_i + 129
3   Test.out                            0x0000000104f1a905 _ZN9exception12AppExceptionC1ERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_S9_i + 53
4   Test.out                            0x0000000104f195ce _ZL9TestFunc2v + 126
5   Test.out                            0x0000000104f19549 _ZL9TestFunc1v + 9
6   Test.out                            0x0000000104f184a9 _ZL13ExceptionTestv + 9
7   Test.out                            0x0000000104f181ed main + 61
8   libdyld.dylib                       0x00007fff72982cc9 start + 1
9   ???                                 0x0000000000000001 0x0 + 1


Linux版(GCC 7.5)】

/<省略/Test.out(_ZN11StackTracer13GetStackTraceEv+0x58) [0x5648b25adbe8]
/<省略>/Test.out(_ZN9exception12AppException20GenerateErrorMessageB5cxx11Ev+0x3e) [0x5648b25ac77e]
/<省略>/Test.out(_ZN9exception12AppExceptionC2ERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEES8_S8_i+0xa4) [0x5648b25ac604]
/<省略>/Test.out(+0x872f) [0x5648b25ab72f]
/<省略>/Test.out(+0x866d) [0x5648b25ab66d]
/<省略>/Test.out(+0x8661) [0x5648b25ab661]
/<省略>/Test.out(main+0x38) [0x5648b25ab382]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7fd7fe637b97]
/<省略>/Test.out(_start+0x2a) [0x5648b25ab26a]


上記のように、そのままのシンボル情報では、関数名に当たる部分が_ZN11StackTracer13GetStackTraceEvといったコンパイラによってマングリング(名前修飾)された状態の名前で出力されます。
このままでも分からなくもないですが、デマングルしてより分かりやすい関数名にして出力したいと思います。

デマングルを行うライブラリはいくつかあるみたいですが、今回はcxxabi.hライブラリに定義されている、abi::__cxa_demangle関数を用いてデマングルを行います。

まずは、上記文字列を分解して、以下の情報を取り出します。

  • オブジェクト名
  • マングリングされたシンボル名
  • アドレス
  • アドレスオフセット

取り出した情報は、シンボル情報として構造体SymbolInfoに格納することにします。

/* シンボル情報 */
struct SymbolInfo
{
    /* シンボル情報有効フラグ */
    bool is_valid;
    /* オブジェクト名 */
    std::string object_name;
    /* アドレス */
    std::string address;
    /* マングルされたシンボル名 */
    std::string mangled_symbol_name;
    /* オフセット */
    std::string offset;

    /* コンストラクタ */
    SymbolInfo()
        : is_valid(false)
        , object_name("")
        , address("")
        , mangled_symbol_name("")
        , offset("")
    {
        /* Nothing to do */
    }
};


シンボル情報をテキストから抽出する関数は、環境ごとに正規表現を使って実装します。

/* Mac(Apple Clang 11.0の場合) */
#if TARGET_TYPE == TARGET_MAC
/* シンボル情報取得 */
inline SymbolInfo GetSymbolInfo(const std::string& raw_symbol_info)
{
    /* シンボル情報パターン : <Depth> <ObjectName> <Address> <MangledSymbolName> + <Offset> */
    std::regex pattern("^(\\d+)\\s+([\\w.\\?]+)\\s+(0x[0-9A-Fa-f]+)\\s+(\\w+)\\s+\\+\\s+(\\d+)$");
    std::smatch sm;

    /* シンボル情報生成 */
    SymbolInfo symbol_info;

    /* シンボル情報パターンにマッチ */
    if (std::regex_match(raw_symbol_info, sm, pattern))
    {
        /* 有効なシンボル情報生成 */
        symbol_info.object_name = sm[2].str();
        symbol_info.address = sm[3].str();
        symbol_info.mangled_symbol_name = sm[4].str();
        symbol_info.offset = sm[5].str();
        symbol_info.is_valid = true;
    }
    else
    {
        /* 無効なシンボル情報生成 */
        symbol_info.object_name = "";
        symbol_info.address = "";
        symbol_info.mangled_symbol_name = "";
        symbol_info.offset = "";
        symbol_info.is_valid = false;
    }

    return symbol_info;
}

/* Linux(GCC 7.5.0の場合) */
#elif TARGET_TYPE == TARGET_LINUX
/* シンボル情報取得 */
inline SymbolInfo GetSymbolInfo(const std::string& raw_symbol_info)
{
    /* シンボル情報パターン : <ObjectName>(<MangledSymbolName>+<Offset>) [<Address>] */
    std::regex pattern("^(.+)\\((\\w*)\\+(0x[0-9a-fA-F]+)\\)\\s+\\[(0x[0-9a-fA-F]+)\\]$");
    std::smatch sm;

    /* シンボル情報生成 */
    SymbolInfo symbol_info;

    /* シンボル情報パターンにマッチ */
    if (std::regex_match(raw_symbol_info, sm, pattern))
    {
        /* 有効なシンボル情報生成 */
        symbol_info.object_name = sm[1].str();
        symbol_info.mangled_symbol_name = sm[2].str();
        symbol_info.offset = sm[3].str();
        symbol_info.address = sm[4].str();
        symbol_info.is_valid = true;
    }
    else
    {
        /* 無効ななシンボル情報生成 */
        symbol_info.object_name = "";
        symbol_info.address = "";
        symbol_info.mangled_symbol_name = "";
        symbol_info.offset = "";
        symbol_info.is_valid = false;
    }

    return symbol_info;
}

/* 上記以外(コンパイラ不明)の場合 */
#else
/* シンボル情報取得 */
inline SymbolInfo GetSymbolInfo(const std::string& raw_symbol_info)
{
    /* 無効なシンボル情報を生成 */
    SymbolInfo symbol_info;
    symbol_info.object_name = "";
    symbol_info.address = "";
    symbol_info.mangled_symbol_name = "";
    symbol_info.offset = "";
    symbol_info.is_valid = false;
    return symbol_info;
}
#endif


抽出・生成したシンボル情報を元に、シンボルリストに登録する文字列を生成します。
その際に、マングリングされたシンボル名をabi::__cxa_demangle関数を使ってデマングルされた関数名に変換します。

また、デマングルに失敗した場合は、マングリングされたシンボル名をそのまま使うようにし、シンボル情報の生成に失敗した場合には、オリジナルのシンボル情報をそのまま返すようにしています。

/* シンボル情報を整形して取得 */
inline std::string GetSymbolInfoText(const std::string& raw_symbol_info)
{
    /* シンボル情報を取得 */
    const SymbolInfo symbol_info = GetSymbolInfo(raw_symbol_info);

    /* シンボル情報テキストを初期化 */
    std::string symbol_info_text = "";

    /* シンボル情報が有効 */
    if (symbol_info.is_valid == true)
    {
        /* マングルされたシンボル名を取得 */
        std::string mangled_symbol_name = symbol_info.mangled_symbol_name;

        /* マングルされたシンボル名を判定 */
        if (mangled_symbol_name[0] == '_')
        {
            /* デマングルされたシンボル名を取得 */
            int status = 0;
            char* demangled_symbol_name = abi::__cxa_demangle(mangled_symbol_name.c_str(), 0, 0, &status);

            /* デマングルに成功したらデマングルされたシンボル名を関数名にセット、失敗したらマングルされたシンボル名を使用 */
            std::string function_name = (status == 0) ? demangled_symbol_name : mangled_symbol_name;

            /* デマングルされたシンボル名のメモリ解放 */
            free(demangled_symbol_name);

            /* シンボル情報テキストをセット */
            symbol_info_text = StringFormat("%s %s + %s", symbol_info.object_name, function_name, symbol_info.offset);
        }
        else
        {
            /* シンボル情報テキストをセット */
            symbol_info_text = StringFormat("%s %s + %s", symbol_info.object_name, mangled_symbol_name, symbol_info.offset);
        }
    }
    else
    {
        /* シンボル情報テキストをセット */
        symbol_info_text = raw_symbol_info;
    }

    return symbol_info_text;
}


あとは、backtrace_symbols関数で取得したシンボル情報を、上記関数で変換し、シンボルリストに登録するように変更すれば完成です。

/* シンボル情報を取得 */
std::string raw_symbol_info = symbols[i];

#if DEMANGLE_SYMBOL_ENABLED == 1
/* シンボル情報を整形して取得 */
std::string symbol_info = GetSymbolInfoText(raw_symbol_info);
#else
/* シンボル情報をそのまま取得 */
std::string symbol_info = raw_symbol_info;
#endif

/* シンボル情報をシンボルリストに追加 */
stack_trace.symbols.push_back(symbol_info);


StackTracerクラスのソース全文を以下にに示します。

【StackTracer.cpp】

#include "StackTracer.h"
#include "CompileSwitch.h"
#include "StringFormat.h"

#if TARGET_TYPE == TARGET_MSVC
#include <Windows.h>
#include <ImageHlp.h>

#include <string>

#pragma comment(lib, "imagehlp.lib")

/* スタックトレース情報取得 */
const StackTrace StackTracer::GetStackTrace()
{
    /* 最大トレースサイズ */
    constexpr size_t MaxSize = 256;
    /* トレースリスト */
    void* traces[MaxSize] = {};

    /* 現在のプロセスを取得 */
    HANDLE process = GetCurrentProcess();

    /* シンボルハンドラの初期化 */
    SymInitialize(process, NULL, TRUE);

    /* スタックトレースの取得 */
    uint16_t trace_size = CaptureStackBackTrace(0, MaxSize, traces, NULL);

    /* シンボル名最大サイズをセット */
    constexpr size_t MaxNameSize = 255;
    /* シンボル情報サイズを算出 */
    constexpr size_t SymbolInfoSize = sizeof(SYMBOL_INFO) + ((MaxNameSize + 1) * sizeof(char));

    /* シンボル情報のメモリ確保 */
    SYMBOL_INFO* symbol = (SYMBOL_INFO*)calloc(SymbolInfoSize, 1);

    /* スタックトレース情報生成 */
    StackTrace stack_trace;

    /* シンボル情報メモリ確保成功 */
    if (symbol != nullptr)
    {
        /* シンボル名最大サイズをセット */
        symbol->MaxNameLen = MaxNameSize;
        /* シンボル情報サイズをセット */
        symbol->SizeOfStruct = sizeof(SYMBOL_INFO);

        /* トレースサイズをセット */
        stack_trace.trace_size = (uint32_t)trace_size;
        /* トレースリストのメモリ確保 */
        stack_trace.traces.reserve((size_t)trace_size);
        /* シンボルリストのメモリ確保 */
        stack_trace.symbols.reserve((size_t)trace_size);

        /* トレースサイズ分ループ */
        for (uint16_t i = 0; i < trace_size; i++)
        {
            /* トレースアドレスからシンボル情報を取得 */
            SymFromAddr(process, (DWORD64)(traces[i]), 0, symbol);

            /* トレースアドレスをトレースリストに追加 */
            stack_trace.traces.push_back(traces[i]);

            /* シンボル名をシンボルリストに追加 */
            stack_trace.symbols.push_back(std::string(symbol->Name));
        }

        /* シンボル情報のメモリ解放 */
        free(symbol);
    }
    else
    {
        /* Nothing to do */
    }

    return stack_trace;
}

#elif TARGET_TYPE == TARGET_MAC || TARGET_TYPE == TARGET_LINUX

#include <execinfo.h>
#include <string>
#include <iostream>
#include <regex>

/* シンボルのデマングル有効化 */
#define DEMANGLE_SYMBOL_ENABLED     (1)

#if DEMANGLE_SYMBOL_ENABLED == 1
#include <cxxabi.h>
#include <typeinfo>

/* シンボル情報 */
struct SymbolInfo
{
    /* シンボル情報有効フラグ */
    bool is_valid;
    /* オブジェクト名 */
    std::string object_name;
    /* アドレス */
    std::string address;
    /* マングルされたシンボル名 */
    std::string mangled_symbol_name;
    /* オフセット */
    std::string offset;

    /* コンストラクタ */
    SymbolInfo()
        : is_valid(false)
        , object_name("")
        , address("")
        , mangled_symbol_name("")
        , offset("")
    {
        /* Nothing to do */
    }
};

#if TARGET_TYPE == TARGET_MAC
/* シンボル情報取得 */
inline SymbolInfo GetSymbolInfo(const std::string& raw_symbol_info)
{
    /* シンボル情報パターン : <Depth> <ObjectName> <Address> <MangledSymbolName> + <Offset> */
    std::regex pattern("^(\\d+)\\s+([\\w.\\?]+)\\s+(0x[0-9A-Fa-f]+)\\s+(\\w+)\\s+\\+\\s+(\\d+)$");
    std::smatch sm;

    /* シンボル情報生成 */
    SymbolInfo symbol_info;

    /* シンボル情報パターンにマッチ */
    if (std::regex_match(raw_symbol_info, sm, pattern))
    {
        /* 有効なシンボル情報生成 */
        symbol_info.object_name = sm[2].str();
        symbol_info.address = sm[3].str();
        symbol_info.mangled_symbol_name = sm[4].str();
        symbol_info.offset = sm[5].str();
        symbol_info.is_valid = true;
    }
    else
    {
        /* 無効なシンボル情報生成 */
        symbol_info.object_name = "";
        symbol_info.address = "";
        symbol_info.mangled_symbol_name = "";
        symbol_info.offset = "";
        symbol_info.is_valid = false;
    }

    return symbol_info;
}
#elif TARGET_TYPE == TARGET_LINUX
/* シンボル情報取得 */
inline SymbolInfo GetSymbolInfo(const std::string& raw_symbol_info)
{
    /* シンボル情報パターン : <ObjectName>(<MangledSymbolName>+<Offset>) [<Address>] */
    std::regex pattern("^(.+)\\((\\w*)\\+(0x[0-9a-fA-F]+)\\)\\s+\\[(0x[0-9a-fA-F]+)\\]$");
    std::smatch sm;

    /* シンボル情報生成 */
    SymbolInfo symbol_info;

    /* シンボル情報パターンにマッチ */
    if (std::regex_match(raw_symbol_info, sm, pattern))
    {
        /* 有効なシンボル情報生成 */
        symbol_info.object_name = sm[1].str();
        symbol_info.mangled_symbol_name = sm[2].str();
        symbol_info.offset = sm[3].str();
        symbol_info.address = sm[4].str();
        symbol_info.is_valid = true;
    }
    else
    {
        /* 無効ななシンボル情報生成 */
        symbol_info.object_name = "";
        symbol_info.address = "";
        symbol_info.mangled_symbol_name = "";
        symbol_info.offset = "";
        symbol_info.is_valid = false;
    }

    return symbol_info;
}
#else
/* シンボル情報取得 */
inline SymbolInfo GetSymbolInfo(const std::string& raw_symbol_info)
{
    /* 無効なシンボル情報を生成 */
    SymbolInfo symbol_info;
    symbol_info.object_name = "";
    symbol_info.address = "";
    symbol_info.mangled_symbol_name = "";
    symbol_info.offset = "";
    symbol_info.is_valid = false;
    return symbol_info;
}
#endif

/* シンボル情報を整形して取得 */
inline std::string GetSymbolInfoText(const std::string& raw_symbol_info)
{
    /* シンボル情報を取得 */
    const SymbolInfo symbol_info = GetSymbolInfo(raw_symbol_info);

    /* シンボル情報テキストを初期化 */
    std::string symbol_info_text = "";

    /* シンボル情報が有効 */
    if (symbol_info.is_valid == true)
    {
        /* マングルされたシンボル名を取得 */
        std::string mangled_symbol_name = symbol_info.mangled_symbol_name;

        /* マングルされたシンボル名を判定 */
        if (mangled_symbol_name[0] == '_')
        {
            /* デマングルされたシンボル名を取得 */
            int status = 0;
            char* demangled_symbol_name = abi::__cxa_demangle(mangled_symbol_name.c_str(), 0, 0, &status);

            /* デマングルに成功したらデマングルされたシンボル名を関数名にセット、失敗したらマングルされたシンボル名を使用 */
            std::string function_name = (status == 0) ? demangled_symbol_name : mangled_symbol_name;

            /* デマングルされたシンボル名のメモリ解放 */
            free(demangled_symbol_name);

            /* シンボル情報テキストをセット */
            symbol_info_text = StringFormat("%s %s + %s", symbol_info.object_name, function_name, symbol_info.offset);
        }
        else
        {
            /* シンボル情報テキストをセット */
            symbol_info_text = StringFormat("%s %s + %s", symbol_info.object_name, mangled_symbol_name, symbol_info.offset);
        }
    }
    else
    {
        /* シンボル情報テキストをセット */
        symbol_info_text = raw_symbol_info;
    }

    return symbol_info_text;
}

#endif

/* スタックトレース情報取得 */
const StackTrace StackTracer::GetStackTrace()
{
    /* 最大トレースサイズ */
    constexpr size_t MaxSize = 256;
    /* トレースリスト */
    void* traces[MaxSize] = {};

    /* スタックトレース取得 */
    int trace_size = backtrace(traces, MaxSize);
    /* シンボルリスト取得 */
    char** symbols = backtrace_symbols(traces, trace_size);

    /* スタックトレース情報生成 */
    StackTrace stack_trace;

    /* トレースサイズ  */
    stack_trace.trace_size = (uint32_t)trace_size;

    /* トレースリストメモリ確保 */
    stack_trace.traces.reserve(trace_size);
    /* シンボルリストメモリ確保 */
    stack_trace.symbols.reserve(trace_size);

    /* トレースサイズ分ループ */
    for (int i = 0; i < trace_size; i++)
    {
        /* トレースアドレスをリストに追加 */
        stack_trace.traces.push_back(traces[i]);

        /* シンボル情報を取得 */
        std::string raw_symbol_info = symbols[i];

#if DEMANGLE_SYMBOL_ENABLED == 1
        /* シンボル情報を整形して取得 */
        std::string symbol_info = GetSymbolInfoText(raw_symbol_info);
#else
        /* シンボル情報をそのまま取得 */
        std::string symbol_info = raw_symbol_info;
#endif

        /* シンボル情報をシンボルリストに追加 */
        stack_trace.symbols.push_back(symbol_info);
    }

    /* シンボルリスト解放 */
    free(symbols);

    return stack_trace;
}

#else

/* スタックトレース情報取得 */
const StackTrace StackTracer::GetStackTrace()
{
    /* 空のスタックトレース情報生成 */
    StackTrace stack_trace;
    stack_trace.trace_size = 0;
    staci_trace.traces.clear();
    stack_trace.symbols.clear();

    return stack_trace;
}
#endif


例外処理への組み込み

StackTracerクラスを使ったスタックトレース取得処理を、前回作成したAppExceptionクラスに追加してみましょう。

【AppException.h】

#pragma once
#include "ExceptionBase.h"

#include <vector>

namespace exception
{
    /* Application Exceptionクラス宣言 */
    class AppException : public ExceptionBase
    {
    public:
        /* コンストラクタ */
        AppException(const std::string& message);

        /* コンストラクタ */
        AppException(const std::string& message, const std::string& file, const std::string& func, const int line);

        /* デストラクタ */
        virtual ~AppException();

        /* エラー要因を取得 */
        virtual char const* what() const noexcept override;

    private:
        /* エラーメッセージ生成 */
        const std::string GenerateErrorMessage();

    private:
        /* エラーメッセージ */
        std::string m_ErrorMessage;
    };
}

/* Application Exceptionのthrow */
#define THROW_APP_EXCEPTION(message) \
    throw exception::AppException(message, __FILE__, __FUNCTION__, __LINE__)


コンストラクタ内で、エラーメッセージを生成するために、GenerateErrorMessage関数と、メンバ変数m_ErrorMessageを追加しています。


【AppException.cpp】

#include "AppException.h"
#include "StringFormat.h"
#include "StackTracer.h"

#include <sstream>
#include <string>
#include <iomanip>
#include <iostream>

namespace exception
{
    /* コンストラクタ */
    AppException::AppException(const std::string& message)
        : ExceptionBase(message)
        , m_ErrorMessage("")
    {
        /* エラーメッセージ生成 */
        this->m_ErrorMessage = this->GenerateErrorMessage();
    }

    /* コンストラクタ */
    AppException::AppException(const std::string& message, const std::string& file, const std::string& func, const int line)
        : ExceptionBase(message, file, func, line)
        , m_ErrorMessage("")
    {
        /* エラーメッセージ生成 */
        this->m_ErrorMessage = this->GenerateErrorMessage();
    }

    /* デストラクタ */
    AppException::~AppException()
    {
        /* Nothing to do */
    }

    /* エラー要因を取得 */
    char const* AppException::what() const noexcept
    {
        /* エラー情報がある場合は、エラー情報付きメッセージを出力 */
        if (this->m_IsErrorInfoExists == true)
        {
            return this->m_ErrorMessage.c_str();
        }
        else
        {
            return this->m_Message.c_str();
        }
    }

    /* エラーメッセージ生成 */
    const std::string AppException::GenerateErrorMessage()
    {
        /* スタックトレース情報を取得 */
        StackTrace stack_trace = StackTracer::GetStackTrace();

        std::stringstream ss;

        /* エラー情報がある場合は、エラー情報付きメッセージを生成 */
        if (this->m_IsErrorInfoExists == true)
        {
            ss << StringFormat("[Application Error] %s @ %s[%s:L.%d]", this->m_Message, this->m_FunctionName, this->m_FilePath, this->m_LineNumber);
        }
        else
        {
            ss << StringFormat("[Application Error] %s", this->m_Message);
        }
        ss << std::endl;

        /* スタックトレースをダンプ */
        ss << "[Stack Traces] : " << std::endl;
        for (uint32_t i = 0; i < stack_trace.trace_size; i++)
        {
            if (i != 0)
            {
                ss << std::endl;
            }

            ss << "  ";
            ss << std::setw(16) << std::setfill('0') << std::hex << (uint64_t)stack_trace.traces[i];
            ss << " | ";
            ss << stack_trace.symbols[i];
        }
        ss << std::endl;

        return ss.str();
    }
}


上記コードでは、コンストラクタ内でGenerateErrorMessage関数を使ってエラーメッセージを生成し、m_ErrorMessageにセットしています。

what関数では、エラー情報(ファイルパスなど)の有無によって、出力するエラーメッセージを切り替えています。

GenerateErrorMessage関数では、エラーメッセージ、エラー発生箇所の他に、StackTracerクラスを使ったスタックトレースの取得および、ダンプ処理を追加しています。

実行

上記AppExceptionをthrowするとwhat関数からは以下のような情報が出力されます。

[ERROR] [Application Error] Invoke Application Exception @ TestFunc2[/<省略>/Test.cpp:L.34]
[Stack Traces] : 
  0000000109d924a1 | Test.out StackTracer::GetStackTrace() + 97
  0000000109d8c4d7 | Test.out exception::AppException::GenerateErrorMessage() + 55
  0000000109d8ca71 | Test.out exception::AppException::AppException(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) + 129
  0000000109d8cb35 | Test.out exception::AppException::AppException(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, int) + 53
  0000000109d8b7fe | Test.out TestFunc2() + 126
  0000000109d8b779 | Test.out TestFunc1() + 9
  0000000109d8a6d9 | Test.out ExceptionTest() + 9
  0000000109d8a41d | Test.out main + 61
  00007fff72982cc9 | libdyld.dylib start + 1
  0000000000000001 | ??? 0x0 + 1


上記から、Test.cpp 34行目のTestFunc2関数からAppException例外が発生した事がわかります。
また、TestFunc2関数がmain -> ExceptionTest -> TestFunc1 -> TestFunc2と呼ばれたときに例外が発生している事もわかります。

あとがき

前回と今回で、C++における例外発生箇所やスタックトレースの特定を行う処理を実現できました。