Обработка исключений под linux
От: kov_serg Россия  
Дата: 12.08.06 13:28
Оценка: 8 (2)
### 12/08/06 17:03:16 kovserg:
Доброго времени суток.

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

try {
  int *p=NULL;
  struct A { ~A() { printf("a.dtor\n"); } } a;
  *p=*p;
} catch(...) {
  printf("segmentation fault\n");
}


При возникновении исключения типа access violation или деление на 0
в блок catch(...) программа не попадает, а сразу завершается. При
яем a.dtor не вызывается.

Под windows в VC это решалось ключем компилятора /EHa и кодом вида:

#include <windows.h>
#include <eh.h>
struct MyException {
  MyException(int code) : code {}
  int code;
};
void trans_Exception( unsigned int u, EXCEPTION_POINTERS* exc) {
    throw MyException(u);
}
int init_Exceptions() {
    _set_se_translator(trans_Exception); // compile with: /EHa
    return 1;
}
int auto_init = init_Exceptions();


В если linux обрабатывать сигналы то не получается перевести сигнал
в exception. Главная неясность как при возникновении сигнала SIGSEGV
сделать так что бы вызвались деструкторы для созданных объектов.

Поставил ряд экспериментов с gcc. Суть такая что при возникновении сигнала
access violation просто вызвать throw и дальше программа должна поити по
отлаженной схеме пройтись по unwind tables и грохнуть созданные объекты.
Но как оказалось gcc очень странно генерить кадры исключений. Вообще
непонятно как. Но повсей видимости привязывается к месту возникновения
исключения. Решение подобной проблеммы видел только для Tru64
там есть встроенная функция преобразования сигналов в exception
exc_raise_signal_exception
http://www.camk.edu.pl/doc/ccc/Programmers_Guide/XCPTCHPX.HTM

Проделать такое в SuSe, Derbian и RedHat неудалось там такого
даже близко нету . Но ведь неможет такого быть что бы подобная проблемма
не возникала до меня.

Вот код для проведения экспериментов по преобразованию signal -> exception

#include <signal.h>
#include <iostream>
#include <stdexcept>
using namespace std;

static void av_throw() {
    //cout << "prepare to die\n";
    throw 0xDEAD;
}

void emu_call(sigcontext *regs,int subr,int ret_addr) {
    regs->esp-=sizeof(int);
    *(int*)regs->esp=ret_addr;
    regs->eip=subr;
}

static void sig_action(int signum, siginfo_t *si, void* unk) {
    sigcontext *regs=(sigcontext*)((int*)unk+5); // экспериментально подобрано
    //printf("sig action %d\n",signum);
    //printf("old: eip=%08X esp=%08X\n",regs->eip,regs->esp);

    //regs->eip+=7;  // 1. это работает (просто пропускаем место аварии)
    //regs->eip=(int)av_throw; // 2. симулим jmp -- это падает -> signal_abort
    { // 3.  симулим call
        emu_call(regs,(int)av_throw,regs->eip+12); // 3a. с этим адресом возврата работает!!!
        //emu_call(regs,(int)av_throw,regs->eip+7); // 3b. с этим вызывает abort_signal
                                                    // 3b. если добавить ключ -fnon-call-exceptions то тоже работает. правда почему непонятно
        //emu_call(regs,(int)av_throw,regs->eip); // 3с.  с этим вызывает abort_signal
        // видимо его exception_frames как то связаны с адресом возрата
    }
    //throw 0x33; // 4. просто приводит к abort_signal
    //printf("new: eip=%08X esp=%08X\n",regs->eip,regs->esp);
    //sigreturn(regs); // warning: sigreturn is not implemented and will always fail
}

void __throw() {
    throw 0x12378;
}

void test() {
    struct A {
        ~A() { cout<<"a is dead\n"; }
    } a;
    int *p=NULL;
    *p=*p;
    __throw();
}

void sig_test() {
    struct sigaction sa, old_sa;
    sa.sa_sigaction = &sig_action;
    sa.sa_flags     = SA_SIGINFO;
    cout<<"install signal action\n";
    if (sigaction(SIGSEGV, &sa, &old_sa) == -1) throw runtime_error("can't set signal handler");
    try {
      test();
    } catch(int x) {
        cout<<"catch.int="<<x<<endl;
    } catch(...) {
        cout<<"catch.something\n";
    }
}

Вариант 3a. работает. Вариант 3b может работать а может и падать в зависимости от ключа компиляции, вариант 3c всёгда падает.

3a.
install signal action
a is dead
catch.int=57005
Press Enter to continue!

3c.
install signal action
terminate called after throwing an instance of 'int'
/bin/sh: line 1:  7628 Aborted                 ./threads_tst
Press Enter to continue!


Вот что генерит gcc для void test() собственно осюда смещения +7 и +12
Dump of assembler code for function _Z4testv:
0x0804bb7a <test()+0>:     push   %ebp
0x0804bb7b <test()+1>:     mov    %esp,%ebp
0x0804bb7d <test()+3>:     push   %ebx                        //
0x0804bb7e <test()+4>:     sub    $0x14,%esp                  //
0x0804bb81 <test()+7>:     movl   $0x0,0xfffffff8(%ebp)       //
0x0804bb88 <test()+14>:    mov    0xfffffff8(%ebp),%eax       //
0x0804bb8b <test()+17>:    mov    (%eax),%edx                 // +0   -- page fault point
0x0804bb8d <test()+19>:    mov    0xfffffff8(%ebp),%eax       //
0x0804bb90 <test()+22>:    mov    %edx,(%eax)                 //
0x0804bb92 <test()+24>:    call   0x804bb2c <__throw()>       // +7   -- point1
0x0804bb97 <test()+29>:    lea    0xfffffff7(%ebp),%eax       // +12  -- point2
0x0804bb9a <test()+32>:    call   0x804bb5a <test()::A::~A()> // 
0x0804bb9f <test()+37>:    jmp    0x804bbbd <test()+67>       //
0x0804bba1 <test()+39>:    mov    %eax,0xffffffe8(%ebp)       // повидимому unwind handler
0x0804bba4 <test()+42>:    mov    0xffffffe8(%ebp),%ebx       // правда как он его находит
0x0804bba7 <test()+45>:    lea    0xfffffff7(%ebp),%eax       // пока остаётся загадкой :)
0x0804bbaa <test()+48>:    call   0x804bb5a <test()::A::~A()> // a.dtor()
0x0804bbaf <test()+53>:    mov    %ebx,0xffffffe8(%ebp)       //
0x0804bbb2 <test()+56>:    sub    $0xc,%esp                   //
0x0804bbb5 <test()+59>:    pushl  0xffffffe8(%ebp)            //
0x0804bbb8 <test()+62>:    call   0x80495c8 <std::runtime_error::~runtime_error()+384> // передача управления дальше
0x0804bbbd <test()+67>:    mov    0xfffffffc(%ebp),%ebx  
0x0804bbc0 <test()+70>:    leave  
0x0804bbc1 <test()+71>:    ret    
End of assembler dump.

Может кто знает как gcc строит свои unwind tables (как он привязан к стеку).
Или може есть какой ключ компиляции который позволяет сменить способ постраения unwind table,
Что бы при возникновении access violation или деления на ноль код обрабатывался по схеме как в 3a ??
Люди help ! очень надо! незнаю что делать.



30.08.06 15:30: Перенесено модератором из 'C/C++' — Павел Кузнецов
Re: Обработка исключений под linux
От: Аноним  
Дата: 12.08.06 14:20
Оценка:
А почему нельзя обрабатывать асинхронный сигнал, зачем надо переводить асинхронный сигнал в exception?
Почему нельзя поставить обработчик на сигнал и в нем удалять все необходимые объекты?
Re[2]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 12.08.06 14:30
Оценка:
Здравствуйте, Аноним, Вы писали:

А>А почему нельзя обрабатывать асинхронный сигнал, зачем надо переводить асинхронный сигнал в exception?

А>Почему нельзя поставить обработчик на сигнал и в нем удалять все необходимые объекты?

Как ???
Re[3]: Обработка исключений под linux
От: Аноним  
Дата: 12.08.06 16:28
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, Аноним, Вы писали:


А>>А почему нельзя обрабатывать асинхронный сигнал, зачем надо переводить асинхронный сигнал в exception?

А>>Почему нельзя поставить обработчик на сигнал и в нем удалять все необходимые объекты?

_>Как ???


man 2 signal
man 7 signal
1е — как вызывать. 2е — список доступных сигналов.
Re[4]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 13.08.06 13:30
Оценка:
Здравствуйте, Аноним, Вы писали:

А>>А почему нельзя обрабатывать асинхронный сигнал, зачем надо переводить асинхронный сигнал в exception?

А>>Почему нельзя поставить обработчик на сигнал и в нем удалять все необходимые объекты?
_>Как ???
A> man 2 signal
A> man 7 signal
A> 1е — как вызывать. 2е — список доступных сигналов.

Проблема не в том как вызывать сигналы или какие они вообще есть.

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

Цель всего лишь такая при возникновении деления на 0 или segmentation fault
вызывать стандартный throw MyExceptionClass(); Но я немогу вызвать exception
из произвольного места кода. Так например если я просто сгенерю код
для вызова throw например в стеке а потом вызову это подпрограмму.
Программа скажет что случился неожиданный exception и упадёт.
И плевать она хотела на try {} catch() {}. gcc как то привязывается именно
к месту вызова exception. И как его заставить сменить способ генерации
unwind tables. На вариант где я смогу вызвать throw из любого места кода.

И вообще если есть многопроцессорная система и один из них делит на ноль
то обработка исключения ведётся именно этим процессором, а не передаётся другому!
Так что при возникновении signal код выполняется тем же процессором и скорее всего
даже тем же потоком (По крайне мере так должно выглядеть для пользователя).
По крайне мере стек именно этого потока. При обработке сигнала я могу вернуть
выполнение в туже точку. Но даже если я там соберу код который вызывает
throw. А потом верну управление это не сработает.

>>Почему нельзя поставить обработчик на сигнал и в нем удалять все необходимые объекты

Потому что для этого я должен вручную генерить unwind tables. И везде по коду
вставлять поддержку этого безобразия. Это приведёт к громоздкому коду. И не эффективной
работе кода. И вообще почему я должен вручную выполнять работу компилятора ???
Re[5]: Обработка исключений под linux
От: Cyberax Марс  
Дата: 13.08.06 14:02
Оценка: 2 (1)
kov_serg wrote:
> Цель всего лишь такая при возникновении деления на 0 или segmentation fault
> вызывать стандартный throw MyExceptionClass(); Но я немогу вызвать exception
> из произвольного места кода. Так например если я просто сгенерю код
> для вызова throw например в стеке а потом вызову это подпрограмму.
> Программа скажет что случился неожиданный exception и упадёт.
> И плевать она хотела на try {} catch() {}. gcc как то привязывается именно
> к месту вызова exception. И как его заставить сменить способ генерации
> unwind tables. На вариант где я смогу вызвать throw из любого места кода.
Фигней занимаешься. Уже раз 10 написали, что SEGFAULT может повредить
данные, важные для дальнейшего исполнения программы. SEGFAULT -это
ошибка разработчика и патчить ее обработчиками исключений является
кривостью.

Если нужно использовать программы, которые могут упасть — то для этого в
Юниксах придумали специальное средство — fork+IPC. В качестве IPC можно
использовать MPI для многопроцессорных высокопроизводительных систем.
Posted via RSDN NNTP Server 2.0
Sapienti sat!
Re[6]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 14.08.06 06:03
Оценка: +1
Здравствуйте, Cyberax, Вы писали:

...
>> unwind tables. На вариант где я смогу вызвать throw из любого места кода.
C>Фигней занимаешься. Уже раз 10 написали, что SEGFAULT может повредить
C>данные, важные для дальнейшего исполнения программы. SEGFAULT -это
C>ошибка разработчика и патчить ее обработчиками исключений является
C>кривостью.

C>Если нужно использовать программы, которые могут упасть — то для этого в

C>Юниксах придумали специальное средство — fork+IPC. В качестве IPC можно
C>использовать MPI для многопроцессорных высокопроизводительных систем.

Потому и пишу что решения пока не нашел. Я просто надеялся что кто-нибудь
уже сталкивался с такой проблеммой и может что посоветовать. Но пока вижу
только непонимание. "О осинхронное событие ужоснах... Ты что, смерись...
Или иди читай man-ы"
Я пока вижу только одно решение смена компилятора. Мне совершенно не нужно
fork+IPC и подобные прибабахи для много процессорных систем. Должно быть
максимально просто и прозрачно. Мне надо что бы обычная программа которая
использует внешние модули не падала на таких простых ошибках. И компилировалась
как под Windows так и под Linux.
Когда я в первый раз увидел что происходит с моей прогой при делении
на ноль в linux у меня волосы дыбом встали.

try { fast_calc(); } catch (MathOverflow &e) { verbose_calc(log_file); }

Вместо подробного отчета о том где случилось переполнение просто приложение падает.
Причем все потоки тросто аварийно закрываются. Несомненно, Очень Удобно!
Re[5]: Обработка исключений под linux
От: Аноним  
Дата: 15.08.06 08:49
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, Аноним, Вы писали:


А>>>А почему нельзя обрабатывать асинхронный сигнал, зачем надо переводить асинхронный сигнал в exception?

А>>>Почему нельзя поставить обработчик на сигнал и в нем удалять все необходимые объекты?
_>>Как ???
A>> man 2 signal
A>> man 7 signal
A>> 1е — как вызывать. 2е — список доступных сигналов.

_>Проблема не в том как вызывать сигналы или какие они вообще есть.


_>Проблемма в другом. Есть приложение оно запускает несколько потоков

_>в каждом потоке могут создаваться объекты и вызываться внешние модули.
_>Всё бы хорошо но при возникновении обращения но NULL или деления на ноль
_>Если вообще не обрабатывать сигнал приложение немедленно завершается
_>при этом про все созданные объекты забывают.
_>Если же при возникновении сигнала просто убивать сглючивший поток то
_>в памяти накапливаются объекты, файлы, соединения не закрываются.

Так введите список этих объектов, и удаляйте в обработчике сигнала.
Re[7]: Обработка исключений под linux
От: Аноним  
Дата: 15.08.06 08:53
Оценка:
Здравствуйте, kov_serg, Вы писали:


_>Я пока вижу только одно решение смена компилятора. Мне совершенно не нужно

_>fork+IPC и подобные прибабахи для много процессорных систем.

Вообще-то это обычный путь для Unix систем, и никак не связан с количеством
процессов, fork в Unix очень дешев,
используйте процессы и в Windows,
есть же небось что-нибудь boost::process

_>
_>try { fast_calc(); } catch (MathOverflow &e) { verbose_calc(log_file); }
_>

_>Вместо подробного отчета о том где случилось переполнение просто приложение падает.
_>Причем все потоки тросто аварийно закрываются. Несомненно, Очень Удобно!

Ну предложите механизм, который будет работать одинаково хорошо на всех платформах,
которые поддерживает gcc и linux, и тогда возвращайтесь с критикой.
Re[6]: Обработка исключений под linux
От: demi США  
Дата: 15.08.06 09:11
Оценка:
Здравствуйте, Аноним, Вы писали:

Про линукс я не знаю, но немного расскажу про Винду и VC. Так вот. Такой код:
try
{
   volatile int* p = 0;
   *p = 0;
}
catch (...)
{
   printf("Vse, kranti!\n");
}


Не факт, что он выведет такое замечательное сообщение. Потому что throw это синхронное исключение, а разыменование NULL нет. В последнем случае в терминологии Windows происходит обработка SEH. И именно, системой вызывается функция по адресу FS:[0]. Далее, в зависмости от кода возврата проиходит поиск-выполнение-продолжение работы. VC при компиляции делает так, что в FS:[0] попадает обработчик C++ эксепшенов, который занимается тем, что по type_info ищет нужный обработчик. В данном случае, все проходят фильтр и попадают в тело обрабочика. Понятно, что и SEH туда попадают. Так вот, это багофича MS. Нет строчки в стандарте о том, что асинхронные исключение должны быть перехвачены. Более того, несмотря на то, что это может показаться неплохим поведением, это плохо. Потому что при обработке SEH мы не можем полагаться на некотрые инварианты, гарантируемые языком.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Re[7]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 15.08.06 12:56
Оценка:
Здравствуйте, demi, Вы писали:

D>Про линукс я не знаю, но немного расскажу про Винду и VC. Так вот. Такой код:

D>
D>try
D>{
D>   volatile int* p = 0;
D>   *p = 0;
D>}
D>catch (...)
D>{
D>   printf("Vse, kranti!\n");
D>}
D>

Я уже писал "Под windows в VC это решалось ключем компилятора /EHa ...". Мне просто нужен аналого под gcc (если есть)

D>Не факт, что он выведет такое замечательное сообщение. Потому что throw это синхронное исключение, а разыменование NULL нет. В последнем случае в терминологии Windows происходит обработка SEH. И именно, системой вызывается функция по адресу FS:[0]. Далее, в зависмости от кода возврата проиходит поиск-выполнение-продолжение работы. VC при компиляции делает так, что в FS:[0] попадает обработчик C++ эксепшенов, который занимается тем, что по type_info ищет нужный обработчик. В данном случае, все проходят фильтр и попадают в тело обрабочика. Понятно, что и SEH туда попадают. Так вот, это багофича MS. Нет строчки в стандарте о том, что асинхронные исключение должны быть перехвачены. Более того, несмотря на то, что это может показаться неплохим поведением, это плохо. Потому что при обработке SEH мы не можем полагаться на некотрые инварианты, гарантируемые языком.


Схему по которой обрабатывает исключения Windows я знаю. Но даже если в linux нету аналога сегмента задачи FS можно же сделать общую функцию вида TaskInformationBlock* getCurrentTIB(); В котором держать EFP-ExceptionFramePointer. А дальше по традиционной схеме. Проблем с реализацией я вобщем-то не вижу. Но переписывать GCC я не собираюсь. И добавлять в код конструкии которые будут делать это за компилятор тоже не очень хочу.

И еще. Я наверно идиот. Но я не понимаю почему деление на 0 это асинхронное событие? Да и PageFault тоже обрабатывается тем же процессором что и вызвал его. Неважно что он в i386 сначала на Ring0 попадает. Для кода пользователя это должно выглядеть как-будто оно случилось там. ( push flags;call intterrupt_handler )
И какие собственно инварианты тут разрушаются я тоже не вижу! Приведите пример где такие инварианты не сохраняются.
Re[8]: Обработка исключений под linux
От: demi США  
Дата: 15.08.06 15:12
Оценка:
_>И еще. Я наверно идиот.
Ну ваши мыслительные способности я думаю как выше среднего.


_>Но я не понимаю почему деление на 0 это асинхронное событие? Да и PageFault тоже обрабатывается тем же процессором что и вызвал _>его. Неважно что он в i386 сначала на Ring0 попадает. Для кода пользователя это должно выглядеть как-будто оно случилось там. ( _>push flags;call intterrupt_handler )

Почему асинхронное? Да потому, что его инициирует и ловит (ну со всеми оговорками) система. PageFault тот же. Вы его средствами языка можете создать? Наверно, нет. Создайте ооогромный массив, бегите по нему. Да случится page-fault поганый. Он отрботается ситемой, страница будет подгружена — но не вы его кидали, и не в состоянии это контролировать (когда кидать). То же самое с FPU. Маскируем прерывания или нет. Как вы сами его возбудите? Тут дело не в том, кто добивается эксепшена (это всегда вы, ваш код), а кто его ВЫРАБАТЫВЕТ. throw — синхронное, потому что его вырабатывает не ОС и железо, а криворукие программисты. Деление на ноль — его тоже находит железо. А если бы это было так:
int idiv(int a, int b)
{
    if (b == 0)
        throw "division by zero";
    return a/b;
}

То это было бы синхронно. Улавливаете в чем фишка?

_>И какие собственно инварианты тут разрушаются я тоже не вижу! Приведите пример где такие инварианты не сохраняются.

К примеру. У нас есть машина, которая в отличие от Wintel архитектуры не так мягко отрабатывает исключение, связанное с выравниванием. Пусть это жесткий эксепшн. И пусть компилятор творит что-то как MSVC — catch(...) его ловит.
int array[8] = {0}; //гарантированно выровнен
int* p = (int*)(((char*)a1)+1); //не выровнен, обращение ведет к hardware exception
try
{
    *p = 20;
    a1[0] = 2;
}
catch (...)
{
    a1[0] = 1;
}
//что должно быть в a1?


Первое. Компилятор имеет право вынести разыменование в try (оптимизация типа). Потому что оно не возбуждает исключений — и тут бац! поведение программы изменилось. И вообще убрать try-блок. И тогда что должно быть в a1? Вот как раз на инварианты языка компилятор и полагается, делая оптимизацию. Проявите фантазию, за примерами далеко ходить не надо.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Re[9]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 15.08.06 16:07
Оценка:
Здравствуйте, demi, Вы писали:

...
_>>Но я не понимаю почему деление на 0 это асинхронное событие? Да и PageFault тоже обрабатывается тем же процессором что и вызвал _>его. Неважно что он в i386 сначала на Ring0 попадает. Для кода пользователя это должно выглядеть как-будто оно случилось там. ( _>push flags;call intterrupt_handler )
D>Почему асинхронное? Да потому, что его инициирует и ловит (ну со всеми оговорками) система. PageFault тот же. Вы его средствами языка можете создать? Наверно, нет.
int i=0,j=1/i;

Вот и вызвал. А если у меня процессор например ARM и там нет деления. Оно реализуется небольшой подпрограммой. Деление на 0 тоже ансихронное? Я хочу сказать что подобную ситуацию асинхронной называть странновато. В том смысле что она обрабатывается тем же процессором. Тоесть в процессе обработки прерывания стек моей программы не портится, регистры тоже при возврате из прерывания целые. Вот если бы
for(int i=0;i<10;i++) massiv[i]=i;

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

D>Создайте ооогромный массив, бегите по нему. Да случится page-fault поганый. Он отрботается ситемой, страница будет подгружена — но не вы его кидали, и не в состоянии это контролировать (когда кидать).

Если система поддерживает виртуальную память это её проблеммы. Программа пользователя не должна этого замечать. Так ведь оно и есть. Вы же не можити сесть на обработку сигнала и при каждой подгрузке страниы сообщать об этом.

D>То же самое с FPU. Маскируем прерывания или нет. Как вы сами его возбудите? Тут дело не в том, кто добивается эксепшена (это всегда вы, ваш код), а кто его ВЫРАБАТЫВЕТ.

Вырабатывает его всегда процессор, даже если и сопроцессор, то тоже обработка вдётся основным процессорм. А вот асинхронные NMI,IRQ они приходят из вне.

D>throw — синхронное, потому что его вырабатывает не ОС и железо, а криворукие программисты.

Ну чем прерывания процессора хуже! Их тоже могут вызывать криворуки програмисты.

D>Деление на ноль — его тоже находит железо.

Мне ведь всё равно кто его вырабатывает. Мне его обрабатывать надо.

D>А если бы это было так:

D>
D>int idiv(int a, int b)
D>{
D>    if (b == 0)
D>        throw "division by zero";
D>    return a/b;
D>}
D>

D>То это было бы синхронно. Улавливаете в чем фишка?
Нет не улавливаю. Я немогу понять следующее при обработке сигнала я могу вернуться в тоже место. Но немогу вернутся в другое и вызвать там throw.
int idiv(int a,int b) {
  try {
    return a/b;
  } catch(...) {
    throw "ups"; 
  }
}
void _div_exc() { throw "div by zero"; }
void cpu_interrupt int_on_div() {
  change_return_address(_div_exc);
}

Но это полько в linux.gcc потому что то что генерит gcc как-то р@ком привязано к стеку и к месту возникновения исключения. Но это всего лишь особенность gcc.

_>>И какие собственно инварианты тут разрушаются я тоже не вижу! Приведите пример где такие инварианты не сохраняются.

D>К примеру. У нас есть машина, которая в отличие от Wintel архитектуры не так мягко отрабатывает исключение, связанное с выравниванием. Пусть это жесткий эксепшн. И пусть компилятор творит что-то как MSVC — catch(...) его ловит.
D>
D>int array[8] = {0}; //гарантированно выровнен
D>int* p = (int*)(((char*)a1)+1); //не выровнен, обращение ведет к hardware exception
D>try
D>{
D>    *p = 20;
D>    a1[0] = 2;
D>}
D>catch (...)
D>{
D>    a1[0] = 1;
D>}
D>//что должно быть в a1?
D>

a1[0] должно равнятся 2. В независимости от выравнивания.
Извенити в C/C++ на выравнивание никаких исключений возникать недолжно. Даже если они возникают у процессора. Это опять же забота компилятора. Он должен обходить такие ситуации.

D>Первое. Компилятор имеет право вынести разыменование в try (оптимизация типа). Потому что оно не возбуждает исключений — и тут бац! поведение программы изменилось. И вообще убрать try-блок. И тогда что должно быть в a1? Вот как раз на инварианты языка компилятор и полагается, делая оптимизацию. Проявите фантазию, за примерами далеко ходить не надо.

И вообще прерывание по выравниванию это не проблемма пользовательского кода это головная боль тех кто пишет компиляторы.
Вот если я пишу на assembler-е то это моя головная боль.
Re[10]: Обработка исключений под linux
От: demi США  
Дата: 15.08.06 16:58
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Здравствуйте, demi, Вы писали:

_>
_>for(int i=0;i<10;i++) massiv[i]=i;
_>

_>поделил на 0 вот тут бы я удивился. Или где нибуть поделил на 0, а потом через несколько инструкций выскочило деление на 0.
Замечательно! Я вас поздравляю! Вы самый счастливый человек, потому что именно вы кидаете исключения деления на 0. Или по крайней мере знаете, где это происходит. У вас нет багов, вы всегда знаете, где и когда исключение от системы произойдет. Фигня.

D>>Создайте ооогромный массив, бегите по нему. Да случится page-fault Программа пользователя не должна этого замечать.

И не замечает. А вот сесть могу. И еще как могу! Почитайте MSDN, про VirtualAlloc. Соглашусь, что средствами языка я это не сделаю, но когда мы говорим о page fault, мы не ограничены средствами языка — нет понятия page fault в C++.

_>Вырабатывает его всегда процессор, даже если и сопроцессор, то тоже обработка вдётся основным процессорм. А вот асинхронные NMI,IRQ они приходят из вне.

А типа процессор это не железка? Ему просто не нужен механизм IRQ. Зачем из Москвы в Питер ехать через Владик, когда можно напрямик? Проц тут же вызывает обработчики в IDT/cr0/cr1/c2/cr3 или еще где. Зачем пропускать сигнал через чипсет, южный, блин, тормозной мост, когда все здесь!!!

D>>throw — синхронное, потому что его вырабатывает не ОС и железо, а криворукие программисты.

_>Мне ведь всё равно кто его вырабатывает. Мне его обрабатывать надо.
Кто вам мешает! Вы же не ковыряетесь в зубах гвоздем и не прибиваете полку на зубочистки? Вот и здесь — try{}catch(...){} предназначен для обработки синхронных исключений, а никак не для асинхронных. Багофичи MS это зло. Это не только теория, усвоить раз и навсегда что такое синхронное исключение, что нет. Вы требуете от catch (...) того что он НЕ ОБЯЗАН делать. А именно, перехватывать системно-зависимые исключения. Еще раз скажу. catch(...) гарантирует разворот стека. При системном эксепшене это не всегда возможно! А вы говорите где нарушение инварианта — вот оно. Как семантику соблюсти???

_>Нет не улавливаю. Я немогу понять следующее при обработке сигнала я могу вернуться в тоже место. Но немогу вернутся в другое и вызвать там throw.

Ну уж вы, человек очевидно умный но такое загнете, что я в ауте. Вот что вы хотите. Дайте мне механизм throw-catch и семантикой stack unwind, да еще чтоб он работал, когда будет SEH (win32 терминология), да еще и скакнуть куда угодно! Когда вы в обработчике системного сообщения, до вас на стеке системные функции. И их надо корректно завершить. То есть return в них обратно. Как вы представляете реализацию такого механизма? Как скакнуть в другое место, с учетом того, что есть конструкторы деструкторы и прочий хлам? Только call функции! А это не то что надо. Ну вот как??? делайте longjmp на свой страх и риск. Но выбрасывать ниже по стеку систему — это криминал. (PS. в случае ms багофич системных функций под вами нет, конечно, они уже отработали).

_>Извенити в C/C++ на выравнивание никаких исключений возникать недолжно. Даже если они возникают у процессора. Это опять же забота компилятора. Он должен обходить такие ситуации.

Правда??? Есть прототип, который реально размещен в dll, то есть компилер не может узнать, что он делает:
int* GetIntPointer();
Как прикажете обойти ситуацию КОМПИЛЯТОРУ обойти ситуацию, если он не в состоянии предположить хорошее или "плохое" значение ему вернули? Да, компилятор обязан размещеть на стеке, делать массивы, выравнивать и т.д. так, что ексепшенов нет. Но в случае адресной арифметики что ему делать??? Вот именно из-за адресной арифметики много проблем.

_>И вообще прерывание по выравниванию это не проблемма пользовательского кода это головная боль тех кто пишет компиляторы.

Я повторяю, компилятор ответственнен за многое. Саттер надеюсь в авторитете? Так вот почему он пишет, что (про фаст PIMPL когда распинается) что нельзя делать вместо new так: Помните?

class A
{
    char _obj[/*размер наших данных*/];
};


А? Саттер говорит, что будут проблемы с выравниваем. Саттер пишет компиляторы, и ему ли не знать, за что он отвественнен (компилятор). А вы тут говорите, проблемы компилятора.
Не стыдно попасть в дерьмо, стыдно в нём остаться!
Re[11]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 16.08.06 07:16
Оценка: :)
Здравствуйте, demi, Вы писали:


_>>Здравствуйте, demi, Вы писали:

_>>
_>>for(int i=0;i<10;i++) massiv[i]=i;
_>>

_>>поделил на 0 вот тут бы я удивился. Или где нибуть поделил на 0, а потом через несколько инструкций выскочило деление на 0.
D>Замечательно! Я вас поздравляю! Вы самый счастливый человек, потому что именно вы кидаете исключения деления на 0. Или по крайней мере знаете, где это происходит. У вас нет багов, вы всегда знаете, где и когда исключение от системы произойдет. Фигня.
Посмотри обязательно: http://www.karasik.eu.org/dao.html

D>>>Создайте ооогромный массив, бегите по нему. Да случится page-fault Программа пользователя не должна этого замечать.

D>И не замечает. А вот сесть могу. И еще как могу! Почитайте MSDN, про VirtualAlloc. Соглашусь, что средствами языка я это не сделаю, но когда мы говорим о page fault, мы не ограничены средствами языка — нет понятия page fault в C++.
Даже имея функцию VirtualAlloc вы не в состоянии определить когда система загружает и выгружает подкачиваемую память. Это можно сделать только на уровне ядра.

_>>Вырабатывает его всегда процессор, даже если и сопроцессор, то тоже обработка вдётся основным процессорм. А вот асинхронные NMI,IRQ они приходят из вне.

D>А типа процессор это не железка? ...
Программу можно и на эмуляторе пускать. Так что тут предлагаю не дискутировать, что железка а что нет. Это к философам что первично а что вторично. Сама программа определить это не спобна

D>>>throw — синхронное, потому что его вырабатывает не ОС и железо, а криворукие программисты.

Блин я всёравно не понимаю
  if (a) b();

Разве вызов b() асинхронный?
Чем деление на 0 или доступ к запрещенной области хуже. Влез — получи

А вот асинхронный код
void event_handler(params) {
  // process event
}
void thread1() {
  ...
  if (a1) post_event(params1);
  ...
}
....
void threadN() {
  ...
  if (aN) post_event(paramsN);
  ...
}

Или я не прав. А то что делает процессор intel в защищенном режиме — это же ведь проблеммы OS.

_>>Нет не улавливаю. Я немогу понять следующее при обработке сигнала я могу вернуться в тоже место. Но немогу вернутся в другое и вызвать там throw.

D>... Дайте мне механизм throw-catch и семантикой stack unwind, да еще чтоб он работал, когда будет SEH (win32 терминология), да еще и скакнуть куда угодно! Когда вы в обработчике системного сообщения, до вас на стеке системные функции.
Как показывает практика. Обработка такого исключения ведётся уже после того ка система на всё это посмотрела и вернула управление в код пользователя.
Обработка исключения в отдельном системном стеке было только у DOSextender-ов. Да и при обработке сигналов, если нужен отдельный стек это указывается.

D> И их надо корректно завершить. То есть return в них обратно. Как вы представляете реализацию такого механизма? Как скакнуть в другое место, с учетом того, что есть конструкторы деструкторы и прочий хлам? Только call функции! А это не то что надо.

Чем эта реализация не нравится: http://qxov.narod.ru/articles/seh/seh.html (см: SCOPETABLE)

Меня выравнивание ВАЩЕ не интересует. А компилятор на стадии компиляции може предупреждения выводить если не способен генерить код который с этим справится. Конешно реализации компиляторов бывают разные. И их разработчики могут оправдываться как угодно. МНЕ НАДО КОРРЕКТНО ОБРАБАТЫВАТЬ ДЕЛЕНИЕ НА 0, ПЕРЕПОЛНЕНИЯ И ПАДЕНИЯ НА NULL УКАЗАТЕЛЯХ. И всё. Компилятр vc ето способен делать. GCC той версии которая у меня есть пока не удалось заставить. Сегодня буду пробывать intel c++ compiler
Re: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 16.08.06 09:47
Оценка:
kov_serg wrote:

> Возник вопрос досих пор немогу найти решения.

> Что нужно сказать gcc что бы следующий код заработал
>
> try {
> int *p=NULL;
> struct A { ~A() { printf("a.dtor\n"); } } a;
> *p=*p;
> } catch(...) {
> printf("segmentation fault\n");
> }
>
>
>
> При возникновении исключения типа access violation или деление на 0
> в блок catch(...) программа не попадает, а сразу завершается. При
> яем a.dtor не вызывается.
>
> Под windows в VC это решалось ключем компилятора /EHa и кодом вида:

[]

> В если linux обрабатывать сигналы то не получается перевести сигнал

> в exception. Главная неясность как при возникновении сигнала SIGSEGV
> сделать так что бы вызвались деструкторы для созданных объектов.

Обработай сигнал и опосля вызови деструкторы. Но не в обработчике
сигнала
. Из обработчика сигнала ты не можешь сделать практически ничего,
только вызвать async signal safe ф-цию из списка
http://www.opengroup.org/onlinepubs/000095399/functions/xsh_chap02_04.html#tag_02_04_03

> Поставил ряд экспериментов с gcc. Суть такая что при возникновении сигнала

> access violation просто вызвать throw и дальше программа должна поити по
> отлаженной схеме пройтись по unwind tables и грохнуть созданные объекты.
> Но как оказалось gcc очень странно генерить кадры исключений. Вообще
> непонятно как. Но повсей видимости привязывается к месту возникновения
> исключения.

Вывод верный. gcc генерит код + таблицы, которые по instruction counter находят
exception handler. Поэтому, в отличие от m$vc, вход в / выход из try {}
бесплатен, 0 ассемблерных инструкций.

--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[2]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 16.08.06 16:32
Оценка:
Здравствуйте, MaximE, Вы писали:

...
ME>Обработай сигнал и опосля вызови деструкторы. Но не в обработчике
ME>сигнала
. Из обработчика сигнала ты не можешь сделать практически ничего,
ME>только вызвать async signal safe ф-цию из списка
ME>http://www.opengroup.org/onlinepubs/000095399/functions/xsh_chap02_04.html#tag_02_04_03
Так я в коде так и сделал вернулься из обработчика сигнала в то место где случилось
авария и сделал вид что в этом месте вызвал подпрограмму. Которая уже вызывает throw. Но это не прокатило

ME>... gcc генерит код + таблицы, которые по instruction counter находят

ME>exception handler. Поэтому, в отличие от m$vc, вход в / выход из try {}
ME>бесплатен, 0 ассемблерных инструкций.
А можно по подробней как он их генерит. Если бы я знал то мог найти ближайшую точку и
передать туда управление
Re: Обработка исключений под linux
От: Аноним  
Дата: 17.08.06 08:26
Оценка: :)
Уже писали, но повторю:
Деление на ноль и GPF это не исключения — их никто не кидал при помощи throw, поэтому поймать их при помощи catch() невозможно.

Не уверен, что и при помощи сигналов что-то можно сделать — не все сигналы можно перехватить; возможно, для некоторых сигналов установлены предопределённые действия по выходу из обработчика, которые нельзя отменить (я бы сделал так, ибо нефига).

Что полезного можно сделать, так это запускать две программы и когда одна падает, вторая пусть печатает, что, извините, первая упала.
Re[12]: Обработка исключений под linux
От: Аноним  
Дата: 17.08.06 10:40
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Меня выравнивание ВАЩЕ не интересует. А компилятор на стадии компиляции може предупреждения выводить если не способен генерить код который с этим справится. Конешно реализации компиляторов бывают разные. И их разработчики могут оправдываться как угодно. МНЕ НАДО КОРРЕКТНО ОБРАБАТЫВАТЬ ДЕЛЕНИЕ НА 0, ПЕРЕПОЛНЕНИЯ И ПАДЕНИЯ НА NULL УКАЗАТЕЛЯХ. И всё. Компилятр vc ето способен делать. GCC той версии которая у меня есть пока не удалось заставить. Сегодня буду пробывать intel c++ compiler


На что спорим что он тоже не умеет?
Re[3]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 17.08.06 12:35
Оценка:
kov_serg wrote:

[]

> ME>... gcc генерит код + таблицы, которые по instruction counter находят

> ME>exception handler. Поэтому, в отличие от m$vc, вход в / выход из try {}
> ME>бесплатен, 0 ассемблерных инструкций.
> А можно по подробней как он их генерит. Если бы я знал то мог найти
> ближайшую точку и передать туда управление

Начни с .pdf отсюда http://netlab.ru.is/exception/LinuxCXX.shtml

--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[13]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 17.08.06 15:49
Оценка:
Здравствуйте, Аноним, Вы писали:

А>На что спорим что он тоже не умеет?


На что спорим?
Я уже готов поспорить
Re[2]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 19.08.06 13:56
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Уже писали, но повторю:

А>Деление на ноль и GPF это не исключения — их никто не кидал при помощи throw, поэтому поймать их при помощи catch() невозможно.

А>Не уверен, что и при помощи сигналов что-то можно сделать — не все сигналы можно перехватить; возможно, для некоторых сигналов установлены предопределённые действия по выходу из обработчика, которые нельзя отменить (я бы сделал так, ибо нефига).

Поставил Intel C++ Compiler.v9.1.039 . В нём спокойно можно делать throw из обработчика сигналов . И нет проблем. Всё с чем я немог смерится в gcc тут отсутствует. И работает вполне предсказуемо.

А>Что полезного можно сделать, так это запускать две программы и когда одна падает, вторая пусть печатает, что, извините, первая упала.

Сам такой прогой пользуйся. Придставь делал в ней что-то, а потом при попытке сохранить данные или так без повода, так внезапно, она падает. А вторая прога тебе и говорит: "Ужос дорогой пользователь, все ваши данные не только потеряны но и испорчены. Но могу вас утешить: моей вины в этом нет"
KDeveloper работает по такой схеме. При каждом падении KDeveloper удивляюсь какие нехорошие слова я знаю
Re[3]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 19.08.06 16:38
Оценка:
kov_serg wrote:


> А>Не уверен, что и при помощи сигналов что-то можно сделать — не все

> сигналы можно перехватить; возможно, для некоторых сигналов установлены
> предопределённые действия по выходу из обработчика, которые нельзя
> отменить (я бы сделал так, ибо нефига).
> Поставил Intel C++ Compiler.v9.1.039 . В нём спокойно можно делать throw
> из обработчика сигналов . И нет проблем. Всё с чем я немог смерится в
> gcc тут отсутствует. И работает вполне предсказуемо.

Интересно, предсказуемо ли поведение в следущем сценарии.

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

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 2.0
Re[4]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 21.08.06 06:28
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>kov_serg wrote:



>> А>Не уверен, что и при помощи сигналов что-то можно сделать — не все

>> А>сигналы можно перехватить; возможно, для некоторых сигналов установлены
>> А>предопределённые действия по выходу из обработчика, которые нельзя
>> А>отменить (я бы сделал так, ибо нефига).
>> Поставил Intel C++ Compiler.v9.1.039 . В нём спокойно можно делать throw
>> из обработчика сигналов . И нет проблем. Всё с чем я немог смерится в
>> gcc тут отсутствует. И работает вполне предсказуемо.

ME>Интересно, предсказуемо ли поведение в следущем сценарии.


ME>В плагине твоего приложения иногда происходит деление на 0, ты бросаешь из

ME>обработчика сигнала исключение. Код плагина может не использовать RAII или быть
ME>написан на C, другими словами раскрутка стэка в коде плагина может не освободить
ME>ресурсов, или оставить контекст плагина в рассогласованом состоянии.

Тут всё просто мы зарание предупреждаем разработчиков плагинов что бы они сами
отлавливали свои исключения и освобождали память. И рекомендуем им intel c++ compiler.
Даём примеры и SDK. Но плагин же может выделять память и не освобождать её и при этом
не вызывать исключений. И это будет только на совести тех кто писал плагин.
Re[3]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 21.08.06 11:45
Оценка:
kov_serg wrote:

> А>Уже писали, но повторю:

> А>Деление на ноль и GPF это не исключения — их никто не кидал при помощи
> throw, поэтому поймать их при помощи catch() невозможно.
>
> А>Не уверен, что и при помощи сигналов что-то можно сделать — не все
> сигналы можно перехватить; возможно, для некоторых сигналов установлены
> предопределённые действия по выходу из обработчика, которые нельзя
> отменить (я бы сделал так, ибо нефига).
> Поставил Intel C++ Compiler.v9.1.039 . В нём спокойно можно делать throw
> из обработчика сигналов . И нет проблем. Всё с чем я немог смерится в
> gcc тут отсутствует. И работает вполне предсказуемо.

Это не работает по следующим причинам:

1) Обработчик сигнала вызывется kernel'ом в отдельном стеке, т.е. не в
том стеке, где твои объекты. Чтобы это проверить, выведи адрес переменной на
стеке (не из signal handler), брось исключение из signal handler и выведи адрес
той же переменной из catch().
2) Если компилятор Intel не поддерживает асинхронных исключений (что вероятно
для linux версии), раскрутка стэка не будет работать правильно. Но в свете п.1
это уже не имеет никакого значения.

--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[4]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 21.08.06 12:09
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>kov_serg wrote:


>> А>Уже писали, но повторю:

>> А>Деление на ноль и GPF это не исключения — их никто не кидал при помощи
>> throw, поэтому поймать их при помощи catch() невозможно.
>>
>> А>Не уверен, что и при помощи сигналов что-то можно сделать — не все
>> сигналы можно перехватить; возможно, для некоторых сигналов установлены
>> предопределённые действия по выходу из обработчика, которые нельзя
>> отменить (я бы сделал так, ибо нефига).
>> Поставил Intel C++ Compiler.v9.1.039 . В нём спокойно можно делать throw
>> из обработчика сигналов . И нет проблем. Всё с чем я немог смерится в
>> gcc тут отсутствует. И работает вполне предсказуемо.

ME>Это не работает по следующим причинам:

Вожете говорить что угодно но оно работает!

ME>1) Обработчик сигнала вызывется kernel'ом в отдельном стеке, т.е. не в

ME>том стеке, где твои объекты. Чтобы это проверить, выведи адрес переменной на
ME>стеке (не из signal handler), брось исключение из signal handler и выведи адрес
ME>той же переменной из catch().
Я поставил эксперменты которые показали что обработчики сигналов div0 gpf обрабатываются
именно в том стеке где и было исключения. Обработка исключений у icc и у gcc ведётся
по схожей схеме. Только он делает правильный вход в обработчик сигналов в отличие от gcc.
И вообще если почитаете man-ы отдельный стек будет если при установке обработчика это
специально указать дополнительным параметром.

ME>2) Если компилятор Intel не поддерживает асинхронных исключений (что вероятно

ME>для linux версии), раскрутка стэка не будет работать правильно. Но в свете п.1
ME>это уже не имеет никакого значения.
Повторяю схема обработки исключенй уних практически 1 в один.
Re[5]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 21.08.06 12:26
Оценка:
kov_serg wrote:

> ME>kov_serg wrote:

>
>> > А>Уже писали, но повторю:
>> > А>Деление на ноль и GPF это не исключения — их никто не кидал при помощи
>> > throw, поэтому поймать их при помощи catch() невозможно.
>> >
>> > А>Не уверен, что и при помощи сигналов что-то можно сделать — не все
>> > сигналы можно перехватить; возможно, для некоторых сигналов установлены
>> > предопределённые действия по выходу из обработчика, которые нельзя
>> > отменить (я бы сделал так, ибо нефига).
>> > Поставил Intel C++ Compiler.v9.1.039 . В нём спокойно можно делать throw
>> > из обработчика сигналов . И нет проблем. Всё с чем я немог смерится в
>> > gcc тут отсутствует. И работает вполне предсказуемо.
>
> ME>Это не работает по следующим причинам:
> Вожете говорить что угодно но оно работает!



Попробуй следущий тест, если он выведет 0, значит действительно работает:

#include <stdio.h>
#include <signal.h>
#include <stdexcept>

void handler(int) { throw std::runtime_error("handler"); }

int count;

int f(int n)
{
     try
     {
         n = 100 / n;
         ++count;
     }
     catch(std::exception&)
     {
     }
     return n;
}

int main(int ac, char** av)
{
     struct sigaction sa;
     sa.sa_handler = handler;
     sa.sa_flags = 0;
     sigaction(SIGFPE, &sa, 0);

     f(ac - 1);
     printf("%d\n", count);
}


--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[5]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 21.08.06 13:48
Оценка:
kov_serg wrote:

[]

> ME>1) Обработчик сигнала вызывется kernel'ом в *отдельном стеке*, т.е. не в

> ME>том стеке, где твои объекты. Чтобы это проверить, выведи адрес
> переменной на
> ME>стеке (не из signal handler), брось исключение из signal handler и
> выведи адрес
> ME>той же переменной из catch().

> Я поставил эксперменты которые показали что обработчики сигналов div0

> gpf обрабатываются именно в том стеке где и было исключения. Обработка исключений у icc и у
> gcc ведётся по схожей схеме.

Это не зависит от компилятора.

Understanding the Linux kernel, 2nd

10.3.3 Catching the Signal
...
Executing a signal handler is a rather complex task because of the need to
juggle stacks carefully while switching between User Mode and Kernel Mode. We
explain exactly what is entailed here.
Signal handlers are functions defined by User Mode processes and included in the
User Mode code segment. The handle_signal( ) function runs in Kernel Mode while
signal handlers run in User Mode; this means that the current process must first
execute the signal handler in User Mode before being allowed to resume its
"normal" execution. Moreover, when the kernel attempts to resume the normal
execution of the process, the Kernel Mode stack no longer contains the hardware
context of the interrupted program because the Kernel Mode stack is emptied at
every transition from User Mode to Kernel Mode.
An additional complication is that signal handlers may invoke system calls. In
this case, after the service routine executes, control must be returned to the
signal handler instead of to the code of the interrupted program.
The solution adopted in Linux consists of copying the hardware context saved in
the Kernel Mode stack onto the User Mode stack of the current process. The User
Mode stack is also modified in such a way that, when the signal handler
terminates, the sigreturn( ) system call is automatically invoked to copy the
hardware context back on the Kernel Mode stack and restore the original content
of the User Mode stack.
Figure 10-2 illustrates the flow of execution of the functions involved in
catching a signal. A nonblocked signal is sent to a process. When an interrupt
or exception occurs, the process switches into Kernel Mode. Right before
returning to User Mode, the kernel executes the do_signal( ) function, which in
turn handles the signal (by invoking handle_signal( )) and sets up the User Mode
stack (by invoking setup_frame( ) or setup_rt_frame( )). When the process
switches again to User Mode, it starts executing the signal handler because the
handler's starting address was forced into the program counter. When that
function terminates, the return code placed on the User Mode stack by the
setup_frame( ) or setup_rt_frame( ) function is executed. This code invokes the
sigreturn( ) system call, whose service routine copies the hardware context of
the normal program in the Kernel Mode stack and restores the User Mode stack
back to its original state (by invoking restore_sigcontext( )). When the system
call terminates, the normal program can thus resume its execution.
...
10.3.3.1 Setting up the frame
To properly set the User Mode stack of the process, the handle_signal( )
function invokes either setup_frame( ) (for signals that do not require a
siginfo_t table; see Section 10.4 later in this chapter) or setup_rt_frame( )
(for signals that do require a siginfo_t table). To choose among these two
functions, the kernel checks the value of the SA_SIGINFO flag in the sa_flags
field of the sigaction table associated with the signal.
The setup_frame( ) function receives four parameters, which have the following
meanings:
sig — Signal number
ka — Address of the k_sigaction table associated with the signal
oldset — Address of a bit mask array of blocked signals
regs — Address in the Kernel Mode stack area where the User Mode register
contents are saved
The setup_frame( ) function pushes onto the User Mode stack a data structure
called a frame, which contains the information needed to handle the signal and
to ensure the correct return to the sys_sigreturn( ) function. A frame is a
sigframe table that includes the following fields (see Figure 10-3):
pretcode — Return address of the signal handler function; it points to the
retcode field (later in this list) in the same table.
sig — The signal number; this is the parameter required by the signal handler.
sc — Structure of type sigcontext containing the hardware context of the User
Mode process right before switching to Kernel Mode (this information is copied
from the Kernel Mode stack of current). It also contains a bit array that
specifies the blocked regular signals of the process.
fpstate — Structure of type _fpstate that may be used to store the floating
point registers of the User Mode process (see Section 3.3.4).
extramask — Bit array that specifies the blocked real-time signals.
retcode — Eight-byte code issuing a sigreturn( ) system call; this code is
executed when returning from the signal handler.
Figure 10-3. Frame on the User Mode stack

The setup_frame( ) function starts by invoking get_sigframe( ) to compute the
first memory location of the frame. That memory location is usually[4] in the
User Mode stack, so the function returns the value:
[4] Linux allows processes to specify an alternate stack for their signal
handlers by invoking the sigaltstack( ) system call; this feature is also
requested by the X/Open standard. When an alternate stack is present, the
get_sigframe( ) function returns an address inside that stack. We don't discuss
this feature further, since it is conceptually similar to regular signal handling.
(regs->esp — sizeof(struct sigframe)) & 0xfffffff8

Since stacks grow toward lower addresses, the initial address of the frame is
obtained by subtracting its size from the address of the current stack top and
aligning the result to a multiple of 8.

The returned address is then verified by means of the access_ok macro; if it is
valid, the function repeatedly invokes _ _put_user( ) to fill all the fields of
the frame. Once this is done, it modifies the regs area of the Kernel Mode
stack, thus ensuring that control is transferred to the signal handler when
current resumes its execution in User Mode:

regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) ka->sa.sa_handler;

The setup_frame( ) function terminates by resetting the segmentation registers
saved on the Kernel Mode stack to their default value. Now the information
needed by the signal handler is on the top of the User Mode stack.
The setup_rt_frame( ) function is very similar to setup_frame( ), but it puts on
the User Mode stack an extended frame (stored in the rt_sigframe data structure)
that also includes the content of the siginfo_t table associated with the signal.


> Только он делает правильный вход в обработчик сигналов

> в отличие от gcc.

Как отличить правильный вход от неправильного?

> И вообще если почитаете man-ы отдельный стек будет если при установке

> обработчика это специально указать дополнительным параметром.

Читай выше, что просходит со стэком.

--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[6]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 21.08.06 15:11
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>Попробуй следущий тест, если он выведет 0, значит действительно работает:


ME>
ME>#include <stdio.h>
ME>#include <signal.h>
ME>#include <stdexcept>

ME>void handler(int) { throw std::runtime_error("handler"); }

ME>int count;

ME>int f(int n)
ME>{
ME>     try
ME>     {
ME>         n = 100 / n;
ME>         ++count;
ME>     }
ME>     catch(std::exception&)
ME>     {
ME>     }
ME>     return n;
ME>}

ME>int main(int ac, char** av)
ME>{
ME>     struct sigaction sa;
ME>     sa.sa_handler = handler;
ME>     sa.sa_flags = 0;
ME>     sigaction(SIGFPE, &sa, 0);

ME>     f(ac - 1);
ME>     printf("%d\n", count);
ME>}
ME>

Признаю свою ошибку. Дествительно не работает. Но результаты весьма странные:
#include <stdio.h>
#include <signal.h>
#include <stdexcept>

void handler(int) { throw std::runtime_error("handler"); }

int count=0;

int f(int n)
{
     try
     {
         n = 100 / n;
         ++count;
     }
     catch(std::exception&)
     {
     }
     return n;
}

int main(int ac, char** av)
{
     struct sigaction sa;
     sa.sa_handler = handler;
     sa.sa_flags = 0;
     sigaction(SIGFPE, &sa, 0);

     f(ac - 1);
     printf("%d\n", count);
}

/*
./icc -static test2.cpp
./a.out
Segmentation fault
./a.out one
1
*/

Очень насторажил "Segmentation fault"

Дальше больше:
#include <stdio.h>
#include <signal.h>

void stk(char* place) {
  static   int base=0;
  volatile int z=0;
  if (!base) base=(int)&z;
  int sp=(int)&z;
  printf("%s.stack: %d (la=0x%08X)\n",place,sp-base,sp);
}

void test() {
  stk("test()");
  try {
    struct A { ~A() { printf("A.dtor\n"); } } a;
    printf("throwing AV\n");
    *(int*)0=0;
    //volatile int i=0,j=1/i; printf("j=%d\n",j);
    printf("no errors :) strange!\n");
  } catch(int x) {
    printf("catch int=%d\n",x);
  }
}

void sig_handler_install();
void sig_handler(int num) { 
  printf("sig_handler %d\n",num);
  stk("sig_handler()");
  throw 1001; 
}
void sig_handler_install() {
  struct sigaction sa;
  sa.sa_handler = sig_handler;
  sa.sa_flags   = 0;
  printf("install signal handler\n");
  sigaction(SIGFPE,  &sa, 0);
  sigaction(SIGSEGV, &sa, 0);
}

int main(int,char**) {
  stk("main()");
  sig_handler_install();
  printf("-----test1------\n"); test();
  printf("-----test2------\n"); sig_handler_install(); test();
  printf("-----done-------\n");
}

/*

page fault:
./icc -static test.cpp
./a.out
main().stack: 0 (la=0xBFD9EAD4)
install signal handler
-----test1------
test().stack: -60 (la=0xBFD9EA98)
throwing AV
sig_handler 11
sig_handler().stack: -800 (la=0xBFD9E7B4)
A.dtor
catch int=1001
-----test2------
install signal handler
test().stack: -60 (la=0xBFD9EA98)
throwing AV
Segmentation fault

divide by zero:
./icc -static test.cpp
./a.out
main().stack: 0 (la=0xBFCBF244)
install signal handler
-----test1------
test().stack: -68 (la=0xBFCBF200)
throwing AV
sig_handler 8
sig_handler().stack: -808 (la=0xBFCBEF1C)
terminate called after throwing an instance of 'int'
Aborted

*/


замечание: обратим внимание на стек не далеко отлетает ~800 байт (в windows 980)
ни как не в отдельное адресное пространство.

Самое весёлое что для AV первое исключение обработалось как надо.
Кода я это увидел второй вызов test() я удивился. Добавил еще один
sig_handler_install(); Еще раз запустил результат не изменился
"Я ПЛАКАТЬ"
Попробывал деление на 0. Офигел еще раз


Умееш же MaximE испортить настроение!
Теперь придётся второй вариант использовать. С ручным построением точек возврата
И какого это линукс не дружит с исключениями Руки бы поотрывал тому кто придумал в gcc
так исключения обрабатывать
Re[7]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 21.08.06 15:44
Оценка:
kov_serg wrote:

> ME>Попробуй следущий тест, если он выведет 0, значит действительно работает:


> Признаю свою ошибку. Дествительно не работает.


[]

> Дальше больше:


[]

> замечание: обратим внимание на стек не далеко отлетает ~800 байт (в

> windows 980) ни как не в отдельное адресное пространство.

Так и должно быть. Kernel создает stack frame для signal handler на том же самом
стеке, но это другой, новый frame, не frame той ф-ции, которая сделала fault.
См. цитату из умной книжки в другом постинге.

[]

> И какого это линукс не дружит с исключениями Руки бы поотрывал тому кто

> придумал в gcc так исключения обрабатывать.

gcc тут не причем. Это так устроена обработка сигналов в OS.

--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[7]: Обработка исключений под linux
От: mr_jek  
Дата: 21.08.06 16:33
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>И какого это линукс не дружит с исключениями Руки бы поотрывал тому кто придумал в gcc

_>так исключения обрабатывать

А причем здесь gcc, вы же с помощью icc все собрали, или я что-то пропустил?
Re[8]: Обработка исключений под linux
От: MaximE Великобритания  
Дата: 21.08.06 17:02
Оценка:
mr_jek wrote:

> Здравствуйте, kov_serg, Вы писали:

>
> _>И какого это линукс не дружит с исключениями Руки бы поотрывал тому
> кто придумал в gcc
> _>так исключения обрабатывать
>
> А причем здесь gcc, вы же с помощью icc все собрали, или я что-то пропустил?

Не имеет значения каким компилятором собирать, т.к. не в компиляторе дело.

--
Maxim Yegorushkin

No Microsoft product was used in any way to write or send this text.
If you use a Microsoft product to read it, you're doing so at your own risk
Posted via RSDN NNTP Server 2.0
Re[9]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 29.08.06 12:11
Оценка: :)
Здравствуйте, MaximE, Вы писали:

...
ME>Не имеет значения каким компилятором собирать, т.к. не в компиляторе дело.

Вот схема (детали реализации чуть ниже) которая работает так как надо под linux:

#include "exc_emu.h"
struct SimpleObject {
    SimpleObject() { ... }
    virtual ~SimpleObject() { ... }
};
...
void some_func() {
    ...
    TRY {
        ...
        SimpleObject so1; EXC_MARKER;
        SimpleObject so2; EXC_MARKER;
            ... div by zero or a/v ..
        SimpleObject so3; EXC_MARKER;
        ...
        fn( exc_param_marker(SimpleObject()) );
        ...
    }
    CATCH(runtime_error &re) {
        ...
    }
    CATCH(...) {
        ...
    }
    TRY_END
    ...
}
...


Если использовать такой синтаксис то обработка исключений работает великолепно. Но напрягает после каждого объекта ставить EXC_MARKER, что бы при возникновении исключения его обработка шла именно с последнего EXC_MARKER (quick_save). Если бы заставить компилятор после каждого создания статического объекта вызывать EXC_MARKER, проблема как-то решилась. Или если бы схема обработки исключений была иной, например как в vc то всё было бы проще. Так что именно компилятор виновен!!!
Руки бы им поотрывал!!!

Вот детали реализации:
// exc_emu.h
#pragma once
#include "Threads.h"
#include <map>
using namespace std;

struct ef_t { // exception frame
    ef_t *prev; // prev frame
    void *lra;  // last saved safe return address
    int   ebp;  // old ebp value
    ef_t()  { prev=NULL; lra=NULL; ebp=0; } 
};

struct tib_t { // thread information block
    ef_t      *efp; // exception frame pointer
    ef_t       head;// dummy frame for first exception
    threadid_t id;  // thread id
    static tib_t* getTIB(); // return thread information block

    tib_t() {}
    tib_t(threadid_t id) : id(id) {}
    tib_t(const tib_t& src){ efp=src.efp; head=src.head; id=src.id; }

    typedef map<threadid_t,tib_t> tibs_t;
    static tibs_t tibs;       // Thread Information Blocks
    static Mutex  tibs_mutex; // mutex for tibs
};

enum ExcCodes { exc_av=1, exc_div0=2,exc_unk=-1 };
void exc_marker(int exc=0);

struct ExcScope {
    ExcScope()  { exc_enter(&frame); }
    ~ExcScope() { exc_leave();       }
private:
    ef_t frame;
    static void exc_enter(ef_t *fp);
    static void exc_leave();
};

#define TRY        try { ExcScope __try_scope; exc_marker();
#define CATCH(x)   exc_marker(); } catch(x) { exc_marker();
#define EXC_MARKER { exc_marker(0); }
#define TRY_END    }

template<class T> T& exc_param_marker(T& p) { EXC_MARKER; return p; }
struct ExcObject { ExcObject() { EXC_MARKER; } };


// exc_emu.cpp
#include "exc_emu.h"
#include <signal.h>
#include <stdexcept>
using namespace std;

tib_t::tibs_t tib_t::tibs;
Mutex  tib_t::tibs_mutex;

tib_t* tib_t::getTIB() { // return tib for current thread
    Lock lock(&tib_t::tibs_mutex);
    threadid_t id=Thread::getCurrentThreadID();
    tibs_t::iterator i=tibs.find(id);
    tib_t *tib;
    if (i!=tibs.end()) {
        tib=&(i->second);
    } else {
        tib=&(tibs[id]=tib_t(id));
        tib->efp=&(tib->head);
    }
    return tib;
}
void ExcScope::exc_enter(ef_t *fp) {
    tib_t* tib=tib_t::getTIB();
    fp->prev=tib->efp;
    tib->efp=fp;
    EXC_MARKER;
}
void ExcScope::exc_leave() {
    tib_t* tib=tib_t::getTIB();
    if (!tib->efp->prev) throw runtime_error("no previous exception frame");
    tib->efp=tib->efp->prev;
    EXC_MARKER;
}
void exc_marker(int p) {
    int ra=(&p)[-1]; // ra  - return address
    int bp=(&p)[-2]; // ebp - prev stack frame register
    if (p) {
        if (p==exc_av)   throw runtime_error("access violation");
        if (p==exc_div0) throw runtime_error("div by zero");
        throw runtime_error("unknown error");
    }
    tib_t* tib=tib_t::getTIB();
    tib->efp->lra=(void*)ra;
    tib->efp->ebp=bp;
}
void emu_push(sigcontext *regs,int val) {
    regs->esp-=sizeof(int);
    *(int*)regs->esp=val;
}
void emu_call(sigcontext *regs,int subr,int ret_addr) {
    emu_push(regs,ret_addr);
    regs->eip=subr;
}
void panic() { /*exit(1);*/ throw 0xDEAD; }
void sig_action(int signum, siginfo_t *si, void* unk) {
    sigcontext *regs=(sigcontext*)((int*)unk+5); // !!! подобрано экспериментально Linux<SuSe10,x86>::gcc
    tib_t *tib=tib_t::getTIB();
    void *ra=tib->efp->lra;
    if (ra==NULL) { panic(); }
    int param=exc_unk;
    switch(signum) {
    case SIGSEGV: param=exc_av;   break;
    case SIGFPE:  param=exc_div0; break;
    };
    regs->ebp=tib->efp->ebp;
    emu_push(regs,param);
    emu_call(regs,(int)(void*)exc_marker,(int)ra);
}
int sig_install() {
    struct sigaction sa,old_sa;
    sa.sa_flags=SA_SIGINFO;
    sa.sa_sigaction=&sig_action;
    if (sigaction(SIGSEGV,&sa,&old_sa)==-1)
        throw runtime_error("can't set SEGV signal handler");
    if (sigaction(SIGFPE ,&sa,&old_sa)==-1)
        throw runtime_error("can't set FPE signal handler");
    return 1;
}
int sig_installed=sig_install();


// Threads.h
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>

#define threadid_t long

struct Thread {
    static threadid_t getCurrentThreadID() { return (threadid_t)pthread_self(); }
    //....
};
struct Mutex {
    Mutex()      { pthread_mutex_init(&mutex,NULL); }
    void Enter() { pthread_mutex_lock(&mutex);      }
    void Leave() { pthread_mutex_unlock(&mutex);    }
    ~Mutex()     { pthread_mutex_destroy(&mutex);   }
private:
    pthread_mutex_t mutex;
};
struct Lock {
    Lock(Mutex *mutex) : mutex(mutex) { if (mutex) mutex->Enter(); }
    ~Lock() { if (mutex) { mutex->Leave(); mutex=NULL; } }
private:
    Mutex *mutex;
};
//....


Впринципе gcc поддерживает автоматическое профилирования и сам вставляет функции
extern "C" {
void __cyg_profile_func_enter(void *this_fn, void *call_site) __attribute__((__no_instrument_function__));
void __cyg_profile_func_exit (void *this_fn, void *call_site) __attribute__((__no_instrument_function__));
};

и можно поставить EXC_MARKER скажем в функцию __cyg_profile_func_exit но это приводит к тому что map<> уже нельзя использовать т.к. возникнет рекурсия и всем функциям придётся ставить __attribute__((__no_instrument_function__)).
И в общем случае это всё равно не выход. Т.к. вызываться EXC_MARKER в таком варианте будет чересчур часто.

Короче проблемма с обработкой исключений в linux gcc остаётся
Что делать Я в панике
Re[10]: Обработка исключений под linux
От: Elifant  
Дата: 30.08.06 02:33
Оценка:
Здравствуйте, kov_serg, Вы писали:

_>Вот схема (детали реализации чуть ниже) которая работает так как надо под linux:


_>
_>...
_>


_>Если использовать такой синтаксис то обработка исключений работает великолепно. Но напрягает после каждого объекта ставить EXC_MARKER, что бы при возникновении исключения его обработка шла именно с последнего EXC_MARKER (quick_save). Если бы заставить компилятор после каждого создания статического объекта вызывать EXC_MARKER, проблема как-то решилась. Или если бы схема обработки исключений была иной, например как в vc то всё было бы проще. Так что именно компилятор виновен!!!

_>Руки бы им поотрывал!!!

А в сторону ключей '-fasynchronous-unwind-tables', '-fnon-call-exceptions' копал? Не помогает?
Re[11]: Обработка исключений под linux
От: kov_serg Россия  
Дата: 30.08.06 08:04
Оценка:
Здравствуйте, Elifant, Вы писали:

...
E>А в сторону ключей '-fasynchronous-unwind-tables', '-fnon-call-exceptions' копал? Не помогает?

Пробывал не помагает
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.