函数调用树生成方法、系统、计算机设备及可读存储介质
【技术领域】
本发明涉及软件领域,具体涉及一种函数调用树生成方法、系统、计算机设备及可读存储介质。
【背景技术】
Java语言被越来越广泛地应用于构建各种复杂的软件系统,每个系统可能有很多类,每个类有很多函数,生成的软件系统中函数的调用树可以在很多场景下被用到。比如,分析系统中某个私有函数的变更可能会影响到哪些对外提供的接口的行为。现有的技术方案是通过静态扫描分析代码编译后生成的字节码文件,解析其中的函数调用指令,得出函数间的调用关系,最终组装出调用树的。
但是现有技术所采用的静态分析的方式有诸多不足:一方面,由于Java的多态特性,某个接口可能有不同的实现类,而实际执行时具体使用哪一个实现类可能是在运行时动态决定的;另一方面,由于Java的动态加载能力和复杂的扩展能力,许多类之间的依赖是通过配置文件解析得到的,通过静态分析无法分析出其中的依赖关系,或者因为涉及到复杂的第三方框架使得分析过于复杂而近乎不可能实现;再者,通过静态分析得到的树可能因为包含各种框架和底层代码的函数而过于庞大,传输和处理都非常不便,往往需要二次处理。
【发明内容】
为解决前述问题,本发明提供了一种函数调用树生成方法,既方便又准确的生成Java函数调用树。
为了达到上述目的,本发明采用如下技术方案:
一种函数调用树生成方法,包括如下步骤:
使用字节码转换器在函数的前后分别织入增强代码,以进行字节码增强;
运行覆盖测试,在运行期间使用堆栈数据结构收集软件系统执行过程中的实际调用函数,以形成调用树。
可选的,在启动脚本中使用Java提供的Agent机制加载所述字节码转换器,所述字节码转换器中使用ASM在函数前后进行字节码增强。
可选的,类加载过程中调用所述字节码转换器在函数前织入增强代码作为前置增强,在函数被调用前触发,在函数后织入增强代码作为后置增强,在函数被调用后触发。
可选的,形成调用树具体包括:
前置增强将函数的标识构造成调用树的一个节点;
获取堆栈的栈顶元素节点,如果堆栈为空,则直接进入堆栈,如果堆栈不为空,则将函数的标识所构造成的节点设为栈顶元素节点的子节点,再放入堆栈;
函数执行完成后,后置增强弹出栈顶元素,如果弹出栈顶元素后发现堆栈已经为空,则对本次请求的处理已经完成,将当前线程上下文中已经构造出的调用树上报;如果栈顶元素不为空,继续执行堆栈中位于栈顶的函数。
可选的,如果函数包括子函数,则形成调用树时依照运行请求中执行函数的顺序进行。
本发明具有如下有益效果:
本申请所提供的技术方案,通过字节码在函数前后分别插桩,运行时生成调用树的方式,大大减轻了对复杂软件系统的字节码静态分析的复杂度,同时明显的提高了准确性。字节码技术可以方便的设置函数包名前缀进行过滤,目标更明确。
此外,本发明还提供了一种函数调用树生成系统,包括:
字节码转换器,用以在函数的前后分别织入增强代码,以进行字节码增强;
调用树形成模块,用以在运行期间使用堆栈数据结构收集软件系统执行过程中的实际调用函数,以形成调用树。
可选的,在启动脚本中使用Java提供的Agent机制加载所述字节码转换器,所述字节码转换器中使用ASM在函数前后进行字节码增强。
可选的,类加载过程中调用所述字节码转换器在函数前织入增强代码作为前置增强,在函数被调用前触发,在函数后织入增强代码作为后置增强,在函数被调用后触发。
可选的,调用树形成模块形成调用树具体包括:
前置增强将函数的标识构造成调用树的一个节点;
获取堆栈的栈顶元素节点,如果堆栈为空,则直接进入堆栈,如果堆栈不为空,则将函数的标识所构造成的节点设为栈顶元素节点的子节点,再放入堆栈;
函数执行完成后,后置增强弹出栈顶元素,如果弹出栈顶元素后发现堆栈已经为空,则对本次请求的处理已经完成,将当前线程上下文中已经构造出的调用树上报;如果栈顶元素不为空,继续执行堆栈中位于栈顶的函数。
可选的,如果函数包括子函数,调用树形成模块在形成调用树时依照运行请求中执行函数的顺序进行。
本发明所提供的函数调用树生成系统的有益效果与前述函数调用树生成方法的有益效果推理过程相似,在此不再赘述。
同时,本发明还提供了一种计算机设备,包括存储器和处理器,所述存储器存储有计算机程序,所述处理器执行所述计算机程序时实现上述任一项所述的方法。
同时,本发明还提供了一种计算机可读存储介质,其上存储有计算机程序,所述计算机程序被处理器执行时实现上述任一项所述的方法。
本发明的这些特点和优点将会在下面的具体实施方式以及附图中进行详细的揭露。本发明最佳的实施方式或手段将结合附图来详尽表现,但并非是对本发明技术方案的限制。另外,在每个下文和附图中出现的这些特征、要素和组件是具有多个,并且为了表示方便而标记了不同的符号或数字,但均表示相同或相似构造或功能的部件。
【附图说明】
下面结合附图对本发明作进一步说明:
图1为本发明实施例一的流程图;
图2为本发明实施例二中函数的调用关系示图;
图3为本发明实施例二中生成函数调用树的流程图;
图4为本发明实施例二中形成调用树过程中每一步堆栈的状态变化图。
【具体实施方式】
下面结合本发明实施例的附图对本发明实施例的技术方案进行解释和说明,但下述实施例仅为本发明的优选实施例,并非全部。基于实施方式中的实施例,本领域技术人员在没有做出创造性劳动的前提下所获得其他实施例,都属于本发明的保护范围。
在本说明书中引用的“一个实施例”或“实例”或“例子”意指结合实施例本身描述的特定特征、结构或特性可被包括在本专利公开的至少一个实施例中。短语“在一个实施例中”在说明书中的各位置的出现不必都是指同一个实施例。
实施例一:
如图1所示,本实施例提供了一种函数调用树生成方法,使用字节码增强技术在函数前后分别插桩,结合堆栈这种数据结构,在单元测试或覆盖测试时生成调用树,包括如下步骤:
在启动脚本中使用Java提供的Agent机制加载字节码转换器,在类加载过程中调用字节码转换器,字节码转换器中使用ASM在函数的前后分别织入增强代码,以进行字节码增强,在函数前织入增强代码作为前置增强,函数在被调用前先被触发,在函数后织入增强代码作为后置增强,函数被调用后被触发。Java Agent是Java提供的机制,该机制可以允许在软件系统启动前期或运行中加载自定义的字节码转换器。而ASM是Java中比较通用的字节码增强技术,具有功能强大、速度快的特点,本实施例中的字节码转换器就是用ASM实现。
运行覆盖测试,在类加载过程中,Java会自动调用字节码转换器在函数前后分别织入的增强代码,即前置增强以及后置增强。当某个函数被调用前会先触发前置增强逻辑,被调用后会触发后置增强逻辑;
在运行期间使用堆栈数据结构收集软件系统执行过程中的实际调用函数,以形成调用树,具体包括:
前置增强将函数的标识构造成调用树的一个节点;
获取堆栈的栈顶元素节点,如果堆栈为空,则直接进入堆栈,如果堆栈不为空,则将函数的标识所构造成的节点设为栈顶元素节点的子节点,再放入堆栈;
函数执行完成后,后置增强弹出栈顶元素,如果弹出栈顶元素后发现堆栈已经为空,则对本次请求的处理已经完成,将当前线程上下文中已经构造出的调用树上报;如果栈顶元素不为空,继续执行堆栈中位于栈顶的函数。
本实施例所提供的方法,通过字节码在函数前后分别插桩,运行时生成调用树的方式,大大减轻了对复杂软件系统的字节码静态分析的复杂度,同时明显的提高了准确性。字节码技术可以方便的设置函数包名前缀进行过滤,目标更明确。
实施例二
如图2所示,本实施例与实施例一不同的是,本实施例中,所调用的函数A包括子函数B和子函数C,子函数B和子函数C相互独立,单独运行,子函数B包括子函数D,运行请求中执行函数的顺序为:执行函数A,调用并执行函数B,再调用并执行函数D,函数D执行结束,函数B执行结束,再调用并执行函数C,函数C执行结束,函数A执行结束。因此,在本实施例中,需要对函数A、函数B、函数C以及函数D的前后分别织入增强代码,各自形成其前置增强和后置增强。函数前后织入增强代码,形成前置增强与后置增强的过程与实施例一相同,在此不再赘述。
形成调用树时,具体步骤依照运行请求中执行函数的顺序进行,如图3及图4所示:
函数A的前置增强将函数A的标识构造成调用树的一个节点;
获取堆栈的栈顶元素节点,此时,堆栈为空,函数A直接进入堆栈,开始执行函数A;
执行函数A时调用函数B,函数B的前置增强将函数B的标识构造成调用树的一个节点,此时堆栈不为空,说明堆栈中的函数A与函数B具有父子调用关系,因此,将函数B的标识所构造成的节点设为栈顶元素节点的子节点,函数B进入堆栈;
执行函数B,调用函数D,函数D的前置增强将函数D的标识构造成调用树的另一个节点,此时堆栈不为空,说明堆栈中的函数B与函数D具有父子调用关系,因此,将函数D的标识所构造成的节点设为栈顶元素节点的子节点,函数D进入堆栈;
函数D执行完毕,函数D的后置增强弹出栈顶元素,此时栈顶元素不为空,说明堆栈内还有其他函数,调用树未构造完,因此,继续执行堆栈中位于栈顶的函数,即函数B;
函数B执行完毕,函数B的后置增强弹出栈顶元素,此时栈顶元素仍然不为空,说明堆栈内仍然有其他函数,调用树仍然未构造完,因此,继续执行堆栈中位于栈顶的函数,即函数A;
继续执行函数A,调用函数C,函数C的前置增强将函数C的标识构造成调用树的一个节点,此时堆栈不为空,说明堆栈中的函数A与函数C具有父子调用关系,因此,将函数C的标识所构造成的节点设为栈顶元素节点的子节点,函数C进入堆栈;
函数C执行完毕,函数C的后置增强弹出栈顶元素,弹出栈顶元素后,此时堆栈为空,说明对本次请求的处理已经完成,将当前线程上下文中已经构造出的调用树上报。
实施例三
本实施例提供一种函数调用树生成系统,包括:
字节码转换器,在启动脚本中使用Java提供的Agent机制加载字节码转换器,在类加载过程中被调用,使用ASM在函数的前后分别织入增强代码,以进行字节码增强,在函数前织入增强代码作为前置增强,在函数被调用前触发,在函数后织入增强代码作为后置增强,在函数被调用后触发;
调用树形成模块,用以在运行期间使用堆栈数据结构收集软件系统执行过程中的实际调用函数,以形成调用树。调用树形成模块形成调用树具体包括:
前置增强将函数的标识构造成调用树的一个节点;获取堆栈的栈顶元素节点,如果堆栈为空,则直接进入堆栈,如果堆栈不为空,则将函数的标识所构造成的节点设为栈顶元素节点的子节点,再放入堆栈;
函数执行完成后,后置增强弹出栈顶元素,如果弹出栈顶元素后发现堆栈已经为空,则对本次请求的处理已经完成,将当前线程上下文中已经构造出的调用树上报;如果栈顶元素不为空,继续执行堆栈中位于栈顶的函数。
如果函数包括子函数,调用树形成模块在形成调用树时依照运行请求中执行函数的顺序进行。
实施例四、
本实施例提供了一种计算机设备,包括存储器和处理器,存储器中存储有计算机程序,该处理器执行计算机程序时实现如上所述的任意实施例中的方法。本领域普通技术人员可以理解,实现上述实施例方法中的全部或部分流程,是可以通过计算机程序来指令相关的硬件来完成。据此,所述的计算机程序可存储于一非易失性计算机可读取存储介质中,该计算机程序在执行时,可实现上述任意一项实施例所述的方法。其中,本申请所提供的各实施例中所使用的对存储器、存储、数据库或其它介质的任何引用,均可包括非易失性和/或易失性存储器。非易失性存储器可包括只读存储器(ROM)、可编程ROM(PROM)、电可编程ROM(EPROM)、电可擦除可编程ROM(EEPROM)或闪存。易失性存储器可包括随机存取存储器(RAM)或者外部高速缓冲存储器。作为说明而非局限,RAM以多种形式可得,诸如静态RAM(SRAM)、动态RAM(DRAM)、同步DRAM(SDRAM)、双数据率SDRAM(DDRSDRAM)、增强型SDRAM(ESDRAM)、同步链路(Synchlink)DRAM(SLDRAM)、存储器总线(Rambus)直接RAM(RDRAM)、直接存储器总线动态RAM(DRDRAM)以及存储器总线动态RAM(RDRAM)等。
以上,仅为本发明的具体实施方式,但本发明的保护范围并不局限于此,熟悉该本领域的技术人员应该明白本发明包括但不限于附图和上面具体实施方式中描述的内容。任何不偏离本发明的功能和结构原理的修改都将包括在权利要求书的范围中。