分段故障本身就是悬而未决

xeo*_*eor 16 c c++ linux

我今天在服务器上遇到了一些问题,现在我已经把它归结为它无法摆脱遇到段错误的进程.

在该过程发生seg-fault之后,该过程只是一直悬挂,而不是被杀死.

应该导致错误的测试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("%s\n", buf);
 return 1;
}
Run Code Online (Sandbox Code Playgroud)

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

在有问题的服务器上运行此(并按下输入1次)会导致它挂起.我也在另一台具有相同内核版本(和大多数相同的软件包)的服务器上运行它,它得到seg-fault然后退出.

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

糟糕的服务器

"\n", 1024)                     = 1
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
# It hangs here....
Run Code Online (Sandbox Code Playgroud)

工作服务器

"\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
Run Code Online (Sandbox Code Playgroud)

当进程挂起时(在它有segfaulted之后),这就是它的外观.

不能^它

root@server { ~ }# ./segfault

^C^C^C
Run Code Online (Sandbox Code Playgroud)

从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
Run Code Online (Sandbox Code Playgroud)

另一个有趣的事情是我无法附加strace一个悬挂的段错误过程.实际上这样做会让它被杀死.

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

ulimit -c 0是坐着ulimit -c,ulimit -H -culimit -S -c所有的显示值0

  • 内核版本: 3.10.0-229.14.1.el7.x86_64
  • 发行版: 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("%s\n", buf);
 return 1;
}
Run Code Online (Sandbox Code Playgroud)

挂了.输出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]
Run Code Online (Sandbox Code Playgroud)

但是,int main(void){*(volatile char*)0=0;}触发段错误的较小的c代码()确实会导致段错误并且没有挂起...

har*_*mic 3

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

为什么段错误会挂起?

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

但为什么要花这么长时间呢?一个可能的解释是,您用来创建段错误的方法导致进程拥有大量虚拟地址空间。

正如 MM 的评论中指出的,表达式 1<<31 的结果是 C 标准未定义的,因此很难说出传递给 malloc 的实际值是什么,但根据后续行为,我假设它是一个很大的数字。

请注意,要使 malloc 成功,您的系统中并不需要实际拥有这么多 RAM - 内核将扩展进程的虚拟大小,但只有当您的程序实际访问此 RAM 时才会分配实际 RAM。

我相信对 malloc 的调用会成功,或者至少会返回,因为您声明在按 Enter 键后会出现段错误,因此在调用 fgets 之后也会出现段错误。

无论如何,段错误都会导致内核执行核心转储。如果进程具有较大的虚拟大小,则可能需要很长时间,特别是如果内核决定转储所有页面,即使是那些从未被进程触及的页面。我不确定它是否会这样做,但如果确实如此,并且系统中没有足够的 RAM,则必须开始将页面换入内存或换出内存,以便将它们转储到核心转储。这会产生高 IO 负载,从而导致进程看起来无响应(并且整体系统性能会下降)。

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

如果您能够重现该行为,那么您可以检查:

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

即使 ulimit -c 为 0,为什么仍会创建核心转储?

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

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

对此有几种可能的解释。一方面,这取决于系统中可用 RAM 的数量。如果有足够的可用 RAM 并且系统不会被推送到交换区,那么大型进程的单个核心转储可能不会花费那么长时间,并且不会被视为挂起。

如果在最初的实验中,有多个进程处于这种状态,那么症状会比只有一个进程出现异常时的情况严重得多。

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

也有可能 yum 更新更新了 abrtd,但没有重新启动它,因此当您重新启动它时,新版本正在运行。