Мне нужно отловить ошибку сегментации в операциях очистки сторонней библиотеки. Иногда это происходит непосредственно перед выходом из моей программы, и я не могу исправить настоящую причину этого. В программировании под Windows я мог сделать это с помощью __try - __catch. Есть ли кроссплатформенный или зависящий от платформы способ сделать то же самое? Мне это нужно в Linux, gcc.
Как отловить ошибку сегментации в Linux?
Ответы (5)
В 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) и утечки / сбоя позже.
- person zneak; 04.06.2015
./build_gcc_linux_release
выдает несколько ошибок.
- person alfC; 23.12.2016
Вот пример того, как это сделать на 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
в обработчике.
- person stefanct; 17.11.2017
STDOUT_FILENO
напрямую. Таким образом, ваш комментарий вводит в заблуждение, и я его критиковал.
- person stefanct; 24.11.2017
Для переносимости, вероятно, следует использовать std::signal
из стандартной библиотеки C ++, но есть много ограничений на то, что может делать обработчик сигналов. К сожалению, невозможно поймать SIGSEGV из программы на C ++ без введения неопределенного поведения, потому что в спецификации сказано:
- поведение undefined для вызова любой библиотечной функции из обработчика, кроме очень узкого подмножества стандартных библиотечных функций (
abort
,exit
, некоторые атомарные функции, переустановка текущего обработчика сигнала,memcpy
,memmove
, характеристики типа,std::move
,std::forward
и еще немного). - это неопределенное поведение, если обработчик использует выражение
throw
. - это неопределенное поведение, если обработчик возвращается при обработке SIGFPE, SIGILL, SIGSEGV
Это доказывает, что невозможно поймать SIGSEGV из программы, используя строго стандартный и переносимый C ++. SIGSEGV по-прежнему перехватывается операционной системой и обычно передается родительскому процессу при вызове функции семейства wait.
Вы, вероятно, столкнетесь с такими же проблемами при использовании сигнала POSIX, потому что в 2.4.3 Сигнальные действия:
Поведение процесса не определено после обычного возврата из функции перехвата сигналов для сигналов SIGBUS, SIGFPE, SIGILL или SIGSEGV, которые не были сгенерированы
kill()
,sigqueue()
илиraise()
.
Несколько слов о longjump
s. Предполагая, что мы используем сигналы POSIX, использование longjump
для имитации раскрутки стека не поможет:
Хотя
longjmp()
является асинхронно-сигнально-безопасной функцией, если она вызывается из обработчика сигнала, который прервал не-асинхронную-сигнально-безопасную функцию или эквивалент (например, обработка, эквивалентнаяexit()
, выполненная после возврата из первоначального вызова кmain()
), поведение любого последующего вызова не-асинхронно-сигнально-безопасной функции или ее эквивалента не определено.
Это означает, что продолжение, вызванное вызовом longjump, не может надежно вызвать обычно полезную библиотечную функцию, такую как printf
, malloc
или exit
, или вернуться из main, не вызывая неопределенного поведения. Таким образом, продолжение может выполнять только ограниченные операции и может выходить только через какой-то ненормальный механизм завершения.
Короче говоря, перехватить SIGSEGV и возобновить выполнение программы на портативном компьютере, вероятно, невозможно без введения UB. Даже если вы работаете на платформе Windows, для которой у вас есть доступ к структурированной обработке исключений, стоит упомянуть, что MSDN предлагает никогда не пытаться обрабатывать аппаратные исключения: Аппаратные исключения.
И наконец, что не менее важно, возникнет ли какой-либо SIGSEGV при разыменовании указателя с нулевым значением (или указателя с недопустимым значением), это не является требованием стандарта. Поскольку косвенное обращение через указатель с нулевым значением или любой указатель с недопустимым значением является неопределенным поведением < / strong>, что означает, что компилятор предполагает, что ваш код никогда не попытается сделать это во время выполнения, компилятор может произвести преобразование кода, которое исключит такое неопределенное поведение. Например, из cppreference,
int foo(int* p) {
int x = *p;
if(!p)
return x; // Either UB above or this branch is never taken
else
return 0;
}
int main() {
int* p = nullptr;
std::cout << foo(p);
}
Здесь истинный путь if
может быть полностью опущен компилятором в качестве оптимизации; только часть else
могла быть сохранена. В противном случае компилятор заключает, что foo()
никогда не получит указатель с нулевым значением во время выполнения, поскольку это приведет к неопределенному поведению. Вызывая его с указателем с нулевым значением, вы можете наблюдать значение 0
, выводимое на стандартный вывод, и отсутствие сбоя, вы можете наблюдать сбой с SIGSEG, на самом деле вы могли наблюдать что угодно, поскольку к программам, которые не свободны от undefined, не предъявляются разумные требования. поведение.
Здесь можно найти решение 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);
}
}
Иногда нам нужно поймать 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 () в цикле - память может не освобождаться, пока функция не вернется.