我是通过C在Unix中进行信号处理的新手,我一直在寻找一些关于它的教程(出于纯粹的兴趣)。
我的问题是,是否可以继续执行程序超过处理信号的位置?
我知道信号处理功能会进行清理,但本着异常处理的精神(例如在C ++中),是否可以以相同的方式处理该信号并使程序继续正常运行? / p>
目前catch
进入无限循环(可能是退出的方式是调用exit(1)
)。
我的目的是为b
分配1并使程序优雅地完成(如果可能的话)。
这是我的代码:
#include <signal.h>
#include <stdio.h>
int a = 5;
int b = 0;
void catch(int sig)
{
printf("Caught the signal, will handle it now\n");
b = 1;
}
int main(void)
{
signal(SIGFPE, catch);
int c = a / b;
return 0;
}
另外,由于C是程序性的,为什么在执行后者之后实际调用了有问题的语句之前声明的信号处理程序呢?
最后,为了使处理函数正确地进行清理,需要在函数之前声明所有需要在异常情况下清理的变量,对吧?
如果以上某些内容非常明显,请提前感谢您的回答和道歉。
答案 0 :(得分:11)
是的,这就是信号处理程序的用途。但是需要专门处理一些信号才能使程序继续(例如SIGSEGV,SIGFPE,......)。
请参阅sigaction
的联机帮助页:
根据POSIX,在忽略SIGFPE,SIGILL或SIGSEGV信号之后,进程的行为是未定义的 由kill(2)或raise(3)生成。整数除零具有未定义的结果。在某些架构上它会生成一个 SIGFPE信号。 (也将最负整数除以-1可能会生成SIGFPE。)忽略此信号可能会导致a 无尽的循环。
现在,你 忽略了信号,没有采取任何措施来防止它发生(再次)。您需要信号处理程序中的执行上下文并手动修复它,这涉及覆盖一些寄存器。
如果在sa_flags中指定了SA_SIGINFO,那么sa_sigaction(而不是 sa_handler)指定signum的信号处理函数。这个 函数接收信号编号作为其第一个参数,一个指针 siginfo_t作为其第二个参数和指向ucontext_t的指针 (强制转换为void *)作为其第三个参数。 (通常,处理程序 函数没有使用第三个参数。看到 getcontext(2)有关ucontext_t的更多信息。)
上下文允许在发生故障时访问寄存器,需要更改以允许程序继续运行。见lkml post。如前所述,siglongjmp
也可能是一种选择。该帖子还提供了一个可重复使用的解决方案来处理错误,而不必使变量全局等。:
因为你自己处理它,你有任何你想要的灵活性 与错误处理。例如,您可以创建错误处理程序 使用类似的东西跳转到函数中的某个指定点 这样:
__label__ error_handler;
__asm__("divl %2"
:"=a" (low), "=d" (high)
:"g" (divisor), "c" (&&error_handler))
... do normal cases ...
error_handler:
... check against zero division or overflow, so whatever you want to ..
然后,您的SIGFPE处理程序只需执行类似
的操作context.eip = context.ecx;
答案 1 :(得分:6)
通常,是的,在处理程序返回后继续执行。但是,如果信号由于硬件错误(例如浮点异常或分段错误)而导致 ,则无法撤消该错误,因此无论如何都会终止程序。
换句话说,您必须区分信号和导致信号的事物。信号本身非常精细且可处理,但它们并不总能让您修复导致信号的错误。
(某些信号是特殊的,例如ABRT和STOP,即使你只是用kill
手动发出这样的信号,你仍然无法“防止其影响”。当然,KILL根本无法处理。)
答案 2 :(得分:6)
如果您知道自己在做什么,可以将指令指针设置为在违规指令之后指向右侧。下面是我的x86(32位和64位)示例。不要在家里或实际产品中试试!!!
#define _GNU_SOURCE /* Bring REG_XXX names from /usr/include/sys/ucontext.h */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ucontext.h>
static void sigaction_segv(int signal, siginfo_t *si, void *arg)
{
ucontext_t *ctx = (ucontext_t *)arg;
/* We are on linux x86, the returning IP is stored in RIP (64bit) or EIP (32bit).
In this example, the length of the offending instruction is 6 bytes.
So we skip the offender ! */
#if __WORDSIZE == 64
printf("Caught SIGSEGV, addr %p, RIP 0x%lx\n", si->si_addr, ctx->uc_mcontext.gregs[REG_RIP]);
ctx->uc_mcontext.gregs[REG_RIP] += 6;
#else
printf("Caught SIGSEGV, addr %p, EIP 0x%x\n", si->si_addr, ctx->uc_mcontext.gregs[REG_EIP]);
ctx->uc_mcontext.gregs[REG_EIP] += 6;
#endif
}
int main(void)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = sigaction_segv;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGSEGV, &sa, NULL);
/* Generate a seg fault */
*(int *)NULL = 0;
printf("Back to normal execution.\n");
return 0;
}