嵌入式软件bug分析基本要求

摘要:软件从来不是一次就能完美的,需要以包容的眼光看待它的残缺。那问题究竟为何产生,如何去除呢?

1、软件问题从哪来

软件缺陷问题千千万万,主要是需求、实现、和运行环境三方面。

1.1 需求描述偏差

客户角度的描述,在经过业务对接、产品经理的转述,最终呈现的软件需求可能已经偏离了原始的述求,开发人员基于自身经验的理解偏差,开发过程缺乏有效的沟通及监督,导致最终的软件功能与客户的核心诉求存在偏差。

1.2 异常处理机制不完善

嵌入式软件必定是运行在特定的硬件设备,硬件本身或环境问题等特殊干扰,开发人员因经验不足缺乏风险评估,面对电脑是无法全方位猜测、模拟各种异常环境下的差异,最终导致设备在特定场景下运行异常。

1.3 软件开发能力不足

嵌入式系统的复杂度与开发人员的能力矛盾,导致软件本身的逻辑存在缺陷。

2、软件开发与软件问题

关于软件bug的来源,排除不可控的外界因素,与软件开发人员相关,或者开发人员可以减少问题的发生的可能,从软件开发角度解决的方案如下:

2.1 重视需求分析

软件开发就是写程序,并设法使之运行,这是个错误的想法。软件结果与客户期望不一致,需求问题不全是软件开发的锅。大多数情况下客户的原始述求不会直接到软件开发,软件开发没法去反诉找客户确认,只能通过软件的实现形式去甄别不合理的,或者针对客观环境、研发团队的基础去评估风险。

比如客户要求可以设备可以定时1秒采集一次温度,精度要求0.0001摄氏度;或者要求数据采集持续采集24h后,每天12:00准点TCP上报后台服务器。这就需要考虑温度传感器的精度、RTC唤醒以及TCP联网时间、24小时采样数据的存储。如果硬件资源或者客观环境无法实现,盲目承诺客户,或者开始编码,最终结果可想而知。

软件开发是个人的任务,但开发前多沟通确认,进行风险评估反馈,减少开发的无用功,也是对开发人员的基本要求。

2.2 积累行业经验

嵌入式产品都是针对某个细分行业,见多识广,才能预判可能出现的异常,开发阶段有针对性的去处理,或者提前告知使用者去规避。有时候经验比技术能力重要。

2.3 提高开发水平

软件开发水平,首先是个人能力,熟悉软件SDK的应用,相关的操作系统、设计模式、调试方法等。软件开发能力大多数情况下决定软件质量和可维护性,这是个长期学习提高的过程,如果一定要提供捷径,那就是多阅读优秀的开源代码。

2.4 先设计再编码

软件开发不能随心所欲,先明确方案和大概的实现流程,胸有成竹,然后再开始编码,完善细节。这理论没毛病,但真正执行起来却比较难,大多数情况下都只在乎软件出结果,而实际上方案不合理,后期修修补补更浪费时间。如果制度和时间不允许,个人在纸上画画框图和结构,先构思再开发也能弥补,起码不至于南辕北辙。

2.5 编码规范

编码规范是软件开发团队合作的标准,嵌入式行业可以参考“华为技术C语言编程规范”,但实际开发过程,和前面的先设计再编码一样,各种不可控因素,比如项目进度压力和开发者水平与认知的差异,导致有编码规范却无法严格执行。随着软件工程规模的扩大,软件交期、代码同步、重构或交接,其风险也逐渐放大。存在编码规则并不能解决问题,只有强制执行才有意义。

2.6 代码缺陷静态检查与单元测试

软件质量是项目成败的关键点之一,在开发周期有限,人力资源不足的情况下,使用工具实现代码自动扫描,分析出潜在隐患点,可从源头减少软件bug,比如cppCheck、PC-lint等,实现代码自动静态分析,或者人工视检,有效规避简单的软件风险。

如果可能,最佳的选择是单元测试,单元测试比可交付成果本身更重要,文档注释不全时,单元测试就是设计文档;单元测试定义的API和用法,以及可能的使用风险点,就是最佳的参考范例;不足100%的覆盖率就是玩忽职守,开发人员应该全权负责测试自己造出的产品。依靠后期的黑盒测试发现问题,其消耗的人力物力,是编写单元测试的几倍,而且单元测试可以反复的自动测试。不过这种情况更多的是存在于开发理论中。可以参考微信公众号 嵌入式系统 的《代码的保养》第二章。

3、前期减少问题

软件问题的解决,有些不是个人能解决的,需要协调沟通,或者与研发团队的整体风格、制度有关。个人能决定的是软件具体逻辑,这也是体现个人技术能力的重点。

3.1 C语言基础

1、多看优秀代码,学习其技巧。 

2、使用带参数检测的接口,比如优先选择snprintf,少用sprintf,其它str前缀的如strncmp也是,但要明白这类接口和memcmp区别。不同的编译器表现不一致,平时也要多关注。

在GCC中编译运行(设备):

  char str[5];
  int ret = snprintf(str, 3, "%s", "abcdefg");
  //ret = 7 ,str = ab

  char str[99];
  int ret = snprintf(str, 99, "%s", "abcdefg");
  //ret = 7 ,str = abcdefg

注:snprintf的返回值为字符串的长度,且写入的字符串后面带有‘\0’结束符。

在VC中编译运行:

  char str[5];
  int ret = snprintf(str, 3, "%s", "abcdefg");
  //ret = -1 ,str = abc  [后面不会自动补\0结束符]

3、注意函数返回类型,避免类型强制转换导致调用判断异常,有些编译器对隐示类型转换直接报错,因为它确实存在风险。 

4、合理的使用sizeof、struct、union、weak等关键字,增加代码的可读性和可扩展性。 

5、参数使用前,如数组小标,指针变量使用前必须先判断是否合法。

6、浮点数不能直接进行==和!=比较,等等,这些细节太多,可以参考《C陷阱与缺陷》。

7、讲的都会,说的都对,但真实际写代码,就容易各种小问题,主要还是态度问题,缺乏自我检查、自测的步骤,依靠测试发现bug去驱动研发调试修复是大忌。

3.2 动态内存

1、尽量做到申请与释放在同一个函数,申请内存后,先判断是否申请成功,再进行其它操作。

2、内存申请与释放之间有特殊情况return,要注意释放。

3、释放结构体指针前,注意该变量内部是否还有指针变量动态申请空间,先释放内部,再释放外部。 

4、关于内存申请与释放,或使用越界是C语言的劣势,如果设备堆空间足够大,可以在申请时额外多申请固定空间,记录申请函数、长度、并在首尾标记,后续释放时检查内存区首尾标记是否被覆盖;或者查询是哪些函数申请的内存始终没有被释放。具体实现方案请关注微信公众号 嵌入式系统 的相关文章。

3.3 跨平台问题

1、使用系统API前先判断自身传入参数的有效性和范围等是否符合要求,一般系统API是库文件,使用错误更难发现问题。

2、针对不同的平台常用的接口,务必增加适配层隔离,便于调试和后续移植。比如有的平台中断(SDK提供的中断回调不一定是硬件中断)不支持串口日志。

3.4 RTOS系统特性

1、多任务的竞争,在RTOS系统中,需要注意全局函数、全局变量的使用,避免互相竞争影响,对公共函数尽量做到可重入设计,具体实现方案请关注微信公众号 嵌入式系统 的《基于RTOS的软件开发理论》 。

2、中断与任务的调度关系 请关注微信公众号 嵌入式系统 的《基于RTOS的软件开发理论》。

3、合理分配任务栈空间和消息队列的深度,函数内部尽量少用大数组。

3.5 个人素养

软件编码完成,不是能编译就收工了,其功能是否符合预期,开发人员自己检查是最高效的,很多问题都是开发不仔细,或者很简单的C基础应用错误,这不是技术问题而是心态。可以多看看开源代码,或者《C专家编程》等。

4、后期解决问题

如果软件问题不可避免,该如何去修复解决呢?

一般来说100%出现的问题都比较容易解决,找到相关代码仔细检查或者加点日志就能发现问题。难处理的是小概率出现的问题,稳定复现它就是成功的一半。

4.1 问题复现

稳定复现问题才能快速对问题进行定位、解决以及验证,如何提高复现的概率?

1、模拟复现条件问题只在特定的条件下出现,对于依赖外部输入的条件难以满足,可以考虑程序里预设直接进入对应状态,或者软件内部进行极端的压力测试。

2、提高相关代码执行频率,进行某个操作才可能出现异常,人工持续操作,或者软件频繁执行相应的功能,提高问题点的执行频率,加快复现速度。

3、增大测试样本量 ,个别样机难出现,如果条件允许,可以使用多个设备同时进行测试。一般情况下试产就是为了发现这类问题。

4.2 问题定位

缩小排查范围,确认引入问题的函数或代码片段。

1、打印日志 ,日志是最直接、简单的调试方法,在问题的可疑点增加日志输出,以此来追踪程序执行流程以及关键变量的值,观察是否与预期相符。

2、版本回退使用版本管理工具时可以通过不断回退版本,验证前面版本的情况,定位首次引入该问题的版本,针对该版本的改动进行排查。

3、二分注释“二分注释”类似二分查找法的方式注释掉部分代码,以此判断问题是否由注释掉的这部分代码引起。具体为将与问题不相干的部分代码注释掉一半,看问题是否解决,未解决则注释另一半,如果解决则继续将注释范围缩小一半,以此类推逐渐缩小问题的范围,确定是哪一块代码导致这个问题。

4、硬件协助,借助示波器、逻辑分析仪分析波形,必要时也请硬件协助分析;问题样机与正常样机的主控对调,看问题是否随芯片走。尤其是涉及驱动方面的问题,比如充电、中断、复位、外设通信调试异常时。

5、仿真调试 ,在线调试可以起到和打印LOG类似的作用,适合排查程序崩溃类的BUG,当程序陷入异常中断候可以直接STOP查看call stack以及内核寄存器的值,快速定位问题点,不过这需要硬件支持。

6、三板斧使用最多的是前面三种方法,这三板斧足以应付大部分业务逻辑问题;偶尔请硬件协助解决驱动问题,日常开发中的问题都能解决。个别系统层面或者架构不合理导致的深沉问题,要么花时间死磕coredunmp,要么联系原厂FAE协助,一般芯片方案商都提供技术支持。

4.3 问题修复与回归测试

1、缩小范围确定问题代码,再排查具体的函数,修复问题点

2、有些问题属于架构层面,比如和RTOS相关的竞争关系,这种就无法定位到具体问题代码点,只能在宏观上依靠经验或操作系统理论去解决

3、解决后需要进行回归测试,确认问题是否不再出现,也要确认修改不会引入其他新问题。

4.4 复盘

1、一般情况下最后发现原因都是很简单的几句话,比如数据越界或者循环体多执行一次,看起来都是很简单的基础用法,因为一句错误可能需要几周时间来发现解决,为什么当初写错而且没检查发现呢?

2、总结问题产生的原因及解决方法,今后如何防范,对其他平台否值得借鉴,做到举一反三,从失败中吸取经验。

5、心得

业务指示开发、测试驱动开发,这一荒谬方法论,体现在部门合作与职责不清,整体就是效率低下、互相推诿,在这样的环境下开发软件也很累。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/375312.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

手撕spring bean的加载过程

这里我们采用手撕源码的方式,开始探索spring boot源码中最有意思的部分-bean的生命周期,也可以通过其中的原理理解很多面试以及工作中偶发遇到的问题。 springboot基于约定大于配置的思想对spring进行优化,使得这个框架变得更加轻量化&#…

Redis-布隆过滤器解决穿透详解

本文已收录于专栏 《中间件合集》 目录 背景介绍概念说明原理说明解决穿透安装使用安装过程Redis为普通安装的配置方式Redis为Docker镜像安装的配置方式 具体使用控制台操作命令说明Spring Boot集成布隆过滤器 总结提升 背景介绍 布隆过滤器可以帮助我们解决Redis缓存雪崩的问题…

【JS逆向学习】今日头条

逆向目标 目标网页:https://www.toutiao.com/?wid1707099375036目标接口:https://www.toutiao.com/api/pc/list/feed目标参数:_signature 逆向过程 老规矩先观察网络请求,过滤XHR请求观察加密参数,发现Payload的_s…

从蜗牛到超光速:Python 加速Playwright安装过程

目录 1. Python Playwright 安装简介2. 镜像加速的概念3. Python Playwright 镜像加速的必要性4. Python 安装 playwright5. 如何使用镜像加速安装 Python Playwright6. 总结 1. Python Playwright 安装简介 Python Playwright 是一个用于自动化 Web 浏览器交互的库它支持多种…

【C语言】三子棋游戏实现代码

目录 1.三子棋代码功能介绍 2.三子棋游戏实现步骤 ①打印菜单栏 ②判断是否进入三子棋游戏 ③三子棋游戏基本函数实现 (1)清空(初始化)棋盘函数实现 (2)打印棋盘函数实现 (3&#xff0…

科研绘图-半小提琴图-

文章目录 前言1.软件安装-Origin 20222.绘制半小提琴图3.绘制径向条形图 前言 本文叙述记录的是一些科研绘图的实现方法,具体介绍从软件安装到实现图表绘制的详细过程。 1.软件安装-Origin 2022 Origin是一款具有丰富绘图功能的科研绘图软件,安装过程…

【Leetcode】1696. 跳跃游戏 VI

文章目录 题目思路代码结果 题目 题目链接 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i 1, min(n -…

Kuberntes权威指南

一、目录 二、Kubernetes入门 三、Kubernetes核心原理 四、Kubernetes开发指南 五、Kubernetes运维指南 六、Kubernetes高级案例进阶 七、Kubernetes源码导读

【BIAI】Lecture 12 - Emotion in Brain AI

Emotion in Brain & AI 专业术语 Limbic system 边缘系统 Amygdala 杏仁核 temporal lobe 颞叶 hippocampus 海马体 Central Nucleus 中央核 medial amygdala 内侧杏仁核 ventral periaqueductal gray 腹侧中脑导水管周围灰质 课程大纲 What is emotion 当大脑检测到某些具…

C++适配器——stack queue

栈和队列 本章思维导图: 注:本章思维导图对应的.xmind和.png文件都已同步导入至资源,可免费查看 文章目录 栈和队列1. 适配器2. 栈 stack2.1 概念及结构2.2 使用2.3 模拟实现 3. 队列 queue3.1 普通队列 queue3.1.1 概念及结构3.1.2 使用3.1…

vue3 watch和watchEffect

Watch监听ref定义的数据 1.ref数据基本数据类型 let sumref(0) const stopWatchwatch(sum,(new,old)>{ If(new>10){ stopWatch() } console.log(‘sum数据变化了’) })2.ref数据为对象类型,监听的是对象的地址值,若想监听…

Android:Android Studio安装及环境配置

1开发环境搭建 Android开发需要使用java的jdk环境,所以需要下载JAVA JDK。 1.1安装配置JAVA JDK Java的JDK下载: https://www.oracle.com/technetwork/java/javase/downloads/index.html 配置java的环境变量: JAVA_HOME:java安装路径。 新增环境变量CLASSPATH 在Path环境…

如何部署Linux AMH服务器管理面板并结合内网穿透远程访问

文章目录 1. Linux 安装AMH 面板2. 本地访问AMH 面板3. Linux安装Cpolar4. 配置AMH面板公网地址5. 远程访问AMH面板6. 固定AMH面板公网地址 AMH 是一款基于 Linux 系统的服务器管理面板,它提供了一系列的功能,包括网站管理、FTP 管理、数据库管理、DNS 管…

如何使用第三方API采集电商数据呢?

电商商家最常唠叨的就是店铺运营难做。每日多平台店铺数据统计汇总繁琐耗时,人工效率偏低,且工作内容有限。 特别是眼下“618,双十一,双十二,年底大促”将至,如何提高运营的效率和质量、保证产品及服务的良…

vue3 使用defineAsyncComponent 动态加载组件

问题场景 在项目中使用静态加载组件基本能覆盖80%的场景了,如下图 但是我们在需要 循环生成一些的component 的时候或者在 开发ssr服务端渲染的页面 就会遇到有些组件以静态方式导入就会报错,导致进程失败,那么这时候就需要用到动态组件。那…

P1808 单词分类

P1808 单词分类 题目描述 Oliver 为了学好英语决定苦背单词,但很快他发现要直接记住杂乱无章的单词非常困难,他决定对单词进行分类。 两个单词可以分为一类当且仅当组成这两个单词的各个字母的数量均相等。 例如 AABAC,它和 CBAAA 就可以…

Vue中对虚拟DOM的理解

作为现代前端开发中的主流框架之一,Vue.js是一个非常流行的JavaScript框架,其核心概念之一就是虚拟DOM(Virtual DOM)。在本篇文章中,我们将深入探讨Vue中虚拟DOM的概念,并讨论为什么它在前端开发中如此重要…

高清符合要求的SCI图片使用RStudio导出

4.图片格式区别和常识 在计算机中,JPEG(发音为jay-peg, IPA:[ˈdʒeɪpɛg])是一种针对照片视频而广泛使用的有损压缩标准方法。这个名称代表Joint Photographic Experts Group(联合图像专家小组)。此团队创…

总结:图像生成网络

1、最新的几款图像生成网络 eCNN 文献:Bahrami A, Karimian A, Fatemizadeh E, et al. A new deep convolutional neural network design with efficient learning capability: Application to CT image synthesis from MRI[J]. Medical physics, 2020, 47(10): 515…

Linux 分析指定JAVA服务进程所占内存CPU详情

1、获取服务进程PID [rootVM-32-26-centos ~]# service be3Service status Application is running as root (UID 0). This is considered insecure. Running [25383]2、获取进程占用详情 [rootVM-32-26-centos ~]# cat /proc/25383/status Name: java Umask: 0022 State: S…