Javaのバイトコードで遊ぶ (Hello World)
ASMを使ってJavaのバイトコードを生成して遊んでみます。 まずはその準備について。 Javaのバージョンは1.8.0_60、ASMのバージョンは5.0.3です。
生成したバイトコードを実行したり、クラスファイルに書き込みたいので そのためのユーティリティ(ExampleUtil)を準備します。 生成するクラスが持つmainメソッドを実行することにします。
private static class MyClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } public static void execMain(String className, byte[] bytes) throws IllegalAccessException, IllegalArgumentException, NoSuchMethodException, SecurityException { MyClassLoader myClassLoader = new MyClassLoader(); Class<?> c = myClassLoader.defineClass(className, bytes); try { c.getMethod("main", String[].class).invoke(null, new Object[] { new String[0] }); } catch (InvocationTargetException e) { e.getCause().printStackTrace(); } } public static void write(String className, byte[] bytes) throws IOException { File dir = new File("target" + File.separator + "output"); if (!dir.exists()) dir.mkdirs(); DataOutputStream out = new DataOutputStream( new FileOutputStream(new File(dir, className + ".class"))); out.write(bytes); out.flush(); out.close(); }
次にバイトコードを生成する処理です。
private static byte[] generate(String className) { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null); // constructor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); // main mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // println mv.visitLdcInsn("Hello world."); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); cw.visitEnd(); return cw.toByteArray(); }
generateメソッドが返すbyte[]を受け取って、実行したり、ファイルに出力したりする部分。
public static void main(String... args) throws Exception { String className = "HelloWorld"; byte[] bytes = generate(className); ExampleUtil.execMain(className, bytes); ExampleUtil.write(className, bytes); }
これを実行して、「Hello world.」という文字列が表示されればOKです。 出力したクラスファイルを実行するには以下のコマンド。
$ java HelloWorld Hello world.
出力したクラスファイルの中身を覗く場合は以下。オプションは適宜変更すればOKです。
$ javap -v -p -s HelloWorld Classfile /home/vagrant/sandbox/workspaces/asm_practice/AsmPlaying/target/output/HelloWorld.class Last modified 2015/10/13; size 341 bytes MD5 checksum 01828f9e45f7fe516438e7d3c51b04f4 public abstract class HelloWorld minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Utf8 HelloWorld #2 = Class #1 // HelloWorld #3 = Utf8 java/lang/Object #4 = Class #3 // java/lang/Object #5 = Utf8 <init> #6 = Utf8 ()V #7 = NameAndType #5:#6 // "<init>":()V #8 = Methodref #4.#7 // java/lang/Object."<init>":()V #9 = Utf8 main #10 = Utf8 ([Ljava/lang/String;)V #11 = Utf8 java/lang/System #12 = Class #11 // java/lang/System #13 = Utf8 out #14 = Utf8 Ljava/io/PrintStream; #15 = NameAndType #13:#14 // out:Ljava/io/PrintStream; #16 = Fieldref #12.#15 // java/lang/System.out:Ljava/io/PrintStream; #17 = Utf8 Hello world. #18 = String #17 // Hello world. #19 = Utf8 java/io/PrintStream #20 = Class #19 // java/io/PrintStream #21 = Utf8 println #22 = Utf8 (Ljava/lang/Object;)V #23 = NameAndType #21:#22 // println:(Ljava/lang/Object;)V #24 = Methodref #20.#23 // java/io/PrintStream.println:(Ljava/lang/Object;)V #25 = Utf8 Code { public HelloWorld(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #18 // String Hello world. 5: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 8: return }
これをベースに次回からいろいろなバイトコードを生成して遊んでみたいと思います。 最後にコードの全体を載せておきます。
ExampleUtil
package example; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; public class ExampleUtil { private static class MyClassLoader extends ClassLoader { public Class<?> defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } public static void execMain(String className, byte[] bytes) throws IllegalAccessException, IllegalArgumentException, NoSuchMethodException, SecurityException { MyClassLoader myClassLoader = new MyClassLoader(); Class<?> c = myClassLoader.defineClass(className, bytes); try { c.getMethod("main", String[].class).invoke(null, new Object[] { new String[0] }); } catch (InvocationTargetException e) { e.getCause().printStackTrace(); } } public static void write(String className, byte[] bytes) throws IOException { File dir = new File("target" + File.separator + "output"); if (!dir.exists()) dir.mkdirs(); DataOutputStream out = new DataOutputStream( new FileOutputStream(new File(dir, className + ".class"))); out.write(bytes); out.flush(); out.close(); } }
HelloWorld
package example; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; public class HelloWorld implements Opcodes { private static byte[] generate(String className) { ClassWriter cw = new ClassWriter(0); MethodVisitor mv; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", null); // constructor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); mv.visitInsn(RETURN); mv.visitMaxs(1, 1); mv.visitEnd(); // main mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); // println mv.visitLdcInsn("Hello world."); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); mv.visitInsn(RETURN); mv.visitMaxs(2, 1); mv.visitEnd(); cw.visitEnd(); return cw.toByteArray(); } public static void main(String... args) throws Exception { String className = "HelloWorld"; byte[] bytes = generate(className); ExampleUtil.execMain(className, bytes); ExampleUtil.write(className, bytes); } }