Posts tagged ‘opcodes’

Niby nic, a jednak. Wymyśliłem sobie, że pewien program w Javie zamiast wypluwać XML’a, który później musi być przyjęty jako konfiguracja innego programu w Javie będzie generował bytecode klasy z wypełnionymi danymi, tak, żeby nie trzeba bylo tracić ani czasu ani zasobów na konwersje XML’a do jakiegos znośnego formatu.

Jak zwykle trzeba sie na początek troche porozglądać, żeby sprawdzić jakie są dostępne narzędzia. Do głowy przyszło mi wykorzystywane w springu i hibernate cglib, ale strone mają jakąś toporną, więc szukałem dalej. Moje wcześniejsze poszukiwania opcode’ów w php , zwróciło moją uwagę w kierunku specyfikacji Virtualnej Machiny 🙂, no i rzuciłem ją na tapetę, ale że to kobyła i do tego nudna jak flaki z olejem, to postanowiłem wypróbować czegoś w praktyce.

Poszukiwania przywiodły mnie do bilioteki asm.

Pozostało tylko pisać, ale asm oferuje jeszcze jedno świetne narzędzie, plugin do eclipse’a pozwalający:

– podejrzeć bytecode danej klasy

– podejrzeć kod samego ASM potrzebny do wygenerowania aktualnie wybranego fragmentu kodu

Szczególnie druga  opcja daje spore pole do popisu. Można po prostu napisać kawałek kodu w edytorze, zaznaczyć go i zobaczyć jakie instrukcje trzeba wykonać, żeby coś identycznego wypluć jako bytecode. Dla mnie świetna sprawa do pokombinowania troche, świetnej zabawy, a przy okazji szybkiego zdobywania wiedzy.

Oto wynik:

import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.aspectj.org.objectweb.asm.ClassWriter;
import org.aspectj.org.objectweb.asm.Label;
import org.aspectj.org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Constants;

public class Bla implements Constants {
public Bla(){

}

public void bla(String s){
System.out.println(s);
}
public static void bla(int s){}

public static void main(String[] args) throws Exception {
ClassWriter cw = new ClassWriter(false);
cw.visit(
V1_5, // version
ACC_PUBLIC+ACC_SUPER, // access
"asm1/Notifier", // class name
null, // signature
"java/lang/Object", // super class
(String[])null // interfaces
);

MethodVisitor mv;
mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitInsn(RETURN);
Label l2 = new Label();
mv.visitLabel(l2);
mv.visitLocalVariable("this", "LBla;", null, l0, l2, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();

mv = cw.visitMethod( ACC_PUBLIC,
"notify", // method name
"(Ljava/lang/String;)V", // description
"", // method descriptor
new String[]{} // exceptions
);
mv.visitCode();
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();

cw.visitEnd();

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("/tmp/asm1/Notifier.class")));
out.write(cw.toByteArray());
out.flush();
out.close();

Class c = Class.forName("asm1.Notifier");

Object o = c.newInstance();

Method m = c.getMethod("notify", new Class[]{String.class});
m.invoke(o, new Object[]{"Hello World!"});
m.invoke(o, new Object[]{"Some other text :)"});
}

}

Jak widać, kod np konstruktora jest żywcem przepisany z okienka „bytecode”, ale co najważniejsze TO DZIAŁA!

Pare pomocnych wskazówek:

– odpalenie skompilowanego programu spod Eclipse poprzez run, nie zadziałało, pokazał mi błedy z zarządzaniem zabezpieczeniami i tyle sie skończylo, ale po odpowiednim dostarczeniu argumentów z wiersza poleceń (używając -Xbootclasspath/p: orac -classpath) działa jak złoto

– sam „bytecode plugin” nie działa w wersji Eclipse’a 3.4 i trzeba sobie dodać nowy URL do listy źródeł oprogramowania, a na liście pojawią się pluginy dla tejże wersji

– jak zapewne widać moja klasa Bla, posłużyła za szablon instrukcji jakie trzeba wykonać, żeby wypisać poprawny bytecode (metoda bla oraz konstruktor) a piszę o tym po to, żeby zaczęcić do tejże metody

Narazie prace nad moim rozszerzeniem stanęły w miejscu, ale poszukując informacji nt. budowy takowego znalazłem pare ciekawych artykułów i wszystkie oscylują wokół wykonywania php i przetwarzania kodu na wewnętrzną reprezentację Zend engine, czli opcode’y (nie wiem jaki jest odpowiednik po polsku), oto one.

Na początek wspominana już wcześniej przeze mnie Sarah Golemon i jej wprowadzenie do opcodeów nowy link. Później  Terry Chay (chyba, wniskuję z nazwy domeny) opisuje swoją batalię z Zend enginem. Następnie lista opcodeów lokalnie zapisana z opisem i wytłumaczeniem. I ostatni z nich, czyli jak są Zend engine przetwarza tablice.

A wszystko zaczęło sie od tego, że zacząłem sie zastanawiać jakby tu udoskonalić jakiś system szablonów np. Smarty. Pierwszy strzał padł na rozszerzenie języka, dystrybuowane jako pliki źródłowe, kompilowane dynamicznie (jako .so), ale teraz myślę, że chyba lepiej by było rozszerzyć sam Zend engine o mapowanie kodów danego języka szablonów, bezpośrednio na opcode’y, ale do tego to jeszcze długa droga.

Są jeszcze 2 rzeczy, które planuje wykorzystać jako drogowskazy. Są to gotowe rozszerzenia php, dostępne przez system instalacji PECL. Jedno z nich to opcode cache, które ma zapewniać, ponowne wykorzystywanie opcodeów, tak żeby nie była potrzebna ich kompilacja – czyli autorzy też musieli się dość mocno wgryźć w sam Zend engine i na to właśnie liczę:)

Druga to już chyba nieco prostsza sprawa: pecl_http , które ma chować cała magię związaną z wysyłaniem i odbieraniem żądania HTTP, a że przeglądałem podobne rozwiązania zarówno w Apache Httpd (ma całkiem miły framework w C do budowania rozszerzeń i filtrów), HttpServletRequest w Javie (specyfikacja Servlet 2.4), kończąc na mojej implementacji (na potrzeby Fligg’a) to doszedłem do wniosku, że pomimo różnic między wszystkimi prezentowanymi rozwiązaniami, łatwiej mi się będzie przegryzać przez rozszerzenie pecl’owe.

No nic zobaczymy jak z czasem będzie, może pecl_smarty kiedyś ujrzy światło dzienne.