一种栈溢出检测系统及方法与流程

文档序号:14911639发布日期:2018-07-10 23:34阅读:598来源:国知局

本发明涉及计算机技术领域,具体是涉及一种栈溢出检测系统及方法。



背景技术:

在计算机科学中,栈通常用来存储临时变量、向被调用的函数传递参数、保存任务状态,保存函数返回地址等。通常情况下,栈基址,即栈底是一个固定的地址,栈顶则是在栈基址的基础上向上或者向下增长一定大小空间后的地址,栈顶与栈底之间的空间即为栈空间。SP(Stack Pointer,栈指针)指向栈的实际顶部,SP不能越出栈顶。本发明以32位的MIPS(Microprocessor without Interlocked Piped Stages,无内部互锁流水级的微处理器)系统为例进行说明,MIPS具有32个通用寄存器,其中SP寄存器、RA(Return Address,返回地址)寄存器等扮演着重要的角色。MIPS栈空间采用的是向下增长的方式,栈底在高地址,SP寄存器的值就是当前运行函数的栈指针,每个栈帧中所存放的内容和存放顺序由目标体系架构的调用约定来定义。

通常情况下,嵌入式软件系统中的栈大小是预先确定的。例如在VxWorks操作系统中,任务栈是独立的,创建任务时可以指定每个任务的栈大小;中断栈是共享的,中断栈在系统启动时分配,其大小可由用户来配置。如果由于某种原因,例如局部变量过大、嵌套的函数层次过深等,栈指针可能越出了所分配的栈空间范围,导致内存覆盖或者非法内存访问。如果不能在栈溢出的第一时间发现问题,并阻止程序继续运行,则错误会传播,导致问题的定位更加复杂。因此,有必要在第一时间就能检测出嵌入式软件系统中的任务栈或者中断栈是否发生了溢出。

检测任务栈或者中断栈溢出的方法通常有下面几种:

1.静态分析法

静态分析法的基本思想是:获取每个函数的栈大小,建立一棵调用树,调用树中的每个节点均表示一个函数,其根节点为任务或者中断的入口函数,叶节点则是不再调用其它任何函数的函数。然后累加调用树中根节点到各个叶节点的每条路径的栈大小,将其中最大的栈大小作为该任务所需的栈大小;对于中断栈,还需要额外考虑最坏情况下的中断嵌套所消耗的栈空间大小。最后检查是否为任务或者中断分配了足够的栈空间,从而判断是否发生了栈溢出。

对于上述的静态分析法,其函数调用树的建立并非是完整的,例如递归函数或者某些采用函数指针方式动态运行的函数等,就很难用该方法完整的描述出其调用树,因此也难以准确的进行栈大小的计算,从而无法全面的判断栈是否发生了溢出。

2.动态分析法

方法一,向整个栈空间填充特定的数据,例如VxWorks操作系统在任务创建时可以设置选项对其栈空间进行特定数据的填充,在系统运行时,检查栈空间,确定有多少特定数据由于栈的使用而被改写了。一旦发现全部被改写,则提示栈溢出。

方法二,利用MMU(Memory Management Unit,内存管理单元)将栈空间的顶部作为检测空间,将其读写属性设置为只读,发生栈溢出时,处理器将会尝试写这个只读的区域。这个操作将引发一个异常事件,可被程序捕获到,因此可以判断栈溢出。或者采用其它类似的方法,在栈顶写入随机的数据,然后检测该随机的数据是否被改写等。

方法三,公开号为CN 101241464 B的中国专利文献公开了一种检测堆栈帧破坏的方法,主要用于检测栈帧是否被破坏,同时也可以检测栈是否溢出。其基本原理是:在栈顶增加反向跟踪栈指针(RTSP),在函数入桩和出桩时更新RTSP,以SP与RTSP之间的安全距离是否满足要求来判断栈是否已经溢出或者即将溢出。

对于上述的动态分析法,方法一的检测方法依赖于事后对栈空间的内容扫描,属于事后检测,实际上在发生栈溢出时是无法在第一时间内检测出来的,此时栈已经溢出并且可能改写了栈空间之外的内存等。方法二可以在第一时间内检测,但如果栈溢出访问的地址穿越了检测空间从而突破了栈顶部的隔离保护,也将无法检测到栈溢出。对于方法三,以VxWorks操作系统为例,需要在TCB(Task Control Block,任务控制块)中增加RTSP字段,或者需要使用TCB的预留字段,或者需要借助任务变量来实现,并且需要针对内核任务和用户任务采用不同的方法,实现起来比较复杂。同时增加的RTSP额外占用了栈顶空间,当函数调用层次很深时,对栈空间也是一笔不小的开销。此外,该方法是需要获知源码才能实施。



技术实现要素:

针对现有技术中存在的缺陷,本发明的目的在于提供一种栈溢出检测系统及方法,不但能够在第一时间内准确地检测到栈溢出,避免遗漏,而且在栈溢出时提示栈空间之外可能被非法改动的内存区域,而且节省栈空间开销。

本发明提供一种栈溢出检测系统,其设于可执行文件中,可执行文件包括任务创建函数和至少一个任务执行函数,所述系统包括:

初始化模块,其设于所述任务创建函数的出口中,用于在进行任务创建时获取栈的基本信息;

栈溢出检测模块,其设于每个所述任务执行函数入口,用于根据该任务执行函数当前执行指令对应的SP值以及该任务执行函数的栈的基本信息,判断是否发生栈溢出;

异常处理模块,其用于记录栈溢出检测模块的判断结果,当发生栈溢出时,回溯被非法覆盖的内存空间地址。

在上述技术方案的基础上,所述可执行文件还包括中断初始化函数,所述系统还包括第二初始化模块,所述第二初始化模块设于所述中断初始化函数的出口中,用于在进行中断初始化时获取栈的基本信息。

在上述技术方案的基础上,所述可执行文件基于VxWorks操作系统,所述基本信息包括栈的类别、栈基址和栈大小,所述栈的类别包括中断栈和任务栈。

在上述技术方案的基础上,所述异常处理模块还用于在栈空间临近消耗完的情况下给出预警信息。

本发明提供一种基于上述栈溢出检测系统的检测方法,所述检测方法包括以下步骤:

第一初始化模块在进行任务创建时获取栈的基本信息;

在每个所述任务执行函数入口处开始运行栈溢出检测模块,栈溢出检测模块根据该任务执行函数当前执行指令对应的SP值以及该任务执行函数的栈的基本信息,判断是否发生栈溢出;

异常处理模块记录栈溢出检测模块的判断结果,当发生栈溢出时,回溯被非法覆盖的内存空间地址。

在上述技术方案的基础上,所述可执行文件还包括中断初始化函数,所述系统还包括第二初始化模块,所述第二初始化模块设于所述中断初始化函数的出口中,所述方法还包括所述第二初始化模块在进行中断初始化时获取栈的基本信息;

在每个所述任务执行函数入口处开始运行栈溢出检测模块,栈溢出检测模块根据该任务执行函数当前执行指令对应的SP值以及该任务执行函数的栈的基本信息,判断是否发生栈溢出。

在上述技术方案的基础上,可执行文件基于VxWorks操作系统,栈的基本信息包括栈的类别、栈基址和栈大小,所述栈的类别包括中断栈和任务栈。

在上述技术方案的基础上,所述基本信息还包括预留字节;

所述栈溢出检测模块判断是否发生栈溢出的方法包括:

根据当前执行指令判断该任务执行函数对应的栈的类别,并获取该栈的栈基址和栈大小;

计算当前执行指令对应的SP值减去所述栈基址以及所述预留字节所得到的差值,当所述差值大于所述栈大小时,判定发生栈溢出并调用所述异常处理模块。

在上述技术方案的基础上,所述基本信息还包括所述栈大小的使用率阈值;

所述栈溢出检测模块判断是否发生栈溢出的方法还包括:当所述差值与所述栈大小的比值大于所述使用率阈值时,判定即将发生栈溢出,并调用所述异常处理模块。

在上述技术方案的基础上,所述可执行文件、初始化模块、栈溢出检测模块和异常处理模块均为源代码模块,且均通过GNU编译器套件进行编译,所述初始化模块为所述第一初始化模块,或者所述初始化模块为所述第一初始化模块和第二初始化模块;

被调用的所述异常处理模块从所述栈溢出检测模块所在的任务执行函数入口处获取调用函数地址和返回地址;

当所述栈溢出检测模块判定发生栈溢出,记录所述调用函数地址和返回地址,将所述返回地址作为指令地址,向前回溯指令,若所回溯的指令为栈上的保存指令,则记录该指令非法覆盖的内存地址,继续向前回溯指令,直至到达所述调用函数地址;

当所述栈溢出检测模块判定未发生栈溢出时,记录任务ID或者中断信息,以及所述调用函数地址和返回地址。

与现有技术相比,本发明的优点如下:

(1)在每个任务执行函数入口处开始运行栈溢出检测模块,栈溢出检测模块判定发生栈溢出后,调用异常处理模块回溯被非法覆盖的内存空间地址,能够在第一时间内准确地检测到栈溢出,避免遗漏,而且在栈溢出时提示栈空间之外可能被非法改动的内存区域。

(2)无需额外占用栈顶空间,也无需记录RTSP等信息,节省栈空间开销。

(3)当差值与栈大小的比值大于使用率阈值时,判定即将发生栈溢出,实现在栈空间临近消耗完的情况下给出预警。

附图说明

图1是本发明第一实施例栈溢出检测系统示意图;

图2是VxWorks操作系统中任务堆栈与TCB的示意图;

图3是本发明第一实施例栈溢出检测系统的安装方法中,由原始可执行文件P1得到插桩后的可执行文件P2;

图4是本发明第一实施例栈溢出检测系统的栈溢出检测方法流程图;

图5是本发明第二实施例栈溢出检测系统示意图;

图6是本发明第二实施例栈溢出检测系统的栈溢出检测方法流程图;

图7是步骤S20的流程图;

图8是步骤S30的流程图。

具体实施方式

本发明第一实施例提供一种栈溢出检测系统,其设于可执行文件中,可执行文件包括任务创建函数和至少一个任务执行函数,参见图1所示,本栈溢出检测系统包括设于任务创建函数中的第一初始化模块1a、设于可执行文件的每个任务执行函数入口的栈溢出检测模块2,栈溢出检测模块2调用异常处理模块3。第一初始化模块1a

第一初始化模块1a用于在可执行文件进行任务创建时获取栈的基本信息,基本信息包括栈的类别、栈基址和栈大小,栈的类别包括中断栈和任务栈。

第一初始化模块1a初始化任务中断信息表(Task-Interrupt Information Block)TIIB,TIIB中的每个表项用于记录栈的基本信息,包括下面的内容:类别、ID、栈基址、栈大小、预留字节大小(如图2所示,VxWorks操作系统至少预留了16字节)和使用率阈值(百分比)。其中,类别表示是中断还是任务,ID表示任务ID,中断则可用特殊的ID来表示。对于任务来说,其栈基址和栈大小需要待任务创建后才能获知并记录到TIIB中。这个可以通过统一的创建任务的接口或者钩子函数来实现。TIIB表项中仅有一条记录表示中断,多条记录表示任务。

TIIB的表项如表1下:

表1:

栈溢出检测模块2用于根据该任务执行函数当前执行指令对应的SP值以及从第一初始化模块1a获取的该任务执行函数的栈的基本信息,判断是否发生栈溢出。

异常处理模块3用于记录栈溢出检测模块的判断结果,当发生栈溢出时,回溯被非法覆盖的内存空间地址。

本发明第一实施例以32位的MIPS系统和VxWorks操作系统为例,对本栈溢出检测系统进行说明,可执行文件基于VxWorks操作系统。

MIPS通常只在任务执行函数进入和退出的时刻才调整SP值。其中形如addiu$sp,$sp,x的指令为SP值调整指令,x的值进行符号扩展后就是函数栈帧的大小,x为负值时,该指令表示SP入栈调整指令,x为正值时,该指令表示SP出栈调整指令。

在MIPS中,形如sw$ra,x($sp)的指令为RA在栈上的保存指令,用于将RA寄存器的内容保存到栈上;其它还有参数的保存指令等,将这一类的指令都称为栈上的保存指令。

VxWorks操作系统中,任务栈是独立的,创建任务时可以指定每个任务的栈大小,当任务创建后,栈基址也就确定了。由于任务ID、栈基址、TCB指针其实指向的是同一块内存,因此可以通过任务ID来访问栈基址。中断栈是共享的,在系统内核初始化时其栈基址就已经确定,并且可以通过全局变量访问到。此外,VxWorks操作系统中获取当前指令执行是否在中断上下文中,当前运行的任务ID等接口函数都可以通过全局变量直接访问到,这样可以避免在栈溢出检测模块2中再次调用函数。

图2是本发明所基于的VxWorks操作系统的任务栈与TCB的示意图,是以栈从高地址向低地址增长为例来说明的。VxWorks创建一个任务时,会在内存池中为任务分配一块连续的栈和TCB的存储空间,其中栈基址与TCB的指针是同一个地址,同时也作为任务ID。也即任务ID,栈基址,TCB指针其实指向同一块内存。此外,VxWorks操作系统还会在栈中预留一些空间,即预留字节,因此实际可用的栈空间比申请的栈空间要稍小一些。

第一初始化模块1a、栈溢出检测模块2和异常处理模块3均为源代码模块,或者第一初始化模块1a、栈溢出检测模块2和异常处理模块3均为目标代码模块,根据原始可执行文件是源代码或者目标代码来确定。具体的,在本栈溢出检测系统中,若原始可执行文件是源代码级的,则第一初始化模块1a、栈溢出检测模块2和异常处理模块3可以是源代码级的,也可以是目标代码级的,根据需要选用;若原始可执行文件是目标代码级的,则第一初始化模块1a、栈溢出检测模块2和异常处理模块3都是目标代码级的。

图3所示为本发明第一实施例栈溢出检测系统的安装方法,将栈溢出检测系统插入原始可执行文件P1中,得到插桩后的可执行文件P2,本安装方法包括以下步骤:

加载原始可执行文件P1,原始可执行文件P1包括原始任务创建函数。

通过插桩将初始化模块插入原始任务创建函数的出口,栈溢出检测模块插入每个原始任务执行函数入口,异常处理模块配置为被栈溢出检测模块调用。

本发明涉及的插桩工具包括源代码级的插桩工具和目标代码级的插桩工具。其中源代码级的插桩工具可以是gcc(GNU Compiler Collection,GNU编译器套件)编译器。在编译时增加-finstrument-functions选项后,gcc会自动在函数入口的地方添加__cyg_profile_func_enter(void*this_func,void*call_site)函数的调用,该函数的调用包括两个参数:调用函数和返回地址,通过这两个参数,在栈发生溢出时,可以回溯哪些内存可能被非法改写。

在本发明的其它实施例中,可执行文件、第一初始化模块1a、栈溢出检测模块2和异常处理模块3均为源代码,且均通过GNU编译器套件进行编译。

图4所示为本发明第一实施例栈溢出检测系统的栈溢出检测方法,可执行文件基于VxWorks操作系统,栈的类别包括中断栈和任务栈。本检测方法包括以下步骤:

S1.第一初始化模块1a在进行任务创建时获取栈的基本信息,栈的基本信息包括栈的类别、栈基址和栈大小。

由于VxWorks操作系统还会在栈中预留一些空间,即预留字节,因此实际可用的栈空间比申请的栈空间要稍小一些。上述栈的基本信息还包括该预留字节和栈大小的使用率阈值。

S2.在每个任务执行函数入口处开始运行栈溢出检测模块2,栈溢出检测模块2根据该任务执行函数当前执行指令对应的SP值以及从第一初始化模块1a获取的该任务执行函数的栈的基本信息,判断是否发生栈溢出。

S3.异常处理模块3记录栈溢出检测模块的判断结果,当发生栈溢出时,回溯被非法覆盖的内存空间地址。

在本发明第一实施例的基础上,本发明第二实施例提供一种栈溢出检测系统,可执行文件还包括中断初始化函数,本栈溢出检测系统还包括第二初始化模块,第二初始化模块设于中断初始化函数的出口中,用于在进行中断初始化时获取栈的基本信息。具体的,参见图5所示,本栈溢出检测系统包括设于任务创建函数中的第一初始化模块1a、设于中断初始化函数中的第二初始化模块1b,设于可执行文件的每个任务执行函数入口的栈溢出检测模块2,栈溢出检测模块2调用异常处理模块3。

对于中断而言,本发明适用于具有独立中断栈的嵌入式操作系统,比如在VxWorks下PowerPC MIPS有独立的中断栈。

原始可执行文件还包括原始中断初始化函数,在本发明第一实施例栈溢出检测系统的安装方法的基础上,本发明第二实施例栈溢出检测系统的安装方法还包括:

加载原始可执行文件后,通过插桩将第二初始化模块1b插入原始中断初始化函数的出口。

参见图6所示,本发明第二实施例栈溢出检测系统的检测方法包括:

S10.第一初始化模块1a在进行任务创建时获取栈的基本信息,第二初始化模块1b在进行中断初始化时获取栈的基本信息,栈的基本信息包括栈的类别、栈基址和栈大小。

由于VxWorks操作系统还会在栈中预留一些空间,即预留字节,因此实际可用的栈空间比申请的栈空间要稍小一些。上述栈的基本信息还包括该预留字节和栈大小的使用率阈值。

S20.在每个任务执行函数入口处开始运行栈溢出检测模块2,栈溢出检测模块2根据该任务执行函数当前执行指令对应的SP值以及从对应的初始化模块获取的该任务执行函数的栈的基本信息,判断是否发生栈溢出。

S30.异常处理模块3记录栈溢出检测模块的判断结果,当发生栈溢出时,回溯被非法覆盖的内存空间地址。

在每个任务执行函数入口处开始运行栈溢出检测模块2,栈溢出检测模块2判定发生栈溢出后,调用异常处理模块回溯被非法覆盖的内存空间地址,能够在第一时间内准确地检测到栈溢出,避免遗漏,而且在栈溢出时提示栈空间之外可能被非法改动的内存区域。无需额外占用栈顶空间,也无需记录RTSP等信息,节省栈空间开销。

步骤S20中,栈溢出检测模块2判断是否发生栈溢出的方法具体包括:

根据当前执行指令判断该任务执行函数对应的栈的类别,并从该类别对应的初始化模块中获取该栈的栈基址和栈大小。栈的类别包括中断栈和任务栈,栈的类别为任务栈时,从第一初始化模块1a中获取该栈的栈基址和栈大小;栈的类别为中断栈时,从第二初始化模块1b中获取该栈的栈基址和栈大小。

计算当前执行指令对应的SP值减去栈基址以及预留字节所得到的差值,差值=栈基址-SP值-预留字节,当差值大于栈大小时,判定发生栈溢出并调用异常处理模块3。

当差值与栈大小的比值大于使用率阈值时,判定即将发生栈溢出,并调用异常处理模块3,实现在栈空间临近消耗完的情况下给出预警。

栈溢出检测模块2判断当前执行的指令是否在中断上下文中,如果在中断上下文中,则以中断栈基址和中断栈大小作为参数来计算;否则获取当前运行的任务ID,以任务栈基址和任务栈大小作为参数来计算。判断当前SP指针与栈基址的距离是否超过了栈大小,如果超过,则认为发生了栈溢出,转到异常处理模块3。此外还可以根据需要设定任务或者中断的栈大小百分比阈值,超过该阈值则给出预警。具体步骤参见图7所示:

步骤201、判断当前指令的执行是否在中断上下文中,如果是,则转到步骤204;

步骤202、获取当前执行任务ID;

步骤203、根据任务ID以及类别为任务筛选出TIIB对应的条目,获取其栈基址和栈大小,转到步骤205;

步骤204、根据类别为中断,筛选出TIIB对应的条目,获取其栈基址和栈大小;

步骤205、计算当前SP值与栈基址之间除去预留字节后的差值:即差值=栈基址-SP值-预留字节;

步骤206、判断差值是否大于TIIB对应条目的栈大小,如果是,表示栈溢出,转至步骤208,否则结束;

步骤207、判断差值是否大于TIIB对应条目的栈大小*阈值百分比,如果是,表示栈即将溢出,转至步骤208,否则结束;

步骤208、调用异常处理模块3,结束。

在本实施例中,可执行文件、第一初始化模块1a、第二初始化模块1b、栈溢出检测模块2和异常处理模块3均为源代码,且均通过GNU编译器套件进行编译。

步骤S30包括:

被调用的异常处理模块3从栈溢出检测模块2所在的任务执行函数入口处获取调用函数地址和返回地址。

当栈溢出检测模块2判定发生栈溢出,记录调用函数地址和返回地址,将返回地址作为指令地址,向前回溯指令,若所回溯的指令为栈上的保存指令,则记录该指令非法覆盖的内存地址,继续向前回溯指令,直至到达调用函数地址。

当栈溢出检测模块2判定未发生栈溢出时,记录任务ID或者中断信息,以及调用函数地址和返回地址。

异常处理模块3记录栈溢出的任务或者中断信息,如果是即将溢出,则仅记录任务或者中断的信息即可;如果发生了溢出,说明SP寄存器的值已经越过了任务或者中断栈空间。由于栈溢出检测模块插入的指令在任务执行函数入口处,具体位置是在函数入栈调整指令以及其它栈上的保存指令之后,同时由于入栈调整指令与其它栈上的保存指令地址一般是相邻的,因此可以回溯可能被非法覆盖的内存空间地址。回溯方法为:记录入口栈溢出检测模块传入的调用函数地址以及返回地址的参数,并且利用该返回地址参数作为程序指令地址,向前回溯指令,分析所回溯的指令是否为栈上的保存指令,如果是,则记录该指令可能非法覆盖的内存地址,直至到达调用函数地址。具体步骤参见图8所示:

步骤301、获取栈溢出检测模块2的两个参数:调用函数地址this_func以及返回地址call_site;

步骤302、判断栈溢出是已经溢出还是即将溢出,如果是即将溢出,转到步骤308;

步骤303、读取this_func处的指令,如果不是入栈调整指令,则转到步骤308;

步骤304、记录入栈SP的调整值,获取在调用函数的第一条指令运行之前的SP值SPi;

步骤305、读取call_site处的前一条指令,判断是否是栈上的保存指令,如果否,则转至步骤307;

步骤306、分析栈上的保存指令在SPi上的偏移地址,获知被改写的内存地址并记录;

步骤307、继续前向回溯,如果回溯的指令地址到达this_func,则结束;否则重复步骤305~307。

步骤308、记录任务ID或者中断信息,记录this_func和call_site,结束。

在其他的实施例中,可执行文件、第一初始化模块1a、第二初始化模块1b、栈溢出检测模块2和异常处理模块3均为目标代码模块。为了提高效率,可以采用目标代码级的栈溢出检测模块2,其模块内部不再调用其它函数。

本发明不局限于上述实施方式,对于本技术领域的普通技术人员来说,在不脱离本发明原理的前提下,还可以做出若干改进和润饰,这些改进和润饰也视为本发明的保护范围之内。本说明书中未作详细描述的内容属于本领域专业技术人员公知的现有技术。

当前第1页1 2 3 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1