gpt4 book ai didi

c++ - 有没有办法在进程中捕获堆栈溢出? C++ Linux

转载 作者:行者123 更新时间:2023-12-01 14:47:34 24 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





How to use “sigaltstack” in signal handler program?

(2 个回答)


去年关闭。




我有以下代码,它进入无限递归并在耗尽分配给它的堆栈限制时触发段错误。我正在 try catch 此段错误并正常退出。但是,我无法在任何信号编号中捕获此段错误。

(客户正面临此问题,并希望针对此类用例提供解决方案。通过诸如“limit stacksize 128M”之类的方式增加堆栈大小使他的测试通过。但是,他要求正常退出而不是段错误。以下代码只是重现了实际问题,而不是实际算法的作用)。

任何帮助表示赞赏。如果我尝试捕捉信号的方式不正确,也请告诉我。编译: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 segfault\n";
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()SA_ONSTACK 标志作为 sigaction()

其次,根据堆栈溢出的“严重程度”(您的测试程序可能不会触发,但现实世界的程序可能会触发),作为“溢出影响操作”的内存访问(尝试)可能会以除 SIGSEGV 之外的其他信号结束。
您的示例“非特定地”捕获了所有信号,但这在实践中可能相当不足/相当困惑 - 您向您的应用程序发送 SIGUSR1 或 shell /终端在后台发送 SIGTTOU 绝对不表示堆栈溢出。
这意味着还有另一个问题 - 由于堆栈溢出而进行“堆栈外”内存访问时会出现哪些信号?你怎么知道你得到的特定信号是由于堆栈访问引起的?这个答案比第一眼更复杂:

  • 如果堆栈溢出“足够小”,可以想象它在保护页内(有效映射,但故意不可读),它将触发 SIGSEGV
  • 如果(没有使用保护页并且)访问的是未映射的内存区域,您将收到一个 SIGBUS
  • 即使某些 CPU 指令也可能对访问“无效内存地址 X”是否导致 SIGSEGVSIGBUS 产生影响(例如,在 x86 上,某些指令引发 #GP,而其他指令 #PF 的读取/写入地址相同 -内核可能将一个转换为 SIGBUS 另一个转换为 SIGSEGV )
  • 如果碰巧有其他内存映射发生在此访问的位置(例如,您有 char local_to_blow_stack[1ULL << 40]; memset(&local_to_blow_stack, 0, 1); )并且恰如其分地发生了其他有效的事情是“无论您的堆栈减去什么”),该访问实际上会正常工作。如果没有编译器为您创建“辅助”代码来识别此类访问,实际上有可能您已经破坏了堆栈,并且在最终到达触发信号的内存区域之前仍然进行了多次成功/非信令内存访问。
  • 除了堆栈访问之外,您可能会收到其他无效操作的这些信号。堆访问、内存映射文件/设备访问也可能导致相同的结果。

  • 因此,“仅捕获信号”,甚至“捕获可能因堆栈溢出而发生的所有信号”都是不够的。您需要在信号处理程序中对内存访问位置以及可能的操作/cpu 指令进行解码,以验证尝试的内存访问实际上是“堆栈访问越界”。线程可以检索自己的堆栈边界 - https://man7.org/linux/man-pages/man3/pthread_getattr_np.3.html 可用于此目的,至少在 Linux 上( _np 暗示“不可移植” - 这不能保证在所有系统上都可用,其他人可能有不同的接口(interface)检索此信息) - 但是...找到被访问的内存位置取决于信号和再次访问指令。通常(但不总是)它在 siginfo( si_addr)字段中。

    据我所知,究竟哪些信号在什么情况下填充了 si_addr,以及那里的地址是否是例如发出内存访问或尝试访问的内存位置的指令在某种程度上取决于系统和硬件(Linux 的行为可能与 Windows 或 MacOSX 不同,并且在 ARM 上与在 x86 上不同)因此您还需要验证“此 si_addr 中的 siginfo_t 位于发出信号的线程堆栈附近”,但也可能验证导致它的指令实际上是内存访问/ si_addr 可以“追溯到”出错的指令。那(找到错误指令的地址/程序计数器)......需要解码信号处理程序的另一个参数, ucontext_t......并且你在硬件/操作系统细节中很深很深[在这里递归无限]。

    在这一点上,我想终止; “简单”但不完美的解决方案只需要一个备用信号堆栈,以及通过 pthread_getattr_np() 检索当前堆栈边界的处理程序,以将 si_addr 与之进行比较。如果您或其他人的生活取决于正确答案,请记住以上内容。

    关于c++ - 有没有办法在进程中捕获堆栈溢出? C++ Linux,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62432847/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com