嵌入式Linux宕机问题GDB调试(二)

Coredump文件分析宕机问题完整指南

一、环境准备

1.1 目标板启用核心转储

bash 复制代码
# 启用核心转储(无大小限制)
ulimit -c unlimited

# 设置核心转储文件格式
# %e - 可执行文件名
# %p - 进程ID
# %t - 时间戳
# %s - 信号编号
echo "/var/core/core.%e.%p.%s.%t" > /proc/sys/kernel/core_pattern

# 创建转储目录
mkdir -p /var/core
chmod 777 /var/core

# 验证配置
cat /proc/sys/kernel/core_pattern    # 查看转储格式
ulimit -a | grep core                 # 确认core大小限制

1.2 编译选项

makefile 复制代码
# 调试版本编译选项
CFLAGS += -g              # 生成调试符号
CFLAGS += -O0             # 禁用优化(便于调试)
CFLAGS += -fno-omit-frame-pointer  # 保留帧指针(保证调用栈完整)

# 发布版本(保留调试符号)
CFLAGS += -g -O2 -fno-omit-frame-pointer

# 分离调试符号
objcopy --only-keep-debug myapp myapp.debug    # 提取调试符号
strip --strip-debug --strip-unneeded myapp     # 剥离符号
objcopy --add-gnu-debuglink=myapp.debug myapp  # 关联调试文件

二、启动GDB分析Coredump

2.1 基本启动方式

bash 复制代码
# 本地分析
gdb ./myapp core.1234

# 交叉调试分析
arm-linux-gnueabihf-gdb ./myapp ./myapp.debug core.myapp.1234.11.1699999999

# 分步加载方式
arm-linux-gnueabihf-gdb

2.2 GDB内部加载命令

gdb 复制代码
# 加载可执行文件
file ./myapp

# 加载调试符号文件
add-symbol-file ./myapp.debug

# 加载核心转储文件
core-file core.myapp.1234.11.1699999999

# 设置共享库搜索路径(交叉调试必需)
set solib-search-path ./lib:/opt/sdk/sysroot/lib

# 设置系统根目录(用于查找库文件)
set sysroot /opt/sdk/sysroot

三、崩溃信息分析

3.1 查看基本信息

gdb 复制代码
# 查看当前信号信息(了解崩溃原因)
info signals

# 查看目标信息(确认core文件加载状态)
info target

# 查看程序状态
info program

# 查看当前线程
info threads

3.2 查看寄存器状态

gdb 复制代码
# 查看通用寄存器
info registers

# 查看所有寄存器(包括浮点、向量寄存器)
info all-registers

# 查看特定寄存器
info registers rax rbx rcx rdx    # x86架构
info registers r0 r1 r2 r3         # ARM架构

# 打印程序计数器(崩溃位置)
print $pc
print/x $pc    # 十六进制格式

# 打印栈指针
print $sp
print $fp      # 帧指针

# 查看指令指针处的指令
x/i $pc

四、调用栈分析

4.1 基本调用栈命令

gdb 复制代码
# 显示调用栈
bt

# 显示完整调用栈(包含局部变量)
bt full

# 显示指定层数的调用栈
bt 5           # 只显示5层
bt full 5      # 5层并包含局部变量

# 显示所有线程的调用栈
thread apply all bt
thread apply all bt full

4.2 栈帧操作

gdb 复制代码
# 查看当前栈帧信息
info frame

# 查看当前栈帧的参数
info args

# 查看当前栈帧的局部变量
info locals

# 切换栈帧(向上移动,调用者方向)
up
up 2           # 向上移动2层

# 切换栈帧(向下移动,被调用者方向)
down
down 2         # 向下移动2层

# 切换到指定栈帧
frame 0        # 切换到第0帧(崩溃位置)
frame 3        # 切换到第3帧

# 查看所有栈帧概要
info stack

4.3 查看栈帧源码

gdb 复制代码
# 显示当前位置的源代码
list

# 显示指定行数的源代码
list 50              # 显示第50行附近
list main.c:100      # 显示main.c第100行附近

# 显示更多源代码
list +               # 向后显示
list -               # 向前显示

# 显示指定范围
list 1,50            # 显示第1到50行

# 查看当前位置的反汇编
disassemble

# 查看指定范围的反汇编
disassemble main
disassemble 0x400500,0x400600

五、变量与内存分析

5.1 打印变量

gdb 复制代码
# 打印变量值
print var

# 打印指针指向的值
print *ptr

# 打印数组(前10个元素)
print arr[0]@10

# 指定格式打印
print/x var     # 十六进制
print/d var     # 十进制
print/t var     # 二进制
print/c var     # 字符
print/s str     # 字符串

# 打印结构体
print *struct_ptr
print struct_var.member

# 打印字符串(指定长度)
print str[0]@50

# 查看变量类型
whatis var              # 简单类型
ptype struct_name       # 详细类型定义

5.2 查看内存

gdb 复制代码
# x命令格式: x/NFU address
# N - 显示单元数量
# F - 显示格式(x十六进制, d十进制, t二进制, c字符, s字符串, i指令)
# U - 单元大小(b字节, h半字, w字, g双字)

# 查看内存(十六进制)
x/20x 0x400000          # 20个十六进制字
x/20xw 0x400000         # 20个十六进制字(4字节)
x/20xb 0x400000         # 20个十六进制字节

# 查看指令
x/20i $pc               # 从PC位置显示20条指令

# 查看字符串
x/s string_ptr          # 显示字符串

# 查看字符数组
x/50c buffer            # 显示50个字符

# 查看栈内容
x/20x $sp               # 查看栈顶内容
x/20gx $sp              # 查看栈顶(8字节单位)

5.3 自动显示

gdb 复制代码
# 设置自动显示(每次停止时自动打印)
display var
display/x $pc           # 自动显示PC(十六进制)

# 查看自动显示列表
info display

# 删除自动显示
undisplay 1             # 删除编号1的自动显示
delete display 1        # 同上

六、线程分析

6.1 查看线程信息

gdb 复制代码
# 列出所有线程
info threads

# 输出示例:
#   Id   Target Id         Frame
# * 1    Thread 0x7ffff7fc0740 (LWP 1234) "myapp" 0x00007ffff7dd5f7d in pthread_join
#   2    Thread 0x7ffff6bc0700 (LWP 1235) "myapp" worker_thread () at worker.c:100
#   3    Thread 0x7ffff63bf700 (LWP 1236) "myapp" 0x00007ffff7dd8ed5 in pthread_cond_wait

# 查看特定线程详细信息
info thread 2

# 查看线程数量
info threads | wc -l

6.2 切换线程

gdb 复制代码
# 切换到指定线程
thread 2

# 切换后查看该线程的调用栈
thread 2
bt

# 查看该线程的寄存器
thread 2
info registers

6.3 批量线程分析

gdb 复制代码
# 显示所有线程的调用栈
thread apply all bt

# 显示所有线程的完整调用栈
thread apply all bt full

# 显示指定范围的线程调用栈
thread apply 1-3 bt

# 对所有线程执行命令
thread apply all info registers

6.4 死锁检测

gdb 复制代码
# 查看所有线程调用栈,找出等待锁的线程
thread apply all bt

# 查看互斥锁状态
print *mutex
print mutex->__data.__owner    # 查看锁持有者线程ID

# 查看条件变量
print *cond

七、常见崩溃场景分析

7.1 段错误分析流程

gdb 复制代码
# 1. 加载core文件
gdb ./myapp core.1234

# 2. 查看崩溃信号
info signals

# 3. 查看调用栈
bt

# 4. 切换到崩溃栈帧
frame 0

# 5. 查看源代码
list

# 6. 查看局部变量
info locals
info args

# 7. 查看相关指针是否为空
print ptr

# 8. 查看寄存器
info registers

# 9. 反汇编分析
disassemble

7.2 空指针解引用示例

gdb 复制代码
# 假设崩溃在 data->value 访问
(gdb) bt
#0  0x0000000000400abc in process_data (data=0x0) at main.c:50
#1  0x0000000000400bcd in main () at main.c:100

# 查看崩溃位置
frame 0
list

# 查看指针值
print data
$1 = (Data *) 0x0    # 空指针!

# 查看寄存器
info registers
rax            0x0    0    # 空指针在rax中

# 反汇编确认
disassemble
# mov    (%rdi),%rax  # 尝试解引用空指针

7.3 数组越界分析

gdb 复制代码
# 查看数组边界
print sizeof(arr)
print sizeof(arr)/sizeof(arr[0])    # 数组长度

# 查看访问的索引
print index

# 查看数组内容
print arr[0]@20

# 使用观察点检测越界
watch arr[100]           # 监控越界位置

7.4 栈溢出分析

gdb 复制代码
# 查看栈指针
print $sp

# 查看栈帧信息
info frame

# 查看栈内存
x/50x $sp

# 检查栈保护函数
info functions __stack_chk_fail

7.5 内存释放后使用

gdb 复制代码
# 查看指针值
print ptr

# 查看指针指向的内存
x/20x ptr

# 检查是否为已释放区域(通常为0x0000000000000000或无效地址)
# 使用valgrind或AddressSanitizer更容易检测

八、高级分析技巧

8.1 条件断点与观察点

gdb 复制代码
# 设置观察点(数据变化时停止)
watch global_var         # 变量被写入时停止
rwatch global_var        # 变量被读取时停止
awatch global_var        # 变量被读或写时停止

# 查看观察点
info watchpoints

# 设置条件断点
break main.c:100 if x > 10
break process_data if data == NULL

# 修改现有断点条件
condition 1 count > 100

8.2 内存映射查看

gdb 复制代码
# 查看内存映射
info proc mappings

# 输出示例:
# Start Addr           End Addr       Size     Offset objfile
#    0x400000           0x401000     0x1000        0x0 /home/user/myapp
#    0x600000           0x601000     0x1000        0x0 /home/user/myapp
#  0x7ffff7dd5000     0x7ffff7dfc000   0x27000        0x0 /lib/libc.so.6

# 查看共享库信息
info sharedlibrary

8.3 反向调试

gdb 复制代码
# 启用执行记录
record
record full

# 运行程序
continue

# 反向执行
reverse-continue        # 反向继续执行
reverse-step            # 反向单步(进入函数)
reverse-next            # 反向单步(跳过函数)
reverse-stepi           # 反向单条指令

# 查看记录状态
info record

# 停止记录
record stop

8.4 函数调用与变量修改

gdb 复制代码
# 在调试中调用函数
call func(1, 2)
call printf("value = %d\n", x)

# 修改变量值
set var = 10
set *ptr = 0
set {int}0x400000 = 100

# 修改字符串
set {char[10]}buffer = "hello"

# 强制函数返回
return
return 10               # 返回指定值

九、批处理自动化分析

9.1 命令行批处理

bash 复制代码
# 使用-ex参数执行多个命令
arm-linux-gnueabihf-gdb -batch \
    -ex "file ./myapp" \
    -ex "core-file ./core.1234" \
    -ex "bt full" \
    -ex "thread apply all bt" \
    -ex "info registers" \
    > analysis.txt

9.2 GDB脚本文件

gdb 复制代码
# analysis.gdb - 保存为脚本文件
set pagination off      # 关闭分页
set confirm off         # 关闭确认提示

# 加载文件
file ./myapp
core-file ./core.1234

# 设置日志输出
set logging file analysis.txt
set logging on

# 执行分析
echo === Signal Info ===\n
info signals

echo \n=== Registers ===\n
info registers

echo \n=== Backtrace ===\n
bt full

echo \n=== All Threads ===\n
thread apply all bt

echo \n=== Memory Map ===\n
info proc mappings

echo \n=== Disassembly ===\n
disassemble

# 退出
set logging off
quit
bash 复制代码
# 执行脚本
gdb -x analysis.gdb

9.3 完整自动化分析脚本

bash 复制代码
#!/bin/bash
# auto_analyze.sh

CORE_FILE=$1
BINARY=$2
DEBUG_FILE=$3

if [ -z "$CORE_FILE" ] || [ -z "$BINARY" ]; then
    echo "Usage: $0 <core-file> <binary> [debug-file]"
    exit 1
fi

GDB="arm-linux-gnueabihf-gdb"
REPORT="analysis_$(basename $CORE_FILE).txt"

echo "=== Crash Analysis Report ===" > $REPORT
echo "Date: $(date)" >> $REPORT
echo "Core: $CORE_FILE" >> $REPORT
echo "Binary: $BINARY" >> $REPORT
echo "" >> $REPORT

$GDB -batch \
    -ex "file $BINARY" \
    ${DEBUG_FILE:+-ex "add-symbol-file $DEBUG_FILE"} \
    -ex "core-file $CORE_FILE" \
    -ex "set pagination off" \
    -ex "echo === Signal Info ===\\n" \
    -ex "info signals" \
    -ex "echo \\n=== Registers ===\\n" \
    -ex "info registers" \
    -ex "echo \\n=== Backtrace ===\\n" \
    -ex "bt full" \
    -ex "echo \\n=== All Threads ===\\n" \
    -ex "thread apply all bt" \
    -ex "echo \\n=== Memory Map ===\\n" \
    -ex "info proc mappings" \
    -ex "echo \\n=== Disassembly ===\\n" \
    -ex "disassemble" \
    >> $REPORT 2>&1

echo "Analysis complete: $REPORT"

十、信号与错误对照表

信号 编号 名称 常见原因
SIGSEGV 11 段错误 空指针解引用、数组越界、释放后使用
SIGABRT 6 中止 abort()调用、assert()失败、内存分配失败
SIGBUS 7 总线错误 内存对齐错误、映射文件访问错误
SIGFPE 8 浮点异常 除零、浮点溢出
SIGILL 4 非法指令 执行损坏的代码、错误的函数指针
SIGPIPE 13 管道破裂 写入已关闭的管道/套接字

十一、GDB命令速查表

命令 简写 说明
file 加载可执行文件
core-file 加载核心转储文件
add-symbol-file 加载调试符号文件
run r 运行程序
continue c 继续执行
next n 单步执行(跳过函数)
step s 单步执行(进入函数)
finish fin 执行到函数返回
backtrace bt 显示调用栈
frame f 切换/显示栈帧
up 向上移动栈帧
down 向下移动栈帧
print p 打印变量/表达式
display 设置自动显示
list l 显示源代码
disassemble dis 反汇编
info i 查看各种信息
thread t 线程操作
break b 设置断点
watch wa 设置观察点
x 查看内存
set 修改变量/设置选项
call 调用函数
quit q 退出GDB

十二、典型分析流程总结

复制代码
1. 加载core文件
   gdb ./myapp core.1234

2. 确认崩溃信号
   info signals

3. 查看调用栈
   bt full

4. 切换到崩溃栈帧
   frame 0

5. 查看源代码
   list

6. 查看局部变量和参数
   info locals
   info args

7. 查看相关变量值
   print var
   print *ptr

8. 查看寄存器
   info registers

9. 查看内存
   x/20x address

10. 多线程程序查看所有线程
    thread apply all bt

11. 分析死锁(如适用)
    print *mutex
    print mutex->__data.__owner

12. 反汇编分析(如需要)
    disassemble
相关推荐
lifewange1 小时前
VMware如何安装并配置CentOs镜像
linux·运维·centos
j_xxx404_1 小时前
【Linux进程间通信】硬核剖析:消息队列、信号量、内核IPC资源统一管理与mmap加餐
linux·运维·开发语言·c++·人工智能·ai
keyipatience1 小时前
14.Linux进程状态:从运行到僵尸的奥秘
linux
SilentSamsara2 小时前
生成器完全指南:`yield` 与惰性求值的工程价值
linux·开发语言·python·算法·机器学习·青少年编程
不怕犯错,就怕不做13 小时前
RK3562的CPU如何降频及关闭硬件编解码
linux·驱动开发·嵌入式硬件
CoderMeijun13 小时前
Linux 文件操作详解:open/read/write/lseek 系统调用
linux·文件操作·系统调用·open·文件描述符
可可西里_X_back13 小时前
Linux学习(二)- 驱动开发步骤
linux·驱动开发·学习
Hical_W13 小时前
Hical 踩坑实录五部曲(二):MSVC / GCC / Clang 三平台 C++20 编译差异
linux·windows·经验分享·嵌入式硬件·macos·开源·c++20
活蹦乱跳酸菜鱼14 小时前
linux ATF BL2执行过程
linux