трансформирующий класс не имеет никакого эффекта - PullRequest
7 голосов
/ 29 сентября 2019

Основываясь на этом уроке, я пытаюсь заставить работать Java-агента. https://www.baeldung.com/java-instrumentation#loading-a-java-agent

Я получаю [Agent] Transforming class TestApplication У меня нет ошибок, но я не вижу никакого эффекта от преобразования класса.

В конечном итоге я хотел бы получить как статическую нагрузку, так и динамическую. загрузить на работу, но сейчас я сосредоточен на статическом пути.


public class Static_Agent {

    public static void premain(String agentArgs, Instrumentation inst) {
        String[] tokens = agentArgs.split(";");
        String className = tokens[0];
        String methodName = tokens[1];

        System.out.println(">> "+className);
        System.out.println(">> "+methodName);
        transformClass(className, methodName, inst);
    }



    public static void transformClass(String className, String methodName, Instrumentation instrumentation) {
        Class<?> targetCls = null;
        ClassLoader targetClassLoader = null;
        // see if we can get the class using forName
        try {
            targetCls = Class.forName(className);
            targetClassLoader = targetCls.getClassLoader();
            transform(targetCls, methodName, targetClassLoader, instrumentation);
            return;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        // otherwise iterate all loaded classes and find what we want
        for(Class<?> clazz: instrumentation.getAllLoadedClasses()) {
            if(clazz.getName().equals(className)) {
                targetCls = clazz;
                targetClassLoader = targetCls.getClassLoader();
                transform(targetCls, methodName, targetClassLoader, instrumentation);
                return;
            }
        }
        throw new RuntimeException("Failed to find class [" + className + "]");
    }


    public static void transform(Class<?> clazz, String methodName, ClassLoader classLoader, Instrumentation instrumentation) {
        Transformer dt = new Transformer(clazz.getName(), methodName, classLoader);
        instrumentation.addTransformer(dt, true);
        try {
            instrumentation.retransformClasses(clazz);
        } catch (Exception ex) {
            throw new RuntimeException("Transform failed for class: [" + clazz.getName() + "]", ex);
        }
    }



}
public class Transformer implements ClassFileTransformer {


    /** The internal form class name of the class to transform */
    private String targetClassName;
    /** The class loader of the class we want to transform */
    private ClassLoader targetClassLoader;

    private String targetMethodName;

    public Transformer(String targetClassName, String targetMethodName, ClassLoader targetClassLoader) {
        this.targetClassName = targetClassName;
        this.targetClassLoader = targetClassLoader;
        this.targetMethodName = targetMethodName;
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        byte[] byteCode = classfileBuffer;

        String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
        if (!className.equals(finalTargetClassName)) {
            return byteCode;
        }

        if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
            System.out.println("[Agent] Transforming class TestApplication");
            try {

                ClassPool cp = ClassPool.getDefault();
                CtClass cc = cp.get(targetClassName);
                CtMethod m = cc.getDeclaredMethod(targetMethodName);
                m.addLocalVariable("startTime", CtClass.longType);
                m.insertBefore("startTime = System.currentTimeMillis();");

                StringBuilder endBlock = new StringBuilder();

                m.addLocalVariable("endTime", CtClass.longType);
                m.addLocalVariable("opTime", CtClass.longType);
                endBlock.append("endTime = System.currentTimeMillis();");
                endBlock.append("opTime = (endTime-startTime)/1000;");

                endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");

                m.insertAfter(endBlock.toString());

                byteCode = cc.toBytecode();
                cc.detach();
            } catch (Exception e) {
                System.out.println("Exception"+e);
            }
        }
        return byteCode;
    }
}
public class TestApplication {

    public static void main(String[] args) {

        try {
            TestApplication.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public static void run() throws Exception {
        System.out.println("--- start ---");

        while (true) {
            test();
            Thread.sleep(4_000);
        }


    }


    static int count = 0;

    public static void test() {
        System.out.println(count++);
    }

}

Я запускаю с:

java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -jar application.jar

В случаеэто помогает, проект находится здесь: https://github.com/clankill3r/java_agent

Редактировать:

В Transformer.java ближе к концу файла я использую e.printStackTrace(); сейчас.

Iполучить следующую ошибку:

[Агент] Преобразование класса TestApplication javassist.NotFoundException: doeke.application.TestApplication в javassist.ClassPool.get (ClassPool.java:436) в doeke.transformer.Transformer.transform(Transformer.java:48) в java.instrument / java.lang.instrument.ClassFileTransformer.transform (ClassFileTransformer.java:246) в java.instrument / sun.instrument.TransformerManager.transform (TransformerManager.java:188) в java. инструмент / sun.instrument.InstrumentationImpl.transform (InstrumentationImpl.java:563) в java.instrument / sun.instrument.InstrumentationImpl.retransformClasses0 (собственный метод) в java.instrument / sun.instrument.InstrumentationImpl.retransformClasses (InstrumentationImpl.java:167) в doeke.static_agent.Static_agent.Static_gententAntStatic_Agent.java:56) в doeke.static_agent.Static_Agent.transformClass (Static_Agent.java:34) в doeke.static_agent.Static_Agent.premain (Static_Agent.java:22) в java.base / jdk.internal.remplAintoMor(Собственный метод) в java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke (NativeMethodAccessorImpl.java:62) в java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.inetl.java. /java.lang.reflect. .java: 525)

--- звездат ---

0

1

1 Ответ

6 голосов
/ 02 октября 2019

Спасибо, что подняли этот вопрос, чтобы дать мне возможность взглянуть на Java Instrumentation.

Потратив некоторое время на перекрестную проверку образцов кода и предоставленного учебного пособия. Проблема не в кодах программы, а в способе запуска вашей программы.

Если вы добавите несколько регистраторов в метод transform () в Transformer.java, вы обнаружите, что путь к коду не работает после выполнения:

ClassPool cp = ClassPool.getDefault();

И после замены перехвата исключениякод в том же методе от:

} catch (Exception e) {

до:

} catch (NotFoundException | CannotCompileException | IOException e) {

Это даст вам больше подсказок, как показано ниже:

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(Unknown Source)
        at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(Unknown Source)
Caused by: java.lang.NoClassDefFoundError: javassist/NotFoundException
        at doeke.static_agent.Static_Agent.transform(Static_Agent.java:60)
        at doeke.static_agent.Static_Agent.transformClass(Static_Agent.java:40)
        at doeke.static_agent.Static_Agent.premain(Static_Agent.java:28)
        ... 6 more
Caused by: java.lang.ClassNotFoundException: javassist.NotFoundException
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 9 more
FATAL ERROR in native method: processing of -javaagent failed

До этого момента,коренная причина более очевидна. Это происходит потому, что при запуске программы эти соответствующие классы javassist (например, ClassPool, CtClass, CtMethod и т. Д.) Не могут ссылаться на соответствующие библиотеки во время выполнения.

Итак, решение - :

  1. при условии, что вы экспортировали static_agent.jar в ту же папку «build», что и application.jar

  2. все остальные структуры папок остаются такими же, как показано в предоставленном вами github

  3. давайте "cd" для build папка в командной консоли

  4. исправление исходного сценария запуска программы, как показано ниже

ОС Windows:

java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar;application.jar doeke.application.TestApplication

Unix/ ОС Linux:

java -javaagent:static_agent.jar="doeke.application.TestApplication;test" -cp ../libs/javassist-3.12.1.GA.jar:application.jar doeke.application.TestApplication

Вы, наконец, получите ожидаемый результат:

[Agent] In premain method.
>> doeke.application.TestApplication
>> test
[Agent] Transforming class
--- start ---
0
[Application] Withdrawal operation completed in:0 seconds!
1
[Application] Withdrawal operation completed in:0 seconds!

РЕДАКТИРОВАТЬ

Кроме того, позвольтеЯ вставил несколько кодов, касающихся того, как вставлять коды в середине метода через javassist.

В случае, если метод test () в TestApplication.java был изменен на:

line 30    public static void test() {
line 31        System.out.println(count++);
line 32        
line 33        System.out.println("Last line of test() method");
line 34    }

Предположим, чтомы хотим добавить строку между count и ========= , скажем, «Это разделитель строк», результат которого будет выглядеть какe:

1 
-- This is line separator -- 
Last line of test() method

Затем, в методе transform (...) Transformer.java, вы можете добавить строку кода следующим образом:

m.insertAt(32,"System.out.println(\"-- This is line separator --\");");

, что делает его:

@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
        ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
    byte[] byteCode = classfileBuffer;

    String finalTargetClassName = this.targetClassName.replaceAll("\\.", "/");
    if (!className.equals(finalTargetClassName)) {
        return byteCode;
    }

    if (className.equals(finalTargetClassName) && loader.equals(targetClassLoader)) {
        System.out.println("[Agent] Transforming class TestApplication");
        try {
            // Step 1 Preparation
            ClassPool cp = ClassPool.getDefault();
            CtClass cc = cp.get(targetClassName);
            CtMethod m = cc.getDeclaredMethod(targetMethodName);

            // Step 2 Declare variables
            m.addLocalVariable("startTime", CtClass.longType);
            m.addLocalVariable("endTime", CtClass.longType);
            m.addLocalVariable("opTime", CtClass.longType);

            // Step 3 Insertion of extra logics/implementation
            m.insertBefore("startTime = System.currentTimeMillis();");

            m.insertAt(32,"System.out.println(\"-- This is line separator --\");");

            StringBuilder endBlock = new StringBuilder();

            endBlock.append("endTime = System.currentTimeMillis();");
            endBlock.append("opTime = (endTime-startTime)/1000;");
            endBlock.append("System.out.println(\"[Application] Withdrawal operation completed in:\" + opTime + \" seconds!\");");

            m.insertAfter(endBlock.toString());

            // Step 4 Detach from ClassPool and clean up stuff
            byteCode = cc.toBytecode();
            cc.detach();
        } catch (NotFoundException | CannotCompileException | IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    return byteCode;
}

Наконец, будет получен результат, подобный приведенному ниже, при печати кода в середине метода:

[Agent] In premain method.
className=doeke.application.TestApplication
methodName=test
>> doeke.application.TestApplication
>> test
[Agent] Transforming class TestApplication
--- start ---
0
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
1
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
2
-- This is line separator --
=========
[Application] Withdrawal operation completed in:0 seconds!
...