Обработка исключений под 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++' — Павел Кузнецов
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.