Linux 中的 SEH 等效项或如何处理操作系统信号(如 SIGSERV)并继续

Posted

技术标签:

【中文标题】Linux 中的 SEH 等效项或如何处理操作系统信号(如 SIGSERV)并继续【英文标题】:SEH Equivalent in Linux or How do I handle OS Signals (like SIGSERV) and yet keep continuing 【发布时间】:2014-08-14 09:57:37 【问题描述】:

我目前正在开发一个单元测试框架,用户可以在其中创建测试用例并在框架中注册。

我还想确保如果任何用户测试代码导致崩溃,它不应该导致整个框架崩溃,而是应该被标记为失败。为了完成这项工作,我编写了以下代码,以便我可以在沙盒函数中运行用户代码

bool SandBox(void *(*fn)(void *),void *arg, void *rc)

#ifdef WIN32
    __try
    
        if (rc)
            rc = fn(arg);
        else
            fn(arg);
        return true;
    
    __except (EXCEPTION_EXECUTE_HANDLER)
    
        return false;
    

#else
#endif

这在 Windows 上完美运行,但我希望我的框架是可移植的,为了做到这一点,我希望确保 posix 环境具有类似的功能。

我知道 C 信号处理程序可以拦截 OS 信号,但是将信号处理机制转换为 SEH 框架有一些我无法解决的挑战

    即使我的程序收到信号,如何继续执行? 如何将执行控制从失败位置跳转到可用于错误处理的块(类似于 except)? 如何清理资源?

另一种可能性是我正在考虑在具有自己的信号处理程序的单独线程上运行用户测试代码并从信号处理程序终止线程,但再次不确定这是否可行。

因此,在我想得更远之前,如果社区知道更好的解决方案来解决这个问题/情况,我希望他们能提供帮助。

【问题讨论】:

【参考方案1】:

如您所说,您可以通过signal()sigaction() 捕获SIGSEGV。

不建议继续,因为这将是未定义的行为,即您的内存可能已损坏,这可能会使其他测试用例也失败(甚至过早终止您的整个过程)。

是否可以将测试用例作为子流程一一运行?这样,您可以检查退出状态,并检测它是否干净地终止、是否有错误或由于信号。

在单独的线程中运行测试用例会遇到同样的问题:在测试用例和驱动测试用例的代码之间没有内存保护。

建议的方法是:

fork() 创建子进程。

在子进程中,你execve() 你的测试用例。这可能是具有不同参数的相同二进制文件来选择某个测试用例)。

在父进程中,调用waitpid()等待测试用例终止。您从父进程中的 fork() 调用中收到了 pid。

使用 WIFEXITED、WEXITSTATUS、WIFSIGNALED、WTERMSIG 宏评估子进程状态。

如果您的测试用例需要超时,您还可以为 SIGCHLD 安装一个处理程序。如果超时首先过去,kill() 子进程。请注意,您只能从信号处理程序中调用某些函数。

进一步说明:execve() 并不是真正需要的。您可以继续并直接调用您指定的测试用例。

【讨论】:

您的可移植性要求将难以满足——或者至少需要更多的封装。 感谢您的回答。我喜欢你建议的方法。我需要一些时间来测试这个,所以等待社区发表评论,之后我可能会接受这个解决方案。在单独的上下文中,您为什么认为使用 fork 可能无法实现可移植性? 我只是在考虑您可能想要坚持使用 SEH,并且需要在 Linux/Unix 上使用不同的方法(fork/exec/wait)。但这可能可以通过封装差异来解决,即测试用例的执行方式。 是的,您的考虑是正确的,正如我正在考虑的那样(Windows 上的 SEH,posix 上的 fork)。您是否仍然预见到便携性问题? +1 用于建议多进程方法.../“这可能会让其他测试用例也失败”比让它们不正确通过更令人担忧...【参考方案2】:

作为对sstn's answer 的补充,在 Linux 上,您可以拥有 处理器和系统特定的 C 代码:

使用sigaction(2) 和SA_SIGINFO 安装信号处理程序 使用该信号处理程序的第三个参数,它是(特定于机器的)ucontext_t* 指针

分析机器特定的上下文状态(即机器从ucontext_t*注册mcontext_t*) - 详情参见getcontext(3);通过“反汇编”代码指针,您将能够知道哪个操作失败,您可以获得错误地址。

修改和修复该机器状态,这意味着通过调用 mmap(2) 更改进程地址空间和/或通过 mcontext_t* 修改某些机器寄存器

从信号处理程序返回到“已修复”状态,可能位于不同的指令地址。

这当然是不可移植的,而且编码和调试很痛苦。您可能需要禁用一些编译器优化,使用asm 指令或volatile 指针等...

在 Debian 或 Ubuntu 上,请参阅 /usr/include/x86_64-linux-gnu/sys/ucontext.h 标头文件。

IIRC 一些旧版本的 SML/NJ 玩了这样的把戏。

仔细阅读signal(7) 并研究您的处理器的ABI 规范,例如x86-64 ABI specification


在实践中,您也可以(更容易地)使用信号处理程序中的siglongjmp(3)。您也可能故意违反signal(7) 规则。您可以使用 Ian Taylor(在 Google 工作 GCC)libbacktrace 库,如果您的应用程序及其库具有调试信息(例如使用 g++ -O1 -g2 编译),它会更好地工作。另请参阅 GNU libc backtrace(3) 和 dladdr(3)


据传处理SIGEGV 在Linux 上效率不高。在 GNU/Hurd 上,你会使用它的 external pager mechanism。


另一种可能性是从gdb 调试器运行测试程序。 gdb 的最新版本可以用 Python 编写脚本,因此您可以自动化很多事情。这实际上可能是最便携的方法(因为最近 gdb 已被移植到许多系统上)。

附录

最近(2016 年 6 月)4.6 或未来或已修补的内核可能能够处理page faults in user space,尤其是userfaultfd;但我不太了解细节。另见this question。

【讨论】:

我看到的问题是您只能获得一个 SIGSEGV 用于对未映射区域(或只读区域)的内存访问,例如空指针解引用。但是可能存在对映射区域的其他无效内存访问,这可能使您的整个程序状态无效。 是的,您需要对可能的故障原因做出一些假设。 可移植性可能是个问题,因为我的目标是更大的平台子集,包括但不限于 AIX、HP、Solaris 和 zOS。

以上是关于Linux 中的 SEH 等效项或如何处理操作系统信号(如 SIGSERV)并继续的主要内容,如果未能解决你的问题,请参考以下文章

Linux中的find和grep命令对查找到的文件如何处理啊

Linux中的find和grep命令对查找到的文件如何处理啊

在 Linux 中的多个平台上启用 OpenCL?如何处理 ICD 文件?

Linux中的find和grep命令对查找到的文件如何处理啊

如何处理 Postgresql 查询中的单引号 [重复]

Linux内核17-硬件如何处理中断和异常