<p style="text-indent: 21pt;">在写这个自动化测试框架的时候,我一直在留意各方面的需求。毕竟,我本人并没有做过真正的自动化测试。管理测试方面的领导,提出一个需求,就是在用例运行失败的时候,应该将过程记录下来,并形成报告,Email给相关人员。</p>
<p style="text-indent: 21pt;">个人认为这个需求是非常合理的。事实上,任何系统,如果没有输出,那么只能停留在程序员手里。有了报表,才叫真正解决了用户的目标需求。</p>
<p style="text-indent: 21pt;">在分析这个需求的过程,我提出了针对每一个操作接口的每一个方法,进行Log。而完成这个工作的第一方法,就想到了AOP,也就是Hook技术的应用。因为Delphi下面并没有对AOP的直接支持,所以考虑这个实现,变成了一个技术研究过程。</p>
<p style="text-indent: 21pt;">从技术上讲,本篇博客只适合了解VCL的Delphi程序员阅读。但其间的思想,相信大家都可以借鉴。下面我的描述过程,是以我的探索过程来进行讲述的。中间会带出相关技术点,供大家参考。</p>
<p style="text-indent: 21pt;">第一、接口的方法,是由类来实现的。框架中,已经对所有支持的类都进行了登记。那么,只需要在这些类型中,找到所有实现的接口的所有方法的地址,那么Hook就变得有可能了。</p>
<p style="text-indent: 21pt;">TObject有一个方法:GetInterfaceTable,可以获取所有接口列表。所有非常容易找到接口对应的VTable。VTable在Delphi中并没有明确的注释,但是可以知道VTable是一个指针列表,每一项都记录着一个方法的“实现地址”。</p>
<p style="text-indent: 21pt;">可惜的是,我发现VTable本身并没有告诉你,这个接口有多少个方法!另外,你也不能得到每一个方法的名称,以及参数等等描述。</p>
<p style="text-indent: 21pt;">第二、于是我考虑到接口的RTTI。接口的RTTI,我以前是没有使用过的。通过VCL的代码研究,发现接口中有一个非常特殊的接口定义:{$M+}IInvokable = interface;{$M-}。这个接口本身并没有添加什么服务,只是使用了编译指令M,来使得接口拥有了RTTI。</p>
<p style="text-indent: 21pt;">实现的时候,可以通过从IInvokable派生,或者直接添加编译指令,从而获得RTTI服务。</p>
<p style="text-indent: 21pt;">下面的问题是,如何使用RTTI?我们知道,Delphi中有一个单元叫TypInfo.pas,后来我发现,其实有另外一个单元叫:IntfInfo.pas。这里面有一个方法GetIntfMetaData可以帮助你获得RTTI。另外,值得一提的是,获取接口类型的PTypeInfo的方法是调用TypeInfo(IMyInterface);</p>
<p style="text-indent: 21pt;">第三、通过MetaData分析,我们可以知道接口的方法个数以及每一个方法的详细定义。那么,现在就是如何Hook了。下面是一个Object的对象实例事例图。</p>
<p style="text-indent: 21pt;"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xiammy/267259/o_IntfVTable.jpg"></p>
<p style="text-indent: 21pt;">做左边,有Self标识的是对象的实例数据块。某一个几口指针IMyInterface指针,指向了一个VTable。而VTable中的每一个Method,都指向了一段代码,这段代码的前一部分,是为了计算EAX(保证将IMyInterface的地址,偏移到Self所在地址)。</p>
<p style="text-indent: 21pt;">分析上面的结构,再实际在CPU窗体中,调试以下接口方法的调用过程,发现,必然和Method地址有关。因此,Hook的目标,就非常自然地变成修改VTable中的Method1的地址值。</p>
<p style="text-indent: 21pt;">第四、如何修改代码?这里建议大家学习以下FastCode代码。简单一点,就是通过调用VirutalProtect方法,修改代码段中内存的访问属性,然后修改地址,最后再恢复回去。</p>
<p style="text-indent: 21pt;">显然在Hook之前,必须声明新的函数。</p>
<p style="text-indent: 21pt;">第五、新的函数并不是那么好声明的。关注一下,接口函数的调用代码,你会发现很多问题。下面举一个简单的例子。</p>
<p style="text-indent: 21pt;">IMyInterface = interface(IInvokable)</p>
<p style="text-indent: 21pt;"> procedure AAA;</p>
<p style="text-indent: 21pt;">end;</p>
<p style="text-indent: 21pt;">假设TMyIntfImpl类实现了上面的接口。那么oIntfObj: IMyInterface声明的对象,oIntfObj.AAA;的汇编代码是如下的样子:</p>
<p style="text-indent: 21pt;"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xiammy/267259/o_intfcall1.jpg"></p>
<p style="text-indent: 21pt;"><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xiammy/267259/o_intfcall2.jpg"></p>
<p style="text-indent: 21pt;">上面是两段代码的图片,其中[dex+$0c]指的就是$004661FD,也就是第二段代码图片的首地址。大家可以再联系一下上面的示意图理解一下地址的关键。</p>
<p style="text-indent: 21pt;">好,言归正传。这里注意一下,我们要修改的是[dex+$0c]里面的值。但是由于这个是call过去的。所以在call之前,会在堆栈中压入函数返回地址。另外,在调用函数之前,还有函数参数的准备。比如说Self指针的传入到EAX中,如果本身方法还有参数的话,可能占用其他寄存器或者堆栈。</p>
<p style="text-indent: 21pt;">由于我们要求是Hook住所有的方法,并且所有方法的参数类型并不一定一样。所以在call之前的代码,是无法预计的。所以在新的函数中,必须考虑如何做到保存寄存器和做到ret时候的栈平衡。</p>
<p style="text-indent: 21pt;">通过我的实践,我的做法是通过先弹出当前的ret地址,保存到一个数据区中。等待调用完原先的代码后,再压栈。而调用Writelog的时候,先保存寄存器,调用完了之后,再恢复寄存器。这是因为寄存器也可能是返回值的地方。而且后续代码有可能优化使用。</p>
<p style="text-indent: 21pt;">第六、完成了汇编的编写,还有一个问题,那就是由于每一个函数的原地址不一样,所以必须为每一个函数,定义一个代理函数。由于这些函数的地址和个数都是未定的,所以,这里就必须要用到动态创建代码。</p>
<p style="text-indent: 21pt;">动态创建代码的方法看上去简单,申请一段空间,将那一段模板代码地址复制过来。但是,实际情况并非如此。</p>
<p style="text-indent: 21pt;">首先,申请控件的时候,使用VirutalAlloc,并指定EXECUTE_READWRITE属性。另外,要关注到原来的代码是在代码段执行的,所以有些函数的地址可能只是一个偏移地址。而后申请的代码,是在HEAP中运行的,所以,如果只是单纯地复制,函数调用就会报错了。</p>
<p style="text-indent: 21pt;">好了,上面讲了六点关键因素。如果你足够理解上面的过程,你也可以做到AOP了。这篇文章是一个纯技术的,可能关心测试的会非常失望,只能说sorry了。</p>
分享到:
相关推荐
这个项目是一个简单的比赛管理系统,该练手小项目希望能帮助到大家,SSM的整合 使用技术 IOC容器:Spring Web框架:SpringMVC ORM框架:Mybatis 数据源:C3P0 日志:log4j AOP 前端框架:Hui 其他插件: ...
spring-web.jar(必须) :这个jar 文件包含Web 应用开发时,用到Spring 框架时所需的核心类,包括自动载入Web Application Context 特性的类、Struts 与JSF 集成类、文件上传的支持类、Filter 类和大量工具辅助类。...
NET委托:一个C#睡前故事 [推荐] - [原创] Microsoft .NET策略及框架概述 卸载Class? Web Form 窗体 如何实现web页面的提示保存功能 在ASP.Net中两种利用CSS实现多界面的方法 如何在客户端调用服务端代码 页面一...
我们前面已经指出Oracle的Lob字段和一般类型的字段在操作上有一个明显的区别--那就是你必须首先通过Oracle的empty_blob()/empty_clob()初始化Lob字段,然后获取该字段的引用,通过这个引用更改其值。所以要完成对...
a.2把spring添加为一个maven2依赖项 a.3spring与ant a.4spring与log4j 附录b用(和不用)spring进行测试 b.1测试简介 b.1.1理解不同类型的测试 b.1.2使用junit b.1.3spring在测试中的角色 b.2单元测试...
A.2 把Spring添加为一个Maven 2依赖项 A.3 Spring与Ant A.4 Spring与Log4j 附录B 用(和不用)Spring进行测试 B.1 测试简介 B.1.1 理解不同类型的测试 B.1.2 使用JUnit B.1.3 Spring在测试中的角色 B.2 单元...
A.2 把Spring添加为一个Maven 2依赖项 A.3 Spring与Ant A.4 Spring与Log4j 附录B 用(和不用)Spring进行测试 B.1 测试简介 B.1.1 理解不同类型的测试 B.1.2 使用JUnit B.1.3 Spring在测试中的角色 B.2 单元...
Spring.NET是一个应用程序框架,其目的是协助开发人员创建企业级的.NET应用程序。它提供了很多方面的功能,比如依赖注入、面向方面编程(AOP)、数据访问抽象及ASP.NET扩展等等。Spring.NET以Java版的Spring框架为...
它不会尝试替换已经流行的框架(例如NLog或log4net),但会与它们一起使用以通过自动化来减少日志记录的工作量。 只需标记您需要记录的方法,LogSpect就会实际上将您的应用程序重写为instant记录说明。 它将记录...
一、简介当下Java后端的SpringBoot微服务框架大火,原因离不开注解的使用,其简单易配置的注解方式使得更多的社区为其编写适用于SpringBoot的框架,也就是注解逐渐取代了传统的xml配置方式。那么注解在Android中也...
自动化代码检查 sonar 代码规范 阿里巴巴Java开发规范手册 UMPAY——编码规范 日志规范 异常规范 网络 协议 TCP/IP HTTP hession file HTTPS 负载均衡 容器 JBOSS tomcat resin jetty 容灾 ...
1,实体主键类型支持多种(1),整型(自增)(2),字符串型(3),导型(4),雪花算法(长整型) 2,支持SAAS应用,实体主要以租户ID为标识,持久化层自动将租户ID过滤当前用户的租户ID。3,判断菜单功能权限不再...
提供代码生成器从DB生成实体、持久化、服务以及MVC控制器,每层依赖接口,并需要在客户端将对应实现层用Autofac程序集依赖注入,用AOP提供日志跟踪、事务、模型验证等。对Autofac、Redis、RabbitMQ封装扩展;DB访问...