调试coredump步骤(coredump原理)

2022-07-28 17:36:33 浏览数 (2)

大家好,又见面了,我是你们的朋友全栈君。

文章目录

  • 1 前言
  • 2 coredump
    • 2.1 什么是coredump
    • 2.2 coredump意义
    • 2.3 coredump产生的场景
    • 2.2 开启coredump
    • 2.3 coredump存储位置与命名
  • 3 使用coredump
  • 4 参考文章

1 前言

  在上一篇文章中描述了如何使用Valgrind工具检查内存相关问题,包括内存泄露、空指针使用、野指针使用、重复释放等问题。对于大多数情况下,Valgrind的作用性体现更多在于“内存泄露”检查,因为空指针、野指针的访问,会引发程序段错误(segment fault )而终止,此时可以借助linux系统的coredump文件结合gdb工具可以快速定位到问题发生位置。此外,程序崩溃引发系统记录coredump文件的原因是众多的,野指针、空指针访问只是其中一种,如堆栈溢出、内存越界等等都会引起coredump,利用好coredump文件,可以帮助我们解决实际项目中的异常问题。

2 coredump

2.1 什么是coredump

   coredump指的是应用程序因为各种原因导致异常终止时,操作系统将应用程序的异常发生时的状态信息记录为一个coredump的文件。一个coredump文件主要包含了应用程序的内存信息、寄存器状态、堆栈地址、函数调用上下文,开发人员通过分析这些信息,确定程序异常发生时的调用位置,如果是堆栈溢出,还需分析多层函数的调用信息。

  通俗来说,coredump是操作系统记录应用程序非正常终止的信息,留给我们排查问题的依据。

2.2 coredump意义

  coredump对于分析程序异常的作用是不言而喻的。以以前我们学习ARM 32位MCU为例(STM32),由于初学过程,代码质量参差不齐,经常引起硬件错误中断(Hard Fault)。面对这种情况,我们是束手无策的,一方面是程序发生错误后没有记录到有参考意义的信息(当然,可以通过仿真器实时获取堆栈信息,但对于实际产品不不现实);另一方面是问题复现概率比较低,复现条件不确定。linux系统是一个“考虑周全”的操作系统,应用程序发生异常,会记录一些关键的信息,已便于我们分析。coredump的意义就在于此。

  • 根据记录信息分析程序异常的原因
  • 根据记录信息反推出现问题的条件,复现问题来验证

2.3 coredump产生的场景

  应用程序发生异常时,会产生coredump文件记录,这些异常几乎都与内存相关,总结起来包括几点。

【1】内存访问越界

  • 数组下标越界
  • 超出动态(malloc/new)内存申请范围
  • 字符串没有结束符,一些函数依赖于字符串结束符,如 strcpy、strcmp、sprintf

【2】访问非法指针

  • 空指针(未申请内存)
  • 野指针(已释放内存)
  • 重复释放指针(内存)
  • 指针强制转换,指针强制转换需特别谨慎,可能因为对齐、起始地址等问题引起内存访问错误

【3】堆栈溢出,分配大量局部变量、多重函数调用、较深的函数递归等可能导致堆栈溢出

【4】多线程访问

  • 调用不可重入函数
  • 共享数据未互斥访问

2.2 开启coredump

  系统默认不开启coredump记录功能,执行"ulimit -c"查看是否开启,返回0表示未开启coredump记录功能。

  • 查看是否记录coredump
代码语言:javascript复制
acuity@ubuntu:~$ ulimit -c
1024

  可以使用“ulimit -c [size]”命令指定记录coredump文件的大小,即是开启coredump记录。需要注意的是,单位为block,1block=512bytes。

  • 开启coredump
代码语言:javascript复制
acuity@ubuntu:~$ ulimit -c 1024

  万一程序比较糟糕,指定的coredump文件大小限制,导致文件记录不到或者缺失怎么办。此时,一劳永逸的办法就是不限制coredump文件大小;执行“ulimit -c unlimited”设定,设置时需要root权限。

  • 不限制coredump文件大小
代码语言:javascript复制
root@ubuntu:/home/acuity# ulimit -c unlimited
root@ubuntu:/home/acuity# ulimit -c 
unlimited

  以上方式都是在终端临时设置开启coredump记录功能,系统重启后失效,很显然这不是理想的方法。理想的方法是修改配置文件,使得系统一直开启coredump记录功能,至少在项目开发测试阶段是需要开启的。原则上,软件发布后也应该记录,出现问题后能够有追溯和分析问题的依据。

  • 通过配置文件使能

  在"/etc/profile"文件增加" ulimit -c unlimited "

注: ulimit 命令是一个设置资源限制的命令,除了coredump外,还可以设定其他资源限制

  • -a:查看当前资源限制信息
  • -c <core最大值>:设定core文件的最大值,单位为块(block)
  • -d <数据节段大小>:进程数据段最大值,单位为KB
  • -f <文件大小>:进程可创建最大文件值,单位为块(block)
  • -H:设置资源的硬性限制,设置后不可更改
  • -l <内存大小>: 可加锁内存大小,单位 为KB
  • -m <内存大小>:指定可使用内存的上限,单位为KB
  • -n <文件数目>:进程最大可打开的文件数(文件描述符数目)
  • -p <缓冲区大小>:管道缓冲区的大小,单位为KB
  • -s <堆栈大小>:线程最大堆栈大小,单位为KB
  • -S:设置资源的弹性限制,不可超过硬性资源限制
  • -t <cpu时间>:cpu最大占用时间,单位为秒
  • -u <进程数目>:用户可创建的最大进程数
  • -v <虚拟内存大小>:进程最大可用虚拟内存,单位为KB

  **除此之外,还有可以通过在代码中设定开启coredump。**然而一般不推荐该方式, 因为如果代码中没有增加开启功能,而应用程序又发生了异常,系统将无法记录coredump。建议在系统配置文件设置开启。

访问接口:

代码语言:javascript复制
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);	/* 获取coredump 文件限制大 小 */
int setrlimit(int resource, const struct rlimit *rlim);/* 设置coredump 文件限制 大小 */

例子:

代码语言:javascript复制
#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; 
    }   
}

2.3 coredump存储位置与命名

  coredump文件默认存储于应用程序执行目录下,文件名称为“core”。使用默认文件名称显然不是一个好的方式,如果有多个应用程序异常终止,将覆盖core文件;或者同一个应用程序,在异常终止后被守护进程重新启动运行,再次异常时导致core文件被覆盖。

  • 文件名称带进程id(PID)

  修改"/proc/sys/kernel/core_uses_pid"文件,可以将进程的id作为作为扩展名,文件内容为1表示使用扩展名,默认为0;使用进程id扩展名时,生成的core文件格式为"core.xxx",xxx为进程id。

  • 更详细的名称以及存储位置

  修改"/proc/sys/kernel/core_pattern"文件可以设置coredump文件的存储位置和更详细的文件名称。默认位置和名称信息如下:

代码语言:javascript复制
root@ubuntu:/home/acuity# cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c %d %P %E

  扩展字符含义:

代码语言:javascript复制
%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”文件
代码语言:javascript复制
echo ./core.%e.%p.%t > /proc/sys/kernel/core_pattern

  如需指定其他存储路径,可以修改路径部分。

  • “/home”目录生成“core-name-pit-time”文件
代码语言:javascript复制
echo /home/core-%e-%p-%t > /proc/sys/kernel/core_pattern

注: 指定某些目录,可以生成coredump文件,但文件内容为空,可能是权限问题??

3 使用coredump

  编写一个“非法”程序,让系统记录coredump,结合gdb来分析过程;编译时需加入"-g",保留调试信息。

代码语言:javascript复制
#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]rn", p);
	
	free(p);	
	free(p);	/* 重复释放*/
	
    return 0;
}

  编译执行该程序,由于访问野指针,程序异常退出,将产生一个coredump文件。

  • 查看coredump文件
代码语言:javascript复制
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 调试命令
代码语言:javascript复制
gdb exe-file core-file
  • 查看coredump信息
代码语言:javascript复制
gdb后,键入“bt”
  • 执行结果

  通过分析,出现异常的地方是第17行,翻阅源码,17行执行了重复释放动态申请内存的操作。

4 参考文章

【1】详解coredump

【2】Linux上Core Dump文件的形成和分析

【3】由coreDump引发的一次探讨

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/128707.html原文链接:https://javaforall.cn

0 人点赞