`
cloudtech
  • 浏览: 4612398 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

利用ASM进行方法拦截中获取相关数据的实现

 
阅读更多

方法拦截中获取相关数据的实现

如果你看不懂我的上一篇文章,请你退回去一步一步根据例子运行起来,在你运行的过程中你会加深对
程序的理解。

本篇文章主要是利用ASM来在拦截方法时获取方法调用时的相关数据,如参数列表,本地变量列表,方法
调用栈以及操作状态码等重要数据,以及方法执行时间。结合当前系统的内存状态,CPU占用率等系统信息
可以为业务逻辑出现异常时提供最可靠的分析依据。本篇要求对ByteCode有一定了解,即使你因为某些原
因不得不照抄这里的实现,我也希望你能在先使用这些实现范例后再弄懂它。能看懂这些内容并能正确使用
这些实现的,你本来就有能力弄懂它。

一个比较绕人的地方,我们来比较一下用ASM动态生成class和利用代理代理来注入代码的不同:
在利用代理来注入代码时,其实我们是利用反射来invoke一个Method对象。所以要注入的代码直接插入
在invoke的前后:


这样的实现非常好理解,说白了就是如果你要执行方法m,那么请你交给我来帮助你执行,这样我就可以向别
人炫耀一下:我要执行方法m了(before),我已经执行完方法m了(after)。
而在ASM动态生成class时,MyAdviceAdaptor的onMethodEnter和onMethodEixt方法是在包装某个方法
调用的,而不是运行某个方法时调用的。如果我们在onMethodEnter方法中调用
System.out.println("Hello,world");
那么是在visitMethod时调用了和要生成的新方法无关的一个System.out.println("Hello");的调用。
生成后的这个方法没有被注入任何指令,所以在执行的时候不会调用System.out.println("Hello");
正确的做法是在onMethodEnter中调用ASM操作来向要包装的方法插入指令:

这样的才能将System.out.println("Hello,world");插入到新生成的方法的最前端。
比如原来的方法是

那么,利用ASM生成这个方法应该是

而现在因为MyAdviceAdaptor的onMethodEnterr的方法中有插入指令的代码,所以这些代码会在MethoeVisitor
开始原始方法的生成之前最先插入,即生成新方法的指令变成:

这样原来的方法就被重新生成为:


同样,必须在onMethodEixt中使用ASM插入指令的方法才能将代码注入到生成后的方法中。

好了,弄明白上面的理论。我们来实现相关数据的获取。
首先,在一个方法调用时,利用ASM我们能够获取到方法的参数列表和本地变量列表。以及方法最后操作状态码
利用注入在原方法体前后的代码的时间差可以计算出原方法代码的执行时间。
因为onMethodEnter方法中插入的代码会在原始方法的所有操作包括实参传递之前插入,所以获取参数表,
本地变量表,操作状态码都在onMethodEixt方法获取:

获取本地变量列表:

获取方法参数列表:

好了,假如我们现在要传出这样几个信息:
被拦截的方法所在的类,方法名称,本地变量列表(数组),参数列表(数组),操作状态码。那么:

OK,现在栈顶向下的五个操作数分别是args,vars,opcode,mthodName,className,我们当然可以同样用ASM指令

生成代码将栈中的五个操作数传递到指定的目的地,但这样的操作太复杂,我们只要调用一个有五个参数的方法,然后在

这个方法内用普通JAVA代码自由地实现向目的地传送,而不是在被拦截的方法中用ASM代码来实现向目的地传送。

所以我们只要在外部定义好一个方法形如:

就可以将被拦截的方法的实参列表,本地变量列表,操作状态码等信息传递到外部来。

获取调用栈
利用sun.reflect.Reflection.getCallerClass(x);我们只能获取调用者所在的类,如果是同一类中不同方法的
之间的调用测无法明确地查看方法调用链。
使用Throwable的getStackTrace()则可以获取完整的方法调用栈。我们只要构造一个Throwable对象就可以通过
它来获取调用这个方法的所有调用者:

这里仅仅是调用StackTraceElement的toString()方法,你可以根据需要获取它的详细信息。
由于set[0]是t对象所在的方法本身,我们不需要这个调用者信息。所以循环直接从1开始。
如果Test.main()方法中调用了这个方法,那么第一个调用者直接是Test.main();
本来我们需要在onMethodEixt方法中利用ASM指令来让被拦截的方法调用这个方法,并将方法返回值压栈作为一个参数
传给send方法。但是因为被拦截的方法一定会调用send方法,所以我们直接在send中调用getInvokeStack(),那么被拦截的
方法就是第二调用者。所以我们只要将循环从2开始,然后在send方法中调用getInvokeStack();就可以获取到被拦截的方法
的所有调用链。

实际上,我们在OnMethidEnter方法中同样会将className,methodName利用类似的方法传递出来。同时在输入要拦截的方法时还会传入
一个随机串给MyAdviceAdaptor的构造方法,然后在OnMethidEnter和OnMethidEixt方法中同时会调用ASM指令把这个随机串都传给形如

Sender.send()的方法,这样可以对方法进入和退出的send数据进行配对。并可以用两个配对的send的时间差计算方法执行时间而不是把

计算原方法代码执行时间的代码注入到原方法。


其它的细节问题。以后再作交待。(整个测试项目的压缩文档需要联系获取,以前在blog上提供MMS项目的压缩档,有很多兄弟竟然直接拿项目中我的手机号进行开发测试,弄得我收到大量的测试彩信)

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics