Модераторы: feodorv, GremlinProg, xvr, Fixin
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> SEH и STL исключения, все под одну гребенку 
:(
    Опции темы
BearFear
Дата 1.9.2012, 18:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Всем приуэт. Возникла следующая дилемма. Для весьма простенькой программы надо предоставить возможность вывода информации об ошибках (замучала меня эта тема блин). После прочтения статей и бла-бла-бла... в общем родился вот такой велосипед с тремя колесами и одной педалью.

Код

// main.cpp
int main(int argc, char** argv) {
    try {
        shell main_shell(shelled_main);
        main_shell.start();
    } catch (std::exception &Exception) {
        // std исключение
        // Аналогично следующему catch, только вместо аргументов указывается строка описания ошибки Exception.how() которая выводится в лог

    } catch (shell_exception &Exception) {
        // shell исключение
        // Вызов RaisedException с кодом под ПОЛЬЗОВАТЕЛЬСКИЕ исключения с указанием кода ошибки в аргументах исключения (до 15ти аргументов)

    } catch (...) {
        // вывод без информации об исключении. Только время, код (соответствующий) и прочая шелуха.
    }

    return 0;
}


В данном случае, создается объект для данного потока, устанавливающий ExceptionHandler для системы и принимающий указатель на функцию shelled_main которая заменяет стандартный мэйн. Далее, вызывается shelled_main и в случае необходимости кидается throw который и обрабатывается указанным try-catch. В случае возникновения исключения stl соответственно обрабатывается условия под исключения stl. В случае если throw без аргумента (класса исключения) то соответственная ветка catch(...). (поправка: пустой throw вызовет unexpected)

Код

// shell.hpp

typedef int (*main_function)();
typedef unsigned long (*thread_function)();

class shell {
public:
    shell(main_function MainFunction); // ..............................................> создать shell для главного потока
    shell(thread_function ThreadFunction); // ..........................................> создать shell для параллельного потока
    void start(); // ...................................................................> вызвать в контексте главного или параллельного потока

private:
    main_function _MainFunction;
    thread_function _ThreadFunction;
    static long __stdcall exception_handler(EXCEPTION_POINTERS *ExceptionPointers); // .> обработчик runtime исключений
};


Объект shell предназначен для установки обработчиков как для main, так и для потоков, которые создаются отдельным объектом, внутри прокси-функции так же производится подобная установка try-catch и вызов пользовательской функции, которая должна выполняться в контексте потока.

Код

// shell.cpp
shell::shell(main_function MainFunction) {
    SetErrorMode(SEM_NOGPFAULTERRORBOX); // ..................................................> установить тип отчета об ошибках (без диалога)
    SetUnhandledExceptionFilter(exception_handler); // .......................................> установить обработчик ошибок (runtime_error)
    _MainFunction = MainFunction;
    _ThreadFunction = NULL;
}

shell::shell(thread_function ThreadFunction) {
    SetErrorMode(SEM_NOGPFAULTERRORBOX); // ..................................................> установить тип отчета об ошибках (без диалога)
    SetUnhandledExceptionFilter(exception_handler); // .......................................> установить обработчик ошибок (runtime_error)    
    _ThreadFunction = ThreadFunction;
    _MainFunction = NULL;
}

void shell::start() {
    if (_MainFunction != NULL) {
        _MainFunction();
        return;
    }

    if (_ThreadFunction != NULL) {
        _ThreadFunction();
        return;
    }

}

long __stdcall shell::exception_handler(EXCEPTION_POINTERS *ExceptionPointers) {
    // Здесь будет располагаться обработчик системных ошибок, который будет
    // производить отчет в лог без возможности возобновления работы.
    return EXCEPTION_EXECUTE_HANDLER;
}

Фактически, такой код приведет к генерации от 1го до 2х исключений подряд. В случае систем исключения, вызывается exception_handler класса shell для данного потока. В случае stl исключения, сначала вызывается исключение уровня try-catch где производится формирование инфы под одну гребенку и вызов системного исключения с уникальным (отличным от системных кодов) кодом исключения. Фактически, цель этого велосипеда, в случае какой либо ошибки, собрать инфу в лог. Повторюсь, ПО позволяет слетать в случае внезапного чиха, так как задача ПО очень тривиальна, хоть и работает с сетью. В общем должны отлавливаться все ошибки и формирование лога проихводится почти одинаково для всех видов ошибок\исключений, будь то рантайм или логические.

Код

// shell_exception.hpp
class shell_exception {
public:
    shell_exception(const char *Module, unsigned long Line);
    
    const char *get_module();
    unsigned long get_line();
    
private:
    char _Module[256];
    unsigned long _Line;
    
};

Для примера, объект принимает в конструкторе имя модуля и номер строки где возникла логическая ошибка. Это дело можно будет потом допустим поменять на что то иное, например код ошибки, который внутри объекта будет использоваться как индекс описания ошибки из файла, и в таком случае на выходе получим уже описание ошибки как для stl::[some_error].

Код

// shell_exception.cpp
shell_exception::shell_exception(const char *Module, unsigned long Line) {
    unsigned int Length = strlen(Module);
    if (Length > 256)
        Length = 256;
    memcpy(_Module, Module, Length);
    _Line = Line;
}

Собстно код, тут только один комментарий. Данный код пока что не эффективен в силу недоделанности системы. Но отделяет от итогового варианта лишь два момента, которые пока что просто не написал.
1) нет статического глобального объекта строки, в которую будут сваливаться имя модуля и код
2) нет реализации вывода в лог из exception_handler и вызовов RaiseException в конструкциях try-catch

Код

// shelled_main.cpp :)
int shelled_main();

Ну тут все ясно, это прототип shelled_main функции главного потока процесса. В итоге, как бы, программист начинает кодирование отсюда. Все остальное является как бы не то что бы закрытым... как бы шаблоном проекта, каркасом последующих приложений.

Код

// shelled_main.cpp
int shelled_main() {
    throw shell_exception(__FILE__, __LINE__);
    return 0;
}

Для примера, дропается throw который будет обработан catch(shell_exception&) и будет выведена инфа о факте исключения, с подробностями.

Код

// thread.hpp
class thread {
public:
    thread(thread_function ThreadFunction); // ...................................................> Конструктор
    ~thread();
    
    void start();
    void pause();
    void stop();
    void wait();
    bool try_wait(unsigned long Interval);

private:
    thread_function _ThreadFunction;
    void *_ThreadHandle;
    unsigned long _ThreadId;
    static unsigned long __stdcall thread_proxy(void *Argument);
};

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

Код

// thread.cpp - здесь я приведу только прокси-функцию между классом потока и пользовательской функции контекста потока >>>
unsigned long __stdcall thread::thread_proxy(void *Argument) {
    try {
        thread *Parent = reinterpret_cast<thread*> (Argument);
        shell thread_shell(Parent->_ThreadFunction);
        thread_shell.start();
    } catch (std::exception &Exception) {
        // std исключение
        // Аналогично как для main.cpp

    } catch (shell_exception &Exception) {
        // shell исключение
        // Аналогично как для main.cpp

    } catch (...) {
        // пустое исключение
        // Аналогично как для main.cpp
    }
    return 1;
}


Вот собственно и весь код. Хотелось бы узнать, братцы, является ли такой велосипед целесообразным в данных условиях:
1 - ПО не предназначено для отлова и решения ошибок, так как расчитано на других юзверей. Система должна быть простой настолько, что бы ошибки сводились к отчету об ошибке и отправке на сервер в случае если ошибка не логическая, а stl или системная. О логической ошибке пользователь должен будет прочитать лог и уже сделать вывод, что у него случилось

2 - ПО не обладает важными данными которые надо сохранять или оперировать локально, это графическое ПО которое всего лишь может отсылать ошибки на сервер (для статистики о багах и собственно что бы знать, что исправлять).

3 - Формат лога об ошибках один для всех ситуаций и выводится в xml формат для его более простого просмотра с помощью другой утилиты, а так же что бы сервер смог например прочитать лог и сохранить нужную для разработов инфу на баг тракере, откуда уже будут разбирать задания все кому не лень.

Это сообщение отредактировал(а) BearFear - 2.9.2012, 01:13
PM MAIL   Вверх
BearFear
Дата 1.9.2012, 18:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Ай, чуть не забыл. Компилятор MinGW.
PM MAIL   Вверх
BearFear
Дата 1.9.2012, 20:22 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



У меня есть только одно сомнение (может о других я не знаю), на счет вызова системного экцепшена из другого экцепшена. Не знаю чем это может быть черевато, так как не до конца еще понимаю всю подноготную односвязного списка обработчиков.
PM MAIL   Вверх
BearFear
Дата 1.9.2012, 22:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Жалко с Касперски не дружу, он бы точно, фыркнув, щелчком пальца решил такой вопрос, ухоха.

Это сообщение отредактировал(а) BearFear - 1.9.2012, 22:02
PM MAIL   Вверх
BearFear
Дата 1.9.2012, 22:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



В excpt.h нашел строчки, которые определяют макросы __try1 и __except1, в теле которых выполняется асм инлайн по установке обработчиков ошибок. Завтра займусь проверкой, если это то что я думаю, то всю мою конструкцию можно будет переписать одной функцией и обработчика с указанием ее хэндла в __try1(exception_handler). Это существенно сократит код.

А ваще, в манере захватчика-террориста, лучше даже будет сделать свой макрос с красивым именем smile

Код

#define __try1(pHandler) __asm__ ("pushl %0;pushl %%fs:0;movl %%esp,%%fs:0;" : : "g" (pHandler));

/*
 * A macro which (despite its name) *removes* an installed
 * exception handler. Should be used only in conjunction with the above
 * install routine __try1.
 * Move the pointer to the old reg. struct (at the current stack
 * position) to fs:0, replacing the pointer we installed above,
 * then add 8 to the stack pointer to get rid of the space we
 * used when we pushed on our new reg. struct above. Notice that
 * the stack must be in the exact state at this point that it was
 * after we did __try1 or this will smash things.
 * >>> скорее всего не будет пригодно для каких то экзотических решений внутри
 * >>> данной try-except секции. Либо делать поправки по ситуации. В этом придется долго ковыряться.
 */
#define    __except1 __asm__ ("movl (%%esp),%%eax;movl %%eax,%%fs:0;addl $8,%%esp;" : : : "%eax");


Это сообщение отредактировал(а) BearFear - 1.9.2012, 23:06
PM MAIL   Вверх
BearFear
Дата 1.9.2012, 23:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Код

long __stdcall exception_handler(EXCEPTION_POINTERS *ExceptionPointers) {
    printf("shell_except is works");
    return EXCEPTION_EXECUTE_HANDLER;
}

#define shell_try asm ("push %0\n" "push fs:0\n" "mov fs:0, esp\n" : : "g" (exception_handler));
#define    shell_except asm ("mov eax, [esp]\n" "mov fs:0, eax\n" "add esp, 8\n");

int main(int argc, char** argv) {
    shell_try {
        RaiseException(0x06669990, EXCEPTION_NONCONTINUABLE, 0, NULL);
    } shell_except
    
    return 0;
}


Код

gcc ... -masm=intel


Код

HUMAN_SAID: seh_exp.exe
shell_except is works


Прикольно  smile 
Код

>>> уот здеся shell_try и последующий вызов RaiseException
00401415  |. 68 E4134000               PUSH zfw.004013E4                                                    ; |ExceptionFlags = 4013E4; SE handler installation
0040141A  |. 64:FF35 00000000          PUSH DWORD PTR FS:[0]                                                ; |ExceptionCode
00401421  |. 64:8925 00000000          MOV DWORD PTR FS:[0],ESP                                             ; |
00401428  |. C74424 0C 00000000        MOV DWORD PTR SS:[ESP+C],0                                           ; |
00401430  |. C74424 08 00000000        MOV DWORD PTR SS:[ESP+8],0                                           ; |
00401438  |. C74424 04 01000000        MOV DWORD PTR SS:[ESP+4],1                                           ; |
00401440  |. C70424 90996606           MOV DWORD PTR SS:[ESP],6669990                                       ; |
00401447  |. E8 540D0000               CALL <JMP.&KERNEL32.RaiseException>                                  ; \RaiseException
>> конец shell_try

Код

>>> а здеся shell_except (хотя его можно обозвать как end_try) 
00401446   . 8B0424                    MOV EAX,DWORD PTR SS:[ESP]
00401449   . 64:A3 00000000            MOV DWORD PTR FS:[0],EAX
0040144F   . 83C4 08                   ADD ESP,8
>>> конец shell_except

Код

>>> тело хандлера
004013E4  /$ 55                        PUSH EBP                                                             ;  Structured exception handler
004013E5  |. 89E5                      MOV EBP,ESP
004013E7  |. 83EC 18                   SUB ESP,18
004013EA  |. C70424 64404000           MOV DWORD PTR SS:[ESP],zfw.00404064                                  ; |ASCII "shell_except is works"
004013F1  |. E8 520D0000               CALL <JMP.&msvcrt.printf>                                            ; \printf
004013F6  |. B8 01000000               MOV EAX,1
004013FB  |. C9                        LEAVE
004013FC  \. C2 0400                   RETN 4


Используя __cyg_profile_func_[enter|exit] можно довести до автоматизма некоторые вещи, например автоматическая установка обработчиков для определенных функций.

Живые есть? Самому с собой не прикольно болтать  smile 

Это сообщение отредактировал(а) BearFear - 2.9.2012, 00:01
PM MAIL   Вверх
BearFear
Дата 2.9.2012, 19:38 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Обнаруживается плавающая ошибка:
 - код исключения не всегда верный
 - аргументы не всегда владеют верными указателями

происходит все в блоке вызова RaiseException внутри catch.
Почему такое может происходить?

Это сообщение отредактировал(а) BearFear - 2.9.2012, 19:39
PM MAIL   Вверх
BearFear
Дата 2.9.2012, 21:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Ну что, никто не сможет помочь? Никто не знает как в низах работают STL исключения?

Это сообщение отредактировал(а) BearFear - 2.9.2012, 21:20
PM MAIL   Вверх
kosmonaFFFt
Дата 3.9.2012, 06:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 538
Регистрация: 14.4.2008
Где: Иннополис

Репутация: нет
Всего: 5



Цитата(BearFear @  1.9.2012,  22:06 Найти цитируемый пост)
// shell_exception.hpp
class shell_exception {
public:
    shell_exception(const char *Module, unsigned long Line);
    
    const char *get_module();
    unsigned long get_line();
    
private:
    char _Module[256];
    unsigned long _Line;
    
};



А зачем использовать массив char и длину, когда есть ::std::string?

А что касается исключений, то я например использую ::boost::exception для проброса информации об ошибке в блок catch, отдельно ловлю stl исключения, и даже не парюсь на тему низкоуровневых вещей со всякими ассемблерами, и специфичными только для винды вещами (пишу один и тот же проект попеременно то в linux, то в винде)...


--------------------
user posted image
PM MAIL ICQ   Вверх
GremlinProg
Дата 3.9.2012, 07:15 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 2706
Регистрация: 9.8.2005
Где: Тюмень

Репутация: 99
Всего: 106



Цитата(BearFear @  1.9.2012,  20:06 Найти цитируемый пост)
 сначала вызывается исключение уровня try-catch где производится формирование инфы под одну гребенку и вызов системного исключения с уникальным (отличным от системных кодов) кодом исключения

ну и зачем этот, к тому же "вложенный" велосипед?
stl и так перехватывает свой собственный SEH (__try-__except), генерируя c++ исключение (try-catch) со своим классом,
какой смысл после этого переводить все обратно в SEH?

ну поставь просто свой собственный SEH в майн'е и фантазируй дальше как душе угодно
Цитата(BearFear @  1.9.2012,  20:06 Найти цитируемый пост)
Для примера, объект принимает в конструкторе имя модуля и номер строки где возникла логическая ошибка. Это дело можно будет потом допустим поменять на что то иное, например код ошибки, который внутри объекта будет использоваться как индекс описания ошибки из файла, и в таком случае на выходе получим уже описание ошибки как для stl::

опять сомнительный велосипед
Цитата(BearFear @  1.9.2012,  20:06 Найти цитируемый пост)
Хотелось бы узнать, братцы, является ли такой велосипед целесообразным в данных условиях

как-то условия не очень вяжутся с определением

если не хочется трогать ПО, в плане диагностики, как ты эту диагностику проведешь-то?
разворачивая стек?
а смысл тогда в "номер строки где возникла логическая ошибка" и т.п.?
ты же не имеешь эту информацию в произвольном участке кода, где может возникнуть исключение

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

или просто поставь Unhandled-фильтр и генерируй свой репорт с версией ПО, адресом и кодом исключения

Добавлено через 7 минут и 22 секунды
кстати, это скорее C++-исключения, а не STL


--------------------
"Гений всегда разумнее, чем умнее. Ум — это машина, разум — водитель этой машины."
PM WWW ICQ   Вверх
BearFear
Дата 3.9.2012, 10:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Да, придется отказаться от вложения исключения. Я толком не смог разобраться почему такое происходит, но после С++ отката аргументы для RaiseException перезаписываются, разумеется в обработчик исключений попадает черт пойми что. В принципе, для исправления ситуации надо пару трок поменять. Убрать RaisedException из catch блоков и добавить туда код логирования. Опять таки, это только для main, скажем так, на крайний случай. Далее я определил макрос critical_error который переопределяет вызов throw с указанием модуля и строки + еще добавил комментарий. Выглядит так critical_error("comment"); На выходе уже идет "source.cpp:45 comment". 

Массив char использовал что бы при отладке не ковыряться в куче подвызовов string объекта smile это временно я сделал smile

отказываться от try-catch я не собираюсь, опять таки, это только самый низкий уровень который выше системного __try __catch. В коде shelled_main и выше будут простые try-catch с возможностью отката неправильных объектов (без перегруженных операторов new) и прочих прелестей. Просто выгоднее и удобнее как для обработки так и для генерации, использовать один алгоритм логирования, в котором будет указано время, адрес, код. аргументы исключения. Стек вызовов в той же Olly отображается, генерить его локально в лог смысла нет. Достаточно знать адрес исключения (начало инструкции до исключения) что бы поставить брейкпоинт там и соблюдая все условия слета, смоделировать ситуацию. Вот.

Спасибо ребят, очень важно было мнение со стороны, хоть какое нибудь smile
PM MAIL   Вверх
BearFear
Дата 3.9.2012, 21:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 93
Регистрация: 10.8.2012

Репутация: нет
Всего: нет



Да, в общем так и сделал. Сначала долго проверял, может быть созданный в try секции shell_exception объект затирается до того как его используют коды в catch по ссылке... но все не то. В общем, теперь добавился объект report, который работает с файлом лога (что бы не писать повторяющийся код вывода в лог). Собстно он весьма гуманно реагирует на запросы о выводе строк, достаточно всего лишь вызвать соответствующий из трех методов для вывода:
А) контекста
Б) текстового описания ошибки для stl или пользовательского throw
В) событие (дружелюбное описание события с выводом циферок и прочего в блок комментариев в логе. Позволяет более ясно видеть картину в соотношении со стеком вызовов).

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

Это сообщение отредактировал(а) BearFear - 3.9.2012, 21:29
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "C/C++: Системное программирование и WinAPI"
Fixin
GremlinProg
xvr
feodorv
  • Большое количество информации и примеров с использованием функций WinAPI можно найти в MSDN
  • Описание сообщений, уведомлений и примеров с использованием компонент WinAPI (BUTTON, EDIT, STATIC, и т.п.), можно найти в MSDN Control Library
  • Непосредственно, перед созданием новой темы, проверьте заголовок и удостоверьтесь, что он отражает суть обсуждения.
  • После заполнения поля "Название темы", обратите внимание на наличие и содержание панели "А здесь смотрели?", возможно Ваш вопрос уже был решен.
  • Приводите часть кода, в которой предположительно находится проблема или ошибка.
  • Если указываете код, пользуйтесь тегами [code][/code], или их кнопочными аналогами.
  • Если вопрос решен, воспользуйтесь соответствующей ссылкой, расположенной напротив названия темы.
  • Один топик - один вопрос!
  • Перед тем как создать тему - прочтите это .

На данный раздел распространяются Правила форума и Правила раздела С++:Общие вопросы .


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Chipset, Step, Fixin, GremlinProg, xvr. feodorv.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Системное программирование и WinAPI | Следующая тема »


 




[ Время генерации скрипта: 0.0557 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.