NestedVM: quando C diventa Java in un MIPS…

Lasciamo riposare per il momento il netbook e diamo uno sguardo ad un progetto molto interessante per i programmatori…

Se siete programmatori Java, prima o poi vi sarà capitato di avere la necessità di utilizzare una libreria o funzione che è disponibile solo in altri linguaggi di programmazione.
Che fare dunque in questi casi?

Per rispondere è doveroso dare uno sguardo a cosa è Java.

Java è sia un linguaggio di programmazione, sia un ambiente (virtuale) dotato di un proprio processore e codice macchina e, non ultimo, un insieme di librerie standard che coprono pressochè ogni dove.

Questo mix permette di:

  • avere codice portabile (basta che esista una Java Virtual Machine, una sorta di emulatore, per far girare il codice Java su qualunque architettura)
  • facilità di programmazione dato da un linguaggio Object Oriented semplificato rispetto al C++ e una gestione automatica della memoria (garbage collection)

Indubbiamente la scelta di legare il linguaggio di programmazione Java al codice prodotto (bytecode) per il processore (virtuale o reale che sia) è solo motivata dalla strategia di coerenza adottata da Sun.
Nulla vieterebbe, come ha fatto Microsoft con Net, di avere più linguaggi di programmazione (esempio C#, VB.NET, J#) che vengono poi convertiti nel bytecode del processore virtuale.

Però questo non risolverebbe totalmente il nostro problema: se anche avessimo un codice C (tradizionale) da utilizzare al posto di Java, rimarrebbe il fatto che il codice non trarrebbe beneficio da tutte le caratteristiche che offre Java stesso (multi-threading nativo, eccezioni, librerie standard).
Il codice C dovrebbe quantomeno essere preso per mano e adattato per renderlo operativo all’eventuale “C” da tradurre in bytecode di Java, dato che ad esempio, i puntatori non sono gestibili dal programmarore, ma sono gestiti dal sistema. Per questo legare JAVA all’architettura sottostante risulta vincente: si traggono tutti i benefici offerti a basso livello (o meglio a livello in cui il programmatore non mette mano) in modo trasparente.

Allora che fare, dato per scontato che mettersi a convertire codice C in Java non è una cosa così rapida?
Una soluzione è quella di utilizzare JNI, l’interfaccia per eseguire codice nativo.
Si prende il codice C, lo si compila per le N architetture su cui è disponibile Java e poi si dice a Java di utilizzare nativamente il codice per il processore reale.

Certo funziona, ma ha lo svantaggio di dover compilare il codice per tante archittetture diverse e se domani ne esce una nuova, il mio codice non potrebbe girare, perdendo tutto quello che di ottimo ha Java 🙁
Inoltre, il codice che gira lo fa come codice della macchina reale: un baco potrebbe causare problemi (ad esempio di sicurezza o buffer overflow) tipici della programmazione tradizionale.

Un’altra soluzione possibile è invece quella di utilizzare NestedVM (http://nestedvm.ibex.org)!

Ho scoperto questo progetto quando ho avuto la necessità di trovare un compilatore assembler per processore 6502 in Java.
A quel tempo non ho trovato nessun programma open source scritto in Java che potessi riutilizzare (adesso invece ne conosco): c’erano solo una miriade di software C pronti all’uso.

Certo imbarcarsi nell’impresa di convertire una marea di codice C in Java non era proponibile per il poco tempo che potevo dedicare all’operazione.
Forse cercando in rete potevo trovare dei programmi che “traducevano” il codice C in Java (mi ricordo che 15 anni fa utilizzavo programmi che convertivano Pascal in C), codice che va poi riaggiustato a mano, così come evidenziato nei punti precedenti: in ogni caso del lavoro extra da sobbarcarsi 🙁

Ma NestedVM mi dava tutto quello che mi serviva: del bytecode Java che sarebbe stato eseguito come se il codice derivasse da un sorgente Java, ma partendo da sorgenti scritti in C!

Quello che fa NestedVM è presto detto:

  • Installa un cross compilatore GCC (che quindi accetta qualunque linguaggio da esso supportato, come C, C++, Fortran) per processori MIPS
  • Trascodifica il codice macchina di Mips in bytecode Java
  • Tramite il linker sostituisce le chiamate a librerie di I/O nelle corrispondenti librerie di NestedVM e Java (quindi I/O su files sono salvaguardati).

La scelta dei progettisti di utilizzare il processore MIPS è stata per opportunità: il processore e le sue istruzioni sono molto simili al processore (virtuale) di Java e il suoi set di istruzioni, rendendo quindi il passaggio tra i due sistemi semplice ed efficente.

Con NestedVM ho allora preso il compilatore in C di Matthew Dillon del lontano 1988 e ne ho reso l’equivalente in bytecode Java: il tutto ha funzionato a meraviglia e sono bastati meno 5 minuti per fare l’operazione 😉

Questo approccio ha comunque alcuni svantaggi:

  • il primo è che del bytecode generato potremo eseguire solo il metodo che era dichiarato main nel programma C: eventuali parametri devono essere passati nell’invocazione del metodo.
  • Il secondo è che non possiamo dare in pasto delle aree di memoria al programma e ottenere il risultato su un altra area di memoria: il tutto deve passare per file.
  • Il terzo è che non tutto è gestito: ad esempio i thread non sono utilizzati da NestedVM per la difficoltà che c’è nel rapporto col codice MIPS prodotto (daltronde i thread sono nativi in Java e ci sono istruzioni al riguardo direttamente a livello del processore Java).

Il primo punto si traduce nel seguente codice Java (tratto dal mio sorgente) per richiamare il bytecode generato da NestedVM:

String[] args;
 
if (debug) args=new String[21];
else args=new String[20];
 
args[0]=" ";
args[1]=inFileName;
args[2]="-o"+outFileName;
args[3]=exDefine;
args[4]=exDefineTune;
args[5]=exDefinePat;
args[6]=exDefineInstr;
args[7]=exFormat;
 
args[8]="-DUSE_INSTR_AD="+song.getUseInstrTable(Song.INSTR_AD);
args[9]="-DUSE_INSTR_SR="+song.getUseInstrTable(Song.INSTR_SR);
args[10]="-DUSE_INSTR_WAVE="+song.getUseInstrTable(Song.INSTR_WAVE);
args[11]="-DUSE_INSTR_FREQ="+song.getUseInstrTable(Song.INSTR_FREQ);
args[12]="-DUSE_INSTR_PULSE="+song.getUseInstrTable(Song.INSTR_PULSE);
args[13]="-DUSE_INSTR_FILTER="+song.getUseInstrTable(Song.INSTR_FILTER);
args[14]="-DUSE_INSTR_RES="+song.getUseInstrTable(Song.INSTR_RES);
args[15]="-DUSE_INSTR_TYPE="+song.getUseInstrTable(Song.INSTR_TYPE);
 
args[16]="-DA4_FREQ="+f4Table;
args[17]="-DEX_BASE="+addr;
args[18]="-DEX_SPEED="+song.getIntSpeed();
args[19]="-DEX_CHIP="+song.getIntChip();
if (debug) args[20]="-L"+tmpPath+File.separator+"out.txt";
 
try {
Class cl = Class.forName("jitt64.asm.Main");
Method mMain = cl.getMethod("run", new Class[]{String[].class});
mMain.invoke(cl.newInstance(), new Object[]{args});
} catch (Exception e) {
System.err.println(e);
return false;
}

in cui Main è il nome del file .class originato da NestedVM e args contiene tutti i parametri passati al programma.

Il secondo si traduce col fatto che avremo un overhead dovuto alla gestione dei files: compilare un sorgente 6502 posto in memoria o doverlo prima leggere da file ha un certo peso (che si può però ovviare locando i files su un file system su ram…cosa semplicissima su Linux, ma anche su Windows risulta attuabile).

Esiste però un vantaggio particolare offerto da NestedVM (almeno per chi produce software non open source): è il miglior offuscatore di codice Java esistente!
Penso che nessun decompilatore Java sia in grado di recuperare il codice sorgente originale, mentre se fosse derivato da un sorgente Java la cosa risulterebbe invece molto più semplice 😉

Ma vediamo come funziona il tutto partendo dal principio.

Una volta ottenuto il sorgente ci basta compilarlo coi classici comandi da console:

wget -c http://nestedvm.ibex.org/dist/nestedvm-2009-08-09.tgz
tar -xzf nestedvm-2009-08-09.tgz
cd nestedvm
make

Ora che il programma è compilato e pronto, per utilizzarlo ci basta attivare due comandi, sempre dalla posizione dove eravamo prima:

make env.sh
source env.sh

Adesso abbiamo un ambiente impostato per utilizzarlo, copiamo quindi il nostro sorgente in C su una subcartella dentro nestedvm.

Adesso ci basta compilare il sorgente utilizzando il compilatore e linker per MIPS creato da NestedVM.

Il modo più semplice è quello di utilizzare dei parametri per la crosscompilazione per il make automatico che avremo fatto sul sorgente C:

make CC=mips-unknown-elf-gcc LD=mips-unknown-elf-gcc

Abbiamo quindi ottenuto un eseguibile MIPS, supponiamio che si chiami main.mips per semplicità, e operiamo la “magia” finale:

java org.ibex.nestedvm.Compiler -outfile Main.class Main main.mips

La nostra classe Main.class è pronta e la possiamo utilizzare come abbiamo visto sopra a patto di includere runtime.jar al nostro eseguibile.

E’ tutto?
Non proprio… Tra le specifiche di NestedVM si parla anche della possibilità di generare sorgenti Java dal codice binario MIPS e poi tali sorgenti possono essere compilati con JAVAC nel modo tradizionale!
Queta tecnica ha vantaggi/svantaggi rispetto alla precedente, ma per me così come ha funzionato senza problemi al primo colpo la tecnica del codice macchina MIPS verso bytecode, è già un must e non sono andato ad indagare oltre 😉

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.