Recherche…


Modification de classes avec des agents

Tout d'abord, assurez-vous que l'agent utilisé possède les attributs suivants dans le fichier Manifest.mf:

Can-Redefine-Classes: true
Can-Retransform-Classes: true

Le démarrage d'un agent Java permettra à l'agent d'accéder à la classe Instrumentation. Avec Instrumentation, vous pouvez appeler addTransformer (Transformateur ClassFileTransformer) . ClassFileTransformers vous permettra de réécrire les octets des classes. La classe n'a qu'une seule méthode qui fournit le ClassLoader qui charge la classe, le nom de la classe, une instance java.lang.Class, c'est ProtectionDomain et enfin les octets de la classe elle-même.

Cela ressemble à ceci:

byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, 
          ProtectionDomain protectionDomain, byte[] classfileBuffer)

La modification d'une classe uniquement à partir d'octets peut prendre des années. Pour remédier à cela, il existe des bibliothèques qui peuvent être utilisées pour convertir les octets de classe en quelque chose de plus utilisable.

Dans cet exemple, je vais utiliser ASM, mais d'autres alternatives comme Javassist et BCEL ont des fonctionnalités similaires.

ClassNode getNode(byte[] bytes) {
    // Create a ClassReader that will parse the byte array into a ClassNode
    ClassReader cr = new ClassReader(bytes);
    ClassNode cn = new ClassNode();
    try {
        // This populates the ClassNode
        cr.accept(cn, ClassReader.EXPAND_FRAMES);
        cr = null;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return cn;
}

De là, des modifications peuvent être apportées à l'objet ClassNode. Cela rend incroyablement facile l'accès aux champs / méthodes. De plus, avec l'API Tree d'ASM, modifier le bytecode des méthodes est un jeu d'enfant.

Une fois les modifications terminées, vous pouvez convertir le ClassNode en octets avec la méthode suivante et les renvoyer dans la méthode de transformation :

public static byte[] getNodeBytes(ClassNode cn, boolean useMaxs) {
    ClassWriter cw = new ClassWriter(useMaxs ? ClassWriter.COMPUTE_MAXS : ClassWriter.COMPUTE_FRAMES);
    cn.accept(cw);
    byte[] b = cw.toByteArray();
    return b;
}

Ajout d'un agent à l'exécution

Les agents peuvent être ajoutés à une machine virtuelle Java lors de l'exécution. Pour charger un agent, vous devez utiliser VirtualMachine.attatch (String String) de l'API Attach. Vous pouvez ensuite charger un jar d'agent compilé avec la méthode suivante:

public static void loadAgent(String agentPath) {
    String vmName = ManagementFactory.getRuntimeMXBean().getName();
    int index = vmName.indexOf('@');
    String pid = vmName.substring(0, index);
    try {
        File agentFile = new File(agentPath);
        VirtualMachine vm = VirtualMachine.attach(pid);
        vm.loadAgent(agentFile.getAbsolutePath(), "");
        VirtualMachine.attach(vm.id());
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

Cela n'appelle pas premain ((String agentArgs, Instrumentation inst) dans l'agent chargé, mais appelle plutôt agentmain (String agentArgs, instrumentation inst) . Cela nécessite que la classe d' agent soit définie dans l'agent Manifest.mf.

Mise en place d'un agent de base

La classe Premain contiendra la méthode "premain (String agentArgs Instrumentation inst)"

Voici un exemple:

import java.lang.instrument.Instrumentation;

public class PremainExample {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println(agentArgs);
    }
}

Une fois compilé dans un fichier jar, ouvrez le manifeste et assurez-vous qu'il possède l'attribut Premain-Class.

Voici un exemple:

Premain-Class: PremainExample

Pour utiliser l'agent avec un autre programme Java "myProgram", vous devez définir l'agent dans les arguments JVM:

java -javaagent:PremainAgent.jar -jar myProgram.jar


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow