我需要在第三方库清理操作中捕获分段错误。有时会在我的程序退出之前发生这种情况,而我无法解决此问题的真正原因。在Windows编程中,我可以使用__try-__catch做到这一点。是否有跨平台或特定于平台的方法来做到这一点?我在Linux中需要这个,gcc。
我需要在第三方库清理操作中捕获分段错误。有时会在我的程序退出之前发生这种情况,而我无法解决此问题的真正原因。在Windows编程中,我可以使用__try-__catch做到这一点。是否有跨平台或特定于平台的方法来做到这一点?我在Linux中需要这个,gcc。
Answers:
在Linux上,我们也可以将它们作为例外。
通常,当您的程序执行分段错误时,会向其发送SIGSEGV
信号。您可以为此信号设置自己的处理程序并减轻后果。当然,您应该真正确定可以从这种情况中恢复过来。我认为,就您而言,您应该改为调试代码。
返回主题。我最近遇到了一个库 (简短手册),该库将此类信号转换为异常,因此您可以编写如下代码:
try
{
*(int*) 0 = 0;
}
catch (std::exception& e)
{
std::cerr << "Exception caught : " << e.what() << std::endl;
}
不过没有检查。适用于我的x86-64 Gentoo盒。它具有特定于平台的后端(从gcc的java实现中借用),因此它可以在许多平台上工作。它仅支持x86和x86-64,但您可以从libjava获得后端,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
调用外,程序中没有其他任何触摸。
在这里找到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);
}
}
为了实现可移植性,应该std::signal
从标准C ++库中使用它,但是对信号处理程序的功能有很多限制。不幸的是,如果不引入未定义的行为,就不可能从C ++程序中捕获SIGSEGV ,因为该规范指出:
abort
,exit
一些原子功能,重新安装电流信号处理程序,memcpy
,memmove
,型性状,`的std ::移动, std::forward
,并且更一些)。throw
表达式。这证明了使用严格的标准和可移植的C ++从程序中捕获SIGSEGV是不可能的。SIGSEGV仍被操作系统捕获,通常在等待时报告给父进程调用系列函数。
使用POSIX信号可能会遇到同样的麻烦,因为在2.4.3 Signal Actions中有一个子句说:
它从一个信号捕获函数通常返回为不是由产生的SIGBUS,SIGFPE,SIGILL或SIGSEGV信号之后的过程的行为是未定义的
kill()
,sigqueue()
或raise()
。
关于longjump
s的一个字。假设我们使用的是POSIX信号,那么使用longjump
模拟堆栈展开将无济于事:
尽管这
longjmp()
是异步信号安全函数,但如果它是通过中断非异步信号安全函数或等效函数的信号处理程序调用的(例如等效于exit()
从初始调用返回之后执行的处理main()
),后续对非异步信号安全函数或等效函数的任何调用的行为均未定义。
这意味着对longjump的调用所调用的继续不能可靠地调用通常有用的库函数(例如)printf
,malloc
或exit
从main返回而不会引起未定义行为。因此,继续只能执行受限制的操作,并且只能通过某种异常终止机制退出。
简而言之,如果不引入UB ,捕获SIGSEGV并在便携式计算机中恢复程序执行可能是不可行的。即使您在可以访问结构化异常处理的Windows平台上工作,也值得一提的是MSDN建议不要尝试处理硬件异常:硬件异常
有时,我们想捕获aSIGSEGV
来找出指针是否有效,也就是说,它是否引用了有效的内存地址。(或者甚至检查某个任意值是否可能是指针。)
一种选择是进行检查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);
PSDLOG()
是printf()
Android日志。在这里FIRST_UNUSED_BIT()
定义。
PPS循环调用alloca()可能不是一个好主意-直到函数返回之前,内存可能不会释放。