欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Core Dump核心转储

程序员文章站 2022-06-16 15:45:20
...

核心转储(core dump),在汉语中有时戏称为吐核,是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。这种信息往往用于调试。


概述编辑
在UNIX系统中,常将“主内存”(main memory) 称为核心(core),因为在使用半导体作为内存材料之前,便是使用核心(core)。而核心映像(core image) 就是 “进程”(process)执行当时的内存内容。当进程发生错误或收到“信号”(signal) 而终止执行时,系统会将核心映像写入一个文件,以作为调试之用,这就是所谓的核心转储(core dump)。
有时程序并未经过彻底测试,这使得它在执行的时候一不小心就会找到破坏。这可能会导致核心转储(core dump)。幸好,现行的UNIX系统极少会面临这样的问题。即使遇到,程序员可以通过核心映像(core image)调试程序来找到错误原因 [1] 

核心转储背景

编辑
核心文件一词来源于磁芯内存(core memory),1950-1970年代的主要的随机存取存储介质。

核心转储使用

编辑
核心文件通常在系统收到特定的信号时由操作系统生成。信号可以由程序执行过程中的异常触发,也可以由外部程序发送。动作的结果一般是生成一个某个进程的内存转储的文件,文件包含了此进程当前的运行堆栈信息。有时程序并未经过彻底测试,这使得它在执行的时候一不小心就会找到破坏。这可能会导致核心转储(core dump)。现在的UNIX系统极少会面临这样的问题。即使遇到,程序员可以通过核心映像调试程序来找到错误原因。

核心转储分析

编辑
程序自身产生的coredump文件一般可以用来分析程序运行到哪里出错了。
Linux平台常用的coredump文件分析工具是gdb;Solaris平台用pstack和pflags;Windows平台用userdump和windbg。
外部程序触发的dump一般用来分析进程的运行情况,比如分析内存使用/线程状态等。
Solaris的常用内存分析工具umem就是需要先通过gcore pid得到coredump的文件然后继续分析内存情况。
C/C++程序员遇到的比较常见的一个问题, 就是自己编写的代码, 在运行过程中出现了意想不到的核心转储。程序发生核心转储的原因是多方面的, 不同的核心转储问题有着不同的解决办法, 同时, 不同的核心转储问题解决的难易程度也存在很大的区别, 有些在短短几秒钟内就可以定位问题, 但是也有一些可能需要花费数天时间才能解决, 这种问题是对软件开发人员的极大的挑战。笔者从事C/C++语言的软件开发工作多年, 前后解决了许多此类问题, 久而久之积累了一定的经验, 现把常见程序核心转储总结一下, 供软件开发人员共飨。
1.无效指针引起的程序核心转储这种情况是一种最常见的核心转储, 大致可以有4 种原因导致程序出现异常:
(1) 对空指针进行了操作。
(2) 对一个未初始化的指针进行了操作。
(3) 对一个已经调用delete 释放了内存的指针再次调用了
delete 去重复释放(谁让你不在第一次delete 后, 将指针赋值为NULL 呢)。
(4) 多线程访问全局变量, 导致内存值异常而程序核心转储。
此类问题通常是代码编写时的疏漏造成的, 属于低级故障, 也比较容易解决, 用调试工具调试一下产生的core 文件,对照代码定位问题出现的原因,10 分钟就可以搞定。
2. 指针越界引起的程序核心转储
这种情况属于一种隐藏比较深的核心转储, 比较难以解决。遇到这种问题时, 用调试工具调试这个core 文件, 尽管也能定位到代码行, 但是从对应行的代码看, 可能这行代码本身并没有什么问题, 它只是一个“被陷害者”。这种核心转储很难发现, 解决起来难度较大。根据笔者的经验, 这种核心转储很可能是其他代码处理过程中的内存越界造成的, 通常由以下两个因素引起。第一个因素:核心转储所在的代码行是一个很简单的操作, 例如赋值语句, “这怎么可能出错呢?” 注释掉该语句运行程序, 核心转储又发生在下一行代码上。此时,相应代码行的操作很可能是对某个全局变量B 的操作, 在这种情况下, 需要将视线转移到该全局变量的定义行代码, 仔细看看该全局变量前后附近定义的变量A,C 因为操作系统不同, 变量位置也不同,有的需要关注B 变量前面定义的变量A, 有的需要关注B 变量后面定义的变量C, 仔细搜索代码, 看看对A,C 变量的处理有没有可能导致内存越界的地方, 很可能就是因为对A,C 操作出现内存越界导致B 变量的操作受到伤害, B 够背运吧。
第二因素: 核心转储的位置内存变量的值莫名其妙, 出现
了异常的值。此时, 需要仔细分析代码和处理流程了。首先排查本函数的代码处理是否有问题, 重点关注memcpy、strstr、sprintf、strcpy 和strcat 等极易出现问题的代码行, 如果确认本函数处理没有问题, 那么就需要根据流程来仔细走查代码, 在这种情况下, 最需要的是耐心和信心。对于这类问题, 肯定是代码走到了某个特殊的逻辑里面, 代码处理缺少必要的保护而引起的, 出现一次, 没有足够的日志记录流程, 很难分析, 从core 文件的内存变量的值也无法定位问题原因。但是, 如果再次出现, 那么就具有比较大的参考价值了, 前后两次的core文件内存变量必然存在某种共性, 需要根据这个特征来分析并复现故障了。笔者曾经遇到对一个未初始化的缓冲区A 做字符串操作strstr (此时它并不会核心转储), 但是当流程走了很多之后走到另一个对变量B 的操作时, 出现了核心转储; 更有甚者, 模块内一个链表算法出现了失误, 导致指针越界。
3. 操作系统相关的特殊性造成的程序核心转储
初学者对于这种情况, 必然让人备感莫名其妙的, “这么简单而又规范的代码, 怎么会出这种问题?” 这种问题与2 的区别在于, 问题很容易复现, 可能程序一运行就核心转储。尽管对这种核心转储很不解, 但应该相信: 越容易出现的问题,越容易解决。就像作为程序员编译一个程序, 一下子出现了几百个编译错误, 根本不用担心, 很可能就是某一行代码多了几个字符, 当把这些代码删去再编译, 几百个编译错误全都消失了。
常遇到的此类问题有两种情况。
第一种情况: 字节对齐方式引起的程序核心转储。可能有两个原因: 其一, 合作伙伴的模块与自身模块所定义的结构体的字节对齐方式不同, 而导致程序出现核心转储; 其二, 在代码中, 把引用到的别的模块的头文件包含到自身文件中的字节对齐方式语法声明的中间了, 结果导致字节对齐方式出现了变化。此类问题不是很常见, 不过一旦出现, 往往让人觉得很蹊跷。其实此类问题从源头上解决应该还是比较简单的, 关键在于一个良好的习惯, 如果在定义接口消息的时候, 多花点时间规整结构体的字段定义, 把它往4 字节的倍数上靠即可, 应该没必要处处节省那么点内存。第二种情况: 程序核心转储情况, 是与程序编译时的链接参数不正确而导致程序运行在反复操作大内存时出现核心转储。经过反复的代码删减、编译和运行的试验, 最终发现问题规律, 于是怀疑和操作系统或者编译有关, 最终解决了这个问题。程序发生核心转储的根本原因还是程序员自己进行程序设计时的编码失误造成的, 这种代码失误绝大多数都是因为没有严格遵守相应的代码编写规范, 所以, 要从根本上杜绝或者减少程序核心转储现象的发生, 还是要从严格遵守代码编写规范来做起 [2] 
linux下core dump【总结】

1、前言

  一直在从事linux下后台开发,经常与core文件打交道。还记得刚开始从事linux下开发时,程序突然崩溃了,也没有任何日志。我不知所措,同事叫我看看core,我却问什么是core,怎么看。同事鄙视的眼神,我依然在目。后来学会了从core文件中分析原因,通过gdb看出程序挂再哪里,分析前后的变量,找出问题的原因。当时就觉得很神奇,core文件是怎么产生的呢?难道系统会自动产生,可是我在自己的linux系统上面写个非法程序测试,并没有产生core问题?这又是怎么回事呢?今天在ngnix的源码时候,发现可以在程序中设置core dump,又是怎么回事呢?在公司发现生成的core文件都带有进程名称、进程ID、和时间,这又是怎么做到的呢?今天带着这些疑问来说说core文件是如何生成,如何配置。

2、基本概念

   当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成“核心转储”)。我们可以认为 core dump 是“内存快照”,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时 dump 下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。core dump 对于编程人员诊断和调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而 core dump 文件可以再现程序出错时的情景。

3、开启core dump

  可以使用命令ulimit开启,也可以在程序中通过setrlimit系统调用开启。

Core Dump核心转储

程序中开启core dump,通过如下API可以查看和设置RLIMIT_CORE

#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);

参考程序如下所示:

Core Dump核心转储
#include <unistd.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>
#define CORE_SIZE   1024 * 1024 * 500
int main()
{
    struct rlimit rlmt;
    if (getrlimit(RLIMIT_CORE, &rlmt) == -1) {
        return -1; 
    }   
    printf("Before set rlimit CORE dump current is:%d, max is:%d\n", (int)rlmt.rlim_cur, (int)rlmt.rlim_max);

    rlmt.rlim_cur = (rlim_t)CORE_SIZE;
    rlmt.rlim_max  = (rlim_t)CORE_SIZE;

    if (setrlimit(RLIMIT_CORE, &rlmt) == -1) {
        return -1; 
    }   

    if (getrlimit(RLIMIT_CORE, &rlmt) == -1) {
        return -1; 
    }   
    printf("After set rlimit CORE dump current is:%d, max is:%d\n", (int)rlmt.rlim_cur, (int)rlmt.rlim_max);

    /*测试非法内存,产生core文件*/
    int *ptr = NULL;
    *ptr = 10; 

    return 0;
}
Core Dump核心转储

执行./main, 生成的core文件如下所示

Core Dump核心转储

GDB调试core文件,查看程序挂在位置。当core dump 之后,使用命令 gdb program core 来查看 core 文件,其中 program 为可执行程序名,core 为生成的 core 文件名。

Core Dump核心转储

4、参考资料

http://www.cnblogs.com/hazir/p/linxu_core_dump.html

http://www.cnblogs.com/niocai/archive/2012/04/01/2428128.html

http://baidutech.blog.51cto.com/4114344/904419/

Linux下coredump

1.coredump简单介绍

程序崩溃时保存的程序运行时的保存的内存信息的coredump文件,可以通过sysctl或者/proc中来设置core文件的文件名以及生成的路径等。一般的coredump文件为ELF格式,coredmp包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息等。许多程序和操作系统出错时会自动生成一个core文件。coredump可以用在很多场合,使用Linux系统在跑一些压力测试或者系统负载一大的话,系统就hang住了或者干脆system panic。这时唯一能帮助你分析和解决问题的就是coredump了。通常进程或者内核收到


2.core文件相关命令

ulimit命令改变shell的资源限制,显示shell的资源限制,coredump项值为coredump文件大小单位blocks(4kbytes),程序崩溃时的行为不可按平常时的行为来估计,比如缓冲区溢出等错误可能导致堆栈被破坏,因此经常会出现某个变量的值被修改成乱七八糟的,然后程序用这个大小去申请内存就可能导致程序比平常时多占用很多内存。因此无论程序正常运行时占用的内存多么少,要保证生成Core文件还是将大小限制设为unlimited为好一般使用ulimit -c unlimited不限制coredump文件大小,生成文件太小gdb时候会报错

ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 129159
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 129159
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

实际程序中可以通过cat /proc/pid/limits查看进程的资源限制,Max core file size中soft limit为coredump文件支持最大值,0则表示不会生成coredump文件,这里以pid=1为例子

#cat /proc/1/limits 
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            8388608              unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             129159               129159               processes 
Max open files            1024                 4096                 files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       129159               129159               signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us

若程序调用了seteuid()/setegid()/setsid()改变了进程的有效用户或组,则在默认情况下系统不会为这些进程生成coredump。很多服务程序都会调用seteuid()或者daemon( )。为了能够让这些进程生成core dump,需要进程中使用函数getrlimit,setrlimit来改变大小,linux系统下man setrlimit查看函数具体说明RLIMIT_CORE指定修改参数为coredump,其他参数具体含义man命令进行查阅函数使用之后进行说明。

如果开启之后还可以设置coredump文件的格式化名字以及生成的路径,/proc/sys/kernel/core_uses_pid支持文件名中带有pid, /proc/sys/kernel/core_pattern可以设置格式化的core文件保存位置或文件名,例如如下示例说明echo "/corefile/core-%e-%p-%t" > core_pattern将会控制所产生的core文件会存放到/corefile目录下,产生的文件名为core-命令名-pid-时间戳

参数列表:

%p - insert pid into filename 添加pid

%u - insert current uid into filename 添加当前uid

%g - insert current gid into filename 添加当前gid

%s - insert signal that caused the coredump into the filename 添加导致产生core的信号

%t - insert UNIX time that the coredump occurred into filename 添加core文件生成时的unix时间

%h - insert hostname where the coredump happened into filename 添加主机名

%e - insert coredumping executable name into filename 添加命令名

Coredump函数在kernel/fs/exec.c中函数为do_coredump( ),如果coredump生成失败可以在do_coredump函数中增加打印,do_coredump的源代码如下所示。

void do_coredump(long signr, int exit_code, struct pt_regs *regs)
{
	struct core_state core_state;
	char corename[CORENAME_MAX_SIZE + 1];
	struct mm_struct *mm = current->mm;
	struct linux_binfmt * binfmt;
	const struct cred *old_cred;
	struct cred *cred;
	int retval = 0;
	int flag = 0;
	int ispipe;
	static atomic_t core_dump_count = ATOMIC_INIT(0);
	struct coredump_params cprm = {
		.signr = signr,
		.regs = regs,
		.limit = rlimit(RLIMIT_CORE),
		/*
		 * We must use the same mm->flags while dumping core to avoid
		 * inconsistency of bit flags, since this flag is not protected
		 * by any locks.
		 */
		.mm_flags = mm->flags,
	};

	audit_core_dumps(signr);
	binfmt = mm->binfmt;

	//binfmt->core_dump根据内核宏初始化赋值core_dump函数,未开宏时为NULL
	if (!binfmt || !binfmt->core_dump)
		goto fail;
	if (!__get_dumpable(cprm.mm_flags))
		goto fail;

	cred = prepare_creds();
	if (!cred)
		goto fail;
	/*
	 *	We cannot trust fsuid as being the "true" uid of the
	 *	process nor do we know its entire history. We only know it
	 *	was tainted so we dump it as root in mode 2.
	 */
	if (__get_dumpable(cprm.mm_flags) == 2) {
		/* Setuid core dump mode */
		flag = O_EXCL;		/* Stop rewrite attacks */
		cred->fsuid = 0;	/* Dump root private */
	}

	retval = coredump_wait(exit_code, &core_state);
	if (retval < 0)
		goto fail_creds;

	old_cred = override_creds(cred);

	/*
	 * Clear any false indication of pending signals that might
	 * be seen by the filesystem code called to write the core file.
	 */
	clear_thread_flag(TIF_SIGPENDING);

	//根据/proc/sys/kernel/core_pattern中值定义core文件名
	ispipe = format_corename(corename, signr);

 	if (ispipe) {
		int dump_count;
		char **helper_argv;

		if (cprm.limit == 1) {
			/*
			 * Normally core limits are irrelevant to pipes, since
			 * we're not writing to the file system, but we use
			 * cprm.limit of 1 here as a speacial value. Any
			 * non-1 limit gets set to RLIM_INFINITY below, but
			 * a limit of 0 skips the dump.  This is a consistent
			 * way to catch recursive crashes.  We can still crash
			 * if the core_pattern binary sets RLIM_CORE =  !1
			 * but it runs as root, and can do lots of stupid things
			 * Note that we use task_tgid_vnr here to grab the pid
			 * of the process group leader.  That way we get the
			 * right pid if a thread in a multi-threaded
			 * core_pattern process dies.
			 */
			printk(KERN_WARNING
				"Process %d(%s) has RLIMIT_CORE set to 1\n",
				task_tgid_vnr(current), current->comm);
			printk(KERN_WARNING "Aborting core\n");
			goto fail_unlock;
		}
		cprm.limit = RLIM_INFINITY;

		dump_count = atomic_inc_return(&core_dump_count);
		if (core_pipe_limit && (core_pipe_limit < dump_count)) {
			printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n",
			       task_tgid_vnr(current), current->comm);
			printk(KERN_WARNING "Skipping core dump\n");
			goto fail_dropcount;
		}

		helper_argv = argv_split(GFP_KERNEL, corename+1, NULL);
		if (!helper_argv) {
			printk(KERN_WARNING "%s failed to allocate memory\n",
			       __func__);
			goto fail_dropcount;
		}

		retval = call_usermodehelper_fns(helper_argv[0], helper_argv,
					NULL, UMH_WAIT_EXEC, umh_pipe_setup,
					NULL, &cprm);
		argv_free(helper_argv);
		if (retval) {
 			printk(KERN_INFO "Core dump to %s pipe failed\n",
			       corename);
			goto close_fail;
 		}
	} else {
		struct inode *inode;
		
		//根据进程的soft limit大小,soft limit大于coredump初始设置最小值=PAGE_SZIE
		if (cprm.limit < binfmt->min_coredump)
			goto fail_unlock;

		cprm.file = filp_open(corename,
				 O_CREAT | 2 | O_NOFOLLOW | O_LARGEFILE | flag,
				 0600);
		if (IS_ERR(cprm.file))
			goto fail_unlock;

		inode = cprm.file->f_path.dentry->d_inode;
		if (inode->i_nlink > 1)
			goto close_fail;
		if (d_unhashed(cprm.file->f_path.dentry))
			goto close_fail;
		/*
		 * AK: actually i see no reason to not allow this for named
		 * pipes etc, but keep the previous behaviour for now.
		 */
		if (!S_ISREG(inode->i_mode))
			goto close_fail;
		/*
		 * Dont allow local users get cute and trick others to coredump
		 * into their pre-created files.
		 */
		if (inode->i_uid != current_fsuid())
			goto close_fail;
		if (!cprm.file->f_op || !cprm.file->f_op->write)
			goto close_fail;
		if (do_truncate(cprm.file->f_path.dentry, 0, 0, cprm.file))
			goto close_fail;
	}
	
	//执行core_dump函数输出寄存器等信息到core文件中
	retval = binfmt->core_dump(&cprm);
	if (retval)
		current->signal->group_exit_code |= 0x80;

	if (ispipe && core_pipe_limit)
		wait_for_dump_helpers(cprm.file);
close_fail:
	if (cprm.file)
		filp_close(cprm.file, NULL);
fail_dropcount:
	if (ispipe)
		atomic_dec(&core_dump_count);
fail_unlock:
	coredump_finish(mm);
	revert_creds(old_cred);
fail_creds:
	put_cred(cred);
fail:
	return;
}
开启系统的coredump步骤为:
  • 开启内核宏支持coredump函数(对应的进程重新编译并编译中-g要加上才能正常显示gdb信息)
    Core Dump核心转储
  • 命令设置coredump文件大小,ulimit -c unlimited表示coredump没有限制或者ulimit -c 1024支持文件大小1024k,如果进程脱离终端利用getrlimit,setrlimit如下所示
    struct rlimit rlp;
    getrlimit(RLIMIT_CORE, &rlp);
    rlp.rlim_cur = 4 * 1024 * 1024;
    setrlimit(RLIMIT_CORE, &rlp);
  • 设置文件生成路径以及文件名。可以/proc/sys/kernel/core_pattern和/proc/sys/kernel/core_uses_pid来设置或者sysctl -w kernel.core_uses_pid =0 sysctl -w kernel.core_pattern = /var/core.%e.%p设置


3. 调试


如上述步骤成功则会生成对应的core文件,如果是大型服务器中core文件可以直接gdb进行调试,这里只说明在嵌入式中如何利用gdb达到调试的目的。对应目录生成的core文件从系统中拷贝出来类似tftp命令 ftp或者利用u盘拷贝。嵌入式中不能拷贝出来coredump文件那之前设置都是白费的。

成功获取core文件,并将拷贝的core文件放入对应的process的程序工程目录下同一目录下且工程目录有process生成bin文件,cd到process的目录

XXX-XXX-XX-gdb bin core

进入gdb模式,调试中可能有一些库要用到,所以还要设置gdb中调用库的库文件的绝对路径,一般linux嵌入式一般是生成的文件系统作为调用路径

initially, you will see a lot of error messages. They can be ignored.  Now on the gdb prompt, type:

(gdb) set solib-absolute-prefix SRCPATH/targets/PROFILE/fs.install

(gdb) bt

bt之后可以看见打印的堆栈信息。

 

造成程序coredump的原因很多,这里根据以往的经验总结一下:

1 内存访问越界

    • 由于使用错误的下标,导致数组访问越界
    • 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符
    • 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。

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

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

4.非法指针

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

5.堆栈溢出

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



相关标签: Core Dump