问题
I'm trying to do this by re-writing the bytecode of the class using ASM 4.0 to replace all the native
methods with non-native
stubs.
So far I have this:
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);
}
}
which is executed by
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();
}
Which seems simple enough: I strip the ACC_NATIVE
off of the method in visitMethod()
and leave everything else unchanged. However, when I do this to java.lang.Object
, it dies with a
Exception in thread "main"
Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"
The StackOverflow happens at instrumentation time, not at runtime, which I think is rather unusual. However, if I remove the & ~Opcodes.ACC_NATIVE
modifier, java.lang.Object
gets rewritten (in this case unchanged) and executes perfectly.
Clearly I am not doing something right, and replacing the native
method with a non-native
method isn't quite as simple as stripping off the native
modifier on the method, but I have no idea where to start. The ASM Docs don't talk about working with native
methods at all. Does anyone with experience working with ASM know what I need to do to get the native
method re-writing to work?
EDIT
Sorry, that short, useless message was what e.printStackTrace()
was giving me, but using e.getStackTrace()
I managed to get something useful:
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)
So it seems to me that the error was in fact happening at execution time (e.g. I was mistaken in thinking it was at instrumentation time) and is the result of calling hashCode()
. As it so happens, hashCode()
one of the native methods which I (probably incorrectly) stripped of it's native
modifier. So clearly it's calling the native
-stripped methods which is causing the problem.
What seems really odd is that the stack trace is only 16 frames deep; I'd have expected kinda more given that it was a StackOverflowError
.
回答1:
It's not quite as simple to replace native code with stubs but it's not far from that
If you look at ClassVisitor#visitMethod(int access, String name, String desc, String signature, String[] exceptions) you'll see that it returns a
MethodVisitor
MethodVisitor which you now have to make use of. If you want to make abstract stubs, you should add at least the call to
methodVisitor.visitEnd()
If you want to make empty stubs, you have to add
visitCode
and also return a value if necessary
回答2:
To elaborate on the accepted answer here is a fully working example of an instrumentation agent which uses ASM to replace the native method java.net.NetworkInterface#getHardwareAddress()
with a stub that returns a fixed value.
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();
}
}
}
来源:https://stackoverflow.com/questions/13274771/rewriting-java-native-methods-using-asm