嵌入式软件堆栈使用分析方法研究
2024-04-14程小贤孙宏强黄鹏武宇娟
程小贤 孙宏强 黄 鹏 武宇娟
摘要:嵌入式软件堆栈使用分析是高安全性嵌入式软件开发工作中的一项重要任务,用于评估和优化嵌入式软件的内存使用情况。通过对堆栈使用情况的分析,开发人员可以确定软件运行期间所需的最大栈空间大小,以避免堆栈溢出等问题。鉴于此,首先研究堆栈的相关概念和工作原理,然后探讨嵌入式软件堆栈使用分析的目的和相关技术,最后结合现有技术提供了一种嵌入式软件堆栈使用分析方法,旨在为高安全性嵌入式软件开展堆栈使用分析工作提供参考。
关键词:嵌入式软件;堆栈;堆栈使用分析方法
中图分类号:TP311 文献标志码:A 文章编号:1671-0797(2024)07-0006-04
DOI:10.19514/j.cnki.cn32-1628/tm.2024.07.002
0 引言
随着嵌入式计算设备基础硬件性能的提升,在通信、工业制造、交通运输等领域,嵌入式系统逐渐承担起更加综合化和关键的任务,这也导致嵌入式软件在结构愈加复杂的同时,其安全性问题也越来越受到重视。堆栈是嵌入式软件中的重要存储结构,它用于保存软件运行过程中的关键信息。堆栈的安全也直接影响到嵌入式软件的安全,因此,在一些安全性要求较高的领域,堆栈的使用分析已成为保证软件安全性的必要工作之一,如机载领域DO-178C中要求,在对软件源代码的准确性和一致性评审和分析时需要包含对堆栈使用的分析。本文结合现有技术,说明如何进行嵌入式软件堆栈使用分析[1]。
1 堆栈的概念
堆栈,又称栈,是一种后进先出的数据结构。当软件中任务因函数调用、中断或异常处理而发生跳转时,会将任务现场的上下文信息、变量、参数等缓存到堆栈中,待处理完成后,返回时从堆栈中再恢复现场信息继续处理[2]。
堆栈因使用场景不同,通常分为三种:任务栈、任务异常栈和中断栈。
任务栈是软件中任务在正常运行时使用到的栈。任务在运行时会持续进行函数调用,每一次调用都意味着一次函数跳转。任务栈用于在函数跳转前缓存现场信息,在函数返回时从任务栈中恢复现场信息。
任务异常栈是系统处理任务引发异常时使用到的栈。任务在运行时可能会因为程序错误而引起系统异常,此时操作系统会进入异常处理,该处理过程中使用任务异常栈保存异常发生现场信息和处理过程信息。
中断栈是系统处理外部中断时使用到的栈。任务在运行时可能会被外部中断打断,此时操作系统会进入中断处理,该处理过程中使用中断栈保存中断发生现场信息和处理过程信息。
嵌入式软件的运行实际上是多个任务的持续调度和执行,通常操作系统在创建每个任务时都会为其分配一个任务栈和一个任务异常栈。任务从其入口函数开始启动执行,执行过程中发生函数调用时进行数据压栈,用任务栈存储参数、变量、寄存器值等临时数据。函数返回时进行弹栈,从任务栈中恢复临时数据。任务切换时会切换使用的任务栈,因此任务栈之间是相互隔离的。任务运行时可能因代码缺陷等原因触发系统异常,此时停止任务运行,将任务栈切换为任务异常栈进行异常处理,处理完成后再切换回任务栈。任务运行时会被外部设备中断打断,如定时器中断、串口中断等,此时停止任务运行,将任务栈切换到中断栈进行中断处理,处理完成后再切换回任务栈。
2 嵌入式软件堆栈使用分析目的
嵌入式软件堆栈使用分析的目的是分析并计算软件运行期间堆栈的最差使用情况,确认堆栈资源分配是否合理,需确保在最差情况下也不会出现栈溢出,从而保证软件运行过程中堆栈中的关键数据始终是正确的。
任务栈是软件运行过程中被操作最频繁的堆栈,且存储着任务运行时的关键信息。任务栈中的信息若被破坏或篡改,将会导致程序运行不符合预期甚至程序崩溃。对于高安全性的嵌入式软件,针对任务栈的堆栈使用分析是软件安全性保证工作的重要部分。故本文只针对嵌入式软件任务栈的堆栈使用分析方法进行说明。
3 嵌入式软件堆栈使用分析技术
在进行嵌入式软件堆栈使用分析时可以采用多种技术,以下针对三种常见的技术方法进行说明。
1)手工计算法:人工计算任务执行流程中各函数调用期间所需的任务栈空间大小,并估算出所有可能的调用路径上所需的任务栈空间大小,其中值最大的即为任务栈的最坏使用情况。该方法需要分析人员熟悉程序源代码和函数调用期间使用的寄存器数量和每个寄存器所占用的栈空间大小。对于较复杂的软件,该方法工作量大、耗时长、易出错,其计算结果准确程度依赖于分析人员个人能力。
2)静态代码分析法:通过堆栈分析工具对代码展开静态分析,从而计算出任务栈的最坏使用情况及路径。工具以任务入口函数为起点,计算出一条从起点到调用终点的任务栈最坏使用路徑,该路径上各函数节点的堆栈使用之和便是任务栈的最坏使用情况。该方法仅前期准备阶段需要一定工作量,后续阶段工作易实施、耗时短、结果直观。但该方法无法分析动态调用场景,如通过指针调用,需要人工干预。
3)动态运行时分析法:通过动态分析工具监控程序的运行时堆栈使用情况从而获得最坏使用情况。该方法通常需先按照工具约定对代码进行插桩并重新编译生成可执行程序,之后通过执行测试用例覆盖所有可能的程序路径,工具可以跟踪用例执行期间的函数调用和返回,记录堆栈的使用情况,最终分析出堆栈最坏使用情况。该方法分析阶段自动化,无须人工干预、工作量较小、可覆盖动态调用场景。但该方法分析结果准确程度依赖于测试用例设计完善程度,若测试用例未覆盖到全部路径,则可能会导致结果不准确。
综上所述,相较于其他两种方法,静态代码分析法使用工具代替人工分析,效率更高,同时无须部署运行目标软件,更加方便灵活。因此本文基于静态代码分析法,介绍如何通过静态分析工具StackAnalyer进行嵌入式软件堆栈使用分析。
4 StackAnalyer概述
StackAnalyer是一款由Abslnt公司开发的用于嵌入式软件堆栈使用情况分析的工具。它是一款静态分析工具,不依赖程序执行。它通过对二进制可执行文件展开静态分析,自动分析从输入的程序入口函数到结束的所有可能调用路径,并计算路径上的堆栈使用情况,识别堆栈最差使用情况和路径。StackAnalyer工具的工作原理如下:
1)以任务入口函数为起点分析代码,为任务建立一棵调用树;
2)分析该调用树中每个节点上函数的堆栈使用量;
3)分析并计算从树根节点到叶子节点哪条调用路径上经过的节点函数的堆栈使用量之和最大,该条路径便是任务堆栈最差使用路径,使用量之和为任务堆栈最差使用情况。
StackAnalyer工具存在如下两处功能缺陷:
1)作为静态代码分析工具无法分析包括函数指针调用在内的动态调用场景,遇到通过函数指针的跳转时,由于跳转地址不是有效的函数地址,会导致分析失败;
2)无法分析系统调用场景,遇到系统调用指令时由于无法识别跳转目的函数,会终止分析该条路径。
针对上述两种场景,在实际操作时需要进行人工干预。
5 嵌入式软件堆栈使用分析方法
本章节结合StackAnalyer工具分析原理和嵌入式软件特点,以图1所示的TaskCase任务为例,针对基于静态代码分析的嵌入式软件堆栈使用分析实施过程进行说明。
5.1 梳理软件任务信息
在进行堆栈使用分析时,只有明确待分析软件中运行的任务的基本信息,才能针对具体任务展开分析和评估工作。因此首先需要梳理出软件系统中运行任务的信息,该工作至关重要,是后续所有工作的基础,且只能由人工完成。梳理的任务需要覆盖本次待分析软件包含的所有任务,且梳理的信息需准确。梳理的任务信息至少需要包含如下属性:
1)任务标识:可唯一标识一个任务的属性,如任务名或任务ID。建议使用任务名作为任务标识,因为通常任务ID在每次运行软件时都可能不同,而任务名是不变的且易于理解。
2)任务入口函数:创建任务时为其指定的入口函数。任务入口函数必须是软件的可执行文件中存在的、有效的函数符号,若存在同名的函数,则需要说明定义该任务入口函数的文件名以便区分。若在创建任务时是通过函数指针方式指定的入口函数,则需要确定指针具体指向的函数。只需要记录函数名即可,无须关注其参数和返回值。
3)任务特权状态:指任务运行时所处的系统特权状态,如用户态和系统态,部分支持虚拟化的系统还存在超级用户态等第三种状态,本文不考虑三态场景。該属性说明了任务运行时任务栈所属的特权空间。
4)分配的任务栈大小:任务创建时为其分配的任务栈大小,用于和分析结果进行对比评估。
5)任务栈最差使用情况:记录当前任务的堆栈使用分析结果。
6)补充分析说明:若工具分析结果不准确或分析失败,则需要人工补充分析并说明具体的补充分析情况,否则无须说明。
在实际工作中可以按列表的形式梳理和记录上述任务信息。以图1中用户TaskCase任务为例梳理任务信息表,如表1所示。
5.2 工具分析
梳理完成任务信息表后,使用StackAnalyer工具对表中任务记录逐条进行静态代码分析。工具根据输入的任务入口函数自动分析并生成分析报告,若分析成功,则会在报告中显示任务栈最大使用大小和路径。以TaskCase任务为例,分析结果报告如图2所示。
5.3 补充分析
如上文所述,StackAnalyer工具在遇到函数指针调用和系统调用时会分析失败,导致无法产生准确的结果,因此需要在工具分析后针对分析失败的任务进行补充分析。
对于函数指针调用场景,需要走读代码确认程序运行时实际调用的函数,使用工具提供的AIS工程脚本,将该函数补充到任务的函数调用树上,重新分析生成最终结果。若存在多个可能调用的函数,则需要对每一个函数都进行补充分析,其中结果值最大的即为该任务最终分析结果。
对于系统调用场景,需要根据具体实现场景分析。系统调用是一种软中断,通常作为用户态程序进入系统态的接口。嵌入式操作系统提供两种通过触发系统调用实现函数调用的机制:
1)进入系统态的系统调用。该机制是一种跨特权状态的调用,会切换任务栈为系统态堆栈,后续堆栈的使用不会再影响任务栈,因此无须计算后续调用函数的堆栈使用量,将其直接指定为0后重新分析最终结果。
2)共享库calllib调用。该机制通过触发系统调用查找内存空间中共享库函数地址从而实现跨层级的函数调用。该调用只能在同一特权状态下进行,不会切换任务栈,后续堆栈使用会影响当前任务栈,因此需要人工对后续调用过程进行堆栈使用分析,将结果补充到最终分析结果中。
5.4 结果分析
将软件中所有任务都分析和补充分析完成后,需要逐条对软件任务信息表中“分配的任务栈大小”和“任务栈最差使用情况”属性进行比较。若前者不小于后者,则说明该任务的任务栈空间分配充足,不存在栈溢出风险,同时可根据具体差值适当调整任务栈大小,节省系统资源;若前者小于后者,则说明存在栈溢出风险,需要扩充该任务的任务栈空间。
6 结束语
本文提供了一种基于静态代码分析的嵌入式软件堆栈使用分析方法,主要说明如何分析软件任务栈的最差使用情况,同时对指针调用和系统调用两种特殊调用场景提供了补充分析方法。对于异常栈和中断栈,其使用场景特殊,无法使用静态分析工具自动分析,因此还需进一步研究对其分析的问题和难点。
[参考文献]
[1] RTCA DO-178C Software Considerations in Airborne Systems and Equipment Certification[S].
[2] 陈媛,何波,卢玲,等.算法与数据结构[M].2版.北京:清华大学出版社,2011.
收稿日期:2023-12-21
作者简介:程小贤(1988—),男,陕西西安人,硕士,工程师,研究方向:民机机载嵌入式操作系统。