三明治

在数学的概念当中,有一个迫敛定理或三明治定理(英文:Squeeze Theorem、Sandwich Theorem),可以帮助我们确定某一点的函数值到底是多少:

这种“三明治”思路可以应用到 Multiple Agents 当中,这样我们就可以检测某一个 Agent Jar 修改了什么内容。

但是,需要注意的一点是:属于同一组的 transformer 才能进行这种“三明治”操作

  • 同属于 retransformation incapable transformer,
  • 同属于 retransformation capable transformer。

示例

Agent Jar 1

LoadTimeAgent

package lsieun.agent;

import lsieun.instrument.*;
import lsieun.utils.*;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

public class LoadTimeAgent {
    public static void premain(String agentArgs, Instrumentation inst) {
        // 第一步,打印信息:agentArgs, inst, classloader, thread
        PrintUtils.printAgentInfo(LoadTimeAgent.class, "Premain-Class", agentArgs, inst);

        // 第二步,解析参数:agentArgs
        boolean flag = Boolean.parseBoolean(agentArgs);

        // 第三步,使用 inst:添加 transformer
        ClassFileTransformer transformer = new SandwichTransformer(flag);
        inst.addTransformer(transformer, false);
    }
}

SandwichTransformer

package lsieun.instrument;

import lsieun.utils.DateUtils;
import lsieun.utils.DumpUtils;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class SandwichTransformer implements ClassFileTransformer {
    private final boolean compare;

    public SandwichTransformer(boolean compare) {
        this.compare = compare;
    }

    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] classfileBuffer) throws IllegalClassFormatException {
        if (!compare) {
            addClass(className, classfileBuffer);
        }
        else {
            compareClass(className, classfileBuffer);
        }
        return null;
    }


    private static final Map<String, byte[]> map = new HashMap<>();

    private static void addClass(String className, byte[] bytes) {
        map.put(className, bytes);
    }

    private static void compareClass(String className, byte[] bytes) {
        byte[] origin_bytes = map.get(className);
        if (origin_bytes == null) return;
        boolean isEqual = Arrays.equals(origin_bytes, bytes);
        if (isEqual) {
            map.remove(className);
            return;
        }

        String newName = className.replace('/', '.');
        String dateStr = DateUtils.getTimeStamp();
        String filenameA = String.format("%s.%s.%s.class", newName, dateStr, "A");
        String filenameB = String.format("%s.%s.%s.class", newName, dateStr, "B");
        DumpUtils.dump(filenameA, origin_bytes);
        DumpUtils.dump(filenameB, bytes);
        System.out.println("Diff: " + filenameA);
        System.out.println("Diff: " + filenameB);
    }
}

Agent Jar 2

TemplateAgent

package lsieun.agent;

import lsieun.utils.PrintUtils;
import lsieun.utils.TransformerUtils;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;

public class TemplateAgent {
    public static void premain(String agentArgs, Instrumentation inst) throws InterruptedException {
        // 第一步,打印信息:agentArgs, inst, classloader, thread
        PrintUtils.printAgentInfo(TemplateAgent.class, "Premain-Class", agentArgs, inst);

        // 第二步,指明要处理的类
        TransformerUtils.internalName = "sample/HelloWorld";

        // 第三步,使用 inst:添加 transformer
        ClassFileTransformer transformer = TransformerUtils::enterMethod;
        inst.addTransformer(transformer, false);
    }

    public static void agentmain(String agentArgs, Instrumentation inst) throws InterruptedException {
        // 第一步,打印信息:agentArgs, inst, classloader, thread
        PrintUtils.printAgentInfo(TemplateAgent.class, "Agent-Class", agentArgs, inst);
    }
}

Run

$ java -cp ./target/classes/ \
  -javaagent:./target/TheAgent.jar=false \
  -javaagent:./target/TemplateAgent001.jar \
  -javaagent:./target/TheAgent.jar=true \
  sample.Program
  
========= ========= ========= SEPARATOR ========= ========= =========
Agent Class Info:
    (1) Premain-Class: lsieun.agent.LoadTimeAgent
    (2) agentArgs: false
    (3) Instrumentation: sun.instrument.InstrumentationImpl@1704856573
    (4) Can-Redefine-Classes: true
    (5) Can-Retransform-Classes: true
    (6) Can-Set-Native-Method-Prefix: true
    (7) Thread Id: main@1(false)
    (8) ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
========= ========= ========= SEPARATOR ========= ========= =========

========= ========= ========= SEPARATOR ========= ========= =========
Agent Class Info:
    (1) Premain-Class: lsieun.agent.TemplateAgent001
    (2) agentArgs: null
    (3) Instrumentation: sun.instrument.InstrumentationImpl@21685669
    (4) Can-Redefine-Classes: true
    (5) Can-Retransform-Classes: true
    (6) Can-Set-Native-Method-Prefix: true
    (7) Thread Id: main@1(false)
    (8) ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
========= ========= ========= SEPARATOR ========= ========= =========

========= ========= ========= SEPARATOR ========= ========= =========
Agent Class Info:
    (1) Premain-Class: lsieun.agent.LoadTimeAgent
    (2) agentArgs: true
    (3) Instrumentation: sun.instrument.InstrumentationImpl@764977973
    (4) Can-Redefine-Classes: true
    (5) Can-Retransform-Classes: true
    (6) Can-Set-Native-Method-Prefix: true
    (7) Thread Id: main@1(false)
    (8) ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
========= ========= ========= SEPARATOR ========= ========= =========

10012@LenovoWin7
|000| 10012@LenovoWin7 remains 600 seconds
transform class: sample/HelloWorld
file:///D:\git-repo\learn-java-agent\dump\sample.HelloWorld.2022.02.03.04.14.09.089.A.class
file:///D:\git-repo\learn-java-agent\dump\sample.HelloWorld.2022.02.03.04.14.09.089.B.class

总结

本文内容总结如下:

  • 第一点,通过“三明治”的方式,我们可以检测某一个 Agent Jar 做了哪些修改。
  • 第二点,使用“三明治”的方式,要注意 transformer 属于同一组当中。