Linux调试之崩溃


Linux内核panic后查看dmesg

由于panic后内核不能再将dmesg写入到存储介质中去,所以重启之后dmesg就丢失了。但是kdump可以在内核panic后启用一个小内核来将dmesg和一些内存信息写进存储介质。使用kdump配合crash查看崩溃信息的。

Kdump简介

kdump是一种基于kexec的内核崩溃转储技术。kdump需要两个内核,分别是生产内核和捕获内核,生产内核是捕获内核服务的对象,且保留了内存的一部分给捕获内核启动使用。当系统崩溃时,kdump使用kexec启动捕获内核,以相应的ramdisk一起组建一个微环境,用于对生产内核下的内存进行收集和转存。

安装kdump

ubuntu上kdump安装配置:

参考;https://www.cnblogs.com/zhangmingda/p/12566534.html

centos下安装配置:

安装kexec-tools

使用kdump服务,必须要用到kexec-tools工具包。

sudo yum update
sudo yum install kexec-tools

安装完成之后可以通过kexec -version查看kexec的版本。

配置kdump kernel

需要为kdump kernel配置内存区域,kdump要求系统正常使用时,不能使用kdump kernel所占用的内存。

1.修改grub文件

vim /etc/default/grub

需要将GRUB_CMDLINE_LINUX=”crashkernel=auto…”中的auto修改为128M。一般设为128M或256M。

2.更新grub配置

只要更改了grub文件,都需要更改grub配置。

sudo grub2-mkconfig -o /boot/grub2/grub.cfg

3.重启系统

reboot
修改kdump默认配置
vim /etc/kdump.conf

其中,需要注意的三行内容是

path /var/crash            #指定coredump文件放在/var/crash文件夹中
core_collector makedumpfile -c -l --message-level 1 -d 31   #加上-c表示压缩,原文件中没有
default reboot         #生成coredump后,重启系统

开启kdump服务

systemctl start kdump.service     //启动kdump
systemctl enable kdump.service    //设置开机启动
测试kdump是否开启

1.检查kdump开启成功

service kdump status

如下所示,表示开启成功

关闭kdump服务

service kdump stop

禁止开机启动

chkconfig kdump off

2.手动触发crash

#echo 1 > /proc/sys/kernel/sysrq
#echo c > /proc/sysrq-trigger

如果配置成功,系统将自动重启,重新进入系统,可以看到/var/crash文件夹下生成了相应文件,是一个以生成coredump日期为文件名的文件,如图所示:

减少dump文件的大小

man makedumpfile

可以看到默认的消息等级(打星星的7)会打印common message,生成的dump文件很大。所以我们把它改成1,只打印process indicator:编辑/etc/default/kdump-tools,在里面加上

MAKEDUMP_ARGS="-c --message-level 1 -d 31"

现在dump文件只有17MB了。

禁止生成vmcore

vmcore文件很大,生成要花费大量时间。我们这里只需要dmesg,所以可以禁止生成vmcore。

编辑/etc/default/kdump-tools

MAKEDUMP_ARGS="-c -d 31 --message-level 0 --dump-dmesg"

其中–dump-dmesg表示直接把dmesg抽出来,而不生成vmcore。

现在生成的文件都很小了,重启也非常快。

Linux运行C程序crash掉的分析

一、程序运行崩溃的原因

Linux下c/c++开发程序崩溃(Segment fault)通常都是指针错误引起的.

比如:

(1)访问了不存在的内存地址

(2)访问了只读的内存地址

(3)访问了系统保护的内存地址int p=0;p=100;

(4)栈溢出,无限递归

(5)内存溢出

二、内核转储文件作用

发生Segment fault时,内核转储文件(core dump)作用

(1) 内核转储的最大好处是能够保存问题发生时的状态。

(2) 只要有可执行文件和内核转储,就可以知道进程当时的状态。

(3) 只要获取内核转储,那么即使没有复现环境,也能调试。

三、配置操作系统的内核转储功能

可以参考《高并发服务器开发与配置》中,用户能打开的最大文件数的设置方法。

启动系统的内核转储功能,需要做如下配置:

(1)查看当前转储文件大小

>> ulimit -c

0 为0,表示当前转储文件大小为0,没有启动内核转储

>>ulimit -c unlimited #设置coredump 大小为无限大

这些需要有root权限, 在ubuntu下每次重新打开中断都需要重新输入上面的第一条命令, 来设置core大小为无限.

四、gdb使用内核转储文件再现崩溃时的状态

>>./test ->运行test崩溃,在当前目录下将产生一个core文件

>>gdb -c ./corefile ./test 使用gdb再现崩溃状态

在进入gdb后, 用bt命令查看backtrace以检查发生程序运行到哪里, 来定位core dump的文件->行.

五、System Dump和Core Dump的区别

\1) 系统Dump(System Dump)

所有开放式操作系统,都存在系统DUMP问题。

产生原因:

由于系统关键/核心进程,产生严重的无法恢复的错误,为了避免系统相关资源受到更大损害,操作系统都会强行停止运行,并将当前内存中的各种结构,核心进程出错位置及其代码状态,保存下来,以便以后分析。最常见的原因是指令走飞,或者缓冲区溢出,或者内存访问越界。走飞就是说代码流有问题,导致执行到某一步指令混乱,跳转到一些不属于它的指令位置去执行一些莫名其妙的东西(没人知道那些地方本来是代码还是数据,而且是不是正确的代码开始位置),或者调用到不属于此进程的内存空间。写过C程序及汇编程序的人士,对这些现象应当是很清楚的。

系统DUMP生成过程的特点:

在生成DUMP过程中,为了避免过多的操作结构,导致问题所在位置正好也在生成DUMP过程所涉及的资源中,造成DUMP不能正常生成,操作系统都用尽量简单的代码来完成,所以避开了一切复杂的管理结构,如文件系统)LVM等等,所以这就是为什么几乎所有开放系统,都要求DUMP设备空间是物理连续的——不用定位一个个数据块,从DUMP设备开头一直写直到完成,这个过程可以只用BIOS级别的操作就可以。这也是为什么在企业级UNIX普遍使用LVM的现状下,DUMP设备只可能是裸设备而不可能是文件系统文件,而且[b]只[/b]用作DUMP的设备,做 LVM镜像是无用的——系统此时根本没有LVM操作,它不会管什么镜像不镜像,就用第一份连续写下去。

所以UNIX系统也不例外,它会将DUMP写到一个裸设或磁带设备。在重启的时候,如果设置的DUMP转存目录(文件系统中的目录)有足够空间,它将会转存成一个文件系统文件,缺省情况下,对于AIX

来说是/var/adm/ras/下的vmcore*这样的文件,对于HPUX来说是 /var/adm/crash下的目录及文件。当然,也可以选择将其转存到磁带设备。会造成系统DUMP的原因主要是:系统补丁级别不一致或缺少)系统内核扩展有BUG(例如Oracle就会安装系统内核扩展))驱动程序有 BUG(因为设备驱动程序一般是工作在内核级别的),等等。所以一旦经常发生类似的系统DUMP,可以考虑将系统补丁包打到最新并一致化)升级微码)升级设备驱动程序(包括FC多路冗余软件))升级安装了内核扩展的软件的补丁包等等。

\2) 进程Core Dump

进程Core Dump产生的技术原因,基本等同于系统DUMP,就是说从程序原理上来说是基本一致的。但进程是运行在低一级的优先级上(此优先级不同于系统中对进程定义的优先级,而是指CPU代码指令的优先级),被操作系统所控制,所以操作系统可以在一个进程出问题时,不影响其他进程的情况下,中止此进程的运行,并将相关环境保存下来,这就是core dump文件,可供分析。

如果进程是用高级语言编写并编译的,且用户有源程序,那么可以通过在编译时带上诊断用符号表(所有高级语言编译程序都有这种功能),通过系统提供的分析工具,加上core文件,能够分析到哪一个源程序语句造成的问题,进而比较容易地修正问题,当然,要做到这样,除非一开始就带上了符号表进行编译,否则只能重新编译程序,并重新运行程序,重现错误,才能显示出源程序出错位置。

如果用户没有源程序,那么只能分析到汇编指令的级别,难于查找问题所在并作出修正,所以这种情况下就不必多费心了,找到出问题的地方也没有办法。

进程Core Dump的时候,操作系统会将进程异常终止掉并释放其占用的资源,不可能对系统本身的运行造成危害。这是与系统DUMP根本区别的一点,系统DUMP产生时,一定伴随着系统崩溃和停机,进程

Core Dump时,只会造成相应的进程被终止,系统本身不可能崩溃。当然如果此进程与其他进程有关联,其他进程也会受到影响,至于后果是什么,就看相关进程对这种异常情况(与自己相关的进程突然

终止)的处理机制是什么了,没有一概的定论。

六、内核转储文件(core dump)永久生效的办法

在终端中输入以下命令,查看内核转储是否有效。

#ulimit -c
0

-c 表示内核转储文件的大小限制,现在显示为0,表示不能用。

永久生效的办法是:

#vi /etc/profile 然后,在profile中添加:
ulimit -c 1073741824     --1G大小

(但是,若将产生的转储文件大小大于该数字时,将不会产生转储文件)或者

ulimit -c unlimited

这样重启机器后生效了。 或者, 使用source命令使之马上生效。

#source /etc/profile

七、指定内核转储的文件名和目录

缺省情况下,内核在coredump时所产生的core文件放在与该程序相同的目录中,并且文件名固定为core。很显然,如果有多个程序产生core文件,或者同一个程序多次崩溃,就会重复覆盖同一个core文

件。可以通过修改kernel的参数,指定内核转储所生成的core文件的路径和文件名。

可以通过在/etc/sysctl.conf文件中,对sysctl变量kernel.core_pattern的设置。

>>vi /etc/sysctl.conf

然后,在sysctl.conf文件中添加下面两句话:

kernel.core_pattern = /var/core/core_%e_%p
kernel.core_uses_pid = 0

需要说明的是, /proc/sys/kernel/core_uses_pid。如果这个文件的内容被配置成1,即使core_pattern中没有设置%p,最后生成的core dump文件名仍会加上进程ID。

这里%e, %p分别表示:

%c 转储文件的大小上限

%e 所dump的文件名

%g 所dump的进程的实际组ID

%h 主机名

%p 所dump的进程PID

%s 导致本次coredump的信号

%t 转储时刻(由1970年1月1日起计的秒数)

%u 所dump进程的实际用户ID

可以使用以下命令,使修改结果马上生效。

>>sysctl –p /etc/sysctl.conf

请在/var目录下先建立core文件夹,然后执行a.out程序,就会在/var/core/下产生以指定格式命名的内核转储文件。查看转储文件的情况:

#ls /var/core
core_a.out_2834

八、例子

Linux下c/c++开发之程序崩溃(Segment fault)时内核转储文件(coredump)生成举例说明

例子的源代码:

#include <stdio.h>
int main(void)
{
int *a = NULL;
*a = 0x1;
return 0;
}

把以上源代码,写成一个a.c文件后,编译a.c文件产生一个a.out的可执行文件:

#gcc -g a.c -o a.out

修改a.out文件的权限后,执行它:

#./a.out

就会显示:

Segmentation fault(core dump)

这表示在当前目录下, 已经生成了a.out对应的内核转储文件。

注意:后面带有(core dump), 才说明转储文件成功生成了。

#file core*
core:ELF 64-bit LSB core file x86-64, version 1(SYSV), SVR4-style, from './a.out'
coreDump: UTF-8 Unicode C program text

要用GDB调试内核转储文件,应该使用以下方式启动GDB:

#gdb -c ./core ./a.out
GNU gdb (GDB) 7.1-Ubuntu
...
Core was generated by './a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x00000000004004dc in main() at a.c:6
6 *a =0x1;

a.c的第6行收到了11号信号。用GDB的list命令可以查看附近的源代码。

(gdb) l 5
1           #include <stdio.h>
2     
3           int main(void)
4           {
5                  int *a = NULL;
6                  *a = 0x1;
7                  return 0;
8           }

这里默认都是当前目录,也可以给core 和a.out 指定路径。


文章作者: 杰克成
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 杰克成 !
评论
  目录