Muszę wychwycić błąd segmentacji w operacjach czyszczenia bibliotek innych firm. Dzieje się to czasami tuż przed zamknięciem programu i nie potrafię naprawić prawdziwej przyczyny. W programowaniu Windows mogłem to zrobić za pomocą __try - __catch. Czy istnieje wieloplatformowy lub specyficzny dla platformy sposób, aby zrobić to samo? Potrzebuję tego w Linuksie, gcc.
c++
segmentation-fault
try-catch
Alex F.
źródło
źródło
Odpowiedzi:
W Linuksie możemy je również mieć jako wyjątki.
Zwykle, gdy program wykonuje błąd segmentacji, wysyłany jest
SIGSEGV
sygnał. Możesz ustawić własną obsługę dla tego sygnału i złagodzić konsekwencje. Oczywiście powinieneś być pewien, że możesz wyleczyć się z sytuacji. Myślę, że w twoim przypadku powinieneś zamiast tego debugować swój kod.Wracając do tematu. Niedawno natknąłem się na bibliotekę ( krótką instrukcję ), która przekształca takie sygnały w wyjątki, więc możesz pisać taki kod:
try { *(int*) 0 = 0; } catch (std::exception& e) { std::cerr << "Exception caught : " << e.what() << std::endl; }
Jednak nie sprawdziłem tego.Działa na moim Gentoo boxie x86-64. Ma zaplecze specyficzne dla platformy (zapożyczone z implementacji java gcc), więc może działać na wielu platformach. Po prostu obsługuje x86 i x86-64 po wyjęciu z pudełka, ale możesz pobrać backendy z libjava, która znajduje się w źródłach gcc.źródło
-fnon-call-exceptions
upewnić się, że działa - i wiąże się to z kosztem wydajności. Istnieje również niebezpieczeństwo, że będziesz wyrzucać z funkcji bez obsługi wyjątków (jak funkcja C), a później wycieknie / ulegnie awarii../build_gcc_linux_release
daje kilka błędów.Oto przykład, jak to zrobić w 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; }
źródło
signal(7)
zawiera listę wszystkich funkcji bezpiecznych dla sygnału asynchronicznego, których można używać przy stosunkowo niewielkiej ostrożności. W powyższym przykładzie jest to również całkowicie bezpieczne, ponieważ nic innego w programie nie dotykastdout
pozaprintf
wywołaniem w programie obsługi.Aby zapewnić przenośność, prawdopodobnie należy użyć
std::signal
standardowej biblioteki C ++, ale istnieje wiele ograniczeń co do tego, co może zrobić program obsługi sygnału. Niestety, nie jest możliwe przechwycenie SIGSEGV z poziomu programu C ++ bez wprowadzenia niezdefiniowanego zachowania, ponieważ specyfikacja mówi:abort
,exit
niektóre funkcje atomowe, zainstaluj aktualną obsługi sygnału,memcpy
,memmove
, cechy typu, `std :: ruch, std::forward
, a niektóre więcej ).throw
wyrażenia.Dowodzi to, że nie da się złapać SIGSEGV z poziomu programu używającego ściśle standardowego i przenośnego C ++. SIGSEGV jest nadal przechwytywany przez system operacyjny i zwykle jest zgłaszany do procesu nadrzędnego, gdy wywoływana jest funkcja rodziny oczekiwania .
Prawdopodobnie napotkasz podobne problemy używając sygnału POSIX, ponieważ istnieje klauzula, która mówi w 2.4.3 Działania sygnału :
Słowo o
longjump
s. Zakładając, że używamy sygnałów POSIX, użycielongjump
do symulacji rozwijania stosu nie pomoże:Oznacza to, że kontynuacja wywołana przez wywołanie longjump nie może niezawodnie wywołać zwykle użytecznej funkcji bibliotecznej, takiej jak
printf
,malloc
lubexit
lub zwrot od głównej bez wywołania niezdefiniowanej zachowanie. W związku z tym kontynuacja może wykonywać tylko ograniczone operacje i może zakończyć się tylko przez jakiś nieprawidłowy mechanizm zakończenia.Krótko mówiąc, przechwycenie SIGSEGV i wznowienie wykonywania programu na urządzeniu przenośnym jest prawdopodobnie niewykonalne bez wprowadzenia UB. Nawet jeśli pracujesz na platformie Windows, dla której masz dostęp do strukturalnej obsługi wyjątków, warto wspomnieć, że MSDN sugeruje, aby nigdy nie próbować obsługiwać wyjątków sprzętowych: Wyjątki sprzętowe
źródło
Rozwiązanie C ++ znalezione tutaj ( 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); } }
źródło
Czasami chcemy złapać a,
SIGSEGV
aby dowiedzieć się, czy wskaźnik jest prawidłowy, to znaczy, czy odwołuje się do prawidłowego adresu pamięci. (Lub nawet sprawdź, czy jakaś dowolna wartość może być wskaźnikiem).Jedną z opcji jest sprawdzenie tego za pomocą
isValidPtr()
(działało na Androidzie):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); }
Inną opcją jest odczytanie atrybutów ochrony pamięci, co jest nieco trudniejsze (działało na Androidzie):
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()
jestprintf()
w dzienniku Androida.FIRST_UNUSED_BIT()
jest zdefiniowane tutaj .PPS Wywoływanie funkcji przydziel () w pętli może nie być dobrym pomysłem - pamięć może nie zostać zwolniona do czasu powrotu funkcji.
źródło