在分段错误错误之后,是否可以恢复C程序的正常执行流程?
struct A {
int x;
};
A* a = 0;
a->x = 123; // this is where segmentation violation occurs
// after handling the error I want to get back here:
printf("normal execution");
// the rest of my source code....
我想要一种类似于NullPointerException的机制,它存在于Java,C#等中。
注意:请不要告诉我C ++中存在异常处理机制,因为我知道,不要告诉我在分配之前应该检查每个指针等。
我真正想要实现的是恢复正常的执行流程,如上例所示。我知道可以使用POSIX信号进行一些操作。应该怎么样?其他想法?
答案 0 :(得分:23)
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <signal.h>
#include <stdlib.h>
#include <ucontext.h>
void safe_func(void)
{
puts("Safe now ?");
exit(0); //can't return to main, it's where the segfault occured.
}
void
handler (int cause, siginfo_t * info, void *uap)
{
//For test. Never ever call stdio functions in a signal handler otherwise*/
printf ("SIGSEGV raised at address %p\n", info->si_addr);
ucontext_t *context = uap;
/*On my particular system, compiled with gcc -O2, the offending instruction
generated for "*f = 16;" is 6 bytes. Lets try to set the instruction
pointer to the next instruction (general register 14 is EIP, on linux x86) */
context->uc_mcontext.gregs[14] += 6;
//alternativly, try to jump to a "safe place"
//context->uc_mcontext.gregs[14] = (unsigned int)safe_func;
}
int
main (int argc, char *argv[])
{
struct sigaction sa;
sa.sa_sigaction = handler;
int *f = NULL;
sigemptyset (&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
if (sigaction (SIGSEGV, &sa, 0)) {
perror ("sigaction");
exit(1);
}
//cause a segfault
*f = 16;
puts("Still Alive");
return 0;
}
$ ./a.out
SIGSEGV raised at address (nil)
Still Alive
如果我在生产代码中看到这样的东西,我会用蝙蝠击败某人,但这是一个丑陋,有趣的黑客。您不知道段错误是否已损坏您的某些数据,您将无法恢复并且知道现在一切正常,没有可行的方法来执行此操作。你可以做的唯一温和的事情是尝试记录错误(直接使用write(),而不是任何stdio函数 - 它们不是信号安全的)并且可能重启程序。对于那些情况,你最好写一个监控子流程退出的超级流程进程,记录它并启动一个新的子进程。
答案 1 :(得分:7)
您可以使用信号处理程序捕获分段错误,并决定继续执行程序(风险自负)。
信号名称为SIGSEGV
。
您必须使用sigaction()
标题中的signal.h
功能。
基本上,它的工作方式如下:
struct sigaction sa1;
struct sigaction sa2;
sa1.sa_handler = your_handler_func;
sa1.sa_flags = 0;
sigemptyset( &sa1.sa_mask );
sigaction( SIGSEGV, &sa1, &sa2 );
这是处理函数的原型:
void your_handler_func( int id );
如您所见,您无需返回。程序的执行将继续,除非你决定自己从处理程序中停止它。
答案 2 :(得分:3)
“所有事情都是允许的,但并非所有事情都是有益的” - 通常一个段错误是游戏结果有充分的理由......比拾起它更好的想法就是保持数据的持久性(数据库,或者至少是一个文件系统)并使它能够从那里停下来。这将为您提供更好的数据可靠性。
答案 3 :(得分:3)
见R.对MacMade回答的评论。
扩展他所说的,(在处理SIGSEV之后,或者,对于那种情况,SIGFPE,CPU + OS可以让你回到违规的insn)这里是我对零处理进行划分的测试:
#include <stdio.h>
#include <limits.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
static jmp_buf context;
static void sig_handler(int signo)
{
/* XXX: don't do this, not reentrant */
printf("Got SIGFPE\n");
/* avoid infinite loop */
longjmp(context, 1);
}
int main()
{
int a;
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sa.sa_handler = sig_handler;
sa.sa_flags = SA_RESTART;
sigaction(SIGFPE, &sa, NULL);
if (setjmp(context)) {
/* If this one was on setjmp's block,
* it would need to be volatile, to
* make sure the compiler reloads it.
*/
sigset_t ss;
/* Make sure to unblock SIGFPE, according to POSIX it
* gets blocked when calling its signal handler.
* sigsetjmp()/siglongjmp would make this unnecessary.
*/
sigemptyset(&ss);
sigaddset(&ss, SIGFPE);
sigprocmask(SIG_UNBLOCK, &ss, NULL);
goto skip;
}
a = 10 / 0;
skip:
printf("Exiting\n");
return 0;
}
答案 4 :(得分:3)
不,在任何逻辑意义上,在分段错误之后恢复正常执行是不可能的。您的程序只是尝试取消引用空指针。如果您的程序预期不存在,您将如何继续正常进行?这是一个编程错误,唯一安全的做法就是退出。
考虑一些可能导致分段错误的原因:
只有在第一种情况下才有任何合理的期望,你可以继续
如果您有一个要取消引用的指针,但它可能合法地为空,您必须在尝试取消引用之前对其进行测试。我知道你不要我告诉你,但这是正确的答案,非常艰难。
编辑:这是一个示例,说明为什么在取消引用空指针后你肯定不想继续使用下一条指令:
void foobarMyProcess(struct SomeStruct* structPtr)
{
char* aBuffer = structPtr->aBigBufferWithLotsOfSpace; // if structPtr is NULL, will SIGSEGV
//
// if you SIGSEGV and come back to here, at this point aBuffer contains whatever garbage was in memory at the point
// where the stack frame was created
//
strcpy(aBuffer, "Some longish string"); // You've just written the string to some random location in your address space
// good luck with that!
}
答案 5 :(得分:1)
调用此方法,当发生段错误时,您的代码将执行segv_handler,然后继续返回原来的位置。
void segv_handler(int)
{
// Do what you want here
}
signal(SIGSEGV, segv_handler);
答案 6 :(得分:1)
从SIGSEGV中恢复没有任何有意义的方法,除非您完全知道是什么导致了它,并且在标准C中无法做到这一点。可能(可以想象)在仪表化环境中,如C-VM( ?)。所有程序错误信号都是如此;如果你试图阻止/忽略它们,或者建立正常返回的处理程序,你的程序可能会在它们发生时可怕地破坏,除非它们是由raise
或kill
生成的。
请帮自己一个忙,并考虑错误案例。
答案 7 :(得分:0)
在POSIX中,当您这样做时,您的进程将被发送给SIGSEGV。默认处理程序只会崩溃您的程序。您可以使用signal()调用添加自己的处理程序。您可以通过自己处理信号来实现您喜欢的任何行为。
答案 8 :(得分:0)
您可以使用SetUnhandledExceptionFilter()函数(在Windows中),但即使能够跳过“非法”指令,您也需要能够解码某些汇编程序操作码。并且,正如glowcoder所说,即使它会在运行时“注释掉”生成段错误的指令,原始程序逻辑会留下什么(如果它可能被称为)? 一切皆有可能,但这并不意味着必须这样做。
答案 9 :(得分:0)
不幸的是,你不能在这种情况下。越野车功能具有未定义的行为,可能已损坏程序的状态。
您可以做的是在新流程中运行这些功能。如果此过程因返回代码指示SIGSEGV而死亡,则表示它已失败。
您也可以自己重写这些功能。
答案 10 :(得分:0)
我可以看到从分段违规中恢复的情况,如果您在循环中处理事件并且其中一个事件导致分段违规,那么您只想跳过此事件,继续处理剩余事件。在我看来,Segmentation Violation与Java中的NullPointerExceptions非常相似。是的,在其中任何一个之后,状态将是不一致和未知的,但在某些情况下,您希望处理这种情况并继续进行。例如,在Algo交易中,您将暂停执行订单并允许交易者手动接管,不会导致整个系统崩溃并破坏所有其他订单。
答案 11 :(得分:-1)
此glib手册为您提供了如何编写信号处理程序的清晰图片。
A signal handler is just a function that you compile together with the rest
of the program. Instead of directly invoking the function, you use signal
or sigaction to tell the operating system to call it when a signal arrives.
This is known as establishing the handler.
在您的情况下,您将不得不等待SIGSEGV指示分段错误。可以找到其他信号列表here。
信号处理程序大致分为两类
SIGSEGV
来自程序错误信号