如何编写信号处理程序以捕捉SIGSEGV?


71

我想编写一个信号处理程序来捕捉SIGSEGV。我保护一块内存以供使用

这样可以保护从缓冲区开始的内存的页面大小字节免受任何读取或写入的影响。

其次,我尝试读取内存:

这将生成一个SIGSEGV,并且将调用我的处理程序。到现在为止还挺好。我的问题是,调用处理程序后,我想通过以下方式更改内存的访问写入:

并继续正常运行我的代码。我不想退出该功能。在将来对同一内存进行写操作时,我想再次捕获该信号并修改写权限,然后记录该事件。

这是代码

问题是只有信号处理程序运行,捕获信号后我无法返回主函数。


2
谢谢您的编辑。我需要花一些时间来学习编辑我的问题..
阿迪

编译时,请始终启用所有警告,然后修复这些警告。(对于gcc,至少要使用:-Wall -Wextra -pedantic 我也要使用-Wconversion -std=gnu99:)编译器会告诉您:1)argc未使用的参数2)argv未使用的参数(建议使用main()的签名:int main( void ) 3)pelse代码块中使用的未经初始化的局部变量。4)参数unused未使用,建议:add语句:(void)unused;作为该函数的第一行。5)a设置局部变量但不使用。
user3629249'9

切勿printf()在信号处理程序中使用!该函数write()可以使用,但最好不要在信号处理程序中执行任何I / O,只需设置一个标志,然后让代码的主线检查该标志
user3629249

该变量pagesize声明为int,但应声明为size_t
user3629249 '16

sig参数应与SIGSEGV进行比较,因为还有其他信号,并且这样的比较将删除有关未使用sig参数的编译器消息
user3629249

Answers:


73

当您的信号处理程序返回时(假设它不调用exit或longjmp或阻止其实际返回的操作),代码将在发生信号时继续执行,重新执行同一条指令。由于此时,内存保护尚未更改,它只会再次抛出信号,您将陷入无限循环中的信号处理程序中。

因此,要使其工作,您必须在信号处理程序中调用mprotect。不幸的是,正如Steven Schansker指出的那样,mprotect不是异步安全的,因此您不能从信号处理程序中安全地调用它。因此,就POSIX而言,您很困惑。

幸运的是,对于大多数实现(据我所知,它是所有现代UNIX和Linux变体),mprotect是系统调用,因此可以从信号处理程序中安全地调用,因此您可以执行大部分所需的操作。问题是,如果您想在读取后重新更改保护,则必须在读取后在主程序中执行此操作。

另一种可能性是对信号处理程序的第三个参数进行操作,该参数指向一个操作系统和特定结构,该结构包含有关信号发生位置的信息。在Linux上,这是ucontext结构,其中包含有关$ PC地址和发生信号的其他寄存器内容的机器特定信息。如果对此进行了修改,则可以更改信号处理程序将返回的位置,因此可以将$ PC更改为仅在出错指令之后,以便在处理程序返回后不再执行。要做到这一点非常棘手(也是不可携带的)。

编辑

所述ucontext结构定义<ucontext.h>。内的ucontext所述字段uc_mcontext包含了机器上下文,并在中,阵列gregs包含通用寄存器上下文。因此,在您的信号处理程序中:

将为您提供发生异常的计算机。您可以阅读它以找出错误的指令,然后执行其他操作。

就在信号处理程序中调用mprotect的可移植性而言,任何遵循SVID规范或BSD4规范的系统都应该是安全的-它们允许在信号中调用任何系统调用(手册第2节中的任何内容)处理程序。


正确,您可以代表程序(例如VM)执行内存访问,然后更新指令指针。呼叫mprotect绝对容易。
Ben Voigt

克里斯,您好,您给了我一些有用的信息。谢谢。.你能告诉我如何在ucontext结构中读取信息(第三个参数并更改$ PC)。我很好奇。
阿迪(Adi)

@ Ben Voigt,我不太清楚您在说什么,请您多加说明。
阿迪

@chris,看来我可以在信号处理程序中执行mprotect,然后安全地返回以执行我的正常执行。我不确定你们是否提到了可移植性,但是我希望这对我来说很好。谢谢大家的帮助
Adi

@chris感谢您的解释,我将看到使用ur技术的PC。
阿迪

25

您已经陷入了所有人最初尝试处理信号时所做的陷阱。陷阱?认为您实际上可以对信号处理程序做任何有用的事情。在信号处理程序中,只允许您调用异步和可重入安全的库调用。

请参阅此CERT咨询以了解原因以及安全的POSIX功能列表。

请注意,您已经在调用的printf()不在该列表中。

mprotect也不是。您不允许从信号处理程序中调用它。它可能有效,但是我可以保证您会遇到麻烦。要特别小心信号处理程序,它们很难正确执行!

编辑

由于我现在已经是一个可移植的傻瓜,我将指出,在未采取适当预防措施的情况下,您也不应写入共享(即全局)变量


1
嗨,史蒂文,如果我不能在信号处理程序中做任何有用的事情,那么我可以更新其中的一些计数器并返回到main并正常运行我的代码,那可以吗?
阿迪(Adi)

从CERT咨询中引述,“只要将代码移植到的所有实现都保证这些功能异步安全,它们可以调用其他功能”。在Linux上,它包含更多功能。
Ben Voigt

当然可以,但是您只需要意识到问题所在即可!我不能说出哪些功能是安全信号,也不是安全信号,我怀疑很多功能可以!
史蒂文·斯兰斯克

2
CERT安全编码是一个不错的网站,我对此一无所知。似乎我有一段时间读了一些新书:)
alecov 2010年

1
“不允许从信号处理程序中调用[mprotect]。” 仅当您需要严格遵守POSIX时。glibc的今天mprotect的是异步信号安全:gnu.org/software/libc/manual/html_node/...
约瑟夫锡布尔赫丁-恢复莫妮卡


5

您不应该从信号处理程序中返回,因为这样行为是不确定的。而是使用longjmp从中跳出。

只有在异步信号安全功能中生成信号的情况下,这才可以。否则,如果程序曾经调用另一个async-signal-unsafe函数,则行为是不确定的。因此,仅应在必要之前立即建立信号处理程序,并尽快将其取消。

实际上,我知道SIGSEGV处理程序的用法很少:

  • 使用异步信号安全回溯库记录回溯,然后死亡。
  • 在诸如JVM或CLR之类的VM中:检查是否在JIT编译的代码中发生了SIGSEGV。如果没有,就死;如果是这样,则抛出一个特定于语言的异常(不是C ++异常),因为JIT编译器知道陷阱可能发生并生成了适当的帧展开数据,所以该异常有效。
  • 克隆()和exec()一个调试器(千万不能使用fork() -通过pthread_atfork注册的电话回调())。

最后,请注意,触发SIGSEGV的任何操作都可能是UB,因为它正在访问无效的内存。但是,如果信号为SIGFPE,则情况并非如此。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.