CN110362301B - 一种终端应用行为反射的处理方法 - Google Patents
一种终端应用行为反射的处理方法 Download PDFInfo
- Publication number
- CN110362301B CN110362301B CN201910498200.7A CN201910498200A CN110362301B CN 110362301 B CN110362301 B CN 110362301B CN 201910498200 A CN201910498200 A CN 201910498200A CN 110362301 B CN110362301 B CN 110362301B
- Authority
- CN
- China
- Prior art keywords
- activity
- application
- model
- runtime
- terminal application
- Prior art date
- Legal status (The legal status is an assumption and is not a legal conclusion. Google has not performed a legal analysis and makes no representation as to the accuracy of the status listed.)
- Active
Links
Images
Classifications
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/30—Creation or generation of source code
- G06F8/35—Creation or generation of source code model driven
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/40—Transformation of program code
- G06F8/41—Compilation
- G06F8/42—Syntactic analysis
- G06F8/427—Parsing
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/40—Transformation of program code
- G06F8/41—Compilation
- G06F8/43—Checking; Contextual analysis
- G06F8/433—Dependency analysis; Data or control flow analysis
Landscapes
- Engineering & Computer Science (AREA)
- General Engineering & Computer Science (AREA)
- Theoretical Computer Science (AREA)
- Software Systems (AREA)
- Physics & Mathematics (AREA)
- General Physics & Mathematics (AREA)
- Stored Programmes (AREA)
Abstract
本发明公开了一种终端应用行为反射的处理方法,通过行为解释器,生成一个完整、准确、详实的应用行为自述,即终端应用应用行为的运行时模型,克服了现有技术在动态、多变、难控的应用运行时的不足,实现了对终端应用应用行为的灵活、完整的监测,然后基于生成的运行时模型,定义运行时模型上的操作以及模型片段在堆、栈区域影响的等价性,实现了复杂的应用行为模型的分解,可操作的模型片段,并基于分解的模型片段,建立行为模型与应用状态和应用代码的因果关联,实现了在终端应用运行时对其应用行为的指令级别的控制。
Description
技术领域
本发明涉及计算机技术,尤其涉一种终端应用行为反射的处理方法。
背景技术
网构软件(也称终端应用)是在互联网开放、动态和多变环境下软件系统基本形态的一种抽象,它既是传统软件结构的自然延伸,又具有区别于集中封装环境下发展起来的传统软件形态的独有基本特征:1)自主性,指网构软件系统中的软件实体具有相对独立性、主动性和自适应性。自主性使其区别于传统软件系统中软件实体的依赖性和被动性;2)协同性,指网构软件系统中软件实体与软件实体之间可按多种静态连接和动态合作方式在开放的网络环境下加以互连、互通、协作和联盟。协同性使其区别于传统软件系统在封闭集中环境下单一静态的连接模式;3)反应性,指网构软件具有感知外部运行和使用环境并对系统演化提供有用信息的能力。反应性使网构软件系统具备了适应开放、动态和多变环境的感知能力;4)演化性,指网构软件结构可根据应用需求和网络环境变化而发生动态演化,主要表现在其实体元素数目的可变性,结构关系的可调节性和结构形态的动态可配置性。演化性使网构软件系统具备了适应比开放、动态和多变环境的应变能力;5)多态性,指网构软件系统的效果体现出相容的多目标性。它可根据某些基本协同原则,在动态变化的网络环境下,满足多种相容的目标形态。多态性使网构软件系统在网络环境下具备了一定的柔性和满足个性化需求的能力。
上述网构软件特征的实现,往往需要在运行态修改软件以保障或改善质量、优化或新增功能。经典软件工程方法与技术强调在开发态修改软件,不支持运行态直接修改软件。
与之对应,编程语言、操作系统、中间件等系统软件,提供了一种常见的运行态监测与控制应用的主要机制——计算反射(computational reflection,简称反射)。基于计算反射可以实现各种开发框架、测试框架,以提高开发人员在代码开发、测试甚至运行部署中的效率。在计算机领域,B.Smith给出了通用的反射性的定义:反射性是实体具有按照描述、操作和处理实体所面临的主要问题域的相同方式来描述、操作和处理实体自身的一种能力。该定义后续被解释为:反射性是程序具有在运行时刻操纵一组数据的能力,这组数据描述了该程序的运行状态,操纵有两方面含意:1)监测(Introspection),程序可以观测并推理自身的状态;2)控制(Intercession),程序可以改变自身的运行或语义。而这两方面都需要能将程序执行的状态编码为数据,而提供这种编码便称为反射也就是说,反射其实是将程序的运行状态映射为一组可操作的数据。前一部分组成基层实体,后一部分组成元层实体,而基层实体与元层实体之间保持了因果关联。根据基层实体的不同,计算反射主要分为结构反射和行为反射。结构反射的基层实体为当前程序及其抽象数据类型(可视为应用的状态),而行为反射的基层实体则是当前程序的执行行为及其执行所需的数据(可视为应用的行为)。
结构反射是指编程语言提供对当前程序及其抽象数据类型反射的能力,由于与编程语言框架(runtime或framework)的能力类似而自然存在,是大多数编程语言框架固有的能力。
行为反射是指编程语言提供对自身的执行语义及其执行所需的数据反射的能力,也就是编程语言框架自身需要被反射,行为反射在监测和控制上面临两个挑战:其一,需要完整描述既有的应用行为,也就是对应用的执行进行监测。应用的执行可视为一组运行时活动的集合,活动的粒度越细,监测的信息也越丰富,监测功能占用的资源就越大,其与业务逻辑之间的资源竞争也就越严重。此时,应用行为监测的复杂性和规模性就成为终端应用行为反射的首要挑战。其二,现有编程语言以及操作系统和中间件等系统软件的行为反射,均不支持指令级的行为控制,根本原因在于指令序列蕴含的复杂的数据和控制依赖,因此,应用行为的指令级控制就成为终端应用行为反射的主要难点。
发明内容
本发明主要目的在于,提供一种终端应用行为反射的处理方法,以克服上述两个挑战,实现对终端应用行为的完整监测和指令级控制。
本发明是通过如下技术方案实现的:
为解决本发明的技术问题,本发明提出了一种终端应用行为反射的处理方法,所述方法包括:
构造所述终端应用的运行时模型,所述运行时模型包括运行时栈模型和运行时堆模型;
基于所述运行时模型,对所述终端应用的应用行为进行控制;
其中:
构造所述终端应用行为的运行时栈模型的子步骤包括:
在所述终端应用运行时,获取所述终端应用的内存中真正执行的代码,并对所述真正执行的代码进行抽象,生成控制流图;
针对所述控制流图,将需要监测的控制流图输入至预设的行为解释器;
利用所述行为解释器对所述需要监测的控制流图进行解释执行,生成所述终端应用运行时的栈活动;
在所述终端应用运行时,生成所述栈活动的控制流间的依赖关系,得到所述终端应用行为的运行时栈模型;
构造所述终端应用行为的运行时堆模型的子步骤包括:
在所述终端应用运行时,生成堆区的初始状态;
生成堆操作活动,得到所述终端应用行为的运行时堆模型;
基于所述运行时模型,对所述终端应用的应用行为进行控制的子步骤包括:
预定义对所述运行时模型的操作以及模型片段在堆、栈区域影响的等价性;
对所述运行时模型保持所述等价性进行转换,对所述运行时模型进行分解,得到一组可操作的模型片段;
根据分解后的模型片段,建立所述运行时模型与应用状态和应用代码的因果关联,以对所述终端应用行为进行控制。
进一步的,构造所述终端应用的运行时模型前,所述方法包括:
确定类筛选器和活动类型筛选器;其中,所述类筛选器基于包和类名正则匹配的粗粒度筛选,用于去除开发人员不关心的程序活动;所述活动类型筛选器基于活动类型的细粒度筛选,用于去除与开发者不关心的活动类型。
进一步的,所述栈活动的活动类型包括方法开始与方法结束,字段读,数组读和同步指令;
利用所述行为解释器对所述需要监测的控制流图进行解释执行,生成所述终端应用运行时的栈活动的子步骤进一步包括:
利用对所述终端应用的应用行为具有监测功能的行为解释器对所述需要监测的控制流图进行解释执行,获得所述终端应用运行时的活动;
根据所关注的类,利用所述类筛选器对所述终端应用运行时的活动进行粗粒度筛选,生成由所述类引起的栈活动;
针对所述栈活动的活动类型,利用所述活动类型筛选器对所述栈活动进行细粒度筛选。
进一步的,所述堆操作活动的活动类型包括对象实例化,数组实例化,对象字段写,数组元素写,清除活动和压缩活动;
所述生成堆操作活动的子步骤进一步包括:
根据所关注的类,利用所述类筛选器对所述终端应用运行时的活动进行粗粒度筛选,生成由所述类引起的堆操作活动;
针对所述堆操作活动的活动类型,利用所述活动类型筛选器对所述堆操作活动进行细粒度筛选。
进一步的,所述依赖关系包括同步依赖和通信依赖。
进一步的,对所述终端应用行为进行控制包括对所述终端应用行为进行行为式控制和/或结构式控制。
进一步的,对所述终端应用行为进行行为式控制的步骤包括:
设终端应用的内存初始状态S,活动序列A;
经过一段时间的执行,获取所述运行后的内存状态为S·A=S',对所述运行时模型保持所述等价性进行转换后的活动序列为A',得到目标状态为S.A'=S”;
利用S.A'=(S.A).A”=S'.A”求解增量活动序列A”,使得A+A”=A',完成对所述终端应用行为进行行为式控制的过程。
进一步的,所述增量活动序列A”包括:控制转移、算术运算、字段读取、数组读取、类实例化、数组实例化,字段赋值、数组赋值、线程同步、垃圾回收、方法调用和类加载。
进一步的,对所述终端应用行为进行结构式控制的步骤包括:
获取终端应用行为的运行时模型M,一组操作以及一个转换后的活动序列A';
将所述一组操作映射为一段目标代码,以使所述目标代码的执行所产生的活动序列A”与活动序列A'保持控制等价关系。
进一步的,将所述一组操作映射为一段目标代码的步骤包括:
获取数据依赖,使得所述活动执行的数据依赖与活动发生时刻相同;
将所述活动转换为相应的目标代码。
与现有技术相比,本发明通过行为解释器,生成一个完整、准确、详实的应用行为自述,即终端应用应用行为的运行时模型,克服了现有技术在动态、多变、难控的应用运行时环境对终端应用应用行为的监测上的不足,实现了对终端应用应用行为的灵活、完整的监测,然后基于生成的运行时模型,定义运行时模型上的操作以及模型片段在堆、栈区域影响的等价性,实现了复杂的应用行为模型的分解,可操作的模型片段,并基于分解的模型片段,建立行为模型与应用状态和应用代码的因果关联,实现了在终端应用运行时对其应用行为的指令级别的控制。
附图说明
图1是现有3G无线资源控制状态机;
图2(a)是一个网络请求合并的实例中合并前的网络请求控制流示意图;
图2(b)是一个网络请求合并的实例中合并后的网络请求控制流示意图;
图3是本发明一种终端应用行为反射的处理方法的步骤流程图。
图4是一个线程之间通信依赖的例子——生产者-消费者模式示意图;
图5是安卓多线程编程示例;
图6是一个多线程编程间依赖示例;
图7(a)是执行前堆区对象;
图7(b)是执行后堆区对象;
图8是自动重构实现网络请求调度的栈模型示例;
图9(a)是面向用户的图形界面接口发消息执行流程示意图;
图9(b)是面向互操作的发送消息接口的执行流程示意图;
图10是本发明示例Reflectall模型生成子系统架构示意图;
图11是本发明示例Reflectall的接口运行子系统的结构示意图;
图12(a)是开源应用集上的实验结果;
图12(b)是闭源应用集上的实验结果;
图13是Reflectall与Emma生成代码覆盖率报告时的应用启动时间结果对比图;
图14(a)是本发明示例计算器的原始类图;
图14(b)是本发明示例修改后的类图。
具体实施方式
为使本发明的目的、技术方案和优点更加清楚明白,下面结合实施例和附图,对本发明作进一步详细说明。
为了更好理解本申请的技术问题,本发明采用选取两个典型案例的应用功能演化场景进行分析,以此来明确现有的行为反射不适用的根本原因。
案例一:
随着智能手机的发展,终端的移动应用越来越依赖云端提供的软件、硬件资源,以提供更好的服务。然而,云端与终端之间的通讯需要消耗大量的电能。联网应用(如天气、邮件、新闻等)呈现了网构软件典型的构件化特点,利用网络实现了终端与云端各构件的通讯。特别在3G/4G环境下,联网应用在后台长时间、间隔式地利用网络获取相应的推送消息。这种长时间、间隔式的消息推送给电池容量有限的智能手机的续航带来了巨大压力。3G和4G是当前主流使用的移动蜂窝网络,其耗电特点更为复杂。一方面,因为蜂窝网络移动性较强,对于一个移动设备,随着物理位置的移动,可能快速切换到不同蜂窝网络基站。因此,对于蜂窝网络基站,不可能将一个信道一直分配给一台移动设备。另一方面,由于移动设备续航有限,而长时间连接至蜂窝网络基站,会大大提高其功耗,影响续航。因此,蜂窝网络标准中,进一步对无线资源控制(Radio Resource Control,RRC)模块的状态进行了规定。
以移动设备中的3G网络模块为例,一共包含三个状态,如图1所示。
(1)IDLE:即空闲状态,在此状态下,3G模块的耗电最低,同时也不能发送、接收任何数据。在这个状态下,如果要发送或接收数据,则会转移至CELL_DCH状态。
(2)CELL_DCH:在这个状态下,3G模块的带宽达到最大,此时能以最大的速率进行数据传输,同时它的功耗也是最大的。如果持续一段时间,仍然没有数据传输的话,它会转移至CELL_FACH状态。根据不同的运营商的设置,持续运行于CELL_DCH状态的时间通常是5秒至10秒。
(3)CELL_FACH:在这个状态下,3G模块的耗电比CELL_DCH要节省50%,同时,在这个状态下,其网络传输速率也较低。如果在这个状态,发送或接收的数据大于某个阀值,则会重新转移至CELL_DCH状态。而如果在CELL_FACH状态持续一段时间没有发送或接收数据,则会转移至IDLE状态。一般来说这段时间通常是10秒至15秒。
图2(a)到图2(b)展示了一个网络请求合并的实例。图2(a)为合并前的网络请求与无线通讯模块耗电,其横轴为时间,上半部分为无线通讯模块的功耗;下半部分的虚线为发起这两个网络请求的线程;下半部分的实线为其控制流。首先,一个后台的新闻推送线程唤醒了负责发送网络请求的线程①;该线程被唤醒后,发起了网络请求②,此时,无线通讯模块的功耗也由IDLE状态下的低功耗,变为CELL_DCH状态下的高功耗;当整个请求完成后,负责发送网络请求的线程将结果返回给新闻推送线程③,此时虽然无线通讯模块没有接收或发送数据,但它仍会保持在高功耗状态,由此开始的无线通讯模块耗电被称之为“尾时间耗电”,对应为图2(a)中用的斜线部分;新闻推送线程在收到返回结果后④,对结果进行处理,并在通知栏上进行提示⑤。又过了一段时间后,另一个版本更新线程也执行了类似的逻辑⑥,发送了网络请求。如图2(a)所示,由于这两个网络请求间隔了几十秒,导致无线通讯模块被唤醒了两次,因此也有了对应的两次“尾时间”,由此导致了额外的网络能耗。
对于安卓应用,有很大一部分后台请求可以被延迟几十秒、甚至两、三分钟,而不会影响用户的体验。例如上述的新闻推送、版本更新推送等。对于这些网络请求,如果在时间维度上进行合并,即两个请求同时发送,而不是间隔了几十秒发送,就可以减少“尾时间”的网络耗电。图2(b)为图2(a)中两个请求进行了合并后的控制流及其无线通讯模块耗电情况。首先,负责发送网络请求的线程被新闻推送线程唤醒后,并不直接发送网络请求,而是进入一个等待状态⑦。一段时间后,另一个网络请求线程被后台更新推送线程唤醒,同时,它也进入一个等待状态⑧。在等待状态结束后⑨,这两个线程同时发送网络请求,对应的无线通讯模块也只被唤醒一次。如图2(b)所示,合并后的网络请求的耗电要远小于合并前的网络请求耗电。
为了实现网络请求合并,需要1)一种网络请求调度机制,即,使原本直接发送的网络请求可被延迟发送;2)一种网络请求调度算法,即找出可被延迟调度的请求,同时利用调度机制进行延迟发送。利用结构反射可以实现自动化重构移动应用的网络请求执行逻辑,并把调度机制内置进应用。然而这就要求不同应用的开发者均使用同一个自动重构框架,并且需要对所有应用进行重新的编译、部署和运行。这对于大量的、分属于不同应用开发者的闭源应用显然不现实。
案例二:
随着微信的普及,微信已经不仅仅是一个简单的通讯应用,它还成为了工作交流的必备工具;催生了利用微信朋友圈、公众号进行营销的“微商”;成为了最大的自媒体的发布平台。微信其核心作为一个通讯工具,其功能还是以满足普通用户为主。即便如此,它也很难满足普通用户的特定需求。例如,随着微信使用的时间越来越长,其缓存的聊天记录文件也越来越大,对于普通用户而言,很难对自己的聊天日志进行管理。更进一步地,微信难以满足微商、自媒体人等特殊群体的特定的需求。要实现微信应用内数据与功能的开放共享,就需要将面向用户的用户界面接口转换为面向互操作的可编程接口。一般而言,对于面向用户的用户界面接口,其执行的起点在于用户界面元素的点击、拖动和输入等操作。经过部分的逻辑处理,以网络请求、数据库查询、文件读写的方式,访问外部资源,获取相应的数据或实现相应的功能。在该处理流程中,有大部分的逻辑是与面向互操作的可编程接口的执行逻辑是相似的,只是其执行的起点不同。然而,现有的行为反射监测和控制的粒度为方法级别。基于现有的行为反射,在既有应用的执行流程中插入某些执行逻辑的方式,难以实现将面向用户的用户界面接口转换为面向互操作的可编程接口:既有的功能可对应于运行时的一组程序活动,以方法为粒度的行为反射其监测的内容有限,无法监测方法内的指令的执行,继而也无法控制。这就导致了现有的解决方案往往是基于既有的代码和文档,对于一个开发团队而言,开发人员的流动、文档的缺失、甚至是不规范的源码注释都会使得移动应用的迭代开发变得难以进行。
由上述两个案例分析可以看出,难以实现移动应用互操作接口根本原因在于现有的工作由于缺乏对应用行为的一个完整、详实的描述,同时也没有对这种指令粒度的自描述的控制的方法。
目前,实现应用行为的完整监测和灵活控制面临着以下几个挑战:
活动的规模性:由于应用的复杂性,需要提供不同粒度的控制流图所生成的运行时栈模型,以保证在分析时的效率和准确性。
活动的正确性:自动生成的应用行为运行时模型应该能正确地反应应用运行时的状态变化。
控制的多态性和实用性:一方面,由于监测的基层实体为应用运行时的行为,因此控制就体现为对运行时行为的修改,然而对模型中的每一个活动均为发生过的活动,如何正确地定义对运行时状态的修改;另一方面,控制还可以体现为实现部分结构反射,正如利用结构反射可以实现部分行为反射,如何基于行为反射实现部分结构反射。由于各种编译优化技术,提供的源代码往往不能直接与运行时执行的指令对应起来,实用性不高,因而本框架需要支持编译优化后的应用。
针对上述问题,参照图3,本发明提出了一种终端应用行为反射的处理方法的步骤流程图,所述方法包括:
步骤1:构造所述终端应用的运行时模型,所述运行时模型包括运行时栈模型和运行时堆模型;
步骤2:基于所述运行时模型,对所述终端应用的应用行为进行控制;
其中:
构造所述终端应用行为的运行时栈模型的子步骤包括:
子步骤S301:在所述终端应用运行时,获取所述终端应用的内存中真正执行的代码,并对所述真正执行的代码进行抽象,生成控制流图;
子步骤S302:针对所述控制流图,将需要监测的控制流图输入至预设的行为解释器;
子步骤S303:利用所述行为解释器对所述需要监测的控制流图进行解释执行,生成所述终端应用运行时的栈活动;
子步骤S304:在所述终端应用运行时,生成所述栈活动的控制流间的依赖关系,得到所述终端应用行为的运行时栈模型;
构造所述终端应用行为的运行时堆模型的子步骤包括:
子步骤S305:在所述终端应用运行时,生成堆区的初始状态;
子步骤S306:生成堆操作活动,得到所述终端应用行为的运行时堆模型;
基于所述运行时模型,对所述终端应用的应用行为进行控制的子步骤包括:
子步骤S307:预定义对所述运行时模型的操作以及模型片段在堆、栈区域影响的等价性;
子步骤S308:对所述运行时模型保持所述等价性进行转换,对所述运行时模型进行分解,得到一组可操作的模型片段;
子步骤S309:根据分解后的模型片段,建立所述运行时模型与应用状态和应用代码的因果关联,以对所述终端应用行为进行控制。
应用在操作系统中运行起来之后,可视为一个或多个进程,操作系统将移动应用所需的可执行文件加载到内存中,并开始执行。一般而言,一个进程所占的内存可分为三个区域:
代码段:存放执行代码的一块内存区域,具有只读属性;
堆区:可分为用于存放全局变量的一块内存区域(数据段),和用于进程运行中动态分配的内存区域,例如,面向对象编程语言Java中,线程创建一个新的对象相当于在堆区中申请了一片内存;
栈区:用于临时存局部变量等。例如,面向对象编程语言Java中,线程调用一个方法时,会新申请一个帧(frame),帧中保存了方法所需的参数等数据。
经过发明人仔细研究发现,终端应用在运行时,代码段的执行会引起堆区和栈区内存数据的变化。应用的运行时模型需要能反映应用在一段时间内的:1)代码的执行情况:在开发时,移动应用的代码可抽象为控制流图,那么对应于运行时,代码的执行情况可抽象为控制流图的一条或多条路径;2)内存数据(如堆区)的变化:在开发时,开发人员会设计好各种数据结构以表示应用的数据模型(Data Model),而在运行时,代码的执行引起对这些数据结构的实例的创建、修改、删除,也就是对应于一组内存的分配和修改操作。从内存区域角度上看,程序执行所影响的最主要的区域是内存的栈区和堆区。1)中的控制流图中的路径可视为对栈变化的一个描述,而2)中主要体现的是堆区数据的变化。
因此,本发明所构造的应用运行时模型包括一个描述栈变化的运行时栈模型和一个描述堆变化的运行时堆模型。其中运行时栈模型还包括对代码的获取,以此将一个进程所占的内存完整的分为三个区域。通过本发明实施例的运行时栈模型,可以了解在任意时刻移动应用的代码执行情况;而通过运行时堆模型,可以了解在任意时刻代码执行所依赖的对象数据状态。
运行时栈模型
控制流图为一个有向图G=<B,P>;
其中,B={b1,b2,…,bn}为基本块;
对于任意pi=(bi1,bi2),pi∈P,当且仅当bi2可能bi1之后执行。而在运行时,控制流图会实例化为一个或多个控制流,并按照控制流图中的路径执行基本块。本发明称在某一时刻执行的基本块为活动,则一段时间内的运行时栈模型是由控制流图,一个或多个控制流,和一组活动序列组成。当基本块的粒度为指令粒度时,活动序列就是指令执行序列。下面给出本发明所述的运行时栈模型的形式化定义。
定义运行时栈模型为一个或多个控制流在一段时间内发生的活动的集合M=<G,T,A,I,E>;
其中,G=<B,P>为控制流图,T={t1,t2,…,tn}为一组时刻,I={i1,i2,…,in}表示t1至tn时刻的程序的堆区状态。
运行时栈模型可视为控制流图的多条路径的集合,因此,在运行时栈模型中的边必须在控制流图中有相应的边。即:其中ai=(fi1,ti2,bi3),aj=(fj1,tj2,bj3)aj=(fj1,tj2,bj3),有(bi3,bj3)∈P。除此之外,运行时栈模型的边表示两个活动发生的前后关系,对于在同一控制流中的两个活动,具有时间上的前后顺序关系;对于在不同控制流中的两个活动如果存在边,则表明这两个活动间还具有依赖关系。
在同一个控制流中,若有两个活动具有前后发生关系,则对于任意的其他活动,都不可能在同一控制流的这两个活动之间发生,即其中ai=(fi1,ti2,bi3),aj=(fj1,tj2,bj3),如果fi1≠fj1,则
在不同控制流中,若两个活动具有前后发生关系,则对于后一个活动所在的控制流,在发生前一活动的时刻之后,可以先发生其他活动。其中ai=(fi1,ti2,bi3),aj=(fj1,tj2,bj3),如果fi1≠fj1,则ti2<tj2。
定义程序活动aj同步依赖于程序活动ai,如果aj的开始或结束由ai的执行决定,一般地,而ai往往是一些线程同步操作。称aj通信依赖于ai,如果aj的某项数据依赖是由ai活动产生。以面向对象编程语言Java为例,基本块的粒度为源代码的基本块的粒度。运行栈模型的每一个控制流对应于一个Java线程的执行序列。线程的状态转移共有六种状态:
创建:线程对象刚创建,还未开始时处于该状态;
运行:线程处于正在运行的状态,在该状态的线程可能等待一些系统资源,如CPU;
阻塞:线程正在等待某个监控锁(Monitor Lock),例如线程在进入synchronized关键字修改的方法或是代码块时,线程会进入阻塞状态;
等待/定时等待:线程正在等待,例如,当线程调用某个对象的wait方法进入等待状态。当该对象的notify方法被该用时,线程会重新进入运行状态;
死亡:当一个线程的run方法执行结束后,会进入死亡状态。
由以上的状态转换可以发现,在某些情况下,某一个处于运行状态的线程可以唤醒另一个处于非运行状态的线程进入运行状态。本发明称线程之间的这种关系为同步依赖关系。在这些线程间唤醒发生时,对应于运行时栈模型中,处于运行状态的线程发生的活动会与非运行状态线程进入运行状态时发生的活动存在一条跨线程(跨控制流)的边。从Java的语言层面,可将这些线程依赖归纳为四类,如表1所示。
表1:Java语言层面的同步依赖关系分类
在表1中,每个运行线程的活动都会与非运行线程的活动对应。因此,称表1中的线程间依赖关系为同步依赖关系。基于以上线程的状态转换,Java提供了多种多线程编程库,以支持,例如java.util.concurrent中提供了读写锁、可重入锁、阻塞锁和线程池等。
图4展示了一个线程之间通信依赖的例子——生产者-消费者模式。在该例子中,Task类表示计算任务;静态字段tasks表示待处理任务队列;postTask方法表示生成并提交任务;handleTask方法表示处理任务。如图4所示,存在两个线程:1)线程1表示生产者线程,会提交任务至待处理任务队列;2)线程2表示消费者线程,每隔一段时间便会查看待处理任务队列,并处理相应的任务。在这个例子中,生产者线程与消费者线程之间并无同步依赖关系——消费者线程每隔一段时间便自动由定时等待状态转为运行状态,但是却存在通信的依赖关系——如果生产者线程不提交任务,消费者线程的task.run方法便不会被调用。
由上述例子可以发现,运行时栈模型中的活动关系的生成必须依赖运行时的相应数据。在经典的数据流分析中,数据流分析算法会根据控制流图的结构计算数据流方程,并迭代至稳定点。因此,应用的运行时模型除了上述的运行时栈模型外,还需一个描述内存堆区数据状态变化的运行时堆模型。
运行时堆模型
经典的数据流图常用于需求分析阶段。软件利用数据流图由抽象到具体、逐层分解待开发的软件系统。数据流图为一个有向图,包含了两种不同类型的边和多种不同的节点用以描述数据从一个初始节点出发,进行层层计算,最后得到最终结果。在运行时,数据流图的某个节点本质上对应了一组内存数据的变化。因此,本发明的应用的行为运行时模型的堆模型重点关注内存数据的变化,而不是变化的操作。本发明所述的运行时的堆模型仅从内存数据变化角度对应用运行时的内存的堆区进行建模。
运行时堆模型是由一组内存数据的初始值和在一段时间内发生堆的内存修改活动的集合M=<D,A,T,R>;
其中,D={d1,d2,…,dn},为一组内存地址的初始值,A={i1,i2,…,in},为引起内存数据变化的活动,T={t1,t2,…,tn},为时间戳。
对于不同的面向对象编程语言,它们会提供不同的应用编程接口以实现内存的动态分配和回收。例如C/C++语言,通过在标准库函数中提供malloc和free函数实现内存的分配和回收;而Java语言,通过new关键字可创建新的对象,实现内存的分配,通过自动的垃圾回收机制,实现对内存的回收。
(一)基于上述阐述,针对步骤1,接下来对如何构造本发明实施例的上述运行时模型进行详细阐述。
在本发明实施例中,构建运行时栈模型包含以下三个基本要素:1)控制流图:包含了所有可能的活动和所有可能的活动关系,是程序的源代码,或是中间代码的抽象表示;2)运行时发生的一组活动,即控制流图中的一条路径,可视为栈模型中的一组节点;3)运行时发生的活动之间的关系,即栈模型的边。
栈模型的构造需要着力解决三方面的挑战:第一,由于编译优化、运行时即时编译等技术,应用的源代码和编译生成的字节码可能与运行时内存中的代码段不同,如何保障控制流图与运行时的活动能正确映射;第二,如何生成不同粒度的活动以描述复杂的应用运行状态的改变;第三,由于现在的应用大量使用多线程编译以保证界面的响应速度,提升用户体验,如何生成运行时控制流之间的依赖关系。针对以上三个挑战,本发明实施例的第一步是通过在运行时获取内存中真正执行的代码,对当前执行的代码进行抽象,可以保证控制流图与运行时活动的准确映射。第二步提出一种行为解释器,该行为解释器以上步生成的控制流图作为输入,对其进行解释执行。第三步,解释执行过程中生成应用运行时的活动;而模型生成的最后一步,是在运行时生成控制流之间的依赖关系。
下面,对运行时栈模型的生成步骤作进一步概述。
一、控制流图生成。在应用的开发过程中,由于安装包发布会包含应用编译过的中间代码,因此,出于保护的目的,应用通过会使用各种混淆工具对生成的中间代码,例如安卓下的dex字节码,进行混淆。这会导致直接提供的源代码难以与应用运行时执行的活动进行映射。这些混淆过的代码会通过应用运行时环境进行加载执行。例如,在安卓应用的Dex字节码会在Android Runtime(ART)中执行。本发明采用修改应用运行时的方式获取应用的字节码。这种方式可以带来两个好处,一是无需提供与匹配的中间代码或源代码,提高了方法的实用性;二是在应用运行时生成的中间代码可保证与执行的活动的一致性,从而保障了控制流图与运行时的控制流的匹配。
具体的,控制流图的生成:
根据指令类别得出基本块的边界,一条指令是基本块的开始当且仅当:1)它是一个方法的第一条指令或2)有某一条指令可能跳转至当前指令。而一条指令是基本块的结束,当且仅当:1)它是方法的返回,如return,throw指令;或者2)它是一条跳转指令,如if,goto,或者该指令可能抛出异常。在界定好基本块的起始和结束后,本发明的控制流图生成算法分为以下三个步骤:
计算所有跳转指令(包括显式跳转和异常跳转)的目标地址,将该地址的指令标记为可作为基本块开始的指令。
初始化基本块队列为空,并由低至高遍历每一条指令,若该指令是基本块的开始,或是当前基本块为空,则新建一个基本块,将其作为当前基本块,并放到基本块队列末尾;若该指令是基本块的结束,则将其放到当前基本块内,并新建一个基本块作为当前基本块,并放到基本块队列末尾。
遍历整个基本块队列,建立基本块的前趋和后继关系:一个基本块的最后一条指令如果是可跳转的指令,则在该基本块和跳转的目标基本块添加一条有向边;如果一个基本块不是return或goto指令,则与队列中的下一个基本块添加一条有向边。
二、按需重分配控制流图的执行,以及由行为解释器生成应用运行时的活动。在应用运行时,每一个线程会对应一条控制流,每一条控制流可视为一组有序的活动。这组活动可视为上步生成的控制流图的一条路径。因此,本发明提出一个适用于监测程序执行的行为解释器。根据配置,将需要监测的控制流图分配至行为解释器中执行。如果将每一条指令的执行都对应于一个活动,会导致这组指令序列变得规模极大而难以处理:1、数值计算语句难以与语义对应;2、程序循环产生的大量活动会湮没真正的处理逻辑。因此,本发明将活动分为数值计算、分支控制、方法调用等,并在行为解释器中实现了提供多种粒度的活动筛选的活动筛选器,以便生成合适的栈模型。
在本发明实施例的构造方法中,包括类筛选器和活动类型筛选器;其中,所述类筛选器基于包和类名正则匹配的粗粒度筛选,用于去除开发人员不关心的程序活动;所述活动类型筛选器基于活动类型的细粒度筛选,用于去除与开发者不关心的活动类型。
其中,所述栈活动的活动类型包括方法开始与方法结束,字段读,数组读和同步指令;基于上述活动类型,子步骤S303的实现方法包括:
利用对所述终端应用的应用行为具有监测功能的行为解释器对所述需要监测的控制流图进行解释执行,获得所述终端应用运行时的活动;
根据所关注的类,利用所述类筛选器对所述终端应用运行时的活动进行粗粒度筛选,生成由所述类引起的栈活动;
针对所述栈活动的活动类型,利用所述活动类型筛选器对所述栈活动进行细粒度筛选。
本发明实施例通过灵活地指定特定的包、类及指令类型,可以生成所需的栈模型,提高了易用性。
为提高构造模型的准确性,本发明把执行方法调用指令的开始和结束都视作活动并进行记录。从Java的方法调用上看,其调用显现的是一种树状的结构:对于某一个方法调用,在执行过程中可能又发生多个方法的调用。因此,为了保证生成的序列能还原成这种树状的结构,本发明以下标s表示方法调用开始的活动,下标e表示方法调用结束的活动。对于上述示例的两种程序执行情况会对应为两个不同的序列:
1)如果calculate都是在doInBackground中被调用,则序列为ds→cs→ce→cs→ce→de;
2)如果有一个calculate是自身的递归调用,则序列为ds→cs→cs→ce→ce→de。
将生成的活动序列重新构造成树状结构的方法调用可采用调用树构建算法。算法的过程其实是模拟Java虚拟机执行流程的过程。算法开始时,每一个线程的活动对应一个actions对象。对于每一个线程,维护两个数据结构:1)为已执行过的子控制流队列;2)为当前控制流的执行的函数栈。按序遍历actions中的各个活动,并进行以下判断:
如果没有当前控制流,则实例化一个,并压入函数栈。
如果当前活动为方法开始类型,则实例化一个新的子控制流,将新实例化的子控制流压入函数栈,并添加进当前控制流的活动队列。最后将当前控制流设置为刚实例化的子控制流。
如果当前活动为方法结束类型,则进行弹栈操作。如果弹栈结束后,函数栈为空,则说明当前线程的子控制流已经执行完毕,可以添加进已执行的子控制流队列中;如果函数栈不为空,则当前的控制流设置为函数栈顶的那个子控制流。
否则将当前活动压入子控制流的活动队列中。
类似于方法调用指令,其他类型的指令都可以有指令开始执行和执行结束两种活动。因为这些指令具有原子性,即在同一线程中,指令执行的开始与结束之间具有不会发生其他活动,所以,对于这些类型的指令仅需有指令开始执行的活动即可。
在具体实现中,运行时的活动表示实现可以有存储形式:可以是内存中的对象,也可以是持久化的二进制文件或ASIC II文件。本发明中,运行时堆模型可以巴科斯范式的形式表示。
本发明通过一个活动的序列化与反序列化的机制来实现这种伸缩性。在运行时模型生成时,将运行时模型中的活动序列存放于可配置大小的缓冲区中,当活动数量超过预设时,则将缓冲区的活动进行序列化,并持久化到本地存储中。
三、控制流之间依赖关系的生成。多线程编程已成为安卓应用开发中重要的一部分。利用多线程编程可实现用户界面的高效响应,和多项计算任务的并行加速。多线程编程中的线程同步和相互唤醒(称之为线程依赖关系),可抽象为对于栈模型中控制流之间的边。线程的依赖关系是与时间相关的一个关系:在某时刻,主线程可以向后台线程发送计算任务,此时为后台线程执行的活动依赖主线程的活动;而在下一时刻,后台线程完成计算任务后,通知主线程进行界面更新;此时,为主线程执行的活动依赖后台线程的活动。因此,本发明对这些线程间依赖关系进行分类,并对不同类型的依赖进行处理,以在运行时生成这些依赖关系。
在本发明实施例中,所述依赖关系包括同步依赖和通信依赖。线程间利用Java语言规范中提供的线程状态转移相关的方法,如Thread.join,Object.wait,Object.notify等实现多个线程之间的协同,本发明称这些线程间的依赖关系为同步依赖。而线程间利用对象,实现多线程间的协同的,本发明称这些线程间的依赖关系为通信依赖。在实际开发中,应用开发者会复用框架层提供的各种多线程编程类,以提升开发效率。框架层虽然提供良好语义的应用编程接口给应用层的类,屏蔽了实现细节,但是为了保障框架的性能和鲁棒性,往往实现较为复杂。利用这些编程框架实现的程序,在运行时线程之间既可能是同步依赖,也可能是通信依赖。
以图5中的BackgroundTask.execute方法调用的开始至onPostExecute方法调用的结束为例,全局共有两个活跃的线程,它们之间相互发生了同步依赖和通信依赖。该过程的方法调用如图6所示:图中的上下两条轴分别表示前台线程和后台线程随时间变化的方法栈的情况;图中的框表示方法,其中灰色的框表示框架层的方法,白色的框表示应用层的方法,即应用开发人员实现的方法;图中的箭头表示线程间的依赖关系,其中实线箭头表示同步依赖,而虚线箭头表示通信依赖。在图6所展示的方法调用过程中,BackgroundTask.execute方法在执行过程中(活动①)会调用ThreadPoolExecutor.execute方法,进而调用后台线程对象的start方法(活动②),进一步会引起后台线程的run方法的调用(活动③)。在后台线程的run方法开始执行后,经过层层调用,最终会调用BackgroundTask.doInBackground方法(活动④),在该方法的执行过程,除了会调用calculate方法进行计算任务外,还会调用AsyncTask.publishProgress方法(活动⑤),以使前台线程调用onProgressUpdated方法(活动⑥)对界面进行更新。随后,后台线程结束计算任务后,会再次用类似的方式通知前台进程当前任务已结束。其中,活动②与活动③之间为同步依赖,而活动⑤与活动⑥之间为通信依赖。
同步依赖的生成:
为了实现同步依赖的生成,Java中与同步依赖相关的方法都会被认为是需要收集的活动。这样运行时栈模型就能收集到如表1的各种与同步依赖相关的活动。对于存在同步依赖的两个活动,后发生的活动可能是一个方法的结束或是一个方法的开始,据此,可将同步依赖分为两种,并进行分别处理:
对于一个方法的结束依赖另一个方法的结束的情况,利用时间戳由后往前寻找其他线程中可匹配的活动,如果找到,则对应于一个同步依赖关系。例如Thread.join的结束依赖Thread.run的结束;Object.wait的结束依赖Object.notify的结束,对于如Thread.join或Object.wait的方法结束活动,就可利用时间戳由后往前寻找其他线程中可匹配的活动。如果找到了,则对应于一个同步依赖关系。
在生成控制流间的同步依赖关系时,对于一个方法的结束依赖另一个方法的结束的情况,利用时间戳由后往前寻找其他线程中可匹配的活动,如果找到,则对应于一个同步依赖关系;例如Thread.join的结束依赖Thread.run的结束;Object.wait的结束依赖Object.notify的结束,对于如Thread.join或Object.wait的方法结束活动,就可利用时间戳由后往前寻找其他线程中可匹配的活动。如果找到了,则对应于一个同步依赖关系。
对于一个活动的开始依赖另一个活动的结束的情况,先对当前线程进行检查,如果该活动为当前线程中的第一个执行的活动,则该活动是依赖另一个线程结束活动的,否则该活动仅是正常的方法调用,并不依赖另一个线程的活动。例如Thread.run的开始依赖Thread.start的结束,由于在Java中,一个活动的开始(即某个方法的调用)可以在任意地方进行任意次数,包括Thread.run方法。因此,要判断一个Thread.run方法的调用是否依赖Thread.start方法的调用不能直接根据时间戳由后往面查找匹配,需要先对当前的Thread.run进行检查:如果该活动为当前线程中的第一个执行的活动,则它是依赖另一个线程Thread.start结束活动的,否则它仅是正常的方法调用,并不依赖另一个线程的活动。
通信依赖的生成:
线程间不基于Java提供的能实现线程状态转移的方法,实现多线程间的协同的,本发明称这些线程间的依赖关系为通信依赖。
以图6中活动⑤与⑥为例,其具体实现是基于MessageQueue的next方法与enqueueMessage方法。在这过程中,如果前台线程的待处理队列中还有元素排队等待处理的话,则活动⑤引起的enqueueMessage方法,仅会将当前任务添加到该队列中,并不会显式地唤醒前台线程。但从逻辑上,可认为对于某一MessageQueue对象而言,其next方法所返回的Message对象,会被Handler以参数的形式传入dispatchMessage方法中,因此可认为MessageQueue的next方法的结束依赖MessageQueue.enqueueMessage,并且该依赖是以参数Message为匹配对象的而不是MessageQueue。
在生成控制流间的通信依赖关系时,对所有与活动间通信依赖相关的类进行总结,并将这些类的相关方法与线程依赖相关的方法一起作为生成通信依赖的知识库。该知识库亦可支持应用的进行自定义。
在本发明实施例中,运行时堆模型包含以下基本要素:1)一个堆区的初始状态;2)运行时发生的一组影响堆区数据的活动。本发明首先给出堆区初始状态的描述方法,并在运行时生成符合该表示的堆数据初始状态。其次,本发明给出了堆操作活动的描述方法,并在运行时构造运行时堆模型中的活动。最后,给出了堆区初始状态与堆操作活动的BNF表示。
下面,对运行时堆模型的生成步骤作进一步概述。
一、是对堆区初始状态的生成。堆区的初始状态为开始时刻的堆区数据的状态。在Java虚拟机规范中,仅对堆区给出了最简单的描述:堆是运行时用于分析所有类实例和数组的区域,该区域由一个自动存储管理系统(即垃圾回收器)进行管理。堆中的对象从来都不会显式地回收,而是会由垃圾回收器自动回收。堆区的初始状态可视为某一时刻堆区数据的快照,因此,如果在生成内存堆区数据状态时,如果有别的线程继续执行,并进行堆区操作(例如创建对象、执行垃圾回收等),就会破坏初始状态的原子性。因此,本发明首先给出了一种描述堆区初始状态的BNF表示,并在生成堆区数据初始状态时采用“冻结”堆区数据的方式,保证了初始状态生成过程的原子性。
二、堆模型中的活动的生成。在应用运行时,Java的垃圾回收器可以产生回收内存的活动。除了这些活动外,其他活动可视为运行时栈模型中的活动的一个子集。一方面,如果将每一个会影响堆区的数据的操作都对应于一个活动,会导致这组活动数量变得极大而难以处理。例如某应用中存在大文件的I/O操作,如果全部操作都以活动的形式记录下来,则活动的数据量将不小于大文件的数据量;另一方面,类似于控制流模型,可能仅关注部分类、方法的执行,生成过大的堆模型反而难以分析。这里扩展了活动的描述使其支持描述垃圾回收活动,类似于运行时栈模型,提供多种粒度的活动选择筛选选项,以便生成合适的堆模型。生成的堆模型详实地描述了所关心的对象的变化情况,因此,利用基于时间戳的堆对象状态查询算法可以查询堆对象在任意时刻的状态。
接下来,采用一个具体的例子对运行时堆模型建模过程进行介绍:
Java堆区的数据仅包括实例化的对象和数组。对于应用而言,对象的创建可能发生在应用层代码或框架层代码,因此,我们将应用中的对象分为应用层和框架层,以图5实现的代码为例,在触发点击事件前,堆区的对象如图7(a)所示。图中每一个圆表示一个对象,而圆与圆之间的连线表示引用关系。图7(a)中与应用业务逻辑相关对象有展示界面FloatActivity、可触发后台计算任务的按钮Button、待处理的后台任务BackgroundTask、用于展示任务计算结果的TextView和点击事件监听对象OnClickListener。除了与业务逻辑相关的对象,还有许多框架层的对象。例如,框架层一个MessageQueue对象。实现后台处理任务可以通知前台进行更新。图7(a)和图7(b)中的实线箭头表示对象的引用关系,即对象A如果有某个字段指向另一个对象B,则由A至B有一条有向连接;虚线箭头表示在整个事件的处理过程中,存在过对象的引用关系,而在结束时,已没有引用关系了。
在事件触发过程中,会创建如下对象:在BackgroundTask.execute方法在执行过程中,会调用ThreadPoolExecutor.execute方法,此时,由于是该对象首次执行execute方法,该对象会新建一个Thread对象①,为后台执行的线程;在调用后台线程对象的start方法之后,进一步会引起后台线程的run方法的调用并正式开始调用doInBackground方法。在该方法中,除了会调用calculate方法进行计算任务外,还会调用AsyncTask.publishProgress方法,在执行该方法前,传入的参数会被封装到新创建的Integer[]对象中②;而在方法执行过程中,则是会新创建一个Message对象③,并放至全局的一个MessageQueue队列中。当前台线程接收到该Message,并执行onProgressPublished时,会创建一个StringBuilder对象④,以构造setText所需的参数,并通过StringBuilder.toString方法实例化出一个新的String对象⑤。而当doInBackground方法执行结束前,又会创建一个新的StringBuilder对象⑥,并且计算出返回值String对象⑦。该String对象会被再次封装到一个新创建的Message对象⑧中,并通知前台线程执行onPostExecute方法。上述过程已对部分步骤进行简化,在实际运行中会存在更多的对象创建。例如,Thread对象并不是直接依赖BackgroundTask而是会经过FutureTask,Callable等对象的层层封装,间接依赖BackgroundTask对象。在该过程结束后,对象①至⑧都可能会在某次垃圾回收中被回收。
在堆模型中,本发明将图7(b)中的每个对象的实例化、字段赋值以及回收都视为一个活动。类似运行时栈模型的表示,下面本发明优选以巴科斯范式的形式给出一种运行时堆模型的表示。
其中,DataAction与运行时栈模型构造关键技术中所描述的ControlAction类似,ControlAction用于描述执行的指令情况,而DataAction用于描述内存数据的变化情况。Number表示数字类型,可以是数值,也可以是内存地址;String表示字符串类型。从上述表示中,可以发现,模型的复杂度主要取决于:1)初始状态中对象的数量;2)堆区数据活动的数量。
在安卓的具体实现中,其堆区可分为三个子区域:1)应用堆(App Heap),当前应用实例化对象和数组时使用的内存区域;2)镜像堆(Image Heap),装载了当前应用镜像的内存区域;3)孵化堆(Zygote Heap),系统启动时加载的系统类存放的内存区域。本发明所述的初始状态主要关注运行时变化最大的应用堆。对于安卓平台上的Dalvik虚拟机和ART虚拟机而言,它们实现了可以在任一时刻,将当前应用堆的状态以文件的状态保存下来(堆转储操作)。该文件是一种私有的内存镜像格式,通过安卓开发者工具可将其转换为符合J2EE平台规定的hprof格式。
然而直接基于现在的应用堆转储仅能反映某一时刻的堆状态,难以实现反映一段时间内的任意时刻的堆状态。首先,执行一次堆转储操作需要挂起所有线程,耗时高,生成的文件从几十兆字节至几百兆字节不等,难以通过每隔一段时间执行一次堆转储操作实现反映一段时间内的任意时刻的堆状态。其次,执行堆转储操作并不会转储那些被回收器回收的对象,包括那些在执行过程中产生的临时对象,然而对于一个执行过程而言,产生的临时对象对描述过程的执行也十分重要,例如,图7(b)中的对象②至⑧都是临时对象,这些对象不能直接利用堆转储进行持久化。
在本发明实施例中,所述构造所述终端应用行为的运行时堆模型的子步骤包括:所述终端应用运行时的活动包括实例化活动、修改活动和回收活动。
其中,实例化活动(NewAction),即创建新的对象、新的数组的活动,可对应于字节码中的newInstance、newArray等指令在运行时的执行。
修改活动(ModifiyAction),即修改类的静态字段、对象的字段、数组的元素的值的活动,可对应于字节码中的sput,iput,aput等指令。
回收活动(GCAction),即执行垃圾回收时,对堆中的对象产生影响的活动。针对回收活动,垃圾回收机制是一种自动的内存管理机制。当一片内存中的数据不再会被用到时,会予以回收释放,以便于进行下次的分配。具体的垃圾回收算法实现有引用计数法、可达性分析算法等。
回收活动在运行时是对应不到dex字节码的指令的,因为它的具体实现是在虚拟机层面。回收活动可进一步细分为清除活动和压缩活动。所谓清除活动就是清除不再需要的对象;而所谓的压缩活动则是对活跃的对象整理到连续的内存空间上,以避免分配大内存时由于碎片化导致分配失败的情况。
除此之外,除了实现的应用层的代码会创建对象以外,框架层的代码也会创建大量对象,在某些情况下,甚至会是几倍于应用层创建的对象。需要提供一种堆模型复杂性管理的机制,以保证生成的运行时栈模型的准确性和易用性。与前述两级筛选机制类似,对于堆模型的活动生成,也有一个两级的筛选机制。基于包和类名正则匹配的粗粒度筛选和基于活动类型的细粒度筛选。
本发明优选提供了6种堆操作活动,所述堆操作活动的活动类型包括对象实例化,数组实例化,对象字段写,数组元素写,清除活动和压缩活动;
所述生成堆操作活动的步骤包括:
根据所关注的类,利用所述类筛选器对所述终端应用运行时的活动进行粗粒度筛选,生成由所述类引起的堆操作活动;
针对所述堆操作活动的活动类型,利用所述活动类型筛选器对所述堆操作活动进行细粒度筛选。
(二)基于上述阐述,针对步骤2,接下来对如何实现对这种指令粒度的自描述的控制进行详细阐述。
在本发明一优选实施例中,所述运行时模型包括一段时间内发生的一组具有逻辑关联的活动的集合;
所述预定义对所述运行时模型的操作包括:
增加,用于在原有活动执行流程中增加一部分程序活动;
删除,用于消除原有活动执行流程中执行的活动对程序本身的影响;
修改,用于在消除原有活动的影响后,重新执行修改的活动。
定义如下:
应用运行时行为模型的操作:令M=<G,T,A,I,E>为应用行为运行时模型,a=(t,i,b),且a∈A,操作集合为OpSet={Add,Delete,Transform},操作Op∈OpSet,其中:
Transform:A→A-{a}∪{a′}。
利用上述的三个操作,可以实现对应用运行时模型中的一组活动的修改。活动序列对内存的控制体现为对内存的读、写操作。对于两级不同的活动,可能对内存产生相同的控制。下面,本发明从内存状态的角度,对控制的等价性作如下定义:
由此引出存控制等价的定义如下:
下面以案例一中的自动重构字节码以实现网络能耗优化的代码作为示例,来对活动序列的控制的等价性做进一步阐述。
案例一的图2(a)和图2(b)所对应的运行时栈模型如图8所示,由图8可以发现,发送网络请求的过程由重构前的②→③变为⑦→⑧,这两个活动序列的栈的变化不相同:这是由于有了额外的调度器NetworkStub。因此,这两个活动序列不是栈控制等价。但是,发送网络请求的过程中,与应用原有执行逻辑的堆的变化情况是相似的:重构前、后,与应用业务逻辑相关的对象的变化保持一致;重构之后,堆中新增了与网络请求调度逻辑相关的对象。因此,仅从与应用原有执行逻辑相关的对象的角度上看,这两个活动序列是堆控制等价。也就是说,这个自动重构是一个实现堆控制等价的重构。类似例子还有利用自动重构实现代码覆盖率报告成的工具,如Emma,JCover,它们自动重构前、后的应用均是保持了原应用的任意一个方法重构前、后的执行序列保持了堆控制等价。由此可发现,重构前后的堆控制等价保证了程序执行的功能一致性。
以案例二的微信应用面向互操作的接口生成为例,既有面向用户的图形界面接口实现发消息的应用行为运行时模型可抽象为如图9(a)所示的顺序图:首先由用户触发界面层的对象向控制层的对象发送消息①,应用的执行逻辑在控制层和数据层切换执行(②→④);当结果返回时,将结果通知界面层对象⑤。假设图9(b)为面向互操作的发送消息接口的执行流程,可以发现这两个执行流程中②→④与⑦→⑨,即后台线程与服务器的交互过程中,其活动的执行保持了栈控制等价与堆控制等价。虽然整个交互过程中这两个活动序列对堆的控制不等价:面向互操作的接口不依赖界面相关的元素,而面向用户的接口依赖聊天窗口对象;对栈的控制也不等价:只有面向用户的接口执行过程涉及了用户界面线程。
上述案例表明,通过重构前、后的应用执行流程的堆控制等价来保证重构前后应用业务逻辑的一致性,并增加正交于业务逻辑的功能,例如,生成代码覆盖报告、调度网络请求。从结构反射的控制来看,一方面,其本质是修改了应用的代码,进而影响了应用的执行流程。另一方面,基于结构可以实现行为反射,即实现对应用执行流程的监测与修改。这也意味着,对于基于行为解释器实现的行为反射,其控制也有了两方面的含意。一方面,行为反射的控制本质上需要修改应用执行的行为。另一方面,如果将运行时的行为序列作为程序,通过行为的修改,也可实现对应用代码的修改。因此,行为反射的控制在一定程度上也能实现结构反射。下面,给出本发明对基于运行时模型的行为控制的分类与定义。
控制的分类:应用行为运行时模型可视为应用执行某一项功能时的一个完整的顺序图,其所描述是在一段时间内发生的一组具有逻辑关联的活动的集合。如果把应用运行时模型视为一组对内存产生影响的活动,那么增加、删除、修改则意味着对当前程序运行状态的修改。而如果把应用运行时模型视为一段执行过的代码,则增加、删除、修改意味着对这段代码的修改,而对应用的控制就体现为生成了一段新的代码。
在本发明优选实施例中,对所述终端应用行为进行控制包括对所述终端应用行为进行行为式控制和/或结构式控制。本发明将对当前应用内存直接产生影响的控制定义为行为式控制,而将这种可以产生新的应用代码的控制定义为结构式控制。
(1)行为式控制
行为式控制强调直接对原应用的状态进行控制和修改,即在不生成或编写新的代码片段、不重新编译、不重新启动应用的情况下,对原应用状态和行为进行控制。行为式控制将行为运行时模型视为一组发生过的活动,对行为运行时模型的操作意味着修改原应用的已经发生过的活动对内存状态的影响。
在本发明实施例中,对所述终端应用行为进行行为式控制的步骤包括:
设终端应用的内存初始状态S,活动序列A;
经过一段时间的执行,获取所述运行后的内存状态为S·A=S',对所述运行时模型保持所述等价性进行转换后的活动序列为A',得到目标状态为S.A'=S”;
利用S.A'=(S.A).A”=S'.A”求解增量活动序列A”,使得A+A”=A',完成对所述终端应用行为进行行为式控制的过程。
下面,根据运行时发生的不同活动的增加、删除和修改操作的语义给出行为式控制所需的增量活动序列A”:所述增量活动序列A”包括:控制转移、算术运算、字段读取、数组读取、类实例化、数组实例化,字段赋值、数组赋值、线程同步、垃圾回收、方法调用和类加载。
控制转移:控制转移活动所对应的指令包括if系列指令、switch系列指令等。对于一条转移控制指令,其执行的效果为继续执行、或跳转至某个分支。如果修改所影响的是程序的结构,那么就需要考虑其外部性,即控制转移的修改会导致其后续执行的一系列活动均不同。然而,此时的行为控制仅是控制单一的活动,因此,仅从对栈的影响来看,这条指令影响的是PC寄存器的计算,而PC寄存器是变化最频繁的寄存器——每一条指令的执行,都会使的PC寄存器发生变化。控制转移指令的增加、删除、修改的操作很难与活动执行对应,因此,对于控制转移指令,本发明不定义任何操作。
算术运算:算术运算活动的效果是对栈中的某些寄存器进行算术运算。算术指令可视为对某一寄存器进行赋值。例如:add-int/2addr Reg1 Reg2在运行时所对应的活动意味着对寄存器Reg1进行了赋值,赋值的结果为该寄存器与Reg2寄存器的值之和。对该活动的增加、删除、修改操作可实现为对目标寄存器的控制:Add的含意就是增加一个对该活动所赋值的寄存器进行进一步赋值;Delete的含意就是消除该寄存器赋值的效果,即将该寄存器的值恢复;Transform的含意就是对被修改的活动所影响的寄存器的赋值。Add、Delete和Transform都需要应用到达S'状态时该活动所对应的方法仍未执行完毕。如果方法已经执行完毕,方法栈已经销毁,也就不存在该寄存器。
字段读取:字段读取活动影响的内存区域是栈。字段读取活动对内存的影响是读取堆中的某个对象的某个字段,并写到特定寄存器里。因此,这类活动的增加、删除、修改的语义与算术运算活动相同。
数组读取:数组读取活动与字段读取活动接近,对内存的影响是从堆中的某个数组中读取某个元素,并写到某个寄存器上。因此,这类活动的增加、删除、修改的语义与算术运算活动相同。
类实例化:该类型的活动影响的内存区域是堆。Java编程语言中,类实例化活动对应的指令为new-instance。不论是Java的.class格式的字节码还是dex格式的字节码,类的实例化均分为两条指令:一为new-instance,二为调用其对应的构造函数。对于类实例化活动,增加(Add)的语义则是新增一个同样类型的对象;修改的语义无定义;删除的语义则是消除这个对象,也就是强制回收该对象。对于存在别的对象引用它的情况,则可实现为将引用它的对象的相应字段赋值为空。
数组实例化:该类型的活动的增、删改的语义与类实例化相同。
字段赋值:该类型的活动影响的内存区域为堆。字段赋值就是读取某个寄存器的值,并写到堆中的某个对象的某个字段中。因此,这类活动的增加(Add)意味着新增一个活动,该活动为对某个对象的某个字段的赋值;Delete意味着消除本次写活动的影响,即将其赋值为之前的值;而Transform则是对某个对象的某个字段的修改。
数组赋值:与字段赋值相似,其增加、删除、修改的语义与字段赋值一致。
线程同步:虽然该类型的活动会影响线程之间的执行次序,但该类型的活动既不直接影响栈区,也不影响堆区。因此,从内存控制等价的角度,对于线程同步本发明不定义任何操作。
垃圾回收:垃圾回收活动是对堆区产生影响的活动。具体地,有分为回收对象活动和压缩对象活动。对于回收对象活动,Add意味着对一个对象进行回收,与NewInstance的Delete所实现的控制一样,是强制对该对象回收,对于存在引用的情况则赋值为空。Delete则意味着对象实例化,并且,将该对象的状态恢复至刚回收时刻的状态。回收对象活动的Transform操作无定义。对于对象压缩活动,该活动的效果是将一个对象由地址1移至地址2。从编程语言的角度上看,这个压缩活动对开发者是完全透明的;有的虚拟机甚至可以不实现带压缩活动的垃圾回收算法。因此对于压缩活动,其三种操作无定义。
方法调用:相比于上述的活动,方法调用活动是较复杂的。方法调用活动是一个包含一系列活动的活动。因此,它不仅会对栈区产生影响,还可能对堆区数据产生影响。方法调用活动的Add操作意味着再执行一次方法调用;Delete则意味着消除方法调用的影响。对于已经执行结束的方法调用,Delete意味着同时消除该方法所包含的堆区相关的活动;而Transform则难以定义。1)如果定义为在新的上下文环境重新执行一次方法调用,则重新执行的方法调用的序列难以与!'保持内存控制等价的关系。2)如果定义为对直接对参数所对应的寄存器和栈进行修改,则很难在基于寄存器的dex字节码上和基于操作栈的.class格式字节码上保持一致。因此,对于方法调用不定义Transform操作。
类加载:类加载活动对应于ClassLinker的一组初始化活动及相关类的<clinit>方法的执行。与普通的方法调用不同,类加载所引起的<clinit>只会被调用一次。因此,其Add操作无定义。而Delete着意味着类卸载,本发明将其定义为执行所有属于这个类的对象的Add操作,并消除该类在ClassLinker的影响。
(2)结构式控制
结构式控制意味着对运行时活动的修改会反映到原应用的代码段中。即结构式控制将应用行为运行时模型当作一段执行过的代码片段,对行为运行时模型的增加、删除、修改操作,则体现为对代码片段的增加、删除、修改。
在本发明实施例中,对所述终端应用行为进行结构式控制的步骤包括:
获取终端应用行为的运行时模型M,一组操作以及一个转换后的活动序列A';
将所述一组操作映射为一段目标代码,以使所述目标代码的执行所产生的活动序列A”与活动序列A'保持控制等价关系。
对于任一给定的一个行为运行时模型片段,它由一组方法调用、字段读写等活动组成。每一个活动均可通过程序分析的方法自动生成相应的代码片段,和原片段保持堆控制等价。
上述步骤中,针对任意活动,将所述一组操作映射为一段目标代码的步骤包括:获取数据依赖,使得所述活动执行的数据依赖与活动发生时刻相同;
将所述活动转换为相应的目标代码。
在本发明实施例中,所述数据依赖包括数值类型和对象类型;
对于一个活动,当其依赖的数据类型为数值类型时,直接采用运行时模型中活动执行时对应的数值;
对于一个活动,当其依赖的数据类型为对象类型时,分为局部对象和全局对象;其中,当其依赖的数据类型为局部对象时,通过回放与该对象相关活动的方式生成构造所述局部对象的代码;当其依赖的数据类型为全局对象时,通过程序分析构造出一组可达的对象引用链以获取该对象,根据对象之间的引用类型,自动生成动态数据依赖相关的代码。
本发明实施例中的局部对象是在功能执行过程中实例化出来的对象,并且在功能执行结束后会被垃圾回收的对象。针对所述局部对象的构造,包括基于活动序列构造的局部对象构造和基于运行时堆模型的局部对象构造;下面以运行时堆模型为例进行阐述:
对于基于活动序列的局部对象构造:
通过求解活动序列中某一个对象的堆控制等价序列的算法,其输入为一个包含一组活动序列控制流f和一个相关对象。首先判断当前控制流节点是否与该对象相关,如果不相关则直接返回空。如果相关,则构造一个数组resut以保存堆控制等价的活动序列。其次,第一个等价的活动序列就是该控制流的最外层活动。例如,当前控制流是一个方法调用,那么直接再执行一次相同的方法调用,相应对象的状态就堆控制等价。除此以外,如果该控制流是一个方法调用,那么堆控制等价的活动序列还包括其子控制流的堆等价序列的笛卡尔积。求子控制流的堆控制等价序列可视为进行函数内联的过程。对一组平均子控制流个数为K,平均深度为H的控制流而言,其堆控制等价序列个数为O(KH)。
对于基于运行时堆模型的局部对象构造:
基于运行时堆模型中的初始Snapshot和所有堆操作活动序列DataActions,可实现查询任意时刻的该对象的状态。首先是加载初始镜像和活动,步骤如下:
读入初始镜像,由此获得一个初始的Snapshot,在初始的Snapshot中,每个对象都以当时运行时内存地址的标识。
遍历所有活动:对于实例化活动,依次在初始的Snapshot中添加相应的对象;对于修改活动和清除活动,将其添加至相关对象的活动序列中;对于压缩活动,除了添加至至相关对象的序列中之外,再创建一个以压缩后地址为标识的对象,并将该压缩活动作为该新建对象的首个活动。
通过以上两步可以保证所有在该过程出现的对象,都在的Snapshot中。其中,由于压缩活动、回收活动、再分配活动,可能导致某个内存地址对应于多个对象。因此,在Snapshot中以标识和创建时间作为一个对象的全局标识符。
在加载完毕后,可实现任意对象的任意时候的状态查询。要查询某一时刻某一对象的状态,就是查询该对象在该时刻的每个字段的值。对于数值类型的字段而言,该时刻的值就是离该时刻最近的一次修改操作的值;而对于对象类型的字段,则是当时所引用的对象的地址在该时刻的值。因此,算法的核心是,根据对象中每一字段的活动,查询该时刻的值。首先,将一个对象相关的所有活动分配至每个字段,然后通过二分查询实现查找按时间顺序排序的活动序列中最接近该时刻的活动。令DataActionCount为某对象的某字段相关的活动,ObjectCount为对象数量,FieldCount为类的平均字段数量。则平均上看有:
因此,算法的复杂度为每个字段进行log(DataActionCount)次数的查询,即,算法的复杂度为:O(FieldCount*log(DataActionCount))。本算法与活动数量的对数成正比,因此可以在活动数量巨大时保持检索的效率。
对比上述两种生成局部依赖的方式,从执行效率上看,第二种更高——它相当于是仅将与所需对象相关的字段写操作回放执行一遍;从可读性上来看,基于活动序列的局部对象构造可以产生行数较少的代码。
在本发明实施例中,所述全局对象根据运行时堆模型中的引用链获得。
基于引用链的全局对象引用获取方法:全局对象就是在功能执行前后,该对象堆存在于堆中。全局对象可通过两种方式获得:
根据运行时堆模型中的引用链获得或者利用前述算法,以该全局对象为相关对象,生成相应的模型片段。由于该对象为全局对象,在原有执行逻辑中可能存在某个数据依赖较少,甚至没有数据依赖的方法可直接获取该全局对象。因此,只需筛选生成的相应的模型片段中元素个数较少的片段。
根据对象的依赖关系,选择好若干个对象在若干控制流中的模型片段,即可通过本方法自动生成相应代码片段。根据生成的代码片段进行一定的编辑,即可实现利用本发明的结构式控制完成对应用行为修改。
针对行为反射在控制上面临的挑战,下面,采用一具体实例验证本发明实施例对终端应用行为监测和控制的有效性。
针对移动互联网中广泛使用的安卓移动应用,给出行为反射框架的原型系统实现:Reflectall。Reflectall的全称为Reflection at low level interpreter,具有两重含意,一是基于底层的行为解释器实现的反射;二是它可以监测和控制指令级别的应用行为。Reflectall基于安卓操作系统开源项目。为了实现移动应用行为的监测与控制,Reflectall平台可分为行为运行时模型构造子系统、模型分析与代码生成子系统和运行子系统,实现了行为反射框架中的监测和控制。
参照图10,为Reflectall模型生成子系统架构示意图。Reflectall的行为运行时模型构造子系统实现了移动应用行为运行时模型的构造,其核心实现在系统层,由优化-反优化器、行为解释器、模型构建和接口层四个模块组成。这个四个模块实现了移动应用行为的监测与控制。
其中,优化-反优化器:安卓运行时环境可加载CPU可直接执行的原生指令。因此,需要将以原生指令切换为字节码,即反优化,并通过行为解释器进行解释执行,从而实现对移动应用运行时活动的监测。移动应用由于其复杂性,难以监测移动应用执行中的所有活动,因此引入了一个两级筛选机制。优化-反优化器实现了两级筛选机制中的类筛选机制,通过优化-反优化器,可以按需地将待监测的类反优化为字节码,并进行解释执行;而对于未被监测的类,则仍然在原生执行器中执行。优化-反优化器会在如下三种情况下触发:1)收到开始监测的命令时,会根据所配置的参数,对当前已经加载的类的方法进行筛选,并进行反优化;2)收到结束监测命令时,会对当前已反优化后的类进行再优化,令其重新进入原生执行器中执行;3)类链接器加载新的类时,会对类进行类似于情况1)中的筛选和反优化过程。为了保证程序执行的正确性,反优化的过程需要和部分垃圾回收算法一样,暂时挂起所有线程的执行,待反优化执行结束后,再恢复线程的执行。通过这种局部反优化,并保持解释执行与原生执行共存的状态,可以大大减少监测的性能开销。
行为解释器:行为解释器是解释执行dex格式字节码的解释器,能在解释执行的过程中,监测当前的程序执行中发生的活动。移动应用行为运行时模型中的活动大多是由行为解释器生成。除了行为解释器所生成的活动,垃圾回收器也可以生成部分活动——垃圾回收活动。行为解释器还实现了两级筛选机制中的活动筛选机制,可根据配置的活动收集粒度产生不同种类的活动。
模型构建器:行为解释器与垃圾回收器所产生的活动会在模型构建器中构建。当运行时产生的活动较多时,会导致内存占用较大。因此,模型构建器实现了在线和离线的模型构建。当活动较少时,模型构建器运行于在线的模型构建模式时,当活动数量达到配置的阀值,模型构建器会将当前已产生的活动序列持久化,并以文件的形式持久化至存储。
接口层:对优化-反优化器、行为解释器和模型构建器所提供的功能进行封装。同时也提供反序列化活动所需的接口,例如根据地址查找对象和给定对象转换为地址等。
本发明实例所实现的原型中,可以生成两种移动应用行为运行时模型:1)包含了运行时数据依赖的精细化模型;2)不包含运行时数据依赖的精简模型。基于系统层的实现,在框架层,Reflectall包括一组行为反射接口,可监测不同粒度的应用的活动,生成不同粒度的应用行为运行时模型;一组远程调试连接接口,可控制应用活动监测的开始与结束。在应用层,对框架层的接口进行封装,实现了一个安卓应用,可以以Web服务的形式对外提供远程调试接口。
Reflectall的分析与代码生成子系统为浏览器-服务端架构。分析与代码生成子系统实现了:
版本管理:利用git管理不同版本的移动应用与互操作接口。同时支持服务器端编译,并利用客户端的接口管理应用将编译好的dex字节码推送至客户端。
栈模型可视化:提供了一个树状的视图,并支持基于关键字的数据依赖沾染分析。
Reflectall的接口运行子系统在安卓开源项目的框架层上添加了一个行为反射类加载器,如图11所示,为本发明示例Reflectall的接口运行子系统的结构示意图。当应用进程启动时,会检查是否有可加载的行为反射接口字节码文件。如果存在适合当前应用的行为反射接口字节码文件,则通过行为反射类加载器将其加载进应用进程,同时,利用Binder通信机制向接口管理应用注册当前应用提供的互操作接口。接口管理应用提供了接口转发、状态检测等服务。调用者进程通过接口管理应用可实现与指定应用互操作。
具体验证时,本发明以一个包含69个开源安卓应用的开源应用集和一个包含39个闭源应用的闭源应用集对Reflectall模型生成的性能进行验证。其次,以35个闭源应用的互操作接口开发验证Reflectall的行为式控制的有效性。最后以一个开源应用计算器的运行状态的监测和控制验证Reflectall行为式控制的有效性。
验证对终端应用行为监测的有效性:
移动应用行为运行时模型的构造开销与模型的活动数量正相关——应用越复杂、活动越多,生成行为运行时模型的开销也就越大。相比闭源应用,开源应用在实现的复杂程度远低于闭源应用。开源应用集的类的数量的中位数为58个,方法的数量的中位数为246个;75%的开源应用集中的应用的类的数量不大于167个;方法的数量不大于859个。而闭源应用集中的应用,应用的类的数量的中位数为14266,方法的数量的中位数是87717个,是开源应用集中对应的数值的245倍和102倍。实验所使用的硬件配置如下:1)使用Android智能手机红米2A,它的CPU是1.5GHz,内存为1GB,运行安卓操作系统版本为5.1.1。2)实验使用一台普通PC作为远程控制端控制手机进行实验,该PC的CPU为Intel酷睿i5 3427U(1.8GHz),内存为4GB,运行OSX 10.11操作系统。
目前,监测应用执行流程的方法除了本发明所述的实现行为解释器的方式外,还包括运行时元消息绑定和编译时字节码重构两种方式。表2给出了三种方式所支持的活动监测的粒度。Reflectall在活动监测的粒度上比基于运行时绑定的方法要更细:支持到指令级别的活动监测;同时也比基于字节码重构的方法适应范围更广:基于字节码重构的方式需要修改原应用的编译流程,难以直接在经过混淆、加固的应用上使用。
表2:监测程序执行流程的方法比较
执行流程监测的粒度 | 是否需要字节码 | |
Reflectall | 支持方法级别、指令级别的监测 | 不需要 |
基于运行时绑定的方法 | 支持方法级别的监测 | 不需要 |
基于字节码重构的方法 | 支持方法级别、指令级别的监测 | 需要 |
本发明在实验一对比Reflectall与基于运行时绑定的方法在监测程序执行流程方面的性能。在实验二对比Reflectall与基于字节码重构的方法在监测指令粒度的活动的性能。
实验一:对比运行时元消息绑定的方法
Xposed框架是一款可以在不修改APK的情况下监测和修改程序运行行为的框架服务(rovo89,2012)。类似于Reflectall,Xposed框架也是在安卓操作系统的系统层上进行修改。Xposed框架实现了元消息模型的行为反射,即Xposed在应用运行时根据配置对指定的方法绑定相应的元对象。在后续的执行中,这些绑定了元对象的方法在执行前、执行后会调用元对象中的before、after方法。本文利用Xposed框架,实现了与Reflectall类似监测程序执行的Xposed模块。本节以应用启动时间作为指标,在两台硬件配置相同的红米2A手机上,分别部署了Reflectall和基于Xposed的监测模块,安卓操作系统均为5.1.1。通过以下6个不同的实验场景,比较Reflectall与基于Xposed框架的方法在开源应用集和闭源应用集上监测应用的所有应用类执行的性能,如表3所示。在每个场景下,每个应用启动10次,实验的结果如图12(a)和图12(b)所示。
表3:监测程序执行流程的方法比较
图12(a)为开源应用集上的实验结果。在开源应用集中,69个应用在上述6个场景下均能正常启动。图12(a)中的实线部分为部署了Reflectall的三个场景;虚线部分为部署了Xposed框架的三个场景。在不监测程序执行流程的情况下,部署Reflectall的手机(场景1)平均启动时间为392毫秒,而部署了Xposed框架的手机(场景3)的平台启动时间为449毫秒。这是由于Xposed框架的实现即便不绑定元对象,也在应用加载时会有一定的开销。而Reflectall的优化-反优化器实现了在不监测时,所有代码均在原生执行器中执行。在场景2下,Reflectall的平均启动时间为486毫秒,相比不监测情况(392毫秒),额外开销为23%;而基于Xposed框架的方法,在场景2下的平均启动时间高达2078毫秒,相比不监测情况(449毫秒),其额外开销为368%。而在生成更复杂的行为运行时模型时(场景3和场景6),Reflectall的额外开销仅为27%,而基于Xposed框架的方法高达477%。
图12(b)为闭源应用集上的实验结果。在不监测的场景下,相比于实现简单的开源应用,闭源应用的平均启动时间为936毫秒(Reflectall)和1010毫秒(Xposed)。而在场景2下,由于应用的复杂性,Reflectall有3个应用无响应,剩余的36个应用的平均启动时间为1601毫秒,相比不监测的场景(936毫秒)额外开销为71%。而场景4下有22个应用无响应,剩余的17个应用的平均启动时间为4593毫秒,相比不监测的场景(1010毫秒),其额外开销为354%。而在生成更复杂的行为运行时模型时(场景3和场景6),Reflectall的额外开销为98%,而基于Xposed框架的方法高达470%。
Reflectall生成行为运行时模型的性能在开源应用集上和闭源应用集的差异较大,这是由于闭源应用集中的应用实现更为复杂,因此生成的模型规模也更大,可能引起多次的垃圾回收和活动持久化过程,由此带来了更多的性能开销。而基于Xposed的方法在这两个应用集中开销接近,原因在于利用Xposed进行监测时,有22个应用无响应,占整个闭源应用集的57%。Reflectall的性能开销比基于Xposed框架的方法低的一大原因是基于Xposed框架的方法的所使用的编程语言是Java,而Reflectall所实现的行为解释器编程语言为C++。当应用的执行流程较复杂时,基于Xposed框架的方法对内存的分配和回收会比Reflectall更加频繁。通过上述实验表明,本发明Reflectall的这种实现方式可以处理更复杂的应用。
实验二:对比基于字节码重构的方法
很多常用的Java库都用到了字节码重构框架。字节码重构的一个很重要的使用场景就是程序分析。例如,流行的bug定位工具FindBugs在底层就使用了ASM来分析字节码并定位漏洞。另外一个常见的使用场景就是利用字节码重构生成程序的代码覆盖率报告,例如Emma(Roubtsov,2005)、JCover(JCover,2017)。Reflectall所生成的精简模型可转换为代码覆盖率报告。本实验将对比Reflectall与Emma在生成代码覆盖报告上的区别。由于基于字节码重构的方法不适合应用于仅有应用安装包的闭源应用。因此,这部分的仅针对开源应用集进行。本实现仍以应用启动时间作为指标,在两台硬件配置相同的红米2A手机上,分别部署了Reflectall和未修改的安卓系统,安卓操作系统均为5.1.1。本实验在部署了Reflectall的手机上安装原版应用;在未经修改的安卓手机上安装经过Emma插桩过的应用,在以下3个不同的实验场景,比较Reflectall与Emma生成开源应用集上生成代码覆盖报告的性能。在每个场景下,每个应用启动10次,实验的结果如图13所示。
实验结果表明,Reflectall与Emma的应用平均启动时间接近,平均启动时间为442毫秒和455毫秒,额外开销分别为13%和16%。但是从生成的代码覆盖信息上,Reflectall要比Emma更丰富。表4给出了Reflectall在代码覆盖报告的监测粒度和部署运行的区别。Emma对块覆盖的报告可能存在不准确,并且不支持分支执行次数,而Reflectall基于行为解释器可以保证覆盖报告的准确性,同时也实现了对分支指令(如If-gt、Packed-Switch)的各个分支的执行次数的统计。另一区别是Emma需要进行一定配置,以对字节码进行重构,并且重构之后需要重打包;而Reflectall不需要这些配置,也不会改变移动应用的编译流程。因此,Reflectall比Emma这类基于字节码重构的工具更具易用性和实用性。
表4:Reflectall与Emma的监测粒度对比
对比类别 | Emma | Reflectall |
类覆盖 | 支持 | 支持 |
方法覆盖 | 支持 | 支持 |
块覆盖 | 部分支持 | 支持 |
分支覆盖 | 部分支持 | 支持 |
行覆盖 | 支持 | 支持 |
分支执行次数 | 不支持 | 支持 |
指令覆盖 | 不支持 | 支持 |
是否需要字节码 | 需要 | 不需要 |
是否需要重打包 | 需要 | 不需要 |
验证对终端应用行为控制的有效性:
本发明以Reflectall模型所述的面向用户的图形界面接口转换为面向互操作的接口算法作为结构式控制的应用示例,在闭源应用集中的其中35个应用中进行实验。最后,给出一个基于行为式控制实现在不对原应用的代码进行修改的情况下,实现应用状态的实时修改的示例。
结构式控制示例
本发明示例在闭源应用集中的选取了35个应用,针对不同应用提供的功能,制定好了待开发的接口,总共150个互操作接口。接口的开发人员共计四名,Java开发经验1年至3年不等。在进行为期两周的Reflectall使用培训后,开始进行接口开发。各个应用对应的接口数量和工作量(以人天为单位)由于数量较大,暂不在本文列出。
从上述实验中,发现:
(1)互操作接口的开发时间由0.5人天至5天人不等。其中,工作量最大的那个接口是由于涉及了三个应用进程,执行逻辑较为复杂。
(2)对于同一个应用的不同功能,往往存在模式相似的现象,因此,对于同一个应用中,这些执行模式相似的接口,往往后续接口的开发工作量要远小于第一个接口的开发工作量。
(3)利用Reflectall所抽象的应用行为运行时模型,应用开发人员可以在完全不了解应用、没有文档和代码的情况,快速地实现互操作接口,每个接口的平均开发量为1.5人天。
行为式控制示例
如前文所述,行为式控制实现的是在不重启应用、不对应用进行重新编译,只基于运行时行为反射接口,就实现对原应用的监测与控制。在本发明示例中,以一个安卓计算器应用作为示例,详细介绍如何利用本文所实现的行为反射接口的行为式控制,实现对计算器使用过程中的表达式的记录和回放。
该计算器的原始类图如14(a)所示。类MainActivity表示计算器的主界面,在主界面中,除了一系列数字和计算符的按钮外,还有一个显示当前表达式的文本框CalculatorEditText(editText字段)。如果不基于本文所述的行为反射接口,而利用AspectJ将正交于计算器计算逻辑的表达式监测与修改逻辑在编译时编织进原应用,需要做如下的修改:
开发人员需要实现一个监控类ExpressionRecorder,并在MainActivity中新增一个字段,为ExpresionRecorder类型。要实现对运行时表达式的监测,开发人员需要定义一个切面:该切面为editText的onTextChanged方法的执行前;在该切面执行的逻辑为,记录onTextChanged方法的参数。对所记录的序列进行整理,由于onTextChanged在每一次表达式变化时都会被调用,例如,输入1+1时,该方法会被调用3次。为了便于显示,开发人员需要实现将监测到的多个表达式进行子串的筛选,实现表达式重复子串的过滤。即,对于某个表达式,如果是另一个表达式的子串,则过滤。在记录了onTextChanged所执行的方法后,开发人员通过理解代码,在ExpressionRecorder中编写一个调用editText的setText方法,实现对界面上表达式状态的修改。最后,对ExpressionRecorder所记录的表达式进行可视化,三个按钮,分别起到开始记录表达式、停止记录表达式和将当前表达式修改为之前记录的某个表达式的功能。
在进行上述的修改后,开发人员将AspectJ的编译流程配置至原应用的编译流程中,进行编译并生成新的计算器应用。修改后的类图如14(b)所示。可以发现,开发人员定义的切面实际上引入的是CalculatorEditText与ExpressionRecorder的耦合——在CalculatorEditText的执行流程中,添加了将表达式记录到ExpressionRecorder的逻辑。
而基于本发明控制提供的行为反射接口,只需要做如下修改:
开发人员需要实现一个监控类ExpressionRecorder。要实现对运行时表达式的监测,只需要调用行为反射接口的模型生成功能,其中配置好类筛选器为CalculatorEditText。筛选模型中的活动,实现表达式重复子串的过滤。通过模型的Add操作,添加一个setText的活动,实现对界面表达式的修改功能。最后,对ExpressionRecorder所记录的表达式进行同上的可视化。
基于本发明所述的远程接口,对应用运行时状态的监测和修改的运行逻辑不需要与原应用运行于同一进程中,因此,该部分逻辑可以剥离出来,单独编译为一个原计算器应用的插件。
相比本发明所述的方法,基于AspectJ的方法需要:
第1步,修改原应用,新增加MainActivity与ExpressionRecorder的耦合。
第2步,修改源应用的编译流程,将正交于原应用的业务功能的代码编织到原应用中。
实际上,第2步中也就意味着在代码中建立CalculatorEditText与ExpressionRecorder的耦合。而本发明的实现方式,则是利用行为反射接口,真正地把ExpressionRecorder实现的监测逻辑与原应用的功能剥离开来,耦合程度更低。同时,利用本发明所述的行为反射接口的开发流程会更简单。
综上,本发明在终端应用行为控制方面,基于Reflectall指令级的控制,成功的将微博、京东在内的复杂的闭源应用中150个面向用户的功能自动化地转换为应用可编程接口,总共耗时225天,平均耗时1.5人/天,展示了Reflectall在实现移动应用按需互操作上的有效性。
针对终端应用行为反射的两大挑战,本发明提出一套基于运行时模型的应用计算反射技术框架:该框架的核心思想在于通过实现一个行为解释器,生成一个完整、准确、详实的应用行为自述(即应用行为运行时模型)。通过定义模型上的操纵,建立应用行为运行时模型与应用本身的因果关联,实现指令粒度的行为反射。在本发明中,所提出的技术框架包括一种构造终端应用行为的运行时模型的方法和一种基于运行时模型实现对终端应用控制的方法。最后,本发明还实现了一个支持安卓应用的计算反射引擎。
首先,针对现有技术对终端应用应用行为在监测上的不足,本发明提出一种将应用行为抽象为应用行为运行时模型的方法。应用的执行可视为编程语言框架(例如解释器、虚拟机),根据应用的代码段,对内存进行读写操作。什么样的方法被执行,可对应为编程语言框架在栈上的操作;什么样的对象数据被修改,则可对应为编程语言框架对堆上的操作。本发明分别对编程语言框架在堆、栈上的操作的特点进行建模,并详细介绍了如何通过实现行为解释器的方式,构造该模型。
其次,针对现有技术在应用行为的控制上的不足,本发明提出一种基于运行时模型实现对终端应用控制的方法。通过定义行为模型上的操作、以及定义行为模型片段在堆、栈区域影响的等价性,实现了复杂的应用行为模型的分解,并基于分解的结果,分别建立行为模型与应用状态、行为模型与应用代码的因果关联,实现运行时指令级别的应用行为控制。
最后,本发明设计并实现了一个支持安卓应用的计算反射引擎。该计算反射引擎可以在不对既有安卓应用的字节码作任何修改的情况下监测应用运行时的行为,并且能实现指令级别的应用行为控制。该计算反射引擎可作为一个单独的运行环境使用,也可被集成到各种主流的开发平台或商业软件中,给开发者提供对应用运行时监测和控制的基本能力。
上述实施例仅为优选实施例,并不用以限制本发明的保护范围,在本发明的精神和原则之内所作的任何修改、等同替换和改进等,均应包含在本发明的保护范围之内。
Claims (10)
1.一种终端应用行为反射的处理方法,其特征在于,所述方法包括:
构造所述终端应用的运行时模型,所述运行时模型包括运行时栈模型和运行时堆模型;
基于所述运行时模型,对所述终端应用的应用行为进行控制;
其中:
构造所述终端应用的运行时栈模型的子步骤包括:
在所述终端应用运行时,获取所述终端应用的内存中真正执行的代码,并对所述真正执行的代码进行抽象,生成控制流图;
针对所述控制流图,将需要监测的控制流图输入至预设的行为解释器;
利用所述行为解释器对所述需要监测的控制流图进行解释执行,生成所述终端应用运行时的栈活动;
在所述终端应用运行时,生成所述栈活动的控制流间的依赖关系,得到所述终端应用行为的运行时栈模型;
构造所述终端应用的运行时堆模型的子步骤包括:
在所述终端应用运行时,生成堆区的初始状态;
生成堆操作活动,得到所述终端应用行为的运行时堆模型;
基于所述运行时模型,对所述终端应用的应用行为进行控制的子步骤包括:
预定义对所述运行时模型的操作以及模型片段在堆、栈区域影响的等价性;
对所述运行时模型保持所述等价性进行转换,对所述运行时模型进行分解,得到一组可操作的模型片段;
根据分解后的模型片段,建立所述运行时模型与应用状态和应用代码的因果关联,以对所述终端应用行为进行控制。
2.如权利要求1所述的方法,其特征在于,构造所述终端应用的运行时模型前,所述方法包括:
确定类筛选器和活动类型筛选器;其中,所述类筛选器基于包和类名正则匹配的粗粒度筛选,用于去除开发人员不关心的程序活动;所述活动类型筛选器基于活动类型的细粒度筛选,用于去除与开发者不关心的活动类型。
3.如权利要求2所述的方法,其特征在于,所述栈活动的活动类型包括方法开始与方法结束,字段读,数组读和同步指令;
利用所述行为解释器对所述需要监测的控制流图进行解释执行,生成所述终端应用运行时的栈活动的子步骤进一步包括:
利用对所述终端应用的应用行为具有监测功能的行为解释器对所述需要监测的控制流图进行解释执行,获得所述终端应用运行时的活动;
根据所关注的类,利用所述类筛选器对所述终端应用运行时的活动进行粗粒度筛选,生成由所述类引起的栈活动;
针对所述栈活动的活动类型,利用所述活动类型筛选器对所述栈活动进行细粒度筛选。
4.如权利要求2所述的方法,其特征在于,所述堆操作活动的活动类型包括对象实例化,数组实例化,对象字段写,数组元素写,清除活动和压缩活动;
所述生成堆操作活动的子步骤进一步包括:
根据所关注的类,利用所述类筛选器对所述终端应用运行时的活动进行粗粒度筛选,生成由所述类引起的堆操作活动;
针对所述堆操作活动的活动类型,利用所述活动类型筛选器对所述堆操作活动进行细粒度筛选。
5.如权利要求1所述的方法,其特征在于,所述依赖关系包括同步依赖和通信依赖。
6.如权利要求1所述的方法,其特征在于,对所述终端应用行为进行控制包括对所述终端应用行为进行行为式控制和/或结构式控制。
7.如权利要求6所述的方法,其特征在于,对所述终端应用行为进行行为式控制的步骤包括:
设终端应用的内存初始状态S,活动序列A;
经过一段时间的执行,获取运行后的内存状态为S·A=S',对所述运行时模型保持所述等价性进行转换后的活动序列为A',得到目标状态为S·A′=S″;
利用S·A′=(S·A)·A″=S′·A″求解增量活动序列A″,使得A+A″=A',完成对所述终端应用行为进行行为式控制的过程。
8.如权利要求7所述的方法,其特征在于,所述增量活动序列A″包括:控制转移、算术运算、字段读取、数组读取、类实例化、数组实例化,字段赋值、数组赋值、线程同步、垃圾回收、方法调用和类加载。
9.如权利要求6所述的方法,其特征在于,对所述终端应用行为进行结构式控制的步骤包括:
获取终端应用的运行时模型M,一组操作以及一个转换后的活动序列A';
将所述一组操作映射为一段目标代码,以使所述目标代码的执行所产生的活动序列A″与活动序列A'保持控制等价关系。
10.如权利要求9所述的方法,其特征在于,将所述一组操作映射为一段目标代码的步骤包括:
获取数据依赖,使得所述活动执行的数据依赖与活动发生时刻相同;
将所述活动转换为相应的目标代码。
Priority Applications (2)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN201910498200.7A CN110362301B (zh) | 2019-06-10 | 2019-06-10 | 一种终端应用行为反射的处理方法 |
PCT/CN2019/119270 WO2020248510A1 (zh) | 2019-06-10 | 2019-11-18 | 一种终端应用行为反射的处理方法 |
Applications Claiming Priority (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN201910498200.7A CN110362301B (zh) | 2019-06-10 | 2019-06-10 | 一种终端应用行为反射的处理方法 |
Publications (2)
Publication Number | Publication Date |
---|---|
CN110362301A CN110362301A (zh) | 2019-10-22 |
CN110362301B true CN110362301B (zh) | 2021-04-09 |
Family
ID=68216828
Family Applications (1)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN201910498200.7A Active CN110362301B (zh) | 2019-06-10 | 2019-06-10 | 一种终端应用行为反射的处理方法 |
Country Status (2)
Country | Link |
---|---|
CN (1) | CN110362301B (zh) |
WO (1) | WO2020248510A1 (zh) |
Families Citing this family (5)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN110362363B (zh) * | 2019-06-10 | 2021-03-12 | 北京大学 | 一种基于运行时模型实现对终端应用控制的方法 |
CN110347448B (zh) * | 2019-06-10 | 2021-02-12 | 北京大学 | 一种构造终端应用行为的运行时模型的方法 |
CN110362301B (zh) * | 2019-06-10 | 2021-04-09 | 北京大学 | 一种终端应用行为反射的处理方法 |
CN110888773B (zh) * | 2019-10-28 | 2023-06-06 | 北京字节跳动网络技术有限公司 | 一种获取线程标识的方法、装置、介质和电子设备 |
CN113434190B (zh) * | 2021-06-30 | 2023-06-16 | 青岛海尔科技有限公司 | 数据处理方法和装置、存储介质及电子设备 |
Family Cites Families (6)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
US6550058B1 (en) * | 2000-02-03 | 2003-04-15 | International Business Machines Corporation | Stack clearing device and method |
EP1752874A1 (en) * | 2005-07-19 | 2007-02-14 | Alcatel | Adaptive evolutionary computer software product |
CN109189374B (zh) * | 2018-06-22 | 2020-08-28 | 北京大学 | 基于对象引用链的对象构造代码生成方法及系统 |
CN109240666B (zh) * | 2018-06-22 | 2020-08-25 | 北京大学 | 基于调用栈和依赖路径的函数调用代码生成方法及系统 |
CN109189469B (zh) * | 2018-06-22 | 2020-08-28 | 北京大学 | 基于反射的安卓应用微服务化方法及系统 |
CN110362301B (zh) * | 2019-06-10 | 2021-04-09 | 北京大学 | 一种终端应用行为反射的处理方法 |
-
2019
- 2019-06-10 CN CN201910498200.7A patent/CN110362301B/zh active Active
- 2019-11-18 WO PCT/CN2019/119270 patent/WO2020248510A1/zh active Application Filing
Also Published As
Publication number | Publication date |
---|---|
CN110362301A (zh) | 2019-10-22 |
WO2020248510A1 (zh) | 2020-12-17 |
Similar Documents
Publication | Publication Date | Title |
---|---|---|
CN110362301B (zh) | 一种终端应用行为反射的处理方法 | |
CN110362363B (zh) | 一种基于运行时模型实现对终端应用控制的方法 | |
US10635570B2 (en) | Memory leak profiling events | |
EP2671161B1 (en) | System and method for determining an objects lifetime in an object oriented environment | |
Barr et al. | Jist: Embedding simulation time into a virtual machine | |
WO2009082382A1 (en) | Automated model generation for computer based business process | |
WO2008064899A2 (en) | Parallelization and instrumentation in a producer graph oriented programming framework | |
Kounev et al. | The descartes modeling language | |
CN109240666B (zh) | 基于调用栈和依赖路径的函数调用代码生成方法及系统 | |
US20140047222A1 (en) | Method and device for recombining runtime instruction | |
Satoh | A framework for data processing at the edges of networks | |
Johnsen et al. | Dynamic resource reallocation between deployment components | |
CN115968469A (zh) | 用于可共享应用快照的编译策略 | |
CN110291508A (zh) | 垃圾收集器 | |
CN110347448B (zh) | 一种构造终端应用行为的运行时模型的方法 | |
US10185647B2 (en) | Debugging remote vertex code on test machine | |
US20200356384A1 (en) | Intelligently determining a virtual machine configuration during runtime based on garbage collection characteristics | |
Satoh | MapReduce-based data processing on IoT | |
US8490115B2 (en) | Ambient state for asynchronous methods | |
Simão et al. | A checkpointing‐enabled and resource‐aware Java Virtual Machine for efficient and robust e‐Science applications in grid environments | |
Wang | Thin serverless functions with graalvm native image | |
Ehlers | Self-adaptive performance monitoring for component-based software systems | |
Tasneem et al. | Android memory optimization | |
Satoh | Mapreduce processing on IoT clouds | |
Royer et al. | The Tiny Java Library for Maintaining Model Provenance |
Legal Events
Date | Code | Title | Description |
---|---|---|---|
PB01 | Publication | ||
PB01 | Publication | ||
SE01 | Entry into force of request for substantive examination | ||
SE01 | Entry into force of request for substantive examination | ||
GR01 | Patent grant | ||
GR01 | Patent grant |