Мне нужно отловить ошибку сегментации в операциях очистки сторонней библиотеки. Иногда это происходит непосредственно перед выходом из моей программы, и я не могу исправить настоящую причину этого. В программировании под Windows я мог сделать это с помощью __try - __catch. Есть ли кроссплатформенный или специфичный для платформы способ сделать то же самое? Мне это нужно в Linux, gcc.
c++
segmentation-fault
try-catch
Алекс Ф
источник
источник
Ответы:
В Linux они тоже могут быть исключениями.
Обычно, когда ваша программа выполняет ошибку сегментации, ей посылается
SIGSEGV
сигнал. Вы можете настроить свой собственный обработчик для этого сигнала и смягчить последствия. Конечно, вы действительно должны быть уверены, что сможете оправиться от ситуации. В вашем случае, я думаю, вам следует вместо этого отладить свой код.Вернемся к теме. Недавно я столкнулся с библиотекой ( кратким руководством ), которая преобразует такие сигналы в исключения, поэтому вы можете написать такой код:
try { *(int*) 0 = 0; } catch (std::exception& e) { std::cerr << "Exception caught : " << e.what() << std::endl; }
Но не проверял.Работает на моем компьютере Gentoo x86-64. Он имеет платформенно-зависимый бэкэнд (заимствованный из java-реализации gcc), поэтому он может работать на многих платформах. Он просто поддерживает x86 и x86-64 из коробки, но вы можете получить бэкэнд из libjava, который находится в исходных кодах gcc.источник
-fnon-call-exceptions
убедиться, что он работает, а это требует снижения производительности. Также существует опасность того, что вы выбрасываете из функции без поддержки исключений (например, функции C) и утечки / сбоя позже../build_gcc_linux_release
дает несколько ошибок.Вот пример того, как это сделать на C.
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> void segfault_sigaction(int signal, siginfo_t *si, void *arg) { printf("Caught segfault at address %p\n", si->si_addr); exit(0); } int main(void) { int *foo = NULL; struct sigaction sa; memset(&sa, 0, sizeof(struct sigaction)); sigemptyset(&sa.sa_mask); sa.sa_sigaction = segfault_sigaction; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); /* Cause a seg fault */ *foo = 1; return 0; }
источник
signal(7)
перечисляет все функции, безопасные для асинхронных сигналов, которые можно использовать с относительно небольшой осторожностью. В приведенном выше примере это также полностью безопасно, потому что ничего другого в программе не касается,stdout
кромеprintf
вызова в обработчике.Для переносимости, вероятно, следует использовать
std::signal
стандартную библиотеку C ++, но есть много ограничений на то, что может делать обработчик сигналов. К сожалению, невозможно поймать SIGSEGV из программы на C ++ без введения неопределенного поведения, потому что в спецификации сказано:abort
,exit
, некоторые атомарные функций, переустановите текущий обработчик сигналаmemcpy
,memmove
, черты типа, `зОго :: двигаться, std::forward
, и еще немного ).throw
выражение.Это доказывает, что невозможно поймать SIGSEGV из программы, использующей строго стандартный и переносимый C ++. SIGSEGV по-прежнему перехватывается операционной системой и обычно передается родительскому процессу при вызове функции семейства ожидания .
Вы, вероятно, столкнетесь с такими же проблемами при использовании сигнала POSIX, потому что в 2.4.3 Signal Actions есть пункт, который гласит :
Несколько слов о
longjump
с. Предполагая, что мы используем сигналы POSIX, использованиеlongjump
для имитации раскрутки стека не поможет:Это означает, что продолжение, вызванное вызовом longjump, не может надежно вызвать обычно полезную библиотечную функцию, такую как
printf
,malloc
или,exit
или возврат из main, не вызывая неопределенного поведения. Таким образом, продолжение может выполнять только ограниченные операции и может выходить только через какой-то ненормальный механизм завершения.Короче говоря, перехват SIGSEGV и возобновление выполнения программы на переносном компьютере, вероятно, невозможно без введения UB. Даже если вы работаете на платформе Windows, для которой у вас есть доступ к структурированной обработке исключений, стоит упомянуть, что MSDN предлагает никогда не пытаться обрабатывать аппаратные исключения: Аппаратные исключения
источник
Решение C ++ найдено здесь ( http://www.cplusplus.com/forum/unices/16430/ )
#include <signal.h> #include <stdio.h> #include <unistd.h> void ouch(int sig) { printf("OUCH! - I got signal %d\n", sig); } int main() { struct sigaction act; act.sa_handler = ouch; sigemptyset(&act.sa_mask); act.sa_flags = 0; sigaction(SIGINT, &act, 0); while(1) { printf("Hello World!\n"); sleep(1); } }
источник
Иногда нам нужно поймать a,
SIGSEGV
чтобы узнать, действителен ли указатель, то есть ссылается ли он на действительный адрес памяти. (Или даже проверьте, может ли какое-то произвольное значение быть указателем.)Один из вариантов - проверить это
isValidPtr()
(работает на Android):int isValidPtr(const void*p, int len) { if (!p) { return 0; } int ret = 1; int nullfd = open("/dev/random", O_WRONLY); if (write(nullfd, p, len) < 0) { ret = 0; /* Not OK */ } close(nullfd); return ret; } int isValidOrNullPtr(const void*p, int len) { return !p||isValidPtr(p, len); }
Другой вариант - прочитать атрибуты защиты памяти, что немного сложнее (работает на Android):
re_mprot.c:
#include <errno.h> #include <malloc.h> //#define PAGE_SIZE 4096 #include "dlog.h" #include "stdlib.h" #include "re_mprot.h" struct buffer { int pos; int size; char* mem; }; char* _buf_reset(struct buffer*b) { b->mem[b->pos] = 0; b->pos = 0; return b->mem; } struct buffer* _new_buffer(int length) { struct buffer* res = malloc(sizeof(struct buffer)+length+4); res->pos = 0; res->size = length; res->mem = (void*)(res+1); return res; } int _buf_putchar(struct buffer*b, int c) { b->mem[b->pos++] = c; return b->pos >= b->size; } void show_mappings(void) { DLOG("-----------------------------------------------\n"); int a; FILE *f = fopen("/proc/self/maps", "r"); struct buffer* b = _new_buffer(1024); while ((a = fgetc(f)) >= 0) { if (_buf_putchar(b,a) || a == '\n') { DLOG("/proc/self/maps: %s",_buf_reset(b)); } } if (b->pos) { DLOG("/proc/self/maps: %s",_buf_reset(b)); } free(b); fclose(f); DLOG("-----------------------------------------------\n"); } unsigned int read_mprotection(void* addr) { int a; unsigned int res = MPROT_0; FILE *f = fopen("/proc/self/maps", "r"); struct buffer* b = _new_buffer(1024); while ((a = fgetc(f)) >= 0) { if (_buf_putchar(b,a) || a == '\n') { char*end0 = (void*)0; unsigned long addr0 = strtoul(b->mem, &end0, 0x10); char*end1 = (void*)0; unsigned long addr1 = strtoul(end0+1, &end1, 0x10); if ((void*)addr0 < addr && addr < (void*)addr1) { res |= (end1+1)[0] == 'r' ? MPROT_R : 0; res |= (end1+1)[1] == 'w' ? MPROT_W : 0; res |= (end1+1)[2] == 'x' ? MPROT_X : 0; res |= (end1+1)[3] == 'p' ? MPROT_P : (end1+1)[3] == 's' ? MPROT_S : 0; break; } _buf_reset(b); } } free(b); fclose(f); return res; } int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask) { unsigned prot1 = read_mprotection(addr); return (prot1 & prot_mask) == prot; } char* _mprot_tostring_(char*buf, unsigned int prot) { buf[0] = prot & MPROT_R ? 'r' : '-'; buf[1] = prot & MPROT_W ? 'w' : '-'; buf[2] = prot & MPROT_X ? 'x' : '-'; buf[3] = prot & MPROT_S ? 's' : prot & MPROT_P ? 'p' : '-'; buf[4] = 0; return buf; }
re_mprot.h:
#include <alloca.h> #include "re_bits.h" #include <sys/mman.h> void show_mappings(void); enum { MPROT_0 = 0, // not found at all MPROT_R = PROT_READ, // readable MPROT_W = PROT_WRITE, // writable MPROT_X = PROT_EXEC, // executable MPROT_S = FIRST_UNUSED_BIT(MPROT_R|MPROT_W|MPROT_X), // shared MPROT_P = MPROT_S<<1, // private }; // returns a non-zero value if the address is mapped (because either MPROT_P or MPROT_S will be set for valid addresses) unsigned int read_mprotection(void* addr); // check memory protection against the mask // returns true if all bits corresponding to non-zero bits in the mask // are the same in prot and read_mprotection(addr) int has_mprotection(void* addr, unsigned int prot, unsigned int prot_mask); // convert the protection mask into a string. Uses alloca(), no need to free() the memory! #define mprot_tostring(x) ( _mprot_tostring_( (char*)alloca(8) , (x) ) ) char* _mprot_tostring_(char*buf, unsigned int prot);
PS
DLOG()
находитсяprintf()
в журнале Android.FIRST_UNUSED_BIT()
определяется здесь .PPS Возможно, не стоит вызывать alloca () в цикле - память может не освобождаться, пока функция не вернется.
источник