Java Language
Agents Java
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