一个基于Eclipse的通用Java程序插桩工具郑晓梅Word文件下载.docx
- 文档编号:19345167
- 上传时间:2023-01-05
- 格式:DOCX
- 页数:20
- 大小:32.58KB
一个基于Eclipse的通用Java程序插桩工具郑晓梅Word文件下载.docx
《一个基于Eclipse的通用Java程序插桩工具郑晓梅Word文件下载.docx》由会员分享,可在线阅读,更多相关《一个基于Eclipse的通用Java程序插桩工具郑晓梅Word文件下载.docx(20页珍藏版)》请在冰豆网上搜索。
通过插桩可以收集程序在执
行过程中某一时刻的系统状态(快照)信息。
比如,通过插桩
收集某次执行的路径覆盖信息[1];
还可以通过插桩收集函数
调用图,并以此来验证某个属性(如函数调用关系、调用顺序
是否符合一定的规则)。
目前利用插桩技术的常用方法是:
对
特定的应用编制一个特别的插桩程序,将代码直接插入到源
程序中。
比如文献[2,3]等就是根据具体应用单独各自编写
插桩代码。
然而这样做往往会导致一次次的重复开发,无谓
地浪费了时间和人力;
另外,插桩代码充斥在整个逻辑代码
中,也非常影响开发人员对程序逻辑的把握。
那么,能否有一
种较为通用的插桩手段和工具,来支持目前测试、分析及验证
等技术对于程序插桩的不同需求,又可以对所植入的插桩代
码进行有效管理呢?
这正是本文所要解决的问题。
本文提出了一种通用的Java程序插桩方法,并基于E-
clipse开发了相应的支撑工具EStrumentJ(EclipseInstrument
forJava),使Java程序的开发人员能够通过该工具为测试、分
析以及验证等不同的需求定制相应的插桩实现。
同时,该工
具还可以对源程序和所植入的代码片段进行显式隔离与合
并。
用户既可以只关注源程序本身(这样就可以有效屏蔽所
植入代码对源程序理解上的干扰),同时可以清晰地看到植入
点的位置及相应的提示信息,从而对植入代码如何影响源程
序有一个明确的把握。
针对不同的插桩需求,用户还可以在
稿日期:
2010-08-15 返修日期:
2010-11-22 本文受863国家高技术研究发展计划(2007AA010302),国家自然科学基金(60425204),江苏
省自然科学基金(BK2007714),江苏省高校自然科学基金(07KJB10002)资助。
郑晓梅(1978-),女,讲师,主要研究方向为医学信息安全、中医特征信息建模与分析验证等,E-mail:
zxmluck@。
EStrumentJ中定义匹配规则,用来捕获程序的特定执行点,
并在该执行点处插入代码片段。
与其它工具显著的不同点是,EStrumentJ是基于Eclipse
官方插件JDT(JavaDevelopmentTool)所提供的Java抽象语
法树实现程序执行点的定义,因此具有更细的粒度,不但可以
匹配方法调用点,甚至可以匹配源程序中循环和分支的内部
位置。
本文首先简要介绍EStrumentJ的设计思想,然后给出底
层插桩引擎的系统结构,同时说明插桩引擎的实现方法,之后
进一步介绍基于底层插桩引擎的上层手工插桩和基于规则匹
配的批量自动插桩插件的实现,最后将我们的工作与其他相
关工作进行了比较,并指出本文的贡献及未来工作的方向。
2 设计思想
2.1 关注点分离
在传统的编程情况下,代码中经常会充斥着很多与业务
功能无关的代码,比如安全控制、记录日志等。
其实这些关注
点与软件的业务功能并没有直接的关系,它们可以说是正交
于业务逻辑功能的。
这些与业务功能无关的代码数量很多,
经常会“淹没”了业务功能代码,使维护人员不易清晰地看出
原开发人员的设计意图。
AOP[4]改变了这一切,它的思想就
是要把独立于功能的软件模块从软件开发中独立出来,使程
序员不必关心业务逻辑以外的事情,比如安全需求、日志要求
等。
这使得程序看上去很“干净”。
我们把这种“干净”的思想融入到我们的设计中。
对于有
着如此广泛需求并且与业务无关的插桩技术,也应该像安全
控制一样从软件开发中剥离出来,成为一个独立的关注点。
对于这样一个关注点,需要提供一个基础设施,使这个关注点
能够得到发挥。
我们的做法是将这个关注点实现为一个E-
clipse插件,作为Eclipse的一个基础设施,随时能够供程序员
使用。
我们这个关注点相对于安全控制、日志等关注点又是
有区别的,其所插入的代码都会被编译进发布的软件中。
而
这个关注点的代码只出现在开发阶段,发布出去的软件不包
括这些插入的代码。
2.2 无痕
我们认为,计算机应该像文字一样无痕地嵌入到生活的
方方面面,无需刻意地注意那些计算机的存在。
事实上,我们
确实淹没在其中,无时无刻不在使用它。
它是我们手上随时
可用的工具,不管你是否需要,它就在那里。
我们将无痕的想
法也融入到我们的设计思想中。
要做到无痕,在插桩的时候,
不应该给程序员造成不必要的困扰,插桩的代码对程序员来
说应该不可见。
同时,要使程序员能够感知一个插桩点的存
在,程序员必须能够确切知道在什么地方会有什么样的插桩
动作将会进行,这才不会使程序员感到意外。
我们选择了E-
clipse提供的标记(marker)设施,来达到这个目的。
标记是
Eclipse为插件开发者提供的一种可以出现在编辑器左侧标
尺上的控件,这个控件与一个文件的某一行关联,并能携带一
些可持久化的属性,这些属性可用来传递程序员的插桩意图。
2.3 强扩展性
如同很多软件(比如vim[5],emacs[6])一样,Eclipse有很
好的扩展机制。
这使得Eclipse不会局限于开始的一些功能,
不会局限于某一种编程语言,也不会局限于一种编程方式;
这
使得Eclipse可以不断成长,它可以利用第三方开发的插件来
扩充自身的功能。
我们开发的插桩引擎也采用了同样的思想:
只将一个最
小的核放在引擎里面。
这个核只提供编译执行插桩程序的基
础设施。
利用Eclipse提供的插件机制,我们对外提供了一个
扩展点,这个扩展点定义了插桩动作,该动作被插桩引擎调
用。
这样,更多的动作细节通过提供扩展点的方式,由外来的
插件完成。
这样插件通过扩展这个扩展点,增加不同的插桩
动作。
下一部分开始介绍这个插桩引擎的系统结构。
3 系统结构
我们设计的系统结构如图1所示。
图1 无痕插桩系统框架结构图
利用Eclipse插件机制,构建了一个多层次的系统结构。
利用Eclipse的现有插件,构建了一个引擎核,这个引擎核并
不进行特定的插桩动作,只提供一个编译执行的基础设施。
在这个引擎核之上,可以定义一些插桩插件,这些插件通过扩
展引擎核提供的一个扩展点来实现自己特定的插桩需求。
些扩展了引擎核的插件同样可以继续被扩展,也可以直接与
最终用Eclipse进行日常开发的程序员交互。
插桩引擎核可
以同时支持多种插桩需求,当对一个文件执行完所有的插桩
以后,再将不同的插桩动作通过diff-patch的方式合并到一
起。
用户也可以通过配置,使插桩引擎只处理一种(或若干
种)插桩动作,如图2所示。
图2 插桩引擎配置
被选中的标记类型所表示的插桩需求将被满足。
未被选
中的需求,即使在程序中添加了相应的插桩标记,也不会起作
当用户试图执行该被插桩程序时,插桩引擎就收集用户
对原程序的插桩意图(标记),根据用户对原程序的插桩及配
置来对原程序进行插桩,然后保存Eclipse为原程序生成的
class文件,编译插桩后的代码。
执行插桩后的代码,待该被
插桩程序执行完毕后,再将保存的class文件恢复到原先的地
方。
如果编译出错,则直接将保存的class文件恢复到原处,
并在编辑器中向程序员展示编译出错的地方。
下面一部分就
对该插桩引擎的实现做一个详细的介绍。
4 插桩引擎的实现
4.1 插桩引擎的工作流
该引擎的工作分成4部分:
1)插桩点的定义与显示;
2)实
现插桩动作;
3)被插桩程序的编译与执行;
4)被插桩程序编译
出错时的处理。
下面逐一介绍这4个方面的功能。
我们要求插桩点的定义是,它们不应该被硬编码到引擎
中,引擎扩展者(ED)可以自定义自己的插桩要求;
同时,引擎
·
140·
必须能够感知到引擎扩展者的插桩意图,这样引擎才能在编
译的时候执行相应的处理动作。
另外,考虑到最终Eclipse使
用者(EU)可能希望在下次打开工程时,上次所设置的插桩效
果依然存在,不需要再次重新设置插桩,我们需要保存它们所
设置的插桩点,因此用一个标记(marker)来标识一个插桩点。
标记是Eclipse开发环境提供的一种资源,可与一个文件的某
一行进行关联,标记的类型决定了不同的插桩类型。
插桩引
擎通过查找资源方式获取一个文件中所有的标记,进而得到
所有的插桩点。
标记将会出现在集成开发环境编辑区左侧的
标尺上,为不同的插桩标记赋予不同的提示信息,以使使用者
能够了解当时设置插桩点时的意图。
将该标记设置为可持久
化的,使插桩信息能够在不同的会话之间传递。
引擎工作流
如图3所示。
图3 引擎工作流
与插桩点的定义一样,我们也希望插桩动作不要固化在
引擎中,这个动作应该交由引擎的扩展者来实现。
为了达到
这样的效果,我们在引擎中声明了一个新的扩展点。
不同类
型的插桩动作都需要扩展这个扩展点。
引擎的扩展者在扩展
引擎时,必须实现一个接口IInstrumenter,该接口声明了如
下3个方法:
publicStringgetMarkerLabel();
publicStringgetUtilities(IProjectproject);
publicvoidinstrumentFile(StringfromFile,StringtoFile,IMarker[]
markers);
其中的getMarkerLabel方法返回图2中显示的标记类
型名字。
instrumentFile方法利用文件中包含的标记(mark-
ers)对fromFile进行插桩,插桩结果被输出到toFile文件中。
具体的插桩动作留给了扩展者。
不同的扩展者可能需要定义
这样一些方法,即能够在被插桩的程序中使用。
getUtilities
方法为他们提供了一个机会,让其能够提供一些方法,作为该
函数的返回值,在后面的实例中将看到这个方法的使用。
插桩引擎现在只支持JavaApplication的编译与运行。
通过扩展org.eclipse.debug.core.launchConfigurationTypes,
增加了一种新的执行类型,该扩展点的代理类直接继承org.
eclipse.jdt.launching.JavaLaunchDelegate。
在launch方法
中,先将原程序的class文件保存起来,以避免以后因为再次
编译而花费额外的时间。
编译好被插桩后的程序以后,再调
用父类的launch方法运行程序。
为了编译被插桩后的程序,
我们使用了Eclipse的jdt插件的内部编译器(org.eclipse.
piler.batch.Main1))。
为了配置运行时的参
数,我们使用了与普通javaapplication同样的参数配置界面。
另外,为了使被插桩程序执行完毕后不会影响原程序,达到无
痕的目的,我们增加了一个回调函数。
该回调函数在被插桩
程序运行完后,将原先保存的原程序的class文件再复制回
去。
程序在编译时难免会出错,我们为出错的编译提供了两
种信息输出方式:
直接显示和记录日志。
为了捕获编译出错
的信息,我们在调用piler.batch.
Main的编译方法(booleancompile(StringcommandLine,
PrintWriteroutWriter,PrintWritererrWriter))时给了一个
errWriter参数,该参数指示编译器将出错信息定向到该流
(stream)。
为了将编译出错的信息立刻显示在用户界面上,
我们定义了一种新的标记,使其继承org.eclipse.jdt.core.
problem,并在每个标记上提供一些简单信息,以提示开发者
(EU)编译错误的位置。
在标记上标示的信息是有限的,因为
不太适合将过于详细的信息(比如被插桩的代码)显示在标记
上,而且程序员有时也需要完整的信息来发现问题,所以需要
另外的方式向他们展现完整的出错信息,这些信息就被记录
在日志文件中。
在插桩过程中,插桩后的文件的行数可能会
发生变化,这就需要解决一个问题:
如何正确地定位错误。
里,我们通过标记的属性(extralines)来记录该标记所代表的
插桩代码需要占用的额外行数。
在出错时,通过计算得到被
插桩后的程序中的行所对应的源程序的行数。
引擎扩展者负
责为自己的标记赋予extralines属性,如果没有赋值,则认为
该插桩动作没有占用额外的行。
4.2 插桩引擎提供的扩展点
为了使引擎的扩展者能够定义一种插桩动作,我们的插
桩引擎提供了一个扩展点instrument:
〈!
ELEMENTextension(instrumenter)〉
ATTLISTextension
pointCDATA#REQUIRED
idCDATA#IMPLIED
nameCDATA#IMPLIED〉
ELEMENTinstrumenterEMPTY〉
ATTLISTinstrumenter
classCDATA#REQUIRED
markertypeCDATA#REQUIRED〉
从上面的定义可以看出,最重要的是instrumenter项,该
项提供了两个域:
class和markertype。
class域需要引擎扩
展者提供一个实现IInstrumenter接口的类,而markertype
是一种标记类型的id,该标记是扩展者用来标识插桩点的,因
此该标记就使引擎能够查找一个文件里面的所有插桩点。
一
个新的插桩需求就可以通过扩展该扩展点来实现。
下面两部
分介绍两个实例,这两个实例利用这个插桩引擎开发了两个
实用工具。
5 基于插桩引擎的手动与自动插桩
5.1 手动插桩
随着计算机技术的日益发展,对称多处理、多核等新技术
不断涌现。
为了充分利用这些新技术,多线程、多进程等程序
范性也经常被人们使用。
多线程程序一般逻辑比较复杂,很
难一次性开发出没有逻辑错误的软件,这就需要对程序进行
调试。
但是普通的调试办法(设置断点-执行到断点-检查状
态)并不能适应多线程程序:
多线程程序的本质是多个执行体
同时执行,如果让其中的某一个线程暂且挂起,不能反映真实
的执行环境。
另外,由多个线程组成的进程,每次执行都是一
个特殊的各个线程之间的交错实例。
一旦让线程因为非程序
自身的原因而挂起,它就不能反映当时那一次真实的执行轨
迹。
调试时最好的办法是不要挂起线程,而是使其一直执行。
可以使用最原始的调试手段:
插桩调试,即在需要检查状态的
地方插入一段代码,用以显示或捕捉那个时刻的系统状态。
然而不断地向工程里面“加入代码-调试-修改代码-删除代码”
是一件相当费时、费力的事情,如果需要监测状态的地方太
多,也会导致管理很混乱,程序员自己有可能都弄不清楚在哪
些地方增加了一些代码来监测状态。
为了减轻程序员的工作
量和维护效率,可以利用本文涉及的无痕插桩引擎来提供一
个调试助手。
通过这个调试助手,程序员不必显示向程序中
添加代码,而只要在某些感兴趣的地方设置一些标记(就如同
设置断点一样),并赋予这些标记一定的属性,来表达程序员
监测系统状态的意图。
5.1.1 定义扩展
通过扩展上一节中描述的插桩框架提供的扩展点(in-
strument)来实现上面的调试助手所需要的插桩要求,扩展定
义如下:
〈extensionpoint="
cn.edu.nju.seg.debug.assist.ui.engine.instru-
ment"
〉
〈instrumenter
class="
cn.edu.nju.seg.debug.assist.ui.watchpoint.
WatchPointInstrumenter"
markertype="
cn.edu.nju.seg.debug.assist.ui.watch-
pointmarker"
〈/instrumenter〉
〈/extension〉
在这个定义中,WatchpointInstrumenter类实现了接口
IInstrumenter。
而watchpointmarker标记提供了另外一些设
施:
插桩点的显示、插桩信息的保存和持久化。
在该调试助手
中,我们提供了两种插桩设施:
表达式求值与原生代码。
程序
员(EU)可以在原程序的某一行(在该行之前)检查某一个表
达式的值(包括简单变量),表达式之间用分号作为分隔符。
有时候,程序员可能有更复杂的需求。
这时候,他们还可
以通过直接在某一行(同样,在该行之前)插入一段java代
码,该代码会被原封不动地插入,我们称之为原生代码。
5.1.2 标签的生成
本插件扩展了org.eclipse.ui.popupMenus扩展点,以#
CompilationUnitRulerContext为TargetID,即右击编辑器左
侧的标尺,在出现的菜单中选择“EyeWatch…”对插桩点进行
编辑,如图4所示。
图4 调试助手示意图
插入的表达式与原生代码将在程序运行之前被插入到原
文件中,与原文件一起进行编译。
5.2 基于规则的批量自动插桩
在测试和验证中,要插桩的地方非常多,不可能像上面的
手动插桩一样,手工地在需要监测系统状态的地方插入插桩
点。
这需要一个能够理解程序员(EU)的意图,从而进行批量
插桩的设施。
为此,可以利用前面的插桩引擎开发一个基于
规则的批量插桩工具。
程序员提供一定的规则,然后由这个
插桩工具根据这些规则对程序员希望插桩的代码进行插桩。
5.2.1 规则定义
我们的批量插桩工具是基于规则的,就是说由程序员给
出插桩规则,然后我们的插桩工具完成插桩,最后交由引擎实
现插桩要求。
规则的BNF如下:
rules∷=EMPTYrule'
n'
rules
rule∷=propertyruleinitrulecontrolrule
propertyrule∷=property'
:
'
properties
properties∷=EMPTYproperty;
property=IDENTIFIER'
('
TYPE'
)'
initrule∷=INIT'
ACTION
controlrule∷=controls'
controls∷=controlcontrol'
'
controls
control∷=METHODENTRYMETHODEXITMETHOD
RETURNIFFORWHILEBREAKCONTIN-
UEFORBREAK
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 一个 基于 Eclipse 通用 Java 程序 工具 郑晓梅