10

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

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

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 框架有一些我无法解决的挑战

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

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

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

4

2 回答 2

7

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

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

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

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

建议的方法是:

fork()创建一个子进程。

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

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

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

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

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

于 2014-08-14T10:09:30.267 回答
4

为了补充sstn 的回答,在 Linux 上,您可以拥有处理器和系统特定的C 代码:

  • 使用sigaction(2)安装信号处理程序SA_SIGINFO
  • 使用该信号处理程序的第三个参数,它是一个(机器特定的)ucontext_t*指针
  • 分析机器特定的上下文状态(即机器mcontext_t*从该状态注册ucontext_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 规范


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


据传处理SIGEGV在 Linux 上效率不高。在 GNU/Hurd 上,您将使用它的外部寻呼机机制


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

附加物

最近(2016 年 6 月)4.6 或未来或已修补的内核可能能够处理用户空间中的页面错误,尤其是userfaultfd;但我不太了解细节。另请参阅此问题

于 2014-08-14T10:21:10.680 回答