如何使用C以编程方式回溯分叉子进程的崩溃

问题描述 投票:0回答:1

是否有可能使用C / C ++代码回溯Linux中子进程崩溃的位置?我想做的是以下内容:

  1. fork一个新的子进程并检索它的PID
  2. 等待forked子进程崩溃...可能使用SIGCHLD的信号处理程序,或者使用waitpid()/ waitid()
  3. 在崩溃的位置检索子堆栈的跟踪

当附加的进程崩溃时,这将使父进程的行为类似于调试器。您可以假设子进程使用调试符号进行编译,父进程具有root权限。

实现此类功能的最简单方法是什么?

c linux process fork backtrace
1个回答
2
投票

在Linux中使用作为GNU C库一部分提供的libSegFault库要简单得多。在我的系统上,它安装在/lib/x86_64-linux-gnu/libSegFault.so中。

您需要做的就是将SEGFAULT_SIGNALS环境变量设置为all(这样您就可以捕获库支持的所有崩溃原因),可选择SEGFAULT_OUTPUT_NAME指向堆栈跟踪写入的文件(默认为标准错误),LD_PRELOAD为指向segfault库。只要该过程不修改这些环境变量,它们也适用于所有子进程。

例如,如果./yourprog是一个让孩子崩溃的程序,并且你想要堆栈跟踪到./yourprog.stacktrace,那么运行

SEGFAULT_SIGNALS=all \
SEGFAULT_OUTPUT_NAME=./yourprog.stacktrace \
LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so \
  ./yourprog

或全部在一行中没有反斜杠(\)。

唯一的缺点是每次崩溃都会覆盖现有文件,因此您只会看到最新的文件。如果你安装了/proc,那么崩溃转储包括一个回溯和崩溃时的进程的内存映射。


如果你坚持在你自己的C程序中这样做,我建议你先看看libSegFault sources

关键是,堆栈跟踪必须由进程本身转储;父母无法访问它。为此,您可以使用以下方法将代码注入子进程: LD_PRELOAD环境变量(这是Linux中的动态链接器控制变量之一)。 (请注意,堆栈跟踪等是在信号处理程序上下文中完成的,因此只应使用异步信号安全函数。)

例如,父进程可以创建管道,并在执行目标进程之前将其写入端移动到子进程中的特定描述符,并在LD_PRELOAD中使用帮助程序预加载库路径。

辅助预载库插入signal()sigaction(),可能还有sigprocmask()sigwait()sigwaitinfo()pthread_sigmask(),以确保在传递此类信号时执行辅助程序库崩溃转储信号处理程序(SIGSEGVSIGBUSSIGILL,可能是SIGTRAP)。信号处理程序执行堆栈转储(并打印/ proc / PID / maps),然后将信号处理设置为默认值,并重新引发信号(使用raise())。

从本质上讲,它归结为与上面的libSegFault相同,除了你自己的C代码。


如果您不想将代码注入子进程,或者管理信号处理程序太复杂,则可以使用ptrace

当tracee被一个信号(SIGKILL除外)杀死时,接收信号的线程首先停止(“signal-delivery-stop”),因此跟踪器可以检查其堆栈(以及tracee的内存映射),然后再让子进程继续/死亡。

在实践中,ptracing更具侵入性,因为有许多事件会导致跟踪线程停止。对于多线程进程而言,它比LD_PRELOAD方法复杂得多,因为ptrace可以控制tracee中的各个线程;还有更多细节要做对。

© www.soinside.com 2019 - 2024. All rights reserved.