2013-05-27 6 views
2

Мне нужно сделать систему обработки исключений Linux/GCC совместимой с Windows/MinGW.Обработка исключений и stacktrace под Windows (MinGW/gcc)

Примечание: Мне нужно перехватывать исключения и исключать их из внутренней библиотеки.

Вот как я реализовал его под Linux/GCC ...

Заголовок:

#include <execinfo.h> 
#include <signal.h> 

static void handler(int sig) 
{ 
    // Catch exceptions 
    switch(sig) 
    { 
    case SIGABRT: 
     fputs("Caught SIGABRT: usually caused by an abort() or assert()\n", stderr); 
     break; 
    case SIGFPE: 
     fputs("Caught SIGFPE: arithmetic exception, such as divide by zero\n", 
      stderr); 
     break; 
    case SIGILL: 
     fputs("Caught SIGILL: illegal instruction\n", stderr); 
     break; 
    case SIGINT: 
     fputs("Caught SIGINT: interactive attention signal, probably a ctrl+c\n", 
      stderr); 
     break; 
    case SIGSEGV: 
     fputs("Caught SIGSEGV: segfault\n", stderr); 
     break; 
    case SIGTERM: 
    default: 
     fputs("Caught SIGTERM: a termination request was sent to the program\n", 
      stderr); 
     break; 
    } 

    // Print stacktrace 
    void *array[10]; 
    size_t size; 

    // get void*'s for all entries on the stack 
    size = backtrace(array, 10); 

    // Ctrl+C interrupt => No backtrace 
    if (sig != (int)SIGINT) { 
     // print out all the frames to stderr 
     fprintf(stderr, "Error: signal %d:\n", sig); 
     backtrace_symbols_fd(array, size, 2); 
    } 
    exit(sig); 

} 

Cpp:

signal(SIGABRT, handler); 
signal(SIGFPE, handler); 
signal(SIGILL, handler); 
signal(SIGINT, handler); 
signal(SIGSEGV, handler); 
signal(SIGTERM, handler); 

Все вышеперечисленное отлично работает под Linux. Теперь я хотел бы иметь такое же поведение в версии Windows, в моей библиотеке ...

Вот как я перехватывать исключения:

#include <windows.h> 

static LONG WINAPI windows_exception_handler(EXCEPTION_POINTERS * ExceptionInfo) 
{ 
    switch(ExceptionInfo->ExceptionRecord->ExceptionCode) 
    { 
    case EXCEPTION_ACCESS_VIOLATION: 
     fputs("Error: EXCEPTION_ACCESS_VIOLATION\n", stderr); 
     break; 
    case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 
     fputs("Error: EXCEPTION_ARRAY_BOUNDS_EXCEEDED\n", stderr); 
     break; 
    case EXCEPTION_BREAKPOINT: 
     fputs("Error: EXCEPTION_BREAKPOINT\n", stderr); 
     break; 
    case EXCEPTION_DATATYPE_MISALIGNMENT: 
     fputs("Error: EXCEPTION_DATATYPE_MISALIGNMENT\n", stderr); 
     break; 
    case EXCEPTION_FLT_DENORMAL_OPERAND: 
     fputs("Error: EXCEPTION_FLT_DENORMAL_OPERAND\n", stderr); 
     break; 
    case EXCEPTION_FLT_DIVIDE_BY_ZERO: 
     fputs("Error: EXCEPTION_FLT_DIVIDE_BY_ZERO\n", stderr); 
     break; 
    case EXCEPTION_FLT_INEXACT_RESULT: 
     fputs("Error: EXCEPTION_FLT_INEXACT_RESULT\n", stderr); 
     break; 
    case EXCEPTION_FLT_INVALID_OPERATION: 
     fputs("Error: EXCEPTION_FLT_INVALID_OPERATION\n", stderr); 
     break; 
    case EXCEPTION_FLT_OVERFLOW: 
     fputs("Error: EXCEPTION_FLT_OVERFLOW\n", stderr); 
     break; 
    case EXCEPTION_FLT_STACK_CHECK: 
     fputs("Error: EXCEPTION_FLT_STACK_CHECK\n", stderr); 
     break; 
    case EXCEPTION_FLT_UNDERFLOW: 
     fputs("Error: EXCEPTION_FLT_UNDERFLOW\n", stderr); 
     break; 
    case EXCEPTION_ILLEGAL_INSTRUCTION: 
     fputs("Error: EXCEPTION_ILLEGAL_INSTRUCTION\n", stderr); 
     break; 
    case EXCEPTION_IN_PAGE_ERROR: 
     fputs("Error: EXCEPTION_IN_PAGE_ERROR\n", stderr); 
     break; 
    case EXCEPTION_INT_DIVIDE_BY_ZERO: 
     fputs("Error: EXCEPTION_INT_DIVIDE_BY_ZERO\n", stderr); 
     break; 
    case EXCEPTION_INT_OVERFLOW: 
     fputs("Error: EXCEPTION_INT_OVERFLOW\n", stderr); 
     break; 
    case EXCEPTION_INVALID_DISPOSITION: 
     fputs("Error: EXCEPTION_INVALID_DISPOSITION\n", stderr); 
     break; 
    case EXCEPTION_NONCONTINUABLE_EXCEPTION: 
     fputs("Error: EXCEPTION_NONCONTINUABLE_EXCEPTION\n", stderr); 
     break; 
    case EXCEPTION_PRIV_INSTRUCTION: 
     fputs("Error: EXCEPTION_PRIV_INSTRUCTION\n", stderr); 
     break; 
    case EXCEPTION_SINGLE_STEP: 
     fputs("Error: EXCEPTION_SINGLE_STEP\n", stderr); 
     break; 
    case EXCEPTION_STACK_OVERFLOW: 
     fputs("Error: EXCEPTION_STACK_OVERFLOW\n", stderr); 
     break; 
    default: 
     fputs("Error: Unrecognized Exception\n", stderr); 
     break; 
    } 
    fflush(stderr); 

    if (EXCEPTION_STACK_OVERFLOW != ExceptionInfo->ExceptionRecord->ExceptionCode) 
    { 
     // TODO : ... 
     //windows_print_stacktrace(ExceptionInfo->ContextRecord); 
    } 

    return EXCEPTION_EXECUTE_HANDLER; 
} 

Вот как можно было бы напечатать StackTrace :

#include <windows.h> 
#include <imagehlp.h> 
void windows_print_stacktrace(CONTEXT* context) 
{ 
    SymInitialize(GetCurrentProcess(), 0, true); 

    STACKFRAME frame = { 0 }; 

    /* setup initial stack frame */ 
    frame.AddrPC.Offset   = context->Eip; 
    frame.AddrPC.Mode   = AddrModeFlat; 
    frame.AddrStack.Offset  = context->Esp; 
    frame.AddrStack.Mode  = AddrModeFlat; 
    frame.AddrFrame.Offset  = context->Ebp; 
    frame.AddrFrame.Mode  = AddrModeFlat; 

    while (StackWalk(IMAGE_FILE_MACHINE_I386 , 
        GetCurrentProcess(), 
        GetCurrentThread(), 
        &frame, 
        context, 
        0, 
        SymFunctionTableAccess, 
        SymGetModuleBase, 
        0)) 
    { 
    addr2line(global_program_name, (void*)frame.AddrPC.Offset); 
    } 

    SymCleanup(GetCurrentProcess()); 
} 

Где addr2line будет:

#include <stdlib.h> 
#include <stdio.h> 

/* Resolve symbol name and source location given the path to the executable 
    and an address */ 
int addr2line(char const * const program_name, void const * const addr) 
{ 
    char addr2line_cmd[512] = {0}; 

    /* have addr2line map the address to the relent line in the code */ 
    sprintf(addr2line_cmd,"addr2line -f -p -e %.256s %p", program_name, addr); 

    /* This will print a nicely formatted string specifying the 
    function and source line of the address */ 
    return system(addr2line_cmd); 
} 

Но:

MinGW не backtrace, Пустоты backtrace_symbols особенности. И вышеизложенное требует знать global_program_name, которого у меня нет, поскольку код для управления исключениями находится в dll основных загрузках программы.

Так Вопрос:

Есть ли способ, чтобы получить global_program_name динамически из библиотеки DLL? А если нет, то это будет хороший подход, чтобы получить стек, напечатанный с помощью MinGW?

Nota Bene: Еще один подзапрос меня дразнит в этот момент. Чтобы получить хорошие стеки, мне нужно включить опцию компилятора -g. Получаю ли я производительность, используя его (даже если я буду поддерживать максимальную оптимизацию -O3)? Или я просто влияю на размер моей общей библиотеки?

Спасибо за любую помощь по этому вопросу.

+1

Вы можете включить все [код для стека блуждания] (http://stackoverflow.com/a/6207030/179910) вместо того, чтобы запустить внешний процесс значительная его часть. –

+0

Ну, моя цель здесь заключалась в том, чтобы сохранить код отслеживания стека вне досягаемости конечного пользователя. Пользователь связывает DLL и делает свою собственную программу с использованием встроенных функций. В этом контексте stacktrace используется в основном для исключения исключений (от меня) из dll. Я должен был сказать это сначала в моем вопросе. Или, может быть, я не понял код, на который вы указали? –

+0

Другими словами, пользователь dll не должен иметь дело с каким-либо дополнительным кодом в своей основной программе ... –

ответ

2

На платформах Windows Вы можете получить имя программы с глобальной переменной __argv.

#include <stdlib.h> 

addr2line(__argv[0], addr); 
+0

Хорошо спасибо. Но почему вы удалили «на платформах Windows»? Я не могу распознать его под Linux (gcc (Debian 4.7.2-4) 4.7.2). –

+1

AFAIK 'stdlib.h', входящий в состав MinGW, также доступен на другой платформе, но не специально для Linux. Я проверял это, но забыл добавить примечания к другим платформам;) –

+0

С моей конфигурацией для части Linux это было бы больше похоже на http://stackoverflow.com/a/9098478/1715716. Спасибо за решение MinGW ;-) –

2

Хотя предыдущий ответ может работы иногда, это может потерпеть неудачу во многих ситуациях. Argv [0] - это параметр командной строки, который может быть передан при вызове функций типа execve (включая варианты Windows).Для того, чтобы надежно получить исполняемые использовать следующий код:

TCHAR szExeFileName[MAX_PATH]; 
GetModuleFileName(NULL, szExeFileName, MAX_PATH);