Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Программирование под Unix/Linux > signal & setjmp / longjmp


Автор: null56 19.3.2008, 19:32
Проблема возникла не у меня,  у моего коллеги... и конечно я посоветовал ему лучше отладить свой код... но он ни в какую...
Задача состоит в том, чтобы отловить segmentation fault и не вылетать из программы, а просто проигнорировать этот момент и продолжить со следующей итерации...

Вчера познакомился с макросами setjmp и longjmp, но их работа по видимому осталась для меня загадкой... 

Приведу пример кода, над которым я ставлю эксперименты

Цитата

#include <stdio.h> 
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
//-
void jumper();
void eventHandler(int);
//-
jmp_buf env;
//-
int main()
{
    
    signal(SIGSEGV, eventHandler);    
    while (true)
    {
  jumper();
  sleep(5);
    }
    return 0;    
}

void jumper()
{
    char *pTest = NULL;
    if (setjmp(env) == 0)
    {
  // SEGMENTATION FAULT
  *pTest = 123;
    }
    else
    {
  // IGNORE FAULT
  pTest = NULL;
    }
}

void eventHandler(int sig)
{
// ЭТО ТАК... ТЕСТ
    //signal(sig, eventHandler);

// ВОСТАНОВЛЕНИЕ КОНТЕКСТА СОБСТВЕННО
    longjmp(env, 1);
}



Первая итерация отрабатывает, как надо... Но на второй, судя по дебагеру теряется адрес обработчика сигнала и программа вылетает на этапе генерации прерывания, а именно на строке *pTest = 123;

Объясните пожалуйста, где моя ошибка... Судя по всему я просто не правильно понимаю работу этих макросов...

Заранее благодарен всем откликнувшимся за помощь...

Автор: creatorcode 19.3.2008, 19:48
Код

char *pTest = NULL;
*pTest = 123;

pTest - нулевой указатель, поэтому и вылетает с ошибкой. Вы должны сначала выделить память под указатель и только потом использовать его. Например так:
Код

pTest=(char*)malloc(size);

где size - размер выделяемой памяти. В вашем случае по всей видимости size=1. Хотя тогда непонятно с какой вообще целью используется указатель.

Автор: MAKCim 19.3.2008, 19:52
не вижу ошибки
у меня все сводится к бесконечному циклу, т. е как и должно быть

Добавлено через 44 секунды
creatorcode, 
вы не в теме, извините

Автор: creatorcode 19.3.2008, 20:05
Прошу прощения, не сразу въехал в вопрос. Попробую оправдаться smile 
Код

#include <stdio.h> 
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
//-
void jumper();
void eventHandler(int);
//-
jmp_buf env;
//-
int main()
{
    
    signal(SIGSEGV, eventHandler);    
    while (true)
    {
  jumper();
  sleep(5);
    }
    return 0;    
}

void jumper()
{
    char *pTest = NULL;
    if (setjmp(env) == 0)
    {
  // SEGMENTATION FAULT
  *pTest = 123;
    }
    else
    {
  // IGNORE FAULT
  pTest = NULL;
    }
longjmp(env, 1);//!!!!!!!!!!!!!!!!!!!!!
}

void eventHandler(int sig)
{
// ЭТО ТАК... ТЕСТ
    //signal(sig, eventHandler);

// ВОСТАНОВЛЕНИЕ КОНТЕКСТА СОБСТВЕННО
    longjmp(env, 1);
}


К тому же не вижу смысла использовать signal. Можно вполне обойтись и без него:
Код

#include <stdio.h> 
#include <setjmp.h>
void jumper();
jmp_buf env;
int main()
{
    while (true)
    {
     jumper();
    sleep(5);
    }
    return 0;    
}

void jumper()
{
    char *pTest = NULL;
    if (setjmp(env) == 0)
    {
     // SEGMENTATION FAULT
     *pTest = 123;
    }
    else
    {
    // IGNORE FAULT
     pTest = NULL;
    }
    longjmp(env, 1);//!!!!!!!!!!!!!!!!!!!!!
}

Автор: null56 19.3.2008, 20:17
MAKCim, 

Вы попробовали запускать этот код?
Потому что у меня там ожидание стоит между циклами 5 секунд, а ошибка вылетает на ВТОРОЙ (!!!) итерации цикла?
Приведу пример код с отладочной печатью...

Код

#include <stdio.h> 
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
//-
void jumper();
void eventHandler(int);
//-
jmp_buf env;
//-
int main()
{
    signal(SIGSEGV, eventHandler);    
    while (true)
    {
        printf("Before\n");
        jumper();
        sleep(5);
        printf("After\n");
    }
    return 0;    
}

void jumper(/*jmp_buf &env*/)
{
    char *pTest = NULL;
    if (setjmp(env) == 0)
    {
        printf("Bad\n");
        *pTest = 123;
    }
    else
    {
        printf("Good\n");
        pTest = NULL;
    }
}

void eventHandler(int sig)
{
    printf("Event\n");
    longjmp(env, 1);
}



Результатом работы будет:
Цитата

Before
Bad
Event
Good
After
Before
Bad

Автор: null56 19.3.2008, 22:50
Аналогичная программа, использующая обычный метод не вылетает...

Код

#include <stdio.h> 
#include <setjmp.h>
#include <signal.h>
#include <unistd.h>
//-
void jumper();
void userHandler();
//-
jmp_buf env;
//-
int main()
{
    while (true)
    {
        printf("Before\n");
        jumper();
        sleep(5);
        printf("After\n");
    }
    return 0;    
}

void jumper()
{
    char *pTest = NULL;
    if (setjmp(env) == 0)
    {
        printf("Bad\n");
        userHandler();
    }
    else
    {
        printf("Good\n");
    }
}

void userHandler()
{
    printf("User\n");
    longjmp(env, 1);
}


В чем хитрость... в какой области памяти создается функция, которая назначается методом signal?

Автор: MAKCim 19.3.2008, 23:13
null56, 
вообщем, все понятно
сначала решение
чтобы все заработало, нужно использовать sigsetjmp() и siglongjmp()
теперь почему не работало
следите за логикой
1. при формировании ядром сигнального фрейма, в стек записывается адрес возврата
т. е когда выполняется код обработчика SIGSEGV на вершине стека лежит адрес возврата
при вызове signal() мы не можем его явно указать, поэтому код libc, которому соответствует функция signal()
устанавливает его за нас
по-умолчанию адрес возврата, указываемый в libc, соответствует внутренней функции libc, которая осуществляет восстановление контекста процесса исходя из данных, которые ядро сохранило для нас в сигнальном фрейме
2. на первой итерации SIGSEGV успешно перехватывается и управление переходит на наш обработчик
но вместо того, чтобы перейти по адресу, находящемуся на вершине стека, идет вызов longjmp(), который полностью восстанавливает контекст процесса
важно отметить, что ядро перед вызовом обработчика сигнала блокирует этот сигнал
поэтому после longjmp() мы оказываемся тут
Код

else
    {
        printf("Good\n");
        pTest = NULL;
    }

и при этом SIGSEGV заблокирован
3. генерация сигнала для процесса в результате #PF осуществляется в функции ядра force_sig_info()
вот ее код
Код

int
 force_sig_info(int sig, struct siginfo *info, struct task_struct *t)
 {
         unsigned long int flags;
         int ret, blocked, ignored;
         struct k_sigaction *action;
 
         spin_lock_irqsave(&t->sighand->siglock, flags);
         action = &t->sighand->action[sig-1];
         ignored = action->sa.sa_handler == SIG_IGN;
         blocked = sigismember(&t->blocked, sig);
         if (blocked || ignored) {
                 action->sa.sa_handler = SIG_DFL;
                 if (blocked) {
                         sigdelset(&t->blocked, sig);
                         recalc_sigpending_tsk(t);
                 }
         }
         ret = specific_send_sig_info(sig, info, t);
         spin_unlock_irqrestore(&t->sighand->siglock, flags);
 
         return ret;
 }

ключевой момент зключается в том, что в ней идет проверка, не заблокирован ли генерируемый сигнал
и если заблокирован (а в нашем случае он заблокирован, как я показал выше), то он разблокируется с восстановлением дефолтного состояния обработчика сигнала, которым является SIG_DFL
для SIGSEGV дефолный обработчик приводит к завершению процесса
поэтому на второй итерации идет завершение процесса, а не передача управления на наш обработчик
sigsetjmp() сохраняет маску заблокированных к моменту вызова сигналов
(на первой итерации SIGSEGV еще не заблокирован)
siglongjmp() восстанавливает маску заблокированных сигналов
а значит после siglongjmp() SIGSEGV не будет заблокирован и в функции force_sig_info() не произойдет сброс нашего обработчика

Автор: null56 20.3.2008, 01:08
Огромное спасибо за помощь...
Я понял свою ошибку, она в большинстве своем все - таки была от незнания...

Действительно, создавая непосредсвенный переход на свою точку в стеке, я теряю обратный ход событий, который подразумевал в libc вызов своих процедур, которые отвечали за разблокировку сигнала.

Еще раз спасибо всем откликнувшимся...

ЗЫ: 
MAKCim, я вопрос не по теме отправил вам в личку

Автор: xvr 20.3.2008, 13:06
Цитата(null56 @ 20.3.2008,  01:08)
Огромное спасибо за помощь...
Я понял свою ошибку, она в большинстве своем все - таки была от незнания...

Действительно, создавая непосредсвенный переход на свою точку в стеке, я теряю обратный ход событий, который подразумевал в libc вызов своих процедур, которые отвечали за разблокировку сигнала.

Еще раз спасибо всем откликнувшимся...

ЗЫ: 
MAKCim, я вопрос не по теме отправил вам в личку

Я бы еще добавил по поводу signal:

Цитата

Upon  arrival of a signal with number signum the following happens.  ... Finally, if the  handler  is  set to a function sighandler then first either the handler is reset to SIG_DFL or an implementation-dependent blocking of the signal is performed and next sighandler is called with argument signum.
...
The original Unix signal() would reset the handler to SIG_DFL, and System  V  (and the Linux kernel and libc4,5) does the same.  On the other hand, BSD does not reset the handler, but blocks new instances of  this signal from occurring during a call of the handler.  The glibc2 library follows the BSD behaviour.


Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)