Возвращение к жизни после Нарушения Сегментации

Действительно ли возможно восстановить нормальный поток выполнения программы C после ошибки Отказа Сегментации?

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. Как это должно быть похожим? Другие идеи?

14
задан Joe 20 July 2010 в 15:54
поделиться

10 ответов

#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 - они небезопасны для сигнала) и, возможно, перезапустите программу. В таких случаях гораздо лучше написать супервизор, который отслеживает выход дочернего процесса, регистрирует его и запускает новый дочерний процесс.

23
ответ дан 1 December 2019 в 06:59
поделиться

Это руководство по glib дает вам четкое представление о том, как писать обработчики сигналов.

A signal handler is just a function that you compile together with the rest
of the program. Instead of directly invoking the function, you use signal 
or sigaction to tell the operating system to call it when a signal arrives.
This is known as establishing the handler.

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

Обработчики сигналов в целом делятся на категории полотенец

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

SIGSEGV относится к сигналам ошибок программы

-1
ответ дан 1 December 2019 в 06:59
поделиться

Вы можете использовать функцию SetUnhandledExceptionFilter () (в Windows), но даже для того, чтобы пропустить "незаконный" Инструкция вам понадобится для декодирования некоторых кодов операций ассемблера. И, как сказал световой кодер, даже если он «закомментирует» во время выполнения инструкции, которые генерируют ошибки сегментации, что останется от исходной логики программы (если ее можно так назвать)? Все возможно, но это не значит, что это нужно делать.

0
ответ дан 1 December 2019 в 06:59
поделиться

В POSIX ваш процесс получит SIGSEGV, когда вы это сделаете. Обработчик по умолчанию просто приводит к сбою вашей программы. Вы можете добавить свой собственный обработчик с помощью вызова signal (). Вы можете реализовать любое поведение, которое вам нравится, обработав сигнал самостоятельно.

0
ответ дан 1 December 2019 в 06:59
поделиться

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

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

signal(SIGSEGV, segv_handler);
1
ответ дан 1 December 2019 в 06:59
поделиться

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

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

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

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

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

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

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
ответ дан 1 December 2019 в 06:59
поделиться

См. комментарий Р. к ответу 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
ответ дан 1 December 2019 в 06:59
поделиться

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

3
ответ дан 1 December 2019 в 06:59
поделиться

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

Имя сигнала - 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 );

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

7
ответ дан 1 December 2019 в 06:59
поделиться

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

Просто сделайте себе одолжение и учитывайте случаи ошибок.

1
ответ дан 1 December 2019 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: