#软件调试
更多精彩内容 |
---|
个人内容分类汇总 |
C++软件调试、异常定位 |
Windows下封装的崩溃报告模块
什么是Core文件,有什么用
Core文件是在程序崩溃或异常终止时由操作系统生成的一个二进制文件,它包含了进程在崩溃前的内存映像。Core文件的作用是帮助程序员分析程序崩溃的原因,进行程序调试。
当程序崩溃时,Core文件中存储了进程的堆栈、寄存器、内存等信息。程序员可以使用调试工具如GDB来分析Core文件,以确定程序崩溃的原因。通过分析Core文件,程序员可以了解程序在崩溃前发生的情况,包括变量的值,函数调用的堆栈信息等,从而找到程序中的错误。
Core文件还可用于恢复程序状态。如果程序在处理大量数据时崩溃,程序员可以使用Core文件来恢复程序状态,从崩溃点开始进行调试,以便更快地找到问题并修复它。
总之,Core文件是程序调试和故障排除中的重要工具,可以帮助程序员快速定位并解决程序中的错误。
测试系统:
为了排除其它影响,每个测试的系统环境都是在虚拟机中新配置的纯净环境,没有安装任何其它软件和进行任何配置。
创建一个文件夹Code
mkdir Code
进入Code文件夹,创建一个main.cpp文件
cd Code
touch main.cpp
打开main.cpp,写入下列代码
touch main.cpp
#include
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文件;
命令为 gdb 可执行程序 Core文件
;
如下图所示可看出段错误为main.cpp文件中main()函数中,位于文件第6行;
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:将核心转储可执行文件名插入文件名
打开一个终端,如下图所示,将生成的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文件,导致内存不足。在上一节中使用C++生成的Core文件我们通过GDB命令行去调试,定位异常位置;
这里同样使用GDB命令行进行调试;
../untitled/widget.cpp
文件中的第29行,位于Widget::on_pushButton_2_clicked()
函数中;Arithmetic exception.
;同样使用GDB命令行调试空指针生成的core文件
Segmentation fault.
(段错误),无法直接看出是异常发生位置是在哪个文件,哪一行,哪个函数;在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文件中加上下列三行代码,就可以在生成的可执行程序中包含调试信息;
# 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模式生成的大小差不多。