了解如何使用visitFrame

时间:2013-12-05 04:12:12

标签: java bytecode java-bytecode-asm

我正在读取JAR文件中的一堆类,我计划在Java中注入一个简单的方法(然后转储新的jar),它将一些数据发布到PHP文件中:

public static void post(final String n, final String o){
    try{
        final URL url = new URL("http://urltophpfile.com/phpfile.php");
        final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setReadTimeout(60000);
        connection.setConnectTimeout(60000);
        connection.setDoInput(true);
        connection.setDoOutput(true);
        connection.setRequestMethod("POST");
        connection.addRequestProperty("User-Agent", "useragent");
        final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
        writer.write(String.format("n=%s&p=%s", n, o));
        writer.flush();
        writer.close();
        connection.getInputStream().read();
    }catch(IOException ex){
        ex.printStackTrace();
    }
}

然后我使用Intellij Bytecode Viewer查看了它的字节码,产生了:

    public static post(Ljava/lang/String;Ljava/lang/String;)V
    TRYCATCHBLOCK L0 L1 L2 java/io/IOException
            L0
    LINENUMBER 11 L0
    NEW java/net/URL
            DUP
    LDC "http://urltophpfile.com/phpfile.phpp"
    INVOKESPECIAL java/net/URL.<init> (Ljava/lang/String;)V
    ASTORE 2
    L3
    LINENUMBER 12 L3
    ALOAD 2
    INVOKEVIRTUAL java/net/URL.openConnection ()Ljava/net/URLConnection;
    CHECKCAST java/net/HttpURLConnection
    ASTORE 3
    L4
    LINENUMBER 13 L4
    ALOAD 3
    LDC 60000
    INVOKEVIRTUAL java/net/HttpURLConnection.setReadTimeout (I)V
            L5
    LINENUMBER 14 L5
    ALOAD 3
    LDC 60000
    INVOKEVIRTUAL java/net/HttpURLConnection.setConnectTimeout (I)V
            L6
    LINENUMBER 15 L6
    ALOAD 3
    ICONST_1
    INVOKEVIRTUAL java/net/HttpURLConnection.setDoInput (Z)V
            L7
    LINENUMBER 16 L7
    ALOAD 3
    ICONST_1
    INVOKEVIRTUAL java/net/HttpURLConnection.setDoOutput (Z)V
            L8
    LINENUMBER 17 L8
    ALOAD 3
    LDC "POST"
    INVOKEVIRTUAL java/net/HttpURLConnection.setRequestMethod (Ljava/lang/String;)V
            L9
    LINENUMBER 18 L9
    ALOAD 3
    LDC "User-Agent"
    LDC "useragent"
    INVOKEVIRTUAL java/net/HttpURLConnection.addRequestProperty (Ljava/lang/String;Ljava/lang/String;)V
            L10
    LINENUMBER 19 L10
    NEW java/io/BufferedWriter
            DUP
    NEW java/io/OutputStreamWriter
            DUP
    ALOAD 3
    INVOKEVIRTUAL java/net/HttpURLConnection.getOutputStream ()Ljava/io/OutputStream;
    INVOKESPECIAL java/io/OutputStreamWriter.<init> (Ljava/io/OutputStream;)V
    INVOKESPECIAL java/io/BufferedWriter.<init> (Ljava/io/Writer;)V
    ASTORE 4
    L11
    LINENUMBER 20 L11
    ALOAD 4
    LDC "n=%s&p=%s"
    ICONST_2
    ANEWARRAY java/lang/Object
            DUP
    ICONST_0
    ALOAD 0
    AASTORE
            DUP
    ICONST_1
    ALOAD 1
    AASTORE
    INVOKESTATIC java/lang/String.format (Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
    INVOKEVIRTUAL java/io/BufferedWriter.write (Ljava/lang/String;)V
            L12
    LINENUMBER 21 L12
    ALOAD 4
    INVOKEVIRTUAL java/io/BufferedWriter.flush ()V
            L13
    LINENUMBER 22 L13
    ALOAD 4
    INVOKEVIRTUAL java/io/BufferedWriter.close ()V
            L14
    LINENUMBER 23 L14
    ALOAD 3
    INVOKEVIRTUAL java/net/HttpURLConnection.getInputStream ()Ljava/io/InputStream;
    INVOKEVIRTUAL java/io/InputStream.read ()I
            POP
    L1
    LINENUMBER 26 L1
    GOTO L15
    L2
    LINENUMBER 24 L2
    FRAME SAME1 java/io/IOException
    ASTORE 2
    L16
    LINENUMBER 25 L16
    ALOAD 2
    INVOKEVIRTUAL java/io/IOException.printStackTrace ()V
            L15
    LINENUMBER 27 L15
    FRAME SAME
    RETURN
            L17
    LOCALVARIABLE url Ljava/net/URL; L3 L1 2
    LOCALVARIABLE connection Ljava/net/HttpURLConnection; L4 L1 3
    LOCALVARIABLE writer Ljava/io/BufferedWriter; L11 L1 4
    LOCALVARIABLE ex Ljava/io/IOException; L16 L15 2
    LOCALVARIABLE n Ljava/lang/String; L0 L17 0
    LOCALVARIABLE o Ljava/lang/String; L0 L17 1
    MAXSTACK = 6
    MAXLOCALS = 5
}

然后我转换为ASM:

    final MethodNode postMethod = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "post", "(Ljava/lang/String;Ljava/lang/String;)V", null, null);
    final Label tryStart = new Label();
    postMethod.visitLabel(tryStart);
    postMethod.visitTypeInsn(Opcodes.NEW, "java/net/URL");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitLdcInsn("http://urltophpfile.com/phpfile.php");
    postMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/net/URL", "<init>", "(Ljava/lang/String;)V");
    postMethod.visitVarInsn(Opcodes.ASTORE, 2);
    postMethod.visitVarInsn(Opcodes.ALOAD, 2);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/URLConnection", "openConnection", "()Ljava/net/URLConnection;");
    postMethod.visitTypeInsn(Opcodes.CHECKCAST, "java/net/HttpURLConnection");
    postMethod.visitVarInsn(Opcodes.ASTORE, 3);
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn(60000);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setReadTimeout", "(I)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn(60000);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setConnectTimeout", "(I)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitInsn(Opcodes.ICONST_1);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoInput", "(Z)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitInsn(Opcodes.ICONST_1);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoOutput", "(Z)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn("POST");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "setRequestMethod", "(Ljava/lang/String;)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitLdcInsn("User-Agent");
    postMethod.visitLdcInsn("useragent");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V");
    postMethod.visitTypeInsn(Opcodes.NEW, "java/io/BufferedWriter");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitTypeInsn(Opcodes.NEW, "java/io/OutputStreamWriter");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "getOutputStream", "()Ljava/io/OutputStream;");
    postMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/io/OutputStreamWriter", "<init>", "(Ljava/io/OutputStream;)V");
    postMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/io/BufferedWriter", "<init>", "(Ljava/io/Writer;)V");
    postMethod.visitVarInsn(Opcodes.ASTORE, 4);
    postMethod.visitVarInsn(Opcodes.ALOAD, 4);
    postMethod.visitLdcInsn("n=%s&p=%s");
    postMethod.visitInsn(Opcodes.ICONST_2);
    postMethod.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitInsn(Opcodes.ICONST_0);
    postMethod.visitVarInsn(Opcodes.ALOAD, 0);
    postMethod.visitInsn(Opcodes.AASTORE);
    postMethod.visitInsn(Opcodes.DUP);
    postMethod.visitInsn(Opcodes.ICONST_1);
    postMethod.visitVarInsn(Opcodes.ALOAD, 1);
    postMethod.visitInsn(Opcodes.AASTORE);
    postMethod.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)L/java/lang/String;");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/BufferedWriter", "write", "(Ljava/lang/String;)V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 4);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/BufferedWriter", "flush", "()V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 4);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/BufferedWriter", "close", "()V");
    postMethod.visitVarInsn(Opcodes.ALOAD, 3);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/HttpURLConnection", "getInputStream", "()Ljava/io/InputStream;");
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/InputStream", "read", "()I");
    postMethod.visitInsn(Opcodes.POP);
    final Label gotoEnd = new Label();
    postMethod.visitJumpInsn(Opcodes.GOTO, gotoEnd);
    // FRAME SAME1 java/io/IOException <- how to create instruction?
    final Label catchStart = new Label();
    postMethod.visitLabel(catchStart);
    postMethod.visitVarInsn(Opcodes.ASTORE, 2);
    postMethod.visitVarInsn(Opcodes.ALOAD, 2);
    postMethod.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/IOException", "printStackTrace", "()V");
    final Label tryEnd = new Label();
    postMethod.visitLabel(tryEnd);
    postMethod.visitLabel(gotoEnd);
    // FRAME SAME <- how to create instruction?
    postMethod.visitInsn(Opcodes.RETURN);
    postMethod.visitTryCatchBlock(tryStart, tryEnd, catchStart, "java/io/IOException");
    postMethod.visitMaxs(6, 5);

问题在于,当我尝试将所有类转储到jar并启动它时,我收到异常:

Exception in thread "AWT-EventQueue-0" java.lang.VerifyError: Expecting a stackmap frame at branch target 124
Exception Details:
Location:
        Client.post(Ljava/lang/String;Ljava/lang/String;)V @0: new
Reason:
Expected stackmap frame at this location.
        Bytecode:
        0000000: bb0d ed59 1316 21b7 1119 4d2c b616 27c0
0000010: 1629 4e2d 1316 2ab6 162d 2d13 162a b616
0000020: 302d 04b6 1633 2d04 b616 362d 1316 38b6
0000030: 163b 2d13 163d 1316 3fb6 1642 bb14 5059
        0000040: bb16 4459 2db6 1645 b716 48b7 1456 3a04
0000050: 1904 1316 4a05 bd03 0059 032a 5359 042b
0000060: 53b8 164d b616 4f19 04b6 1652 1904 b614
0000070: 652d b616 53b6 1657 57a7 0008 4d2c b613
0000080: 9ab1
Exception Handler Table:
bci [0, 129] => handler: 124

我收到此异常,因为我不知道如何正确使用visitFrame方法。我试着看documentation for visitFrame,但这没什么用。有人可以解释如何在我的场景中正确使用visitFrame吗?非常感谢任何帮助,谢谢。

1 个答案:

答案 0 :(得分:7)

借助Java 6,Oracle引入了堆栈映射框,使Java类的运行时验证更加容易。 (在更容易运行时更容易,而不像更容易为字节代码的作者。)这样的框架的想法是你带走了一些重量的Java运行时验证程序通过告诉验证器操作数堆栈上有哪种值以及在跳转指令的每个目标的本地变量中存储了哪些值。使用ASM时,必须通过调用visitFrame来提供所有这些信息。这样,验证器不必在运行时推断这些值。在Java 6中,堆栈映射帧的存在是可选的,在Java 7中,堆栈映射帧变为必需的。

您的代码包含两个字节代码级跳转指令:

  • 发生异常的catch块的开头
  • 在方法结束之前的catch块的末尾,当没有异常发生时控制流跳转到(当在Java源代码中可以跳过时,字节代码总是包含显式返回语句)< / LI>

因此,您必须在调用后添加堆栈映射框:

postMethod.visitJumpInsn(Opcodes.GOTO, gotoEnd);

并在字节代码中显式返回语句之前:

postMethod.visitInsn(RETURN);

然后,此堆栈映射框将添加到方法的堆栈映射表中。因此,您的选择是:

  • 使用较旧的类版本创建Java类(例如Java 5,其中堆栈映射框架未知。
  • 使用较新的Java版本(例如7)编译代码,并在此类上运行ASMifier。您将通过调用MethodVisitor.visitFrame来获取考虑这些堆栈映射帧的示例输出。
  • 使用JVM选项-XX:-UseSplitVerifier禁用验证程序验证堆栈映射框的部分。

如果要创建具有指示由Java 7编译器编译的类的版本号的Java类,则必须在跳转指令的每个目标之后创建这些堆栈映射帧。否则,运行时验证程序会抱怨你观察到的是什么。

为了完整起见,这是我在您的代码上使用ASMifier时得到的结果:

mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "post", "(Ljava/lang/String;Ljava/lang/String;)V", null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
Label l2 = new Label();
mv.visitTryCatchBlock(l0, l1, l2, "java/io/IOException");
mv.visitLabel(l0);
mv.visitTypeInsn(NEW, "java/net/URL");
mv.visitInsn(DUP);
mv.visitLdcInsn("http://urltophpfile.com/phpfile.php");
mv.visitMethodInsn(INVOKESPECIAL, "java/net/URL", "<init>", "(Ljava/lang/String;)V");
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/URL", "openConnection", "()Ljava/net/URLConnection;");
mv.visitTypeInsn(CHECKCAST, "java/net/HttpURLConnection");
mv.visitVarInsn(ASTORE, 3);
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn(new Integer(60000));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setReadTimeout", "(I)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn(new Integer(60000));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setConnectTimeout", "(I)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoInput", "(Z)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setDoOutput", "(Z)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn("POST");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "setRequestMethod", "(Ljava/lang/String;)V");
mv.visitVarInsn(ALOAD, 3);
mv.visitLdcInsn("User-Agent");
mv.visitLdcInsn("useragent");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V");
mv.visitTypeInsn(NEW, "java/io/BufferedWriter");
mv.visitInsn(DUP);
mv.visitTypeInsn(NEW, "java/io/OutputStreamWriter");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "getOutputStream", "()Ljava/io/OutputStream;");
mv.visitMethodInsn(INVOKESPECIAL, "java/io/OutputStreamWriter", "<init>", "(Ljava/io/OutputStream;)V");
mv.visitMethodInsn(INVOKESPECIAL, "java/io/BufferedWriter", "<init>", "(Ljava/io/Writer;)V");
mv.visitVarInsn(ASTORE, 4);
mv.visitVarInsn(ALOAD, 4);
mv.visitLdcInsn("n=%s&p=%s");
mv.visitInsn(ICONST_2);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 0);
mv.visitInsn(AASTORE);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_1);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/String", "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedWriter", "write", "(Ljava/lang/String;)V");
mv.visitVarInsn(ALOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedWriter", "flush", "()V");
mv.visitVarInsn(ALOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/BufferedWriter", "close", "()V");
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/net/HttpURLConnection", "getInputStream", "()Ljava/io/InputStream;");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/InputStream", "read", "()I");
mv.visitInsn(POP);
mv.visitLabel(l1);
Label l3 = new Label();
mv.visitJumpInsn(GOTO, l3);
mv.visitLabel(l2);
mv.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/io/IOException"});
mv.visitVarInsn(ASTORE, 2);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/IOException", "printStackTrace", "()V");
mv.visitLabel(l3);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitInsn(RETURN);
mv.visitMaxs(6, 5);
mv.visitEnd();