Обнаружение ошибок в C
-
23-08-2019 - |
Вопрос
У меня есть программа, которая иногда отказывается от арифметики указателей.Я знаю, что это случается, но я не могу легко проверить заранее, есть ли сбой сегмента или нет - либо я могу «предварительно просканировать» входные данные, чтобы увидеть, вызовет ли это сбой сегмента (что может быть невозможно определить), или я могу переоборудовать его, чтобы не использовать арифметику указателей, что потребует значительно большего объема работы, или я могу попытаться поймать ошибку сегмента.Итак, мой вопрос:
1) Как в C можно поймать сегфолт?Я знаю что-нибудь в ОС вызывает сбой, но что может сделать программа C в случае сбоя, чтобы умереть немного более изящно, чем просто Segmentation fault
?
2) Насколько это портативно?
Я полагаю, что это крайне непереносимое поведение, поэтому, если вы опубликуете какой-либо код для обнаружения сбоя, пожалуйста, расскажите мне, над чем он работает.Я использую Mac OS X, но мне бы хотелось, чтобы моя программа работала на как можно большем количестве платформ, и я хочу посмотреть, какие у меня есть варианты.
И не волнуйтесь: по сути, все, что я хочу сделать, это распечатать более удобное для пользователя сообщение об ошибке и освободить немного malloc()
потеряешь память, а потом умрешь.Я не планирую просто игнорировать все полученные сегменты и идти вперед.
Решение
Вам необходимо определить обработчик сигнала.В системах Unix это делается с помощью функции sigaction
.Я сделал это с тем же кодом в 64- и 32-разрядной версии Fedora и в Sun Solaris.
Другие советы
Что ж, SIGSEGV можно перехватывать, а это POSIX, поэтому в этом смысле он переносим.
Но меня беспокоит то, что вы, похоже, хотите справиться с сбоем сегмента, а не устранять проблему, вызывающую сбой сегмента.Если бы мне пришлось выбирать, виновата ли ОС или мой собственный код, я знаю, что бы я выбрал.Я предлагаю вам найти эту ошибку, исправить ее, а затем написать тестовый пример, чтобы убедиться, что она больше никогда вас не укусит.
Вы можете использовать функцию сигнал чтобы установить новый обработчик сигнала:
#include <signal.h>
void (*signal(int signum, void (*sighandler)(int)))(int);
Что-то вроде следующего кода:
signal(SIGINT , clean_exit_on_sig);
signal(SIGABRT , clean_exit_on_sig);
signal(SIGILL , clean_exit_on_sig);
signal(SIGFPE , clean_exit_on_sig);
signal(SIGSEGV, clean_exit_on_sig); // <-- this one is for segmentation fault
signal(SIGTERM , clean_exit_on_sig);
void
clean_exit_on_sig(int sig_num)
{
printf ("\n Signal %d received",sig_num);
}
Безопасные действия в обработчике сигнала: очень ограниченное.Небезопасно вызывать любую библиотечную функцию, о которой не известно, что она является реентерабельной, что исключает, например, free()
и printf()
.Лучше всего установить переменную и вернуть ее, но это вам не очень поможет.Также безопасно использовать системные вызовы, такие как write()
.
Обратите внимание, что в двух приведенных здесь примерах обратной трассировки backtrace_symbols_fd()
функция будет безопасной, поскольку она напрямую использует необработанный fd, но вызов fprintf()
неверно, и его следует заменить использованием write()
.
Я думаю, вы пытаетесь решить проблему, которой не существует.По крайней мере, вы работаете не с той стороны.Вы не сможете ловить ошибка сегментации, поскольку эта ошибка/исключение выдается ОС (это вызванный вашей программой, ОС просто ловит это).
Я бы посоветовал вам переосмыслить свою стратегию относительно ввода:Почему его невозможно продезинфицировать?Самое важное — это проверка размера, для этого в стандартной библиотеке C есть соответствующие функции.Тогда, конечно, вам придется проверить достоверность введенных данных относительно содержимого.Да, это, вероятно, потребует много работы, но это единственный способ написать надежную программу.
РЕДАКТИРОВАТЬ: Я не большой эксперт по C и не знал, что даже ошибка сегментации может быть обработана обработчиком сигнала.Тем не менее, я думаю, что это неправильный путь по причинам, упомянутым выше.
Вам нужно будет предоставить обработчик SIGSEGV, этот выглядит вполне прилично.
обработка сигналов (относительно) переносима на машины Unix (включая Mac и Linux).Большие различия заключаются в деталях исключений, которые передаются в качестве аргумента процедуре обработки сигналов.Извините, но для этого вам, вероятно, понадобится куча #ifdef, если вы хотите печатать более разумные сообщения об ошибках (например, где и по какому адресу произошла ошибка)...
ок, вот фрагмент кода, с которого вы можете начать:
#include <signal.h>
/* reached when a segv occurrs */
void
SEGVFunction( SIGARGS )
{
...
}
...
main(...) {
signal(SIGSEGV, SEGVFunction); /* tell the OS, where to go in case... */
...
... do your work ...
}
Ваша задача:
- проверьте, что такое SIGARGS (зависит от ОС, поэтому используйте ifdef)
- Узнайте, как извлечь адрес неисправности и компьютер из информации об исключении в sigArgs.
- напечатать разумное сообщение
- Выход
Теоретически вы могли бы даже исправить компьютер в обработчике сигнала (после инструкции по ошибке) и продолжить.Однако типичные обработчики сигналов либо выходят(), либо возвращаются в longjmp() в место сохранения в основном файле.
с уважением
Здесь приведен пример того, как перехватить SIGSEGV и распечатать трассировку стека с помощью функции backtrace() glibc:
как сгенерировать трассировку стека при сбое моего приложения на C++
Вы можете использовать это, чтобы отловить сбой и очистить его, но будьте осторожны:вам не следует делать слишком много вещей в обработчике сигналов, особенно в том, что связано с вызовами типа malloc().Существует множество вызовов, которые небезопасны для сигнала, и вы можете в конечном итоге выстрелить себе в ногу, если, скажем, вызовите malloc из malloc.