更多ulimit的命令使用,可以参考:http://baike.baidu.com/view/4832100.htm
这些需要有root权限, 在ubuntu下每次重新打开中断都需要重新输入上面的ulimit命令, 来设置core大小为无限.
2, 当前用户,即执行对应程序的用户具有对写入core目录的写权限以及有足够的空间。
3, 几种不会产生core文件的情况说明:
The core file will not be generated if
(a) the process was set-user-ID and the current user is not the owner of the program file, or
(b) the process was set-group-ID and the current user is not the group owner of the file,
(c) the user does not have permission to write in the current working directory,
(d) the file already exists and the user does not have permission to write to it, or
(e) the file is too big (recall the RLIMIT_CORE limit in Section 7.11). The permissions of the core file (assuming that the file doesn’t already exist) are usually user-read and user-write, although Mac OS X sets only user-read.
五,coredump产生的几种可能情况
造成程序coredump的原因有很多,这里总结一些比较常用的经验吧:
1,内存访问越界
a) 由于使用错误的下标,导致数组访问越界。
b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符。
c) 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。
2,多线程程序使用了线程不安全的函数。
应该使用下面这些可重入的函数,它们很容易被用错:
asctime_r(3c) gethostbyname_r(3n) getservbyname_r(3n)ctermid_r(3s) gethostent_r(3n) getservbyport_r(3n) ctime_r(3c) getlogin_r(3c)getservent_r(3n) fgetgrent_r(3c) getnetbyaddr_r(3n) getspent_r(3c)fgetpwent_r(3c) getnetbyname_r(3n) getspnam_r(3c) fgetspent_r(3c)getnetent_r(3n) gmtime_r(3c) gamma_r(3m) getnetgrent_r(3n) lgamma_r(3m) getauclassent_r(3)getprotobyname_r(3n) localtime_r(3c) getauclassnam_r(3) etprotobynumber_r(3n)nis_sperror_r(3n) getauevent_r(3) getprotoent_r(3n) rand_r(3c) getauevnam_r(3)getpwent_r(3c) readdir_r(3c) getauevnum_r(3) getpwnam_r(3c) strtok_r(3c) getgrent_r(3c)getpwuid_r(3c) tmpnam_r(3s) getgrgid_r(3c) getrpcbyname_r(3n) ttyname_r(3c)getgrnam_r(3c) getrpcbynumber_r(3n) gethostbyaddr_r(3n) getrpcent_r(3n)
3,多线程读写的数据未加锁保护。
对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成coredump
4,非法指针
a) 使用空指针
b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump。
5,堆栈溢出
不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误。
六,利用gdb进行coredump的定位
其实分析coredump的工具有很多,现在大部分类unix系统都提供了分析coredump文件的工具,不过,我们经常用到的工具是gdb。
这里我们以程序为例子来说明如何进行定位。
1, 段错误 – segmentfault
Ø 我们写一段代码往受到系统保护的地址写内容。
Ø 按如下方式进行编译和执行,注意这里需要-g选项编译。
可以看到,当输入12的时候,系统提示段错误并且core dumped
Ø 我们进入对应的core文件生成目录,优先确认是否core文件格式并启用gdb进行调试。
从红色方框截图可以看到,程序中止是因为信号11,且从bt(backtrace)命令(或者where)可以看到函数的调用栈,即程序执行到coremain.cpp的第5行,且里面调用scanf 函数,而该函数其实内部会调用_IO_vfscanf_internal()函数。
接下来我们继续用gdb,进行调试对应的程序。
记住几个常用的gdb命令:
l(list) ,显示源代码,并且可以看到对应的行号;
b(break)x, x是行号,表示在对应的行号位置设置断点;
p(print)x, x是变量名,表示打印变量x的值
r(run), 表示继续执行到断点的位置
n(next),表示执行下一步
c(continue),表示继续执行
q(quit),表示退出gdb
启动gdb,注意该程序编译需要-g选项进行。
注: SIGSEGV 11 Core Invalid memoryreference
七,附注:
1, gdb的查看源码
显示源代码
GDB 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g的参数,把源程序信息编译到执行文件中。不然就看不到源程序了。当程序停下来以后,GDB会报告程序停在了那个文件的第几行上。你可以用list命令来打印程序的源代码。还是来看一看查看源代码的GDB命令吧。
显示程序第linenum行的周围的源程序。
显示函数名为function的函数的源程序。
显示当前行后面的源程序。
list -
显示当前行前面的源程序。
一般是打印当前行的上5行和下5行,如果显示函数是是上2行下8行,默认是10行,当然,你也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。
setlistsize
设置一次显示源代码的行数。
showlistsize
查看当前listsize的设置。
list命令还有下面的用法:
list,
显示从first行到last行之间的源代码。
list ,
显示从当前行到last行之间的源代码。
list +
往后显示源代码。
一般来说在list后面可以跟以下这些参数:
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
哪个文件的哪一行。
哪个文件中的哪个函数。
<*address> 程序运行时的语句在内存中的地址。
2, 一些常用signal的含义
SIGABRT:调用abort函数时产生此信号。进程异常终止。
SIGBUS:指示一个实现定义的硬件故障。
SIGEMT:指示一个实现定义的硬件故障。EMT这一名字来自PDP-11的emulator trap 指令。
SIGFPE:此信号表示一个算术运算异常,例如除以0,浮点溢出等。
SIGILL:此信号指示进程已执行一条非法硬件指令。4.3BSD由abort函数产生此信号。SIGABRT现在被用于此。
SIGIOT:这指示一个实现定义的硬件故障。IOT这个名字来自于PDP-11对于输入/输出TRAP(input/outputTRAP)指令的缩写。系统V的早期版本,由abort函数产生此信号。SIGABRT现在被用于此。
SIGQUIT:当用户在终端上按退出键(一般采用Ctrl-/)时,产生此信号,并送至前台进
程组中的所有进程。此信号不仅终止前台进程组(如SIGINT所做的那样),同时产生一个core文件。
SIGSEGV:指示进程进行了一次无效的存储访问。名字SEGV表示“段违例(segmentationviolation)”。
SIGSYS:指示一个无效的系统调用。由于某种未知原因,进程执行了一条系统调用指令,但其指示系统调用类型的参数却是无效的。
SIGTRAP:指示一个实现定义的硬件故障。此信号名来自于PDP-11的TRAP指令。
SIGXCPUSVR4和4.3+BSD支持资源限制的概念。如果进程超过了其软C P U时间限制,则产生此信号。
SIGXFSZ:如果进程超过了其软文件长度限制,则SVR4和4.3+BSD产生此信号。
3, Core_pattern的格式
可以在core_pattern模板中使用变量还很多,见下面的列表:
%% 单个%字符
%p 所dump进程的进程ID
%u 所dump进程的实际用户ID
%g 所dump进程的实际组ID
%s 导致本次core dump的信号
%t core dump的时间 (由1970年1月1日计起的秒数)
%h 主机名
%e 程序文件名
摘要:在我们往期对coredump的分析中,是依赖于core文件的,而core文件中也几乎包含了程序当前的所有状态(堆栈、内存、寄存器等)。然而在实际的线上环境中,由于core文件太大、保存core文件耗时太久,出于线上系统的稳定性与快速恢复考虑,我们往往不会保留core文件。同时,程序堆栈被破坏的情况下,即使我们保留了core文件,也无法准确获取程序崩溃时准确的上下文信息。本文主要介绍在不保留core文件的情况下,如何获取程序崩溃时候的上下文信息(主要是函数调用栈)。
## 1.coredump原理
当程序发生内存越界访问等行为时,会触发OS的保护机制,此时OS会产生一个信号(signal)发送给对应的进程。当进程从内核态到用户态切换时,该进程会处理这个信号。此类信号(比如SEGV)的默认处理行为生成一个coredump文件。
这里会涉及以下几个问题:
1. 保存的core文件在什么地方?
2. core文件,具体会把进程地址空间的哪些内容保存下来?
3. 如何控制core文件的大小?
4. 如果在处理信号的时候,又产生了新的同类信号,该如何处理?
5. 处理信号的代码,是运行在用户态还是内核态?
6. 在一个多线程的程序中,是由哪个线程在处理这个信号?
问题4~问题6是信号处理的相关内容,我们不在这里解释,会在信号处理的章节详细分析。问题1~问题3解释如下
* `/proc/sys/kernel/core_pattern` 指定core文件存储的位置,缺省值是`core`,表示将core文件存储到当前目录。这个pattern是可以定制的,模式如下:
%p 出Core进程的PID
%u 出Core进程的UID
%s 造成Core的signal号
%t 出Core的时间,从1970-01-0100:00:00开始的秒数
%e 出Core进程对应的可执行文件名
* `/proc/sys/kernel/core_uses_pid` 取值是0或者1,表示是否在core文件名字后面加上进程号
* `/proc/$pid/coredump_filter` 设置那些内存会被dump出来
bit 0 Dump anonymous private mappings.
bit 1 Dump anonymous shared mappings.
bit 2 Dump file-backed private mappings.
bit 3 Dump file-backed shared mappings.
bit 4 (since Linux 2.6.24)
Dump ELF headers.
bit 5 (since Linux 2.6.28)
Dump private huge pages.
bit 6 (since Linux 2.6.28)
Dump shared huge pages.
* `ulimit -c ` 决定save的core文件大小限制
## 2.自定义信号处理函数
我们需要在自定义的信号处理函数中打印出程序崩溃时候的活跃函数堆栈信息。这里我们有两种方式:1.使用backtrace等方法,读取进程堆栈上的信息;2.在函数调用的同时,用户自己维护一套数据结构,用于保存函数调用链,在信号处理函数中,将这个函数调用链打印出来。
### 2.1使用backtrace获取函数调用链
在[从汇编语言看函数调用](http://www.uufool.com/?p=54)和[栈破坏下的coredump分析方法](http://www.uufool.com/?p=78)两篇文章中,我们知道进程堆栈上保存了rbp寄存器对应的list。backtrace本质上就是利用进程堆栈上的数据,推断出来的当前函数调用链。这里我们不分析backtrace的源码,直接给出关键性质的代码。
```cpp
void dump_trace(int Signal)
const int len = 200;
void* buffer[len];
printf("dump_trace\n");
int nptrs = ::backtrace(buffer, len);
printf("backtrace\n");
char** buffer_array = ::backtrace_symbols(buffer, nptrs);
printf("sig:%d nptrs:%d\n", Signal, nptrs);
if (buffer_array) {
for (int i = 0; i < nptrs; ++i) {
printf("frame=%d||trace_back=%s||\n", i, buffer_array[i]);
free(buffer_array);
exit(0);
signal(SIGSEGV, dump_trace);//注册信号处理函数
完整的代码可以参考[这里](https://github.com/yukun89/draft/tree/master/dump)。利用signal函数,我们将dump_trace注册为SIGSEGV的信号处理函数,来取代默认的保存core文件的行为。
### 2.2 用户自己维护一个函数调用链
为什么我们需要费力自己去维护一个函数调用链而不是直接调用backtrace呢? 因为遇到进程堆栈被写花的时候,我们是无法找到完整的函数调用栈信息的。自己去维护函数调用链的原理如下:维护一个堆栈,在函数调用的时候,将调用的函数入栈;函数调用结束时,将这个函数出栈。这样当coredump发生时,即使进程堆栈被破坏的情况下,这个用户自定义的函数堆栈中依然保存了函数调用链的信息。
那么如何在函数调用的开始和结束执行对应的操作呢?g++/gcc正好提供了这种功能,能够让我们在函数的开始和结束嵌入对应的代码。我们需要做的仅仅是实现两个预先声明的函数,核心代码如下
```cpp
#ifdef __cplusplus
extern "C" {
#endif
void __attribute__((no_instrument_function))
__cyg_profile_func_enter(void *this_func, void *call_site);
void __attribute__((no_instrument_function))
__cyg_profile_func_exit(void *this_func, void *call_site);
#ifdef __cplusplus
#endif
void __cyg_profile_func_enter(void *this_func, void *call_site)
char buffer[64] = {0};
int len = snprintf(buffer, 60, "%p call %p", this_func, call_site);
std::string content = std::string(buffer);
call_list.push(content);
return ;
void __cyg_profile_func_exit(void *this_func, void *call_site)
call_list.pop();
return ;
代码解释:`void __cyg_profile_func_enter(void *this_func, void *call_site)` 函数有两个参数,第一参数表示调用方的地址,第二个参数表示被调用方的地址。需要注意的有以下几点:
* `__cyg_profile_func_enter`和`__cyg_profile_func_exit`这两个函数本身是需要设置属性`no_instrument_function`的;否则会陷入对这两个函数本身的无限递归调用。
* 为了将上述代码嵌入到每个函数的开始和结束,需要在编译代码的时候使用特定的编译参数`-finstrument-functions`
* 上述代码所在的编译单元是必须不能使用`-finstrument-functions`的:否则会陷入循环调用(想想为什么)
相关demo代码的说明在[这里](https://github.com/yukun89/draft/tree/master/dump),大家可以自行测试。我们只给出函数在crash时候的输出结果。对于文中的地址信息,我们可以使用addr2line获得这些地址对应的源文件地址。
```shell
sig:11
frame_0: 0x401172 call 0x4011f0
frame_1: 0x401172 call 0x4011e1
frame_2: 0x401172 call 0x4011f0
frame_3: 0x401172 call 0x4011e1
frame_4: 0x401172 call 0x4011f0
frame_5: 0x401172 call 0x40129f
frame_6: 0x40123e call 0x7f6e0bf52b15
## 3.coredump的各种可能性
综合前面所讲解的所有coredump的种类,我们总结coredump的各种可能性如下:
- 内存访问越界
+ 下标导致的数组访问越界
+ 字符串不包含对应结束符导致的越界访问
+ 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函数,将目标字符串读/写爆
2. 多线程数据未进行加锁保护:STL容器vector、map等都是非线程安全的
3. 指针相关
+ 空指针解引用
+ 非法的指针转化
+ use after free
+ double free
4. 堆栈相关
+ 栈变量过大导致的堆栈溢出
+ 栈变量的非法写入,导致程序调用栈被破坏无法回溯
gdb 调试coredump文件过程:
第一步:首先需要一个进程的coredump文件,怎么搞出coredump文件呢?
1、 ps -fax|grep 进程名称 找到进程的pid
2、gdb -p pid 调试进程
3、gcore coredump名称 则生成core文件
https://www.cnblogs.com/wangjian8888/p/11978397.html 该链接有应用程序崩溃后生成core文件具体方法
第二步:找出coredump文件的应用程序
1、gdb -c corefile 使用gdb调试core文件
2、info auxv 索引31对应的是core文件的应用程序
第三部:gdb使用应用程序调试coredump文件
gdb coredump应用程序 coredump文件 调试coredump文件
通过以上三步就可以调试coredump文件了
通过以下命令调试coredump文件
info threads 显示所有线程
bt 显示线程堆栈信息
thread thread_num 切换线程
frame num 切换栈
info r 显示当前帧的寄存器信息 (每一帧的寄存器信息都是不相同的)
readelf应用coredump
readelf -h 读取coredump文件头
readelf -wl 读取应用程序debug_line
readelf -wf 读取应用程序fde和cie信息
在linux环境下调试多线程,总觉得不像.NET那么方便。这几天就为找一个死锁的bug折腾好久,介绍一下用过的方法吧。
多线程如果dump,多为段错误,一般都涉及内存非法读写。可以这样处理,使用下面的命令打开系统开关,让其可以在死掉的时候生成core文件。
ulimit -c unlimited
这样的话死掉的时候就可以在当前目录看到core.pid(pid为进程号)的文件。接着使用gdb:
gdb ./bin ./core.pid
进去后,使用bt查看死掉时栈的情况,在使用frame命令。
还有就是里面某个线程停住,也没死,这种情况一般就是死锁或者涉及消息接受的超时问题(听人说的,没有遇到过)。遇到这种情况,可以使用:
gcore pid (调试进程的pid号)
手动生成core文件,在使用pstack(linux下好像不好使)查看堆栈的情况。如果都看不出来,就仔细查看代码,看看是不是在 if,return,break,continue这种语句操作是忘记解锁,还有嵌套锁的问题,都需要分析清楚了。
最后,说一句,静心看代码,捶胸顿足是没有用的。