Как стать автором
Обновить

Обработка Segmentation Fault в C++

Время на прочтение3 мин
Количество просмотров69K

Вводная


C++ является «небезопасным» («unmanaged») языком, поэтому программы могут «вылетать» — аварийно завершать работу без сохранения данных пользователя, сообщения об ошибке и т.п. — стоит только, например, залезть в не инициализированную память. Например:
void fall()
{
  char * s = "short_text";
  sprintf(s,"This is very long text");
}

или
void fall()
{
  int * pointer = NULL;
  *pointer = 13;
}


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

Общего решения задача не имеет, так как C++ не имеет собственной модели обработки исключений, связанных с работой с памятью. Тем не менее, мы рассмотрим два способа, использующих особенности операционной системы, вызвавшей исключение.



Способ 1: SEH


Если Вы используете OS Windows в качестве целевой ОС и Visual C++ в качестве компилятора, то Вы можете использовать Structured Exception Handling — расширение языка С++ от Microsoft, позволяющее отлавливать любые исключения, происходящие в программе.

Общий синтаксис обработки исключений выглядит следующим образом:

__try
{
  segfault1();
}
__except( condition1 )
{
  // обработка исключения, если condition1 == EXCEPTION_EXECUTE_HANDLER.
  // в condition1 может (должен) быть вызов метода, проверяющего 
  //    тип исключения, и возвращающего EXCEPTION_EXECUTE_HANDLER 
  //    если тип исключения соответствует тому, что мы хотим обработать
}
__except( condition2 )
{
  // еще один обработчик
}
__finally
{
  // то, что выполнится если ни один из обработчиков не почешется
}


Вот «работающий пример» — «скопируй и вставь в Visual Studio»
#include <stdio.h>
#include <windows.h>
#include <excpt.h>

int memento() // обработка Segfault
{
	MessageBoxA(NULL,"Memento Mori","Exception catched!",NULL);
	return 0;
}

void fall() // генерация segfault
{
	  int* p = 0x00000000;   
	  *p = 13;
}

int main(int argc, char *argv[])
{
	__try
	{
		fall();
	}
	__except (EXCEPTION_EXECUTE_HANDLER)
	{
		memento();
	}
}


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

Данная методика, при всей ее привлекательности, имеет ряд минусов:

  • Один компилятор. Одна ОС. Не «чистый С++». Если Вы хотите работать без средств MS — Вы не сможете использовать эту методику
  • Один поток — одна таблица. Если Вы напишете конструкцию из __try… __except, внутри __try запустите другой поток и, не выходя из __try второй поток вызовет segfault, то… ничего не произойдет, программа упадет «как обычно». Потому, что на каждый поток нужно писать отдельный обработчик SEH.


Минусов оказалось настолько много, что приходится искать второе решение.

Способ 2: POSIX — сигналы



Способ рассчитан на то, что в момент падения программа получает POSIX-сообщение SIGSEGV. Это безусловно так во всех UNIX-системах, но это фактически так (хотя никто не гарантировал, windows — не posix-совместима) и в windows тоже.

Методика простая — мы должны написать обработчик сообщения SIGSEGV, в котором программа совершит «прощальные действия» и, наконец, упадет:
void posix_death_signal(int signum)
{
	memento(); // прощальные действия
        signal(signum, SIG_DFL); // перепосылка сигнала
	exit(3); //выход из программы. Если не сделать этого, то обработчик будет вызываться бесконечно.
}

после чего мы должны зарегистрировать этот обработчик:
signal(SIGSEGV, posix_death_signal);


Вот готовый пример:
#include <stdio.h>
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>
#include <signal.h>


int memento()
{
	int a=0;
	MessageBoxA(NULL,"Memento mori","POSIX Signal",NULL);
	return 0;
}
void fall()
{
	  int* p = 0x00000000; 
	  *p = 13;
}
void posix_death_signal(int signum)
{
	memento();
	signal(signum, SIG_DFL);
	exit(3);
}


int main(int argc, char *argv[])
{
	signal(SIGSEGV, posix_death_signal);
	fall();
}


В отличие от SEH, это работает всегда: решение «многопоточное» (вы можете уронить программу в любом потоке, обработчик запустится в любом случае) и «кроссплатформенное» — работает под любым компилятором, и под любой POSIX-совместимой ОС.
Теги:
Хабы:
+53
Комментарии59

Публикации

Изменить настройки темы

Истории

Работа

QT разработчик
6 вакансий
Программист C++
123 вакансии

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн