ByteBuddy присоединяется к локальному запущенному процессу - PullRequest
1 голос
/ 06 марта 2019

Я пытаюсь использовать ByteBuddy для подключения к запущенному процессу, запущенному на моем компьютере.Я ожидаю, что в то время, когда я присоединяюсь к работающей программе, мой агент заставит загруженные классы быть перезагруженными и для моих операторов печати Transformer будут отображаться.

Вместо этого происходит то, что, когда я останавливаюзапущенный процесс, к которому я присоединяюсь, я вижу некоторые операторы печати из моего Transformer для некоторых классов JDK.

Код, указанный ниже:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy;
import net.bytebuddy.implementation.FixedValue;

import java.io.*;

import static net.bytebuddy.matcher.ElementMatchers.named;

public class Thief {

    public static void main(String[] args) throws Throwable {
        String pid = "86476"; // <-- modify this to attach to any java process running on your computer
        System.out.println(new Thief().guessSecurityCode(pid));
    }

    public String guessSecurityCode(final String pid) throws Throwable {
        File jarFile = createAgent();
        ByteBuddyAgent.attach(jarFile, pid);
        return "0000";
    }


    private static String generateSimpleAgent() {

        return  "import java.lang.instrument.ClassFileTransformer;" + "\n" +
                "import java.lang.instrument.Instrumentation;" + "\n" +
                "import java.security.ProtectionDomain;" + "\n" +
                "\n\n" +
                "public class Agent {" +"\n" +
                "    public static void agentmain(String argument, Instrumentation inst) {" +"\n" +
                "        inst.addTransformer(new ClassFileTransformer() {" +"\n" +
                "            @Override" +"\n" +
                "            public byte[] transform(" +"\n" +
                "                ClassLoader loader," +"\n" +
                "                String className," +"\n" +
                "                Class<?> classBeingRedefined," +"\n" +
                "                ProtectionDomain protectionDomain," +"\n" +
                "                byte[] classFileBuffer) {" +"\n" +
                "            System.out.println(\"transform on : \" +className);" +"\n" +
                "            return classFileBuffer;" +"\n" +
                "            }" +"\n" +
                "        });" +"\n" +
                "    }" +"\n" +
                "}" +"\n";
    }

    private static String generateAgentManifest() {
        return  String.join("\n", "Agent-Class: Agent",
                                                         "Can-Retransform-Classes: true",
                                                         "Can-Redefine-Classes: true",
                                                         "Premain-Class: Agent"
        );
    }

    private static String generateAgentManifest2() {
        return  String.join("\n",
                "Manifest-Version: 1.0",
                            "Agent-Class: Agent",
                            "Permissions: all-permissions"
        );
    }

    private static String generateTransformer() {
        return String.join("\n",
                "import java.lang.instrument.ClassFileTransformer;",
                            "import java.security.ProtectionDomain;",
                            "import java.util.Arrays;",
                            "public class Transformer implements ClassFileTransformer {",
                            "    public byte[] transform(ClassLoader loader, String className, Class<?> cls, ProtectionDomain dom, byte[] buf) {",
                            "        return null;",
                            "    }",
                            "}"
        );
    }

    private static void writeFile(String path, String data) throws IOException {
        final PrintWriter out = new PrintWriter(path);
        out.print(data);
        out.close();
    }

    private static void runCommand(String cmd) throws Exception {
        System.out.println("[commmand] " + cmd);
        String s;
        Process p = Runtime.getRuntime().exec(cmd);
        BufferedReader out = new BufferedReader(new InputStreamReader(p.getInputStream()));
        while ((s = out.readLine()) != null) {
            System.out.println("[out] " + s);
        }
        out = new BufferedReader(new InputStreamReader(p.getErrorStream()));
        while ((s = out.readLine()) != null) {
            System.out.println("[err] " + s);
        }
        p.waitFor();
        System.out.println("[exit status] " + p.exitValue());
        p.destroy();
    }

    private static File createAgent() throws Throwable {
        writeFile("Agent.java", generateSimpleAgent());
        writeFile("Transformer.java", generateTransformer());
        writeFile("manifest.mf", generateAgentManifest2());
        runCommand("javac Agent.java Transformer.java");
        runCommand("jar -cfm agent.jar manifest.mf Agent.class Transformer.class");
        return new File("agent.jar");
    }
}

1 Ответ

1 голос
/ 07 марта 2019

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

Чтобы повторно преобразовать классы, вы сначала должны использовать addTransformer(yourTransformer, true) для регистрации, затем вызвать retransformClasses с классами, которые вы хотите преобразовать. Помните о существовании getAllLoadedClasses и getInitiatedClasses(ClassLoader)

В качестве дополнительного примечания я настоятельно не рекомендую придерживаться подхода встраивания Java-агента в виде строк исходного кода, необходимости записывать их во временные файлы, вызывать компилятор и, в конечном итоге, создавать файл jar. Вы можете легко интегрировать классы агента в ваш обычный исходный код. Затем, чтобы создать файл jar, содержащий только классы агента, вам просто нужно скопировать уже существующие файлы .class из базы кода вашего приложения в jar агента. В простых случаях вы можете одновременно сделать файл jar приложения действительным файлом jar агента и просто использовать его без каких-либо дополнительных шагов копирования.

Кроме того, имейте в виду, что ClassFileTransformer всегда должен возвращать null для всех классов, которые не меняются. Возвращение оригинальных байтов файла класса будет семантически одинаковым, но это требует дополнительных усилий на стороне вызывающего, чтобы выяснить, что вы не изменили его. Для кода, который будет вызываться для каждого загруженного класса, но обычно его интересуют лишь немногие (или он просто хочет напечатать информацию без каких-либо изменений), такие проблемы с производительностью имеют значение.

...