Как и зачем инструментировать
байткод в Java приложениях?
Специально для JUG.ua, 25 мая 2017
@antonarhipov
Антон Архипов
@antonarhipov
Javassist & ASM inside!
Привет!
@Entity

@Table(name = "owners")

public class Owner extends Person {

@Column(name = "address")

@NotEmpty

private String address;



@Column(name = "city")

@NotEmpty

private String city;



@Column(name = "telephone")

@NotEmpty

@Digits(fraction = 0, integer = 10)

private String telephone;



@OneToMany(cascade = CascadeType.ALL,
mappedBy = "owner")

private Set<Pet> pets;
public class JavassistLazyInitializer
extends BasicLazyInitializer
implements MethodHandler {
final JavassistLazyInitializer instance
= new JavassistLazyInitializer(…);


ProxyFactory factory = new ProxyFactory();

factory.setSuperclass(interfaces.length == 1?persistentClass:null);

factory.setInterfaces(interfaces);

factory.setFilter(FINALIZE_FILTER);


Class cl = factory.createClass();

final HibernateProxy proxy = (HibernateProxy) cl.newInstance();

((ProxyObject)proxy).setHandler(instance);

instance.constructed = true;

return proxy;
public class JavassistLazyInitializer
extends BasicLazyInitializer
implements MethodHandler {
final JavassistLazyInitializer instance
= new JavassistLazyInitializer(…);


ProxyFactory factory = new ProxyFactory();

factory.setSuperclass(interfaces.length == 1?persistentClass:null);

factory.setInterfaces(interfaces);

factory.setFilter(FINALIZE_FILTER);


Class cl = factory.createClass();

final HibernateProxy proxy = (HibernateProxy) cl.newInstance();

((ProxyObject)proxy).setHandler(instance);

instance.constructed = true;

return proxy;
Зачем?
Модели программирования (AOP, ORM, итд)
Специализированные отладчики
Агенты для мониторинга (NewRelic, XRebel)
Инструменты для разработки (JRebel)
Обфускация (Proguard)
итд
Починить
ошибку в
open-source
библиотеке.
Потому, что я
могу!
Как?
java.lang.instrument
-javaagent
ASM, Javassist, Byte Buddy, cglib, итд
Classloaders
ASM
public class Hello {

public static void main(String[] args) {

System.out.println("Hello, World!");

}

}
public class Hello {

public static void main(String[] args) {

System.out.println("Hello, World!");

}

}
> javap -c Hello
Дизассемблировать Hello
public class Hello {

public static void main(String[] args) {

System.out.println("Hello, World!");

}

}
> javap -c Hello
Compiled from "Hello.java"
public class Hello extends java.lang.Object{
public Hello();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3; //String Hello, World!
5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
java -cp asm-all-3.3.1.jar:asm-util-3.3.1.jar 
org.objectweb.asm.util.ASMifierClassVisitor 
Items.class
ASM
“Сделаю всё как хочу”
Приходится писать много кода
Надо понимать Java-байткод
byte
buddy
Class<?> dynamicType = new ByteBuddy()
.subclass(Object.class)
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("Hello World!"))
.make()
.load(getClass().getClassLoader())
.getLoaded();
System.out.println(dynamicType.newInstance().toString()); //Hello World!
System.out.println(dynamicType);
//class net.bytebuddy.renamed.java.lang.Object$ByteBuddy$KUZX06bM
Byte Buddy
Модный API
Попурялный / активный проект
“Много магии”
JAVASSIST
API
ClassPool
CtClass
CtClass
CtClass
CtClass
CtField
CtMethod
CtConst
CtMethod
insertBefore
insertAfter
instrument
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("com.zt.A",
cp.get("com.zt.Clazz"));
CtMethod[] methods = ct.getMethods();

for (CtMethod method : methods) {

//…
}
ct.writeFile("/output");
}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("com.zt.A",
cp.get("com.zt.Clazz"));
CtMethod[] methods = ct.getMethods();

for (CtMethod method : methods) {

//…
}
ct.writeFile("/output");
}
ClassPool cp = new ClassPool(null);
cp.appendSystemPath();
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("com.zt.A",
cp.get("com.zt.Clazz"));
CtMethod[] methods = ct.getMethods();

for (CtMethod method : methods) {

//…
}
ct.writeFile("/output");
}
public class A extends Clazz { 

public A() {

}

}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("com.zt.A",
cp.get("com.zt.Clazz"));
CtMethod[] methods = ct.getMethods();

for (CtMethod method : methods) {

//…
}
ct.writeFile("/output");
}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("com.zt.A",
cp.get("com.zt.Clazz"));
CtMethod[] methods = ct.getMethods();

for (CtMethod method : methods) {

//…
}
ct.writeFile("/output");
}
mars:output anton$ javap -c com/zt/A.class
public class com.zt.A extends com.zt.Clazz {
public com.zt.A();
Code:
0: aload_0
1: invokespecial #10
4: return
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ct = cp.makeClass("com.zt.A",
cp.get("com.zt.Clazz"));
CtMethod[] methods = ct.getMethods();

for (CtMethod method : methods) {

//…
}
ct.writeFile("/output");
}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"()V");
foo.insertBefore("System.out.println();");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"()V");
foo.insertBefore("System.out.println();");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"()V");
foo.insertBefore("System.out.println();");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
public void foo() { 

}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"(Ljava/lang/String;)V");
foo.insertBefore("System.out.println();");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
public void foo(String s) { 

}
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lja
foo.insertBefore("System.out.println();");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
Дескрипторы бывают длинноваты ;)
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"(Ljava/lang/String;)V");
foo.insertBefore("System.out.println($1)");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
$1, $2, $3 — локальные переменные
$0 — this (для виртуальных методов)
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"(Ljava/lang/String;)V");
foo.insertBefore("System.out.println($1)");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
Exception in thread "main" javassist.CannotCompileException: [source error] ; is missing
at javassist.CtBehavior.insertBefore(CtBehavior.java:774)
at javassist.CtBehavior.insertBefore(CtBehavior.java:734)
at com.zt.basics.Ex.main(Ex.java:35)
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get("com.zt.A");
CtMethod foo = ctClass.getMethod("foo",
"(Ljava/lang/String;)V");
foo.insertBefore("System.out.println($1);");
Class c = ctClass.toClass();

A a = (A) c.newInstance();

a.foo("Hello");

}
CtMethod foo = …
foo.insertBefore(…);
foo.insertAfter(…);
Можно реализовать
трассировку
… или добавить логирование
… или реализовать свой “AOP”
CtMethod foo = …
foo.instrument(new ExprEditor() {
@Override

public void edit(NewExpr e)
throws CannotCompileException {

e.replace("{" +

"$_ = $proceed($$);" +

"System.out.println($_);" +

"}");

}
});
CtMethod foo = …
foo.instrument(new ExprEditor() {
@Override

public void edit(NewExpr e)
throws CannotCompileException {

e.replace("{" +

"$_ = $proceed($$);" +

"System.out.println($_);" +

"}");

}
});
CtMethod foo = …
foo.instrument(new ExprEditor() {
@Override

public void edit(NewExpr e)
throws CannotCompileException {

e.replace("{" +

"$_ = $proceed($$);" +

"System.out.println($_);" +

"}");

}
});
CtMethod foo = …
foo.instrument(new ExprEditor() {
@Override

public void edit(MethodCall m)
throws CannotCompileException {

if(m.getMethodName().contains("println")) {

m.replace("{}");

}
}
});
CtMethod foo = …
foo.instrument(new ExprEditor() {
@Override

public void edit(FieldAccess m)
throws CannotCompileException {

if (f.isWriter()) {

CtField field = f.getField();

String setterName = findSetter(field);

f.replace("{" + "$0." + setterName + "($$);" + "}");

}
}
});
Javassist
“Простой как тесак”
Неприхотлив в использовании
Производительность - так себе :(
Java-агенты
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String args, Instrumentation inst)
throws Exception {
inst.addTransformer(new ClassFileTransformer {
// here be dragons
});
}
}
$> java –javaagent:agent.jar application.Main
META-INF/MANIFEST.MF
Premain-Class: Agent
https://www.youtube.com/watch?v=SY5lMgWlHAw
DEMO
https://github.com/zeroturnaround/callspy
https://github.com/bsideup/javaagent-boilerplate
anton@zeroturnaround.com
@antonarhipov

JUG.ua 20170225 - Java bytecode instrumentation

  • 1.
    Как и зачеминструментировать байткод в Java приложениях? Специально для JUG.ua, 25 мая 2017 @antonarhipov
  • 2.
  • 4.
    @Entity
 @Table(name = "owners")
 publicclass Owner extends Person {
 @Column(name = "address")
 @NotEmpty
 private String address;
 
 @Column(name = "city")
 @NotEmpty
 private String city;
 
 @Column(name = "telephone")
 @NotEmpty
 @Digits(fraction = 0, integer = 10)
 private String telephone;
 
 @OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
 private Set<Pet> pets;
  • 5.
    public class JavassistLazyInitializer extendsBasicLazyInitializer implements MethodHandler { final JavassistLazyInitializer instance = new JavassistLazyInitializer(…); 
 ProxyFactory factory = new ProxyFactory();
 factory.setSuperclass(interfaces.length == 1?persistentClass:null);
 factory.setInterfaces(interfaces);
 factory.setFilter(FINALIZE_FILTER); 
 Class cl = factory.createClass();
 final HibernateProxy proxy = (HibernateProxy) cl.newInstance();
 ((ProxyObject)proxy).setHandler(instance);
 instance.constructed = true;
 return proxy;
  • 6.
    public class JavassistLazyInitializer extendsBasicLazyInitializer implements MethodHandler { final JavassistLazyInitializer instance = new JavassistLazyInitializer(…); 
 ProxyFactory factory = new ProxyFactory();
 factory.setSuperclass(interfaces.length == 1?persistentClass:null);
 factory.setInterfaces(interfaces);
 factory.setFilter(FINALIZE_FILTER); 
 Class cl = factory.createClass();
 final HibernateProxy proxy = (HibernateProxy) cl.newInstance();
 ((ProxyObject)proxy).setHandler(instance);
 instance.constructed = true;
 return proxy;
  • 8.
    Зачем? Модели программирования (AOP,ORM, итд) Специализированные отладчики Агенты для мониторинга (NewRelic, XRebel) Инструменты для разработки (JRebel) Обфускация (Proguard) итд
  • 9.
  • 10.
  • 11.
  • 12.
    public class Hello{
 public static void main(String[] args) {
 System.out.println("Hello, World!");
 }
 }
  • 13.
    public class Hello{
 public static void main(String[] args) {
 System.out.println("Hello, World!");
 }
 } > javap -c Hello Дизассемблировать Hello
  • 14.
    public class Hello{
 public static void main(String[] args) {
 System.out.println("Hello, World!");
 }
 } > javap -c Hello Compiled from "Hello.java" public class Hello extends java.lang.Object{ public Hello(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3; //String Hello, World! 5: invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
  • 16.
    java -cp asm-all-3.3.1.jar:asm-util-3.3.1.jar org.objectweb.asm.util.ASMifierClassVisitor Items.class
  • 17.
    ASM “Сделаю всё какхочу” Приходится писать много кода Надо понимать Java-байткод
  • 18.
  • 19.
    Class<?> dynamicType =new ByteBuddy() .subclass(Object.class) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("Hello World!")) .make() .load(getClass().getClassLoader()) .getLoaded(); System.out.println(dynamicType.newInstance().toString()); //Hello World! System.out.println(dynamicType); //class net.bytebuddy.renamed.java.lang.Object$ByteBuddy$KUZX06bM
  • 20.
    Byte Buddy Модный API Попурялный/ активный проект “Много магии”
  • 21.
  • 22.
  • 23.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); }
  • 24.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } ClassPool cp = new ClassPool(null); cp.appendSystemPath();
  • 25.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } public class A extends Clazz { 
 public A() {
 }
 }
  • 26.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); }
  • 27.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); } mars:output anton$ javap -c com/zt/A.class public class com.zt.A extends com.zt.Clazz { public com.zt.A(); Code: 0: aload_0 1: invokespecial #10 4: return
  • 28.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ct = cp.makeClass("com.zt.A", cp.get("com.zt.Clazz")); CtMethod[] methods = ct.getMethods();
 for (CtMethod method : methods) {
 //… } ct.writeFile("/output"); }
  • 29.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "()V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 }
  • 30.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "()V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 }
  • 31.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "()V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } public void foo() { 
 }
  • 32.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } public void foo(String s) { 
 }
  • 33.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lja foo.insertBefore("System.out.println();"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } Дескрипторы бывают длинноваты ;)
  • 34.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println($1)"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } $1, $2, $3 — локальные переменные $0 — this (для виртуальных методов)
  • 35.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println($1)"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 } Exception in thread "main" javassist.CannotCompileException: [source error] ; is missing at javassist.CtBehavior.insertBefore(CtBehavior.java:774) at javassist.CtBehavior.insertBefore(CtBehavior.java:734) at com.zt.basics.Ex.main(Ex.java:35)
  • 36.
    public static voidmain(String[] args) throws Exception { ClassPool cp = ClassPool.getDefault(); CtClass ctClass = cp.get("com.zt.A"); CtMethod foo = ctClass.getMethod("foo", "(Ljava/lang/String;)V"); foo.insertBefore("System.out.println($1);"); Class c = ctClass.toClass();
 A a = (A) c.newInstance();
 a.foo("Hello");
 }
  • 37.
    CtMethod foo =… foo.insertBefore(…); foo.insertAfter(…); Можно реализовать трассировку … или добавить логирование … или реализовать свой “AOP”
  • 38.
    CtMethod foo =… foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } });
  • 39.
    CtMethod foo =… foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } });
  • 40.
    CtMethod foo =… foo.instrument(new ExprEditor() { @Override
 public void edit(NewExpr e) throws CannotCompileException {
 e.replace("{" +
 "$_ = $proceed($$);" +
 "System.out.println($_);" +
 "}");
 } });
  • 41.
    CtMethod foo =… foo.instrument(new ExprEditor() { @Override
 public void edit(MethodCall m) throws CannotCompileException {
 if(m.getMethodName().contains("println")) {
 m.replace("{}");
 } } });
  • 42.
    CtMethod foo =… foo.instrument(new ExprEditor() { @Override
 public void edit(FieldAccess m) throws CannotCompileException {
 if (f.isWriter()) {
 CtField field = f.getField();
 String setterName = findSetter(field);
 f.replace("{" + "$0." + setterName + "($$);" + "}");
 } } });
  • 43.
    Javassist “Простой как тесак” Неприхотливв использовании Производительность - так себе :(
  • 44.
  • 45.
    import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; publicclass Agent { public static void premain(String args, Instrumentation inst) throws Exception { inst.addTransformer(new ClassFileTransformer { // here be dragons }); } } $> java –javaagent:agent.jar application.Main META-INF/MANIFEST.MF Premain-Class: Agent
  • 46.
  • 47.
  • 48.