Whats is Java Agent? .. java.lang.instrument.Instrumentation
之前有写 基于AOP的日志调试 讨论一种跟踪Java程序的方法, 但不是很完美.后来发现了 Btrace , 由于它借助动态字节码注入技术 , 实现优雅且功能强大.
只不过, 用起来总是磕磕绊绊的, 时常为了跟踪某个问题, 却花了大把的时间调试Btrace的脚本. 为此, 我尝试将几种跟踪模式固化成脚本模板, 待用的时候去调整一下正则表达式之类的.
跟踪过程往往是假设与验证的螺旋迭代过程, 反复的用BTrace跟踪目标进程, 总有那么几次莫名其妙的不可用, 最后不得不重启目标进程. 若真是线上不能停的服务, 我想这种方式还是不靠谱啊.
为此, 据决定自己的搞个用起来简单, 又能良好支持反复跟踪而不用重启目标进程的工具.
AOP
AOP是Btrace, jip1等众多监测工具的核心思想, 用一段代码最容易说明:
1
2
3
4
5
|
public void say(String words){
Trace.enter();
System.out.println(words);
Trace.exit();
} |
如上, Trace.enter() 和 Trace.exit() 将say(words)内的代码环抱起来, 对方法进出的进行切面的处理, 便可获取运行时的上下文, 如:
- 调用栈
- 当前线程
- 时间消耗
- 参数与返回值
- 当前实例状态
实现的选择
实现切面的方式, 我知道的有以下几种:
代理(装饰器)模式
设计模式中装饰器模式和代理模式, 尽管解决的问题域不同, 代码实现是非常相似, 均可以实现切面处理, 这里视为等价. 依旧用代码说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
interface Person {
void say(String words);
} class Officer implements Person {
public void say(String words) { lie(words); }
private void lie(String words) {...}
} class Proxy implements Person {
private final Officer officer;
public Proxy(Officer officer) { this .officer = officer; }
public void say(String words) {
enter();
officer.say(words);
exit();
}
private void enter() { ... }
private void exit() { ... }
} Person p = new Proxy( new Officer());
|
很明显, 上述enter() 和exit()是实现切面的地方, 通过获取Officer的Proxy实例, 便可对Officer实例的行为进行跟踪. 这种方式实现起来最简单, 也最直接.
Java Proxy
Java Proxy是JDK内置的代理API, 借助反射机制实现. 用它来是完成切面则会是:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class ProxyInvocationHandler implements InvocationHandler {
private final Object target;
public ProxyInvocationHandler(Object target) { this .target = target;}
public Object handle(Object proxy, Method method, Object[] args) {
enter();
method.invoke(target, args);
exit();
}
private void enter() { ... }
private void exit() { ... }
} ClassLoader loader = ... Class<?>[] interfaces = {Person. class };
Person p = (Person)Proxy.newInstance(loader, interfaces, new ProxyInvocationHandler( new Officer()));
|
相比较上一中方法, 这种不太易读, 但更为通用, 对具体实现依赖很少.
AspectJ
AspectJ是基于字节码操作(运行时利用ASM库)的AOP实现, 相比较Java proxy, 它会显得对调用更”透明”, 编写更简明(类似DSL), 性能更好. 如下代码:
1
2
3
|
pointcut say(): execute(* say(..)) before(): say() { ... } after() : say() { ... } |
Aspectj实现切面的时机有两种: 静态编译和类加载期编织(load-time weaving). 并且它对IDE的支持很丰富.
CGlib
与AspectJ一样CGlib也是操作字节码来实现AOP的, 使用上与Java Proxy非常相似, 只是不像Java Proxy对接口有依赖, 我们熟知的Spring, Guice之类的IoC容器实现AOP都是使用它来完成的.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Callback implements MethodInterceptor {
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
enter();
proxy.invokeSuper(obj, args);
exit();
}
private void enter() { ... }
private void exit() { ... }
} Enhancer e = new Enhancer();
e.setSuperclass(Officer. class );
e.setCallback( new Callback());
Person p = e.create(); |
字节码操纵
上面四种方法各有适用的场景, 但唯独对运行着的Java进程进行动态的跟踪支持不了, 当然也许是我了解的不够深入, 若有基于上述方案的办法还请不吝赐教.
还是回到Btrace的思路上来, 在理解了它借助java.lang.Instrumentation进行字节码注入的实现原理后, 实现动态变化跟踪方式或目标应该没有问题.
借下来的问题, 如何操作(注入)字节码实现切面的处理. 可喜的是, “构建自己的监测工具”一文给我提供了一个很好的切入点. 在此基础上, 经过一些对ASM的深入研究, 可以实现:
- 方法调用进入时, 获取当前实例(this) 和 参数值列表;
- 方法调用出去时, 获取返回值;
- 方法异常抛出时, 触发回调并获取异常实例.
其切面实现的核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
private static class ProbeMethodAdapter extends AdviceAdapter {
protected ProbeMethodAdapter(MethodVisitor mv, int access, String name, String desc, String className) {
super (mv, access, name, desc);
start = new Label();
end = new Label();
methodName = name;
this .className = className;
}
@Override
public void visitMaxs( int maxStack, int maxLocals) {
mark(end);
catchException(start, end, Type.getType(Throwable. class ));
dup();
push(className);
push(methodName);
push(methodDesc);
loadThis();
invokeStatic(Probe.TYPE, Probe.EXIT);
visitInsn(ATHROW);
super .visitMaxs(maxStack, maxLocals);
}
@Override
protected void onMethodEnter() {
push(className);
push(methodName);
push(methodDesc);
loadThis();
loadArgArray();
invokeStatic(Probe.TYPE, Probe.ENTRY);
mark(start);
}
@Override
protected void onMethodExit( int opcode) {
if (opcode == ATHROW) return ; // do nothing, @see visitMax
prepareResultBy(opcode);
push(className);
push(methodName);
push(methodDesc);
loadThis();
invokeStatic(Probe.TYPE, Probe.EXIT);
}
private void prepareResultBy( int opcode) {
if (opcode == RETURN) { // void
push((Type) null );
} else if (opcode == ARETURN) { // object
dup();
} else {
if (opcode == LRETURN || opcode == DRETURN) { // long or double
dup2();
} else {
dup();
}
box(Type.getReturnType(methodDesc));
}
}
private final String className;
private final String methodName;
private final Label start;
private final Label end;
} |
更多参考请见这里的 Demo , 它是javaagent, 在伴随宿主进程启动后, 提供MBean可用jconsole进行动态跟踪的管理.
后续的方向
- 提供基于Web的远程交互界面;
- 提供基于Shell的本地命令行接口;
- 提供Profile统计和趋势输出;
- 提供跟踪日志定位与分析.
相关推荐
本文主要介绍了一种基于字节码搜索的Java反序列化漏洞调用链挖掘方法。文章的核心在于结合了污点分析与符号执行技术来生成从反序列化漏洞入口点到危险函数的调用树。这一方法能够自动化地进行调用链的构建,大幅提升...
在Java开发领域,字节码编辑器是一种不可或缺的工具,它允许开发者查看、分析并修改Java类文件的字节码,从而实现对程序运行时行为的深入控制。Recaf是这样一个现代、用户友好的字节码编辑器,它提供了一种直观的...
Java字节码反编译是Java开发者在进行代码分析、逆向工程或学习...通过理解字节码和掌握反编译工具,我们可以深化对Java程序的理解,提高解决问题的能力。然而,也应尊重软件的知识产权,只在合法范围内使用反编译技术。
数据流分析是一种静态分析技术,用于在不运行程序的情况下,从字节码层面获取关于程序变量的信息,包括数据类型、控制流等。这种分析对于软件开发工具,特别是静态分析工具,具有重要意义,因为它们依赖于准确的数据...
Btrace利用Java的代理技术(JDK Proxy)和Java字节码操作库(ASM)来实现其功能,因此无需修改或重新编译源代码,极大地提高了问题诊断的效率。 **安装Btrace** 1. 首先,你需要从官方网站...
由于Java字节码独立于具体的硬件平台,使得Java程序具有跨平台的特性。在进行回归测试时,通过分析和操作字节码,我们可以对应用程序进行更深层次的测试和调试。 大型应用的回归测试通常面临两个主要挑战:一是测试...
BTrace 动态地检测目标应用程序的类以注入跟踪代码(“字节码跟踪”)。致谢基于ASM由JCTools提供支持由hppcrt提供支持使用JProfiler Java Profiler进行优化使用 SDKMAN 构建环境助手!构建 BTrace设置您需要安装...
在IT领域,二进制查看器(Binary Viewer)是一种用于检查和分析二进制数据的工具,它能够显示非文本格式的数据,如图片、音频、视频文件或计算机程序的原始字节码。这类工具对于软件开发者、逆向工程师以及系统管理...
BTrace的工作原理是基于字节码级别的跟踪,它在JVM启动时作为一个代理,动态地插入到目标应用的字节码中,当程序执行到特定点时,插入的追踪代码就会被执行,从而收集到相应的运行时数据。这种技术避免了对原始代码...
首先,JVM(Java虚拟机)是运行Java字节码的虚拟机进程,它是Java程序运行的基础。性能调优工具如JVM参数调优、垃圾回收机制、以及运行期剖析接口等,都是为了更好地管理JVM性能而设计的工具和方法。在Java代码运行...
总的来说,Java动态生成代码并编译载入是一种强大的技术,它结合了反射和编译的能力,使得Java程序具备了更强的灵活性和适应性。但是,需要注意的是,这种技术也可能会带来代码复杂度的增加,以及可能的安全风险,...
字节码的使用使得Java程序能在任何支持JVM的设备上运行。 3. **Java模拟器的实现**: 通常,Java模拟器通过以下方式实现: - **动态编译**: 模拟器可以实时编译字节码到本地机器码,如HotSpot JVM的即时编译(JIT)...
标题“Java应用程序打包”指的是将Java源代码编译成字节码(.class文件),然后通过特定的工具整合成一个或多个可执行文件或库的过程。这个过程通常包括JAR(Java Archive)文件的创建,这是Java中常用的打包方式。 ...
在提供的压缩包文件中,"E-Book"可能是源代码文件,包含了上述提到的各种Java文件,例如.java源代码文件、.class编译后的字节码文件,或者可能是配置文件、资源文件等。进一步研究这些文件将有助于深入理解E-Book...
字节码是一种平台无关的中间语言,使得Java程序能在任何支持Java的平台上运行。 2. **反编译(Decompiler)**:反编译工具如JAD(Java Decompiler)和JD-GUI可以将`.class`文件转换回接近原始`.java`源代码的形式。...
Java 类加载器是 Java 虚拟机(JVM)的一个关键组件,负责在程序运行时动态地加载类和接口。它按照一定的顺序加载类,并确保每个类只被加载一次。Java 类加载器分为以下几种: - **启动类加载器(Bootstrap ClassLoader...
1. **编译与打包**:在Java中,源代码通常以`.java`文件形式存在,通过`javac`编译器将其转换为`.class`字节码文件。这些`.class`文件是Java虚拟机(JVM)可执行的。然而,为了便于分发和管理,开发者通常会将多个...