Подтвердить что ты не робот

Как я могу распечатать трассировку стека за обнаруженные исключения в С++ и вводе кода в С++

Я хочу иметь трассировку стека не только для моих исключений, но также для любых потомков std::exception

Как я понимаю, трассировка стека полностью потеряна, когда исключение поймано из-за разворачивания стека (разворачивание).

Таким образом, единственный способ, с помощью которого я могу его захватить, - вставить код контекстной информации (трассировку стека) в месте вызова конструктора std::exception. Я прав?

Если это так, скажите, пожалуйста, как можно сделать инъекцию кода (если возможно) на С++. Ваш метод может быть не полностью безопасным, потому что он мне нужен только для версии Debug моего приложения. Может быть, мне нужно использовать ассемблер?

Меня интересует только решение для GCC. Он может использовать функции С++ 0x

4b9b3361

Ответ 1

Поскольку вы упомянули, что довольны тем, что является специфическим для GCC, я собрал пример того, как вы можете это сделать. Это чистое зло, хотя, вмешиваясь в внутренние части библиотеки поддержки С++. Я не уверен, что хочу использовать это в производственном коде. В любом случае:

#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, мы можем работать с stacktrace для печати/сохранения или того, что вы хотите сделать. Мы используем dlsym() для нахождения реальной версии __cxa_throw.

В моем примере выбрано int, чтобы доказать, что вы можете сделать это буквально любым типом, а не только своими собственными пользовательскими исключениями.

Он использует type_info, чтобы получить имя типа, который был брошен, а затем разворачивает его.

Вы можете инкапсулировать глобальные переменные, которые хранят stacktrace немного лучше, если вы хотите.

Я скомпилировал и протестировал это с помощью:

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]

Пожалуйста, не принимайте это как пример хорошего совета, хотя - это пример того, что вы можете сделать с небольшим количеством обмана и выкалыванием во внутренностях!

Ответ 2

В Linux это можно реализовать, добавив вызов backtrace() в конструкторе исключения, чтобы захватить трассировку стека в переменную члена исключения, К сожалению, он не будет работать для стандартных исключений, только для тех, которые вы определяете.

Ответ 3

Несколько лет назад я написал это: Unchaining chained exceptions в С++

В основном, некоторые макросы регистрируют место, где происходит раскрутка стека, когда генерируется исключение.

Обновленную версию фреймворка можно найти в библиотеке Imebra (http://imebra.com).

Я бы повторно реализовал некоторые его части (например, сохранение трассировки стека в локальном хранилище потоков).

Ответ 4

Решение от Flexo очень хорошо работает и работает хорошо. Это также имеет то преимущество, что перевод от обратных адресов к именам процедур выполняется только в части catch, поэтому до получателя исключения, если они заботятся о backtrace или нет.

Однако есть также случаи, когда решение, основанное на libunwind, может быть предпочтительным, т.е. поскольку libunwind может в некоторых сценариях собирать имена процедур, где функции backtrace не могут этого сделать.

Здесь я представляю идею, основанную на ответе Flexo, но с несколькими расширениями. Он использует libunwind для создания обратной линии во время броска и непосредственно печатает на stderr. Он использует libDL для идентификации имени файла общего объекта. Он использует отладочную информацию DWARF от elfutils для сбора имени файла исходного кода и номера строки. Он использует API С++ для демонстрации исключений С++. Пользователи могут установить переменную 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