你知道CoreDump吗?

什么是core dump文件

Core dump文件是当一个进程在收到某些信号后终止时产生的文件,其中包含进程终止时刻进程内存的镜像。我们可以使用gdb从该镜像中观察进程终止时处于什么状态,用于追踪排查定位问题。

产生core dump的可能原因

  • 内存访问越界

    a) 由于使用错误的下标,导致数组访问越界

    b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符

    c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

  • 多线程程序使用了线程不安全的函数。

  • 多线程读写的数据未加锁保护。对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成core dump

  • 非法指针

a) 使用空指针

b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump.

  • 堆栈溢出.不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。

    如何判断一个文件是coredump文件?

    在类unix系统下,coredump文件本身主要的格式也是ELF格式,因此,我们可以通过readelf命令进行判断。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    root@ubuntu16-desktop:~# readelf -h core 
    ELF Header:
    Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
    Class: ELF64
    Data: 2's complement, little endian
    Version: 1 (current)
    OS/ABI: UNIX - System V
    ABI Version: 0
    Type: CORE (Core file) //这里可以看出来
    Machine: Advanced Micro Devices X86-64
    Version: 0x1
    Entry point address: 0x0
    Start of program headers: 64 (bytes into file)
    Start of section headers: 0 (bytes into file)
    Flags: 0x0
    Size of this header: 64 (bytes)
    Size of program headers: 56 (bytes)
    Number of program headers: 19
    Size of section headers: 0 (bytes)
    Number of section headers: 0
    Section header string table index: 0

或者通过file命令

1
2
root@ubuntu16-desktop:~# file core
core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './test'

如何分析core dump文件

使用调试器,如gdbhttps://www.gnu.org/software/gdb/
举个栗子:写了个空指针栗子

1
2
3
4
5
#include <stdio.h>
int main(){ int *p = NULL;
printf("hello world! \n");
printf("this will cause core dump p %d", *p);
}

执行该可执行文件会产生一个core文件(必须先通过参数打开该功能,要不即使有core dump,也不会产生core文件),如何分析呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
gdb ./test core 
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./test...done.
[New LWP 18173]
Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0000000000400584 in main () at main.c:4
4 printf("this will cause core dump p %d", *p);
(gdb) bt //一般就可以看到出错的代码是哪一句了,还可以打印出相应变量的数值,进行进一步分析。
#0 0x0000000000400584 in main () at main.c:4
(gdb)

对于结构复杂的程序,如涉及模板类及复杂的调用,gdb得出了出错位置,似乎这还不够,这时候要使用更为专业的工具——valgrind。
下载地址:http://valgrind.org/downloads/current.html

进入下载文件夹,分别执行(需要root权限,且必须按默认路径安装,否则有加载错误):

1
2
3
4
5
./configure

make

make install

安装成功后,使用类似如下命令启动程序:

valgrind --tool=memcheck --leak-check=full --track-origins=yes --leak-resolution=high --show-reachable=yes --log-file=memchecklog ./controller_test

其中,–log-file=memchecklog指记录日志文件,名字为memchecklog;–tool=memcheck和–leak-check=full用于内存检测。

可以得到类似的记录:

1
2
3
4
5
6
7
8
9
==23735==
==23735== Thread 1:
==23735== Invalid read of size 4
==23735== at 0x804F327: ResourceHandler<HBMessage>::~ResourceHandler() (ResourceHandler.cpp:48)
==23735== by 0x804FDBE: ConnectionManager<HBMessage>::~ConnectionManager() (ConnectionManager.cpp:74)
==23735== by 0×8057288: MainThread::~MainThread() (MainThread.cpp:73)
==23735== by 0x8077B2F: main (Main.cpp:177)
==23735== Address 0×0 is not stack’d, malloc’d or (recently) free’d
==23735==

可以看到说明为无法访问Address 0x0,明显为一处错误。

这样valgrind直接给出了出错原因以及程序中所有的内存调用、释放记录,非常智能,在得知错误原因的情况下,找出错误就效率高多了。再说一句,valgrind同时给出了程序的Memory Leak情况的报告,给出了new-delete对应情况,所有泄漏点位置给出,这一点在其他工具很难做到,十分好用。

与core dump有关的内核参数

  • kernel.core_pattern
    设置core文件保存位置或文件名,只有文件名时,则保存在应用程序运行的目录下

  • /proc/sys/kernel/core_pipe_limit
    定义了可以有多少个并发的崩溃程序可以通过管道模式传递给指定的core信息收集程序。如果超过了指定数,则后续的程序将不会处理,只在内核日志中做记录。0是个特殊的值,当设置为0时,不限制并行捕捉崩溃的进程,但不会等待用户程序搜集完毕方才回收/proc/pid目录(就是说,崩溃程序的相关信息可能随时被回收,搜集的信息可能不全)。

  • kernel.core_uses_pid
    Core文件的文件名是否添加应用程序pid做为扩展。0:不添加,1:添加

  • 设置Core文件大小上限

    1
    2
    3
    4
    5
    6
     # ulimit -c 0 // 0表示不产生core文件# ulimit -c 100 // 100表示设置core文件最大为100k,当然可以根据自己需要设置,注意设置单位是KB# ulimit -c unlimited // 不限制core文件大小
    使用上述命令设置core文件大小只对当前shell环境有效,系统重启或者退出当前shell设置就会失效,恢复默认设置。

    若想永久生效可以把该shell放到/etc/profile文件中,系统重新启动初始化环境时会执行其中的命令,该设置就会在全局范围内生效,达到永久生效的效果。也可以使用 source /etc/profile命令立即全局生效。

    # echo "unlimit -c unlimited" >> /etc/profile // 配置添加到/etc/profile中# source /etc/profile // 立即生效
  • 设置Core文件存储目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # echo "/var/core-dir/core-%e-%p-%t" >  /proc/sys/kernel/core_pattern
    该命令可以控制core文件保存位置和文件名格式。注意需要使用root权限执行,并且存储路径必须是绝对路径,不能是相对路径

    其中%e表示添加用户程序名称,%p表示添加程序进程PID,%t表示添加产生core文件时的时间戳,还有其他一些非常有用的格式,可以参阅CORE(5)文档。

    这样修改后系统重启后也会消失,同样也有永久生效的办法

    修改/etc/sysctl.conf文件在其中修改或者添加

    /etc/sysctl.conf
    kernel.core_pattern = /var/core-dir/core-%e-%p-%t
    然后执行下面命令配置立即生效,这样系统重启后配置依然有效

    # sysctl –p /etc/sysctl.conf

core dump文件产生流程

大概说一下从进程出现异常到生成core文件的流程,不会涉及太多Linux系统实现细节,具体细节可以参考相关文档和linux内核源码。

  • 进程运行时发生一个异常,比如非法内存地址访问(即段错误),相应硬件会上报该异常,CPU检测到该异常时会进入异常处理流程,包括保存当前上下文,跳转到对应的中断向量执行入口等

  • 在异常处理流程中如果判断该异常发生时是处于用户态,则该异常只会影响当前进程,此时向用户态进程发送相应的信号,如段错误就会发送SIGSEGV信号

  • 当用户态进程被调度时会检查pending的信号,如果发现pending的信号是SIG_KERNEL_COREDUMP_MASK中的一个,就会进入core文件内容收集存储流程,然后根据配置(core_pattern等)生成core文件。

处理docker容器进程Core Dump的方法

  • 使用ulimit -c和/proc/sys/kernel/core_pattern设置Core Dump文件大小限制和存储位置

  • 使用管道程序增强Core Dump文件处理能力

  • 使用管道程序和容器内进程结合的方式完成内核态转到用户态,在容器内处理Core文件存储

简单来说就是,因为有关core dump的设置容器并没有隔离,所以在主机上所有的设置,容器也是一样的。但是我们想针对容器进行特殊处理,那么我们可以使用linux的piping技术转储core文件。但是管道程序是作为内核线程在运行的,运行在内核态,并且在宿主机Initial Namespace中以root用户身份运行,不在任何容器内。我们想在容器内运行这么一个程序,可以使用socket activation技术,简单来说,就是由系统init进程(对于目前大多数linux系统来说是systemd)来为普通应用进程监听特定socket,此时应用进程并未启动,当有连接到达该socket后,由init进程接管该连接并跟进配置文件启动相应的应用进程,然后把连接传递给应用进程来处理,主要好处是当没有连接到达时,应用进程无需常驻后台空跑耗费系统资源。非常适合像Core Dump这种低频服务。我们可以设置一个unix socket来把管道程序的文件描述符传递到容器内进程,完成传递后, 管道程序就可以退出,由容器内进程处理core文件的存储。

参考

小米运维https://blog.csdn.net/pengzhouzhou/article/details/88313891
https://www.cnblogs.com/bodhitree/p/5850212.html

-------------本文结束感谢您的阅读-------------