Lo scheletro di un Virtual Device Driver

Adesso gia' sapete qualcosa su VMM e i VxDs, ora possiamo apprendere come programmare un VxD. Avete pero' bisogno del Windows 95/98 Device Driver Development Kit. E' essenziale che lo abbiate. Il Windows 95 DDK e' disponibile solo ai sottoscrittori dell'MSDN. Al contrario il Windows 98 DDK e' ottenibile gratuitamente dalla Microsoft. Potete utilizzare il Windows 98 DDK anche per sviluppare VxD benche' sia orientato verso i WDM. E' possibile scaricare il Windows 98 DDK da questo indirizzo http://www.microsoft.com/hwdev/ddk/install98ddk.htm.
Potete scaricare l'intero pacchetto, circa 30 MB, o potete scaricare selettivamente solo quelle parti a cui siete interessati. Se scegliete di non prendere l'intero package, non dimenticatevi di scaricare la documentazione del Windows 95 DDK inclusa in other.exe
Il Windows 98 DDK contiene MASM versione 6.11d. Dovreste tuttavia aggiornarlo all'ultima versione. Per informazioni su dove scaricare gli upgrade per le ultime versioni, controllate a quest'indirizzo.
Il Windows 9x DDK contiene inoltre parecchi file include essenziali che non sono compresi nel package di MASM32.
Gli esempi di questo tutorial sono prelevabili da  qui.

Il formato file LE

Un VxD utilizza il formato file linear executable (LE). Questo e' il formato file sviluppato per OS/2 versione 2.0. Esso puo' contenere sia codice a 16 e 32-bit, il che e' uno dei requisiti dei VxDs. Ricordatevi che i VxDs risalgono ai giorni di Windows 3.x. A quell'epoca, Windows si eseguiva a partire dal DOS cosicche' alcuni VxDs potevano richiedere una qualche inizialiazzazione in modo reale prima che Windows commutasse la macchina in modo protetto. Il codice in modo reale a 16 bit deve quindi risiedere nello stesso file eseguibile di quello in modo protetto a 32 bit. Di conseguenza il formato di file LE e' la scelta logica. I driver NT, fortunatamente, non devono avere a che fare con alcuna inizializzazione in modo reale cosi' non devono usare il formato file LE. Essi invece utilizzano il formato file PE.

Il formato file LE
Codice e dati in un file LE sono salvati in segmenti con differenti attributi di esecuzione. Di seguito e' disponibile l'elenco delle classi di segmento.

  • LCODE   Codice e dati page-locked. Questo segmento e' bloccato in memoria. In altre parole, questo segmento non verra' paginato su disco. Il codice ed i dati che devono essere presenti in ogni momento in memoria dovrebbero risiedere in questo segmento. Specialmente un gestore di interrupt hardware.
  • PCODE Codice paginabile. Questo segmento e' paginabile da VMM. Il codice in questo segmento non necessita di essere sempre presente in memoria. VMM paginera' su disco questo segmento nel caso avesse bisogno di memoria fisica.
  • PDATA Dati paginabili.
  • ICODE Codice di inizializzazione. Il codice in questo segmento e' utilizzato solamente durante la fase di inizializzazione del VxD. Dopo l'inizializzazione, questo segmento verra' scaricato da VMM per liberare memoria fisica.
  • DBOCODE Codice e dati di debug. Il codice e i dati presenti in questo segmento sono utilizzati solo quando il VxD e' eseguito da un debugger. Esso, ad esempio, contiene gli handler per il messaggio di controllo Debug_Query.
  • SCODE Codice e dati statici. Questo segmento sara' sempre presente in memoria anche quando il VxD sara' scaricato. Questo segmento e' utile specialmente ai VxD dinamici quando devono essere caricati/scaricati piu' volte durante una sessione di Windows e vogliono memorizzare l'ultima configurazione/stato.
  • RCODE Codice e dati di inizializzazione in modo reale. Questo segmento contiene il codice e i dati a 16 bit per l'inizializzazione in modo reale.
  • 16ICODE Dati (USE16) per l'inizializzazione in modo protetto. Questo e' un segmento a 16bit che contiene il codice che il VxD vuole copiare dal modo protetto al modo V86. Per esempio, se volete copiare del codice V86 in una VM, esso deve risiedere in questo segmento. Se mettete il codice in un altro segmento l'assemblatore generera' codice errato, ad esempio potrebbe generare codice 32 bit invece di quello preventivato a 16 bit.
  • MCODE Stringe dei messagi precompilati. Questo segmento contine le stringe compilate sulla base delle macro per i messaggi di VMM. Questo vi aiuta a creare versioni multilingua del vostro driver.

Quanto detto non significa che il vostro VxD debba possedere TUTTI questi segmenti. Potete scegliere quali volete utilizzare. Per esempio, se il VxD non possiede nessuna inizializzazione in modo reale, non e' necessario includere il segmentoRCODE .
La maggior parte delle volte utilizzerete solo LCODE, PCODE e PDATA
. E' una vostra valutazione, come programmatori del VxD, scegliere i segmenti piu' appropriati per il vostro codice/dati. In generale, dovreste utilizzare il piu' possibilePCODE e PDATA in quanto VMM puo' paginare i segmenti fuori o dentro la memoria se e' necessario. Dovreste poi usare LCODE per memorizzare i gestori degli interrupt hardware e i servizi che saranno chiamati da questi handlers. Voi non utilizzate direttamente le classi di segmento. Dovete invece dichiarare i segmenti  basati su queste classi. Queste dichiarazioni di segmento sono contenute in un file di definizione del modulo (.def). La dichiarazione completa di un module definition file di un VxD e' la seguente :

VXD FIRSTVXD
SEGMENTS
    _LPTEXT    CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LTEXT      CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LDATA      CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TEXT        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _DATA        CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    CONST       CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _TLS          CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _BSS          CLASS 'LCODE'    PRELOAD NONDISCARDABLE
    _LMGTABLE    CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _LMSGDATA    CLASS 'MCODE'    PRELOAD NONDISCARDABLE IOPL
    _IMSGTABLE   CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _IMSGDATA     CLASS 'MCODE'    PRELOAD DISCARDABLE IOPL
    _ITEXT             CLASS 'ICODE'     DISCARDABLE
    _IDATA             CLASS 'ICODE'    DISCARDABLE
    _PTEXT            CLASS 'PCODE'    NONDISCARDABLE
    _PMSGTABLE    CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PMSGDATA    CLASS 'MCODE'    NONDISCARDABLE IOPL
    _PDATA            CLASS 'PDATA'    NONDISCARDABLE SHARED
    _STEXT            CLASS 'SCODE'    RESIDENT
    _SDATA            CLASS 'SCODE'    RESIDENT
    _DBOSTART    CLASS 'DBOCODE'    PRELOAD NONDISCARDABLE CONFORMING
    _DBOCODE       CLASS 'DBOCODE'    PRELOAD NONDISCARDABLE CONFORMING
    _DBODATA        CLASS 'DBOCODE'    PRELOAD NONDISCARDABLE CONFORMING
    _16ICODE          CLASS '16ICODE'    PRELOAD DISCARDABLE
    _RCODE             CLASS 'RCODE'
EXPORTS
    FIRSTVXD_DDB  @1

Il primo statement e' la dichiarazione del nome del VxD. Il nome di un VxD DEVE essere sempre in maiuscolo. Personalmente ho sperimentato con nomi in minuscolo e il VxD ha rifiutato di eseguire alcunche' eccetto di caricarsi in memoria.
Gli statements seguenti  sono invece le dichiarazioni dei segmenti. La dichiarazione consiste di tre parti: il nome del segmento, la classe e le proprieta' di esecuzione dello stesso. Potete vedere che ci sono molti segmenti basati sulla stessa classe: per esempio  _LPTEXT, _LTEXT, _LDATA sono tutti basati sulla classe di segmento  LCODE con esattamente le stesse proprieta'. Questi segmenti sono dichiarati allo scopo di rendere la stesura del codice piu' semplice da comprendere. Ad esempio, LCODE puo' contenere sia codice che dati. Sara' tutto piu' semplice per il programmatore se puo' memorizzare i dati nel segmento _LDATA e il codice nel segmento _LTEXT . Eventualmente nell'eseguibile finale entrambi i segmenti possono essere combinati in uno solo .
Un VxD esporta soltanto un simbolo, il device descriptor block (DDB). Il DDB e' in sostanza la struttura che contiene ogni cosa di cui VMM abbisogna di conoscere di un VxD. Voi DOVETE necessariamente esportare il DDB nel file di definizione del modulo.
Nella maggior parte dei casi potrete utilizzare il precedente file .DEF nei vostri nuovi progetti di VxD. Dovrete solo cambiare il nome del VxD nella prima e nell'ultima linea del file .DEF. Le dichiarazioni dei segmenti sono superflue per un progetto di VxD in asm. Sono riportate per un loro utilizzo con un progetto di VxD in C, ma usarle per un progetto in asm e' ugualmente corretto. Riceverete molti messaggi di warning ma poi si assemblera'. Potete evitare questi noiosi messagi di warning cancellando le dichiarazioni di segmenti che non utilizzate nel vostro progetto. 
vmm.inc contiene parecchie macro per la dichiarazione dei segmenti nel vostro file sorgente: 
 

_LTEXT VxD_LOCKED_CODE_SEG
_PTEXT VxD_PAGEABLE_CODE_SEG
_DBOCODE VxD_DEBUG_ONLY_CODE_SEG
_ITEXT VxD_INIT_CODE_SEG
_LDATA VxD_LOCKED_DATA_SEG
_IDATA VxD_IDATA_SEG
_PDATA VxD_PAGEABLE_DATA_SEG
_STEXT VxD_STATIC_CODE_SEG
_SDATA VxD_STATIC_DATA_SEG
_DBODATA VxD_DEBUG_ONLY_DATA_SEG
_16ICODE VxD_16BIT_INIT_SEG
_RCODE VxD_REAL_INIT_SEG

Ogni macro ha la sua controparte di chiusura. Ad esempio, se volete dichiarare un segmento  _LTEXT nel vostro file sorgente, dovreste farlo in questo modo:

VxD_LOCKED_CODE_SEG

<inserite il vostro codice qui>

VxD_LOCKED_CODE_ENDS

VxD Skeleton

Ora che conoscete i segmenti in un file LE, possiamo passare al file sorgente. Una cosa che potete notare sulla programmazione dei VxD e che fa un uso massiccio delle macro. Nella programmazione di un VxD trovere macro dappertutto. Ci vuole un po' per abituarsi. Queste macro sono fornite per nascondere alcuni dettagli sporchi ai programmatori ed, in qualche modo, rendere il codice sorgente piu' portabile. Se siete curiosi potete guardavi la definizione di queste macro nei vari file di include come ad esempio vmm.inc.
Qui di seguito c'e' il codice sorgente per lo scheletro di un VxD:
 
.386p
include vmm.inc

DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER

Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD

end


A prima vista, il sorgente non sembra nemmeno codice sorgente asm. Questo e' per via dell'uso delle macro. Ma analizziamo questo codice e presto vi sara' tutto chiaro.

         .386p

Dice all'assemblatore che vogliamo utilizzare il set di istruzioni 80386 incluse le istruzioni CPU privilegiate. Potete anche usare  .486p o .586p.
include vmm.inc
Dovete includere vmm.inc in ogni file sorgente per VxD perche' esso contiene le definizioni delle macro che utilizzate nei sorgenti. Potete aggiungere altri file include secondo le esigenze.
DECLARE_VIRTUAL_DEVICE FIRSTVXD,1,0, FIRSTVXD_Control, UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Come menzionato precedentemente, VMM apprende tutto cio' che ha bisogno di conoscere su un VxD dal device descriptor block (DDB). Un device descriptor block e' una struttura che contiene le informazioni vitali sul VxD, come in nome del VxD, il suo device ID, l'entrypoint dei suoi servizi VxD (se esistono) e cosi' via. Potete consultare questa struttura in vmm.inc. E' dichiarata come VxD_Desc_Block. Dovrete esportare questa struttura nel file .DEF . Vi sono 22 membri in questa struttura, ma di solito avrete bisogno di riempire solo alcuni di essi. In virtu' di cio' vmm.inc contiene una macro che inizializzera' e riempira' i membri della struttura per voi. Questa' macro e'  DECLARE_VIRTUAL_DEVICE. Essa ha il seguente formato:

Declare_Virtual_Device   Name, MajorVer, MinorVer, CtrlProc, DeviceID, InitOrder, V86Proc, PMProc, RefData

Una cosa che potete notare e' che nei sorgenti di un VxD le labels sono case-insensitive. Vi e' consentito utilizzare caratteri in maiuscolo, in minuscolo o una combinazione di questi. Procediamo coll'analizzare ogni parametro di Declare_virtual_device.

  • Name  Il nome del VxD. La lunghezza minima/massima e' di 8 caratteri. Esso DEVE essere in maiuscolo. Il nome dovrebbe essere univoco rispetto ai VxD nel sistema. La macro utilizza il nome anche per creare il nome del  DDB appendendo  _DDB al nome del device. In tal modo se usate  FIRSTVXD come nome del VxD, la macro Declare_Virtual_Device dichiarera' il nome del DDB come FIRSTVXD_DDB . Ricordatevi che dovrete anche esportare il DDB nel file .DEF. Dovete eguagliare la label nel file sorgente con quella nel file .DEF.
  • MajorVer e MinorVer Il valore superiore ed inferiroe di versione del vostro VxD
  • CtrlProc Il nome della  device control procedure per il vostro VxD. Una device control procedure e' una funzione che riceve e processa i messaggi di controllo per un VxD. Potete pensare ad una device control procedure come all'equivalente della window procedure. Dal momento che utilizzeremo la macro  Begin_Control_Dispatch per creare la nostra device control procedure, adotteremo il nome standard la cui forma e' VxDName_Control. La macro Begin_Control_Dispatch appende _Control al nome passatole (soliamente le si passa il nome del VxD) di conseguenza nel parametro CtrlProc dobbiamo specificare il nome del nostro VxD seguito da  _Control .
  • DeviceID L'identificativo univoco a 16-bit del vostro VxD. Avete bisogno dell' ID se e solo se il  vostro VxD deve gestire una delle seguenti situazioni
    • Il vostro VxD esporta dei VxD services per l'uso con altri VxDs. Visto che l'interfaccia int 20h utilizza il device ID per localizzare/identificare il VxD, e' imperativo che il vostro VxD debba avere un identificativo univoco.
    • il vostro VxD notifica globalmente la sua esistenza alle applicazioni in modo reale durante l'inizializzazione via int 2Fh funzione 1607h.
    • Alcuni programmi in modo reale  (TSR) utilizzeranno l' int 2Fh, funzione 1605h per caricare il vostro VxD.
    Se il vostro VxD non necessita di un device ID unico, potete specificare  UNDEFINED_DEVICE_ID in questo campo. Se invece avete bisogno di un ID univoco, dovete chiederne uno alla Microsoft.
  • InitOrder Ordine di inizializzazione, o in pratica, ordine di caricamento. VMM carica i VxDs in un ordine specifico. Ogni VxD dovra' possedere un numero d'ordine per il caricamento. Ad esempio,
      VMM_INIT_ORDER             EQU 000000000H
      DEBUG_INIT_ORDER         EQU 000000000H
      DEBUGCMD_INIT_ORDER  EQU 000000000H
      PERF_INIT_ORDER            EQU 000900000H
      APM_INIT_ORDER             EQU 001000000H


    Come vedete VMM, DEBUG e DEBUGCMD sono i primi VxDs ad essere caricati, seguiti da  PERF e APM. Il VxD con un piu' basso valore dell'ordine di inizializzazione e' caricato prima. Se il vostro VxD richiede i servizi di un'altro VxD durante la fase di inizializzazione, dovreste specificare come ordine di inizializzazione un valore piu' grande di quello del VxD che volete chiamare, in modo tale che, al momento in cui il vostro VxD e' caricato, il VxD e' gia' presente in memoria e pronto per voi. Se il vostro VxD non risente dell'ordine di inizializzazione, specificate UNDEFINED_INIT_ORDER in questo parametro.

  • V86Proc e PMProc Il vostro VxD puo' esportare delle API per l'utilizzo da parte di programmi in modo V86 ed in modo protetto. V86Proc e PMProc specificano gli indirizzi di queste API. Ricordatevi che i VxDs esistono principalmente per supportare quelle VMs, non appartenenti a quelle di sistema, che eseguono programmi DOS o applicazioni in modo protetto. Tutto cio' giustifica il motivo per i VxDs di supportare API per programmi DOS e in modo proteto. Se non esportate queste APi potete omettere questi campi.
  • RefData Dati di riferimento utilizzati dal Input Output Supervisor (IOS). L'unica occasione in cui utilizzerete questo campo e' quando programmerete un layer block driver per l'utilizzarlo con IOS. Potete anche omettere questo campo se il vostro VxD non e' un layer driver.
Abbiamo poi la macro Begin_Control_Dispatch .
Begin_control_dispatch FIRSTVXD
End_control_dispatch FIRSTVXD
Questa macro e la sua controparte, definiscono la device control procedure, ovvero la funzione che VMM chiama quando ci sono dei messaggi di controllo per il vostro VxD. Voi dovete specificare la prima meta' del nome della device control procedure, nel nostro esempio noi utilizziamo FIRSTVXD. La macro appendera' poi _Control al nome da noi fornito.Questo nome deve corrispondere a quello che avete specificato nel parametro  CtrlProc della macro Declare_virtual_device . La device control procedure si trova sempre in un segmento locked (VxD_LOCKED_CODE_SEG). La precedente device control procedure non fa nulla di particolare. Siete voi a dover specificare a quali messagi di controllo il vostro VxD e' interessato a processere e le funzioni che li gestiranno. A questo scopo usate la macro Control_Dispatch .
Control_Dispatchmessaggio, funzione
Per esempio, se il vostro VxD processa solo il messaggio Device_Init, la vostra control procedure dovrebbe apparire in questo modo:
Begin_Control_Dispatch  FIRSTVXD
  Control_Dispatch  Device_Init, OnDeviceInit
End_Control_DispatchFIRSTVXD
OnDeviceInit is the name of the function that will handle Device_Init message. You can name your function anything you like.
You end the VxD source code with end directive.
To recapitulate, at a minimum, a VxD must have a device control block and a device control procedure. You declare a device control block with Declare_Virtual_Device macro and a device control procedure with Begin_Control_Dispatch macro. You must export the device control block by specifying its name under EXPORTS directive in .DEF file.

Assemblare il VxD

Il processo di assemblaggio e' lo stesso di quello utilizzato per assemblare una normale applicazione win32. Invocate ml.exe con il codice sorgente e quindi linkate il file oggetto con link.exe. Le differenze consistono nei parametri della command line utilizzati da ml.exe e link.exe

 ml -coff -c -Cx -DMASM6 -DBLD_COFF -DIS_32firstvxd.asm

-coff  Specifica il formato oggetto COFF
-c   Assembla solamente. Non chiamare il linker per linkare il file oggetto.
-Cx  Preserva il case delle labels public ed extern.
-D<text> Definisce una macro di testo. Per esempio,  -DBLD_COFF definisce una macro di testo "BLD_COFF" che sara' utilizzata in una assemblaggio condizionale. Se siete interessati, potete cercare BLD_COFF nei file di include e vedere da voi quale effetto essa ha sul processo di assemblaggio. Cio' detto, nella command line sopra sono definite tre macro di testo: BLD_COFF, IS_32 e MASM6. Se siete familiari col C, questo processo e' analogo a :

#define BLD_COFF
#define IS_32
#define MASM6
link -vxd -def:firstvxd.def  firstvxd.obj

-vxd Specifica che vogliamo realizzare un VxD da un file oggetto
-def:<.DEF file> Specifica il nome del file di definizione del modulo del VxD

Trovo piu' conveniente utilizzare un makefile ma, se non vi piace l'approccio del makefile, potete sempre creare un file batch per automatizzare il processo di assembling.

NAME=firstvxd

$(NAME).vxd:$(NAME).obj
        link -vxd -def:$(NAME).def $(NAME).obj

$(NAME).obj:$(NAME).asm
        ml -coff -c -Cx  -DMASM6 -DBLD_COFF -DIS_32 $(NAME).asm

Traduzione italiana a cura di: Kill3xx - kaneda27@hotmail.com

Torna alla main page! Torna alla pagina principale