目录
环境
- VMware® Workstation 16 Pro (版本:16.1.2 build-17966106)
- ubuntu-22.04.2-desktop-amd64
问题情况
- 本人在运行百万并发的服务端程序时,程序运行报:段错误(核心已转储),导致程序异常退出,如下
解决思路
- 首先要确定核心转储文件的生成路径和大小限制。然后使用调试器(如 GDB)来分析核心转储文件和堆栈跟踪信息后,修复代码中导致"段错误"的原因。
原因分析
1.什么是段错误?
- 段错误(Segmentation Fault)是一种常见的程序错误,通常在访问无效的内存地址时发生。当程序试图访问一个不属于它的内存段时,操作系统会发送一个信号(SIGSEGV(段错误信号))给程序,称为段错误。
2.可能产生段错误的情况
- **内存访问错误:**最常见的原因之一是程序试图访问无效的内存地址或未初始化的指针。这可能是由于代码错误、缓冲区溢出或内存越界等造成的。当程序尝试访问系统不允许访问的内存区域时,操作系统会引发段错误。
- **无效的指令或操作:**另一个常见原因是程序执行了无效的指令或操作。这可能是由于编译错误、错误的代码逻辑或架构不兼容性等引起的。当处理器尝试执行无效的指令或操作时,会导致段错误。
- 动态内存分配问题: 使用动态内存分配(如
malloc
或new
)时,如果出现内存泄漏、重复释放已释放的内存或访问已释放的内存等问题,可能导致段错误。这些问题可能是由于错误的内存管理导致的。- **栈溢出:**如果程序的栈空间超出了其允许的范围,例如无限递归调用或大量局部变量使用导致的栈溢出,会产生段错误。
- **库或依赖项问题:**有时,段错误可能是由于使用损坏的库、不兼容的版本或缺少的依赖项引起的。库的错误使用或配置问题可能导致段错误。
- **硬件问题:**虽然比较罕见,但硬件故障(如内存损坏)也可能导致程序报告段错误并生成核心转储。
3.核心已转储是转储到那儿?
- 当程序发生段错误时,操作系统会生成一个名为
core
或core.<进程ID>
的核心转储文件,其中包含了程序崩溃时的内存映像和其他相关信息。这个core文件通常会被转储到当前工作目录下。- 但我的 core 文件并没有生成到程序的工作目录下,看下面解决...
解决方法
1.检查操作系统的核心转储文件(core dump file)生成设置
- 使用命令
ulimit -a
查看当前的核心转储文件大小限制和其他限制信息- 查找输出中的"core file size"(核心转储文件大小)字段,红框中的
0
表示当前禁用了核心转储文件的生成。可以更改这个限制来启用核心转储文件的生成。
2.更改 "core file size"字段 的限制来启用核心转储文件的生成
- 使用
ulimit -c unlimited
命令可以将core文件的大小限制设置为无限制,但通过ulimit
命令设置的参数仅在当前 shell 进程生效,也就是当前会话。一旦关闭终端窗口,设置将被重置为默认值。因此,这种修改并不是永久性的。(不推荐)- 如果想要在系统级别永久修改core文件的生成大小限制,需要进行操作系统的配置更改。可以通过修改
/etc/security/limits.conf
文件来设置core文件大小限制。添加或修改下面两行:
- * soft core unlimited
- * hard core unlimited
- 重启虚拟机,重新加载系统的参数配置,以确保更改生效(重启命令: sudo reboot)
3.话不多说,直接测试一下
- 不想跑服务端程序了,太费时了,直接写一个测试栗子,代码如下:
- 运行该测试栗子后,并没有在工程目录下生成 core 文件。
4.确定一下 core 文件的生成路径
- 查找资料说Linux 内核有一个参数
kernel.core_pattern
,用于指定生成核心转储文件时的文件名和路径模式,相关的配置文件为/proc/sys/kernel/core_pattern
。而在 Linux 中,可以使用sysctl
命令来检查和更改核心转储文件的生成路径限制。- 然后使用
sysctl kernel.core_pattern
命令来查看当前的核心转储文件生成路径。它输出了下面一行内容:
- kernel.core_pattern = |/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E
- 上面这一行是啥意思呢,解释如下:
|/usr/share/apport/apport
: 它是一个特殊的核心转储处理程序(core dump handler),它是一个用于收集和报告故障的工具。当进程收到SIGSEGV
或类似的信号时,内核将使用kernel.core_pattern
中指定的处理程序来处理和处理核心转储文件。%p
: 进程 ID。%s
: 目前正在运行的线程 ID。%c
: 产生核心转储文件的信号代码。%d
: 序列号,用于确保在同一目录中生成的核心转储文件具有唯一的名称。%P
: 父进程 ID。%u
: 用户名。%g
: 组名。%E
: 产生核心转储文件的可执行文件全路径。- 具体而言,
/usr/share/apport/apport
是一个用于 Ubuntu 系统的工具,它可以收集有关崩溃和故障的信息,并生成相应的错误报告。
5.修改 core 文件的生成路径
- 可以使用
sudo sysctl -w kernel.core_pattern=<path_to_directory>/core
命令将其恢复为期望的路径。确保<path_to_directory>
是一个有效的目录路径。比如
- sudo sysctl -w kernel.core_pattern=core
- 重新编译一下,在当前目录下生成了
core.<进程ID>
的核心转储文件,如下- 注意:以上方式修改
kernel.core_pattern
的值只在运行时生效,并不是永久的,在系统重启后,该更改将被重置为默认值。- 可以了解最后一节"番外知识"
6.使用调试工具 gdb 来加载和分析 core 文件
- 生成核心文件: 使用 gcc 编译记得加 -g 命令。
- 加载核心文件: 使用gdb命令行加载核心文件,将核心文件加载到调试环境中。
- gdb <可执行文件路径> <核心文件路径>
- 查看堆栈回溯: 运行gdb后,使用
bt
命令(或backtrace
)来查看堆栈回溯,它将显示程序在崩溃时的函数调用链。
- (gdb) bt
- 检查变量值: 你可以使用
- (gdb) print variable_name
- 跳转到特定帧: 使用
frame
命令可以在堆栈帧之间进行导航,并查看在特定帧上的堆栈信息。帧编号通常是从0开始按逆序分配的,也就是最底部帧的编号是0。
- (gdb) frame frame_number
- **分析原因:**分析堆栈回溯和变量值,可以帮助你定位程序崩溃的原因。一般情况下,最底部的堆栈帧提供了最初崩溃的位置。
- 操作如下: 说明 *P 未初始化
番外知识
1.对 /proc/sys/kernel/core_pattern 可以添加可写权限吗?
- 答案是不可以。默认权限如下(所有者具有读写权限,组用户和其他用户只有读取权限)。
- 对于
/proc/sys/kernel/core_pattern
文件,不能直接添加可读权限。这是因为/proc
目录和其下的文件是虚拟文件系统(procfs
)的一部分,用于提供对内核和进程信息的访问,它们的权限和所有权是由内核控制的,而不受Linux文件系统权限模型的限制。- 在
/proc
目录中,每个文件和目录的权限通常被设置为只读,不允许用户直接修改它们的权限。这是为了确保提供的信息的完整性和一致性,并防止对内核和进程状态的非授权更改。- 因此,无法通过常规的
chmod
命令或其他方式直接向/proc/sys/kernel/core_pattern
添加可读权限或更改其权限。尝试执行类似以下命令时会出现错误:
- sudo chmod +w /proc/sys/kernel/core_pattern
- 你会收到"Operation not permitted"或"不允许的操作"类似的错误消息。
2.在系统重启后,对
/proc/sys/kernel/core_pattern
文件的更改为什么被重置为默认值?
- 这是因为
/proc/sys/
目录中的文件是在内核启动期间动态生成的,其值来自于内核参数或其他系统设置。在系统重启时,这些文件会重新加载为其默认值或由某些配置文件指定的值。
3.怎么实现永久性修改
/proc/sys/kernel/core_pattern
文件呢?(这条有问题)
- 编辑
/etc/sysctl.conf
文件: (这种方式不太好,每次系统重启后要执行一下 sudo sysctl -p 命令才会修改/proc/sys/kernel/core_pattern的值)
- 可以编辑
/etc/sysctl.conf
文件,将核心转储文件模式的修改添加到该文件中,添加内容如下:
- kernel.core_pattern = core
- 在保存并退出文件后,使用以下命令重新加载配置,使新的核心转储文件模式生效:
- sudo sysctl -p
- 创建并编辑系统启动脚本: 可以编写一个脚本,以在系统启动时将核心转储文件模式设置为所需的值。将脚本放置在适当的位置,例如
/etc/init.d/
目录,并设置为在系统启动时执行。(测试了没有用)
- 创建启动脚本文件: 在所选择的目录中创建一个新文件
- sudo vim /etc/init.d/my_startup_script.sh
- 编写启动脚本: 在脚本中添加如下内容,保存成功并退出。
- #!/bin/bash
- echo "core" >> /proc/sys/kernel/core_pattern
- exit 0
- 赋予脚本执行权限: 使用以下命令为启动脚本文件赋予执行权限
- sudo chmod +x /etc/init.d/my_startup_script.sh
- 配置启动脚本的执行: 将启动脚本添加到系统的启动过程中,以确保在系统启动时执行
- sudo update-rc.d my_startup_script.sh defaults
- 若要禁用脚本的启动,可以使用以下命令(了解)
- sudo update-rc.d -f my_startup_script.sh remove
注意
- 需要注意的是,更改
/proc/sys/kernel/core_pattern
文件的权限和内容是敏感操作,可能会影响系统的稳定性和安全性。务必小心谨慎,并确保了解所做更改的影响。