Unix 与 coredump
coredump 是什么
当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成“核心转储”)。
我们可以认为 core dump 是“内存快照”,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时 dump 下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。
core dump 对于编程人员诊断和调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而 core dump 文件可以再现程序出错时的情景。
为什么会产生coredump文件
core产生的原因很多,比如过去一些Unix的版本不支持现代Linux上这种gdb直接附着到进程上进行调试的机制,需要先向进程发送终止信号,然后用工具阅读core文件。在Linux上,我们就可以使用kill向一个指定的进程发送信号或者使用gcore命令来使其主动出core并退出。
如果从浅层次的原因上来讲,出core意味着当前进程存在BUG,需要程序员修复。
从深层次的原因上讲,是当前进程触犯了某些OS层级的保护机制,逼迫OS向当前进程发送诸如SIGSEGV(即signal 11)之类的信号, 例如访问空指针或数组越界出core,实际上是触犯了OS的内存管理,访问了非当前进程的内存空间,OS需要通过出core来进行警示,这就好像一个人身体内存在病毒,免疫系统就会通过发热来警示,并导致人体发烧是一个道理(有意思的是,并不是每次数组越界都会出Core,这和OS的内存管理中虚拟页面分配大小和边界有关,即使不出core,也很有可能读到脏数据,引起后续程序行为紊乱,这是一种很难追查的BUG)。
coredump产生场景
上面说当程序运行过程中异常终止或崩溃时会发生 core dump,但还没说到什么具体的情景程序会发生异常终止或崩溃,例如我们使用 kill -9 命令杀死一个进程会发生 core dump 吗?实验证明是不能的,那么什么情况会产生呢?
Linux 中信号是一种异步事件处理的机制,每种信号对应有其默认的操作,你可以在 这里查看 Linux 系统提供的信号以及默认处理。默认操作主要包括忽略该信号(Ingore)、暂停进程(Stop)、终止进程(Terminate)、终止并发生core dump(core)等。如果我们信号均是采用默认操作,那么,以下列出几种信号,它们在发生时会产生 core dump:
Signal | Action | Comment |
---|---|---|
SIGTRAP | Core | Trace/breakpoint trap |
SIGSEGV | Core | Invalid memory reference |
SIGQUIT | Core | Quit from keyboard |
SIGILL | Core | Illegal Instruction |
SIGABRT | Core | Abort signal from abort |
当然不仅限于上面的几种信号。这就是为什么我们使用 Ctrl+z 来挂起一个进程或者 Ctrl+C 结束一个进程均不会产生 core dump,因为前者会向进程发出 SIGTSTP 信号,该信号的默认操作为暂停进程(Stop Process);后者会向进程发出SIGINT 信号,该信号默认操作为终止进程(Terminate Process)。同样上面提到的 kill -9 命令会发出 SIGKILL 命令,该命令默认为终止进程。而如果我们使用 Ctrl+\ 来终止一个进程,会向进程发出 SIGQUIT 信号,默认是会产生 core dump 的。还有其它情景会产生 core dump, 如:程序调用 abort() 函数、访存错误、非法指令等等。
造成程序coredump的原因有很多,这里总结一些比较常用的经验
内存访问越界
a) 由于使用错误的下标,导致数组访问越界。b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符。
c) 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
多线程程序使用了线程不安全的函数。
应该使用下面这些可重入的函数,它们很容易被用错:
asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n)ctermid_r(3s)
gethostent_r(3n) getservbyport_r(3n) ctime_r(3c)
getlogin_r(3c)getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n)
getspent_r(3c)fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c)
fgetspent_r(3c)getnetent_r(3n) gmtime_r(3c) gamma_r(3m)
getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3)getprotobyname_r(3n)
localtime_r(3c) getauclassnam_r(3)
etprotobynumber_r(3n)nis_sperror_r(3n) getauevent_r(3)
getprotoent_r(3n) rand_r(3c) getauevnam_r(3)getpwent_r(3c)
readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c)
getgrent_r(3c)getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c)
getrpcbyname_r(3n) ttyname_r(3c)getgrnam_r(3c) getrpcbynumber_r(3n)
gethostbyaddr_r(3n) getrpcent_r(3n)
- 多线程读写的数据未加锁保护。
对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成coredump
非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump。
5,堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
- coredump文件配置
1) 在终端中输入ulimit -c 如果结果为0,说明当程序崩溃时,系统并不能生成core dump。
2) 使用ulimit -c unlimited命令,开启core dump功能,并且不限制生成core dump文件的大小。如果需要限制,加数字限制即可。ulimit - c 1024
3) 默认情况下,core dump生成的文件名为core,而且就在程序当前目录下。新的core会覆盖已存在的core。通过修改/proc/sys/kernel/core_uses_pid文件,可以将进程的pid作为作为扩展名,生成的core文件格式为core.xxx,其中xxx即为pid
4) 通过修改/proc/sys/kernel/core_pattern可以控制core文件保存位置和文件格式。例如:将所有的core文件生成到/corefile目录下,文件名的格式为core-命令名-pid-时间戳. echo “/corefile/core-%e-%p-%t” > /proc/sys/kernel/core_pattern
当前系统配置:
/proc/sys/kernel/core_pattern
core文件所在目录:/opt/logs/logs/core/
- coredump如何排查
业务程序(以java为例)
排查方式可以参考《JDK core dump分析》。
总而言之,jmap、jstack 都可以直接分析 core,也可以把它转成 hprof 格式的文件,进行进一步地可视化分析。gdb $JAVA_HOME$/bin/java core-26492
/data/soft/jdk/bin/jstack /data/soft/jdk/bin/java core.26492
/data/soft/jdk/bin/jmap /data/soft/jdk/bin/java core.14652
/data/soft/jdk/bin/jmap -dump:format=b,file=dump.hprof /data/soft/jdk/bin/java core.14652
进入gdb上下文后再执行bt命令,可以看到进程退出时的线程栈。
代码块gdb /data/soft/jdk/bin/java core.11668
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7 Copyright (C) 2013
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-redhat-linux-gnu”. For bug
reporting instructions, please see:
http://www.gnu.org/software/gdb/bugs/… Reading symbols from
/data/soft/jdk1.8.0_141/bin/java…Missing separate debuginfo for
/data/soft/jdk1.8.0_141/bin/java Try: yum —enablerepo=’debug‘
install
/usr/lib/debug/.build-id/c9/0f19ee0af98c47ccaa7181853cfd14867bc931.debug
(no debugging symbols found)…done. [New LWP 14367] … [New LWP
14355] [New LWP 11685] [Thread debugging using libthread_db enabled]
Using host libthread_db library “/lib64/libthread_db.so.1”. Missing
separate debuginfo for
/data/soft/jdk1.8.0_141/jre/lib/amd64/server/libjvm.so Try: yum
—enablerepo=’debug‘ install /usr/lib/debug/.build-id/43/a2bc419314aa566cd5ba54779ae18b47022cad.debug
Missing separate debuginfo for
/data/soft/jdk1.8.0_141/jre/lib/amd64/libverify.so Try: yum
—enablerepo=’debug‘ install /usr/lib/debug/.build-id/b3/de2701fa3aef182d8e7b2db8d294a91af1eb7c.debug
Missing separate debuginfo for
/data/soft/jdk1.8.0_141/jre/lib/amd64/libmanagement.so Try: yum
—enablerepo=’debug‘ install /usr/lib/debug/.build-id/ee/7e746ef83f2c2fbe8ea0cb42706b63cd74de3f.debug
Missing separate debuginfo for
/data/soft/jdk1.8.0_141/jre/lib/amd64/libinstrument.so Try: yum
—enablerepo=’debug‘ install /usr/lib/debug/.build-id/13/552fe85b2af551ab806735bc5e5f2a5e00fbb1.debug
Core was generated by `/data/soft/jdk/bin/java
-Djava.util.logging.config.file=/data/home/aics/taskbot’. Program terminated with signal 6, Aborted.0 0x00007f50d652b1d7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56 56 return
INLINE_SYSCALL (tgkill, 3, pid, selftid, sig); (gdb) bt
0 0x00007f50d652b1d7 in __GI_raise (sig=sig@entry=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
1 0x00007f50d652c8c8 in __GI_abort () at abort.c:90
2 0x00007f50397417f4 in tensorflow::internal::LogMessageFatal::~LogMessageFatal() () from
/data/home/aics/taskbot-tomcat/temp/tensorflow_native_libraries-1545395060637-0/libtensorflow_framework.so
3 0x00007f5039721cf0 in tensorflow::monitoring::CollectionRegistry::Register(tensorflow::monitoring::AbstractMetricDef
const*, std::function
const&) () from
/data/home/aics/taskbot-tomcat/temp/tensorflow_native_libraries-1545395060637-0/libtensorflow_framework.so4 0x00007f501520951f in ?? () from /data/home/aics/taskbot-tomcat/temp/tensorflow_native_libraries-1545633543384-0/libtensorflow_jni.so
5 0x00007f50151334db in ?? () from /data/home/aics/taskbot-tomcat/temp/tensorflow_native_libraries-1545633543384-0/libtensorflow_jni.so
6 0x00007f50d6efd1e3 in call_init (env=0x7f50d00c4460, argv=0x7ffe7bc01488, argc=21, l=
) at dl-init.c:82 7 _dl_init (main_map=main_map@entry=0x7f50249fa900, argc=21, argv=0x7ffe7bc01488, env=0x7f50d00c4460) at dl-init.c:131
8 0x00007f50d6f018f6 in dl_open_worker (a=a@entry=0x7f503b2f7b98) at dl-open.c:560
9 0x00007f50d6efcff4 in _dl_catch_error (objname=objname@entry=0x7f503b2f7b88,
errstring=errstring@entry=0x7f503b2f7b90,
mallocedp=mallocedp@entry=0x7f503b2f7b80,
operate=operate@entry=0x7f50d6f01540,
args=args@entry=0x7f503b2f7b98) at dl-error.c:17710 0x00007f50d6f00feb in _dl_open (file=0x7f5024008550 “/data/home/aics/taskbot-tomcat/temp/tensorflow_native_libraries-1545633543384-0/libtensorflow_jni.so”,
mode=-2147483647, caller_dlopen=
, nsid=-2, argc=21,
argv=0x7ffe7bc01488, env=0x7f50d00c4460) at dl-open.c:65011 0x00007f50d68b8fbb in dlopen_doit (a=a@entry=0x7f503b2f7da0) at dlopen.c:66
12 0x00007f50d6efcff4 in _dl_catch_error (objname=0x7f5024ae73e0, errstring=0x7f5024ae73e8, mallocedp=0x7f5024ae73d8,
operate=0x7f50d68b8f60
, args=0x7f503b2f7da0) at
dl-error.c:17713 0x00007f50d68b95bd in _dlerror_run (operate=operate@entry=0x7f50d68b8f60
, args=args@entry=0x7f503b2f7da0) at dlerror.c:163
14 0x00007f50d68b9051 in __dlopen (file=
, mode= ) at dlopen.c:87 15 0x00007f50d5e2a24e in os::dll_load(char const, char, int) () from /data/soft/jdk1.8.0_141/jre/lib/amd64/server/libjvm.so
16 0x00007f50d5c2198c in JVM_LoadLibrary () from /data/soft/jdk1.8.0_141/jre/lib/amd64/server/libjvm.so
17 0x00007f50d4ac7df8 in Java_java_lang_ClassLoader_00024NativeLibrary_load () from
/data/soft/jdk1.8.0_141/jre/lib/amd64/libjava.so
非业务程序
原则上不建议随便升级容器内部的各种库,如gcc,glibc等,这种随机安装可能导致容器组件不稳定
短期应急方法
关闭相应组件,例如crond程序经常产生core,可以通过/etc/init.d/crond stop关闭。
长期处理方法
非业务程序的coredump,可以联系运维人员。
coredump截断问题
coredump截断分两种情况
第一种:通过ulimit的方法限制coredump生成的大小,比如通过ulimit -c命令限制coredump的大小。
但是这个命令无法限制容器内的coredump的大小。
被ulimit限制后生成截断的大小都是固定的。
第二种:没有限制coredump的大小,但是每次都是随机出现coredump截断的情况。
一般这种情况coredump的core文件本身就已经是完全乱了,导致存储coredump大小的数据也是不准确的。
这种情况下生成的coredump的大小是随机的,每次core大小都不固定。
怎么分辨是什么原因导致的截断
方法1:查看每次生成的core的大小,如果每次生成的coredump的大小都是固定的,那么说明是通过生成coredump的脚本文件限制的
处理方法:业务可以TT到SRE,绑定取消coredump限制。具体方法:取消coredump大小限制
方法2:可以查看生成coredump的Py脚本,查看在生成coredump的时候有没有大小限制。
方法3:如果每次生成的coredump的大小不固定。
这时可以查看tcmalloc等内存管理是不是异常。
有一个比较明显的现象是生成coredump的时间和 tcmalloc异常的时间点每次都是基本吻合,这时候可以找《hulk技术支持》确认即可。
这种情况下tcmalloc的问题修复了,那么coredump被截断的情况也就消失了
coredump截断的影响
在通过gdb调试core文件的时候会提示coredump的大小异常,无法进行gdb的调试。
Linux 支持的信号列表
一共有 64 种。
1 |
|