如何生成core文件

如何生成core文件,第1张

1、先用#ulimit -a可以查看系统core文件的大小限制(第一行),core文件大小设置为0, 即没有打开core dump设置;

[cpp] view plain copy print?

root@XZX:~/cnnic/project/dnsx/dnsX# 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) 46621

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) 46621

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

root@XZX:~/cnnic/project/dnsx/dnsX# 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) 46621

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) 46621

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

2、接下来使用#ulimit -c [kbytes]可以设置系统允许生成的core文件大小;

ulimit -c 0 不产生core文件

ulimit -c 100 设置core文件最大为100k

ulimit -c unlimited 不限制core文件大小

执行#ulimit -c unlimited,然后#ulimit -a查看结果如下(第一行):

[cpp] view plain copy print?

root@XZX:~/cnnic/project/dnsx/dnsX# ulimit -a

core file size          (blocks, -c) unlimited

data seg size           (kbytes, -d) unlimited

scheduling priority     山知        (-e) 0

file size               (blocks, -f) unlimited

pending signals                 (-i) 46621

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) 46621

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

root@XZX:~/cnnic/project/dnsx/dnsX# ulimit -a

core file size          (blocks, -c) unlimited

data seg size           (kbytes, -d) unlimited

scheduling priority             (-e) 0

file size               (blocks, -f) unlimited

pending signals                 (-i) 46621

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) 46621

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

此时,core dump设置打开了,再执行程序出现段错误时,在当前工作目录下产生了core文件,然后我们就可以用gdb调试core文件了。

例如:

#gdb ./test core.2065

注:Linux下的C程序常常会因为内存访问错误等原因造成segment fault(段错误),此时如果系统core dump功能是打开的,那么将会有内存映像转储到硬盘上来,之后可以用gdb对core文件进行分析,还原系统发生段错误时刻的堆栈情况。这对于我们发现程序bug很有帮助。

很多系统默认的core文件大小都是0,我们可以通过在shell的启动脚本/etc/bashrc或者~/.bashrc等地方来加入 ulimit -c 命令来指定core文件大小,从而确保core文件能够生成。

除此之外,还可以在/proc/sys/kernel/core_pattern里设置core文件的文件名模板,详情请看core的官方man手册。

需要说明的是:上述方法只是在当前shell中生效,重启之后,就不再有效了。永久生效的办法是如下:

永久生效办法:

#vi /etc/profile 然后,在profile中添加:

ulimit -c 1073741824

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

或者

ulimit -c unlimited

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

#source /etc/profile

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

修改完内核转储设置后,当程序core dump后发现确实在本地目录产生了core文件,但是如果程序多次core dump时,core文件会被覆盖,原因是每次core dump后生成的文件名默认都叫core,接下来就分享下如果想在每次core dum时产生的core文件都带上进程号怎么 *** 作,或者你想把内核转储文件保存到其他目录怎么办?

1、core dump文件名自动加上进程ID

#echo 1 > /proc/sys/kernel/core_uses_pid

最后生成的core dump文件名会加上进程ID.

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

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

#vim /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_2456

进程在运行过程中遇到逻辑错误, 比如除零, 空指针等等, 系统会触发一个软件中断.

这个中断会以信号的方式通知进程, 这些信号的默认处理方式是结束进程.

发生这种情况, 我们就认为进程崩溃了.

进程崩溃后, 我们会希望知道它是为何崩溃的, 是哪个函数, 哪行代码引起的错误.

另外, 在进程退出前, 我们还希望做一些善后处理, 比如把某些数据存入数据库, 等等.

下面, 我会介绍一些技术来达成这两个目标.

1. 在core文件中查看堆栈信息

如果进程崩溃时, 我们能看到当时的堆栈信息, 就能很快定位到错误烂猛的代码.

在 gcc 中加入 -g 选项, 可执行文件中便会包含调试信息. 进程崩溃后, 会生成一个 core 文件.

我们可以用 gdb 查看这个 core 文件, 从而知道进程崩溃时的环境.

在调试阶段, core文件能给我们带来很多便利. 但是在正式环境中, 它有很大的局限:

1. 包含调试信息的可执行文件会很大. 并且运行速度也会大幅降低.

2. 一个 core 文件常常很大, 如果进程频繁崩溃, 硬盘资源会变得很紧张.

所以, 在正式环境中运行的程序, 不会包含调试信息.

它的core文件的大小, 我们会把它设为0, 也就是不会输入core文件.

在这个前提下, 我们如何得到进程的堆栈信息嫌历坦呢?

2. 动态获取线程的堆栈

c 语言提供了 backtrace 函数, 通过这个函数可以动态的获取当前线程的堆栈.

要使用 backtrace 函数, 有两点要求:

1. 程序使用的是 ELF 二进制格式.

2. 程序连接时使用了 -rdynamic 选项.

-rdynamic可用来通知链接器将所有符号添加到动态符号表中, 这些信息比 -g 选项的信息要少得多.

下面是将要用到的函数说明:

#include <execinfo.h>

int backtrace(void **buffer,int size)

用于获取当前线程的调用堆栈, 获取的信息将会被存放在buffer中, 它是一个指针列表。

参数 size 用来指定buffer中可以保存多少个void* 元素。

函数返回值是实际获取的指针个数, 最大不超过size大小

注意: 某些编译器的优化选项对获取正确的调用堆栈有干扰,

另外内联函数没有堆栈框架删除框架指针也会导致无法正确解析堆栈内容

char ** backtrace_symbols (void *const *buffer, int size)

把从backtrace函数获取的信息转化为一个字符串数组.

参数buffer应该是从backtrace函数获取的指针数组,

size是该数组中的元素个数(backtrace的返回值)

函数返回值是一个指向字符串数组的指针, 它的大小同buffer相同.

每个字符串包含了一个相对于buffer中对应元素的可打印信息.

它包括函数名,函数的偏移地址, 和实际的返回地址.

该函数的返回值是通过malloc函数芹桐申请的空间, 因此调用者必须使用free函数来释放指针.

注意: 如果不能为字符串获取足够的空间, 函数的返回值将会为NULL.

void backtrace_symbols_fd (void *const *buffer, int size, int fd)

与backtrace_symbols 函数具有相同的功能,

不同的是它不会给调用者返回字符串数组, 而是将结果写入文件描述符为fd的文件中,每个函数对应一行.

3. 捕捉信号

我们希望在进程崩溃时打印堆栈, 所以我们需要捕捉到相应的信号. 方法很简单.

#include <signal.h>

void (*signal(int signum,void(* handler)(int)))(int)

或者: typedef void(*sig_t) ( int )

sig_t signal(int signum,sig_t handler)

参数说明:  

第一个参数signum指明了所要处理的信号类型,它可以是除了SIGKILL和SIGSTOP外的任何一种信号。  

第二个参数handler描述了与信号关联的动作,它可以取以下三种值:

1. 一个返回值为正数的函数的地址, 也就是我们的信号处理函数.

这个函数应有如下形式的定义: int func(int sig)sig是传递给它的唯一参数。

执行了signal()调用后,进程只要接收到类型为sig的信号,不管其正在执行程序的哪一部分,就立即执行func()函数。

当func()函数执行结束后,控制权返回进程被中断的那一点继续执行。

2. SIGIGN, 忽略该信号.

3. SIGDFL, 恢复系统对信号的默认处理。

返回值: 返回先前的信号处理函数指针,如果有错误则返回SIG_ERR(-1)。

注意:

当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,

直到信号处理函数执行完毕再重新调用相应的处理函数。

如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。

在信号发生跳转到自定的handler处理函数执行后,系统会自动将此处理函数换回原来系统预设的处理方式,

如果要改变此 *** 作请改用sigaction()。

4. 实例

下面我们实际编码, 看看具体如何在捕捉到信号后, 打印进程堆栈, 然后结束进程.

#include <iostream>

#include <time.h>

#include <signal.h>

#include <string.h>

#include <execinfo.h>

#include <fcntl.h>

#include <map>

using namespace std

map<int, string>SIG_LIST

#define SET_SIG(sig) SIG_LIST[sig] = #sig

void SetSigList(){

SIG_LIST.clear()

SET_SIG(SIGILL)//非法指令

SET_SIG(SIGBUS)//总线错误

SET_SIG(SIGFPE)//浮点异常

SET_SIG(SIGABRT)//来自abort函数的终止信号

SET_SIG(SIGSEGV)//无效的存储器引用(段错误)

SET_SIG(SIGPIPE)//向一个没有读用户的管道做写 *** 作

SET_SIG(SIGTERM)//软件终止信号

SET_SIG(SIGSTKFLT)//协处理器上的栈故障

SET_SIG(SIGXFSZ)//文件大小超出限制

SET_SIG(SIGTRAP)//跟踪陷阱

}

string&GetSigName(int sig){

return SIG_LIST[sig]

}

void SaveBackTrace(int sig){

//打开文件

time_t tSetTime

time(&tSetTime)

tm* ptm = localtime(&tSetTime)

char fname[256] = {0}

sprintf(fname, "core.%d-%d-%d_%d_%d_%d",

ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,

ptm->tm_hour, ptm->tm_min, ptm->tm_sec)

FILE* f = fopen(fname, "a")

if (f == NULL){

exit(1)

}

int fd = fileno(f)

//锁定文件

flock fl

fl.l_type = F_WRLCK

fl.l_start = 0

fl.l_whence = SEEK_SET

fl.l_len = 0

fl.l_pid = getpid()

fcntl(fd, F_SETLKW, &fl)

//输出程序的绝对路径

char buffer[4096]

memset(buffer, 0, sizeof(buffer))

int count = readlink("/proc/self/exe", buffer, sizeof(buffer))

if(count >0){

buffer[count] = '\n'

buffer[count + 1] = 0

fwrite(buffer, 1, count+1, f)

}

//输出信息的时间

memset(buffer, 0, sizeof(buffer))

sprintf(buffer, "Dump Time: %d-%d-%d %d:%d:%d\n",

ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday,

ptm->tm_hour, ptm->tm_min, ptm->tm_sec)

fwrite(buffer, 1, strlen(buffer), f)

//线程和信号

sprintf(buffer, "Curr thread: %d, Catch signal:%s\n",

pthread_self(), GetSigName(sig).c_str())

fwrite(buffer, 1, strlen(buffer), f)

//堆栈

void* DumpArray[256]

intnSize =backtrace(DumpArray, 256)

sprintf(buffer, "backtrace rank = %d\n", nSize)

fwrite(buffer, 1, strlen(buffer), f)

if (nSize >0){

char** symbols = backtrace_symbols(DumpArray, nSize)

if (symbols != NULL){

for (int i=0i<nSizei++){

fwrite(symbols[i], 1, strlen(symbols[i]), f)

fwrite("\n", 1, 1, f)

}

free(symbols)

}

}

//文件解锁后关闭, 最后终止进程

fl.l_type = F_UNLCK

fcntl(fd, F_SETLK, &fl)

fclose(f)

exit(1)

}

void SetSigCatchFun(){

map<int, string>::iterator it

for (it=SIG_LIST.begin()it!=SIG_LIST.end()it++){

signal(it->first, SaveBackTrace)

}

}

void Fun(){

int a = 0

int b = 1 / a

}

static void* ThreadFun(void* arg){

Fun()

return NULL

}

int main(){

SetSigList()

SetSigCatchFun()

printf("main thread id = %d\n", (pthread_t)pthread_self())

pthread_t pid

if (pthread_create(&pid, NULL, ThreadFun, NULL)){

exit(1)

}

printf("fun thread id = %d\n", pid)

for(){

sleep(1)

}

return 0

}

文件名为 bt.cpp

编译: g++ bt.cpp -rdynamic -I /usr/local/include -L /usr/local/lib -pthread -o bt

主线程创建了 fun 线程, fun 线程有一个除零错误, 系统抛出 SIGFPE 信号.

该信号使 fun 线程中断, 我们注册的 SaveBackTrace 函数捕获到这个信号, 打印相关信息, 然后终止进程.

在输出的core文件中, 我们可以看到简单的堆栈信息.

5. 善后处理

在上面的例子中, fun 线程被 SIGFPE 中断, 转而执行 SaveBackTrace 函数.

此时, main 线程仍然在正常运行.

如果我们把 SaveBackTrace 函数最后的 exit(1)替换成 for()sleep(1)

main 线程就可以一直正常的运行下去.

利用这个特点, 我们可以做很多其它事情.

游戏的服务器进程常常有这些线程:

网络线程, 数据库线程, 业务处理线程. 引发逻辑错误的代码常常位于业务处理线程.

而数据库线程由于功能稳定, 逻辑简单, 是十分强壮的.

那么, 如果业务处理线程有逻辑错误, 我们捕捉到信号后, 可以在信号处理函数的最后,

通知数据库线程保存游戏数据.

直到数据库线程把游戏信息全部存入数据库, 信号处理函数才返回.

这样, 服务器宕机不会导致回档, 损失被大大降低.

要实现这个机制, 要求数据库模块和业务处理模块具有低耦合度.

当然, 实际应用的时候, 还有许多细节要考虑.

比如, 业务处理线程正在处理玩家的数据, 由于发生不可预知的错误, 玩家的数据被损坏了, 这些玩家的数据就不应该被存入数据库.

df 查看文件系统和挂载点,用lslv -l 查看文件系统对应培誉乱的磁盘。1. # df -gFilesystemGB blocks Free %UsedIused %Iused Mounted on/dev/hd4 4.00 3.932% 5294 1% //dev/hd2 4.00 2.15 47%47409 9% /配档usr/dev/hd9var4.00 3.796% 970 1% /var/dev/hd3 4.00 3.53 12% 2468 1% /tmp/dev/fwdump0.12 0.121%4 1% /var/adm/ras/platform/proc - -- - - /proc/dev/hd10opt 23.50 0.58 98%6651231% /opt---以 /tmp文件系统为例, 它是在LV hd3 上面的。 2, lslv -l hd3 就可以看到这个LV所在的物理盘, 即你文件系虚悔统所在的物理盘。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/tougao/12302221.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-24
下一篇 2023-05-24

发表评论

登录后才能评论

评论列表(0条)

保存