该脚本可作为程序的启动脚本,其使用日志文件记录程序运行时的标准输出(stdout)和标准错误(stderr)信息,使用核心转储文件记录程序崩溃时的各种状态信息。具体内容如下:
#!/bin/bash
# ===== 配置区域 =====
APP_NAME="Widgets" # 可执行程序名称
LOG_DIR="/opt/auto/logs" # 日志文件目录
QT_LOG_PATTERN="${LOG_DIR}/*.log" # Qt 日志文件模式
CORE_PATTERN="${LOG_DIR}/core.${APP_NAME}.*" # 核心转储文件模式
# ====================
# 创建日志目录
mkdir -p "$LOG_DIR"
# 生成带时间戳的日志文件名
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="${LOG_DIR}/${APP_NAME}_${TIMESTAMP}.log"
# 启用核心转储
echo "===== 启用核心转储 =====" | tee -a "$LOG_FILE"
ulimit -c unlimited
echo "/opt/auto/logs/core.${APP_NAME}.%e.%t" | sudo tee /proc/sys/kernel/core_pattern
# 设置设备权限
echo "===== 设置设备权限 =====" | tee -a "$LOG_FILE"
sudo chmod 777 /dev/ttyS0
sudo chmod 777 /dev/ttyS1
# 设置环境变量
export LD_LIBRARY_PATH="/opt/auto/lib/:$LD_LIBRARY_PATH"
cd /opt/auto || exit 1
# 记录启动信息
{
echo "===== 启动时间: $(date) ====="
echo "应用程序: $APP_NAME"
echo "工作目录: $(pwd)"
echo "环境变量 LD_LIBRARY_PATH: $LD_LIBRARY_PATH"
echo "核心转储模式: $(cat /proc/sys/kernel/core_pattern)"
echo "当前用户: $(whoami)"
echo "显示环境: $DISPLAY"
echo "系统信息: $(uname -a)"
echo "内存信息: $(free -h)"
} >> "$LOG_FILE"
# 启动程序并捕获输出
echo "===== 启动应用程序 =====" >> "$LOG_FILE"
./"$APP_NAME" >> "$LOG_FILE" 2>&1
EXIT_CODE=$?
# 记录退出信息
{
echo "===== 程序退出时间: $(date) ====="
echo "退出代码: $EXIT_CODE"
} >> "$LOG_FILE"
# 增强崩溃诊断
if [ $EXIT_CODE -ne 0 ]; then
echo "===== 开始崩溃诊断 =====" >> "$LOG_FILE"
# 1. 记录最近的 Qt 日志
echo "----- 最近的 Qt 日志 (最多20行) -----" >> "$LOG_FILE"
LATEST_QT_LOG=$(ls -t $QT_LOG_PATTERN | head -n 1)
if [ -f "$LATEST_QT_LOG" ]; then
echo "检测到 Qt 日志文件: $LATEST_QT_LOG" >> "$LOG_FILE"
tail -n 20 "$LATEST_QT_LOG" >> "$LOG_FILE"
else
echo "未找到 Qt 日志文件" >> "$LOG_FILE"
fi
# 2. 检查核心转储
echo "----- 核心转储检查 -----" >> "$LOG_FILE"
if ls $CORE_PATTERN 1> /dev/null 2>&1; then
echo "发现核心转储文件:" >> "$LOG_FILE"
ls -lh $CORE_PATTERN >> "$LOG_FILE"
echo "使用以下命令分析: gdb /opt/auto/$APP_NAME /opt/auto/logs/core.${APP_NAME}.*" >> "$LOG_FILE"
else
echo "未发现核心转储文件" >> "$LOG_FILE"
fi
# 3. 系统资源检查
echo "----- 系统资源状态 -----" >> "$LOG_FILE"
echo "内存使用: $(free -h | awk '/Mem/{print $3"/"$2}')" >> "$LOG_FILE"
echo "CPU负载: $(uptime | awk -F'load average: ' '{print $2}')" >> "$LOG_FILE"
echo "磁盘空间: $(df -h / | awk 'NR==2{print $4 " free"}')" >> "$LOG_FILE"
# 4. 进程检查
echo "----- 相关进程检查 -----" >> "$LOG_FILE"
pgrep -a "$APP_NAME" >> "$LOG_FILE" 2>&1
echo "===== 崩溃诊断结束 =====" >> "$LOG_FILE"
fi
# 添加日志文件权限设置(确保所有用户可读)
chmod 644 "$LOG_FILE" 2>/dev/null
exit $EXIT_CODE
程序崩溃后,可使用 GDB 指令对核心转储文件进行分析,步骤示例如下:
(1)查看核心转储功能
ulimit -c # 查看核心转储功能是否被启用,如果返回 0 代表未未启用,返回 unlimited 代表已启用
ulimit -c unlimited # 启用核心转储功能
(2)如果没有安装gdb,则使用以下指令安装:
sudo apt-get install gdb
(3)gdb /opt/auto/Widgets /opt/auto/logs/xxx # Widgets为可执行程序名称,xxx 为核心转储文件名称
(4)bt # 查看堆栈跟踪,显示程序崩溃时的调用层次
输出示例:
#0 0x0000555555555169 in crashFunction() ()
#1 0x0000555555555191 in main ()
(5)frame 0 # 切换到栈帧0,即 crashFunction() 函数
输出示例:
#0 0x0000555555555169 in crashFunction() ()
(6)list # 显示当前栈帧位置附近的源代码
(7)分析代码
注意:分析核心转储时如果需要更多详细的代码信息,则需要在 debug 模式下运行程序,如果是使用命令行编译链接程序,则需要加上"-g"参数,如:
gcc -g -o myapp myapp.c
g++ -g -o myapp myapp.cpp
1、使用 systemd-coredump 服务来处理核心转储
(1)列出所有的核心转储:
coredumpctl list
(2)查找所需的核心转储:
coredumpctl list Widgets # 按程序名称查找
coredumpctl list --since "5 minutes ago" # 按时间查找
(3)查看核心转储信息:
coredumpctl info # 查看最新生成的转储信息
coredumpctl info Widgets # 查看指定程序的转储信息
coredumpctl info 1234 # 查看指定PID的转储信息
(4)使用 GDB 调试核心转储
coredumpctl debug # 调试最新生成的转储
coredumpctl debug Widgets # 调试指定程序的转储
coredumpctl debug --since "2025-07-09 10:20:00" # 调试指定时间的转储
(5)导出转储文件
coredumpctl dump > core.Widgets # 导出最新生成的转储文件
coredumpctl dump Widgets > core.Widgets # 导出指定程序的转储文件
(6)文件导出后的 GDB 调试
gdb /opt/auto/Widgets -c core.Widgets
2、修改核心转储文件生成路径
方法一:echo "/opt/auto/logs/core.${APP_NAME}.%e.%t" | sudo tee /proc/sys/kernel/core_pattern
其中,core_pattern 文件是一个虚拟文件,用于设置核心转储文件的命名模式和存储位置,而以上指令通过管道("|")将具体的路径和命名格式传递给了 core_pattern 文件,以此来实现将核心转储文件存储在指定位置的功能
方法二:sudo sysctl -w kernel.core_pattern=/opt/auto/logs/core.%e.%t
该指令通过 sysctl 系统调用修改内核参数,最终也会将路径和命名格式写入到 core_pattern 文件中,和方法一功能类似。但不同的是,方法二不支持使用变量名(如:${APP_NAME})来动态构造命名字符串
3、核心转储文件命名占位符
%e # 可执行文件名称
%t # Unix时间戳
%p # 进程ID(PID)
%s # 导致崩溃的信号编号
%u # 用户ID(UID)
示例:./Widgets >> "$LOG_FILE" 2>&1
(1)./Widgets # 执行可执行程序 Widgets
(2)>> "$LOG_FILE" # 将标准输出重定向到日志文件(">>"代表追加模式,而">"代表覆盖模式)
(3)2>&1 # "2>"表示重定向标准错误,"&1"表示标准输出当前指向的位置,"2>&1"表示将标准错误也重定向到标准输出所在的位置,即日志文件中
注意,在未调用 qInstallMessageHandler() 的情况下,程序中的 qDebug() 会默认输出到标准错误(stderr),此时会被 ./Widgets >> "$LOG_FILE" 2>&1 语句重定向捕获到日志文件中。而如果调用了 qInstallMessageHandler(outputMessage),则 qDebug() 的输出不再发送到标准错误(stderr),而是由 outputMessage 函数处理