Slide 1

Slide 1 text

Instrument to Remove Using Java agents for fun and profit Johannes Bechberger

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

web! ! org.springframework.boot! spring-boot-starter- validation! ! org.springframework.boot! spring-boot-starter- thymeleaf! ! org.springframework.boot! spring-boot-starter- test! test! ! ! com.h2database!

Slide 9

Slide 9 text

Do you need all?

Slide 10

Slide 10 text

Probably not

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Software systems have a natural tendency to grow in size and complexity over time. A part of this growth comes with new features or bug fixes, while another part is due to useless code that accumulates over time. The problem of safely debloating real-world applications remains a long- standing software engineering endeavor today. — Soto-Valero et. al “ Source: https://arxiv.org/pdf/2008.08401.pdf

Slide 13

Slide 13 text

How do we debloat?

Slide 14

Slide 14 text

Debloating tools Dynamic Static Coverage Profiling Code Analysis Dependency Analysis ./gradlew lintGradle GraalVM native image

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

You know the JVM? Could you help us debloat programs using agents or profilers?

Slide 19

Slide 19 text

Existing tools have problems...

Slide 20

Slide 20 text

So let’s write our own * as a proof of concept for a blog post *

Slide 21

Slide 21 text

Reduce the Problem

Slide 22

Slide 22 text

Find unused classes Source: https://arxiv.org/pdf/2008.08401.pdf

Slide 23

Slide 23 text

Find unused classes class A { static { Store.getInstance().processClassUsage("A"); } private int field; public void method() {...} }

Slide 24

Slide 24 text

When are static initializers called “ • T is a class and an instance of A is created. • A static method declared by A is invoked. • A static field declared by A is assigned. • A static field declared by A is used and the field is not a constant variable – JLS SE17 §12.4.2. Detailed Initialization Procedure

Slide 25

Slide 25 text

Static initializers in interfaces interface I { static { Store.getInstance().processClassUsage("I"); } public void method(); } Forbidden in Java Allowed in Byte code

Slide 26

Slide 26 text

Classes found in byte code Recorded classes Bloat classes Dynamically generated classes (e.g. for lambdas)

Slide 27

Slide 27 text

What to do with this information?

Slide 28

Slide 28 text

Classes found in byte code Recorded classes Bloat classes Dynamically generated classes (e.g. for lambdas) Remove

Slide 29

Slide 29 text

class UnusedClass { static { LOG.error("Class UnusedClass is used " + "which is not allowed"); } private int field; public void method() {...} }

Slide 30

Slide 30 text

And now for something complete different Source: Monty Python

Slide 31

Slide 31 text

Implementation

Slide 32

Slide 32 text

In come Java Agents

Slide 33

Slide 33 text

They are essentially like bike computers https://commons.wikimedia.org/wiki/File:Cyclocomputer.jpg

Slide 34

Slide 34 text

JVM Agent attach

Slide 35

Slide 35 text

JVM Agent

Slide 36

Slide 36 text

Structure JVM with Agent Instrumenter Used Classes Instrumenter JVM Instr. JAR Used Classes Reduced JAR JAR with Error Messages JAR Due to Spring classloader magic

Slide 37

Slide 37 text

https://github.com/parttimenerd/dead-code-agent

Slide 38

Slide 38 text

Agent Structure Main premain(args) ClassTF transform Store Class State start store

Slide 39

Slide 39 text

package my.app; class A { static { Store.getInstance().processClassUsage("my.app.A"); } private int field; public void method() {...} } Only one problem...

Slide 40

Slide 40 text

package java.lang; public final class String implements ... { static { Store.getInstance().processClassUsage("java.lang.String"); } @Stable private final byte[] value; ... public String() {...} ... }

Slide 41

Slide 41 text

Class Loader Hierarchies platform app bootstrap X Y class reference delegates to

Slide 42

Slide 42 text

Agent Structure Main premain(args) ClassTF transform Store Class State start store Runtime JAR

Slide 43

Slide 43 text

Class Loader Hierarchies platform app bootstrap X Y class reference delegates to searches in runtime.jar https://mostlynerdless.de/blog/2023/06/02/class-loader-hierarchies/

Slide 44

Slide 44 text

Implemented via inst.appendToBootstrapClassLoaderSearch(new JarFile( getExtractedJARPath().toFile())); instrumentation Instrumentation { void appendToBootstrapClassLoaderSearch(JarFile jarfile); }

Slide 45

Slide 45 text

Normally public static void main(String[] args) { ... } public static void main(String args[]) { ... } void main() { ... }

Slide 46

Slide 46 text

Main Class public static void agentmain(String agentArgs) { ... } JVM start later attach attach public static void premain(String agentArgs) { ... }

Slide 47

Slide 47 text

Main Class public static void agentmain(String agentArgs, Instrumentation inst) { ... } public static void premain(String agentArgs, Instrumentation inst) { ... }

Slide 48

Slide 48 text

Main Class public class Main { public static void premain(String agentArgs, Instrumentation inst) { AgentOptions options = new AgentOptions(agentArgs); ... inst.appendToBootstrapClassLoaderSearch( new JarFile(getExtractedJARPath().toFile())); ... inst.addTransformer(new ClassTransformer(options), true); } ... } Instrumentation.retransformClasses

Slide 49

Slide 49 text

ClassTransformer extends ClassFileTransformer “ An agent registers an implementation of this interface using the addTransformer method so that the transformer’s transform method is invoked when classes are loaded, redefined, or retransformed – ClassFileTransformer Documentation

Slide 50

Slide 50 text

ClassTransformer#transform byte[] transform( ClassLoader loader, String className, Class> klass, ProtectionDomain domain, byte[] classfileBuffer) throws IllegalClassFormatException { ... }

Slide 51

Slide 51 text

ClassTransformer#transform if (className.startsWith("me/bechberger/runtime/Store") || className.startsWith("me/bechberger/ClassTransformer") || className.startsWith("java/") || className.startsWith("jdk/internal") || className.startsWith("sun/")) { return classfileBuffer; } How to actually transform the bytecode?

Slide 52

Slide 52 text

https://www.javassist.org/ https://bytebuddy.net/#/

Slide 53

Slide 53 text

ClassTransformer#transform private void transform(String className, CtClass cc) { String cn = formatClassName(className); Store.getInstance() .processClassLoad(cn,cc.getClassFile().getInterfaces()); cc.makeClassInitializer() .insertBefore( String.format("me.bechberger.runtime.Store.getInstance()" + ".processClassUsage(\"%s\");", cn)); } ?

Slide 54

Slide 54 text

With Spring-PetClinic Class org.apache.tomcat.util.net.SocketBufferHandler is used which is not allowed Class org.apache.tomcat.util.net.SocketBufferHandler$1 is used which is not allowed Class org.apache.tomcat.util.net.NioChannel is used which is not allowed Class org.apache.tomcat.util.net.NioChannel$1 is used which is not allowed ... java -javaagent:./target/dead-code.jar=output=classes.txt \ -jar petclinic.jar u ch.qos.logback.classic.encoder.PatternLayoutEncoder l ch.qos.logback.classic.joran.JoranConfigurator u ch.qos.logback.classic.jul.JULHelper u ch.qos.logback.classic.jul.LevelChangePropagator ...

Slide 55

Slide 55 text

Who has wrote an agent before?

Slide 56

Slide 56 text

Who has used an agent before?

Slide 57

Slide 57 text

Other applications of agents

Slide 58

Slide 58 text

Main agentmain(args) premain(args) Profiler sample sleep Store start store Write your own profiler

Slide 59

Slide 59 text

Who instruments the instrumenter?

Slide 60

Slide 60 text

@ExtendWith(MockitoExtension.class) public class MockitoTest { @Mock List mockedList; @Test public void whenNotUseMockAnnotation_thenCorrect() throws InterruptedException { mockedList.add("one"); Mockito.verify(mockedList).add("one"); assertEquals(0, mockedList.size()); Mockito.when(mockedList.size()).thenReturn(100); assertEquals(100, mockedList.size()); } } Thread.sleep(10000000L);

Slide 61

Slide 61 text

This uses ByteBuddy

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

You know the JVM? Could you help us debloat programs using agents or profilers?

Slide 64

Slide 64 text

Agent design process Choose prior agent(s) Copy agent code Modify code transformer Test and debug

Slide 65

Slide 65 text

public interface Collection extends Iterable { !// !!... default Stream stream() { return StreamSupport.stream(this.spliterator(), false); } !//!!... }

Slide 66

Slide 66 text

public interface Collection extends Iterable { !// !!... default Stream stream() { MockMethodDispatcher var1 = MockMethodDispatcher.get("APErU6j7", this); Callable var4 = var1 !!= null !&& var1.isMocked(this) !&& !var1.isOverridden(this, Collection.class.getMethod("stream")) ? var1.handle(this, Collection.class.getMethod("stream"), new Object[0]) : null; Stream var2 = var4 !!= null ? null : StreamSupport.stream(this.spliterator(), false); if (var4 !!= null) { var2 = (Stream)var4.call(); } return var2; } !//!!... }

Slide 67

Slide 67 text

Instrumenting agent for instrumenting instrumenting agents

Slide 68

Slide 68 text

var exprEditor = new ExprEditor() { @Override public void edit(MethodCall m) throws CannotCompileException { if (!isAddTransformerMethod(m)) { return; } m.replace( "me.bechberger.meta.runtime.InstrumentationHandler" + ".addTransformer($0, $1);"); } };

Slide 69

Slide 69 text

private void transform(String className, CtClass cc) throws CannotCompileException { var exprEditor = 1!/* !.. !*/; for (CtConstructor constructor : cc.getDeclaredConstructors()) { constructor.instrument(exprEditor); } for (CtMethod method : cc.getDeclaredMethods()) { method.instrument(exprEditor); } }

Slide 70

Slide 70 text

https://github.com/parttimenerd/meta-agent

Slide 71

Slide 71 text

@parttimen3rd on Twitter parttimenerd on GitHub mostlynerdless.de @SweetSapMachine sapmachine.io