经验分享:RT-Thread SCons编译不稳定问题debug总结

摘要

在使用RT-Thread的过程中,发现RT-Thread SCons存在编译不稳定的问题。在源文件相同的情况下连续编译,相邻两次生成的map文件和bin文件差异巨大、运行效果不同,不利于版本回溯和管理。
本文主要总结了跟踪解决编译不稳定的问题的过程、经验、及Debug小技巧。

| 文章如有疏漏,欢迎指出~

正文

1. 怎么发现编译不稳定问题的?

(1) Test1:发现同样的源代码编译生成的hex大小有差异。

  1. 在代码里多加一个int型变量,想观察新增变量对编译结果生成的text、data、bss、dec大小的影响,发现增加int变量前后观察到的现象与我理论分析的值不一样。
  2. 不改动代码,连续编译,发现在不改动源代码的情况下,编译生成的text、data、bss、dec大小也会有小幅度的变化。
  3. 因为代码能运行,不受编译结果的影响,所以决定暂不深入追究。

(2) Test2:发现同样的源代码生成的BIN文件内容不同、运行结果不同,有的会卡死有的不会。

  1. 过了一段时间后,有一次,调试过程中发现代码卡死了,边改边调发现同样的代码有的时候能运行正常,有的时候会挂死。
  2. 联想到之前遇到一个问题:在编译优化等级为O3时会挂死,在编译优化等级为O1时能正常运行。
  3. 连着编译了4次,将4次编译生成的BIN文件存下来,分别烧录测试,就发现了会跑挂的BIN文件每次都会跑挂,不会挂的BIN文件每次都不会跑挂。
  4. 用文件对比工具对比两个BIN文件,发现有5000+的差异点;

2. 为什么有必要解决编译不稳定问题?

同样的源代码,编译后生成了内容差异巨大、运行结果不同的BIN文件,相当于在编译过程中引入了随机变量,随机的BIN文件不利于版本管理、压测,也不利于用控制变量法解决发现的BUG(因为编译引入了新的变量)。

很明显,跟踪解决这个问题有利于保证软件质量,提高往后的调测效率。

3. 编译不稳定问题的根本原因及解决方案?

(1) 根本原因:

SCons构建工具进行链接前会从文件系统中收集编译生成的目标文件(*.o, objects files)得到一个objects list。由于文件系统返回文件的顺序具有随机性,所以objects list中的目标文件排序具有随机性,导致最终链接出来的map文件具有随机性。

(2) 解决方案:

在SCons收集到objects list后,在objects list被转化为targe文件之前,对objects进行排序,排序顺序是sort函数的默认排序顺序。测试验证得,这样排序后编译正常、编译结果稳定、运行结果稳定,可作为解决方案。

4. 编译tricks总结:

(1) 关于SCons指令的注意事项及几个有助于调试的指令:

一般使用指令的时候都会去找到官方的指令手册,然后对照着指令手册查自己想要的指令。但在RT-Thread里使用SCons指令时,不能完全参照SCons官网的手册,因为RT-Thread移植的SCons代码里对官方的源码做了封装,有的时候直接用官方指令是得不到官方文档所描述的指令效果的。RT-Thread的官方文档里最像指令手册的文档…目前只找到了RT-Thread工具手册,具体都有什么指令还需要结合RT-Thread的env源代码进行探索。

  1. 指令1,控制输出完整的编译指令
    SCons -Q
    SCons --verbose
    参考链接:RT-Thread工具手册, SCons user guide:2.6, SCons user guide:9.2

  2. 指令2,控制输出一些调试解释信息
    SCons --debug=explain
    参考连接:SCons user guide:6.5

  3. 指令3,控制多线程编译(N为CPU核数)
    SCons -jN
    参考连接:RT-Thread工具手册SCons user guide 10.1.3

(2) 可从map文件分析得到*.o文件大小的工具:

https://github.com/adafruit/linker-map-summary

(3) Linux脚本与window脚本的一个区别:

Linux下的脚本是*.sh,window下的脚本是*.bat,.sh只需比.bat在开头加一行
#!/bin/bash

(4) 两个可以提效的gcc编译选项:

打开gcc所有的warning信息输出:CFLAG = -Wall
打开gcc warning信息的颜色:DFLAG = -fdiagnostics-color=always

5. debug常用思路总结:

解决BUG的一般思路即“猜想-验证”,常用的猜想及其验证思路有以下三种:
(1) 思路1:猜测BUG是由版本升级或Demo移植时引入的BUG。验证步骤如下:

  1. 回退软件版本看BUG是从哪个版本引入的。
  2. 跟各种“正常”的Demo对比差异,看是否是这些差异点引入的BUG。

(2) 思路2:知道该BUG相关逻辑的理想情况,将实际情况与理想情况进行对比来找到答案。验证步骤如下:

  1. 梳理出从“源头”到“该BUG”的逻辑链,对逻辑链上的结点进行观测,看是哪一个结点引入的BUG
  2. 一般通过阅读源代码、做测试、加log、跟小伙伴交流等方式进行逻辑梳理。

(3) 思路3:跟小伙伴交流、脑暴,收集猜测和接下来可进行实验尝试测验的方向,试验收集到的点子、再交流收集点子、再试验收集点子……直到BUG被解决。

6. 有利于加速debug的心态总结:

  • 相信“没有自己看不懂的源代码,再长的源代码一行一行、一遍一遍地看,就会越来越熟”。
  • 知道并接受“开发过程中必会遇到一个又一个的BUG”的事实。
  • 越早解决BUG后面麻烦事儿越少。
  • 有对软件质量负责的意识,有主动发现BUG的意识,平时善于观察,争取把BUG扼杀在摇篮里。
  • 相信没有解决不了的BUG,敢于迎接挑战,不逃避不放弃。
  • 每一个BUG都是一次成长的机遇,勤于总结,用严格的标准要求自己。

参考链接

RT-Thread4.1.0工程用scons连续编译生成的map文件差异很大
Get the most out of the linker map file
Cyril Fougeray’s Blog
GCC编译优化系列-前后编译的两个版本固件bin大小不一样,怎么办
https://reproducible-builds.org/docs/stable-inputs/
https://github.com/RT-Thread/rt-thread/pull/6411/files
https://github.com/RT-Thread/rt-thread/issues/6388


版权声明:本文为childerxxx原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。