Возвращение к жизни после нарушения сегментации
Можно ли восстановить нормальный поток выполнения программы 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, С# и т.д.
Примечание. Пожалуйста, не говорите мне, что в С++ есть механизм обработки исключений, потому что я знаю это, не скажите мне, что я должен проверять каждый указатель перед назначением и т.д.
Чего я действительно хочу достичь - это вернуться к нормальному потоку выполнения, как в приведенном выше примере. Я знаю, что некоторые действия могут быть предприняты с использованием сигналов POSIX. Как это должно выглядеть? Другие идеи?
Ответы
Ответ 1
#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 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 - они не являются безопасными для сигнала) и, возможно, перезапустите программу. В этих случаях вам намного лучше писать процесс супервайзера, который контролирует выход дочернего процесса, регистрирует его и запускает новый дочерний процесс.
Ответ 2
Вы можете уловить ошибки сегментации с помощью обработчика сигналов и принять решение продолжить выполнение программы (при ваших собственных рисках).
Название сигнала 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
"Все допустимо, но не все выгодны" - как правило, segfault - это игра по уважительной причине... Лучшая идея, чем собирать, где это было, - сохранить ваши данные (база данных или наименее файловая система) и позволить ему подобрать, где это было остановлено. Это значительно повысит надежность данных.
Ответ 4
См. комментарий R. для ответа MacMade.
Развернувшись на том, что он сказал (после обработки SIGSEV, или, в этом случае, SIGFPE, CPU + OS может вернуть вас в insending 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 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;
}
Ответ 5
Нет, в любом логическом смысле невозможно восстановить нормальное выполнение после сбоя сегментации. Ваша программа просто попыталась разыменовать нулевой указатель. Как вы будете вести себя нормально, если что-то, чего ожидает ваша программа, нет? Это ошибка программирования, единственная безопасная вещь - выйти.
Рассмотрим некоторые из возможных причин ошибки сегментации:
- вы забыли присвоить законное значение указателю
- указатель был перезаписан, возможно, потому, что вы получаете доступ к памяти кучи, которую вы освободили.
- ошибка испортила кучу
- ошибка повредила стек
- злоумышленник пытается использовать функцию переполнения буфера
- 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!
}
Ответ 6
Вызовите это, и когда произойдет segfault, ваш код выполнит segv_handler, а затем вернется туда, где он был.
void segv_handler(int)
{
// Do what you want here
}
signal(SIGSEGV, segv_handler);
Ответ 7
Не существует значимого способа восстановления из SIGSEGV, если вы не знаете ТОГО, что его вызвало, и нет никакого способа сделать это в стандартном C. Возможно, возможно (возможно) в инструментальной среде, например C-VM (?). То же самое верно для всех сигналов программных ошибок; если вы попытаетесь заблокировать/проигнорировать их или установить обработчики, которые возвращаются нормально, ваша программа, вероятно, сломается ужасно, когда они произойдут, если только они не сгенерированы с помощью raise
или kill
.
Просто сделайте себе одолжение и принимайте во внимание ошибки.
Ответ 8
В POSIX ваш процесс будет отправлен SIGSEGV, когда вы это сделаете. Обработчик по умолчанию просто сбрасывает вашу программу. Вы можете добавить свой собственный обработчик, используя вызов signal(). Вы можете реализовать любое поведение, которое вам нравится, самостоятельно обрабатывая сигнал.
Ответ 9
Вы можете использовать функцию SetUnhandledExceptionFilter() (в окнах), но даже для того, чтобы пропустить "незаконную" команду, вам нужно будет расшифровать некоторые коды операций ассемблера. И, как сказал glowcoder, даже если бы он "закомментировал" во время выполнения инструкции, которые генерируют segfaults, что останется от исходной логики программы (если это можно так назвать)?
Все возможно, но это не значит, что это нужно сделать.
Ответ 10
К сожалению, вы не можете в этом случае. Функция buggy имеет поведение undefined и может привести к повреждению вашего состояния программы.
Что вы можете сделать, это запустить функции в новом процессе. Если этот процесс умирает с кодом возврата, который указывает SIGSEGV, вы знаете, что он потерпел неудачу.
Вы также можете переписать функции самостоятельно.
Ответ 11
Я могу видеть в случае восстановления после нарушения сегментации, если ваши события обработки в цикле и одно из этих событий вызывает нарушение сегментации, вы просто хотите пропустить это событие, продолжить обработку оставшихся событий. В моих глазах Segmentation Violation - это то же самое, что и NullPointerExceptions в Java. Да, состояние будет непоследовательным и неизвестным после любого из них, однако в некоторых случаях вы хотели бы справиться с ситуацией и продолжить. Например, в торговле Algo вы приостановите выполнение заказа и разрешите трейдеру вручную взять на себя управление, без сбоев всей системы и разрушения всех других заказов.
Ответ 12
лучшим решением является доступ в каждый почтовый ящик к каждому небезопасному доступу следующим образом:
#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 memory
}
std::cout<<"i am alive !!"<<std::endl; // we will get into here in any case
system("pause");
return 0;
}
программа никогда не будет разбиваться почти на всех os
Ответ 13
В этом руководстве 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 не укажет на ошибку сегментации. Список других сигналов можно найти здесь.
Обработчики сигналов широко классифицируются на категории буксировки.
- Вы можете иметь функцию обработчика отметить, что сигнал получен путем настройки некоторых
глобальные структуры данных, а затем возвращаются в обычном режиме.
- Вы можете заставить функцию обработчика завершить работу программы или передать ее
управление до точки, где он может восстановиться после ситуации, вызвавшей сигнал.
SIGSEGV
попадает под сигналы программной ошибки