Disinstallare i TSR

Sappiamo che, per un programma TSR, la capacità di disinstallarsi (liberando la RAM allocata e rilasciando i vettori di interrupt agganciati) è una caratteristica utile.

Sappiamo anche, per esperienza, che non tutti i TSR ne sono dotati e pertanto possono essere rimossi dalla memoria esclusivamente con un nuovo bootstrap.

Ecco allora una utility di qualche aiuto: essa è, a sua volta, un TSR, in grado di disinstallare tutti i TSR caricati successivamente.

/********************

    Barninga_Z! - 1991

        ZAPTSR.C - TSR in grado di rimuovere dalla ram tutti i TSR
            caricati dopo la propria installazione (nonche' se' medesimo),
            ripristinando i vettori di interrupt attivi al momento del
            proprio caricamento e liberando tutti i blocchi di memoria
            allocati ad un PSP maggiore del proprio. E' possibile
            installarne piu' copie in RAM; quando si richiede la
            disinstallazione (invocare con un asterisco come parametro
            sulla command line) viene disinstallata l'ultima copia
            installata (criterio L.I.F.O.) e, con essa, tutti i TSR
            caricati successivamente. Passando un piu' (+) sulla command
            line, ZAPTSR si installa allocando a se' stesso tutti i
            blocchi di RAM liberi ad indirizzi minori del proprio.

            STRATEGIA: il numero di copia installata, l'indirizzo di
                segmento del MCB della porzione residente e la tavola
                dei vettori sono conservati nello spazio riservato dalla
                funzione dummy GData(). La comunicazione tra porzione
                residente e porzione transiente e' gestita mediante
                l'int 2Fh. La main() effettua la scelta tra
                disinstallazione e installazione; se ZAPTSR e' gia'
                attivo in RAM viene invocata confirm() per richiedere
                conferma. La func install() gestisce le operazioni
                relative all'installazione; uninstall() quelle relative
                alla disinstallazione.

        Compilato sotto BORLAND C++ 1.01:

            bcc -O -d -rd zaptsr.c

********************/

#pragma  inline
#pragma  warn -pia
#pragma  warn -rvl
#pragma  -k+     // il codice e' scritto per TURBO C++ 1.01

#include <stdio.h>
#include <conio.h>
#include <dos.h>

#define  PRG           "ZAPTSR"    /* nome del programma */
#define  REL           "1.0"       /* versione */
#define  YR            "1991"       /* anno di compilazione */
#define  HEY_YOU       0xB3    /* byte riconoscimento chiamata all'int 2Fh */
#define  HERE_I_AM     0xA1C9        /* risposta per gia' presente in RAM */
#define  HANDSHAKE     0x00  /* richiesta se gia' presente in RAM */
#define  UNINSTALL     0x01  /* richiesta disinstallazione all'int 2Fh */
#define  UNINST_OPT    '*'  /* opzione richiesta disinstallazione */
#define  ALLOC_OPT     '+'   /* opz. allocazione blocchi precedenti */
#define  CNT_SPC       2       /* bytes occupati dal numero di copia install. */
#define  MCB_SPC       2       /* bytes occupati dall'ind.seg. del MCB resid. */
#define  VEC_SPC       1024    /* bytes occupati dalla tavola dei vettori */
#define  FALSE         NULL      /* booleano per falso */
#define  TRUE          (!FALSE)   /* booleano per vero */
#define  LASTBLK       'Z'     /* segnala ultimo blocco DOS di memoria */

const char *CopyRight = \
PRG": TSRs controller -"REL"- Barninga_Z! "YR".\n\
";
const char *ConfirmMsg = \
PRG": Already active; install copy #%u (Y/N)? \
";
const char *EnvErrMsg = \
PRG": Couldn't release environment block; memory error.\n\
";
const char *InstMsg = \
PRG": Copy #%u installed; RAM status and vectors table saved.\n\
        NOTE: Installing "PRG" with a plus sign ('+') as command line\n\
              parameter will protect lower MCBs.\n\
        NOTE: All TSRs loaded after this copy of "PRG" (and "PRG" also)\n\
              will be unloaded by invoking "PRG" with an asterisk ('*')\n\
              as command line parameter.\n\
";
const char *UnInstMsg = \
PRG": Copy #%u uninstalled; vectors restored and RAM freed up.\n\
";
const char *UnInstErrMsg = \
PRG": Cannot uninstall; not active in RAM.\n\
        DOS errorlevel set to 1.\n\
";

struct MCB {   /* struttura per la gestione dei Memory Control Blocks */
    char     pos;      /* 'Z' = ultimo blocco; 'M' = non ultimo */
    unsigned psp;  /* PSP del programma proprietario del blocco */
    unsigned dim;  /* dimensione del blocco in paragrafi */
    char     res[3];   /* riservato al DOS */
    char     name[8];  /* nome progr. se DOS >= 4.0; altrimenti non usato */
};

void _restorezero(void);        /* non e' dichiarata negli include di bcc */

/********************
    GData(): funzione dummy; lo spazio riservato nel code segment dalle
        db e' utilizzato per memorizzare il numero della copia, l'indir.
        di seg. del MCB della porzione residente e la tavola dei vettori.
        L'opcode dell'istruzione RETF rappresenta un byte (in eccesso) in
        coda allo spazio riservato ai dati globali.
        Se si compilasse (sempre con TC++ 1.0 o successivi) ma senza la
        opzione -k- lo spazio disponibile comprenderebbe 5 bytes in eccesso
        (gli opcodes per la gestione dello stack piu' la RETF) di cui 3 in
        testa. Questi ultimi non rappresenterebbero un problema in quanto i
        dati memorizzati in GData() non devono essere inizializzati dal
        compilatore, ma lo sono a run-time.
********************/

void far GData(void)
{
    asm {
        db CNT_SPC dup(?);     /* numero della copia installata */
        db MCB_SPC dup(?);     /* ind. di seg. del MCB della parte residente */
        db VEC_SPC dup(?);     /* tavola dei vettori */
    }
}

/********************
    new2Fh(): gestore dell'int 2Fh. Utilizzato per le comunicazioni tra
        parte transiente e parte residente. Se non riconosce in AH il
        segnale della parte transiente (HEY_YOU) concatena il gestore
        precedente il cui indirizzo e' prelevato direttamente nella copia
        di tavola dei vettori contenuta in GData(). Altrimenti analizza AL
        per determinare quale servizio e' richiesto dalla porzione
        transiente. Sono riconosciuti due servizi:
        1) HANDSHAKE: la parte transiente richiede se vi e' una copia di
           ZAPTSR gia' attiva in RAM; new2Fh() restituisce in AX la parola
           d'ordine quale risposta affermativa e in DX il numero della
           copia.
        2) UNINSTALL: la parte transiente richiede la disinstallazione
           dell'ultima copia caricata; new2Fh() restituisce in AX:DX
           l'indirizzo della GData() residente. La convenzione generale
           per la restituzione di double words da funzioni è DX:AX,
           sacrificata in questo caso per ottenere codice piu' compatto
           efficiente.
********************/

void far new2Fh(void)
{
    asm {
        pop bp;
        cmp ah,HEY_YOU;
        jne CHAIN;
        cmp al,HANDSHAKE;
        jne NO_HANDSHAKE;
        push ds;
        mov ax,seg GData;
        mov ds,ax;
        mov bx,offset GData;
        mov dx,ds:word ptr[bx];        /* DX = numero di copia */
        mov ax,HERE_I_AM;      /* AX = parola d'ordine */
        pop ds;
        iret;
    }
NO_HANDSHAKE:
    asm {
        cmp al,UNINSTALL;
        jne NO_UNINSTALL;
        mov ax,seg GData;
        mov dx,offset GData;   /* AX:DX = indirizzo di GData() residente */
        iret;
    }
NO_UNINSTALL:
CHAIN:
    asm jmp dword ptr GData+MCB_SPC+CNT_SPC+(4*2Fh);
}

/********************
    releaseEnv(): libera il blocco di memoria allocato dal DOS alla copia
        di environment creata per ZAPTSR.
********************/

void releaseEnv(void)
{
    extern unsigned _envseg;

    if(freemem(_envseg))
         printf(EnvErrMsg);
}

/********************
    restoredata(): ripristina i vettori di interrupt copiandoli dalla
        GData() residente (di cui ottiene l'indirizzo via int 2Fh) alla
        tavola dei vettori. Restituisce l'indirizzo di segmento del MCB
        della parte residente.
********************/

unsigned restoredata(void)
{
    asm {
        push ds;
        mov al,UNINSTALL;
        mov ah,HEY_YOU;
        int 2Fh;
        cli;
        mov ds,ax;
        mov si,dx;     /* DS:SI punta a GData residente */
        add si,CNT_SPC;        /* salta il n. di copia */
        mov bx,ds:word ptr [si];
        add si,MCB_SPC;
        xor ax,ax;
        mov es,ax;
        xor di,di;     /* ES:DI punta a tavola vettori */
        mov cx,512;
        rep movsw;     /* ripristina tavola vettori */
        sti;
        pop ds;
    }
    return(_BX);
}

/********************
    getfirstmcb(): ottiene dal DOS l'indirizzo del primo MCB in RAM.
        ATTENZIONE: si basa su un servizio non documentato dell'in 21h
********************/

unsigned getfirstmcb(void)
{
    asm {
        mov ah,52h;
        int 21h;
        mov ax,es:[bx-2];
    }
    return(_AX);
}

/********************
    uninstall(): pilota le oprazioni di disinstallazione. Da restoredata()
        ottiene l'indirizzo di segmento della parte residente; da
        getfirstmcb() ottiene l'indirizzo di segmento del primo MCB nella
        RAM. Questo e' il punto di partenza per un ciclo in cui
        uninstall() libera (azzrandone nel MCB l'indirizzo del PSP
        proprietario) tutti i blocchi appartenenti a PSP residenti ad
        indirizzi successivi a quello del MCB della parte residente.
        Con questa tecnica sfuggono alla disinstallazione i TSR
        eventualmente caricati ad inidirizzi inferiori a quello della
        parte residente, quantunque dopo di essa cronologicamente (evento
        raro, indicatore di problemi di allocazione).
********************/

void uninstall(unsigned cnt)
{
    register resMCB, mcb;

    resMCB = restoredata();
    mcb = getfirstmcb();
    do {
        mcb += ((struct MCB far *)MK_FP(mcb,0))->dim+1;
        if(((struct MCB far *)MK_FP(mcb,0))->psp > resMCB)
            ((struct MCB far *)MK_FP(mcb,0))->psp = 0;
    } while(((struct MCB far *)MK_FP(mcb,0))->pos != LASTBLK);
    printf(UnInstMsg,cnt);
}

/********************
    savedata(): effettua il salvataggio dei dati nella GData(). Sono
        copiati, nell'ordine, il numero della copia di ZAPTSR in fase di
        installazione, l'indirizzo di segmento del MCB del medesimo e la
        tavola dei vettori.
********************/

void savedata(unsigned cnt)
{
    asm {
        push ds;
        cli;
        cld;
        mov ax,seg GData;
        mov es,ax;
        mov di,offset GData;
        mov ax,cnt;    /* salva numero di copia */
        stosw;
        mov ax,_psp;
        dec ax;        /* salva segmento MCB */
        stosw;
        xor ax,ax;
        mov ds,ax;
        xor si,si;
        mov cx,512;    /* salva tavola vettori */
        rep movsw;
        sti;
        pop ds;
    }
}

/********************
    install(): pilota le operazioni di installazione. Invoca
        _restorezero(), definita nello startup code, per ripristinare i
        vettori eventualmente agganciati dallo startup code medesimo;
        invoca savedata() passandole come parametro il numero della copia
        di ZAPTSR in via di installazione; invoca releaseEnv() per
        disallocare il blocco dell'environment; se e' richiesta l'opzione
        ALLOC_OPT sulla command line install() alloca a ZAPTSR tutti i
        blocchi di RAM liberi aventi indirizzo inferiore a quello di
        ZAPTSR stesso, per evitare che TSRs invocati successivamente
        vi si installino, occultandosi. Infine attiva il gestore
        dell'int 2Fh new2Fh() mediante setvect() e installa ZAPTSR
        chiamando direttamente l'int 21h, servizio 31h (non e' usata
        keep() per evitare una nuova chiamata alla _restorezero()). I
        paragrafi residenti sono calcolati in modo da allocare solo la RAM
        indispensabile a GData() e new2Fh(). Notare che non viene salvato
        il vettore originale dell'int 2Fh con getvect() in quanto esso e'
        comunque presente nella copia della tavola dei vettori generata
        con savedata().
********************/

void install(unsigned cnt,unsigned mem)
{
    register mcb;

    _restorezero();        /* ripristina vettori 00h-06h*/
    savedata(cnt);
    releaseEnv();
    if(mem)
        for(mcb = getfirstmcb(); mcb < (_psp-1); mcb +=
        ((struct MCB far *)MK_FP(mcb,0))->dim+1)
            if(!((struct MCB far *)MK_FP(mcb,0))->psp)
                ((struct MCB far *)MK_FP(mcb,0))->psp = _psp;
    setvect(0x2F,(void(interrupt *)())new2Fh);
    printf(InstMsg,cnt);
    _DX = FP_SEG(releaseEnv)+FP_OFF(releaseEnv)/16+1-_psp;
    _AX = 0x3100;
    geninterrupt(0x21);
}

/********************
    AreYouThere(): invoca l'int 2Fh per verificare se e' gia' attiva in
        RAM un'altra copia di ZAPTSR. Solo in questo caso, infatti, in AX
        e' restituito dall'int 2Fh (cioe' dalla new2Fh() residente) il
        valore HERE_I_AM, che funge da parola d'ordine. Allora DX contiene
        il numero della copia di ZAPTSR che ha risposto (l'ultima
        caricata).
********************/

unsigned AreYouThere(void)
{
    _AH = HEY_YOU;
    _AL = HANDSHAKE;
    geninterrupt(0x2F);
    if(_AX == HERE_I_AM)
        return(_DX);
    return(0);
}

/********************
    confirm(): utilizzata per chiedere all'utente la conferma
        dell'intenzione di installare una ulteriore copia di ZAPTSR quando
        ve n'e' gia' una (o piu' di una) attiva in RAM.
********************/

int confirm(unsigned cnt)
{
    int c;

    do {
        printf(ConfirmMsg,cnt);
        printf("%c\n",c = getch());
        switch(c) {
            case 'N':
            case 'n':
                return(FALSE);
            case 'Y':
            case 'y':
                return(TRUE);
        }
    } while(TRUE);
}

/********************
    main(): distingue tra richiesta di installazione o disinstallazione e
        intraprende le azioni opportune. Se il primo (o unico) carattere
        del primo (o unico) parametro della command line e' un asterisco,
        main() procede alla disinstallazione. In qualunque altro caso e'
        effettuata l'installazione. Il registro DOS errorlevel contiene 0
        se ZAPTSR e' stato installato o disinstallato regolarmente; 1 se
        e' stata tentata una disinstallazione senza che ZAPTSR fosse
        attivo in RAM.
********************/

int main(int argc,char **argv)
{
    register cnt = 0, mem = FALSE;

    printf(CopyRight);
    if(argc > 1) {
        if(*argv[1] == UNINST_OPT)
            if(cnt = AreYouThere()) {
                uninstall(cnt);
                return(0);
            }
            else {
                printf(UnInstErrMsg);
                return(1);
            }
        if(*argv[1] == ALLOC_OPT)
            mem = TRUE;
    }
    if(cnt = AreYouThere())
        if(!confirm(cnt+1))
            return(0);
    install(cnt+1,mem);
    return(0);
}

I commenti inseriti nel listato rendono superfluo dilungarsi nella descrizione degli algoritmi; è invece opportuno evidenziare le due limitazioni di cui ZAPTSR soffre. In primo luogo esso controlla solamente i 640 Kb di memoria convenzionale: rimane esclusa la upper memory, resa disponibile tra i 640 Kb e il primo Mb da alcuni gestori di memoria estesa/espansa nonché dal DOS 5.0. Inoltre, la RAM allocata a programmi TSR installati dopo ZAPTSR, ma ad indirizzi di memoria inferiori, non viene liberata: detti programmi sono solamente disattivati mediante il ripristino dei vettori di interrupt originali.

Il programma deve essere compilato con l'opzione ­k+, in quanto le parti scritte in inline assembly tengono conto del codice generato dal compilatore per la gestione standard dello stack anche nelle funzioni che non prendono parametri. Ciò vale anche con riferimento alla GData(), che deve riservare ai dati un numero esatto di byte.

Il lettore volonteroso (e temerario) può tentare di realizzare un programma che operi come un vero e proprio controllore del sistema, intercettando i TSR di volta in volta caricati per essere in grado di individuarli e disinstallarli in ogni caso: la tabella che segue contiene alcuni suggerimenti:

INTERRUPT E SERVIZI DI CARICAMENTO E TERMINAZIONE DEI PROGRAMMI

INT
SERV
STRATEGIA
2Fh
Necessario per consentire la comunicazione tra porzione transiente e porzione residente.
21h
4Bh
Utilizzato dal DOS per caricare da disco a memoria i programmi.

Se AL = 0 il programma viene eseguito. In tal caso DS:DX punta al nome; la word ad ES:BX è l'indirizzo di segmento dell'environment (se è 0 il programma eseguito condivide l'environment di quello chiamante).

Prima di concatenare il gestore originale occorre salvare la tavola dei vettori.

20h
Il programma termina senza rimanere residente: la tabella dei TSR non necessita aggiornamenti.
21h
00h
Come il precedente
21h
4Ch
Come il precedente
21h
31h
Il programma termina e rimane residente in memoria.

Il PSP del programma che sta per essere installato è quello attuale, a meno che il "controllore" lo abbia sostituito con il proprio attivandosi.

La tabella dei TSR deve essere aggiornata con i dati raccolti intercettando l'int 21h servizio 4Bh.

27h
Come il precedente


OK, andiamo avanti a leggere il libro...

Non ci ho capito niente! Ricominciamo...