关于bash:如何在Linux出现分段错误时生成核心转储?

关于bash:如何在Linux出现分段错误时生成核心转储?

How to generate a core dump in Linux on a segmentation fault?

我在Linux中有一个进程出现分段错误。 我如何告诉它在失败时生成核心转储?


这取决于您使用的外壳。如果使用的是bash,则ulimit命令控制与程序执行有关的一些设置,例如是否应该转储core。如果您输入

1
ulimit -c unlimited

那么这将告诉bash它的程序可以转储任何大小的内核。您可以根据需要指定诸如52M的大小,而不是无限制的大小,但是实际上,这不是必需的,因为核心文件的大小可能永远不会成为您的问题。

在tcsh中,您可以输入

1
limit coredumpsize unlimited

如上所述,这里要问的真正问题是如何在未启用核心转储的系统上启用核心转储。在这里回答了这个问题。

如果您来这里是为了学习如何为挂起的进程生成核心转储,答案是

1
gcore <pid>

如果gcore在您的系统上不可用,则

1
kill -ABRT <pid>

不要使用kill -SEGV,因为这通常会调用信号处理程序,这使得诊断阻塞进程更加困难


要检查在哪里生成核心转储,请运行:

1
sysctl kernel.core_pattern

要么:

1
cat /proc/sys/kernel/core_pattern

其中%e是进程名称,而%t是系统时间。您可以在/etc/sysctl.conf中进行更改,然后通过sysctl -p重新加载。

如果未生成核心文件(通过以下方法进行测试:sleep 10 &killall -SIGSEGV sleep),请通过以下方式检查限制:ulimit -a

如果您的核心文件大小有限,请运行:

1
ulimit -c unlimited

使它不受限制。

然后再次测试,如果核心转储成功,则在分段错误指示之后您将看到"(核心转储)",如下所示:

Segmentation fault: 11 (core dumped)

另请参阅:核心转储-但核心文件不在当前目录中?

Ubuntu的

在Ubuntu中,核心转储由Apport处理,可以位于/var/crash/中。但是,在稳定版本中默认情况下禁用它。

有关更多详细信息,请检查:在Ubuntu哪里可以找到核心转储?

苹果系统

对于macOS,请参阅:如何在Mac OS X中生成核心转储?


最后,我所做的就是在崩溃之前将gdb附加到该进程,然后在出现段错误时执行了generate-core-file命令。强制生成核心转储。


也许您可以用这种方式做到这一点,该程序演示了如何捕获分段错误并将其封装到调试器中(这是AIX下使用的原始代码),并打印堆栈跟踪直到分段为止故障。对于Linux,您需要将sprintf变量更改为使用gdb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <stdarg.h>

static void signal_handler(int);
static void dumpstack(void);
static void cleanup(void);
void init_signals(void);
void panic(const char *, ...);

struct sigaction sigact;
char *progname;

int main(int argc, char **argv) {
    char *s;
    progname = *(argv);
    atexit(cleanup);
    init_signals();
    printf("About to seg fault by assigning zero to *s
"
);
    *s = 0;
    sigemptyset(&sigact.sa_mask);
    return 0;
}

void init_signals(void) {
    sigact.sa_handler = signal_handler;
    sigemptyset(&sigact.sa_mask);
    sigact.sa_flags = 0;
    sigaction(SIGINT, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGSEGV);
    sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGBUS);
    sigaction(SIGBUS, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGQUIT);
    sigaction(SIGQUIT, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGHUP);
    sigaction(SIGHUP, &sigact, (struct sigaction *)NULL);

    sigaddset(&sigact.sa_mask, SIGKILL);
    sigaction(SIGKILL, &sigact, (struct sigaction *)NULL);
}

static void signal_handler(int sig) {
    if (sig == SIGHUP) panic("FATAL: Program hanged up
"
);
    if (sig == SIGSEGV || sig == SIGBUS){
        dumpstack();
        panic("FATAL: %s Fault. Logged StackTrace
"
, (sig == SIGSEGV) ?"Segmentation" : ((sig == SIGBUS) ?"Bus" :"Unknown"));
    }
    if (sig == SIGQUIT) panic("QUIT signal ended program
"
);
    if (sig == SIGKILL) panic("KILL signal ended program
"
);
    if (sig == SIGINT) ;
}

void panic(const char *fmt, ...) {
    char buf[50];
    va_list argptr;
    va_start(argptr, fmt);
    vsprintf(buf, fmt, argptr);
    va_end(argptr);
    fprintf(stderr, buf);
    exit(-1);
}

static void dumpstack(void) {
    /* Got this routine from http://www.whitefang.com/unix/faq_toc.html
    ** Section 6.5. Modified to redirect to file to prevent clutter
    */
    /* This needs to be changed... */
    char dbx[160];

    sprintf(dbx,"echo 'where
detach' | dbx -a %d > %s.dump"
, getpid(), progname);
    /* Change the dbx to gdb */

    system(dbx);
    return;
}

void cleanup(void) {
    sigemptyset(&sigact.sa_mask);
    /* Do any cleaning up chores here */
}

您可能还必须添加一个参数来获取gdb来转储核心,如本博客此处所示。


还有更多因素可能会影响核心转储的生成。我遇到了这些:

  • 转储的目录必须可写。默认情况下,这是进程的当前目录,但是可以通过设置/proc/sys/kernel/core_pattern进行更改。
  • 在某些情况下,/proc/sys/fs/suid_dumpable中的内核值可能会阻止生成内核。

手册页中描述了更多可能阻止生成的情况-尝试man core


为了激活核心转储,请执行以下操作:

  • /etc/profile注释行:

    1
    # ulimit -S -c 0 > /dev/null 2>&1
  • /etc/security/limits.conf中,注释掉该行:

    1
    *               soft    core            0
  • 执行cmd limit coredumpsize unlimited并使用cmd limit进行检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    # limit coredumpsize unlimited
    # limit
    cputime      unlimited
    filesize     unlimited
    datasize     unlimited
    stacksize    10240 kbytes
    coredumpsize unlimited
    memoryuse    unlimited
    vmemoryuse   unlimited
    descriptors  1024
    memorylocked 32 kbytes
    maxproc      528383
    #
  • 要检查核心文件是否被写入,您可以使用cmd kill -s SEGV 终止相关进程(不需要,只要没有核心文件被写入,可以将其用作检查):

    1
    # kill -s SEGV <PID>
  • 一旦写入了核心文件,请确保在相关文件(1./2./3。)中再次停用coredump设置!


    对于Ubuntu 14.04

  • 检查核心转储是否启用:

    1
    ulimit -a
  • 其中一行应为:

    1
    core file size          (blocks, -c) unlimited
  • 如果不 :

    gedit ~/.bashrc并将ulimit -c unlimited添加到文件末尾并保存,然后重新运行终端。

  • 使用调试信息构建应用程序:

    在Makefile -O0 -g

  • 运行创建核心转储的应用程序(应在application_name文件附近创建名称为" core"的核心转储文件):

    1
    ./application_name
  • 在gdb下运行:

    1
    gdb application_name core

  • 默认情况下,您将获得一个核心文件。检查进程的当前目录是否可写,否则将不创建核心文件。


    最好使用系统调用setrlimit以编程方式打开核心转储。

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #include <sys/resource.h>

    bool enable_core_dump(){    
        struct rlimit corelim;

        corelim.rlim_cur = RLIM_INFINITY;
        corelim.rlim_max = RLIM_INFINITY;

        return (0 == setrlimit(RLIMIT_CORE, &corelim));
    }

    值得一提的是,如果您设置了systemd,则情况会有所不同。该设置通常将通过core_pattern sysctl值通过systemd-coredump(8)对核心文件进行管道传输。核心文件大小rlimit通常已经配置为"无限制"。

    然后可以使用coredumpctl(1)检索核心转储。

    核心转储等的存储由coredump.conf(5)配置。在coredumpctl手册页中有一些如何获取核心文件的示例,但总之,它看起来像这样:

    查找核心文件:

    1
    2
    [vps@phoenix]~$ coredumpctl list test_me | tail -1
    Sun 2019-01-20 11:17:33 CET   16163  1224  1224  11 present /home/vps/test_me

    获取核心文件:

    1
    [vps@phoenix]~$ coredumpctl -o test_me.core dump 16163


    推荐阅读