嵌入式Linux崩溃分析实战:没有JTAG,怎么"空手"定位死机问题?

嵌入式Linux崩溃分析实战:没有JTAG,怎么"空手"定位死机问题?

一句话总结:产品量产了才发现死机,没有JTAG接口,串口日志也不够用------用ramoops/pstore、kdump、ftrace这三个武器,大部分崩溃问题不用硬件调试器也能定位。

做嵌入式Linux产品的人,最怕的不是功能做不出来,而是产品在客户现场死机了,你拿不到任何调试信息

量产设备通常没有JTAG接口(成本原因或者外壳已经封死),甚至串口都不一定引出来。这时候你怎么定位问题?

90%问题靠以下三个工具可以解决。今天把它们分享一下。

武器一:ramoops/pstore --- 内核崩溃后的"黑匣子"

问题

内核panic了,系统直接重启。你没看到任何日志,只知道"设备又重启了"。

方案

用ramoops(在ARM64上是pstore)保留panic前后的内核日志,重启后自动保存。

操作

内核配置:

ini 复制代码
CONFIG_PSTORE=y
CONFIG_PSTORE_RAM=y
CONFIG_PSTORE_CONSOLE=y
CONFIG_PSTORE_PMSG=y
CONFIG_PSTORE_FTRACE=y

设备树中预留内存:

dts 复制代码
reserved-memory {
    ramoops@0xBF000000 {
        compatible = "ramoops";
        reg = <0x0 0xBF000000 0x0 0x00100000>;
        record-size = <0x00020000>;
        console-size = <0x00020000>;
        ftrace-size = <0x00020000>;
        pmsg-size = <0x00020000>;
    };
};

重启后读取:

bash 复制代码
# 重启后检查
mount -t pstore pstore /sys/fs/pstore
ls /sys/fs/pstore/
cat /sys/fs/pstore/console-ramoops-0
cat /sys/fs/pstore/dmesg-ramoops-*

真实案例

去年一个客户反馈说设备每天凌晨3点准时重启。我们在ramoops里可以看到类似如下关键线索:

csharp 复制代码
[12345.678901] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000018
[12345.678902] Internal error: Oops: 96000004 [#1] PREEMPT SMP
[12345.678903] Modules linked in: xxx_driver(O)
[12345.678904] PC is at xxx_driver_ioctl+0x34/0x120 [xxx_driver]

一看就知道是驱动里的ioctl回调里解引用了一个空指针。再看调用栈,是一个定时器在凌晨3点触发的清理任务。问题定位加修复,两小时搞定。如果没有ramoops,你可能要盲目加printk再等两天复现。

武器二:kdump + kexec --- 宕机后拍一张"CT"

问题

ramoops只能保留内核日志文本。如果问题需要查看完整的内存状态(进程列表、内存分配、寄存器值),ramoops不够用。

方案

kdump在系统崩溃时启动一个"捕获内核",把崩溃时的内存状态完整dump下来,后面可以用crash工具分析。

操作

安装:

bash 复制代码
# 在Buildroot中开启
BR2_PACKAGE_KEXEC=y
BR2_PACKAGE_KEXEC_ZEROHUNTER=y

# 配置内核
CONFIG_KEXEC=y
CONFIG_CRASH_DUMP=y
CONFIG_PROC_VMCORE=y

预留内存(内核启动参数):

ini 复制代码
crashkernel=64M

触发测试:

bash 复制代码
# 手动触发panic测试
echo c > /proc/sysrq-trigger

重启后分析:

bash 复制代码
# vmcore文件位置
ls /var/crash/
# 用crash工具分析
crash vmlinux /var/crash/vmcore

crash工具里最常用的命令:

bash 复制代码
bt       # 看调用栈(哪个函数导致的崩溃)
log      # 看内核日志
ps       # 看进程列表
vm PID   # 看某个进程的内存映射
files PID # 看某个进程打开的文件

什么时候用kdump而不是ramoops?

  • 怀疑是内存 corruption 问题
  • 需要看进程间的资源竞争状态
  • 需要分析spinlock死锁
  • 内核panic但日志信息不够

kdump的代价是预留内存(通常64-128MB)和重启后转储的时间。对于内存敏感的设备,ramoops更轻量。

graph LR subgraph 崩溃处理流程 A[内核崩溃] --> B{有kdump?} B -->|是| C[启动捕获内核] C --> D[转储vmcore到存储] D --> E[系统重启] E --> F[用crash分析vmcore] B -->|否| G{有ramoops?} G -->|是| H[重启后从pstore读取] H --> I[分析kernel日志] G -->|否| J[只能靠串口碰运气] end

武器三:ftrace --- 追踪"慢动作"和"神秘挂起"

问题

系统没崩溃,但莫名其妙"卡住了"------某个线程不响应了,或者中断响应延迟突然变大。这种问题不致命但很烦人。

方案

ftrace是内核内置的追踪器,可以记录函数调用、中断开关、调度事件。关键是零开销(启用时才有额外开销)。

操作

bash 复制代码
# 挂载tracefs
mount -t tracefs tracefs /sys/kernel/tracing

# 查看可用的追踪器
cat /sys/kernel/tracing/available_tracers

# 启用function_graph追踪
echo function_graph > /sys/kernel/tracing/current_tracer
echo my_driver_function > /sys/kernel/tracing/set_ftrace_filter
echo 1 > /sys/kernel/tracing/tracing_on

# 复现问题后
echo 0 > /sys/kernel/tracing/tracing_on
cat /sys/kernel/tracing/trace

高阶用法:追踪中断延迟

bash 复制代码
# 追踪irq_disable时间
echo irqsoff > /sys/kernel/tracing/current_tracer
echo 1 > /sys/kernel/tracing/tracing_on

# 或者追踪抢占关闭时间
echo preemptoff > /sys/kernel/tracing/current_tracer

真实案例

一个工控设备偶尔出现"控制输出延迟响应"。用ftrace的irqsoff追踪器发现,某个驱动的spin_lock_irqsave持有了超过50ms。

bash 复制代码
# irqsoff追踪输出(简化)
# tracer: irqsoff
#
# irqsoff latency 50321 us (临界区长度50ms!)
# 导致中断被屏蔽了50ms
#
# 罪魁函数: xxx_driver_do_blocking_io
# 调用路径:
# xxx_driver_write -> spin_lock_irqsave -> xxx_driver_do_blocking_io

问题:驱动在spinlock保护的临界区里做了阻塞操作(等待硬件完成标志)。spinlock的本意是"短时持有",但硬件响应慢了,导致中断被屏蔽了50ms。

fix很简单:用mutex代替spinlock,或者把等待移到临界区外面。

三件武器的选择策略

场景 工具 原因
内核panic重启 ramoops/pstore 轻量,不需要预留大块内存
需要完整内存分析 kdump 可做post-mortem分析
系统卡死/延迟大 ftrace 零基线开销,精准追踪
用户态进程崩溃 core dump 跟嵌入式Linux关系不大,用ulimit -c

总结

没有JTAG不代表不能调试。嵌入式Linux内核提供了足够多的调试工具,关键是你知不知道用。

我的习惯是:所有量产产品必开ramoops + pstore,这是一个配置项的事儿,成本为零,收益巨大。kdump在内存充足(256MB以上)的设备上也推荐开启。ftrace则是按需启用,系统出问题了再打开复现。

别再跟我说"量产设备没法调试"------不是没法调试,是你没配。这三个工具配置一次,能用一辈子。


P.S. 如果你还在用"出问题就重新烧个带printk的固件等复现"的方式,看完这篇,花半天时间把ramoops加上。下次客户半夜打电话说设备死机了,你不用去现场,远程要个日志文件就能定位。这才是嵌入式工程师该有的水平。

相关推荐
dddwjzx7 小时前
嵌入式Linux C应用编程——tslib库的使用
linux·嵌入式
潘潘的嵌入式日记1 天前
CMIS的Alarm和Warning
嵌入式·翻译·告警·通信协议·英文·cmis
天空'之城1 天前
C 语言工业级通用组件 02:通用内存池
c语言·嵌入式·内存管理·内存池
浅止菌1 天前
说嵌入式不能用C++的,你们写过超过10万行的嵌入式项目吗?
嵌入式
dddwjzx1 天前
嵌入式Linux C应用编程——GPIO
linux·嵌入式
浅止菌1 天前
嵌入式Linux驱动调试:printk的9个高阶用法,最后一个是救命级
嵌入式
dddwjzx1 天前
嵌入式Linux C应用编程——输入设备
linux·嵌入式
潘潘的嵌入式日记3 天前
改完Keil工程文件被缓存覆盖?——uvprojx编辑的进阶四坑
xml·嵌入式·keil·调试·mdk·工程文件
ICECREAM3 天前
嵌入式 —— 串口与 UART 深入详解 —— i.MX6ULL 实战
嵌入式