GDB 调试 Coredump

news/2024/3/29 6:06:24

在这里插入图片描述

在计算机系统中运行程序时,问题经常发生,而且通常很难找到根源。幸运的是,有一种叫做 coredump 的文件可以帮助我们找到问题的源头。本文将解释什么是 coredump,它是如何工作的,以及如何利用它来定位问题。

01 什么是 coredump


Core dump(核心转储)是指在程序运行过程中发生错误或异常时,操作系统将程序的内存内容保存到磁盘上的一种文件。这个文件包含了程序崩溃时的内存状态,包括变量的值、函数调用栈、寄存器状态等信息。通过分析 coredump 文件,可以了解程序崩溃的原因,以便进行调试和修复。

1.1 ulimit 命令

要生成 core dump 文件,通常需要在操作系统中设置相应的配置。在 Linux 和 Unix 系统中,可以使用 ulimit 命令设置 core dump 文件的大小限制,并使用 gcore 或 kill -SIGQUIT 命令生成 core dump 文件。

ulimit 是 Unix 和 Linux 系统中的内置命令,用于控制用户级别的系统资源限制。这些资源包括文件大小、进程数、核心文件大小、堆栈大小等等。以下是一些使用 ulimit 的基础知识。

使用 ulimit -a 可以列出所有的限制,每一行都对应一个资源类型和其对应的限制。
以下是一些比较常用的 ulimit 选项:

  • ulimit -u : 查看用户可以用的最大进程数。
  • ulimit -n : 文件描述符的最大个数,即一个进程可以同时打开的最大文件数。
  • ulimit -d : 一个进程可以使用的最大数据段大小,单位为KB。
  • ulimit -s : 最大堆栈大小,单位为KB。
  • ulimit -c : 生成coredump文件的最大大小,单位为KB。
  • ulimit -v : 进程虚拟内存的最大值,单位KB。

我们可以在命令后添加具体的数值来设置对应的资源限制,例如:

  • ulimit -n 1024 : 设置最大可以打开的文件描述符数为1024。
  • ulimit -c unlimited : 设置coredump的大小为无限。

需要注意的是,ulimit 命令设置的资源限制是以 Shell 环境为单位的,而不是全局性的。也就是说,在一个 Shell 环境中设置的资源限制,并不会影响到其他的 Shell 环境。同时,这些限制只对当前 Shell 环境以及它派生出来的子进程生效,对已经存在的其他进程没有影响。

总的来说,ulimit 命令是一个很有用的工具,它可以帮助我们控制进程对系统资源的使用,从而防止一些程序错误导致系统资源的耗尽。

1.2 coredump 实例

1. ulimit 查看资源限制

一般情况下如果 coredump 文件没有生成,很大可能是由于受到资源限制,使用 ulimit命令查看和设置资源限制

ulimit -c						 # 查看是否开启 core,0 表示关闭 
ulimit -c [filesize] # 设置 core 文件大小
ulimit -c unlimited  # 设置 core 文件大小为无限

2. 设置 core 文件路径

在 Linux 系统中,core dump 文件的路径是由 /proc/sys/kernel/core_pattern 定义的,如果这个文件不存在,或者内容为空,那么 core dump 文件就会生成在当前目录下

也可以通过修改 /proc/sys/kernel/core_pattern 指定 core 文件生成的路径和文件名

# 查看当前 core 文件路径
cat /proc/sys/kernel/core_pattern # 指定 core 文件路径
sudo echo "yourpath/core.%e.%p.%h.%t" > /proc/sys/kernel/core_pattern
sudo sysctl -w kernel.core_pattern=yourpath/core.%e.%p.%h.%t  # # 也可以使用 sysctl 修改 kernel.core_pattern 来指定 core 文件路径

其中 core 文件名称定义中,可以使用占位符保留一些有用信息

%p: pid
%u: uid
%g: gid
%s: signal number
%t: UNIX time of dump
%h: hostname
%e: executable filename

3. 空指针 core 示例

下面这个例子中试图通过一个 NULL 指针来访问内存,这是非法的,因为 NULL 指针并没有指向任何有效的内存位置,因而在运行时,操作系统将识别这个非法操作,并生成一个 Coredump 文件。

#include <stdio.h>int main() {int *ptr = NULL;printf("%d", *ptr);return 0;
}

编译运行这段程序如下,可以看到触发了 core dumped

需要注意的是编译的时候记得加上 -g 参数保留调试信息,否则使用 GDB 调试时会找不到函数名或者变量名

$ gcc -g main.c -o main
$ ./main
[1]    277 segmentation fault (core dumped)  ./main

4. 使用 GDB 调试 core 文件

找到 core 文件,然后使用命令 gdb [exec file] [core file] 调试 core 文件

$ cd your-core-path
$ ls
core.main
$ gdb ~/main ./core.main

GDB 运行后会停止在发生异常的代码处,并且将发生异常的代码打印出来,如下图中指出了异常位于 mian.c 的第 5 行代码 *ptr 是一个空指针

在这里插入图片描述

02 coredump 是怎么发生的


2.1 程序运行错误导致 coredump

程序执行非法操作时,例如解引用空指针,除数为零,或者访问越界的内存,操作系统就会生成一个 coredump 文件并终止程序运行。以下是一些常见导致 core dump 发生的原因:

  • 空指针引用:当程序试图访问一个空指针时,操作系统会捕获这个错误并生成 core dump 文件
  • 内存越界:当程序试图访问超出其分配内存范围的位置时,可能会导致内存越界错误,从而触发 core dump
  • 栈溢出:当程序的函数调用栈超过其允许的最大深度时,可能会发生栈溢出错误,导致 core dump 发生
  • 除零错误:当程序试图除以零时,会触发除零错误,操作系统可能会生成 core dump 文件
  • 非法指令:当程序执行了无效或非法的机器指令时,操作系统通常会生成 core dump 文件
  • 内存分配错误:当程序遇到内存分配错误,如申请内存失败或释放已释放的内存时,可能会触发 core dump

这些程序执行非法操作时,操作系统会向指定进程发送特定信号(signal)终止程序运行并生成 coredump,一些常见信号有:

  • SIGSEGV (信号值 11):当一个进程由于无效的内存访问,如解引用空指针,或访问受保护的内存区域时,系统会向它发送此信号。这是生成coredump最常见的原因
  • SIGABRT (信号值 6):当进程自身检测到一个无法恢复的问题,并选择主动终止时,它会发送这个信号给自己。例如,C库函数abort()就会发送这个信号
  • SIGILL (信号值 4):当CPU检测到进程试图执行一条无效或未定义的指令时,系统会发送此信号。一个可能的原因是代码的内存被错误地当作数据修改
  • SIGFPE (信号值 8):在数学运算错误时发送,比如除以零或浮点溢出
  • SIGBUS (信号值 7):用于处理错误的内存访问,但这个信号在不同的系统和架构下含义可能会有所不同。在某些系统中,它用于处理对齐(alignment)问题
  • SIGQUIT (信号值 3):用户通过控制台(通常是按Ctrl+\)向进程发送此信号,它不仅会停止进程的运行,还会生成coredump
SignalValueActionComment
SIGSEGV11CoreInvalid memory reference
SIGABRT6CoreAbort signal from abort
SIGILL4CoreIllegal Instruction
SIGFPE8CoreFloating point exception
SIGBUS7CoreBus error (bad memory access)
SIGQUIT3CoreQuit from keyboard

2.2 coredump 文件生成原理

本节参考如有侵权,请告知:https://cloud.tencent.com/developer/article/1860631

coredump 文件的产生过程如下图所示:

Linux 内核实现中使用一个复杂的任务结构(task_struct)结构体来代表系统中的每个进程或线程,这个结构体被定义在 include/linux/sched.h 文件中,其中也包含了记录信号处理相关的属性,如用于存放待处理信号的 pending 信号队列和阻塞信号 blocked 等。

当一个信号被发送到进程时,它首先被添加到该进程的 pending 信号队列中。而内核在进程切换的上下文切换中,在运行被调用进程时会先检查这个 pending 信号队列。如果有待处理的信号,内核会在恢复进程运行前,调用信号的处理函数。这个过程大多数发生在 do_signal 函数中,这个函数在每次中断返回,系统调用返回,或者任何可能会改变进程状态的操作后执行。

如果一个信号是致命的(如 SIGSEGV, SIGABRT),并且进程并没有注册处理函数来处理它,那么内核会根据这个信号的默认行为来操作,比如可能会终止进程,或者产生 coredump 文件等。
在这里插入图片描述
通过上述分析,coredump 文件生成的过程可以总结为:程序运行产生 core 相关的信号 signal;do_signal 函数处理信号;如果默认行为是 Core 则调用 do_coredump 函数生成 core 文件。下面结合源码进一步分析该过程

1. 信号处理过程

在进程切换的上下文切换时,检查信号队列 pending 是否有待处理信号,有则调用 do_singal 函数处理,该函数中使用 get_signal_to_deliver 函数从进程的信号队列 pending 中获取一个信号,然后根据信号的类型来进行不同的操作。

get_signal_to_deliver 函数中处理 coredump 相关信号的代码实现如下,首先使用 dequeue_signal 函数获取信号,然后使用 sig_kernel_coredump 函数判断该信号是否为 coredump 相关信号,如果是则执行 core 相关动作

int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,struct pt_regs *regs, void *cookie)
{sigset_t *mask = &current->blocked;int signr = 0;...for (;;) {...// 1. 从进程信号队列中获取一个信号signr = dequeue_signal(current, mask, info); ...// 2. 判断是否会生成 coredump 文件的信号if (sig_kernel_coredump(signr)) {// 3. 调用 do_coredump() 函数生成 coredump 文件do_coredump((long)signr, signr, regs);}...}...
}

2. 生成 coredump 文件

当 coredump 相关信号被处理时,调用内核就会调用 do_coredump 函数来生成 coredump 文件,该函数核心代码如下,首先判断 ulimit 的资源限制情况,如果可用则创建 coredump 文件,最后将当前进程的寄存器、内存管理等相关信息写入到该文件中。

int do_coredump(long signr, int exit_code, struct pt_regs *regs)
{char corename[CORENAME_MAX_SIZE + 1];struct mm_struct *mm = current->mm;struct linux_binfmt *binfmt;struct inode *inode;struct file *file;int retval = 0;int fsuid = current->fsuid;int flag = 0;int ispipe = 0;binfmt = current->binfmt; // 当前进程所使用的可执行文件格式(如ELF格式)...// 1. 判断当前进程可生成的 coredump 文件大小是否受到资源限制if (current->signal->rlim[RLIMIT_CORE].rlim_cur < binfmt->min_coredump)goto fail_unlock;...// 2. 生成 coredump 文件名ispipe = format_corename(corename, core_pattern, signr);...// 3. 创建 coredump 文件file = filp_open(corename, O_CREAT|2|O_NOFOLLOW|O_LARGEFILE|flag, 0600);...// 4. 把进程的内存信息写入到 coredump 文件中retval = binfmt->core_dump(signr, regs, file);fail_unlock:...return retval;
}

如果文章对你有帮助,欢迎一键三连 👍 ⭐️ 💬 。如果还能够点击关注,那真的是对我最大的鼓励 🔥 🔥 🔥 。


参考资料

一文读懂 | coredump文件是如何生成的-腾讯云开发者社区-腾讯云

core dump 路径定义以及监控

Linux下gdb调试生成core文件并调试core文件-阿里云开发者社区

Linux进程描述符task_struct结构体详解

gdb调试coredump(使用篇)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.tangninghui.cn.cn/item-279.htm

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】指针的进阶(一)

目录 前言 1. 字符指针 2. 指针数组 3. 数组指针 3.1 数组指针的定义 3.2 &数组名VS数组名 3.3 数组指针的使用 4. 数组参数、指针参数 4.1 一维数组传参 4.2 二维数组传参 4.3 一级指针传参 4.4 二级指针传参 5. 函数指针 前言 指针在C语言中可谓是有着举足轻重的…

MQ - 08 基础篇_消费者客户端SDK设计(下)

文章目录 导图Pre概述消费分组协调者消费分区分配策略轮询粘性自定义消费确认确认后删除数据确认后保存消费进度数据消费失败处理从服务端拉取数据失败本地业务数据处理失败提交位点信息失败总结导图 Pre

python使用websocket服务传输数据的例子,可以保持长连接

因为我们发短信&#xff08;http&#xff09;久了&#xff0c;所以我们希望有电话&#xff08;websocket&#xff09;&#xff1b;有了电话之后&#xff0c;我们可以愉悦交通&#xff08;双工通信&#xff09;&#xff0c;所以我们说着一句一句话&#xff08;网络的一个一个包&…

闭着眼睛安装Neoj4版本(5.12.0 Community windows)

1.安装 Java SE 17.0.5 &#xff08;及以上&#xff0c;建议和我一样&#xff09;&#xff0c;安装完配置环境变量&#xff0c;成功标志&#xff08;cmd输出java -version的内容&#xff09; 1.上Neo4j Download Center - Graph Database & Analytics 3. 4.进入cmd &#…

图像处理之频域滤波DFT

摘要&#xff1a;傅里叶变换可以将任何满足相应数学条件的信号转换为不同系数的简单正弦和余弦函数的和。图像信号也是一种信号&#xff0c;只不过是二维离散信号&#xff0c;通过傅里叶变换对图像进行变换可以图像存空域转换为频域进行更多的处理。本文主要简要描述傅里叶变换…

uniapp——实现base64格式二维码图片生成+保存二维码图片——基础积累

最近在做二维码推广功能&#xff0c;自从2020年下半年到今天&#xff0c;大概有三年没有用过uniapp了&#xff0c;而且我之前用uniapp开发的程序还比较少&#xff0c;因此很多功能都浪费了很多时间去查资料&#xff0c;现在把功能记录一下。 这里写目录标题 效果图1.base64生成…