Posts tagged ‘ASM’

ASM #5: Memakai Agen

Pada tulisan sebelumnya, class hasil transformasi tidak dapat dipakai secara langsung, melainkan harus memakai interface atau superclass-nya. Bagaimana jika aku tetap ingin memakai seperti biasa tanpa membuat interface atau superclass? Salah satu solusi yang mungkin adalah dengan memakai agent. Class tetap dapat dipakai seperti biasa, bahkan tanpa membuat classloader baru. Hal ini karena agent akan dikerjakan saat JVM dijalankan.

JVM akan mencari dan mengerjakan method milik agent class yang definisinya seperti berikut:

public static void premain(String agentArgs, Instrumentation inst);
atau
public static void premain(String agentArgs);

Berikut ini adalah contoh agent class yang aku pakai:

public class Agent {

  public static void premain(String agentArgs, Instrumentation inst) {
    inst.addTransformer(new ClassFileTransformer(){

      @Override
      public byte[] transform(ClassLoader loader, String className,
          Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
          byte[] classfileBuffer) throws IllegalClassFormatException {

        if (className.equals("latihan/Music")) {
          try {

            ClassReader reader = new ClassReader(classfileBuffer);
            ClassWriter writer = new ClassWriter(reader, 0);
            ToStringAdapter adapter = new ToStringAdapter(writer);

            reader.accept(adapter,0);
            return writer.toByteArray();

          } catch (Exception ex) {
            ex.printStackTrace();
          }
        }
        return classfileBuffer;
      }

    });
  }

}

Agent di atas akan melakukan transformasi melalui ToStringAdapter (turunan dari ClassAdapter) yang sudah aku buat di tulisan sebelumnya. Untuk dapat memakai agent ini, aku masih harus membuat jar dari class ini, serta menambahkan manifest yang isinya sebagai berikut:

Manifest-Version: 1.0
Main-Class: latihan.Main
Premain-Class: latihan.Agent
Boot-Class-Path: lib/asm-3.1.jar lib/asm-commons-3.1.jar lib/asm-util-3.1.jar

Entry yang dibutuhkan untuk agent adalah Premain-Class dan Boot-Class-Path. Fungsinya dapat ditebak dari namanya: Premain-Class sama seperti Main-Class, hanya saja ia menyatakan class mana yang merupakan agent. Boot-Class-Path sama seperti Class-Path, hanya saja ia menyatakan classpath yang khusus dipakai oleh agent.

Sekarang, class Music dapat dipakai layaknya class normal lainnya (tentu saja dengan pengecualian sudah punya definisi method toString() secara otomatis):

public class Main {

  public Main() {
    Music m = new Music();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Btw, untuk menjalankan program di atas, aku tidak lupa menambahkan argument -javaagent agar JVM mengerjakan agent class, seperti:

java -javaagent:latihanASM.jar -jar latihanASM.jar

05 September 2009 at 10:44 AM Tinggalkan Komentar

ASM #4: Ready For Action

Setelah mempelajari cara kerja JVM dan bytecode Java, aku siap untuk melakukan modifikasi class Java secara dinamis. Sebagai contoh kasus, aku akan mengubah method toString() untuk setiap bean agar menampilkan nilai dari setiap field secara otomatis.

Seperti yang telah aku catat sebelumnya, sebuah class dikenali oleh JVM melalui kombinasi dari classloader dan nama class-nya. Untuk melakukan instrumentasi bytecode, aku pasti membuat classloader sendiri. Sebagai efek sampingnya, class hasil instrumentasi tidak akan bisa dipakai oleh class yang di-load oleh JVM sebelumnya. Yup, tidak dapat dipakai secara langsung, tetapi dapat dipakai melalui superclass atau interface-nya. Aku akan mencoba memakai metode interface di tulisan kali ini. Next time, aku akan mencoba memakai agent untuk melakukan instrumentasi class yang di-load JVM. Dengan agent, aku tidak perlu memakai superclass atau interface, tetapi proses menjalankan program menjadi lebih panjang.

Byte code instrumentation dengan interface memang tidak begitu cocok untuk contoh kasus kali ini, tapi setidaknya ini cara yang paling gampang. Aku akan membuat interface Music dan implementasi-nya MusicImpl.

public interface Music {

  public String getMusicID();
  public void setMusicID(String musicId);
  public String getTitle();
  public void setTitle(String title);

}

Lalu, aku akan membuat classloader yang melakukan instrumentasi untuk MusicImpl seperti berikut:

import java.util.*;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.*;

public class MyClassLoader extends ClassLoader {

  @Override
  protected Class<?> findClass(String name) throws ClassNotFoundException {
    if (name.endsWith("Impl")) {
      try {
        ClassReader reader = new ClassReader(name);
        ClassWriter writer = new ClassWriter(reader,0);
        ToStringAdapter adapter = new ToStringAdapter(writer);
        reader.accept(adapter,0);
        byte b[] = writer.toByteArray();
        return defineClass(name,b,0,b.length);

      } catch (Exception e) {
        e.printStackTrace();
      }

    }
    return super.findClass(name);
  }

  protected class ToStringAdapter extends ClassAdapter {

    private Map<String,String> mapFields;
    private String className;

    public ToStringAdapter(ClassVisitor cv) {
      super(cv);
      mapFields = new HashMap<String,String>();
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {
      if (name.equals("toString")) {
        return null;
      }
      return super.visitMethod(access, name, desc, signature, exceptions);
    }

    @Override
    public void visitEnd() {
      MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
      mv.visitCode();
      mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
      mv.visitInsn(DUP);
      mv.visitLdcInsn(className + ": ");
      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V");

      for (String field : mapFields.keySet()) {
        mv.visitLdcInsn(field + "=[");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, className, field, mapFields.get(field));
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/Object;)Ljava/lang/StringBuilder;");

        mv.visitLdcInsn("]; ");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append",
          "(Ljava/lang/String;)Ljava/lang/StringBuilder;");

      }

      mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString",
        "()Ljava/lang/String;");
      mv.visitInsn(ARETURN);
      mv.visitMaxs(3,1);
      mv.visitEnd();
      super.visitEnd();
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
        String signature, Object value) {
      if (access==ACC_PRIVATE) {
        mapFields.put(name,desc);
      }
      return super.visitField(access, name, desc, signature, value);
    }

    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {
      className = name;
      super.visit(version, access, name, signature, superName, interfaces);
    }

  }

}

Pada classloader tersebut, aku akan menambahkan method toString() pada semua class yang namanya diakhiri dengan “Impl”. Jika class tersebut sudah memiliki method toString(), aku akan menghapus method toString() yang sudah ada sebelum menambahkan yang baru. Untuk mempersingkat kode program, aku menganggap bahwa class yang di-transformasi memiliki field turunan dari Object saja, bukan tipe native seperti int, char, dsb. Hal ini karena aku men-hard code method append() yang aku panggil dari class StringBuilder dengan descriptor (Ljava/lang/Object;)Ljava/lang/StringBuilder;

Sekarang aku dapat mencoba memakai classloader di atas, misalnya dengan kode sederhana berikut:

public class Main {

  public Main() throws Exception {
    MyClassLoader myCL = new MyClassLoader();
    Music m = (Music) myCL.findClass("latihan.MusicImpl").newInstance();
    m.setMusicID("MID-123");
    m.setTitle("TITLE");
    System.out.println("Music = " + m);
  }

  public static void main(String[] args) throws Exception {
    new Main();
  }

}

Dan outputnya adalah:

Music = latihan/MusicImpl: title=[TITLE]; musicID=[MID-123];

Hal ini juga berlaku untuk seluruh class lain yang namanya diakhir dengan “Impl”.. Aku tidak perlu lagi membuat method toString() secara manual..

05 September 2009 at 2:03 AM Tinggalkan Komentar

ASM #3: Kode-Kode Bahasa Java

Hari ini aku akan mempelajari bagaimana byte code Java (atau bahasa assembler-nya Java) bekerja. Yang akan menjadi “bahan penelitian” adalah method berikut:

public String toString() {
  return "[MUSIC]: MusicID=[" + musicID + "]; title=[" + title + "]";
}

Setelah di-compile, method di atas akan memiliki atribut code seperti berikut:

00 03 00 01 00 00 00 25 
bb 00 27 59 12 29 b7 00 
2b 2a b4 00 14 b6 00 2d 
12 31 b6 00 2d 2a b4 00 
16 b6 00 2d 12 33 b6 00 
2d b6 00 35 b0 00 00 00 
02 00 18 00 00 00 06 00 
01 00 00 00 1f 00 19 00 
00 00 0c 00 01 00 00 00 
25 00 1a 00 1b 00 00

Wew, bagaimana menerjemahkan byte code yang tampak asing ini? Dua byte pertama, 00 03, adalah jumlah maksimum untuk operand stack. Nilai ini harus dihitung oleh compiler secara manual. Dua byte berikutnya, 00 01, adalah jumlah variabel lokal yang dipakai dalam method (termasuk parameter). Empat byte berikutnya, 00 00 00 25, menunjukkan kalau ada 0×25 (desimal 37) byte berikutnya yang berisi byte code. Dengan demikian, bagian yang benar-benar berisi byte code adalah 25 byte berikutnya, yaitu:

bb 00 27 59 12 29 b7 00 
2b 2a b4 00 14 b6 00 2d 
12 31 b6 00 2d 2a b4 00 
16 b6 00 2d 12 33 b6 00
2d b6 00 35 b0

Byte pertama, 0xbb menunjukkan bahwa instruksi tersebut adalah instruksi new. Dua byte berikutnya, 00 27, adalah referensi ke constant pool index ke-0×27 atau ke-39, dimana berupa class java/lang/StringBuilder. Ini artinya, JVM akan membuat instance baru dari class java.lang.StringBuilder dan men-push referensi instance tersebut ke operand stack.

Instruksi berikutnya adalah, 0×59, adalah instruksi dup. Instruksi ini tidak membutuhkan operand. Pada saat menemukan instruksi ini, JVM akan men-push nilai yang sama dengan nilai yang berada paling atas di operand stack saat ini.

Operand Stack (Sebelum dup):

| Instance StringBuilder | <-- TOP

Operand Stack (Setelah dup):

| Instance StringBuilder | <-- TOP
| Instance StringBuilder |

Instruksi berikutnya 0×12, adalah instruksi ldc. Instruksi ini akan men-load operand-nya (byte berikut-nya, yaitu 0×29 atau 41) yang merupakan referensi index di constant pool ke operand stack. Index ke-41 di operand stack adalah sebuah String “[MUSIC]: MusicID=[". Dengan demikian, isi operand stack akan menjadi:

Operand Stack (Setelah ldc):
| "[Music: MusicID=["      |  <-- TOP
| Instance StringBuilder   |
| Instance StringBuilder   |

P.S: Jumlah operand stack tidak boleh melebihi maksimum yang telah ditentukan sebelumnya, yaitu 3. Jika ada perintah yang men-push sekali lagi, maka JVM akan memunculkan pesan kesalahan.

Instruksi berikutnya, 0xb7, adalah instruksi invokespecial. Instruksi ini membutuhkan dua byte operand, 00 2b (desimal 43), yang merupakan referensi ke method <init> di constant pool. Selain itu, instruksi ini akan mengambil informasi instance class mana yang akan dikerjakan method-nya melalui informasi di operand stack. Karena method yang didefinisikan di index 43 adalah constructor StringBuffer yang mengambil sebuah parameter String, maka isi operand stack menjadi:

Operand Stack (Setelah invokespecial):
| Instance StringBuilder   | <-- TOP

Berikutnya, terdapat instruksi 0x2a atau aload_0. Instruksi ini akan me-load nilai yang terdapat di local variable array yang berada di index 0 ke operand stack. Seperti yang kita tahu, nilai yang paling awal di local variable array adalah referensi ke class yang sedang aktif (this). Akibatnya, isi operand stack akan menjadi:

Operand Stack (Setelah aload_0):
| this (instance class Music)      | <-- TOP
| Instance StringBuilder           |

Instruksi berikutnya, 0xb4, adalah instruksi getfield. Instruksi ini akan men-push nilai field yang ditunjukkan oleh dua byte berikutnya, 00 14 (desimal 20), ke operand stack. Sebelumnya, ia akan men-pop terlebih dahulu dari operand stack untuk mengetahui class mana yang akan diambil nilai field-nya. Karena index ke-20 adalah referensi ke field musicID, maka isi operand stack akan menjadi:

Operand Stack (Setelah getfield):
| "nilai musicID"        | <-- TOP
| Instance StringBuilder |

Instruksi berikutnya, 0xb6, adalah instruksi invokevirtual. Instruksi ini akan mengerjakan method yang direferensikan oleh dua byte berikutnya, 00 2d (atau desimal 45), yaitu method append. Setelah pengerjaan method append, isi operand stack akan menjadi:

Operand Stack (Setelah invokevirtual):
| Instance StringBuilder | <-- TOP

Instruksi berikutnya, 0x12, kembali lagi merupakan instruksi ldc, untuk men-push String "]; title=[" ke operand stack. Setelah itu, instruksi 0xb6 (invokevirtual) akan mengerjakan method append milik StringBuffer. Selanjutnya, instruksi 0x2a (aload_0) akan me-load nilai this ke operand stack. Dan instruksi 0xb4 (getfield) akan mengambil nilai dari field title dan men-push nilai tersebut ke operand stack. Instruksi 0xb6 (invokevirtual) kembali mengerjakan method append. Instruksi 0x12 (ldc) yang berikutnya akan men-push String "]” ke operand stack. Instruksi berikutnya 0xb6 kembali mengerjakan method append. Instruksi 0xb6 berikutnya akan mengerjakan method toString milik StringBuffer. Dan instruksi terakhir, 0xb0 adalah instruksi areturn, yang akan keluar dari method serta mengembalikan nilai berupa referensi class yang akan di-pop dari operand stack.

Wew, perjalanan yang cukup panjang hanya untuk sebuah method yang sangat sederhana, bahkan hanya satu baris saja. Untungnya, aku tidak perlu selalu menerjemahkan bytecode dengan cara manual seperti ini. Di situs ObjectWeb, dimana aku mendownload ASM, aku juga dapat men-download plugin Eclipse untuk melihat isi byte code dari sebuah source code Java. Setelah meng-install plugin tersebut, aku dapat memilih Window, Show View, Byte Code. Akan muncul sebuah window Byte Code di sebelah kanan workbench yang akan berisi dissasembler dari source code Java dimana kursor editor sedang aktif. Ini adalah output untuk method yang aku pakai di tulisan ini:

  public toString()Ljava/lang/String;
   L0
    LINENUMBER 31 L0
    NEW java/lang/StringBuilder
    DUP
    LDC "[MUSIC]: MusicID=["
    INVOKESPECIAL java/lang/StringBuilder.<init>(Ljava/lang/String;)V
    ALOAD 0
    GETFIELD latihan/Music.musicID : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "]; title=["
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0
    GETFIELD latihan/Music.title : Ljava/lang/String;
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    LDC "]"
    INVOKEVIRTUAL java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString()Ljava/lang/String;
    ARETURN
   L1
    LOCALVARIABLE this Llatihan/Music; L0 L1 0
    MAXSTACK = 3
    MAXLOCALS = 1

29 Agustus 2009 at 11:42 PM Tinggalkan Komentar

ASM #2: Membedah File Class

Hari ini aku akan mempelajari bagaimana format sebuah file class. Untuk itu, aku membuat sebuah file class dari source code sederhana berikut:

package latihan;

public class Music {

  private String musicID;
  private String title;
  private final String musicPrefix = "01";

  public Music(String musicId, String title) {
    super();
    musicID = musicId;
    this.title = title;
  }
  public String getMusicID() {
    return musicID;
  }
  public void setMusicID(String musicId) {
    musicID = musicId;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public static Music getMusic(String id) {
    return null;
  }

}

Setelah selesai men-compile Music.java menjadi Music.class, aku membuka file Music.class melalui hex editor untuk melihat isinya. Setiap file class Java selalu diawali dengan empat byte 0xca, 0xfe, 0xba, 0xbe. Cafe Babe, huh? Setelah itu terdapat dua byte yang berisi minor version (nilainya 0) dan dua byte yang berisi major version (nilainya 50 pada contoh class-ku).

Dua byte berikutnya, 00 35, menunjukkan bahwa di dalam constant pool terdapat 34 item. Nilai ini merupakan jumlah item di constant pool ditambah dengan 1. Setelah itu, berikutnya adalah kumpulan byte untuk constant pool. Ukuran dan isinya dapat berbeda-beda tergantung kode program. Secara umum, constant pool terdiri atas satu atau lebih item, dimana setiap item selalu diawali dengan sebuah byte yang berisi jenis item (byte ini disebut tag).

Sebagai contoh, berikut ini adalah sebagian isi constant pool untuk kode program di atas:

Item #1
Tag: Method References
Class Index: 7
Name & Type Index: 27

...

Item #7
Tag: Class
Name Index: 33

...

Item #13
Tag: UTF8
String Value: <init>

...

Item #25
Tag: UTF8
String Value: SourceFile

Item #26
Tag: UTF8
String Value: Music.java

Item #27
Tag: Name & Type
Name Index: 13
Descriptor Index: 34

...

Item #33
Tag: UTF8
String Value: java/lang/Object

Item #34
Tag: UTF8
String Value: ()V

Item pertama menunjukkan informasi method, untuk class yang ada di item ke-7 (yaitu java/lang/Object). Informasi mengenai method yang ditunjukkan oleh item pertama dapat dilihat lebih lanjut di item ke-27, yang selanjutnya memberikan informasi mengenai method bernama “<init>” (item ke-13) yang descriptor-nya adalah “()V” (item ke-34). Btw, aku tidak membuat method dengan nama “<init>” di source code, darimana munculnya method “<init>”? Ini adalah nama yang khusus diberikan untuk constructor.

Setelah isi constant pool, terdapat dua byte yang berisi access flag. Nilai flag ini menunjukkan apakah class ini termasuk class final, abstract, atau merupakan interface. Nilai untuk class percobaan hari ini adalah 0×0021 yang merupakan kombinasi dari flag 0×0001 (class public) dan flag 0×0020 (selalu harus di-set untuk compiler baru).

Berikutnya terdapat dua byte yang merujuk pada item index di constant pool yang berisi referensi class ini (class yang dirujuk oleh keyword this). Lalu, berikutnya terdapat dua byte yang merujuk pada item index di constant pool yang berisi referensi super-class (parent-class). Dalam contoh class percobaan, super class-nya adalah java/lang/Object (item ke-7 di constant pool). Nilai dua byte ini dapat berupa 0×0000, jika class ini adalah java/lang/Object (satu-satunya class di Java yang tidak punya super class).

Setelah itu terdapat dua byte yang berisi jumlah inteface yang di-implement oleh class ini, diikuti dengan rangkaian index di constant pool untuk menjelaskan interface tersebut. Karena class percobaanku tidak men-implementasi-kan interface, nilai interface count adalah 0.

Berikutnya adalah dua byte yang berisi jumlah field/variabel yang didefinisikan dalam class ini (nilainya adalah 3, karena aku hanya mendefinisikan 3 variabel di source code). Berikutnya adalah struktur yang menjelaskan informasi field tersebut. Ukurannya bisa berbeda tergantung pada jumlah field/variabel yang didefinisikan di dalam class ini. Berikut ini adalah isi informasi field pada class percobaan hari ini:

Field #0
Access Flag:  PRIVATE
Name Index: 8
Descriptor Index: 9
Attribute Count: 0

Field #1
Access Flag:  PRIVATE
Name Index: 10
Descriptor Index: 9
Attribute Count: 0

Field #2
Access Flag:  PRIVATE  FINAL
Name Index: 11
Descriptor Index: 9
Attribute Count: 1
  Attribute Name Index: 12
  Attribute Length: 2 [00 02 ]

Setelah informasi field, terdapat informasi mengenai method. Dua byte pertama, seperti biasa, menunjukkan jumlah method yang didefinisikan dalam class ini (nilainya adalah 6, karena aku mendefinisikan 6 method di source code). Berikutnya, terdapat informasi mengenai method. Pada bagian ini terdapat informasi byte code Java untuk masing-masing method yang terletak di atribut dengan nama “Code”. Aku akan mempelajari lebih lanjut tentang atribut ini di kemudian hari. Ini adalah contoh informasi method pada class percobaan:

Method #0
Access Flag:  PUBLIC
Name Index: 13
Descriptor Index: 14
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 61
00 02 00 03 00 00 00 15
2a b7 00 01 2a 12 02 b5
00 03 2a 2b b5 00 04 2a
2c b5 00 05 b1 00 00 00
01 00 10 00 00 00 16 00
05 00 00 00 0a 00 04 00
07 00 0a 00 0b 00 0f 00
0c 00 14 00 0d 

Method #1
Access Flag:  PUBLIC
Name Index: 17
Descriptor Index: 18
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 29
00 01 00 01 00 00 00 05
2a b4 00 04 b0 00 00 00
01 00 10 00 00 00 06 00
01 00 00 00 0f 

Method #2
Access Flag:  PUBLIC
Name Index: 19
Descriptor Index: 20
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 34
00 02 00 02 00 00 00 06
2a 2b b5 00 04 b1 00 00
00 01 00 10 00 00 00 0a
00 02 00 00 00 12 00 05
00 13 

Method #3
Access Flag:  PUBLIC
Name Index: 21
Descriptor Index: 18
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 29
00 01 00 01 00 00 00 05
2a b4 00 05 b0 00 00 00
01 00 10 00 00 00 06 00
01 00 00 00 15 

Method #4
Access Flag:  PUBLIC
Name Index: 22
Descriptor Index: 20
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 34
00 02 00 02 00 00 00 06
2a 2b b5 00 05 b1 00 00
00 01 00 10 00 00 00 0a
00 02 00 00 00 18 00 05
00 19 

Method #5
Access Flag:  PUBLIC  STATIC
Name Index: 23
Descriptor Index: 24
Attribute Count: 1
Atribute Name Index: 15
Atribute Length: 26
00 01 00 01 00 00 00 02
01 b0 00 00 00 01 00 10
00 00 00 06 00 01 00 00
00 1b

Dan bagian yang paling terakhir dari sebuah file class Java adalah atribut untuk class tersebut. Dua byte pertama berisi informasi jumlah atribut, di-ikuti dengan definisi atribut. Ini adalah contoh informasi atribut untuk class percobaanku:

Attribute #0
Attribute Name Index: 25
Attribute Length: 2
00 1a

Atribut tersebut adalah atribut “Source File” yang nilainya adalah referensi ke item 0x1a (atau desimal 26) di constant pool, yang nilainya tidak lain adalah “Music.java”.

23 Agustus 2009 at 12:42 AM Tinggalkan Komentar

ASM #1: JVM, Mesin Yang Tidak Nyata

Untuk melakukan bytecode instrumentation melalui library seperti ASM, aku setidaknya harus mengerti bagaimana cara kerja JVM.  Setiap program Java, atau tepatnya class Java, akan dijalankan oleh Java Virtual Machine (JVM).  Mirip seperti mesin asli, JVM juga memiliki struktur-struktur seperti register dan stack.

Masing-masing thread yang dijalankan oleh JVM memiliki sebuah register Program Counter (pc).  Register pc berisi lokasi alamat instruksi yang sedang dikerjakan.  Untuk method native,  nilai register pc tidak didefinisikan.

Setiap thread di JVM juga memiliki Java Virtual Machine Stack yang kegunaannya mirip seperti di program biasa seperti C, seperti menampung nilai variabel lokal dan hasil perhitungan sementara.

Seluruh thread di JVM memiliki memory yang di-share yang disebut heap. Heap adalah lokasi memori yang berisi informasi instance dari sebuah class dan array.  Garbage collector akan bekerja secara otomatis untuk menentukan instance yang tidak dibutuhkan lagi dan membebaskan lokasi memori di heap sehingga memori dapat dipakai ulang.

Setiap kali sebuah method dipanggil, JVM akan membuat sebuah frame di Java Virtual Machine stack untuk thread bersangkutan.  Setelah method selesai dikerjakan, JVM akan memusnahkan frame tersebut.   Di dalam frame terdapat informasi local variables.  Sebuah “slot” local variable dapat mengandung nilai boolean, byte, char, short, int, float, reference, atau returnAddress.  Nilai long atau double memerlukan dua “slot”  local variable.

Setiap “slot” local variable memiliki index berurut mulai dari 0, 1, 2, dst.   Pada saat method mulai dijalankan, local variable 0 akan berisi referensi ke object yang mengandung method tersebut (nilai this dalam program Java).  Local variable 1, 2, dst berisi nilai parameter secara berurut.

Frame juga mengandung apa yang disebut operand stack yang banyak dipakai oleh instruksi JVM untuk menulis dan membaca nilai.  Pada saat frame pertama kali dibuat, operand stack tidak memiliki isi.

Instruksi JVM terdiri atas sebuah byte yang berisi opcode, diikuti oleh operands (jika ada).  Kebanyakan instruksi JVM bekerja pada tipe data tertentu.  Sebagai contoh, instruksi yang diawali dengan huruf i bekerja pada data int, huruf l bekerja pada data long, huruf s bekerja pada data short, huruf b bekerja pada data byte, huruf c bekerja pada data char, huruf f bekerja pada data float, huruf d bekerja pada data double, dan huruf a bekerja pada data reference.  O ya, di bahasa pemograman Java ada tipe data boolean, tapi  JVM tidak mengenal istilah boolean.   JVM akan menganggap boolean sebagai integer.

Instruksi load dan store dipakai untuk mengambil nilai dan menulis nilai ke dalam local variable. Sebagai contoh, iload 1 akan menulis local variable 1 yang bertipe int ke dalam operand stack.  Sebaliknya, istore 1 akan menulis nilai int yang ada di operand stack ke local variable 1.

Contoh instruksi aritmatika yang tersedia seperti iadd (penjumlahan), isub (pengurangan), imul (perkalian), idiv (pembagian),  irem (modulus), ineg (negation), ishl (shift left), ishr (shift right), ior (bitwise OR), iand (bitwise AND), ixor (bitwise XOR), iinc (local variable increment), lcmp (perbandingan).  Sebagai contoh, instruksi iadd akan menjumlahkan dua nilai int yang ada di operand stack (men-pop dua nilai terakhir), kemudian menyimpan hasil penjumlahan ke operand stack (men-push hasil penjumlahan).

JVM juga menyediakan beberapa instruksi untuk konversi, seperti i2l (int to long), i2f (int to float), i2d (int to double), l2f (long to float), l2d (long to double) dan f2d (float to double).

Untuk membuat instance class baru, terdapat instruksi new.  Untuk membuat array baru, tersedia instruksi newarray, anewarray, dan multianewarray.  Untuk meng-akses field dari sebuah object, instruksi berikut dapat dipergunakan: getfield, putfield, getstatic, putstatic.  Beberapa instruksi lain yang berkaitan dengan class: arraylength, instanceof, dan checkcast.

Instruksi berikut dipakai untuk memanipulasi operand stack: pop, pop2, dup, dup2, dup_xl, dup2_xl, dup_x2, dup2_x2, dan swap.

Contoh instruksi yang dipakai untuk control transfer (percabangan), misalnya: ifeq (jika sama dengan), iflt (jika lebih kecil), tableswitch, goto, dan ret.

Untuk memanggil method dari sebuah class, instruksi berikut dapat dipergunakan: invokevirtual, invokeinterface, invokespecial, dan invokestatic.  Untuk keluar dari method, instruksi berikut dapat dipergunakan sesuai dengan tipe kembalian dari method: return, ireturn, lreturn, freturn, dreturn, dan areturn.  Untuk membuat exception, instruksi athrow dapat dipergunakan.

Untuk mendukung sinkronisasi (keyword synchronize di bahasa pemograman Java), JVM menggunakan monitor, dan menyediakan instruksi berikut: monitorenter dan monitorexit.

17 Agustus 2009 at 2:08 AM Tinggalkan Komentar

ASM: Assemblernya Java

Just kidding… ASM yang sedang aku pelajarin bukanlah bahasa mesin Java, tetapi suatu library untuk mengotak-atik class Java yang sudah tercompile. Konon nama library tersebut diambil dari keyword __asm__ di C, yang kepanjangannya mungkin adalah assembler.

Seandainya aku memiliki sebuah class dari source berikut:

package latihan;

public class Music {

  private String musicID;
  private String title;

  public String getMusicID() {
    return musicID;
  }
  public void setMusicID(String musicId) {
    musicID = musicId;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }

}

Class tersebut aku simpan dengan nama di folder c:\ dengan nama music.class. Dengan bantuan ASM, aku akan membuat program untuk membaca file music.class dan menampilkan setiap variabel dan method milik class tersebut:

public class Main {

  public Main() {
    try {
      BufferedInputStream bis = new BufferedInputStream(new FileInputStream("c:\\Music.class"));
      ClassReader classReader = new ClassReader(bis);
      classReader.accept(new ClassEventHandler(), 0);
      bis.close();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
  }

  public static void main(String[] args) {
    new Main();
  }

  protected class ClassEventHandler implements ClassVisitor {

    @Override
    public void visit(int version, int access, String name,
        String signature, String superName, String[] interfaces) {

      System.out.println("Classname: " + name);
      System.out.println("Superclass Name: " + superName);
      System.out.println("Implements Interface: " + interfaces.toString() + "\n");

    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
      return null;
    }

    @Override
    public void visitAttribute(Attribute attr) {
    }

    @Override
    public void visitEnd() {
      System.out.println("\nVisit End");
    }

    @Override
    public FieldVisitor visitField(int access, String name, String desc,
        String signature, Object value) {

      System.out.println("Field Descriptor: " + desc);
      System.out.println("Field Name: " + name);
      System.out.println("\n");
      return null;

    }

    @Override
    public void visitInnerClass(String name, String outerName,
        String innerName, int access) {
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc,
        String signature, String[] exceptions) {

      System.out.println("Method Descriptor: " + desc);
      System.out.println("Method Name: " + name);
      System.out.println("\n");
      return null;
    }

    @Override
    public void visitOuterClass(String owner, String name, String desc) {
    }

    @Override
    public void visitSource(String source, String debug) {
    }

  }

}

Hasil dari program di atas adalah:

Classname: latihan/Music
Superclass Name: java/lang/Object
Implements Interface: [Ljava.lang.String;@190d11

Field Descriptor: Ljava/lang/String;
Field Name: musicID

Field Descriptor: Ljava/lang/String;
Field Name: title

Method Descriptor: ()V
Method Name: <init>

Method Descriptor: ()Ljava/lang/String;
Method Name: getMusicID

Method Descriptor: (Ljava/lang/String;)V
Method Name: setMusicID

Method Descriptor: ()Ljava/lang/String;
Method Name: getTitle

Method Descriptor: (Ljava/lang/String;)V
Method Name: setTitle

Visit End

Output di atas memakai penamaan internal setelah class di-compile. Wajar saja, karena yang dibaca bukanlah source code program Java, melainkan class Java yang sudah ter-compile. Sebagai contoh, descriptor Ljava/lang/String menunjukkan bahwa itu adalah tipe data java.lang.String. Nilai lain yang mungkin seperti Z untuk boolean, C untuk char, B untuk byte, dan sebagainya. Descriptor ()V pada method menunjukkan bahwa method tersebut tidak menerima argumen dan tidak mengembalikan nilai (void). Descriptor (Ljava/lang/String;)V menunjukkan bahwa method tersebut tidak mengembalikan nilai (void), dan menerima parameter berupa sebuah object java.lang.String.

ASM tidak hanya bisa membaca class, tapi juga bisa membuat class ke dalam bentuk byte array. Byte array ini nantinya dapat ditulis ke dalam file, atau langsung di-load oleh JVM untuk dipakai. Sebagai contoh, aku akan membuat program Java yang membuat class Music di memory (tanpa membuat source code Music.java):

package latihan;

import java.lang.reflect.Field;
import org.objectweb.asm.ClassWriter;
import static org.objectweb.asm.Opcodes.*;

public class Main {

  public Main() {

    MusicClassLoader cl = new MusicClassLoader();
    try {
      Class<?> c = cl.findClass("latihan.Music");
      System.out.println("Class name = " + c.getName());
      for (Field f : c.getFields()) {
        System.out.println("Field name: " + f.getName() + "; Field type: " + f.getType());
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

  }

  public static void main(String args[]) {
    new Main();
  }

  protected class MusicClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
      if (name.equals("latihan.Music")) {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_6, ACC_PUBLIC, "latihan/Music", null, "java/lang/Object", null);
        cw.visitField(ACC_PUBLIC, "musicID", "Ljava/lang/String;", null, null);
        cw.visitField(ACC_PUBLIC, "title", "Ljava/lang/String;", null, null);
        cw.visitEnd();
        byte[] b = cw.toByteArray();
        return defineClass("latihan.Music", b, 0, b.length);
      }
      return findClass(name);
    }
  }
}

Pada contoh di atas, class latihan.Music yang aku buat on-the-fly terlihat tidak begitu berguna karena ia bukan tidak memiliki ‘kontrak’ yang jelas (misalnya tidak melakukan implementasi interface tertentu). Pada tulisan berikutnya, aku akan mencoba mempelajari fungsi ASM yang lain, yaitu melakukan transformasi class.

16 Agustus 2009 at 9:04 AM Tinggalkan Komentar


Arsip


Ikuti

Get every new post delivered to your Inbox.