嵌入式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
相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒1 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言