使用ASM重写Java本地方法

17

我正试图通过使用ASM 4.0重新编写类的字节码,以用非native存根替换所有native方法。

到目前为止,我有以下内容:

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);
    }

}

由...执行

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();
}

这似乎很简单:我从 visitMethod() 方法中删除 ACC_NATIVE,并保留其他所有内容。然而,当我对 java.lang.Object 进行此操作时,它会出错。

Exception in thread "main" 
Exception: java.lang.StackOverflowError thrown from the UncaughtExceptionHandler in thread "main"

StackOverflow问题发生在instrumentation时间而不是runtime,我认为这相当不寻常。但是,如果我删除& ~Opcodes.ACC_NATIVE 修饰符,java.lang.Object 就会被重写(在这种情况下没有更改)并正常执行。

显然我做错了什么,用非native方法替换native方法并不像简单地去掉方法上的native修饰符那样简单,但我不知道从哪里开始。 ASM文档根本不讨论如何使用native方法。有没有使用ASM经验的人知道我需要做什么才能使native方法重写起作用?

编辑

抱歉,那个简短、无用的消息是e.printStackTrace() 给我的,但是使用e.getStackTrace() 我成功地得到了一些有用的信息:

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)

据我看来,错误实际上是在执行时发生的(例如,我错误地认为它是在插装时发生的),并且是调用hashCode()方法的结果。恰好,hashCode()是我(可能不正确地)去掉了其native修饰符的原生方法之一。因此,显然是调用被去除native修饰符的方法导致了问题。

真正奇怪的是,堆栈跟踪仅深入了16层;考虑到它是一个StackOverflowError,我本来期待有更多的层。


1
+1,我不理解的第一个Java/JNI堆栈溢出问题 - Aniket Inge
谷歌搜索“asm原生重写”会出现这个问题,这不是一个好迹象! - Brian Agnew
你是否给该方法添加了 Code 属性?如果没有,我会怀疑 ASM 会感到困惑,因为它没有足够的信息来编写文件。 - kdgregory
1
kdgregory,ASM 不关心。但我无法重现 OP 示例中的问题,涉及 java.lang.Object 类。请提供堆栈跟踪信息。 - Eugene Kuleshov
1
我怀疑你不应该从java.lang.Object中去除本地方法。如果你试图修改非JRE类,为什么不过滤掉JRE类,看看是否仍然遇到这个问题? - Andy
显示剩余6条评论
2个回答

2

1
为了详细说明这个被接受的答案,这里提供一个完整的示例。这个示例是一个仪器代理,使用ASM替换本地方法java.net.NetworkInterface#getHardwareAddress(),并用一个存根返回固定值。请注意,保留HTML标签。
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();
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接