#软件调试
C++(Qt)软件调试—linux下生成/调试Core文件(3)
文章目录
- C++(Qt)软件调试---linux下生成/调试Core文件(3)
- 前言
- 1、C++生成Core和使用GDB调试
- 1、环境
- 2、C++生成Core文件
- 3、使用gdb工具调试core可定位段错误位置;
- 4、修改生成的Core文件路径和名称
- 5、实现过程及结果
- 2、Qt程序生成Core和使用GDB调试
- 1、环境
- 2、Qt生成Core文件
- 3、调试Qt程序生成的Core文件
- 4、Qt在Release模式下调试Core文件
更多精彩内容 |
---|
👉个人内容分类汇总 👈 |
👉C++软件调试、异常定位 👈 |
Windows下封装的崩溃报告模块
前言
-
什么是Core文件,有什么用
Core文件是在程序崩溃或异常终止时由操作系统生成的一个二进制文件,它包含了进程在崩溃前的内存映像。Core文件的作用是帮助程序员分析程序崩溃的原因,进行程序调试。
当程序崩溃时,Core文件中存储了进程的堆栈、寄存器、内存等信息。程序员可以使用调试工具如GDB来分析Core文件,以确定程序崩溃的原因。通过分析Core文件,程序员可以了解程序在崩溃前发生的情况,包括变量的值,函数调用的堆栈信息等,从而找到程序中的错误。
Core文件还可用于恢复程序状态。如果程序在处理大量数据时崩溃,程序员可以使用Core文件来恢复程序状态,从崩溃点开始进行调试,以便更快地找到问题并修复它。
总之,Core文件是程序调试和故障排除中的重要工具,可以帮助程序员快速定位并解决程序中的错误。
1、C++生成Core和使用GDB调试
1、环境
-
测试系统:
- ubuntu-16.04.6-desktop-i386.iso
- ubuntu-22.04.2-desktop-amd64.iso
-
为了排除其它影响,每个测试的系统环境都是在虚拟机中新配置的纯净环境,没有安装任何其它软件和进行任何配置。
2、C++生成Core文件
-
创建一个文件夹Code
mkdir Code
-
进入Code文件夹,创建一个main.cpp文件
cd Code touch main.cpp
-
打开main.cpp,写入下列代码
touch main.cpp
#include <stdio.h> int main() { char* str = NULL; *str = 'a'; return 0; }
-
在ubuntu环境下,默认不生成core文件,需要生成core文件时,需要使用ulimit进行设定;
注意: 此命令设置的core文件大小只在当前终端,当前用户有效,重新打开一个终端窗口或者切换用户就会失效;
ulimit-c”命令设置或获取core文件大小限制,该限制指定进程崩溃时可以创建的核心转储文件的最大大小。
核心转储文件包含崩溃时进程内存的映像,这对于调试崩溃原因非常有用。
“-c”选项以块或字节为单位指定核心文件大小限制,具体取决于系统配置。
# 列出当前终端所有资源限制 ulimit -a # core文件大小 ulimit -c # 设置生成core文件的大小:1024k ulimit -c 1024 # 设置生成core文件的大小:不受限制(推荐使用这个,因为如果是Qt之类的程序生成的Core文件会很大,如果指定的大小不够则可能生成的Core文件无法使用) ulimit -c unlimited
-
如果您想让 ‘ulimit -c unlimited’ 命令永久生效,您需要在系统启动时将其添加到 shell 配置文件中。具体的配置文件路径可能会因操作系统而异,一些常见的配置文件路径如下:
- /etc/profile (适用于所有用户)
- ~/.bashrc (适用于当前用户)
- /etc/bashrc (适用于所有用户)
您可以使用文本编辑器打开适当的文件,并将 ‘ulimit -c unlimited’ 命令添加到文件的末尾。
保存文件后,使用source .bashrc
命令立即生效或者下次启动系统时,该命令将自动执行并永久生效。
但是永久生效就意味着系统中只要由程序异常结束就会生成Core文件,这会导致系统中垃圾文件越来越多,所以除非必要,还是使用临时设置。
如果设置了永久生效,建议将指定生成Core路径时使用绝对路径,将所有生成的Core文件放到同一个文件夹下,方便管理。
-
编译main.cpp文件,用
-g
选项生成的调试信息来显示崩溃或错误时的源代码、变量和堆栈跟踪;g++ main -g
-
使用
./a.out
命令执行编译后的可执行程序,会出现段错误segmentation fault (core dumped)
,并在当前路径生成core文件;
3、使用gdb工具调试core可定位段错误位置;
命令为 gdb 可执行程序 Core文件
;
如下图所示可看出段错误为main.cpp文件中main()函数中,位于文件第6行;
4、修改生成的Core文件路径和名称
core_pattern文件是一个系统文件,用来指定生成Core文件的路径和文件名格式。可以通过修改core_pattern文件来更改Core文件的生成路径和文件名格式。
【临时设置(立即生效)】在Linux系统中,core_pattern文件通常位于/proc/sys/kernel/core_pattern路径下。可以使用命令行编辑器如vi或nano来编辑该文件。
需要注意的是,core_pattern文件是一个虚拟文件,它是在内存中创建的,并不是一个真正的文件。因此,修改core_pattern文件的设置选项只在当前系统运行时有效,重启系统后设置会被恢复为默认值。
【永久设置(需要重启)】如果要永久更改设置选项,可以在系统启动时执行相应的命令,或者修改 /etc/sysctl.conf 文件中的相应设置选项。
可以到/etc/sysctl.conf中,在文件末尾添加上core文件的存储路径(然后重启系统):
kernel.core_pattern=core_%e_%p_%t
-
默认生成的Core文件名就是Core,所以后面生成的会覆盖之前生成的文件,如果想要不覆盖可用自己修改生成的文件名;
-
进入root用户模式;
sudo su
-
查看
/proc/sys/kernel/core_pattern
文件原始配置;cat /proc/sys/kernel/core_pattern 或者 sysctl kernel.core_pattern
-
修改
/proc/sys/kernel/core_pattern
文件中Core生成路径、文件名配置;# 表示在可执行程序当前路径生成Core文件,文件名格式为core_%e_%p_%t echo "core_%e_%p_%t" > /proc/sys/kernel/core_pattern 或者 sudo sysctl -w kernel.core_pattern="core_%e_%p_%t" # 表示在可执行程序当前路径下的Cores文件夹中(需要自己手动创建Cores文件夹)生成Core文件,文件名格式为core_%e_%p_%t echo "./Cores/core_%e_%p_%t" > /proc/sys/kernel/core_pattern # 表示在绝对路径/home/mhf/Cores/中(需要自己手动创建Cores文件夹)生成Core文件,文件名格式为core_%e_%p_%t echo "/home/mhf/Cores/core_%e_%p_%t" > /proc/sys/kernel/core_pattern
如果不包含路径,则core_pattern文件中的内容为【在执行文件当前路径】创建相应的core文件;
如果包含路径,则需要保证路径存在,否则不会生成Core文件;
注意:如果生成的路径的权限比较高,也不会生成Core文件,例如:
# 表示在绝对路径/Cores/中(需要自己手动创建Cores文件夹)生成Core文件,文件名格式为core_%e_%p_%t # /Cores路径需要root权限,如果使用普通用户权限执行a.out则不会生成Core文件,需要使用sudo ./a.out执行才可以生成 echo "/Cores/core_%e_%p_%t" > /proc/sys/kernel/core_pattern
其中可选参数列表为:
%p:将进程ID(PID)插入文件名
%u:将进程的真实用户ID(RUID)插入文件名
%g:将进程的真实组ID(RGID)插入文件名
%s:将生成Core文件时,进程的信号编号插入文件名
%t:将内核转储发生的UNIX时间戳(秒级)插入文件名
%h:将主机名插入文件名
%e:将核心转储可执行文件名插入文件名
5、实现过程及结果
- 显示(core dumped)表示成功生成Core文件。
- ubuntu16.04使用过程及结果如下
- ubuntu22.04使用过程及结果如下
2、Qt程序生成Core和使用GDB调试
1、环境
- 系统:ubuntu22.04
- Qt版本:V5.14.2
2、Qt生成Core文件
-
打开一个终端,如下图所示,将生成的Core文件大小设置位为不限制,设置core生成文件名称,防止覆盖;
-
由于这两项设置是临时的,重新打开一个终端窗口或者通过点击图标方式运行Qt都无效,只能在当前终端窗口通过命令行运行Qt编译的可执行程序在异常时才可以生成Core文件;
-
如果是通过点击图标运行的Qt编译生成的可执行程序,需要在当前终端窗口通过命令行运行可执行程序才可以生成core文件;
-
Qt程序启动后新建一个工程;
-
在pro文件添加
DESTDIR = $$PWD/bin
,将编译后的可执行程序放到bin文件夹下; -
在代码中编写一个空指针异常代码、一个除0异常代码;
-
使用debug编译运行后,分别点击空指针异常代码和除0异常代码的按键;
-
在点击异常代码的按键后,程序会异常退出,在可执行程序所在的bin文件夹下可看到生成了两个Core文件;
-
从下图中可看出生成的两个core文件非常大,所以:
- 最好使用
ulimit -c unlimited
指定无限大小的Core,否则生成的Core大小不足,则无法使用; - 没必要时必要将
ulimit -c unlimited
设置为永久有效,否则系统可能中会生成非常多的core文件,导致内存不足。
- 最好使用
3、调试Qt程序生成的Core文件
-
在上一节中使用C++生成的Core文件我们通过GDB命令行去调试,定位异常位置;
-
这里同样使用GDB命令行进行调试;
- 可用看出异常位置在
../untitled/widget.cpp
文件中的第29行,位于Widget::on_pushButton_2_clicked()
函数中; - 异常原因是:
Arithmetic exception.
;
- 可用看出异常位置在
-
同样使用GDB命令行调试空指针生成的core文件
- 只能看出异常原因是
Segmentation fault.
(段错误),无法直接看出是异常发生位置是在哪个文件,哪一行,哪个函数; - 先不用急, 后面我会将如何使用QtCreator调试生成的Core文件,会更加简单方便,并且也可以直接显示出异常位置是在哪个文件,哪一行,哪个函数;
-
这里看不到异常位置可能是不在这个堆栈内,可以使用
bt
命令打印函数调用信息(堆栈);- 可以看到定位异常在widget.cpp文件中的21行的
Widget::on_pushButton_clicked()
函数内;
- 可以看到定位异常在widget.cpp文件中的21行的
- 只能看出异常原因是
4、Qt在Release模式下调试Core文件
-
在debug和Release编译的程序发生异常退出时都会生成Core文件,但是release模式默认下生成的可执行程序经过编译器优化,不方便调试和定位异常位置;
-
使用Release默认生成的可执行程序进行调试结果如下,通过GDB的
bt
命令打印堆栈信息可以看出异常位置在Widget::on_pushButton_clicked()
函数中,没有具体到哪个文件,哪一行; -
Release模式下,可用通过在pro文件添加下列三行代码关闭打印输出的信息,经过测试,如果添加了
DEFINES += QT_NO_DEBUG_OUTPUT
则无法定位异常位置(Debug模式不影响);DEFINES += QT_NO_DEBUG_OUTPUT # 关闭调试信息输出 qDebug() --- 无法定位异常位置 DEFINES += QT_NO_INFO_OUTPUT # 关闭普通信息输出 qInfo() --- 不影响 DEFINES += QT_NO_WARNING_OUTPUT # 关闭警告信息输出 qWarning() --- 不印象
-
Release模式下,在pro文件添加下列三行代码,生成调试信息,可定位到异常位置在哪个文件,哪个函数,哪一行;
# QMAKE_CC += -g是一个QMake变量,它将-g选项添加到C编译器命令行选项中。-g选项指示编译器生成调试信息以及目标代码。 # 调试信息包含有关源代码的详细信息,例如文件名、行号和变量名。在调试程序时,这些信息对开发人员非常有用,因为它们可以跟踪程序的执行并识别错误的位置。 # 请注意,添加-g标志会增加目标文件和可执行文件的大小,因此不希望在程序的最终发布版本中包含调试信息。 QMAKE_CC += -g # QMAKE_CXX += -g是一个QMake变量,它将-g选项添加到C++编译器命令行选项中。-g选项指示编译器生成调试信息以及目标代码。 QMAKE_CXX += -g # QMAKE_LINK += -g是一个QMake变量,它将-g选项添加到链接器命令行选项中。-g选项指示链接器将调试信息包含在可执行文件中。 QMAKE_LINK += -g
-
Release模式下同时添加
-g
和DEFINES += QT_NO_DEBUG_OUTPUT
可定位到异常位置在哪个文件,哪个函数,哪一行; -
只需要在pro文件中加上下列三行代码,就可以在生成的可执行程序中包含调试信息;
- 其实感觉这个功能比较鸡肋,Debug编译的程序就已经带有了调试信息,所以如果调试问题最好使用Debug模式编译,而Release是编译发布程序的模式,经过优化后去除调试信息,以获得更高的性能和较小的程序文件;
- 而加上这三行代码后在可执行程序中包含调试信息,哪就和Debug没什么区别了,性能比较低了;
- 当然,有些问题会在Release模式下出现,在Debug模式下不会出现,这种情况就可以使用这三行代码。
# QMAKE_CC += -g是一个QMake变量,它将-g选项添加到C编译器命令行选项中。-g选项指示编译器生成调试信息以及目标代码。 # 调试信息包含有关源代码的详细信息,例如文件名、行号和变量名。在调试程序时,这些信息对开发人员非常有用,因为它们可以跟踪程序的执行并识别错误的位置。 # 请注意,添加-g标志会增加目标文件和可执行文件的大小,因此不希望在程序的最终发布版本中包含调试信息。 QMAKE_CC += -g # QMAKE_CXX += -g是一个QMake变量,它将-g选项添加到C++编译器命令行选项中。-g选项指示编译器生成调试信息以及目标代码。 QMAKE_CXX += -g # QMAKE_LINK += -g是一个QMake变量,它将-g选项添加到链接器命令行选项中。-g选项指示链接器将调试信息包含在可执行文件中。 QMAKE_LINK += -g
-
从下图可以看出所有模式生成的Core文件大小都一样,但是Debug模式编译生成的可执行程序是Release模式编译的可执行程序的大小的31倍左右,而Release模式加-g后编译生成的可执行程序大小和Debug模式生成的大小差不多。