分段故障本身就悬而未决

Segmentation fault itself is hanging

本文关键字:悬而未决 故障 分段      更新时间:2023-10-16

我今天的服务器出现了一些问题,现在我已经将其归结为无法摆脱出现segfault的进程。

在进程出现seg故障后,进程只是保持挂起状态,而不是被终止。

应该的测试会导致错误Segmentation fault (core dumped)

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
 char *buf;
 buf = malloc(1<<31);
 fgets(buf, 1024, stdin);
 printf("%sn", buf);
 return 1;
}

使用gcc segfault.c -o segfault && chmod +x segfault编译和设置权限。

在有问题的服务器上运行此操作(并按enter键1次)会导致它挂起。我还在另一台具有相同内核版本(以及大多数相同包)的服务器上运行了这个程序,它出现了seg错误,然后退出。

以下是在两台服务器上运行strace ./segfault之后的最后几行。

错误的服务器

"n", 1024)                     = 1
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
# It hangs here....

工作服务器

"n", 1024)                     = 1
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)
root@server { ~ }# echo $?
139

当进程挂起时(在出现分段故障之后),它就是这样的。

无法^c它

root@server { ~ }# ./segfault
^C^C^C

从ps aux进入

root 22944 0.0 0.0 69700 444 pts/18 S+ 15:39 0:00 ./segfault

cat/proc/22944/stack

[<ffffffff81223ca8>] do_coredump+0x978/0xb10
[<ffffffff810850c7>] get_signal_to_deliver+0x1c7/0x6d0
[<ffffffff81013407>] do_signal+0x57/0x6c0
[<ffffffff81013ad9>] do_notify_resume+0x69/0xb0
[<ffffffff8160bbfc>] retint_signal+0x48/0x8c
[<ffffffffffffffff>] 0xffffffffffffffff

另一件有趣的事情是,我无法将strace附加到挂起的segfault进程。这样做实际上会让它被杀死。

root@server { ~ }# strace -p 1234
Process 1234 attached
+++ killed by SIGSEGV (core dumped) +++

ulimit -c 0是sat,ulimit -culimit -H -culimit -S -c都显示值0

  • 内核版本:3.10.0-229.14.1.el7.x86_64
  • Distro版本:Red Hat Enterprise Linux Server release 7.1 (Maipo)
  • 在vmware中运行

服务器在其他所有方面都正常工作。

更新关闭abrt(systemctl stop abrtd.service)解决了堆芯转储后进程已挂起的问题,并创建了新的堆芯转储进程。再次启动abrt并没有使问题再次出现。

更新2016-01-26我们遇到了一个看起来相似但不完全相同的问题。用于测试的初始代码:

#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
 char *buf;
 buf = malloc(1<<31);
 fgets(buf, 1024, stdin);
 printf("%sn", buf);
 return 1;
}

正在上吊。cat /proc/<pid>/maps的输出为

00400000-00401000 r-xp 00000000 fd:00 13143328                           /root/segfault
00600000-00601000 r--p 00000000 fd:00 13143328                           /root/segfault
00601000-00602000 rw-p 00001000 fd:00 13143328                           /root/segfault
7f6c08000000-7f6c08021000 rw-p 00000000 00:00 0
7f6c08021000-7f6c0c000000 ---p 00000000 00:00 0
7f6c0fd5b000-7f6c0ff11000 r-xp 00000000 fd:00 14284                      /usr/lib64/libc-2.17.so
7f6c0ff11000-7f6c10111000 ---p 001b6000 fd:00 14284                      /usr/lib64/libc-2.17.so
7f6c10111000-7f6c10115000 r--p 001b6000 fd:00 14284                      /usr/lib64/libc-2.17.so
7f6c10115000-7f6c10117000 rw-p 001ba000 fd:00 14284                      /usr/lib64/libc-2.17.so
7f6c10117000-7f6c1011c000 rw-p 00000000 00:00 0
7f6c1011c000-7f6c1013d000 r-xp 00000000 fd:00 14274                      /usr/lib64/ld-2.17.so
7f6c10330000-7f6c10333000 rw-p 00000000 00:00 0
7f6c1033b000-7f6c1033d000 rw-p 00000000 00:00 0
7f6c1033d000-7f6c1033e000 r--p 00021000 fd:00 14274                      /usr/lib64/ld-2.17.so
7f6c1033e000-7f6c1033f000 rw-p 00022000 fd:00 14274                      /usr/lib64/ld-2.17.so
7f6c1033f000-7f6c10340000 rw-p 00000000 00:00 0
7ffc13b5b000-7ffc13b7c000 rw-p 00000000 00:00 0                          [stack]
7ffc13bad000-7ffc13baf000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

然而,触发segfault的较小c代码(int main(void){*(volatile char*)0=0;})确实导致了segfault,并且没有挂起。。。

警告-此答案包含基于手头不完整信息的许多假设。希望它仍然有用!

为什么segfault似乎挂起了

如堆栈跟踪所示,内核正忙于创建崩溃进程的核心转储。

但为什么要花这么长时间?一种可能的解释是,您用于创建segfault的方法导致进程具有巨大的虚拟地址空间。

如M.M.的评论中所指出的,表达式1<lt;31是C标准未定义的,所以很难说传递给malloc的实际值是多少,但根据随后的行为,我假设它是一个很大的数字。

请注意,malloc要想成功,您的系统中实际上没有必要有这么多RAM——内核会扩展进程的虚拟大小,但只有当您的程序真正访问这个RAM时,才会分配实际的RAM。

我相信对malloc的调用成功了,或者至少返回了,因为您在按enter键后声明它是segfault,所以在调用fgets之后也是如此。

在任何情况下,segfault都会导致内核执行内核转储。如果进程的虚拟大小很大,则可能需要很长时间,特别是如果内核决定转储所有页面,即使是进程从未接触过的页面。我不确定它是否会这样做,但如果真的这样做了,如果系统中没有足够的RAM,它将不得不开始在内存中交换页面,以便将它们转储到核心转储。这将产生高IO负载,这可能导致进程看起来没有响应(并且整个系统性能将降低)。

您可以通过查找abrtd转储目录(可能是/var/tmp/abrt,或检查/etc/abrt/abrt.conf)来验证其中的一些内容,在该目录中可以找到已创建的核心转储(或部分核心转储)。

如果你能够再现行为,那么你可以检查:

  • /proc/[pid]/maps查看进程的地址空间映射,看看它是否真的很大
  • 使用类似vmstat的工具来查看系统是否正在交换、正在进行的I/O量以及经历了多少IO等待状态
  • 如果您运行了sar,那么即使在重新启动abrtd之前,您也可以看到类似的信息

为什么要创建一个核心转储,尽管ulimit-c是0

根据这个错误报告,无论ulimit设置如何,abrtd都会触发核心转储的收集。

为什么当arbtd再次启动时,这种情况没有再次发生

对此有几种可能的解释。首先,这将取决于系统中可用RAM的数量。如果有足够的空闲RAM并且系统没有被强制交换,那么大型进程的单核转储可能不会花那么长时间,也不会被认为是挂起的。

如果在你最初的实验中,你有几个过程处于这种状态,那么症状会比只让一个过程行为不端的情况严重得多。

另一种可能性是abrtd的配置已经更改,但服务尚未重新加载,因此当您重新启动它时,它开始使用新的配置,可能会改变它的行为。

也有可能是百胜更新更新了abrtd,但没有重新启动它,所以当您重新启动它时,新版本正在运行。