C语言 使用 setjmp() 和 longjmp() 来防止程序中出现分段错误



我已经编写了一个程序来防止使用setjmp()longjmp()段错误,但是我编写的程序只能防止段错误发生一次(我在while循环中运行我的代码(。

这是我的代码:

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
jmp_buf buf;
void my_sig_handler(int sig)
{
if( sig )
{
printf("Received SIGSEGV signl n");
longjmp(buf,2);
}
}
int main()
{
while( 1)
{
switch( setjmp(buf) )                       // Save the program counter
{
case 0:
signal(SIGSEGV, my_sig_handler);        // Register SIGSEGV signal handler function
printf("Inside 0 statement n");
int *ptr = NULL;
printf("ptr is  %d ", *ptr);            // SEG fault will happen here
break;
case 2:
printf("Inside 2 statement n");        // In case of SEG fault, program should execute this statement
break;
default:
printf("Inside default statement n");
break;
}
}
return 0;
}

输出

Inside 0 statement 
Received SIGSEGV signl 
Inside 2 statement 
Inside 0 statement 
Segmentation fault

预期输出

Inside 0 statement 
Received SIGSEGV signl 
Inside 2 statement 
.
.(Infinite times)
.
Inside 0 statement 
Received SIGSEGV signal
Inside 2 statement 

有人可以解释一下为什么它仅在第一次按预期运行吗?另外,我在这里缺少什么来按预期运行我的代码?

长话短说:longjump(显然(是一个异步信号安全函数,printf也是如此。因此,从信号处理程序调用这些函数将导致未定义的行为。有关详细信息和异步信号安全函数列表,请参阅man 7 signal-safety

最有可能发生的事情是longjump(buf, 2)导致程序异常"逃逸"信号处理程序,这在执行第二个开关案例后会导致另一个分段错误。由于发生了另一个分段错误,因此再次调用信号处理程序,然后您执行另一个longjump(buf, 2),回到原来的位置,导致另一个段错误,依此类推......无限期。


编辑:正如Andrew Henle在下面的评论中所建议的那样,还有两个POSIX功能sigsetjmp()siglongjmp()。但是,我更喜欢下面描述的方法,因为它对我来说看起来更干净,并且可以安全地从信号处理程序返回,将脏工作留给内核。

如果您希望代码按预期运行,则可以让信号在段错误时接收有关上下文的信息:

static void signal_handler(int sig, siginfo_t *info, void *ucontext) {
/* Assuming your architecture is Intel x86_64. */
ucontext_t *uc = (ucontext_t *)ucontext;
greg_t *rip = &uc->uc_mcontext.gregs[REG_RIP];
/* Assign a new value to *rip somehow, which will be where the
execution will continue after the signal handler returns. */
}
int main(void) {
struct sigaction sa;
int err;
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = signal_handler;
err = sigemptyset(&sa.sa_mask);
if (err)
return 1;
err = sigaddset(&sa.sa_mask, SIGSEGV);
if (err)
return 1;
err = sigaction(SIGSEGV, &sa, NULL);
if (err)
return 1;
/* ... */
return 0;
}

这将允许您基本上在您想要的任何地方恢复执行,前提是您实际上知道确切地在哪里恢复。但是,要将rip设置为正确的值,您可能必须使用使用内联 asm 或其他一些肮脏技巧定义的全局标签。

这样的东西应该可以工作(在我的机器上测试(:

/* In main, where you want to retums after SIGSEGV: */
asm voaltile ("checkpoint: .global checkpoint" : );
/* In your signal handler: */
asm volatile (
"movabs $checkpoint, %0"
: "=r" (*rip)
);

如果你想知道为什么这不那么容易,那是因为它甚至不应该首先完成,它基本上是一种可憎的东西,除了可能乐于发现东西如何以最荒谬的方式被打破之外,没有任何目的。

您至少需要以下标头和功能测试宏才能使上述内容正常工作:

#define _GNU_SOURCE
#define __USE_GNU
#include <signal.h>
#include <ucontext.h>

请注意,这(当然(取决于架构和平台。

相关内容

最新更新