standard - Reescritura de métodos nativos de Java utilizando ASM
tag lib jstl (2)
Para elaborar más detalladamente la respuesta aceptada, aquí hay un ejemplo completo de un agente de instrumentación que utiliza ASM para reemplazar el método nativo java.net.NetworkInterface#getHardwareAddress()
con un código auxiliar que devuelve un valor fijo.
public class MacModifyAgent {
private static final String TARGET = "java/net/NetworkInterface";
public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader l, String name, Class<?> c, ProtectionDomain d, byte[] b)
throws IllegalClassFormatException {
if (TARGET.equals(name)) {
return instrument(b);
}
return b;
}
});
}
private static byte[] instrument(byte[] originalBytes) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassAdapter adapter = new ClassAdapter(cw);
ClassReader cr = new ClassReader(originalBytes);
cr.accept(adapter, ClassReader.SKIP_FRAMES);
return cw.toByteArray();
}
public static class ClassAdapter extends ClassVisitor implements Opcodes {
public ClassAdapter(ClassVisitor cv) {
super(ASM4, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
String[] exceptions) {
if ("getHardwareAddress".equals(name)) {
MethodVisitor mv = super.visitMethod(access & ~ACC_NATIVE, name, descriptor, signature, exceptions);
MethodVisitor special = new StubReturnValue(mv, new byte[] { 1, 2, 3, 4, 5, 6 });
return special;
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
}
public static class StubReturnValue extends MethodVisitor implements Opcodes {
private final MethodVisitor target;
private byte[] mac;
public StubReturnValue(MethodVisitor target, byte [] mac) {
super(ASM4, null);
this.target = target;
}
@Override
public void visitCode() {
target.visitCode();
target.visitVarInsn(BIPUSH, 6);
target.visitIntInsn(NEWARRAY, T_BYTE);
for (int i = 0; i < 6; i++) {
target.visitInsn(DUP);
target.visitIntInsn(BIPUSH, i);
target.visitIntInsn(BIPUSH, mac[i]);
target.visitInsn(BASTORE);
}
target.visitInsn(ARETURN);
target.visitEnd();
}
}
}
Intento hacer esto reescribiendo el código de bytes de la clase utilizando ASM 4.0 para reemplazar todos los métodos native
con apéndices no native
.
Hasta ahora tengo esto:
class ClassAdapter extends ClassVisitor {
public ClassAdapter(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@Override
public MethodVisitor visitMethod(int access, String base, String desc, String signature, String[] exceptions) {
return cv.visitMethod(access & ~Opcodes.ACC_NATIVE, base, desc, signature, exceptions);
}
}
que es ejecutado por
private static byte[] instrument(byte[] originalBytes, ClassLoader loader) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
ClassAdapter adapter = new ClassAdapter(cw);
ClassReader cr = new ClassReader(originalBytes);
cr.accept(adapter, ClassReader.SKIP_FRAMES);
return cw.toByteArray();
}
Lo que parece bastante simple: ACC_NATIVE
el ACC_NATIVE
del método en visitMethod()
y dejo todo lo demás sin cambios. Sin embargo, cuando hago esto a java.lang.Object
, muere con un
Exception in thread "main"
Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"
El StackOverflow ocurre en el momento de la instrumentación , no en el tiempo de ejecución , lo que creo que es bastante inusual. Sin embargo, si & ~Opcodes.ACC_NATIVE
modificador & ~Opcodes.ACC_NATIVE
, java.lang.Object
vuelve a escribir (en este caso, sin cambios) y se ejecuta perfectamente.
Claramente, no estoy haciendo algo bien, y reemplazar el método native
con un método no native
no es tan simple como eliminar el modificador native
del método, pero no tengo idea de por dónde empezar. Los documentos de ASM no hablan de trabajar con métodos native
en absoluto. ¿Alguien con experiencia en el trabajo con ASM sabe lo que debo hacer para que el método native
se vuelva a escribir?
EDITAR
Lo siento, ese mensaje corto e inútil fue lo que me dio e.printStackTrace()
, pero al usar e.getStackTrace()
logré obtener algo útil:
java.util.concurrent.ConcurrentHashMap.hash(ConcurrentHashMap.java:332)
java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1124)
java.util.Collections$SetFromMap.add(Collections.java:3903)
sandbox.classloader.MyClassLoader.instrument(Unknown Source)
sandbox.classloader.MyClassLoader.loadClass(Unknown Source)
java.lang.ClassLoader.defineClass1(Native Method)
java.lang.ClassLoader.defineClass(ClassLoader.java:791)
java.lang.ClassLoader.defineClass(ClassLoader.java:634)
sandbox.classloader.MyClassLoader.findClass(Unknown Source)
sandbox.classloader.MyClassLoader.loadClass(Unknown Source)
sandbox.Tester.main(Unknown Source)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:601)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Por lo tanto, me parece que el error ocurrió en el momento de la ejecución (por ejemplo, me equivoqué al pensar que fue en el momento de la instrumentación) y es el resultado de llamar a hashCode()
. Como sucede, hashCode()
uno de los métodos nativos que (de manera incorrecta) eliminé de su modificador native
. Así que claramente está llamando a los métodos native
eliminados que está causando el problema.
Lo que parece realmente extraño es que la traza de la pila tiene solo 16 cuadros de profundidad; Habría esperado un poco más dado que era un StackOverflowError
.
No es tan sencillo reemplazar el código nativo con un código auxiliar, pero no está lejos de eso.
Si observa ClassVisitor # visitMethod (int access, String desc, String desc, String signature, String []) verá que devuelve un
MethodVisitor
MethodVisitor que ahora tienes que utilizar. Si desea realizar apéndices abstractos, debe agregar al menos la llamada a
methodVisitor.visitEnd()
Si desea crear talones vacíos, debe agregar
visitCode
y también devolver un valor si es necesario