有没有一种方法可以捕获进程中的堆栈溢出?C++Linux
Is there a way to catch stack overflow in a process? C++ Linux
我有以下代码,它进入无限递归,并在耗尽分配给它的堆栈限制时触发seg错误。我正试图捕获这个分段错误并优雅地退出。然而,我无法在任何信号数字中捕捉到这种分割错误。
(一位客户正面临这个问题,并希望为这样的用例找到一个解决方案。将堆栈大小增加"极限堆栈大小128M"可以使他的测试通过。然而,他要求的是一个优雅的退出,而不是seg错误。下面的代码只是再现了实际问题,而不是实际算法的作用)。
感谢您的帮助。如果我试图捕捉信号的方式不正确,请也告诉我。编译:g++test.cc-std=c++0x
#include <iostream>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
int recurse_and_crash (int val)
{
// Print rough call stack depth at intervals.
if ((val %1000) == 0)
{
std::cout << "nval: " << val;
}
return val + recurse_and_crash (val+1);
}
void signal_handler(int signal, siginfo_t * si, void * arg)
{
std::cout << "Caught segfaultn";
exit(0);
}
int main(int argc, char ** argv)
{
int signal = 11; // SIGSEGV
if (argc == 2)
{
signal = std::stoi(std::string(argv[1]));
}
struct sigaction sa;
memset(&sa, 0, sizeof(struct sigaction));
sigemptyset(&sa.sa_mask);
sa.sa_sigaction = signal_handler;
sa.sa_flags = SA_SIGINFO;
sigaction(signal, &sa, NULL);
recurse_and_crash (1);
}
这是一个需要解决的复杂问题。在这一点上,我不会给出工作代码,而是关注您已经遇到的一些"漂亮"问题,或者,当您继续为此进行编码时,将遇到的一些问题。
首先,你为什么反复出现?
原因是,虽然信号处理程序是"执行上下文传输",但默认情况下,它们没有自己的堆栈。这意味着,如果由于堆栈溢出而接收到信号,则信号处理程序将尝试在堆栈上为可能传递给它的上下文分配空间,这只是再次重新抛出相同的信号。
要确保信号处理程序在它们自己独立/预先分配的堆栈上运行,请使用sigaltstack()
和sigaction()
的SA_ONSTACK
标志。
其次,根据堆栈溢出的"严重程度"(您的测试程序可能不会触发,但现实世界中的程序可能会触发),作为"溢出影响动作"的内存访问(尝试)可能会以其他信号而非SIGSEGV
结束。
你的例子"不具体地"捕捉到了所有信号,但在实践中这可能是不够的/相当令人困惑的——你向你的应用程序发送SIGUSR1
,或者外壳/终端在接地时向它发送SIGTTOU
,这绝对不代表堆栈流量
这意味着还有另一个问题——当由于堆栈溢出而进行"堆外"内存访问时,预计会出现哪些信号?你怎么能知道你得到的特定信号是由于堆栈访问引起的
这个问题的答案比第一眼看到的更复杂:
- 如果堆栈溢出"足够小",可以想象它在保护页内(一个有效的映射,但故意不可读),它将触发
SIGSEGV
- 如果(没有使用保护页,并且)访问的是未映射的内存区域,则会收到
SIGBUS
- 即使是某些CPU指令也可能会对访问"无效内存地址X"是否会导致
SIGSEGV
或SIGBUS
产生影响(例如,在x86上,某些指令引发#GP
,而其他指令则引发#PF
(对于相同的内存地址读/写),Linux内核可能会将其中一个转换为SIGBUS
,另一个转换成SIGSEGV
) - 如果恰好有其他内存映射到该访问发生的位置(比如说,您有
char local_to_blow_stack[1ULL << 40]; memset(&local_to_blow_stack, 0, 1);
),并且恰好发生了其他有效的事情,即"无论您的堆栈是多少,减去一TB"),则该访问实际上只会起作用。如果没有编译器来创建你的"辅助"代码来识别这种访问,实际上你可能已经破坏了堆栈,并且在最终到达触发信号的mem区域之前仍然进行了多次成功/非信号内存访问 - 对于其他无效操作,但堆栈访问,您可能会收到这些信号。堆访问、内存映射文件/设备访问也可能导致同样的结果
因此,"仅捕获信号",甚至"捕获所有可能因堆栈溢出而发生的信号"都是不够的。您需要信号处理程序中的来解码内存访问位置,和可能是操作/cpu指令,以验证尝试的内存访问实际上是"堆栈访问越界"。线程可以检索自己的堆栈边界-https://man7.org/linux/man-pages/man3/pthread_getattr_np.3.html可以用于此,至少在Linux上是这样(_np
意味着"不可移植"-这并不能保证在所有系统上都可用,其他系统可能有不同的接口来检索此信息)-但是。。。找到被访问的存储器位置取决于信号和再次访问指令通常(但不是总是)它在siginfo
(si_addr
)字段中。
根据我的记忆,确切地说,在什么情况下,哪些信号填充了si_addr
,以及其中的地址是发出内存访问的指令还是尝试访问的内存位置,有点依赖于系统和硬件(Linux的行为可能与Windows或MacOSX不同,在ARM上的行为也与x86不同)
因此,您还需要验证"此siginfo_t
中的si_addr
在信号线程堆栈附近",但也可能验证导致它的指令实际上是内存访问/si_addr
,可以"追溯"到出现故障的指令。(查找出错指令的地址/程序计数器)。。。需要解码信号处理程序ucontext_t
的其他参数。。。在硬件/操作系统的细节中,你是深深的。
在这一点上,我想终止;一个"简单"但并非完美的解决方案只需要一个备用信号堆栈,以及通过pthread_getattr_np()
检索当前堆栈边界的处理程序,以将si_addr
与之进行比较。如果你或他人的生活取决于正确的答案,请记住以上内容。
- boost::进程间消息队列引发错误
- 在进程中对同一管道进行读取和写入时C++管道出现问题
- 是否可以通过C++扩展强制多个python进程共享同一内存
- IPC使用多个管道和分支进程来运行Python程序
- 异常属于C++中的线程还是进程
- WMI检测进程创建事件-c++
- 算法问题:查找从堆栈中弹出的所有序列
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- c++多进程编写一个唯一的文件
- 如何在C++中将函数发送到另一个进程
- 为什么调用堆栈数组会导致内存泄漏
- 在Qt Creator中,如何在连接到正在运行的进程后查看控制台输出
- gdb错误:Backtrace已停止:上一帧与此帧相同(堆栈已损坏?)
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 有没有一种方法可以捕获进程中的堆栈溢出?C++Linux
- 如何在窗口上设置使用 CreateProcess 创建的新进程的主线程的堆栈大小?
- 在 Linux 平台上以 C/C++ 打印进程的所有线程堆栈跟踪
- 分叉进程的零星堆栈指针分段错误
- 如何获取进程中运行的所有线程的堆栈跟踪