web-dev-qa-db-ja.com

C ++でキャッチされた例外のスタックトレースとC ++のコードインジェクションを出力するには

例外だけでなく、std::exceptionのすべての子孫のスタックトレースを取得したい

私が理解しているように、スタックの巻き戻し(展開)のために例外がキャッチされると、スタックトレースは完全に失われます。

したがって、それを取得するために私が見る唯一の方法は、std::exceptionコンストラクター呼び出しの場所でのコード保存コンテキスト情報(スタックトレース)の注入です。私は正しいですか?

その場合は、C++でコードインジェクションを行う方法(可能な場合)を教えてください。私のアプリのデバッグバージョンにのみ必要なので、メソッドは完全に安全ではない可能性があります。アセンブラを使用する必要がありますか?

GCCのソリューションのみに興味があります。 c ++ 0x機能を使用できます

25
boqapt

あなたはGCC固有の何かに満足していると述べたので、これを行う方法の例をまとめました。ただし、C++サポートライブラリの内部に介入するのは純粋に悪です。これを製品コードで使用するかどうかはわかりません。とにかく:

_#include <iostream>
#include <dlfcn.h>
#include <execinfo.h>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
#include <cstdlib>

namespace {
  void * last_frames[20];
  size_t last_size;
  std::string exception_name;

  std::string demangle(const char *name) {
    int status;
    std::unique_ptr<char,void(*)(void*)> realname(abi::__cxa_demangle(name, 0, 0, &status), &std::free);
    return status ? "failed" : &*realname;
  }
}

extern "C" {
  void __cxa_throw(void *ex, void *info, void (*dest)(void *)) {
    exception_name = demangle(reinterpret_cast<const std::type_info*>(info)->name());
    last_size = backtrace(last_frames, sizeof last_frames/sizeof(void*));

    static void (*const rethrow)(void*,void*,void(*)(void*)) __attribute__ ((noreturn)) = (void (*)(void*,void*,void(*)(void*)))dlsym(RTLD_NEXT, "__cxa_throw");
    rethrow(ex,info,dest);
  }
}

void foo() {
  throw 0;
}

int main() {
  try {
    foo();
  }
  catch (...) {
    std::cerr << "Caught a: " << exception_name << std::endl;
    // print to stderr
    backtrace_symbols_fd(last_frames, last_size, 2);
  }
}
_

基本的に、GCCがスローされた例外をディスパッチするために使用する内部実装関数の呼び出しを盗みます。その時点で、スタックトレースを取得し、グローバル変数に保存します。その後、try/catchの後半でその例外に遭遇したときに、スタックトレースを操作して、印刷/保存、または何をしたいかを確認できます。 dlsym()を使用して、___cxa_throw_の実際のバージョンを見つけます。

私の例では、intをスローして、ユーザー定義の例外だけでなく、文字どおり任意の型でこれを実行できることを証明します。

_type_info_を使用して、スローされた型の名前を取得し、それをデマングルします。

必要に応じて、スタックトレースを格納するグローバル変数を少しだけカプセル化することもできます。

私はこれをコンパイルしてテストしました:

g ++ -Wall -Wextra test.cc -g -O0 -rdynamic -ldl

実行すると次のようになります。

 ./ a.out 
 caught a:int 
 ./ a.out(__ cxa_throw + 0x74)[0x80499be] 
 ./ a.out(main + 0x0)[0x8049a61] 
 ./ a.out(main + 0x10)[0x8049a71] 
/lib/i686/cmov/libc.so.6(__ libc_start_main + 0xe6)[0xb75c2ca6] 
 ./ a.out [0x80497e1] 

ただし、これを良いアドバイスの例として使用しないでください。これは、ちょっとした策略と内部のあちこちでできることの例です!

33
Flexo

Linuxでは、これを実装するには、例外コンストラクターで backtrace() への呼び出しを追加して、スタックトレースを例外のメンバー変数にキャプチャします。残念ながら、標準の例外では機能せず、ユーザーが定義した例外でのみ機能します。

4

数年前に私はこれを書きました: C++でのチェーンされた例外のチェーン解除

基本的に、いくつかのマクロは、例外がスローされたときにスタックの巻き戻しが発生した場所を記録します。

フレームワークの更新バージョンは、ライブラリImebra( http://imebra.com )にあります。

その一部を再実装します(スタックトレースをスレッドローカルストレージに格納するなど)。

3
Paolo Brandoli

Flexoのソリューションは非常に優れており、うまく機能します。また、バックトレースアドレスからプロシージャ名への変換がcatchの部分でのみ実行されるという利点もあります。そのため、バックトレースを気にするかどうかに関係なく、例外の受信者次第です。

ただし、libunwindに基づくソリューションを優先できる場合もあります。たとえば、一部のシナリオでは、libunwindがbacktrace関数が失敗するプロシージャ名を収集できるためです。

ここでは、Flexoの回答に基づいたアイデアを示しますが、いくつかの拡張機能があります。 libunwindを使用して、スロー時にバックトレースを生成し、直接stderrに出力します。 libDLを使用して、共有オブジェクトファイル名を識別します。 elfutilsからのDWARFデバッグ情報を使用して、ソースコードファイル名と行番号を収集します。 C++ APIを使用して、C++例外をデマングルします。ユーザーはmExceptionStackTrace変数を設定して、スタックトレースを一時的に有効/無効にすることができます。

インターセプトするすべてのソリューションに関する重要なポイント__cxa_throwは、スタックをウォークするためのオーバーヘッドを潜在的に追加することです。これは、ソースファイル名を収集するためにデバッガシンボルにアクセスするための大きなオーバーヘッドを追加する私のソリューションに特に当てはまります。これは、コードがスローしないことを期待し、スローする(失敗した)テストの強力なスタックトレースが必要な自動テストでは許容できる場合があります。

// Our stack unwinding is a GNU C extension:
#if defined(__GNUC__)
// include elfutils to parse debugger information:
#include <elfutils/libdwfl.h>

// include libunwind to gather the stack trace:
#define UNW_LOCAL_ONLY
#include <libunwind.h>

#include <dlfcn.h>
#include <cxxabi.h>
#include <typeinfo>
#include <stdio.h>
#include <stdlib.h>

#define LIBUNWIND_MAX_PROCNAME_LENGTH 4096

static bool mExceptionStackTrace = false;


// We would like to print a stacktrace for every throw (even in
// sub-libraries and independent of the object thrown). This works
// only for gcc and only with a bit of trickery
extern "C" {
    void print_exception_info(const std::type_info* aExceptionInfo) {
        int vDemangleStatus;
        char* vDemangledExceptionName;

        if (aExceptionInfo != NULL) {
            // Demangle the name of the exception using the GNU C++ ABI:
            vDemangledExceptionName = abi::__cxa_demangle(aExceptionInfo->name(), NULL, NULL, &vDemangleStatus);
            if (vDemangledExceptionName != NULL) {
                fprintf(stderr, "\n");
                fprintf(stderr, "Caught exception %s:\n", vDemangledExceptionName);

                // Free the memory from __cxa_demangle():
                free(vDemangledExceptionName);
            } else {
                // NOTE: if the demangle fails, we do nothing, so the
                // non-demangled name will be printed. Thats ok.
                fprintf(stderr, "\n");
                fprintf(stderr, "Caught exception %s:\n", aExceptionInfo->name());
            }
        } else {
            fprintf(stderr, "\n");
            fprintf(stderr, "Caught exception:\n");
        }
    }

    void libunwind_print_backtrace(const int aFramesToIgnore) {
        unw_cursor_t vUnwindCursor;
        unw_context_t vUnwindContext;
        unw_Word_t ip, sp, off;
        unw_proc_info_t pip;
        int vUnwindStatus, vDemangleStatus, i, n = 0;
        char vProcedureName[LIBUNWIND_MAX_PROCNAME_LENGTH];
        char* vDemangledProcedureName;
        const char* vDynObjectFileName;
        const char* vSourceFileName;
        int vSourceFileLineNumber;

        // This is from libDL used for identification of the object file names:
        Dl_info dlinfo;

        // This is from DWARF for accessing the debugger information:
        Dwarf_Addr addr;
        char* debuginfo_path = NULL;
        Dwfl_Callbacks callbacks = {};
        Dwfl_Line* vDWARFObjLine;


        // initialize the DWARF handling:
        callbacks.find_elf = dwfl_linux_proc_find_elf;
        callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
        callbacks.debuginfo_path = &debuginfo_path;
        Dwfl* dwfl = dwfl_begin(&callbacks);
        if (dwfl == NULL) {
            fprintf(stderr, "libunwind_print_backtrace(): Error initializing DWARF.\n");
        }
        if ((dwfl != NULL) && (dwfl_linux_proc_report(dwfl, getpid()) != 0)) {
            fprintf(stderr, "libunwind_print_backtrace(): Error initializing DWARF.\n");
            dwfl = NULL;
        }
        if ((dwfl != NULL) && (dwfl_report_end(dwfl, NULL, NULL) != 0)) {
            fprintf(stderr, "libunwind_print_backtrace(): Error initializing DWARF.\n");
            dwfl = NULL;
        }


        // Begin stack unwinding with libunwnd:
        vUnwindStatus = unw_getcontext(&vUnwindContext);
        if (vUnwindStatus) {
            fprintf(stderr, "libunwind_print_backtrace(): Error in unw_getcontext: %d\n", vUnwindStatus);
            return;
        }

        vUnwindStatus = unw_init_local(&vUnwindCursor, &vUnwindContext);
        if (vUnwindStatus) {
            fprintf(stderr, "libunwind_print_backtrace(): Error in unw_init_local: %d\n", vUnwindStatus);
            return;
        }

        vUnwindStatus = unw_step(&vUnwindCursor);
        for (i = 0; ((i < aFramesToIgnore) && (vUnwindStatus > 0)); ++i) {
            // We ignore the first aFramesToIgnore stack frames:
            vUnwindStatus = unw_step(&vUnwindCursor);
        }


        while (vUnwindStatus > 0) {
            pip.unwind_info = NULL;
            vUnwindStatus = unw_get_proc_info(&vUnwindCursor, &pip);
            if (vUnwindStatus) {
                fprintf(stderr, "libunwind_print_backtrace(): Error in unw_get_proc_info: %d\n", vUnwindStatus);
                break;
            }

            // Resolve the address of the stack frame using libunwind:
            unw_get_reg(&vUnwindCursor, UNW_REG_IP, &ip);
            unw_get_reg(&vUnwindCursor, UNW_REG_SP, &sp);

            // Resolve the name of the procedure using libunwind:
            // unw_get_proc_name() returns 0 on success, and returns UNW_ENOMEM
            // if the procedure name is too long to fit in the buffer provided and
            // a truncated version of the name has been returned:
            vUnwindStatus = unw_get_proc_name(&vUnwindCursor, vProcedureName, LIBUNWIND_MAX_PROCNAME_LENGTH, &off);
            if (vUnwindStatus == 0) {
                // Demangle the name of the procedure using the GNU C++ ABI:
                vDemangledProcedureName = abi::__cxa_demangle(vProcedureName, NULL, NULL, &vDemangleStatus);
                if (vDemangledProcedureName != NULL) {
                    strncpy(vProcedureName, vDemangledProcedureName, LIBUNWIND_MAX_PROCNAME_LENGTH);
                    // Free the memory from __cxa_demangle():
                    free(vDemangledProcedureName);
                } else {
                    // NOTE: if the demangle fails, we do nothing, so the
                    // non-demangled name will be printed. Thats ok.
                }
            } else if (vUnwindStatus == UNW_ENOMEM) {
                // NOTE: libunwind could resolve the name, but could not store
                // it in a buffer of only LIBUNWIND_MAX_PROCNAME_LENGTH characters.
                // So we have a truncated procedure name that can not be demangled.
                // We ignore the problem and the truncated non-demangled name will
                // be printed.
            } else {
                vProcedureName[0] = '?';
                vProcedureName[1] = '?';
                vProcedureName[2] = '?';
                vProcedureName[3] = 0;
            }


            // Resolve the object file name using dladdr:
            if (dladdr((void *)(pip.start_ip + off), &dlinfo) && dlinfo.dli_fname && *dlinfo.dli_fname) {
                vDynObjectFileName = dlinfo.dli_fname;
            } else {
                vDynObjectFileName = "???";
            }


            // Resolve the source file name using DWARF:
            if (dwfl != NULL) {
                addr = (uintptr_t)(ip - 4);
                Dwfl_Module* module = dwfl_addrmodule(dwfl, addr);
                // Here we could also ask for the procedure name:
                //const char* vProcedureName = dwfl_module_addrname(module, addr);
                // Here we could also ask for the object file name:
                //vDynObjectFileName = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
                vDWARFObjLine = dwfl_getsrc(dwfl, addr);
                if (vDWARFObjLine != NULL) {
                    vSourceFileName = dwfl_lineinfo(vDWARFObjLine, &addr, &vSourceFileLineNumber, NULL, NULL, NULL);
                    //fprintf(stderr, " %s:%d", strrchr(vSourceFileName, '/')+1, vSourceFileLineNumber);
                }
            }
            if (dwfl == NULL || vDWARFObjLine == NULL || vSourceFileName == NULL) {
                vSourceFileName = "???";
                vSourceFileLineNumber = 0;
            }


            // Print the stack frame number:
            fprintf(stderr, "#%2d:", ++n);

            // Print the stack addresses:
            fprintf(stderr, " 0x%016" PRIxPTR " sp=0x%016" PRIxPTR, static_cast<uintptr_t>(ip), static_cast<uintptr_t>(sp));

            // Print the source file name:
            fprintf(stderr, " %s:%d", vSourceFileName, vSourceFileLineNumber);

            // Print the dynamic object file name (that is the library name).
            // This is typically not interesting if we have the source file name.
            //fprintf(stderr, " %s", vDynObjectFileName);

            // Print the procedure name:
            fprintf(stderr, " %s", vProcedureName);

            // Print the procedure offset:
            //fprintf(stderr, " + 0x%" PRIxPTR, static_cast<uintptr_t>(off));

            // Print a newline to terminate the output:
            fprintf(stderr, "\n");


            // Stop the stack trace at the main method (there are some
            // uninteresting higher level functions on the stack):
            if (strcmp(vProcedureName, "main") == 0) {
                break;
            }

            vUnwindStatus = unw_step(&vUnwindCursor);
            if (vUnwindStatus < 0) {
                fprintf(stderr, "libunwind_print_backtrace(): Error in unw_step: %d\n", vUnwindStatus);
            }
        }
    }

    void __cxa_throw(void *thrown_exception, std::type_info *info, void (*dest)(void *)) {
        // print the stack trace to stderr:
        if (mExceptionStackTrace) {
            print_exception_info(info);
            libunwind_print_backtrace(1);
        }

        // call the real __cxa_throw():
        static void (*const rethrow)(void*,void*,void(*)(void*)) __attribute__ ((noreturn)) = (void (*)(void*,void*,void(*)(void*)))dlsym(RTLD_NEXT, "__cxa_throw");
        rethrow(thrown_exception,info,dest);
    }
}
#endif
2
emmenlau