真的存在问题分析的“银弹”吗?
写过代码的开发人员都明白,软件bug是无法避免的。当代码中出现bug时,可能导致系统出现异常崩溃、内存泄漏等现象。因此软件开发人员需要具备强大的诊断能力将这些问题抓出来,而掌握这些诊断能力便成为软件开发人员成为高手的必经之路。
对于运行Linux系统的物联网设备而言,这个问题的答案简单而美好——“银弹”存在且有很多。诸如linux自身的coredump以及强大的valgrind等等,显然linux的成熟与强大为开发者提供了足够多的银弹。但更多的物联网设备中,尤其轻量级设备是无法运行linux的。本文便专注于讨论在这些设备上如何分析遇到的各种问题。
通常这样的轻量级物联网设备会运行在特定的RTOS上,部分设备甚至直接落跑没有OS。更有甚者,很多开发人员拿到的开发环境中唯一可以依赖的就是打印(没有JTAG无法使用GDB工具)。本文针对这样的艰苦环境,给出足够多的“银弹”,方便开发者分析各种问题。由于篇幅有限,所以每种“银弹”都以技术思路阐述为主,不再具体到讨论代码。
感受“银弹”的魅力
“银弹”的魅力在于,一个bug出现后开发人员依靠它可以不用连仿真器、不用打开gdb单步调试的情况下能快速找到bug原因。我们先看看一些例子:
- 代码中访问了非法内存(比如:在不可写的地址处写了数据)导致系统奔溃。
死机诊断“银弹”,可以记录访问非法内存时的pc值,告诉用户挂在了那个函数的哪一行; - 代码跑飞了(pc=0),直接触发异常。
栈回溯“银弹”+ 死机诊断“银弹”,可以将整个栈回溯打印出来,找到A->B->C的函数调用过程; - 版本过大导致内存或者flash消耗太大。
静态内存统计“银弹”,可以方便的分析整个版本的静态内存消耗,找出ram或flash消耗的罪魁祸首; - 内存申请时malloc 失败,出现内存不足。
动态内存统计“银弹”,可以记录用户的历史过往数据,根据用户是在哪个任务或模块中申请的内存、从系统启动开始内存的申请情况等信息,帮助用户查看是否有组件申请了过大内存但没有释放等内存泄漏的情况; - 设备长时间运行后出现内存不足。
内存泄漏分析“银弹”,可以对比两个时间点的动态内存变化,找到已申请未释放的内存,实现内存泄露问题的定位 - 已经有明显的踩内存现象,但是无法具体定位踩内存根因。
内存踩踏检查“银弹”,可以设置指定的内存区域属性为不可访问,从而制造memory访问异常,结合异常现场打印,快速定位踩内存的元凶; - 系统挂死、串口没有打印,问题分析无从下手。
主动异常触发“银弹”,通过串口命令等手段主动触发系统异常,在异常处理中追溯初被打断之前的程序现场,实现问题的定位。
如何使用这些“银弹”?
RTOS领域大部分系统的诊断能力都十分薄弱,需要开发者自己学会并掌握大量的诊断能力。本章节给出这些诊断能力即“银弹”的实现思路,供读者参考。
栈回溯
栈回溯的目的是根据函数栈的内容,分析初函数调用关系。以嵌入式最常见的ARM架构为例,每一级函数调用都会把函数返回地址进行压栈(LP寄存器),开发者要做的就是在出现问题时,通过PC寄存器找到函数,通过SP寄存器找到栈的位置,然后一层层向上找到栈中保存的返回地址。思路如下图:
linux等其他桌面级OS也具备栈回溯能力,但其是通过-mapcs-frame或-funwind-tables编译选项实现的。这些选项对性能和代码体积都有影响,且嵌入式很多bsp代码并不开源,无法加入编译选项重编。所以这里讨论的是不依赖编译选项的栈回溯能力。
这里面给出一些启发性的关键技术点:
- 开发者可以手动分析或者写程序自动完成栈回溯;
- 分析时的输入,栈内容与反汇编代码;
- 开发者需要熟悉CPU架构中使用的压栈指令,对照反汇编寻找这些指令;
- 寻找时从PC开始向上寻找,找到函数入口处的压栈指令,从栈中找到LR,如此往复向上寻找;
死机诊断
类似中断处理,当系统出现异常时会进入特定的向量处理流程。
对于开发者来说,如果使用OS本身已经接管了异常,并在异常发生时进行了必要的打印,那么最重要的就是如何理解这些异常信息了。以ARM的Cortex-M 3/4/7为例,可以参考这篇文档,分析异常原因。
Using Cortex-M3/M4/M7 Fault Exceptions
这里面最核心的思路时,通过异常时的PC找到出问题的指令,通过上述文档提及的下列关键寄存器的值,找到异常的原因。
还有,通常PC地址不足以分析出问题。比如异常出现在free函数中,那是谁调用了free导致问题就变得非常重要。此时可以结合上一节的“栈回溯”实现向上追溯,完成问题分析。
如果开发者使用的OS碰巧异常时打印的信息太少,那就需要自行补充异常处理代码了。从相关vector开始走读代码,并再合适的位置加上打印,这里篇幅有限不再详细展开。
静态内存统计
对于嵌入式物联网设备,内存占用总是非常敏感的。当出现问题时,RAM或者Flash已经不足,那么开发者应该怎么入手分析问题呢?本小节先从静态内存统计入手,分析编译时已经确定的Flash与RAM消耗,找到耗内存大户。
静态内存分析方法的核心是利用好链接时产生的map文件,map文件通过连接选项‘-Map=xxx.map’可以产生。如果使用gcc进行链接,需要使用‘-Wl,-Map=output.map’编译选项,将其传递给链接器。
有了Map文件如何查看?map文件中罗列了每个函数、变量、只读数据的大小。对于小型嵌入式设备,函数与只读数据(比如字符串、初始化的data数据)是放入Flash中的,变量则是放入RAM中的。
可以使用python写一个分析map的脚本,实现类似这样的效果:
该脚本从map文件中提取各个函数、变量与只读数据的信息,并按照从大到小进行排序,最终输出到excle中。如上图所示,开发者就可以很方便查看这些超大的数据从何而来,进而实现静态内存优化的目的。
笔者会将该python脚本配套专门的文章发出,请持续关注本社区。
动态内存统计
内存统计的核心是替换原有平台提供的malloc与free接口,增加辅助信息进行统计。这里给出一个简单的思路:
- 实现new_malloc/new_free。原先外部调用malloc/free的地方替换成new_malloc/new_free。
- new_malloc中将传入的size加16,然后调用malloc申请内存。得到ptr后预留16的offset返回个上一层。最后进行内存统计。
- 这保留的16字节用来记录函数返回地址、当前任务ID、内存块大小等信息,方便内存块追查。
- new_free中对应进行16字节的偏移,再调用free接口。最后进行内存统计。
- 可选:可以维护一个链表把已申请内存串起来。
由此便可以实现:
- 任意时刻可以得知动态内存的占用情况,得知高内存占用的任务;
- 甚至对于大的内存块,可以获取到调用者的信息;
- 当出现内存不足时,可以根据调用者信息进行分类,找到大量申请内存的代码。
另一个思路,便是重新实现OS的malloc与free算法,这里篇幅有限不再详细展开。
内存泄漏分析
想做到内存泄漏分析,首先需要对申请到的内存进行追踪,增加如下的TraceInfo信息。
然后,找到系统内存稳定的时间点。比如一个物联网设备启动之后,做起点A,然后触发一个完整业务,等业务停止后,记作结尾B。接下来对比A与B之间的内存差异,类似如下流程。
上图橙色部分就是需要开发者为内存泄漏分析能力而增加的软件功能。最终找到 LEAK_CHECK Start(即例子中的A)与LEAK_CHECK End(即例子中的B)两个时间点之间,所有已申请未释放的内存。
开发者根据这些内存的栈回溯信息,便能找到代码中内存泄漏的点了。
内存踩踏检查
内存踩踏问题的第一步是找到稳定复现的场景,然后增加诊断信息复现问题,最终捕获踩踏内存的元凶。
这里各处一些大致思路:
- 首先,需要找到一种内存被踩就系统停止的方法。以ARM Cortex-M为例有两种方式,硬件断点或MPU内存保护单元;
- 硬件断点适合小内存范围的监控,MPU适合大范围的内存监控,具体实现方式需要参考ARM文档;
- 利用这样的硬件特性,代码就可以实现watchpoint功能,通过相关API在指定地址增加监控;
- 以栈越界为例,通过下图(左边是原问题版本,右边是增加监控的版本)方式便可以抓出来是那个函数出现了栈越界;
主动异常触发
当系统出现意外的代码死循环时,可能导致串口没有任何打印,这种问题非常难以定位。即使对于有命令行交互的OS,也可能因为中断被屏蔽,无法执行命令,进而毫无定位问题的手段。问题如下图所示
这里笔者给出一种主动异常触发的机制,通过特殊的串口命令(或者其他中断)触发CPU跑到特殊的中断向量中,该向量主动执行非法指令进而触发系统异常。最终系统在异常处理中打印出完整的系统体检报告。
该技术的一些关键点:
- 需要找到一个中断承接打断系统的工作;
- 将该中断优先级设定成最高;
- 重新实现系统的开关中断操作,将“关闭所有中断”替换成“关闭特殊中断外的所有中断”;
- 特殊中断处理流程直接执行非法指令,比如ARM的udf指令,触发异常;
- 异常处理中获取系统信息,恢复出特殊中断之前的系统现场,分析卡死原因。
有没有更简单的方案?
与其让开发人员一步步自己实现这些银弹,能否提供更简便直接使用这些银弹的方法?
答案是有,只要使用阿里云旗下的物联网操作系统AliOS Things,配合配套SmartTrace工具。便直接可以用上上述各种银弹法宝了。
AliOS Things开源在这里:
https://gitee.com/alios-things/AliOS-Things
后续本社区会陆续更新一些SmartTrace相关的诊断维测系列文章,教会开发者使用AliOS Things时如何快速定位各种疑难杂正问题,成为bugfix高手。拥有“银弹”,享受开发的乐趣~~
更多精品好文