Rewriting Java native methods using ASM

*爱你&永不变心* 提交于 2021-02-07 04:55:30

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!