Java Language
Agenti Java
Ricerca…
Modifica delle classi con gli agenti
Innanzitutto, assicurati che l'agente in uso abbia i seguenti attributi in Manifest.mf:
Can-Redefine-Classes: true
Can-Retransform-Classes: true
L'avvio di un agente java consentirà all'agente di accedere alla Strumentazione della classe. Con Instrumentation è possibile chiamare addTransformer (trasformatore ClassFileTransformer) . ClassFileTransformers ti consente di riscrivere i byte delle classi. La classe ha un solo metodo che fornisce ClassLoader che carica la classe, il nome della classe, un'istanza java.lang.Class, è ProtectionDomain e infine i byte della classe stessa.
Sembra questo:
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer)
Modificare una classe puramente da byte può richiedere anni. Per ovviare a questo ci sono librerie che possono essere utilizzate per convertire i byte di classe in qualcosa di più utilizzabile.
In questo esempio userò ASM, ma altre alternative come Javassist e BCEL hanno caratteristiche simili.
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;
}
Da qui le modifiche possono essere apportate all'oggetto ClassNode. Ciò rende incredibilmente facile cambiare campo / metodo di accesso. Inoltre con ASM's Tree API modificare il bytecode dei metodi è un gioco da ragazzi.
Una volta terminate le modifiche, puoi convertire nuovamente il ClassNode in byte con il seguente metodo e restituirli nel metodo di trasformazione :
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;
}
Aggiunta di un agente in fase di runtime
Agenti possono essere aggiunti a una JVM in fase di runtime. Per caricare un agente è necessario utilizzare VirtualMachine.attatch (ID stringa) di Attach API. È quindi possibile caricare un jar compilato con il seguente metodo:
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);
}
}
Questo non chiamerà premain ((String agentArgs, Instrumentation inst) nell'agente caricato, ma invece chiamerà agentmain (String agentArgs, Instrumentation inst) . Ciò richiede che Agent-Class sia impostata nell'agente Manifest.mf.
Impostazione di un agente di base
La classe Premain conterrà il metodo "premain (String agentArgs Instrumentation inst)"
Ecco un esempio:
import java.lang.instrument.Instrumentation;
public class PremainExample {
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println(agentArgs);
}
}
Quando viene compilato in un file jar, apri il manifest e assicurati che abbia l'attributo Premain-Class.
Ecco un esempio:
Premain-Class: PremainExample
Per utilizzare l'agente con un altro programma java "myProgram", è necessario definire l'agente negli argomenti JVM:
java -javaagent:PremainAgent.jar -jar myProgram.jar