NTSTATUS(*PDRIVER_DISPATCH) (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
cioè puntatori a funzione che restituiscono un valore NTSTATUS e ricevono come parametri un puntatore al device che questo driver gestisce ed un puntatore ad un oggetto di tipo IRP.
IRP sta per I/O Request Packet e rappresenta la richiesta fatta dal client al device. L'IRP è creato dall'I/O Manager al momento della richiesta fatta dal client al device ed è costituita da una istanza della struttura IRP seguita da una o più istanze di tipo IO_STACK_LOCATION (l'I/O Manager è responsabile dell'inizializzazione solo della prima istanza di tipo IO_STACK_LOCATION, le altre devono essere inizializzate in un secondo momento da altri soggetti). Le informazioni rilevanti sulla richiesta fatta dal client al device possono essere recuperate dal driver consultando il puntatore all'IRP passato alla relativa dispatch routine. Tali informazioni sono divise tra i campi della struttura IRP stessa e quelli della struttura IO_STACK_LOCATION che segue.
Detto questo, in realtà non è solo il client che può effettuare una richiesta al device come non è solo l'I/O Manager che può creare l'IRP ma al momento sono casi che non ci interessano. Inoltre, per adesso si prenderanno in considerazione solo IRP a cui è "accodata" una sola istanza di tipo IO_STACK_LOCATION. Si è voluto comunque introdurre tali argomenti perché verranno ripresi in futuro e per il particolare modo con cui si accede alla struttura IO_STACK_LOCATION a partire dal puntatore all'IRP. Se non si fosse accennato il fatto che l'istanza di tipo IO_STACK_LOCATION segue quella di tipo IRP probabilmente sarebbe sembrata alquanto ambigua la necessità di usare un metodo (in realtà è una macro) chiamato IoGetCurrentIrpStackLocation per recuperare tale struttura, chiedendosi magari perché IO_STACK_LOCATION non è semplicemente un campo di IRP. Segue una rappresentazione grafica delle strutture IRP e IO_STACK_LOCATION con una breve descrizione dei campi più importanti.
Per quanto riguarda la struttura IRP
MdlAddress punta ad una istanza di tipo MDL quando il supporto all'accesso alla memoria di tipo Direct I/O viene richiesto all'I/O Manager (i dettagli verranno discussi in una prossima lezione).
AssociatedIrp è l'unione di tre membri di cui, al momento, ci interessa solo SystemBuffer, che punta ad un buffer non-paged, allocato nella memoria di sistema, quando il supporto all'accesso alla memoria di tipo Buffered I/O viene richiesto all'I/O Manager (i dettagli verranno discussi in una prossima lezione).
IoStatus contiene due campi: Status e Information. Status è spesso impostato con lo stesso valore NTSTATUS ritornato dalla dispatch routine corrente. Il significato di Information dipende dal tipo di richiesta ma i casi più significativi riguardano le richieste in lettura e scrittura dove Information è impostato che il numero di byte trasferiti nell'operazione.
RequestorMode è un valore che indica la modalità di esecuzione di chi ha fatto la richiesta (UserMode o KernelMode).
PendingReturned è un valore booleano che se è TRUE indica che un driver ha marcato la richiesta come pendente. Ogni funzione di completamento (dettagli più avanti) dovrebbe controllare se questo campo è TRUE ed in caso affermativo (e se non ha intenzione di ritornare STATUS_MORE_PROCESSING_REQUIRED) chiamare IoMarkIrpPending per propagare l'informazione di richiesta pendente ai driver con device più in alto nel device stack (maggiori dettagli verranno forniti in una prossima lezione).
UserBuffer contiene l'indirizzo del buffer, in user space, fornito dal client per soddisfare la richiesta fatta al device. Se, ad esempio, la richiesta è in lettura, UserBuffer punta ad una zona di memoria in user space che verrà usata come input dal client e come output dal driver. Al contrario, se la richiesta è in scrittura, UserBuffer punta ad una zona di memoria in user space che verrà usata come output dal client e come input dal driver. Si pensi a come funzionano ReadFile e WriteFile (richieste in lettura e scrittura fatte al filesystem) per farsi un'idea più precisa.
Tail è l'unione di un gran numero di membri di cui solo Overlay è interessante al momento in quanto IoGetCurrentIrpStackLocation è implementata come macro in questo modo
#define IoGetCurrentIrpStackLocation(Irp) { (Irp)->Tail.Overlay.CurrentStackLocation; }
Per quanto riguarda IO_STACK_LOCATION invece
MajorFunction è l'indice collegato alla dispatch routine (IRP_MJ_CREATE, IRP_MJ_CLOSE, ecc.) ed è utile nei casi in cui una singola funzione gestisce più indici major function (come nel caso di MyDriverCreateClose visto nella lezione precedente; si veda [1]).
Parameters è l'unione di un gran numero di strutture, ognuna valida nel contesto di una operazione specifica. Ad esempio, per una operazione in lettura che coinvolga l'indice major function IRP_MJ_READ (si vedranno i dettagli di questo indice major function e della relativa dispatch routine in una prossima lezione) la struttura da usare è Parameters.Read. Nel caso di una operazione che coinvolga l'indice major function IRP_MJ_DEVICE_CONTROL invece, la struttura da usare è Parameters.DeviceIoControl (anche per tale indice major function verranno dati maggiori dettagli in una prossima lezione).
FileObject è un puntatore al FILE_OBJECT eventualmente associato all'IRP (non importante al momento).
DeviceObject è il puntatore al device object associato all'IRP e che viene passato alla dispatch routine come primo parametro quindi quasi mai si rivela utile.
CompletionRoutine è l'indirizzo della funzione di completamento che è stata impostata con IoSetCompletionRoutine al livello superiore nel device stack (maggiori dettagli verranno forniti in una prossima lezione).
Context è un puntatore all'eventuale parametro da passare alla funzione di completamento.
Si può controllare personalmente la composizione di tali strutture usando il comando dt
kd> dt nt!_IRP . +0x000 Type : Int2B +0x002 Size : Uint2B +0x004 AllocationProcessorNumber : Uint2B +0x006 Reserved : Uint2B +0x008 MdlAddress : +0x010 Flags : Uint4B +0x018 AssociatedIrp : +0x000 MasterIrp : Ptr64 _IRP +0x000 IrpCount : Int4B +0x000 SystemBuffer : Ptr64 Void +0x020 ThreadListEntry : +0x000 Flink : Ptr64 _LIST_ENTRY +0x008 Blink : Ptr64 _LIST_ENTRY +0x030 IoStatus : +0x000 Status : Int4B +0x000 Pointer : Ptr64 Void +0x008 Information : Uint8B +0x040 RequestorMode : Char +0x041 PendingReturned : UChar +0x042 StackCount : Char +0x043 CurrentLocation : Char +0x044 Cancel : UChar +0x045 CancelIrql : UChar +0x046 ApcEnvironment : Char +0x047 AllocationFlags : UChar +0x048 UserIosb : +0x050 UserEvent : +0x058 Overlay : +0x000 AsynchronousParameters : <unnamed-tag> +0x000 AllocationSize : _LARGE_INTEGER +0x068 CancelRoutine : +0x070 UserBuffer : +0x078 Tail : +0x000 Overlay : <unnamed-tag> +0x000 Apc : _KAPC +0x000 CompletionKey : Ptr64 Void
kd> dt nt!_IO_STACK_LOCATION . +0x000 MajorFunction : UChar +0x001 MinorFunction : UChar +0x002 Flags : UChar +0x003 Control : UChar +0x008 Parameters : +0x000 Create : <unnamed-tag> +0x000 CreatePipe : <unnamed-tag> +0x000 CreateMailslot : <unnamed-tag> +0x000 Read : <unnamed-tag> +0x000 Write : <unnamed-tag> +0x000 QueryDirectory : <unnamed-tag> +0x000 NotifyDirectory : <unnamed-tag> +0x000 NotifyDirectoryEx : <unnamed-tag> +0x000 QueryFile : <unnamed-tag> +0x000 SetFile : <unnamed-tag> +0x000 QueryEa : <unnamed-tag> +0x000 SetEa : <unnamed-tag> +0x000 QueryVolume : <unnamed-tag> +0x000 SetVolume : <unnamed-tag> +0x000 FileSystemControl : <unnamed-tag> +0x000 LockControl : <unnamed-tag> +0x000 DeviceIoControl : <unnamed-tag> +0x000 QuerySecurity : <unnamed-tag> +0x000 SetSecurity : <unnamed-tag> +0x000 MountVolume : <unnamed-tag> +0x000 VerifyVolume : <unnamed-tag> +0x000 Scsi : <unnamed-tag> +0x000 QueryQuota : <unnamed-tag> +0x000 SetQuota : <unnamed-tag> +0x000 QueryDeviceRelations : <unnamed-tag> +0x000 QueryInterface : <unnamed-tag> +0x000 DeviceCapabilities : <unnamed-tag> +0x000 FilterResourceRequirements : <unnamed-tag> +0x000 ReadWriteConfig : <unnamed-tag> +0x000 SetLock : <unnamed-tag> +0x000 QueryId : <unnamed-tag> +0x000 QueryDeviceText : <unnamed-tag> +0x000 UsageNotification : <unnamed-tag> +0x000 WaitWake : <unnamed-tag> +0x000 PowerSequence : <unnamed-tag> +0x000 Power : <unnamed-tag> +0x000 StartDevice : <unnamed-tag> +0x000 WMI : <unnamed-tag> +0x000 Others : <unnamed-tag> +0x028 DeviceObject : +0x030 FileObject : +0x038 CompletionRoutine : +0x040 Context :
Ad ogni modo, ottenuto il puntatore all'IRP (che rappresenta la richiesta del client) nella dispatch routine relativa (invocata ed incaricata della gestione di tale richiesta), cosa dovremmo farci di questo IRP? Per vederlo in maniera pratica riprendiamo l'esempio della lezione precedente (si veda [1]) dove si sono inizializzati DriverObject->MajorFunction[IRP_MJ_CREATE] e DriverObject->MajorFunction[IRP_MJ_CLOSE] a MyDriverCreateClose.
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) { // ... // Invocate quando in usermode vengono chiamate CreateFile e CloseHandle DriverObject->MajorFunction[IRP_MJ_CREATE] = MyDriverCreateClose; DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyDriverCreateClose; // ... }
NTSTATUS MyDriverCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) { Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IoCompleteRequest(Irp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
Come detto in quella lezione MyDriverCreateClose si occupa semplicemente di segnalare all'I/O Manager che l'operazione di apertura\richiesta o chiusura\decremento dell'handle ha avuto successo; è poi l'I/O Manager che si occupa di ritornare l'handle (relativo al device object creato dall'Object Manager durante la chiamata a IoCreateDevice) al client o di decrementarne il contatore dei riferimenti. Ad ogni modo, il campo Status viene inizializzato a STATUS_SUCCESS per indicare che la richiesta rappresentata da questo IRP ha avuto successo. Successivamente si imposta Information a zero perché non trattandosi di una operazione in lettura o scrittura tale campo non ha un significato particolare ma bisogna comunque specificarlo azzerandolo (per operazioni in lettura o scrittura il valore zero equivale ad uno stato di errore). In seguito si invoca IoCompleteRequest che cede il controllo all'I/O Manager passandogli l'IRP con il campo IoStatus impostato adeguatamente e un valore opzionale che consente di incrementare la priorità del thread che ha dato origine alla richiesta. Nel contesto di device puramente software tale thread è spesso lo stesso di quello in esecuzione quindi aumentarne la priorità non ha effetto (non si può cambiare priorità ad un thread che si trova nello stato "in esecuzione"). Per questo tale parametro è impostato spesso a IO_NO_INCREMENT. Infine si ritorna lo stesso NTSTATUS impostato in IoStatus.Status. E' possibile ritornare valori differenti in contesti che lo consentono ma tali contesti non sono di interesse al momento.
Riferimenti:
[1] 04 - Comunicazione Client / Driver: Device
[2] Windows Kernel Programming - Pavel Yosifovich
Nessun commento:
Posta un commento