程序运行崩溃分析

1. 概述

在程序开发过程中经常会遇到程序崩溃、内核崩溃等现象,崩溃的原因无非就是内存泄露、内存溢出等导致程序操作了非法指针。当代码量不大、复现几率高的时候排查此类问题可以通过查阅代码、加调试信息等手段来定位问题。但是如果复现概率极低、代码量大,程序运行时依赖多个动态的库,程序strip后debug信息被移除等情况下,通过程序崩溃时产生的backtrack来回溯程序运行异常的状态是一种非常好的手段。崩溃信息分两种:内核崩溃信息、应用程序崩溃信息。内核crash时需要产生详细的backtrack信息,可以在编译的内核中进行功能配置,使用kdump和产生的crash信息来分析问题。这里主要通过案例讲解通过应用程序的崩溃信息排查应用程序的bug,在分析coredump文件前,系统需要设置如下内容: 开启coredump、设置coredump文件名

2. ulimit命令开启coredump

ulimit常用参数介绍:

 -a:显示目前资源限制的设定;
    -c :设定core文件的最大值,单位为区块;
    -d <数据节区大小>:程序数据节区的最大值,单位为KB;
    -f <文件大小>:shell所能建立的最大文件,单位为区块;
    -H:设定资源的硬性限制,也就是管理员所设下的限制;
    -m <内存大小>:指定可使用内存的上限,单位为KB;
    -n <文件数目>:指定同一时间最多可开启的文件数;
    -p <缓冲区大小>:指定管道缓冲区的大小,单位512字节;
    -s <堆叠大小>:指定堆叠的上限,单位为KB;
    -S:设定资源的弹性限制;
    -t :指定CPU使用时间的上限,单位为秒;
    -u <程序数目>:用户最多可开启的程序数目;
       -v <虚拟内存大小>:指定可使用的虚拟内存上限,单位为KB。

查看当前系统的设置
程序运行崩溃分析_第1张图片

ulimit -c 输出如果为0,则说明coredump功能没有打开
ulimit -c 输出如果为unlimited,则说明coredump已打开
通过 ulimit -c unlimited 就可以打开coredump功能
通过 ulimit -c 0 就可以关闭coredump功能

3. 设置coredump文件名和路径

默认生成的core文件保存在可执行文件所在的目录下, 文件名为core,因为根文件系统默认是只读,而且默认文件名不便于区分是哪个应用程序产生的coredump,通过修改/proc/sys/kernel/core_uses_pid可以选择coredump文件的文件名中是否添加pid作为扩展。
查看方法:

下面表示添加程序运行时pid作为扩展名,生成的coredump文件格式为core.xxxx:

下面表示生成的coredump文件同一命名为core:

也可以通过设置proc/sys/kernel/core_pattern可以设置更加详细的coredump文件保存位置和文件名格式。例如:

可通过以下命令修改此文件:

可以将core文件统一生成到/tmp目录下,产生的文件名为core.程序名.pid.信号时间戳的coredump文件,例如/tmp/core.qlplay.6983.11.3963607 表示pid为6983的程序qlplay 在系统时间3963607时,收到11号(SEGV) 信号时产生的coredump文件。core_pattern支持以下是参数列表:
程序运行崩溃分析_第2张图片
将程序产生的coredump文件导出到PC上进行分析(也可以通过交叉编译gdb工具在设备上进行分析,但需要依赖一些glibc库和gdb工具,以及很多情况下需要反汇编应用程序进行分析,建议导出到PC端查看,也可以避免设备内存不足影响分析)

以最近排查的FCT产生的coredump文件进行分析, 使用gdb对coredump进行分析, 三个基本的命令即可定位绝大部分问题发生的位置,因为gdb的命令参数非常庞大,这里也不一一介绍。三个基本命令是 backtrace(缩写bt) 、info proc all (缩写info proc a)、info register(缩写info r)
首先来看一下strip过的应用程序产生的coredump文件
程序运行崩溃分析_第3张图片
从backtrack里面可以看到程序运行到0xb6a4d42c in ?? () 的时候收到系统发送的SIGSEGV信号导致crash,这里?? 因为没有符号信息并不知道具体运行位置。使用info proc all 查看进行运行时内存映射信息
程序运行崩溃分析_第4张图片
程序运行崩溃分析_第5张图片
程序运行崩溃分析_第6张图片

对照frame 0 (0xb6a4d42c) 和内存映射,可以知道, 程序崩溃的时候执行的指令在

这里可以通过反汇编/lib/libpthread-2.26.so来确定程序调用了线程函数的那个API导致的crash,虚拟地址0xb6a4d42c需要换算成相对地址才能确定,相对地址=0xb6a4d42c - 0xb6a3e000 = 0xF42C, 查看libpthread-2.26.so反汇编出来的符号表。
程序运行崩溃分析_第7张图片
可以看到 ldr r4, [r0, #104] ; 0x68 执行这句汇编指令的时候导致的crash, r4、r0的值可以通过info register查看
程序运行崩溃分析_第8张图片
从coredump中可以看到运行的时候,r0的值为0, r1的值为10, 从代码中可以找到程序在终止线程的时候使用pthread_kill调用发送了SIGUSR1也就是10这个信号,导致了程序crash,通过搜索整个程序代码发现调用pthread_kill的地方不多,可以通过加调试信息和review代码的方式进行排查,但是有个更直接的方法就是,coredump里面记录了调用者LR的地址(即frame #1 0x8e129b5c in ?? ()),可以从这个入手。lr的地址范围位于0x8e109000 0x8e1fe000 0xf5000 0x0 /usr/bin/FCT, 可以看出来这个是在FCT程序内调用的pthread_kill(), 反编译FCT程序

然后计算0x8e129b5c的偏移地址0x8e129b5c - 0x8e109000 = 0x20B5C。查看fct.obj 得知pthread_kill 是在wait_closewindows_thread调用导致的,知道大致位置后通过review代码才进行分析,篇幅有限,这里不在贴obj的汇编代码。调试过程中,修改因pthread_kill导致的crash后发现程序还有其他问题,多次执行item测试后程序依然会crash,而且crash的位置不固定,通过上述的方法不能很好的确定问题点,通过修改编译参数CFLAGS 为-O0 -g来添加debug信息,应用程序不进行strip,这样产生的coredump文件会保留debug信息,如下所示,通过查看backtrack即可快速看到程序是运行哪一行代码导致的crash。
例如:
程序运行崩溃分析_第9张图片
程序运行崩溃分析_第10张图片
程序运行崩溃分析_第11张图片
程序运行崩溃分析_第12张图片
调试中发现程序在申请内存或创建线程时异常。第一反应就是内存泄露了,通过top -d1 实时查看程序的虚拟内存情况,发现每点击一次虚拟内存增加4M或8M,且测试完毕内存不回收,根据经验和代码分析,这种情况是线程创建导致,线程结束后没有回收资源导致的内存泄露。review代码,发现几处malloc的内存未释放,以及线程未设置为detach,也没有主动回收资源,导致线程资源占用累计最后耗尽程序的应用内存空间。修复后再进行测试,内存泄露的问题不再复现。

作者:陈福林

你可能感兴趣的:(segmentfault程序员)