Nietypowe wywołanie invokespecial z Jasmin (Java assembler)
Byłem ciekaw czy z klasy Java można wywołać invokespecial na rzecz nieprywatnej metody przywiązanej do instancji obiektu z zewnątrz jego klasy. Za najprostszą metodę stworzenia takiej “dziwnej” klasy uznałem użycie assemblera Jasmin. Był to dobry pretekst do zapoznania się z tym narzędziem.
Stworzyłem dwie klasy w Javie:
Plik A.java:
package eu.zacheusz.invoketry; public class A { public void method () { System.out.println("A.method"); } }
oraz plik B.java:
package eu.zacheusz.invoketry; public class B extends A { @Override public void method () { System.out.println("B.method"); } }
Oraz klasę napisaną w Jasmin. Plik Invoking.j:
.source Invoking.j .class public eu/zacheusz/invoketry/Invoking .super java/lang/Object .method public static main([Ljava/lang/String;)V new eu/zacheusz/invoketry/B ;new B(); dup ;kopiowanie referencji do stworzonego obiektu na stosie invokespecial eu/zacheusz/invoketry/B/()V ;domyślny konstruktor invokespecial eu/zacheusz/invoketry/A/method()V return .end method
Polecenie invokespecial eu/zacheusz/invoketry/A/method()V ma za zadanie wywołać metodę z klasy eu.zacheusz.invoketry.A na rzecz obiektu typu eu.zacheusz.invoketry.B. Normalne wywołanie (invokevirtual) powinno spowodować uruchomienie kodu z przeciążonej metody.
Kompilacja klasy napisanej w assemblerze:
java -jar jasmin.jar -d compiled jassrc\Invoking.j
gdzie jassrc to katalog ze źródłami a compiled katalog przeznaczony na skompilowane klasy (są już w nim eu/zacheusz/invoketry/A.class oraz eu/zacheusz/invoketry/B.class). Wygenerowanie klasy potwierdził komunikat:
Generated: compiled\eu\zacheusz\invoketry\Invoking.class.
Pierwsza próba
java -cp compiled eu.zacheusz.invoketry.Invoking;
zakończla się niepowodzeniem:
Exception in thread "main" java.lang.VerifyError: (class: eu/zacheusz/invoketry/Invoking, method: main signature: ([Ljava/lang/String;)V) Illegal use of nonvirtual function call
Could not find the main class: eu.zacheusz.invoketry.Invoking. Program will exit.
Zadziałał weryfikator. Po wyłączeniu go za pomocą opcji -Xverify:none udało się uruchomić skompilowany kod:
java -Xverify:none -cp compiled eu.zacheusz.invoketry.Invoking
A.method
Mimo, że klasa eu.zacheusz.invoketry.B ma metodę o sygnaturze method()V została wywołana metoda klasy eu.zacheusz.invoketry.A. Po dodaniu kolejnej klasy do hierarchii w pliku C.java:
package eu.zacheusz.invoketry; public class C extends B { @Override public void method () { System.out.println("C.method"); } }
oraz modyfikacji klasy napisanej w assemblerze:
.source Invoking.j .class public eu/zacheusz/invoketry/Invoking .super java/lang/Object .method public static main([Ljava/lang/String;)V new eu/zacheusz/invoketry/B ;new B(); dup ;kopiowanie referencji do stworzonego obiektu na stosie invokespecial eu/zacheusz/invoketry/C/()V ;domyślny konstruktor invokespecial eu/zacheusz/invoketry/A/method()V return .end method
Po skompilowaniu (java -jar jasmin.jar -d compiled jassrc\Invoking.j) próba uruchomienia powoduje błąd maszyny wirtualnej:
java -Xverify:none -cp compiled eu.zacheusz.invoketry.Invoking
#
# A fatal error has been detected by the Java Runtime Environment:
#
# EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0×00000000, pid=5592, tid=2284
#
# JRE version: 6.0_22-b04
# Java VM: Java HotSpot(TM) Client VM (17.1-b03 mixed mode, sharing windows-x86 )
# Problematic frame:
# C 0×00000000
Okazuje się, że w ten sposób można wywołać jedynie metodę klasy będącej bezpośrednim rodzicem. Jest to możliwe dlatego, że wywołania metod na rzecz super są obsługiwane właśnie przez invokespecial. Rozważając przykładową klasę:
package eu.zacheusz.invoketry; public class X extends A { public void m () { super.method(); } }
Po jej zdeassemblowaniu (javap -c -classpath compiled eu.zacheusz.invoketry.X) kod metody m zawiera identyczne wywołanie:
public void m();
Code:
0: aload_0
1: invokespecial #15; //Method eu/zacheusz/invoketry/A.method:()V
4: return
Podsumowując, udało się napisać kod wywołujący przesłoniętą metodę klasy nadrzędnej spoza kodu klasy, ale uruchomienie go wymagało wyłączenia walidacji.
Komentarze
Chcesz coś napisać?