==============================================================================
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
--------------------[ previous ]---[ index ]---[ next ]---------------------

---------------------[ JAVA REVERSE ENGEENERiNG: LA JVM ]---------------------
---------------------------------[ LordFelix ]--------------------------------

0.0 Prefazione
1.0 Piccola introduzione al funzionamento di Java
1.1 Principi di funzionamento della JVM
1.2 I frame
2.0 Tools
3.0 Le istruzioni della JVM: un esempio
4.0 Conclusioni

0.0 Prefazione
    ----------

Al giorno d'oggi l'intero web e' infestato da una serie infinita di applet
java di ogni genere. Spesso mi e' sorta la curiosita' di capire come
funzionasse (almeno a grandi linee) l'interprete contenuto in ogni browser di
un certo livello (lynx a parte ;). Questo scritto vuole rappresentare solo una
prima lettura per chiunque voglia approfondire il funzionamento della Java
Virtual Machine (JVM) e dare un'occhiata al codice delle migliori applet
presenti in rete. Non e' mia intenzione sostituirmi a quella che e' la
"bibbia" della JVM, ovvero il libro "The Java Virtual Machine Specification"
di Tim Lindholm e Frank Yellin disponibile on line su http://java.sun.com
In questa breve panoramica ho eliminato tutti quegli aspetti che riguardano
l'implementazione di una JVM, focalizzando l'attenzione sul set di istruzioni
e sugli strumenti disponibili per il reverse engeenering delle classi java.
Nel seguito daro' per scontata la conoscenza di almeno i costrutti
fondamentali del linguaggio java.
Mi scuso in anticipo per eventuali imprecisioni. Vi prego di segnalarmi ogni
incongruenza scrivendomi all'indirizzo lordfelix@dislessici.org

1.0 Piccola introduzione al funzionamento di Java
    ---------------------------------------------

A differenza degli altri linguaggi di programmazione per Java lo scopo
fondamentale e' funzionare su ogni tipo di hadware che possegga una
implementazione della Java Virtual Machine (JVM). In pratica quando compiliamo
un programma in Java il .class che otteniamo non e' codificato per il
linguaggio macchina del nostro processore, ma e' "tradotto" in una specie di
"macrolinguaggio". Ad eseguire il nostro .class non sara', quindi, il
processore ma un programma che interpreta i bytecode e trasmette i comandi
corrispondenti al processore.
Rivediamo la cosa usando un po' di ascii-art:

 ----------   ------------   ----------   ---------   -------
 |  File  |   | Compiler |   |  File  |   |  JVM  |   | CPU |
 | .java  |-->|          |-->| .class |-->|       |-->|     | 
 ----------   ------------   ----------   ---------   -------

chiaramente l'intermediazione della JVM per l'esecuzione dei programmi rende i
.class assai piu' lenti del codice nativo. Tuttavia l'obiettivo dei
progettisti e' stato quello di ottenere la portabilita' su diverse piattaforme
del compilato, e, devo dire, ci sono riusciti.
La JVM puo' essere utilizzata indipendentemente dal linguaggio Java vero e
proprio. I costrutti del linguaggio sono noti al solo compiler, mentre alla
JVM compete la sola esecuzione del macrocodice prodotto da quest'ultimo.
Cosi' come gli assembler consentono di programmare direttamente con il codice
della CPU allo stesso modo esistono delle applicazioni che consentono di
programmare direttamente la JVM.

1.1 Principi di funzionamento della JVM
    -----------------------------------

Innanzitutto la JVM non include alcun meccanismo di controllo sulla coerenza
dei tipi di dati passati alle sue istruzioni. Si presuppone che il controllo
sui tipi sia stato gia' effettuato dal compilatore. Possiamo raggruppare i
tipi in due categorie: quelli primitivi e i reference. I dati primitivi sono
i classici int, long, float... mentre i reference rappresentano
*esplicitamente* gli oggetti che utilizza il programma.
Oltre ai tipi primitivi numerici sono presenti anche quelli del tipo
returnaddress usati dalle istruzioni di controllo come vedremo in seguito.
I returnaddress non hanno alcun tipo corrispondente nel linguaggio java ma
sono una produzione "esclusiva" del compilatore. Una attenzione particolare
merita il tipo boolean che non ha un corrispettivo nell'ambito della JVM e che
viene trattato come se fosse un int.
Per quanto riguarda i reference, possono "puntare" tre tipi di oggetti: le
classi, le intefacce e gli array. Un reference puo' assumere anche il valore
null (cioe' nessun oggetto).
Come un qualsiasi processore anche la JVM ha un suo insieme di registri. Il
registro pc assume lo stesso significato che ha il registro IP nei processori
Intel: e' il "program counter" ovvero contiene la posizione della istruzione
eseguita in quel momento. Il pc non e' definito se il metodo eseguito dalla
JVM e' "nativo", cioe' fa parte del set di metodi messi a disposizione del
linguaggio (come quelli delle awt, per esempio) oppure scritti in altri
linguaggi. Tali metodi, infatti, vengono eseguiti direttamente dal
microprocessore, bypassando la JVM.
La lunghezza del registro pc e' una word.
Nella JVM e' presente il cosiddetto Java Stack. Esso contene una serie di
sottostrutture, dette frame, che contengono le variabili locali, i risultati
parziali e le informazioni per le chiamate ai membri di altre classi. Inoltre
e' presente un heap in cui vengono memorizzati gli array e le istanze delle
varie classi. 
Assai importante e' una particolare tabella di simboli, detta constant pool.
Essa contiene, per ogni classe, il valore delle costanti e i reference ai
metodi che la classe usa.

1.2 I frame
    -------

Analizziamo con maggiore dettaglio questa particolare struttura. Essa viene
creata per ogni metodo che viene invocato. In ogni istante e' definito un
metodo corrente e il corrispettivo frame. Un frame cessa di essere il frame
corrente quando il metodo corrente chiama un altro metodo. Al ritorno della
chiamata il frame torna ad essere il "frame corrente". Ogni frame presenta
un'area dedicata alle variabili locali e un'altra che funge da stack per gli
operandi che di volta in volta sono necessari alle istruzioni della JVM.
Le variabili locali vengono memorizzate in un array i cui elementi possono
essere richiamati nel piu' classico dei modi: mediante il loro indice. Ogni
locazione di questo array e' sempre lunga una word. Per le variabili che
occupano piu' word viene riservata piu' di una locazione.
Per quanto riguarda lo stack operandi, esso viene usato per passare parametri
ai metodi e per riceverne i risultati, nonche' per fornire gli operandi alle
istruzioni della JVM (come vedremo in seguito).

2.0 Tools
    -----

Prima di procedere all'analisi di un file .class vediamo di quali tools
abbiamo bisogno. E' inutile dire che avete bisogno del Java Development Kit,
necessario per compilare i file .java (http://java.sun.com). Per convertire i
file .class nel corrispondente bytecode avete bisogno di un disassembler: per
i nostri scopi esistono due programmi: D-Java e Jdis. Il primo e' scritto in
c e lo trovate sia per win che per solaris; tuttavia ho notato un certo
"impapocchiamento" del programma nell'assegnazione delle label. Non ci resta
che usare Jdis che e' scritto in java ed e' quindi eseguibile in tutte le
piattaforme con una JVM.
Ho stretto la cerchia dei disassembler a d-java e jdis perche' consentono di
avere l'output nel formato di jasmin; jasmin e' un assemblatore che, ricevuto
il bytecode in ingresso lo trasforma in un file .class. Infine, ma non ultimi,
troviamo due decompilatori veri e propri: jad e mocha. Jad e' scritto in c ed
e' reperibile in formato .exe per dos; mocha e' invece scritto in java.
Entrambi producono, a partire da un file .class il corrispondente .java .
Tutti questi "attrezzi" li trovate sul sito
http://Meurrens.ML.org/ip-Links/Java/codeEngineering/

3.0 Le istruzioni della JVM: un esempio
    -----------------------------------

Introdurremo ora le varie istruzioni della JVM ricorrendo ad un esempio
significativo. Analizzeremo l'applet NervousText fornita nei demo del JDK
nella dir /demo/NervousText. Abbiamo gia' a disposizione il file .class
quindi non ci resta che disassemblarlo per carpirne i segreti. Dato che e'
molto interessante capire la corrispondenza bytecode/istruzioni java
utilizzeremo il jad usando il parametro -a (annotate) che pone le istruzioni
della JVM come commento alle linee di codice ricostruito:

jad -a NervousText.class

Quello che otteniamo e' un file di testo con estensione .jad che contiene
sorgente e assembly JVM. Analizziamolo.
In testa al sorgente troviamo il solito e familiare "preambolo" in cui vengono
dichiarate le classi java necessarie al corretto funzionamento dell'applet:

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public class NervousText extends Applet
    implements Runnable, MouseListener
{

Vengono poi definiti i membri della classe NervousText. Da questo punto in poi
jad comincia a sfornare anche il codice della JVM.

public void init()
    {
        banner = getParameter("text");
    //    0    0:aload_0         
    //    1    1:aload_0         
    //    2    2:ldc1            #6   <String "text">
    //    3    4:invokevirtual   #31  <Method String Applet.getParameter(String)>
    //    4    7:putfield        #25  <Field String banner>

Nelle prime tre righe abbiamo il codice sorgente mentre nei commenti sono
presenti le istruzioni JVM che lo implementano. Vediamo cosa significano e
come operano.

aload_0		Viene posto in cima allo stack operandi il riferimento alla
		classe della variabile locale numero 0. In questo caso tale
		variabile e' Banner ma di essa viene "pushato" solo
		l'informazione "banner e' una stringa" e non il valore o
		l'area di memoria ad essa associata. L'istruzione viene
		eseguita due volte e vedremo fra poco il perche'.

ldc1 #6         Viene pescato l'elemento numero 6 del "constant pool". Il
		constant pool puo' essere visto come un "array" in cui il
		compilatore java memorizza le costanti usate dal programma
		(ma non solo). In questo caso il numero 6 corrisponde alla
		stringa "text" che viene posta in testa allo stack.

invokevirtual #31  Ecco l'istruzione che piu' spesso ricorre nell'assembly
		   JVM: invokevirtual. Essenzialmente "chiama" il metodo
	  	   numero 31 che in questo caso corrisponde alla getParameter
		   della classe applet. Anche questa volta il numero 31 fa
		   riferimento ad una posizione del constant pool che contiene
		   il nome del metodo da "invocare". Inoltre, sempre nel campo
		   31 del constant pool e' indicato il numero di argomenti
		   necessari alla funzione. Essi devono risiedere nello stack
		   secondo l'ordine:

                   argomento n
                   argomento n-1
                     ...
                   argomento 1
                   riferimento all'oggetto che contiene il risultato
                     ...
                   
                   Quindi, appena dopo gli argomenti, in cima allo stack va
		   posto il riferimento all'oggetto che deve contenere il
		   risultato dell'elaborazione del metodo.
                   Nel caso specifico prima di invoke virtual abbiamo la
		   seguente situazione nello stack:

                   riferimento alla stringa "text"
                   riferimento ad un oggetto stringa
                   riferimento ad un oggetto stringa
                     ...

                   dopo l'esecuzione della invokevirtual lo stack conterra' il
		   solo risultato della getParameter (oggetto stringa)
		   riferimento ad un oggetto stringa.
		   In pratica il riferimento "consumato" dalla invoke e'
		   servito a determinare solo il tipo di valore in uscita, in
		   accordo con quello che abbiamo detto in occasione della
		   aload_0. E' importante notare che l'assegnamento del
                   risultato alla variabile Banner non e' ancora avvenuto.

putfield #25	Finalmente viene prelevato il risultato della getParameter e
		viene memorizzato nella posizione 25 del constant pool. Il
		tipo dell'oggetto memorizzato al 25 viene determinato in base
		al rifermento successivo al risultato. Nel nostro caso alla
		fine dell'operazione lo stack e' vuoto. E' per questo motivo
		che l'aload iniziale e' stato ripetuto due volte.

Da queste prime istruzioni si nota subito la cruciale importanza del constant
pool e del livello tutto sommato alto del bytecode. Gli oggetti e i metodi
vengono trattati in quanto tali e non vengono scomposti in tipi piu'
elementari come accade nei compilatori normali.
Proseguiamo nell'analisi...

         if(banner == null)
    //*   5   10:aload_0         
    //*   6   11:getfield        #25  <Field String banner>
    //*   7   14:ifnonnull       23

Di aload abbiamo gia' parlato: un riferimento al tipo stringa viene posto in
cima allo stack.

getfield #25	Si tratta dell'istruzione duale a putfield. Nel nostro caso lo
		stack contiene unicamente il riferimento ad un oggetto stringa
                     ...

                dopo il getfield viene preso il campo #25 del constant pool e
		il suo valore viene trattato come una instanza della classe
		stringa: lo stack conterra' il risultato della getfield:

                   valore dell'oggetto stringa #25
                     ...

ifnonnull 23	Ecco la prima istruzione di controllo che incontriamo. Il suo
		uso e' intuitivo. Controlla che il valore in cima allo stack
		non sia null e se non lo e' salta alla linea 23 altrimenti
		prosegue nell'esecuzione. Come e' prassi il valore in cima
		allo stack (utilizzato nel controllo) viene "poppato" via.

Chi ha un po' di esperienza nei linguaggi di alto livello notera' una certa
somiglianza col costrutto if...goto. Tutti i costrutti if..then..else..
vengono "compilati" ricorrendo all'uso di if...goto e goto semplici. Nel
nostro caso il ramo "then" e' costituito dalle istruzioni dalla 17 alla 20:

            banner = "HotJava";
    //    8   17:aload_0
    //    9   18:ldc1            #1   <String "HotJava">
    //   10   20:putfield        #25  <Field String banner>

Da queste linee si nota che il ramo then non e' altro che un assegnamento di
una costante alla stringa banner. In particolare viene assegnata la stringa
contenuta nella posizione #1 del costant pool all'oggetto contenuto nella
posizione #25 (che abbiamo visto essere banner).

       int i = banner.length();
    //   11   23:aload_0
    //   12   24:getfield        #25  <Field String banner>
    //   13   27:invokevirtual   #32  <Method int String.length()>
    //   14   30:istore_1
 
Le prime tre istruzioni si incaricano di richiamare il metodo length
dell'oggetto banner (in realta' viene invocato il metodo length della classe
string che agisce sul valore in testa allo stack). In questo caso la
definizione della variabile i e' associata alla sua dichiarazione

istore_1	Memorizza il valore in testa allo stack nella variabile locale
		numero 1. Inoltre specifica che tale variabile e' un intero.
		Come sempre il valore in testa allo stack viene eliminato.

        bannerChars = new char[i];
    //   15   31:aload_0
    //   16   32:iload_1
    //   17   33:newarray        char[]
    //   18   35:putfield        #26  <Field char[] bannerChars>

Viene ora dichiarato un array di caratteri. La chiave dell'operazione e'

iload_1		Viene caricato sullo stack il riferimento alla variabile
		locale intera posta nella posizione numero 1 del frame. Si
		tratta della i usata per determinare la lunghezza dell'array
		che si sta per creare.

newarray char[]    Crea un array di caratteri di lunghezza pari al valore
		   intero che si trova in cima allo stack. Naturalmente e'
	           possibile creare array di altri tipi.
                   Dopo l'esecuzione nello stack troviamo un reference al
		   nuovo array. Il tipo di array da creare viene determinato a
		   partire dal byte successivo all'opcode di newarray, secondo
		   la tabella:

                   Array Type | atype
                   ------------------
                   T_BOOLEAN      4
                   T_CHAR         5
                   T_FLOAT        6
                   T_DOUBLE       7
                   T_BYTE         8
                   T_SHORT        9
                   T_INT         10
                   T_LONG        11

putfield #26	Il riferimento al nuovo array va a occupare la posizione #26
		del constant pool.

  banner.getChars(0, banner.length(), bannerChars, 0);
    //   19   38:aload_0         
    //   20   39:getfield        #25  <Field String banner>
    //   21   42:iconst_0        
    //   22   43:aload_0         
    //   23   44:getfield        #25  <Field String banner>
    //   24   47:invokevirtual   #32  <Method int String.length()>
    //   25   50:aload_0         
    //   26   51:getfield        #26  <Field char[] bannerChars>
    //   27   54:iconst_0        
    //   28   55:invokevirtual   #30  <Method void String.getChars(int, int, char[], int)>

La serie di istruzioni prima dell'invoke finale serve a ricostruire i
parametri da passare al metodo getChars della classe string. Considerando
quando detto in precedenza non dovrebbe essere difficile rendersi conto di
come i vari parametri si avvicendano nello stack. L'unica novita' e' la

iconst_0	Inserisce la costante intera 0 in cima allo stack. Esistono
		diverse alternative per questa istruzione:

                   Istruzione | Costante associata
                   -------------------------------
                   iconst_m1           -1
                   iconst_0             0
                   iconst_1             1
                   iconst_2             2
                   iconst_3             3
                   iconst_4             4
                   iconst_5             5

                   Non esistono altri tipi di comandi iconst.

        threadSuspended = false;
    //   29   58:aload_0
    //   30   59:iconst_0
    //   31   60:putfield        #42  <Field boolean threadSuspended>

Ecco un semplice assegnamento: notiamo che il valore false e' in realta'
l'intero 0.

        resize(15 * (i + 1), 50);
    //   32   63:aload_0
    //   33   64:bipush          15
    //   34   66:iload_1
    //   35   67:iconst_1
    //   36   68:iadd
    //   37   69:imul
    //   38   70:bipush          50
    //   39   72:invokevirtual   #37  <Method void Applet.resize(int, int)>

Prima della chiamata alla funzione resize il compilatore produce una serie
di istruzioni necessarie a valutare le espressioni passate come argomenti di
resize.

bipush 15	Bipush inserisce un byte nello stack, nel caso particolare 15

iadd	Somma due interi in cima allo stack e il risutato e' posto ancora
	nello stack.

imul	Funziona come iadd ma esegue la moltiplicazione

Anche in questo caso e' di fondamentale inportanza seguire i valori che si
susseguono nello stack (non mi stanchero' mai di dirlo).

        setFont(new Font("TimesRoman", 1, 36));
    //   40   75:aload_0
    //   41   76:new             #11  <Class Font>
    //   42   79:dup
    //   43   80:ldc1            #3   <String "TimesRoman">
    //   44   82:iconst_1
    //   45   83:bipush          36
    //   46   85:invokespecial   #23  <Method void Font(String, int, int)>
    //   47   88:invokevirtual   #39  <Method void Component.setFont(Font)>

new #11		Crea un nuovo oggetto, in questo caso della classe nella
		posizione 11 del constant pool. Nello specifico si tratta di
		un oggetto font.

Dopo la creazione e' necessario richiamare il costruttore dell'oggetto con i
propri parametri.
Dove devono risiedere questi parametri?? Ma nello stack naturalmente! Cosi' si
spiega il dup che duplica tutto cio' che si trova in testa allo stack (in
questo caso il reference all'oggetto font appena creato) e la serie di
aggiustamenti prima della chiamata al costruttore font() mediante

invokespecial #23   E' utilizzato, con le stesse regole di invokevirtual, per
		    richiamare il costruttore di un'istanza di una classe. In
		    particolare la posizione 23 del constant pool contiene
		    l'inizializzatore della classe font.

        addMouseListener(this);
    //   48   91:aload_0
    //   49   92:aload_0
    //   50   93:invokevirtual   #24  <Method void Component.addMouseListener(MouseListener)>
    //   51   96:return

Questa procedura informa la JVM che l'applet intercetta gli eventi relativi al
mouse. Da notare come il riferimento alla variabile locale 0 venga usato anche
come riferimento all'oggetto this (cioe' all'istanza della stessa classe che
si sta definendo).

return		Termina il metodo ed elimina tutto cio' che e' contenuto nel
		frame associato al metodo. Tipicamente si usa quando il metodo
		ha alcun parametro in uscita.

    public void destroy()
    {
        removeMouseListener(this);
    //    0    0:aload_0
    //    1    1:aload_0
    //    2    2:invokevirtual   #35  <Method void Component.removeMouseListener(MouseListener)>
    //    3    5:return
    }

Il distruttore si limita a rilasciare l'intercettazione degli eventi del
mouse. Da sottolineare l'uso del reference this.

    public void start()
    {
        runner = new Thread(this);
    //    0    0:aload_0
    //    1    1:new             #20  <Class Thread>
    //    2    4:dup
    //    3    5:aload_0
    //    4    6:invokespecial   #22  <Method void Thread(Runnable)>
    //    5    9:putfield        #38  <Field Thread runner>
        runner.start();
    //    6   12:aload_0
    //    7   13:getfield        #38  <Field Thread runner>
    //    8   16:invokevirtual   #41  <Method void Thread.start()>
    //    9   19:return
    }

Nulla da dire. Dovreste essere in grado di decifrare il bytecode di questo
metodo.
Se non ci riuscite... beh... rileggete dall'inizio :D

    public synchronized void stop()
    {
        runner = null;
    //    0    0:aload_0
    //    1    1:aconst_null
    //    2    2:putfield        #38  <Field Thread runner>
        if(threadSuspended)
    //*   3    5:aload_0
    //*   4    6:getfield        #42  <Field boolean threadSuspended>
    //*   5    9:ifeq            21
        {
            threadSuspended = false;
    //    6   12:aload_0
    //    7   13:iconst_0
    //    8   14:putfield        #42  <Field boolean threadSuspended>
            notify();
    //    9   17:aload_0         
    //   10   18:invokevirtual   #33  <Method void Object.notify()>
        }
    //   11   21:return
    }

Questo stralcio di codice ci consente di esaurire il discorso sulle varianti
dell'if nel set di istruzioni della JVM:

ifeq 21		Se il contenuto dello stack e' nullo salta all'istruzione 21.
		Le istruzioni if<cond> operano su interi secondo la tabella:

                ifeq    salta solo se il valore sullo stack e' 0
                ifne    salta solo se il valore sullo stack e' non 0
                iflt    salta solo se il valore sullo stack e' minore di 0
                ifle    salta solo se il valore sullo stack e' minore o
			uguale a 0
                ifgt    salta solo se il valore sullo stack e' maggiore di 0
                ifge    salta solo se il valore sullo stack e' maggiore o
			uguale a 0

                Esiste anche una variante che opera comparando i due interi
		in cima allo stack (chiamiamoli int1 e int2):

                if_icmpeq    salta se e solo se int1 e' uguale a int2
                if_icmpne    salta se e solo se int1 e' diverso da int2
                if_icmplt    salta se e solo se int1 e' minore di int2
                if_icmple    salta se e solo se int1 e' minore o uguale a int2
                if_icmpgt    salta se e solo se int1 e' maggiore di int2
                if_icmpge    salta se e solo se int1 e' maggiore o uguale
	    		     a int2

		Un'ulteriore situazione prevede l'uso di if_acmpeq e
		if_acmpne che operano sui reference. Chiaramente in questo
		caso non sono previste le relazioni d'ordine.
                E' importante ricordare che gli operandi vengono rimossi dallo
		stack.

Il decompilato di NervousText continua, ma a questo punto dovreste essere in
grado di seguirlo da soli. Prima di concludere vi accludo il quadro sinottico
in cui sono riportati i codici memonici di ogni opcode a seconda del tipo di
dato su cui esso opera.
Per la maggior parte li abbiamo visti gia' in azione su determianti tipi. Per
gli altri vi rimando alla bibbia ;)

opcode  |byte    |short    |int      |long     |float    |double   |char   |reference
-------------------------------------------------------------------------------------
Tipush   bipush  	sipush
Tconst                      iconst    lconst    fconst    dconst            aconst
Tload                       iload     lload     fload     dload             aload
Tstore                      istore    lstore    fstore    dstore            astore
Tinc                        iinc
Taload   baload   saload    iaload    laload    faload    daload    caload  aload
Tastore  bastore  sastore   iastore   lastore   fastore   dastore   castore aastore
Tadd                        iadd      ladd      fadd      dadd
Tsub                        isub      lsub      fsub      dsub
Tmul                        imul      lmul      fmul      dmul
Tdiv                        idiv      ldiv      fdiv      ddiv
Trem                        irem      lrem      frem      drem
Tneg                        ineg      lneg      fneg      dneg
Tshl                        ishl      lshl
Tshr                        ishr      lshr
Tushr                       iushr     lushr
Tand                        iand      land
Tor                         ior       lor
Txor                        ixor      lxor
i2T      i2b      i2s                 i2l       i2f       i2d
l2T                         l2i                 l2f       l2d
f2T                         f2i       f2l                 f2d
d2T                         d2i       d2l       d2f
Tcmp                                  lcmp
Tcmpl                                           fcmpl     dcmpl
Tcmpg                                           fcmpg     dcmpg
if_TcmpOP                   if_icmpOP                                       if_acmpOP
Treturn                     ireturn   lreturn   freturn   dreturn           areturn

5.0 Conclusioni
    -----------

Come da piu' parti e' stato rilevato i tecnici della sun che hanno progettato
java hanno badato esclusivamente alla portabilita' e alla sicurezza della JVM
ma non hanno tenuto in conto gli interessi degli sviluppatori.
Se e' vero che e' possibile "incasinare" il meccanismo di decompilazione e
"offuscare" i nomi dei metodi e delle variabili e' anche vero che pubblicare
un .class equivale a fornirne il sorgente. E' sintomatico il fatto che una
delle piu' interessanti applicazioni scritte in java (il programma
JavaZip 2.0) e' facilmente decompilabile con lo jad e addirittura banale da
sproteggere. Per quel poco di esperienza che ho posso affermare che almeno il
90% delle applicazioni sono pienamente decompilabili, un 5% ha come unica
protezione l'"offuscamento" dei simboli e l'altro 5% mette in difficolta' i
decompilatori. Tuttavia e' sempre possibile disassemblare una applet e, data
la "potenza" del set di istruzioni JVM, e' molto facile esaminare e modificare
il programma con un approccio molto simile al death-listing.
Bene... con questo mi sembra di aver esaurito questa breve guida.
Un consiglio: nel momento in cui decidete di cominciare a scrivere
applicazioni java tenete bene in mente questo punto debole.

								LordFelix [Dislessici]


--------------------[ previous ]---[ index ]---[ next ]---------------------
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
==============================================================================