大家好,又见面了,我是你们的朋友全栈君。
在上一篇文章中描述了如何使用Valgrind工具检查内存相关问题,包括内存泄露、空指针使用、野指针使用、重复释放等问题。对于大多数情况下,Valgrind的作用性体现更多在于“内存泄露”检查,因为空指针、野指针的访问,会引发程序段错误(segment fault )而终止,此时可以借助linux系统的coredump文件结合gdb工具可以快速定位到问题发生位置。此外,程序崩溃引发系统记录coredump文件的原因是众多的,野指针、空指针访问只是其中一种,如堆栈溢出、内存越界等等都会引起coredump,利用好coredump文件,可以帮助我们解决实际项目中的异常问题。
coredump指的是应用程序因为各种原因导致异常终止时,操作系统将应用程序的异常发生时的状态信息记录为一个coredump的文件。一个coredump文件主要包含了应用程序的内存信息、寄存器状态、堆栈地址、函数调用上下文,开发人员通过分析这些信息,确定程序异常发生时的调用位置,如果是堆栈溢出,还需分析多层函数的调用信息。
通俗来说,coredump是操作系统记录应用程序非正常终止的信息,留给我们排查问题的依据。
coredump对于分析程序异常的作用是不言而喻的。以以前我们学习ARM 32位MCU为例(STM32),由于初学过程,代码质量参差不齐,经常引起硬件错误中断(Hard Fault)。面对这种情况,我们是束手无策的,一方面是程序发生错误后没有记录到有参考意义的信息(当然,可以通过仿真器实时获取堆栈信息,但对于实际产品不不现实);另一方面是问题复现概率比较低,复现条件不确定。linux系统是一个“考虑周全”的操作系统,应用程序发生异常,会记录一些关键的信息,已便于我们分析。coredump的意义就在于此。
应用程序发生异常时,会产生coredump文件记录,这些异常几乎都与内存相关,总结起来包括几点。
【1】内存访问越界
【2】访问非法指针
【3】堆栈溢出,分配大量局部变量、多重函数调用、较深的函数递归等可能导致堆栈溢出
【4】多线程访问
系统默认不开启coredump记录功能,执行"ulimit -c"
查看是否开启,返回0表示未开启coredump记录功能。
acuity@ubuntu:~$ ulimit -c
1024
可以使用“ulimit -c [size]”
命令指定记录coredump文件的大小,即是开启coredump记录。需要注意的是,单位为block
,1block=512bytes。
acuity@ubuntu:~$ ulimit -c 1024
万一程序比较糟糕,指定的coredump文件大小限制,导致文件记录不到或者缺失怎么办。此时,一劳永逸的办法就是不限制coredump文件大小;执行“ulimit -c unlimited”
设定,设置时需要root权限。
root@ubuntu:/home/acuity# ulimit -c unlimited
root@ubuntu:/home/acuity# ulimit -c
unlimited
以上方式都是在终端临时设置开启coredump记录功能,系统重启后失效,很显然这不是理想的方法。理想的方法是修改配置文件,使得系统一直开启coredump记录功能,至少在项目开发测试阶段是需要开启的。原则上,软件发布后也应该记录,出现问题后能够有追溯和分析问题的依据。
在"/etc/profile"
文件增加" ulimit -c unlimited "
。
注: ulimit 命令是一个设置资源限制的命令,除了coredump外,还可以设定其他资源限制
**除此之外,还有可以通过在代码中设定开启coredump。**然而一般不推荐该方式, 因为如果代码中没有增加开启功能,而应用程序又发生了异常,系统将无法记录coredump。建议在系统配置文件设置开启。
访问接口:
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim); /* 获取coredump 文件限制大 小 */
int setrlimit(int resource, const struct rlimit *rlim);/* 设置coredump 文件限制 大小 */
例子:
#include <sys/resource.h>
int main(int argc, char * argv [ ])
{
struct rlimit rlmt;
rlmt.rlim_cur = (rlim_t)1024;
rlmt.rlim_max = (rlim_t)1024;
if (-1 == setrlimit(RLIMIT_CORE, &rlmt))
{
perror("setrlimit error");
return -1;
}
}
coredump文件默认存储于应用程序执行目录下,文件名称为“core”。使用默认文件名称显然不是一个好的方式,如果有多个应用程序异常终止,将覆盖core文件;或者同一个应用程序,在异常终止后被守护进程重新启动运行,再次异常时导致core文件被覆盖。
修改"/proc/sys/kernel/core_uses_pid"
文件,可以将进程的id作为作为扩展名,文件内容为1表示使用扩展名,默认为0;使用进程id扩展名时,生成的core文件格式为"core.xxx"
,xxx为进程id。
修改"/proc/sys/kernel/core_pattern"
文件可以设置coredump文件的存储位置和更详细的文件名称。默认位置和名称信息如下:
root@ubuntu:/home/acuity# cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P %E
扩展字符含义:
%p - 扩展进程id(pid)
%P - 与%p作用相同
%u - 扩展用户id(uid)
%g - 扩展组id(gid)
%s - 扩展产生信号
%t - 扩展当前时间,从1970-01-0100:00:00开始的秒数
%h - 扩展主机名
%e - 扩展应用程序文件名称
%E - 扩展应用程序文件名称,包括文件绝对路径
coredump存储目录不变(存储于当前应用程序目录下),文件扩展名称增加应用程序文件名称、进程id、当前时间,这是实际场景常用的基本用法,能否适用绝对部分场合。可以用vi直接打开文件编辑,也可以使用echo
修改文件内容,前提都是必须以root权限修改。
“core.name.pit.time”
文件echo ./core.%e.%p.%t > /proc/sys/kernel/core_pattern
如需指定其他存储路径,可以修改路径部分。
“/home”
目录生成“core-name-pit-time”
文件echo /home/core-%e-%p-%t > /proc/sys/kernel/core_pattern
注: 指定某些目录,可以生成coredump文件,但文件内容为空,可能是权限问题??
编写一个“非法”程序,让系统记录coredump,结合gdb来分析过程;编译时需加入"-g"
,保留调试信息。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char * argv [ ])
{
int *p = NULL;
p = malloc(4);
if (p == NULL)
{
perror("malloc failed");
}
printf("address [0x%p]\r\n", p);
free(p);
free(p); /* 重复释放*/
return 0;
}
编译执行该程序,由于访问野指针,程序异常退出,将产生一个coredump文件。
root@ubuntu:/usr# file core.coredump.2046.1591860958
core.coredump.2046.1591860958: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from './coredump'
注: 有时候coredump只生成一个空文件,可以通“file”命令查看
gdb exe-file core-file
gdb后,键入“bt”
通过分析,出现异常的地方是第17行,翻阅源码,17行执行了重复释放动态申请内存的操作。
【1】详解coredump
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/128707.html原文链接:https://javaforall.cn