分段违规后恢复生机

在Segmentation Fault错误之后,是否可以恢复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 ++中有一个exception处理机制,因为我知道,不要告诉我在分配之前应该检查每个指针等。

我真正想要实现的是恢复正常的执行流程,如上例所示。 我知道可以使用POSIX信号进行一些操作。 应该怎么样? 其他想法?

 #include  #include  #include  #include  #include  #include  #include  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 

如果我在生产代码中看到这样的东西,我会用蝙蝠击败某人,但这是一个丑陋,有趣的黑客。 你不知道segfault是否已经损坏了你的一些数据,你将没有理智的恢复方式,并且知道现在一切都很好,没有可行的方法来做到这一点。 你可以做的唯一温和的事情是尝试记录错误(直接使用write(),而不是任何stdio函数 – 它们不是信号安全的)并且可能重启程序。 对于那些情况,你最好写一个监视子进程退出的超级进程进程,记录它并启动一个新的子进程。

您可以使用信号处理程序捕获分段错误,并决定继续执行程序(风险自负)。

信号名称为SIGSEGV

您必须使用来自signal.h标头的sigaction()函数。

基本上,它的工作方式如下:

 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 ); 

如您所见,您无需返回。 程序的执行将继续,除非您决定自己从处理程序中停止它。

“所有事情都是允许的,但并非所有事情都是有益的” – 通常一个段错误是游戏结果有充分理由……比拾起它更好的想法就是保持数据的持久性(数据库,或者至少是一个文件)系统)并使其能够从那里停下来。 这将为您提供更好的数据可靠性。

请参阅R.对MacMade回答的评论。

扩展他所说的,(在处理SIGSEV之后,或者,对于那种情况,SIGFPE,CPU + OS可以让你回到违规的insn)这里是我对零处理进行划分的测试:

 #include  #include  #include  #include  #include  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; } 

不,从任何逻辑意义上讲,在分段故障后恢复正常执行是不可能的。 您的程序只是尝试取消引用空指针。 如果你的程序预期不存在,你将如何继续正常进行? 这是一个编程错误,唯一安全的做法就是退出。

考虑一些分段错误的可能原因:

  • 你忘了给指针分配一个合法的值
  • 指针已被覆盖,因为您正在访问已释放的堆内存
  • 一个bug破坏了堆
  • 一个错误已经破坏了堆栈
  • 恶意第三方正在尝试缓冲区溢出攻击
  • malloc因为内存不足而返回null

只有在第一种情况下才有任何合理的期望,你可以继续

如果您有一个要取消引用的指针但它可能合法地为null,则必须在尝试取消引用之前对其进行测试 。 我知道你不要我告诉你,但这是正确的答案,非常艰难。

编辑:这是一个示例,说明为什么在取消引用空指针后你绝对不想继续下一条指令:

 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! } 

调用此方法,当发生段错误时,您的代码将执行segv_handler,然后继续返回原来的位置。

 void segv_handler(int) { // Do what you want here } signal(SIGSEGV, segv_handler); 

没有有意义的方法可以从SIGSEGV中恢复,除非你完全知道导致它的原因,并且在标准C中无法做到这一点。可能(可以想象)在仪表化环境中,如C-VM(?)。 所有程序错误信号都是如此; 如果你试图阻止/忽略它们,或者建立正常返回的处理程序,你的程序可能会在它们发生时可怕地破坏,除非它们可能是通过raisekill产生的。

只是帮自己一个忙,并考虑错误案例。

在POSIX中,当您这样做时,您的进程将被发送给SIGSEGV。 默认处理程序只会崩溃您的程序。 您可以使用signal()调用添加自己的处理程序。 您可以通过自己处理信号来实现您喜欢的任何行为。

您可以使用SetUnhandledExceptionFilter()函数(在Windows中),但即使能够跳过“非法”指令,您也需要能够解码某些汇编程序操作码。 并且,正如glowcoder所说,即使它会在运行时“注释掉”生成段错误的指令,原始程序逻辑会留下什么(如果它可能被称为)? 一切皆有可能,但并不意味着必须要做。

不幸的是,你不能在这种情况下。 有缺陷的function有未定义的行为,可能已损坏您的程序的状态。

您可以做的是在新进程中运行这些function。 如果此过程因返回代码指示SIGSEGV而死亡,则表示它已失败。

您也可以自己重写这些function。

我可以看到从分段违例中恢复的情况,如果您在循环中处理事件并且其中一个事件导致分段违规,那么您只想跳过此事件,继续处理剩余事件。 在我看来,Segmentation Violation与Java中的NullPointerExceptions非常相似。 是的,在这些之后,状态将是不一致和未知的,但在某些情况下,您希望处理这种情况并继续进行。 例如,在Algo交易中,您将暂停执行订单并允许交易者手动接管,而不会破坏整个系统并破坏所有其他订单。

最好的解决方案是以这种方式收件箱每个不安全的访问:

 #include  #include  #include  static jmp_buf buf; int counter = 0; void signal_handler(int) { longjmp(buf,0); } int main() { signal(SIGSEGV,signal_handler); setjmp(buf); if(counter++ == 0){ // if we did'nt try before *(int*)(0x1215) = 10; // access an other process's memory } std::cout<<"i am alive !!"< 

你的程序几乎不会在所有操作系统中崩溃

这个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指示分段错误。 其他信号列表可以在这里找到。

信号处理程序大致分为两类

  1. 你可以让处理函数通过调整一些全局数据结构注意信号到达,然后正常返回。
  2. 您可以让处理程序函数终止程序或将控制转移到可以从导致信号的情况中恢复的位置。

SIGSEGV属于程序错误信号