基于编译器优化的嵌入式软件缺陷分析方法*

2016-08-10 10:40左万娟
航天控制 2016年5期

董 燕 黄 晨 左万娟 于 倩

北京控制工程研究所,北京100190



基于编译器优化的嵌入式软件缺陷分析方法*

董 燕 黄 晨 左万娟 于 倩

北京控制工程研究所,北京100190

嵌入式编译器会根据设定的编译选项和级别,对源代码进行优化处理,生成可执行目标码。针对嵌入式编译器的3种典型优化技术:数据预取技术、指令重排序技术和覆盖技术,结合具体实例分析问题引入机理,为有效避免编译器优化引入的软件缺陷,给出解决方案和建议。在程序开发和测试阶段应熟悉编译器优化准则,严格遵守编程约定,进一步提升嵌入式软件的质量。 关键词 编译器优化;数据预取;指令重排序;覆盖技术

随着计算机技术的发展,嵌入式软硬件系统已被广泛应用在军事、航空、航天、医疗、交通、金融和通讯等关键行业领域[1]。由于嵌入式资源有限,且对实时性要求较高,对编译器也有特定的要求,不同于一般计算机中的编译器,为提高编译效率和占用空间,嵌入式编译器会根据编译选项的不同对源代码进行相应级别的优化处理。较好的嵌入式编译器在编译和执行高级语言代码时具有和汇编语言代码同等的处理效率和执行结果[2]。

嵌入式编译器优化的目的是为了从空间和时间上提高目标代码的存储和执行效率,对程序的功能进行等价转换,以换取目标代码的执行效率和性能[2]。也正是由于这点,受限于开发和测试人员对嵌入式编译器的理解和认识程度,某些问题的引入仅从高级语言进行分析是无法发现的,如原子操作反汇编成多条代码后引入的中断冲突问题,编译器优化引入的指令重排序问题等,据研究统计,星载嵌入式软件中有相当程度的软件质量问题都与编译器相关。

本文介绍了GCC和Keil C51等常见的嵌入式编译器的翻译过程,包括优化级别和方法,重点分析了数据预取技术,指令重排序技术,覆盖技术的编译器优化方法和原理,以及针对这3种优化技术在编程时的忽视引入的软件问题给出实例分析和规避方法。

1 嵌入式编译器

嵌入式编译器能将高级语言源程序转换为低级语言目标程序,在保证外部功能不变的情况下,减少目标代码的执行时间和存储空间[2]。

1.1 编译器翻译过程

编译器将初始源程序翻译成最终加载到目标机上的目标执行程序,要经过4个阶段:预处理(Pre

processing)、编译(Compilation)、汇编(Assembly)和链接(Linking)[3]。

1) 预处理器:将初始的源程序代码经过宏处理、文件包含及扩展语言等处理,生成源程序供编译器处理;

2) 编译器:将预处理器生成的源程序翻译成机器可识别的汇编程序;

3) 汇编器:将汇编程序翻译成机器语言,生成可重定位的机器程序;

4) 链接器/加载器:将不同的机器程序目标文件收集起来,构造成1个可执行文件,再将库函数绑定到目标程序中,由于程序在存储器中的位置不确定,再通过加载器对其进行重定位,将程序中的相对地址转换成存储器的真实地址,并加载运行。

图1 嵌入式编译器编译过程

1.2 典型嵌入式编译器

1.2.1 GCC编译器[2]

GCC(GNU C Compiler)是一种可移植的优化编译器,具有高度可移植性和代码优化质量。GCC编译程序采用优化的底层模型,由语言相关的前端、语言无关的后端和用于描述目标机特性信息的目标机描述组成[2]。它能够支持多处理器平台的可移植编译系统,将目标机相关部分从编译器中分离出来。

GCC编译器优化选项有5个级别,典型的优化内容如:RTL中间代码表示机制;目标机描述与定义机制;由目标机的机器描述引导中间代码生成及优化策略;控制流分析;数据流分析等[4]。

1.2.2 Keil C51编译器[5]

Keil C51是一种标准C编译器,它为8051微控制器的软件开发提供了C语言环境,同时保留了汇编代码高效、快速的特点。遵循ANSI标准,C51编译器可以实现对8051系列所有资源的操作,可以支持应用程序的调试。

Keil C51编译器优化级别有10级,默认为8级优化。典型的优化内容如:对8051系统的内部数据和位地址进行访问优化;数据覆盖;寄存器变量优化;CASE/SWITCH语句优化等。

1.3 编译器优化技术

所谓编译器优化就是对程序代码进行等价变换,生成更有效的目标代码。嵌入式编译器优化可以在不同阶段进行,可以分为局部优化和全局优化,也可以分为与机器有关和无关的优化等[6-8]。

1.3.1 数据预取技术[9]

为解决处理器访问存储器速度慢于内部寄存器的问题,引入编译器数据预取的方式提升处理器读取存储器的速度。

采用数据预取的基本思想是通过编译器插入预取指令,在处理器读取存储器之前先把数据放入缓存cache中,通过设置预取更新寄存器、寄存器冲突缓冲区和预取缓冲区等专用的硬件来访问缓存cache实现预取数据。这种方式既不会增加系统开销,也不会影响处理器访问缓存cache的速度[9]。

1.3.2 指令重排序[8]

为充分发挥处理器指令流水线的能力,提高执行过程中的效率与性能,减少内存操作速度慢于处理器运行速度带来的空置影响,编译器会按照某种规则将指令执行顺序打乱,多条指令根据设定的时间并行执行,写在后面的代码可能会比写在前面的代码先执行,从而使生成的目标代码具有更大的并行性。分为静态和动态的方法:静态方法是在程序编译优化或链接时实现,通过统计程序中分支的转移频率,计算指令的调用频率,规划指令的排序方式;动态方法是通过统计程序运行时的动态信息实现,需要借助其他辅助工具,根据动态信息实现指令重排[8]。

图2 指令重排序执行示意图

1.3.3 覆盖技术

覆盖技术是指一个程序的若干程序段或几个程序的某些部分共享同一个存储空间,相互独立的程序段在内存空间相互覆盖,实现小容量内存运行较大程序的功能。覆盖技术通常用于系统程序的内存管理,尤其是在嵌入式系统内存比较小时,能有效提高内存的使用率。

覆盖技术是C51单片机特有的一种编译器优化技术。受C51单片机内部堆栈空间限制,局部变量存储在RAM空间;在编译链接时就完成局部变量地址的分配定位;如果各函数之间没有直接或间接的调用关系,则其局部变量空间便可覆盖。

2 实例分析

2.1 volatile避免数据预取

volatile关键字表示避免进行默认的优化,将1个变量定义为volatile,表示该变量可能会意想不到的被改变,告诉编译器在每次使用这个变量时都需要重新读取,而不是使用保存在寄存器里的备份。

实例1:在进行数据采集时,从硬件端口地址连续读2次数据,第1次读取高字节,第2次读取低字节,2次读取的高低字节拼接成1个数据字。使用的编译器为Keil C51,优化级别为8级。

在无volatile修饰外部硬件端口地址时,C语言程序经Keil C51优化编译后,第1次从端口0x2000读取高字节数据放入累加器A中,第2次直接从累加器A中读取低字节数据,实际上硬件端口0x2000的2次数据已经发生改变,但是软件中获取的高低字节数据都是第1次读取到的高字节数据,导致获取的数据错误。如果在端口定义时增加volatile修饰符,从对应的反汇编代码可以清楚看到第2次读取0x2000端口数据时会从端口重新获取新的数据放入累加器A中,未经优化的处理保证了每次读取外部端口数据时都是从所在的端口地址去读取。使用volatile修饰能够保证编译器在读取外部端口地址时不进行优化,每次读取时访问它所在的硬件端口地址,而不是使用寄存器中的备份数据。

基于数据预取的编译器优化方法,为提高存取速度,访问寄存器的速度远快于外部存储器,一般会做减少访问外部存储器的优化处理,将外部存储器变量缓存到寄存器,下次读取时直接从寄存器取数。不使用volatile修饰,对外部端口或者寄存器进行访问操作时,编译器优化处理后会将其中的内容放到寄存器中,当外部端口的数据发生改变时,读取到的还是之前寄存器的数据值,并不是最新硬件端口的数据。对于多线程的情况,当变量值或端口数据在本线程里改变时,会同时把新值拷贝到寄存器中,以保持一致,但是如果变量在别的线程被改变或者端口数据值因外部硬件改变时,寄存器的值不会相应改变,导致本线程读取的值和实际的值不一致。对于外部端口或者寄存器,多线程共享全局变量,定义时使用volatile修饰能够避免编译器数据预取。

2.2 volatile避免指令重排序

使用volatile关键字可以禁用指令重排序,避免进行默认的优化处理。

实例2:某个多任务多线程代码,在其中一个线程中向多个端口地址连续输出一串数据指令。使用的编译器为GCC编译器,优化级别为O2。

表2 GCC编译器-硬件端口缺少volatile

在无volatile修饰时,输出数据的结果与实际向硬件端口地址写数据的顺序不一致,顺序紊乱,规律不易发现。在增加volatile修饰后,实际数据的顺序与向硬件地址写数据的顺序一致。

编译器为提高CPU运行效率进行优化,将写端口地址的操作顺序进行重排序优化操作,可能会使后面的代码先开始执行并先于前面的代码执行结束,导致实际端口输出结果与预期不符。存储器映射的外部地址和硬件寄存器在定义时需要添加volatile修饰符,禁用编译器的指令重排序优化。

2.3 重入函数避免变量地址覆盖

重入函数可被递归调用,也可以同时被2个或更多个任务调用,而不会出现数据错误。非重入函数不能由超过1个任务所共享,除非能确保函数的互斥(使用信号量,或者在代码的关键部分禁用中断)。

实例3:在主程序和中断程序均调用同一个函数模块,主程序在调用该模块时进行访问冲突保护。使用的编译器为Keil C51,优化级别为8级。

Main函数及中断函数调用函数关系及局部变量地址分配是在静态编译时就已经分配好的,可通过m51文件查看各函数和变量的地址分配情况进行分析。

图3 Keil C51编译器-局部变量地址冲突

图3(a)中Amodal函数为普通函数,主程序和中断程序中均调用函数Amodal,其中的局部变量A1在静态编译链接后分配的地址为D:0x000F。为了防止主程序被中断意外打断而对地址D:0x000F存在访问冲突,在调用Amodal前关中断,完成后开中断。函数Bmodal中的局部变量B1和Amodal中的A1在静态编译时分配了相同的地址,且主程序调用函数Bmodal时未进行中断保护,对2个局部变量的共用地址D:0x000F仍然存在访问冲突。

图3(b)中Amodal函数定义为重入函数,在函数定义的末尾添加关键字reentrant,形如void Amodal reentrant。静态编译链接后重入函数的局部变量A1放入模拟堆栈区,所在地址为i:0x0002,每次调用时会新建一块模拟堆栈区,将重入函数需要传递的参数和局部变量保存在该堆栈中,与之前使用的模拟堆栈区分开,这样既避免了主函数和中断中同时调用Amodal的冲突问题,也避免了2个模块中对于不同的局部变量可能分配同一个地址空间的问题。

Keil C51编译器在静态编译链接时为函数的局部变量分配好地址空间,特有的覆盖技术可能将不存在调用关系的函数内局部变量分配到同一个地址空间。针对Keil C51这种编译优化技术,主程序和中断应尽量避免调用同一个函数,如果必须要调用,需要进行保护或者将公共函数定义为重入函数。重入函数可以在任意时刻被打断,且不会丢失数据,对于每个重入函数,根据存储器模型在内部或外部存储器模拟重入堆栈区,每次被调用时使用一块独立的堆栈区进行参数传递和存放局部变量,如果再次被调用,会另外再开辟一段堆栈区进行参数传递和存放局部变量,因此2个堆栈区的局部变量不会出现之前访问冲突的问题。需要注意的是,单片机的资源非常有效,如果调用次数过多有可能导致堆栈溢出。

3 总结

嵌入式编译器为了从空间和时间上提高目标代码的存储和执行效率,对程序的功能进行等价转换。在设定编译器优化级别时需要合理选择,若降低优化级别,会导致代码的执行效率降低;若提高优化级别,可能因为编程处理不当引入程序问题。

高级C语言中的volatile修饰符和重入函数reentrant等都是为了避免编译器优化(数据预取,指令重排序,变量地址覆盖等)导致程序缺陷而引入的:

1) volatile的使用准则:外部硬件端口或寄存器;中断服务程序中的全局变量;多任务或多线程中的全局变量;

2) Keil C51编译器的局部变量地址在编译时已经分配完成,可通过查看m51文件或者反汇编帮助分析是否存在变量地址冲突的问题;定义reentrant重入函数能够避免内存地址访问冲突,但是需要考虑堆栈溢出的问题。

为避免因编译器优化引入代码缺陷,在研制过程中,需要熟悉编译器优化准则,严格遵守编程规范,有效保证嵌入式软件的质量。

[1] 张惠臻,王超,陈雁. 嵌入式软件性能分析方法研究与工具设计[J].计算机应用与软件, 2013,30(10):284-287,321.(Zhang Huizhen, Wang Chao, Chen Yan. On Embedded Software Performance Analysis Methods and Analysis Tool Design[J]. Computer Applications and Software, 2013,30(10):284-287,321.)

[2] 冯钢. 基于GCC的嵌入式系统编译器研究与开发[D]. 浙江大学硕士论文,2004.(Feng Gang. Research and Development of the Cross Compiler Based on GCC for Embedded System[D]. Zhejiang University for the Degree of Master, 2004.)

[3] 任坤. DSP编译器关键技术研究[D].浙江大学博士论文,2007.(Ren Kun. Research on Key Techniques for DSP Compiler[D]. Zhejiang University for the Degree of Doctor, 2007.)

[4] 汪小飞,赵克佳,田祖伟. 数据流分析的关键技术研究[J].计算机科学, 2005,32(12):91-93.(Wang Xiaofei, Zhao Kejia, Tian Zuwei. Research of Key Techniques in Data Flow Analysis[J]. Computer Science, 2005,32(12):91-93.)

[5] 晁阳. 单片机MCS-51原理及应用开发教程[M].清华大学出版社, 2007.(Chao Yang. MCS-51 Microcontroller Principle and Application Development Guide[M]. Tsinghua University Press, 2007.)

[6] 艾菊梅, 陆钢, 王强. 嵌入式系统中基于寄存器的编译器优化技术[J].华东理工学院学报,2007,30(1): 78-80.(Ai Jumei, Lu Gang, Wang Qiang. A Compiler Optimization Technology with Embedded System Based on Register[J]. Journal of East China Institute of Technology, 2007, 30(1): 78-80.)

[7] 张振宇,胡兆权. 嵌入式操作系统编译器优化技术分析[J].信息与电子工程,2003,1(4):269-271,280.(Zhang Zhenyu,Hu Zhaoquan. The Analysis of Embedded Operating System Compiler Optimizing Technique[J]. Information and Electronic Engineering, 2003,1(4):269-271, 280.)

[8] 张定飞, 赵克佳, 黄春. 指令Cache优化中代码重排技术研究[J].计算机工程与应用,2006,(7):28-30,68.(Zhang Dingfei, Zhao Kejia, Huang Chun. Research on Code Reordering Technology of I-Cache Optimization[J]. Computer Engineering and Applications, 2006,(7):28-30,68.)

[9] 连瑞琦,张兆庆,乔如良. 指令级并行编译器的数据预取及优化方法[J].计算机学报,2000,23(6):576-583.(Lian Ruiqi, Zhang Zhaoqing, Qiao Ruliang . A Data Prefetching Method Used in ILP Compilers and Its Optimization[J].Chinese Journal of Computers, 2000,23(6):576-583.)

[10] Todd C M, Monica S L, Anoop G. Design and Evaluation of a Compiler Algorithm for Refetching[C]. Proceedings of the 5th International Conference on Architectural Support for Programming Languages and Operation System, MA, 1992, 62-73.

Analysis of Embedded Software Defection Based on Compiler Optimization

Dong Yan, Huang Chen, Zuo Wanjuan, Yu Qian

Beijing Institute of Control Engineering, Beijing 100190, China

Accordingtocompileroptionandlevel,thesourcecodeisoptimizedtogenerateexecutableobjectcodebyapplyingembeddedcompiler.Thethreetypicalembeddedcompileroptimizationtechniquesknownasdataprefetching,instructionreorderingandcoveringtechniqueareanalyzed.Themechanismisintroducedbycombiningwithspecificexamplestoavoidinvolvingbugsandthenthesolutionandsuggestionareprovided.Compileroptimizationcriteriashouldbeknowncompletelyinthedevelopmentandtestingphase,andstrictcompliancewiththeagreedprogramisobeyed,tofurtherenhancethequalityofembeddedsoftware.

Compileroptimization;Dataprefetching;Instructionreordering;Coveringtechnique

*国家自然科学基金资助项目(91118007)

2016-05-09

董 燕(1972-),女,河南人,硕士,高级工程师,主要研究方向为软件测试;黄 晨(1983-),女,湖北人,博士,工程师,主要研究方向为软件测试;左万娟(1971-),女,四川人,硕士,高级工程师,主要研究方向为软件测试;于 倩(1982-),女,河北人,硕士,高级工程师,主要研究方向为软件测试。

TP311.5

A

1006-3242(2016)05-0064-06