Возвращение к жизни после нарушения сегментации - PullRequest
14 голосов
/ 20 июля 2010

Можно ли восстановить нормальный поток выполнения программы на C после ошибки Segmentation Fault?

struct A {
    int x;
};
A* a = 0;

a->x = 123; // this is where segmentation violation occurs

// after handling the error I want to get back here:
printf("normal execution");
// the rest of my source code....

Мне нужен механизм, похожий на NullPointerException, который присутствует в Java, C # и т. Д.

Примечание : Пожалуйста, не говорите мне, что в C ++ есть механизм обработки исключений, потому что я знаю это, не говорите мне, что я должен проверять каждый указатель перед присваиванием и т.д.

Чего я действительно хочу добиться, так это вернуться к нормальному потоку выполнения, как в примере выше. Я знаю, что некоторые действия могут быть предприняты с использованием сигналов POSIX. Как это должно выглядеть? Другие идеи?

Ответы [ 13 ]

23 голосов
/ 20 июля 2010
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <signal.h>
#include <stdlib.h>
#include <ucontext.h>

void safe_func(void)
{
    puts("Safe now ?");
    exit(0); //can't return to main, it's where the segfault occured.
}

void
handler (int cause, siginfo_t * info, void *uap)
{
  //For test. Never ever call stdio functions in a signal handler otherwise*/
  printf ("SIGSEGV raised at address %p\n", info->si_addr);
  ucontext_t *context = uap;
  /*On my particular system, compiled with gcc -O2, the offending instruction
  generated for "*f = 16;" is 6 bytes. Lets try to set the instruction
  pointer to the next instruction (general register 14 is EIP, on linux x86) */
  context->uc_mcontext.gregs[14] += 6; 
  //alternativly, try to jump to a "safe place"
  //context->uc_mcontext.gregs[14] = (unsigned int)safe_func;
}

int
main (int argc, char *argv[])
{
  struct sigaction sa;
  sa.sa_sigaction = handler;
  int *f = NULL;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = SA_SIGINFO;
  if (sigaction (SIGSEGV, &sa, 0)) {
      perror ("sigaction");
      exit(1);
  }
  //cause a segfault
  *f = 16; 
  puts("Still Alive");
  return 0;
}

$ ./a.out
SIGSEGV raised at address (nil)
Still Alive

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

7 голосов
/ 20 июля 2010

Вы можете отследить ошибки сегментации, используя обработчик сигнала, и принять решение продолжить выполнение программы (на свой страх и риск).

Имя сигнала SIGSEGV.

Вам придется использовать функцию sigaction() из заголовка signal.h.

В основном это работает следующим образом:

struct sigaction sa1;
struct sigaction sa2;

sa1.sa_handler = your_handler_func;
sa1.sa_flags   = 0;
sigemptyset( &sa1.sa_mask );

sigaction( SIGSEGV, &sa1, &sa2 );

Вот прототип функции-обработчика:

void your_handler_func( int id );

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

3 голосов
/ 20 июля 2010

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

Рассмотрим некоторые возможные причины ошибки сегментации:

  • вы забыли присвоить правильное значение указателю
  • указатель был перезаписан, возможно, из-за того, что вы обращаетесь к освобожденной памяти кучи
  • ошибка повредила кучу
  • ошибка повредила стек
  • злонамеренная третья сторона пытается использовать уязвимость переполнения буфера
  • malloc возвратил ноль, потому что у вас недостаточно памяти

Только в первом случае можно ожидать каких-либо разумных ожиданий, которые вы могли бы продолжить

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

Редактировать: вот пример, показывающий, почему вы определенно не хотите выполнять следующую инструкцию после разыменования нулевого указателя:

void foobarMyProcess(struct SomeStruct* structPtr)
{
    char* aBuffer = structPtr->aBigBufferWithLotsOfSpace; // if structPtr is NULL, will SIGSEGV
    //
    // if you SIGSEGV and come back to here, at this point aBuffer contains whatever garbage was in memory at the point
    // where the stack frame was created
    //
    strcpy(aBuffer, "Some longish string");  // You've just written the string to some random location in your address space
                                             // good luck with that!

}
3 голосов
/ 20 июля 2010

См. Комментарий Р. к ответу MacMade.

Расширение того, что он сказал (после обработки SIGSEV, или, в этом случае, SIGFPE, CPU + OS может вернуть вас в оскорбительный insn)Вот тест для деления на ноль:

#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>

static jmp_buf  context;

static void sig_handler(int signo)
{
    /* XXX: don't do this, not reentrant */
    printf("Got SIGFPE\n");

    /* avoid infinite loop */
    longjmp(context, 1);
}

int main()
{
    int a;
    struct sigaction sa;

    memset(&sa, 0, sizeof(struct sigaction));
    sa.sa_handler = sig_handler;
    sa.sa_flags = SA_RESTART;
    sigaction(SIGFPE, &sa, NULL);

    if (setjmp(context)) {
            /* If this one was on setjmp's block,
             * it would need to be volatile, to
             * make sure the compiler reloads it.
             */
            sigset_t ss;

            /* Make sure to unblock SIGFPE, according to POSIX it
             * gets blocked when calling its signal handler.
             * sigsetjmp()/siglongjmp would make this unnecessary.
             */
            sigemptyset(&ss);
            sigaddset(&ss, SIGFPE);
            sigprocmask(SIG_UNBLOCK, &ss, NULL);

            goto skip;
    }

    a = 10 / 0;
skip:
    printf("Exiting\n");

    return 0;
}
3 голосов
/ 20 июля 2010

«Все вещи допустимы, но не все полезны» - как правило, segfault - игра закончена по уважительной причине ... Лучшая идея, чем найти то, где она была бы, должна была сохранить ваши данные (база данных или по крайней мере, файловой системы) и включите его в том месте, где он остановился. Это значительно повысит надежность данных.

1 голос
/ 20 июля 2010

Нет значимого способа восстановления с SIGSEGV, если вы не знаете ТОЧНО, что его вызвало, и нет способа сделать это в стандартном C. Это может быть (возможно) в инструментальной среде, такой как C-VM (?).То же самое верно для всех сигналов ошибки программы;если вы попытаетесь заблокировать / игнорировать их или установить обработчики, которые возвращают нормально, ваша программа, вероятно, будет ужасно сломана, если они произойдут, если, возможно, они не генерируются raise или kill.

Просто сделайтеодобрять и принимать во внимание случаи ошибок.

1 голос
/ 20 июля 2010

Назовите это, и когда произойдет segfault, ваш код выполнит segv_handler и затем вернется к тому, где он был.

void segv_handler(int)
{
  // Do what you want here
}

signal(SIGSEGV, segv_handler);
0 голосов
/ 09 апреля 2016

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

#include <iostream>
#include <signal.h>
#include <setjmp.h>
static jmp_buf buf;
int counter = 0;
void signal_handler(int)
{
     longjmp(buf,0);
}
int main()
{
    signal(SIGSEGV,signal_handler);
    setjmp(buf);
    if(counter++ == 0){ // if we did'nt try before
    *(int*)(0x1215) = 10;  // access an other process's memory
    }
    std::cout<<"i am alive !!"<<std::endl; // we will get into here in any case
    system("pause");
 return 0;   
}

ваша программа никогда не завершится сбоем почти во всех ОС

0 голосов
/ 21 июля 2014

Я вижу в случае восстановления после нарушения сегментации, если ваша обработка событий в цикле и одно из этих событий приводит к нарушению сегментации, вам нужно только пропустить это событие и продолжить обработку оставшихся событий.На мой взгляд, нарушение сегментации во многом совпадает с исключениями NullPointerException в Java.Да, состояние будет непоследовательным и неизвестным после любого из них, однако в некоторых случаях вы хотели бы справиться с ситуацией и продолжить.Например, в торговле Algo вы бы приостановили выполнение ордера и позволили бы трейдеру взять на себя управление вручную, не нарушая всю систему и не разрушая все остальные ордера.

0 голосов
/ 16 июля 2013

К сожалению, вы не можете в этом случае. Функция глючит с неопределенным поведением и может повредить состояние вашей программы.

Что вы МОЖЕТЕ сделать, так это запустить функции в новом процессе. Если этот процесс завершается с кодом возврата, который указывает SIGSEGV, вы знаете, что он потерпел неудачу.

Вы также можете переписать функции самостоятельно.

...