news 2026/6/26 6:56:02

嵌入式应用堆内存随机损坏

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式应用堆内存随机损坏

嵌入式应用堆内存随机损坏 —— 排查过程

2026年6月 · ARM Linux 嵌入式设备


一、问题全景

Symptom: UI freeze after app startup No crash, no core dump generated │ ▼ Initial hypothesis: serial data corrupting store_data.alloc │ ┌──────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ [Dead end 1] [Dead end 2] Mid: heap random GDB hardware Valgrind/ASan corruption watchpoint toolchain QMap/QString board OOM not supported rewritten │ ┌──────────────────┼──────────────────┐ │ │ ▼ ▼ [Dead end 3] Root cause confirmed: Suspecting DeviceMonitor SharedDataStore readLen bug is the cross-thread access ONLY cause with NO lock │ ┌──────┴──────┐ ▼ ▼ Fix: recursive Build abc123def mutex protecting deployed, pending all shared data board verification

二、排查时间线

Day 1— 用户报告:app 启动不久 alloc 被踩,store_data 扩容时 abort。开始 GDB 远程调试。

弯路— 尝试在 ARM 板子上用 GDB 硬件 watchpoint 抓 alloc 写入者。板子约 1GB 内存,GDB 加载约 100MB 完整调试符号后吃掉 500MB,板子 OOM 红屏。硬件断点不可用,软件断点单步执行 CPU 99% 卡死。

Day 2— 写 heap_monitor.sh 监控脚本,用 ELF 符号表提取 vtable 地址,GDB 扫堆定位 DeviceMonitor/DeviceController 对象,轮询 alloc 字段。脚本能跑但 GDB attach/detach 太频繁会打挂板子。

弯路— 对比 dev_monitor.cpp 和 dev_controller.cpp,发现前者 COMM_MODE_TEXT==0 分支用 bytesAvailable 而非 readLen 构造 QByteArray。修改后编译部署,以为修好了。但这是表层问题。

Day 3— 换板子,release 版复现 UI 卡死。GDB attach 拿到主线程 backtrace:

MainWindow::refresh_SystemTimeSlot │ (timer-triggered system time refresh) ▼ QLabel::setText │ ▼ Qt internal memory allocation │ ▼ malloc / realloc │ ▼ glibc malloc_consolidate │ detects heap corruption ▼ abort() │ ▼ libSegFault.so catches SIGABRT │ signal handler calls malloc again ▼ futex deadlock │ all threads freeze one by one ▼ [UI completely frozen]

关键发现— GDB frame 12/13 查看局部变量:showText.d = 0x19a5ef50恰好等于this(MainWindow 地址)。QString 的内部 d 指针被写成了对象指针,这不可能是串口垃圾数据能做到的——是结构性写错。

Day 4— 重新审视代码架构。发现 AppModule::initObject() 中 data_store 的传递模式:

Main Thread SharedDataStore Worker Thread (AppModule) (data_store) (WorkerTask) │ │ │ │──new SharedStore──▶│ │ │ │ │ │──new WorkerTask────┼─────────────────────▶│ │ (passes data_store ptr) │ │ │ │ │──new MainWindow─────┼─▶ │ │ (data_store ptr) │ │ │ │ │ │ │◀──Get_Title() read──│ │ │ QMap access │ │ │ │ │──setValueSlot()────▶│ │ │ write QString │ │ │ │ │ │ ⚠ RACE CONDITION ⚠ │ │ Ref-count corruption → heap damage │

WorkerTask 工作线程通过 data_store 调用的函数

Get_Title Get_Value Get_Uint Get_ValueUint Get_MinValue Get_MaxValue Get_OwnerWindow Get_OwnerPage Get_HourToMin Get_ProcessTime Get_RemainingTime Get_TimingStatus getLangEnabled getTestTitle getRunStep get_UIBoardSelection getHasUserInputData getSelfTest isChannelAorBOn isExtendParam0On isExtendParam1On SendToDevice(int,QString,DataSendType)

全部读取 sensor_data[] 或 active_locale,与主线程的写入并发,均存在竞态。

┌── Main Thread ──┐ ┌── Worker Thread ──┐ │ │ │ │ │ setValueSlot() │ │ Get_Value() │ │ writes to │ RACE! │ reads from │ │ sensor_data[] │◄────────▶│ sensor_data[] │ │ .Cur_Value │ │ .Cur_Value │ │ │ │ │ │ Set_PumpSpeed() │ │ Get_Title() │ │ writes to │ RACE! │ reads │ │ sensor_data[] │◄────────▶│ active_locale │ │ │ │ + QMap │ └──────────────────┘ └────────────────────┘ QString implicit sharing → ref-count is NOT atomic → double-free / use-after-free

Day 5— Valgrind/ASan 尝试均失败。Yocto 交叉编译链不带 libasan,GCC 7.3 编译时未启用 sanitizer。x86 VM 上 Qt5 只有运行库没有开发包且 app 依赖通信口硬件无法直接跑。

修复— 在 SharedDataStore 中启用已有 QMutex 改为递归锁:

// datastore.h: mutable QMutex mutex;// datastore.cpp constructor: mutex(QMutex::Recursive)// 每个读写共享数据的 public 函数首行:QMutexLockerlocker(&mutex);

覆盖30 个函数,setValueSlot 原有手动 tryLock/unlock 替换为 QMutexLocker。纯 emit 信号函数和 QSettings 访问函数无需加锁。


三、技术总结

错误思路为什么是弯路最终结论
GDB 硬件 watchpoint 抓 alloc 写入指令ARM 板性能不足,硬件断点不可用,软件断点单步 CPU 99% 卡死改用轮询监控脚本 + GDB 短暂 attach
只修 DeviceMonitor 的 readLen bug这是真实 bug,但不是唯一原因,修了之后旧板子 release 版仍崩readLen 修复有价值,但堆损坏根因更深
怀疑串口垃圾数据污染堆解释不了 showText.d = this 这种结构性写错跨线程 QString 引用计数竞态才能产生这种"把 A 的地址写到 B"的现象
尝试 Valgrind / ASanYocto 工具链不支持,x86 VM 环境不完整直接看代码逻辑找到 root cause

四、补充隐患:char 数组

代码中大量使用裸char buf[2048]而非 QByteArray 或 std::array:

  • DeviceMonitor::recv_buffer[2048]— 类成员,位于 store_data 之后仅 8 字节,残留旧数据是 readLen bug 的直接原因
  • DeviceController::recvBuffer[2048]— 栈局部变量,每次重新分配避免了残留,是正确写法

建议统一使用 QByteArray + readLen 精确控制,消除硬编码长度。


五、产出

产出路径
SharedDataStore 互斥锁修复datastore.cpp + datastore.h(30个函数)
已编译二进制BuildID abc123def(VM: /build/product/app/AppModule)
监控脚本 heap_monitor.shtools/heap_monitor.sh(不依赖 .debug 文件,自适应任意版本)
分析文档tools/跨线程SharedData分析.md
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/26 6:52:56

全光校园网络等保合规建设方案

全光校园网络等保合规建设方案:为什么老方案越改越难,等保越测越慌?答案很直接:传统网络架构在底层设计上就不满足等保2.0的技术逻辑。交换机堆叠、VLAN隔离、上网行为管理各自为政——不是缺这就是少那,整改一次管两年…

作者头像 李华
网站建设 2026/6/26 6:48:07

【无标题】11维拓扑量子色动力学基础结构与宇宙物理现象统一论述——完整修订(含维度分配、卡比拉-丘对应与欧拉示性数佐证)

11维拓扑量子色动力学基础结构与宇宙物理现象统一论述——完整修订(含维度分配、卡比拉-丘对应与欧拉示性数佐证)摘要本文基于11维拓扑量子色动力学模型,系统拆解四大核心拓扑结构:十字跨桥(二维莫比乌斯拓扑路径&…

作者头像 李华
网站建设 2026/6/26 6:47:13

高端家电哪个品牌质量最好?卡萨帝技术与市场双领先

> 核心观点:在高端家电领域,卡萨帝凭借连续10年市场份额第一、L4级智慧家电认证及MSA控氧保鲜、AI深度奢护等核心技术,构建了以技术先进性、可靠性与市场长期认可为核心的综合质量优势,是当前‘质量最好’定义下的优选品牌。高…

作者头像 李华