一种schedule调度模块的形式化验证方法与流程

文档序号:18899152发布日期:2019-10-18 21:41阅读:307来源:国知局
一种schedule调度模块的形式化验证方法与流程
本发明属于计算机及程序可信领域,特别涉及一种对程序代码进行正确性及安全性的验证技术。
背景技术
:形式化方法以逻辑系统为基础,支持对计算系统进行严格规约(描述)、建模和验证,通过形式化方法进行严格证明的系统,可以认为其满足功能正确性,且无二义性,这是因为形式化方法基于严格数学基础。通过形式化语言构建的规约,对应于软件生命周期不同阶段的状态描述,而验证是对不同状态之间满足的各种逻辑关系即在不同状态所需要满足的各类正确性需求进行证明。也因此形式化证明可以证明一个系统存在某个缺陷或者无法匹配某个属性的问题,经过形式化方法严格证明的系统可以被认为是正确和可靠的。根据ccf中国计算机科学技术发展报告,形式化方法越来越受到国际国内计算机学术界重视。形式化方法已经被许多国际标准化组织列为安全有关系统必备的技术手段。在软件安全等级的sil1-4中,安全级别最高的sil3和sil4要求必须使用形式化方法。而在it产业界,微软开发了一系列形式化验证工具来对众多软件的正确性分析;facebook将给予分离逻辑的验证工具infer广泛用于其android应用的开发过程中;国内的华为公司成立形式化验证团队,使用定理证明辅助工具验证操作系统内核正确性和安全性等。近些年来,对于实际系统进行代码级的形式化证明主要分为操作系统内核验证和编译器验证。操作系统内核验证的工作有sel4微内核的形式化证明,这是由国外nicta工作室完成的,主要通过构建机器模型,证明抽象规范每一步的状态变换,可执行规范都会产生唯一对应状态转换。经过形式化证明的sel4内核可以针对缓冲区溢出、引用空指针、指针类型错误以及内存泄漏等问题进行有效防御,国内方面主要是中科大团队对μc/os-ⅱ的验证。在编译器方面,最出名的便是inria对compcert的证明以及后续工作。该编译器提供了数学上的、经过机器检查的证明,即证明生成的可执行代码的行为完全符合源程序的语义规定。经过该编译器编译的c代码可以排除编译器引入错误的可能性。也就是说该编译器生成的可执行代码被证明与源代码c程序的语义所指定行为完全一致。但是现有技术对于mils架构的schedule调度模块的验证过程较为复杂,验证效率低下。技术实现要素:为解决上述技术问题,本发明提出一种schedule调度模块的形式化验证方法。本发明采用的技术方案为:一种schedule调度模块的形式化验证方法,包括:s1、对schedule调度模块的源代码进行重构操作;s2、对重构之后的代码进行形式化建模;s3、对重构之后的代码进行形式化定理描述;s4、对所建立的形式化模型以及定理描述的状态变化进行形式化证明。所述步骤s1具体为:对schedule调度模块的源代码中引用的函数或者其他数据类型执行导入操作;采用if…else…语句代替函数中的goto语句;将内联汇编代码重构为普通c代码;将复杂类型的函数分离出来,作为单独函数进行证明。步骤s2具体为:采用compcertc编译器和vst工具对函数的结构进行自动化建模。步骤s3所述的定理描述包括:对抽象逻辑进行描述、对具体逻辑进行描述。当已证明一个函数的具体逻辑和抽象逻辑一致性之后,如果待证明函数当中包含了该函数的调用,则在待证明函数进行证明时,使用已证明函数的抽象逻辑代替待证明函数的具体逻辑。本发明的有益效果:本发明的方法首先对mils架构的核心模块代码进行梳理重构,保证改代码能够被compcertc编译器编译通过;其次是对代码进行分析整理掌握核心模块代码执行原理以及功能需求,并描述出该需求;然后通过辅助工具完成对改代码函数的建模工作;之后根据函数结构对函数进行抽象化功能描述,再根据所建立模型以及功能描述和功能需求,进行定理描述;最后对定理进行形式化证明工作,证明模块的正确性和可靠性;通过形式化验证的方法保证了schedule调度模块的功能正确性和安全性,进一步提高mils架构的安全性和可信度。附图说明图1为本发明的实现框图;图2为本发明实施例提供的spec形式化描述流程图。具体实施方式本发明采用compcertc编译器进行mils架构的schedule调度模块的形式化验证。compcertc编译器是由法国inria研究所完成的经过形式化证明的编译器。该编译器提供了数学上的、经过机器检查的证明,即证明生成的可执行代码的行为完全符合源程序的语义规定。经过该编译器编译的c代码可以排除编译器引入错误的可能性。也就是说该编译器生成的可执行代码被证明与源代码c程序的语义所指定行为完全一致。该编译器一共包括3个部分,第一部分为代码解析、类型检查以及预处理,即将c代码转换为compcertc语言的抽象语法树。这一部分是没有进行形式化证明的,但是因为compecertc就相当于c的子集,因此编译器可以根据c语言具体语法输出生成compcertc代码,所以可以通过手动检查这部分转换结果;第二部分为将compcertc语法树转换为汇编语法树,这一部分经过coq工具证明了正确性;第三部分为组装和链接,即将第二部分所生成的汇编语言抽象语法树以具体汇编语法打印,调用系统汇编工具以及链接器分别生成目标文件和可执行文件。本发明的实现流程如图1所示,主要分为三大部分来实现的。第一部分是对源代码进行重构操作,第二部分是对重构之后的代码进行形式化描述工作,包括对函数和数据类型等数据结构形式化建模操作,以及通过对函数执行前后状态变化关系分析所完成的形式化定理描述工作,第三部分则是对第二部分所建立的形式化模型以及定理描述的状态变化进行形式化证明工作,如果证明成立,则表示该函数具有正确性,如果不成立,则存在两种结果,一种是因为函数本身存在问题,不具备正确性,另一种则是因为定理描述存在问题,描述不完备。1、代码重构操作代码重构部分主要包括以下内容:1)将文件中所有导入关系导入到该文件中,即将文件中引用的函数或者其他数据类型等导入到该函数中。源代码通过compcertc编译,compcertc编译器只能编译单个文件,其对文件中引用关系和导入关系工作做得不是太好,因此首先需要将该文件中所有导入关系都导入到该文件中,保证文件能够成功编译。本步骤包含了许多宏选择定义,即通过某个属性的值的定义结果,来选择应该导入哪些头文件或者具体类型定义。因此在进行这部分工作时,需要使用#error语句进行测试,即在所要求的环境下,测试宏选择定义的路径选择结果。2)对函数中出现goto语句部分进行重构,即使用语义相同结构代替goto语句,保证函数中不会出现goto结构。compcert编译器是无法识别goto语句结构的,因此为了能够顺利编译,需要在保证函数功能不变前提下进行重构操作,在代码分析过程中,需要考虑到goto跳转的位置,对于跳转到函数内部位置的goto语句可以使用if…else…语句来代替,而对于跳转到函数外部位置的goto语句,则需要谨慎分析,必要时添加函数调用。在整个代码中,所有goto语句均为跳转到函数内部位置。3)对函数中出现内联汇编部分进行重构,将汇编代码替换为c语言代码形式。内联汇编,就是在程序编写过程中,加入汇编语言。传统的汇编语言抽象程度比较低,无法用来编写大型程序,而且汇编代码受具体机器影响,可移植性无法保证;而纯粹的c语言代码,效率低,每次执行都要先编译为汇编语言,在翻译为机器语言。为了解决这两个问题,就可以引入内联汇编。内联程序的代码可以调用程序中所有的变量以及对应空间的函数。也正是如此,其与c代码集成工作非常容易,而集成之后可以执行在c或者c++无法完成或者完成困难的一些任务。在本次课题中,也包含了许多内联汇编代码,虽然在c程序编写过程中,内联汇编代码优势很大,但是在形式化证明时,compcert是无法编译内联汇编代码的,也因此需要将内联汇编代码重构为普通c代码。4)简化代码,对代码中复杂结构进行分离操作,简化代码,从而简化后续证明工作。对于一些包含有复杂数据类型的函数,证明过程就显得比较冗长复杂,因此可以考虑分离思想,即将复杂数据类型分离出来,作为单独函数,单独证明,最后在主函数中,引入对该复杂类型的函数证明,就可以简化证明结构。2、形式化建模形式化建模工作主要是对模块函数的形式化描述工作,一方面是针对函数结构进行描述,这部分工作可以通过compcertc和vst工具自动转换,另一部分则是要对函数中某些特定结构的建模工作,比如特定结构体等数据类型。自动化建模工作大大减少了整个项目的工作量,但是其自动化建模仅仅是针对函数的结构进行建模,对于其中某些特定结构以及函数功能描述,以及辅助函数的定义还是需要自己去描述。对于结构体类型vst里面定义为“tstruct_namenoattr”,但是该类型定义无法表示结构体变量的内部结构。结构体类型,可以理解为是一个不同类型的集合表示,因此在描述结构体类型的时候,需要考虑其内部变量类型该如何去表示,然后结构体名字就作为这些变量的集合名。表1一些简单类型在vst库中的表示方式,这种表示方式可以用来表示描述函数参数或者中间变量以及返回值的类型,例如参数类型为int,则函数模型中会表示为tint,返回类型为unsignedint,则表示为tuint,但是当涉及到具体操作的时候,可能需要使用其他方式来描述,比如对于c程序里面的一些算术操作,因为在coq中对于整数表示只有z,其中的加减乘除也都是以z为单位来进行计算,所以在整个过程中,我们需要表示变量类型为z,当最后表示在某个地址上存储的值为什么类型时,通过辅助函数,进行类型转换,将z转换为需要类型。表1c语言简单类型与vst库mapping表c语言基本类型vst库中对应类型unsignedinttuintunsignedshorttushortunsignedlongtulongunsignedlonglongtulonginttintsignedlongtlongsignedlonglongtlongstructa{…}tstruct_anoattrchartschar在对结构体进行描述时,实际是要表示内部变量的值,而在函数操作中,对结构体进行操作,也是对这些值进行操作,因此对于整型变量类型的,都可以通过z类型来表示,而在上述例子中包含有“structdomain*domain;”和“structvcpu_runstate_inforunstate”,两种结构体类型成员,这里就需要特殊处理。对于结构体内部嵌套结构体类型指针,就相当于对于结构体的间接引用,指针的值就是指向另一个结构体的地址,而这传递地址实质也是一串整型类型的数字,相当于该类型为“z->struct”,也就是这串地址就可以表示该结构体变量,后续对该结构体的操作,都可以通过该地址来定位结构体进行操作。3、定理描述所描述的定理要符合vst的要求规范,即该部分定理是使用hoare逻辑结构表示,能够反映函数执行前与执行后的各部分状态变化,定理描述的步骤如图2所示,后缀均表示为spec(即函数规约表示)首先需要列举出整个函数执行过程中出现的变量,这里主要是对函数参数以及全局变量的列举,表示在函数执行前有哪些变量状态可能改变;然后在对函数形参及参数的类型进行描述,表示函数结构是否符合之前的函数模型;后续部分①表示pre部分即函数执行前的状态,②表示post部分即函数执行后的状态,因为我们证明方式是正向证明即由前置条件推后置条件成立,所以pre可以作为前提条件辅助证明,我们最终需要证明的就是函数执行后的状态与所预期执行之后状态是否一致,如果一致则函数不存在问题,如果不一致则说明程序存在问题或者spec描述存在问题。另外,在进行定理描述时,同一个函数要进行两次定理描述,一次是对抽象逻辑进行描述,另一次是对具体逻辑进行描述,抽象逻辑就是函数的简化逻辑。在证明时,我们不仅要证明程序逻辑的正确性,还要证明具体逻辑和抽象逻辑的一致性。这样做的好处是,当我们证明一个函数的具体逻辑和抽象逻辑一致性之后,如果其他函数当中包含了该函数的调用,则在其他函数证明时,可以直接使用该函数的抽象逻辑代替具体逻辑。进而简化证明过程。证明过程使用到的主要策略包括:“rewrite”改写策略,其作用就是将当前目标中的目标变量替换为前提中的要求变量。例如前提中假设了“n=m”,通过rewrite策略可以将证明目标中的n变量改为m变量,通过添加箭头符号,可以指示其改写的方向,例如如果为“rewrite<-”,则是将证明目标中的m变量改为n变量。“apply”策略,也是一个替换策略,不过与“rewrite”策略的区别是,该策略提供了替换之后的化简运算操作,例如对于“n=m”的证明目标,前提中刚好有命题“m=n”,如果使用rewrite,则证明目标被简化为m=m,而apply策略则会在在替换以后对证明目标进行化简,从而证明该目标。“forward”策略用来推动hoare逻辑证明的正向执行,例如对于hoare逻辑证明{p}i=0;more{r},首先需要应用顺序规则对于推导出来的断言q语句,其中的赋值、返回、中断、继续等类型的语句都可以通过forward策略自动推导,forward策略应用了一个强后置条件类型的证明规则推导q。另外对于不同的c语句,forward策略也有不同的几种形式:“forward_if”策略,用于对“if…else…”语句的推导;“forward_while”策略,是对于while循环的推导;“forward_call”策略是当涉及到函数调用时,所进行的推导策略。“entailer”策略,是用来完成对于蕴含语句的推导工作。蕴含语句表示“对于p、q两个命题,如果p成立则q成立”,记做p→q,p为蕴含式前件,q为蕴含式后件。在程序中该语句表示任何满足p语句的状态也一定满足q语句,vst中表示为“entailδ,”,“δ”表示上下文全局类型,用来提供状态所附加的约束条件。“entailer”策略主要通过对“δ”内容进行推导,并结合前置条件,判断约束条件在满足p语句的同时是否满足q语句,如果都满足,则推导结束。以下通过对“vcpu_urgent_count_update”具体函数的形式化证明来对本发明的内容进行说明:该函数用来对vcpu中的is_urgent值进行计数更新,is_urgent的值决定了vcpu是否应该被快速唤醒,它用来表示vcpu的紧急程度。上述代码已经进行了代码重构,其主要就是将指针结构替换为函数结构“get_vcpu_domain_id(v)”、“set_vcpu_is_urgent(v,0);”等函数。然后通过compcertc工具和vst工具进行自动化建模工作,再对其中的特定结构体以及函数功能进行描述,这里面的主要结构体就是vcpu结构体和domain结构体,具体描述在上文中已经有过介绍,这里来介绍对函数功能的描述。上述代码表示对该函数的功能描述,其中definition就类似于c语言中函数声明,括号里面表示的是该函数所用到的参数及其类型,vpool就是vcpu结构体的整个集合,这里通过vcpu_id来寻找指定vcpu结构体;dpool为domain结构体的整个集合;av表示的就是该vcpu的抽象表示;v_pause_addr与v_domain_poll_mask都是在该函数中需要用到的结构体的内部变量;对原函数进行分析,发现最终是要改变vcpu的状态,因此实际上需要改变的是vpool域中指定vcpu的值,因此该函数返回状态类型的可以为这个集合类型。“get_vcpu_domain_id”函数首先对vpool域进行检索,通过av的值找到指定结构体,然后得到该结构体中domain的地址,在对dpoll域进行检索,最后找到指定的domain_id并赋值给did变量,其结构如下:根据函数结构进行if判断,判断该d_id的值是否等于0x7fffu,z类型在coq中表示形式是十进制,因此可以转换为十进制2047,如果相等的话,直接退出函数,函数的所有状态未发生改变,因此这里直接返回vpool。如果不相同就执行接下来的if语句,coq中“test_bit”函数是用来检测指定位是否为1或0,如果为1返回true,为0返回false,在原函数中第一个test_bit的参数为“&v->pause_flags”,这里不需要关心该值的结构,只知道该参数类型为一个整型,将v_pasue_addr作为该参数,而v_pause_addr具体值则是放到定理描述中去叙述。原函数中的testbit函数如上,实际就是检测p[nr_1]是否为1,也正是如此在功能描述时,需要对coq里面的testbit参数进行修改(进行移位并除以32)。功能描述结束之后就可以进行定理描述工作,这时就要关心函数执行前后状态的变化情况:这里主要分析下pre和post部分内容,该函数参数为结构体指针类型v,在pre里面声明出来“[_vof(tptrt_struct_vcpu)]”,prop里面就是函数的条件约束,因为代码有点多所以每一种约束列举一个,首先with里面涉及到的变量的约束:比如av、adomian、ad_id等值的范围大小,以及相应值如何得到。还有包括对if条件判断的条件约束,即if条件成立的执行路径和if条件不成立的执行路径。另外就是执行之前vpool和dpool状态不变。post里面没有内容是因为该函数后置状态的改变是因为其他函数调用执行的,因此后置条件里面对于空间谓词均表现在所调用函数的证明当中,所以对这个函数的证明就可以省略空间谓词描述变化,所以这里只提到返回类型tvoid。证明部分就是对上述定理进行证明,这里只列出了证明使用的主要策略。semax_body负责将函数模型、定理描述以及一些全局类型整合成hoare逻辑语句。start_function表示证明工作的开始,forward_call策略表示对函数调用进行推理,原函数中这里调用了get_vcpu_domain_id函数,此时就转到对get_vcpu_doamin_id函数的推理证明,因为该函数之前已经证明过,所以通过forward直接进入下一个证明目标;对if语句证明时,forward_if策略括号里面的结构必须为prop()local()sep()结构,在这里主要是描述if的执行过程,所有if的执行路径均卸载prop里面。在该策略执行完之后,所要证明的目标变为了“set_vcpu_is_urgent()”,这里同样是一个函数调用,因此需要使用forward_call进行推理证明,这样一来证明目标基本完成,最后所要进行的就是该函数经过上述目标语句之后的状态与预期函数执行之后状态是否一致,“unfold”策略是负责展开证明中所遇到的目标函数,这里就是我们之前所进行的功能描述展开该函数之后通过,apply、rewrite等策略进行推导证明,证明功能一致性,如果最终推导成功,则是用qed结束整个证明,表示证明成功,该函数具有正确性,如果推导不成功,则说明函数存在问题或者定理描述存在问题。本领域的普通技术人员将会意识到,这里所述的实施例是为了帮助读者理解本发明的原理,应被理解为本发明的保护范围并不局限于这样的特别陈述和实施例。对于本领域的技术人员来说,本发明可以有各种更改和变化。凡在本发明的精神和原则之内,所作的任何修改、等同替换、改进等,均应包含在本发明的权利要求范围之内。当前第1页12
当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1