\initializearticle
\title *Inspector, ovvero: come controllare i parametri e vivere felici*
\author  *Sebastiano Vigna*
\address *Via California 22\\
	I-20144 Milano MI*
\netaddress[\network{Internet}]  *vigna@ghost.dsi.unimi.it*

\article

\head *Introduzione*

In questo articolo vorrei discutere di un potente strumento di sviluppo creato
in collaborazione con Fabrizio Lodi e Reinhard Spisser; il lavoro è solo
parzialmente completato (anche a causa della mole di conoscenze necessarie e
delle condizioni inusuali di lavoro), ma è già possibile sfruttarne molte
funzionalità.

Negli ultimi anni i programmatori di \hbox{\amiga{}} si sono ritrovati tra le mani una
serie di strumenti di sviluppo di elevatissima qualità. Siamo partiti da
|Mungmem| e |Enforcer| per giungere a |Sushi|, al nuovo |Enforcer|, a
|Mungwall|, |Memoration|, |LVO| e così via. Sfruttando di volta in volta la
presenza di hardware aggiuntivo (per esempio una MMU) o le caratteristiche del
sistema operativo (per esempio la possibilità di modificare i vettori di salto
delle librerie), questi strumenti ci hanno permesso di portare il sistema
operativo ed il software disponibile a livelli di affidabilità prima
impensabili.

Tuttavia, c'è un'area nella quale la situazione è parecchio migliorabile: tutti
gli strumenti che abbiamo a disposizione sono di tipo esaustivo; riportano cioè
una grande quantità di dati da esaminare. Per una (peraltro ragionevole)
decisione di progettazione, non hanno una grande quantità di intelligenza.

Molti sistemi operativi dispongono di una caratteristica che rappresenza
l'approccio opposto a quello esaustivo: il controllo dei parametri. Ogni volta
che una funzione di sistema viene chiamata, i suoi parametri vengono
controllati non solo su una base banalmente sintattica (il puntatore ad un
oggetto non può essere nullo, per esempio), ma anche utilizzando dei semplici
controlli incrociati, come la conoscenza della gamma di valori assunti da un
campo di una struttura.

Il controllo dei parametri è la manna del programmatore, che si trova
automaticamente schermato da quasi tutti gli accadimenti più tetri del debug.
La velocità del sistema ne viene però grandemente penalizzata, specie se il
controllo avviene anche su funzioni utilizzate parecchie volte al secondo.

È per questa ragione che vogliamo (e possiamo dire di aver percorso già un buon
tratto di strada) introdurre un nuovo strumento di sviluppo per \amiga{}. La
motivazione essenziale è che la totale mancanza di controllo dei parametri
(pensiamo solo al fatto che nella V33 |CloseLibrary(NULL)| schiantava la
macchina!) rende lo svilupppo molto più faticoso, quando la maggior parte delle
funzioni viene in realtà chiamata un numero di volte dell'ordine delle unità al
minuto o addirittura all'ora, rendendo così l'onere dei controlli ad ogni
chiamata assolutamente risibile.

I parametri dovrebbero anche essere visualizzabili a piacere, e l'interfaccia
del programma dovrebbe essere ragionevolmente semplice e compatta.

\head *Arriva |Inspector|*

Nacque così l'idea di |Inspector| (che in inglese significa
controllore---quello dei treni, tanto per intenderci). Dato che tutte le
funzioni del sistema operativo di \amiga{} sono raggiungibili (e vanno raggiunte)
tramite librerie, la tecnica principale doveva consistere in una attenta
gestione di un grossissimo numero di chiamate a |SetFunction()|. Dopo molte
discussioni, non solo tra di noi ma anche con altre persone, tra cui
ringraziamo in particolare Davide Massarenti, abbiamo capito che un software
del genere non si poteva pensare in modo tradizionale, e cioè come un blocco
monolitico di codice, al più coadiuvato da utilità di contorno.

Le esigenze di uno strumento simile sono infatti particolarissime:
innanzitutto, ad ogni revisione del sistema operativo potrebbe essere
necessario ampliare il campionario delle funzioni disponibili. Non è pensabile,
almeno in prima istanza, che ogni utente abbia il tempo (o la voglia) di
accollarsi un onere simile, e neppure è pensabile che sia sempre, e
immediatamente, messa a disposizione una nuova versione. Inoltre, è più che
ragionevole pensare che un programmatore voglia poter applicare una tecnica
simile anche alle proprie librerie: si sta infatti diffondendo sempre più la
tecnica di spargere quanto più codice possibile in librerie condivise (vedi ad
esempio il SAS/C 6.x). Questo tipo di distribuzione permette di massimizzare il
codice residente, e di sfruttare automaticamente il meccanismo di carico e
scarico delle librerie come semplice cache.

Di fronte a queste esigenze, la proposta più ragionevole è quella di modellare
il sistema di controllo ``attorno'' a quello reale: ad ogni libreria da
controllare corrisponde una libreria di controllo (contenuta nella directory
|libs:inspectors|, e con il nome prefisso dalla lettera `|i|'). Una libreria
centrale, la |inspector.library|, si preoccupa di eseguire effettivamente le
|SetFunction()| e di gestire alcuni operazioni di contorno.

Le librerie di controllo devono mettere a disposizione due elementi
fondamentali: una serie di funzioni pubbliche in grado di stabilire
efficientemente e con un grado ragionevole di certezza se un probabile
puntatore ad un certo tipo di struttura, proprio di quella libreria, punta in
effetti a tale struttura, e un vettore di funzioni sostitutive da piazzare con
|SetFunction()| al posto di quelle originali quando si vuole attivare il
controllo dei parametri. Tali funzioni, una per ogni funzione pubblica della
libreria base, devono controllare i parametri, visualizzare opzionalmente una
serie di informazioni, e procedere alla chiamata o meno della funzione
originale.

Gli ultimi due compiti sono in larghissima parte automatizzabili. Per il primo
una speranza di questo tipo è piuttosto vana, soprattutto a causa dei tipi
molto deboli del C.

\head *Il generatore di codice*

Per sfruttare al massimo le informazioni contenute nei file |.fd| e nei prototipi
delle funzioni di sistema, il perno attorno a cui ruotano le librerie di
controllo è un generatore di codice (|makelib.rexx|) in grado di digerirsi il
file |.fd| e il file dei prototipi di una libreria, e di creare da essi uno
scheletro completamente funzionante e in grado di eseguire gli ultimi due
compiti di cui sopra.

Per consentire la massima flessibilità nel controllo dei parametri, il
generatore di codice legge da un file |.insp| contenuto nella directory |INSP:|
una serie di spezzoni di codice C da inserire funzione per funzione. Una serie
di linee guida permette allo scrittore del codice di interagire ragionevolmente
con il codice dello scheletro. Per esempio, la variabile |paramsOK| dovrebbe
essere messa a |FALSE| nel caso venisse riscontrata un'irregolarità nei
parametri analizzati. È buona norma, in tal caso, specificare succintamente
quale parametro sta causando disturbo.

L'enorme vantaggio di questa metodologia è che anche possedendo solamente i
file |.fd| e i prototipi di una libreria (per esempio, di una libreria propria)
è già possibile generare in modo completamente automatico una libreria di
controllo che effettua la stampa selettiva delle chiamate e dei parametri. Il
programmatore può poi aggiungere mano a mano, e nell'ordine preferito, codice
di controllo dei parametri per le funzioni che più gli stanno a cuore. Una
semplice ricompilazione rigenera il sistema, integrando il nuovo codice.

L'altro vantaggio è che nel caso si possegga una libreria (per esempio, una di
sistema) con un sistema completo di controllo dei parametri e arrivi una nuova
versione del sistema operativo contenente alcune nuove funzioni nella libreria
stessa, il generatore di codice può ricreare una nuova libreria di
controllo, includendo tutte le funzioni già controllate (cioè non perdendo
alcuna funzionalità) ma anche generando nuovo codice scheletro (cioè
permettendo la visualizzazione delle chiamate alle nuove funzioni).

\head *La |inspector.library|*

Il centro attorno a cui ruotano tutte le librerie di controllo è la
|inspector.library|, che contiene le funzioni essenziali per la gestione dei
patch e delle flag, ed una libreria |ARexx| che verrà discussa successivamente.

Ogni volta che un patch viene attivato o disattivato, la relativa libreria di
controllo viene aperta; quindi, il patch viene eseguito, e la libreria chiusa.

Il codice generato da |makelib.rexx| fa sì che una libreria di controllo si
rifiuti di essere eliminata fino a che tutti i patch siano stati rimossi, anche
se il contatore delle aperture è andato a zero. In questo modo, possiamo
completamente disinteressarci dello stato globale di una libreria di controllo:
sarà lei stessa, in concerto con il sistema di cache delle librerie, a
provvedere ad eliminarsi e ricaricarsi quando necessario.

Bisogna qui far notare che le librerie di controllo aprono sempre la
|inspector.library|, in quanto essa contiene le flag globali al sistema. Non si
può però verificare alcun tipo di deadlock perché le funzioni della
|inspector.library| aprono e loro volta, sì, le librerie di controllo, ma nel
contesto di un altro processo. La |inspector.library| rimane quindi in memoria
fintanto che tutti i patch di tutte le librerie non siano stati rimossi.


\head * Le librerie di controllo *

Buona parte delle librerie di controllo, come abbiamo già spiegato, \kern -3ptviene
generata da |makelib.rexx|. Il codice deve avere alcune
caratteristiche un po' particolari, che andiamo a spiegare.

Le funzioni generate automaticamente in ogni libreria sono |InspectorLibInit()|
e \discretionary{|InspectorLib|}{|Cleanup()|}{|InspectorLibCleanup()|}. La prima si preoccupa di mettere a posto i dati
nella struttura globale della libreria e di aprire la libreria da controllare.
La seconda effettua un controllo in caso di richiesta di espulsione: a meno che
tutti i patch non siano stati rimossi, la libreria rifiuterà di essere
eliminata.

Seguono, una per ogni funzione della libreria originale, le funzioni di patch.
Si badi bene: queste funzioni non figurano nella tavola dei salti della
libreria di controllo! Questa, al contrario, è occupata dalle funzioni create
ad hoc per analizzare i parametri (come |InspIsWindow()|, ad esempio). E
infatti, se il programmatore non aggiunge nessuna funzione propria, la lista di
funzioni della libreria di controllo è sempre vuota (esiste in realtà un
ingresso muto di nome |dummy()| allo scopo di girare attorno a un buco di
|slink|).

È essenziale ricordarsi che le funzioni di patch gireranno ``come se'' fossero
funzioni della libreria controllata, per cui il registro |a6| conterrà il
puntatore a quest'ultima. In altre parole, non è possibile utilizzare alcun
tipo di dato globale indirizzato in modo relativo, perché altrimenti il
compilatore genererà codice del tipo

||movea.l	_LinkerDB(a6),a4||

che è completamente privo di senso, poiché |a6| non contiene quello che il
compilatore crede. Quindi, bisogna evitare la specifica |__saveds| (tanto non
abbiamo dati globali relativi) e compilare il tutto con |DATA=FARONLY|. Non ci
sono a questo proposito grossi problemi, perché i dati globali vengono scritti
da |InspectorLibInit()|, e letti da tutte le altre funzioni.

Senza andare troppo nei dettagli, il codice generato per ogni funzione
comprende la stampa dei parametri, del risultato ed un'eventuale coppia
|Forbid()|/|Permit()| per evitare di fare interferire le stampe di più processi.

Il codice definisce una variabile locale booleana |paramsOK|, inizialmente
posta a |TRUE|. Nel caso questa venga messa a |FALSE| dal codice nel file
|.insp|, e la flag che richiede di saltare l'esecuzione della funzione in caso
di errore nei parametri sia attivata, viene stampato un messaggio di errore e si
esce.


\head * Utilizzo *

|Inspector| non è decisamente un strumento di uso comune. Basta eseguire un
patch sbagliato e il sistema può schiantarsi irrimediabilmente. Anche il
controllo di funzioni apparentemente innocue come |Remove()| richiede la più
grande attenzione: infatti, molte funzioni di Exec sono chiamate decine di
volte al secondo dall'|input.device|, e non è salutare pensare di controllarne
l'ingresso senza prima escludere alcuni processi.

Uno dei grossi vantaggi rispetto ad altri strumenti consiste nella possibilità
di controllare codice altrui: avere un enforcer hit senza avere il sorgente è
abbastanza inutile, ma i dati visualizzati da |Inspector| permettono di
controllare con una certa precisione anche il funzionamento di codice
assolutamente ignoto.

A nostro avviso, un sistema di dimensioni così grandi deve necessariamente
avere un sistema di controllo molto raffinato e flessibile, o si rischia di
renderlo ingovernabile. La configurazione base di |Inspector|
contiene infatti circa quaranta librerie di controllo, più la
|inspector.library|.

Abbiamo scelto di attivare o disattivare i servizi offerti da |Inspector| a
tre livelli: {\it locale} (cioè funzione per funzione), {\it semilocale} (cioè
libreria per libreria) o {\it globale} (cioè per tutte le librerie disponibili).

Le singole flag permettono di attivare o disattivare la stampa dei parametri,
l'utilizzo di |Forbid()| e così via. Una cura particolare è stata rivolta alla
gestione della visibilità delle flag ai vari livelli.

Ogni flag, oltre al proprio stato (si/no), possiede un modo: normale,
trasparente o forzante (si parlerà quindi di flag normali, trasparenti o
forzanti). Non esistono flag globali trasparenti, né locali forzanti, per le
ragioni che vedremo.

Una flag forzante ad un certo livello forza lo stesso valore su tutte le flag
analoghe ai livelli inferiori. Quindi, ad esempio, selezionando la flag di
disabilitazione in modo forzante a livello globale si sospendono tutti i
controlli. La stessa selezione a livello semilocale otterrebbe un effetto
analogo per la libreria coinvolta. Chiaramente, non ha senso parlare di flag
forzanti a livello di singole funzioni.

Una flag normale fornisce un valore che è valido solo a meno che non sia
sovrascritto da un livello superiore. In mancanza di ulteriori specifiche,
questo è lo stato normale di ogni flag.

Per finire, una flag trasparente suggerisce di guardare ad un livello superiore
il proprio valore, anche nel caso il livello superiore non sia forzante. Se ad
esempio dobbiamo controllare parecchie funzioni di Intuition mostrando il
valore dei parametri, ma su una abbiamo bisogno di visualizzare solo
l'ingresso, possiamo attivare le flag semilocali della libreria di controllo, e
mettere in modo trasparente tutte le funzioni di controllo di Intuition, tranne
quella data.

La descrizione precisa dell'algoritmo di decisione per il valore di una flag è
il seguente:

\list\numbered
\item esamina la flag globale; se è forzante, restituiscine il valore;
\item esamina la flag semilocale; se è forzante, restituiscine il valore;
\item esamina la flag locale; se non è trasparente, restituiscine il valore;
\item esamina la flag semilocale; se non è trasparente, restituiscine il valore;
\item restituisci la flag globale.
\endlist

Le possibilità di combinazione sono vastissime, e permettono da una parte di
attivare e disattivare parti del sistema a piacimento, dall'altra di
controllare in maniera fine il comportamento delle singole funzioni.

Venendo ai dettagli implementativi, l'accesso più semplice ad |Inspector|
avviene tramite la libreria di funzioni |ARexx| contenute nella
\discretionary{|inspector.li|-}{|brary|}{|inspector.library|}. Esse permettono di accedere a tutte le funzionalità di
|Inspector| scrivendo una linea |ARexx| come, ad esempio,
||rx "ipatch('intuition',
  'OpenWindowTagList', TRUE)"||
(|rx| può essere omesso se
usate la WShell). Uno degli obiettivi della progettazione di |Inspector| è
stato infatti quello di consentire il più vasto numero di tecniche di accesso
possibile in maniera compatta. Attualmente uno dei sistemi più pratici è
certamente |ARexx|, ma vengono forniti anche comandi \amiga{}Dos che accedono
direttamente alle funzioni della |inspector.library| (su alcuni sistemi
lanciare |ARexx| potrebbe essere poco desiderabile).

È perfettamente possibile creare una applicazione GUI che permetta di
manipolare in maniera più amichevole le funzionalità di |Inspector|:
bisogna però sempre tener presente che si tratta di uno strumento estremamente
pericoloso (basta fare |SetFunction()| su funzioni vitali come |Supervisor()| e
i risultati sono immediati) e non è quindi il caso, come abbiamo già detto, di
incoraggiarne l'uso comune.

È anche possibile chiamare direttamente la |inspector.library| dai propri
programmi. Può infatti succedere che per ragioni di tempo reale non sia
possibile mettere sotto controllo una funzione prima di lanciare
l'applicazione. In tal caso, qualunque programma può chiamare, ad esempio
||InspectorPatch("intuition",
  "OpenWindow", 1);||
ed ottenere il controllo della funzione. Il metodo suggerito, soprattutto in
caso di applicazioni in tempo reale, è però diverso. Conviene prima realizzare
il patch e disattivarlo attivando la flag di disabilitazione. Poi,
||SetInspectorFlags("intuition",
  "OpenWindow",
  OPF_Reset,
  FUNCF_Disable)||
disattiva la flag. La seconda operazione non richiede nessun accesso al disco
(per l'apertura della libreria di controllo), e dura in generale pochissimo.


\head * Problemi di ricorsività *

Nel creare |Inspector|, ci siamo scontrati con gli stessi problemi dei
creatori di |Wedge|\note{|Wedge| è uno strumento di sviluppo della Commodore
che permette di controllare le chiamate a singole funzioni di libreria.}.
Abbiamo seguito per alcuni versi le stesse strade: innanzitutto, tutte le
funzioni di sistema utilizzate della |inspector.library| vengono chiamate
direttamente al loro indirizzo. Durante l'inizializzazione della libreria,
vengono eseguite una serie di |SetFunction()| fasulle che permettono però di
risalire all'indirizzo corrente delle funzioni necessarie. Gli indirizzi
vengono poi memorizzati, e il meccanismo di casting del SAS/C viene utilizzato
per piazzare i vari parametri nei loro rispettivi registri, e la base della
libreria in questione nel registro |a6|. Ad esempio, la funzione |Permit()|
viene memorizzata nella variabile |InspectorBase->CachedPermit|, di tipo |__asm
void (*)(register __a6 void *)|. Una macro ||#define Permit()
(CachedPermit)(SysBase)|| permette poi di chiamare in maniera trasparente la
versione memorizzata (|CachedPermit| è definita come
|InspectorBase->CachedPermit| da un'altra macro). Grazie a questa tecnica, è
possibile (sebbene altamente sconsigliato) controllare le chiamate a
|Forbid()|. Le versioni memorizzate sono poste nella struttura |InspectorBase|,
in modo che anche le librerie di controllo possano a loro volta copiarle ed
utilizzarle.

I problemi purtroppo sono meno evitabili quando una funzione utilizzata da
codice di controllo ne chiama al suo interno un'altra. In tal caso, è
impossibile evitare una ricorsione infinita con conseguente schianto. Le
funzioni di questo tipo sono fortunatamente pochissime---essenzialmente,
|RawDoFmt|---e non sono in particolar modo ``desiderabili''. La questione è
anche largamente semplificata dalla mancanza di output sui file di input/output
standard. Si deve però tener conto del fatto che nel caso si usi |Sushi| per
leggere su una CLI quello che viene scritto su seriale, l'output di |Sushi| può
attivare a sua volta nuovi controlli. Il pericolo di blocco è decisamente
minore a causa del controllo del trabocco del buffer, ma è certamente possibile
ridurre la macchina in condizioni di essenziale inusabilità. {\it Caveat
emptor}.

\head *Limiti*

|Inspector| è ancora un {\it work in progress}. Molte parti del sistema non sono
ancora complete, ed altre saranno probabilmente soggette a modifiche. La
struttura complessiva è già stata ridisegnata molte volte, ma siamo molto
soddisfatti di quella attuale.

Il generatore di codice non è per il momento in grado di gestire librerie che
passano o ritornano parametri in più di un registro (tipico esempio:
|mathieeddoubbas.library|).

Il codice di controllo dei parametri è veramente limitato. La struttura di
|Inspector| permette però di aggiungere con calma nuovi moduli, ed addirittura
di distribuire solo i file |.insp|, lasciando che sia l'utente stesso a
ricostruire il sistema. Un primo obiettivo sarebbe scrivere il controllo dei
parametri di tutte le librerie della Release 2, a cui potrebbe far seguito il
codice per le nuove librerie della 3.0.

\head *Conclusioni*

|Inspector| è nato da un mio vecchio sogno---uno strumento simile a
|SnoopDos|\note{|SnoopDos| è uno strumento di sviluppo liberamente
distribuibile che permette di controllare l'accesso dei programmi alla
|dos.library|.} o |Wedge|, ma di facile utilizzo, di struttura più elegante, e,
soprattutto, dotato di capacità ``intelligenti'' di controllo dei parametri.
Non era possibile, ovviamente, lavorare ad un programma del genere sotto 1.3.
Inoltre, le difficoltà erano già sufficienti lavorando in C---realizzare le
librerie in Assembler, come si era costretti a fare fino a poco tempo fa,
sarebbe stato uno spreco di tempo inaccettabile.

Con le nuove versioni del SAS/C si è creato un ambiente sufficientemente
potente da permettere la scrittura di |Inspector|. Fabrizio Lodi e Reinhard
Spisser hanno entusiasticamente accettato di collaborare ad un progetto la cui
realizzazione avrebbe richiesto molto più tempo e risorse di quante non ne
avessi a disposizione. Le ore passate a confrontarci sulla struttura del
sistema non solo ci hanno fatto ripensare e riscrivere da capo molte volte
componenti che potevano essere considerate soddisfacenti, ma hanno anche
consentito di arrivare al livello attuale di semplicità ed eleganza.

La perfezione viene raggiunta non quando non c'è più nulla da aggiungere, ma
quando non c'è più nulla da togliere (Saint-Exupéry).

{\signaturewidth=15pc
\makesignature}

\endarticle
