-1-中国科技论文在线针对内核扩充函数出错处理的日志手动注入方式钱诚,刘虎球,王瑀屏(复旦学院计算机科学与技术系,上海100084)摘要:提出一种日志手动注入的方式中国linux操作系统,解决驱动程序中的内核扩充函数调用不规范的问题。首先静态扫描源程序,当发觉程序中调用了内核扩充函数但没有检查返回值时,则在调用后手动插入对返回值的检查代码,并促使这种函数在执行发生错误时会被日志记录。一旦系统发生崩溃,这种日志信息将成为关键的调试根据。实验结果表明,该方式对系统性能影响很小,且可以帮助调试人员更高效地定位和调试内核模块错误。关键词:内核扩充函数;日志注入;驱动错误;程序检查中图分类号:TP312文献标志码:AAnautomaticloginjectionmethodhandlingkernelextensionfunctionerrorQianfú,LiuHuqiu,WangYuping(DepartmentofComputerScienceandTechnology,TsinghuaUniversity,Beijing100084,China)Abstract:Anautomaticloginjectionmethodisproposedtosolvethenon-standardcallingofthekernelextensionfunctionsindevicedriver.Firstlythesourcecodeisscannedstatically,andcheckingcodeisinsertedautomaticallywhenthereturnvalueofthekernelextensionfunctionisnotchecked.Atthesametime,alogisrecordedwhenthesefunctionsreturnerrors.Oncethesystemcrashes,theseimportantlogswillbethekeyevidencetodebug.Theexperimentshowsthat,ourmethodhaslittleinfluencetothesystemperformanceandcanhelptheprogrammerslocateanddebugthekernelmoduleerrorefficiently.Keywords:kernelextensionfunction;loginjection;drivererror;programtesting驱动的可靠性仍然是影响操作系统稳定的重要诱因[1]。
通常而言,驱动运行在内核态[2],为此,驱动程序的错误很容易导致整个操作系统的崩溃。在Linux操作系统中,驱动代码占代码总数的70%[3],其中形成的错误是内核其他部份形成错误的3~7倍[4]。对于WindowsXP这样的商业操作系统而言,导致系统崩溃的程序错误中,驱动程序甚至占到了约85%[5]。驱动程序中出现这么多的错误,主要缘由是驱动程序是由许多不同的公司和组织共同开发的,缺少统一的规范,程序员对于内核扩充函数的理解和使用并不正确[6]。因而,对内核扩充函数的使用进行规范与测量是十分必要的。内核扩充函数是操作系统内核为驱动程序等内核模块提供的功能插口。驱动程序一般须要调用内核扩充函数完成基本的资源管理功能,如kmalloc/kfree函数被用于分配和释放显存;request_irq函数被用于注册中断处理函数等。经过常年的改进,这种函数一般会在异常情况下返回出错返回值,但出错返回值却常常被驱动程序员所忽略,没有得到正确处理,一旦内核扩充函数出错,将影响后续代码的正确执行,甚至引起系统崩溃。当操作系统发生错误或则系统崩溃时,调试人员希望晓得系统运行出错时的状态以及引起程序出错的数据特点。
但在商业使用中,引起程序错误的数据很可能是商业秘密,调试人员只能利用系统运行日志查找错误。一旦运行时程序形成错误之处没有相关的日志记录linux 调用内核函数,错基金项目:上海高等教育青年班主任基金捐助项目(YETP0108)作者简介:钱诚(1991—),男,硕士研究生,主要研究方向为操作系统驱动程序通信联系人:王瑀屏,助理研究员,主要研究方向为操作系统,-2-中国科技论文在线误将很难被定位和修补。假如可以使系统运行发生错误时的一些系统状态等信息被日志记录,则调试人员可利用日志更高效地进行调试,以有效提高驱动的可靠性。1相关工作为了提升程序的可靠性,人们先后提出了好多检查程序错误的方式,其中主要有静态检查和动态测量两类技巧。静态检查方式可以达到较高的代码覆盖率,但因为没有实际运行程序,所以对测量出的错误会有一定的误报和漏报。SDV[7](staticdriververify)系统性地剖析Windows内核驱动,可以找出其中的设计不足和弱点。SymDrive[8](symbolicdrive)在真实设备不存在的前提下,采用符号化执行的方式能够对Linux和FreeBSD驱动进行测试。动态测量方式在代码覆盖率方面存在不足,不能检查所有程序分支,并且检查程序的输入样例无法构造。
Nooks[9]从操作系统中隔离驱动程序错误,致使驱动程序的错误不会对操作系统形成影响。ShadowDrivers[6]是Nooks的升级版本,作为驱动备份可以对用户隐藏驱动错误,透明地保存驱动的运行状态。本文所指的用户包括操作系统和应用程序,即应用程序和操作系统并不晓得驱动程序发生了错误,可以不受任何影响地继续正确运行。一方面,SafeDrive[10]从程序的数据类型和注释中手动进行推算,可确保驱动使用的数据是安全的;另一方面,在小型软件投入商业使用的过程中,会发觉好多没有被测量出的程序错误,这种错误发生后,虽然开发厂商希望要尽早解决问题,但商业使用中用户的输入数据和操作行为常常是商业绝密,调试人员很难再现那些错误,为错误定位和修补带来很大困难。基于日志的测量方式是一条有效途径,关键问题是如何以更少的运行时间代价记录更多的有利于调试错误的信息。SherLog[11]将错误日志的内容作为切入点,手动推算出源程序可能的执行路径。缺点是受限于错误日志的内容,假如日志中驱动失败的信息甚少,将难以正确进行推测。LogEnhancer[12]工具是对SherLog测量能力的补充,其通过将有意义的变量值记录到日志中,致使日志的内容愈发丰富全面,以便在错误发生时进行推算,缺点是没有解决程序的什么关键部份应当被日志记录。
ErrLog[13]扫描整个源程序linux vi 命令,辨识会发生异常的程序段落,并测量这种异常发生时是否会被日志记录。现有的静态动态测量工具和日志工具,都无法挺好地处理内核扩充函数出错的问题。因此,本文通过静态检查方式,发觉返回值没有被正确保存和测量的位置,并手动注入记录日志代码,在日志中补充函数执行错误的信息,并结合动态测量方式,通过系统出错时日志中给出的信息协助调试,达到提升系统可靠性的目的。2实验方式基于上述剖析,本文构造了一个剖析C语言源程序的自动机模型,基于该模型实现了一个才能手动定位并记录内核扩充函数出错的工具KefLog(KernelExtensionFunctionLog),并将对其进行详尽的介绍。测量目标KefLog测量的目标是驱动代码中调用内核扩充函数却未正确处理返回值的情况。因而,对可能的未正确处理情况进行督查,发觉实际的未正确处理返回值的情况可以归为3类典型错误。-3-中国科技论文在线未正确处理内核扩充函数返回值的情况如图1所示。intfuncA(…){…funcRetErrNum(…);…}intfuncB(…){…if(funcRetErrNum(…)){…}…}intfuncC(…){…intvar=funcRetErrNum(…);intvar2=var*2;…}(a)(b)(c)图1未正确处理内核扩充函数返回值的情况假定funcRetErrNum是一个在执行时会返回有意义的整数值的内核扩充函数,其中,情况(a)对函数返回值既没有保存也没有检查,即没有作任何处理;情况(b)尽管测量了返回值,但只是对函数正确的返回值作了处理,对于返回值是错误号的情形仍未处理;情况(c)似乎保存了返回值,但没有对其进行检查就直接使用该返回值参与后续运算处理。
KefLog通过构建自动机模型检查上述3种典型错误,并对源程序进行相应的更改,手动注入保存而且检查返回值的代码。假如返回值表明内核扩充函数具有执行成功,则继续执行;否则执行相应的处理代码。理想地处理返回值代码将涉及驱动功能语义,很难手动完成。KefLog参考LogEnhancer和ErrLog的工作,并不更改原驱动语义,而是将内核扩充函数返回值出错的相关信息记录到日志中,便于出错后更高效地进行定位调试。KefLog工具的输入是内核源代码和驱动模块源代码,主要测量过程分为3步:(1)第一步为预处理步骤,目的是消除程序中的注释部份,增加算法冗余和干扰;(2)第二步为测量打算步骤,目的是提取内核扩充函数及其返回值信息;(3)第三步为主要的测量步骤,目的是通过自动机模型对内核扩充函数的调用进行检查和更改。下边分别重点介绍步骤(2)和步骤(3)。检查打算为对程序中调用内核扩充函数的位置进行检查和更改,需要确定什么内核扩充函数会返回错误值,以及会返回什么错误值。本文中,KefLog通过[“extern”]blk[“static”]blk[“const”]blkTYPEblk[“inline”]blkfunc_name([paramX]){…}模式匹配内核文件中的所有函数。
该模式中,extern/static/const/inline是C语言关键字;blk是由空格或制表符等组成的代码空白区;TYPE是函数返回类型;func_name是函数名;paramX表示函数参数名和函数参数类型。根据该模式,先从匹配函数开始,之后再通过大括弧匹配函数体和结尾。假如该函数为内核扩充函数而且有返回错误值的return句子,则该函数被视为重要函数而被记录出来。记录的信息包括函数名和该函数所有可能返回的错误值。在典型的操作系统中,内核扩充函数返回的错误值一般是系统定义的错误号,分别代表系统或资源环境的不同状态,表示不同的意义。Linux操作系统中一些典型常用错误号如表1所示。表中,依照错误的严重性将错误号分为了3类。第1类为最严重的错误,第2和第3类错误的严重性依次增加。KefLog依据错误的严重性程度的不同,注入不同的日志信息。-4-中国科技论文在线表1常用Linux内核错误号错误号名称第1类EPERM、ENOENT、ESRCH、EIO、ENXIO、ENOEXEC、EBADF、ENOMEM、EACCES、EFAULT、EEXIST、ENODEV、EINVAL、ENOSPC第2类E2BIG、ECHILD第3类EAGAIN、EBUSY检查过程主要的测量过程可以具象为一个与编译器类似的自动机模型。
该自动机由系统状态集合、输入符号集合、错误值返回函数集合等构成。(1)系统状态集合。该集合中的元素表示的是测量系统所处的状态。用符号q表示状态,其中包括一个起始状态q0,系统状态集表示为Q{q0,q1,q2,…,qn}。(2)输入符号集合。该集合中的元素是系统输入的符号,以所有的源文件消除注释后的所有内容作为测量的输入。输入符号并不是单个字母,而是更有意义的一些字母组合,例如C程序的关键字、程序变量等。其中函数名也是输入符号,函数参数和函数参数类型也是输入符号。用符号s表示输入符号,输入符号集表示为S{s1,s2,…,sn}。(3)错误值返回函数集合。该集合中的元素是第二次扫描时被记录的这些会有错误值返回的内核扩充函数,每位元素由函数名和该函数的所有错误返回值信息组成。用符号func表示函数,函数集合表示为Func{func1,func2,…,funcn}。自动机检查的流程图如图2所示。图2自动机流程图状态qi表示所有状态的集合,自动机从q0状态开始运行。当输入某一符号sj时,自动机判定输入符号是否为内核扩充函数funcm。判定为不是时,则跳转至状态集合中的某一状态;判定为是时,则继续判定返回值是否被测量。
程序早已被测量,则状态跳转至状态集合中的状态;若没被测量,则会对程序进行更改linux 调用内核函数,插入对返回值的测量句子,更改后跳转至状态集合中的某一状态。当没有输入符号时,自动机停止工作。自动机在检查到图1的3种情况之一时,将会对源程序做对应的更改,如图3所示。-5-中国科技论文在线intfuncA(…){…int_ret1=0;_ret1=funcRetErrNum(…);if(_ret1<0){printk(KEFLOG”Error_ret1valueis%d.n”,_ret1);…}…}intfuncB(…){…int_ret2=0;_ret2=funcRetErrNum(…);if(_ret2<0){printk(KEFLOG”Error_ret2valueis%d.n”,_ret2);…}if(_ret2){…}…}intfuncC(…){…int_ret3=0;_ret3=funcRetErrNum(…);if(_ret3<0){printk(KEFLOG”Error_ret3valueis%d.n”,_ret3);…}intvar=_ret3;intvar2=var*2;…}(a)情况修正(b)情况修正(c)情况修正图3KefLog修正后的内核扩充函数返回值处理(1)针对图3中的情况(a),当KefLog检查到程序中调用了重要函数funcRetErrNum,但并没有对返回值进行保存也没有检查时,KefLog会对程序进行更改。
先保存funcRetErrNum的返回值至变量_retN,接着插入检查返回值是错误号的程序片断,插入的程序会向日志中输出变量名_retN和变量_retN的值。其中,N是从1开始累计的正整数。_retN变量在程序中具有惟一性,便捷调试人员按照该标示进行快速定位。(2)针对图3中的情况(b),当KefLog检查到程序中调用了重要函数funcRetErrNum,但并没有判定函数返回值是错误号时,KefLog会对程序进行更改。首先保存funcRetErrNum的返回值至变量_retN,接着插入检查返回值是错误号的程序片断,插入的程序会向日志中输出变量名_retN和变量_retN的值。(3)针对图3中的情况(c),尽管程序员在调用重要函数funcRetErrNum时保存了返回值至变量var,当KefLog检查到该变量var在没有经过检查就被直接使用时,也会对程序进行更改。首先保存funcRetErrNum的返回值至变量_retN,接着插入检查返回值是错误号的程序片断,插入的程序会向日志中输出变量名_retN和变量_retN的值,最后会插入一条句子,将保存有funcRetErrNum返回值的变量_retN的值形参给程序变量var。
通过上述更改,KefLog促使上述3种情况就会保存并检查返回值,但并不更改驱动程序起初的语义。假如在运行时函数返回的是错误值,则会通过手动注入的代码记录相应的出错日志信息,以作为调试的重要信息。3实验结果为验证KefLog工具,分别从检错能力、调试效率和负载消耗3个方面进行测试实验。检错能力测试检错能力测试的目的是验证KefLog才能有效监测出实际存在的未正确处理内核扩充函数返回值的情况,具体方式为:首先使用KefLog针对Linux内核的版本和版本分别进行检查,记录两次测量的结果;之后进一步比较两次测量的结果,假若同一个位置在版本中存在错误记录,但在版本中不存在错误记录,则可以通过人工比较确-6-中国科技论文在线认觉得KefLog找到了版本的真实错误,版本通过其他方法找到并修正了该未正确处理的情况。通过上述实验,KefLog在版本中共发觉247处在版本中被修正的真实错误。可见,该类错误在内核中广泛存在,KefLog才能有效地检查出内核中的该类函数调用错误,从而提升驱动程序的可靠性。诸如,两个版本的drivers/amba/文件中amba_device_register函数的相应代码如图4所示。Linux