Luigi Di Biasi


XP Programming Style
Sondaggio
Argomento Spider e WebCrawler

Interessante
Non interessante

Driver Development Part 2 (2/2) - Utilizzare gli IOCTL - DRAFT

Driver Development Part 2: Introduction to implementing IOCTLs
( Post 2 di 2 : Utilizzo degli IOCTLs)

 
Link documento originale:http://www.codeproject.com/KB/system/driverdev2.aspx
Autore del documento : Toby Opferman

ATTENZIONE
 La traduzione potrebbe contenere ERRORI.
Inoltre potrebbero essere presenti OMISSIONI, CAMBIAMENTI o APPUNTI presenti per rendere il testo più accessibile... a "me" in primis... 

PENSIERO PERSONALE:
"L'autore spiega in modo molto esaustivo l'utilizzo degli IOCTL senza però spiegare da dove nasce la necessità di utilizzarli. VEDI 5TH EDITION - MANCA PARAGRAFO QUI -"

Input/Output Controls

Gli IOCTL sono usati durante la comunicazione tra driver e applicazioni al posto delle semplici funzioni di read e write. Di norma un driver esporta un determinato numero di IOCTLs, in base al numero di funzioni che mette a disposizione, e definisce le strutture dati che possono essere usate nella comunicazione. Generalmente queste strutture NON dovrebbero utilizzare i puntatori altrimenti l'I/O Manager potrebbe non interpretarle, quando si lavora fuori dal contesto dell'applicazione user

Tutti i dati dovrebbero essere contenuti nello stesso blocco. 
[??in modo da restare nella stessa pagina??]

Se si desidera utilizzare i puntatori bisogna usare il metodo seguente:

- Creare un offset nel BLOCCO DATI dopo la fine dei DATI STATICI. (Quindi consideriamo un blocco fisico più grande composto da un blocco logico di dati statici e la parte restante libera di essere puntata.)
- Utilizzare i puntatori per puntare la parte (OFFSET) del blocco dati, rimanendo all'interno del BLOCCO FISICO.

Questa limitazione è legata al fatto che un driver può leggere i dati in user-mode finchè rimane nello stesso contesto del processo.
Detto questo è possibile implementare i puntatori ma il driver dovrebbe copiare la pagina (dove è contenuto il blocco dati) in un buffer oppure bloccare la pagina in memoria (re implementando una direct i/o). 

Il processo in user-mode, per utilizzare gli IOCTL, chiama la funzione DeviceIoControl. 

Definiamo un IOCTL

La prima cosa da fare è definire un codice IOCTL che verrà utilizzato tra l'applicazione e il driver durante la comunicazione. [dice l'autore: "Io semplicemente riassumo questo articoli da MSDN" http://msdn.microsoft.com/en-us/ms123402.aspx, purtroppo li link non sembra funzionare e non dove puntava.]

Collegare un codice IOCTL al suo corrispondente in user-mode significa gestire una entità simile ai  Windows Message. 

Esso è semplicemente un valore usato dal driver per implementare alcune funzioni richieste con valori di input e output predefiniti. (Ad Esempio WM_PAINT con i valori WPARAM e LPARAM)

Inoltre, bisogna fare qualcosa in piu' rispetto a cio che faremmo con i Windows Message. Un IOCTL definisce i requisiti di accesso in modo da poter definire quale metodo usare per trasferire i dati tra driver è applicazione.

Un IOCTL è un numero a 32 bit strutturato come segue:

 * [Common |Device Type|Required Access|Custom|Function Code|Transfer Type]
 *   31     30       16 15          14  13   12           2  1            0

* I primi due bit (low-bit 0 e 1) definiscono il "transfer type" o modalità di trasferimento che può essere una delle seguenti: (come per la FileRead e la FileWrite)

METHOD_OUT_DIRECT,METHOD_IN_DIRECTMETHOD_BUFFERED or METHOD_NEITHER.

  * I bit dal 2 al 12 definiscono il "Function Code" (La funzione vera e propria)

* Il bit successivo (13) è definito come "custom bit". Viene utilizzato insieme al function code  per determinare se l' IOCTLs è definito come di sistema o è stato definito dal vendor del driver. Il fatto che "function code" successivi o uguali 0x800 siano "costum" ci ricorda il messaggio WM_USER di windows. (I messaggi privati).

* I successivi 2 bit ( 14 e 15 ) definiscono l'accesso richiesto per rilasciare l'IOCTL. L'I/O manager rigetta l'IOCTL richiesto se l'handle non è aperto in modo corretto: FILE_READ_DATA andFILE_WRITE_DATA

    * I bit da 16 a 30 rappresentano il DeviceType per il quale l'IOCTLs è stato scritto. L'ultimo bit rappresenta un valore definito dall'utente.

Esiste una macro che possiamo usare per definire i nostri IOCTLs velocemente: CTL_CODE. Vediamo come è possibile definire questi codici IOCTL:

  Collapse

/* 
 *   IOCTL's are defined by the following bit layout.
 * [Common |Device Type|Required Access|Custom|Function Code|Transfer Type]
 *   31     30       16 15          14  13   12           2  1            0
 * 
 *   Common          - 1 bit.  This is set for user-defined
 *                     device types.
 *   Device Type     - This is the type of device the IOCTL
 *                     belongs to.  This can be user defined
 *                     (Common bit set).  This must match the
 *                     device type of the device object.
 *   Required Access - FILE_READ_DATA, FILE_WRITE_DATA, etc.
 *                     This is the required access for the
 *                     device.
 *   Custom          - 1 bit.  This is set for user-defined
 *                     IOCTL's.  This is used in the same
 *                     manner as "WM_USER".
 *   Function Code   - This is the function code that the
 *                     system or the user defined (custom 
 *                     bit set)
 *   Transfer Type   - METHOD_IN_DIRECT, METHOD_OUT_DIRECT,
 *                     METHOD_NEITHER, METHOD_BUFFERED, This
 *                     the data transfer method to be used.
 *
 */
#define IOCTL_EXAMPLE_SAMPLE_DIRECT_IN_IO    \
        CTL_CODE(FILE_DEVICE_UNKNOWN,        \
                 0x800,                      \
                 METHOD_IN_DIRECT,           \
                 FILE_READ_DATA | FILE_WRITE_DATA)
 
#define IOCTL_EXAMPLE_SAMPLE_DIRECT_OUT_IO   \ 
        CTL_CODE(FILE_DEVICE_UNKNOWN,        \
                 0x801,                      \
                 METHOD_OUT_DIRECT,          \
                 FILE_READ_DATA | FILE_WRITE_DATA) 

#define IOCTL_EXAMPLE_SAMPLE_BUFFERED_IO     \
        CTL_CODE(FILE_DEVICE_UNKNOWN,        \
                 0x802,                      \
                 METHOD_BUFFERED,            \
                 FILE_READ_DATA | FILE_WRITE_DATA)

#define IOCTL_EXAMPLE_SAMPLE_NEITHER_IO      \
        CTL_CODE(FILE_DEVICE_UNKNOWN,        \
                 0x803,                      \
                 METHOD_NEITHER,             \
                 FILE_READ_DATA | FILE_WRITE_DATA)

Implementare una comunicazione tramite IOCTL

La prima cosa che ci occorre è essenzialmente una funzione switch che distribuisca i vari IOCTL alle routine di gestione appropriate. Essenzialmente è la stessa cosa che fa una Windows Procedure quando inoltra i messaggi alle routine di gestione. 

Tuttavia non esiste una cosa del genere "def IOCTL proc". (Credo voglia intendere che la routine di gestione degli IOCTL è comunque puntata nel DriverObject->IRP_MJ_XxX e non si definisce a mano)

Il parametro Parameters.DeviceIoControl.IoControlCode della stuttura  IO_STACK_LOCATION contiene il codice IOCTL che è stato invocato, quindi anche gli IOCTLs viaggiano sugli IRP. 

Il codice seguente mostra la funzione switch mentre si occupa di effettuare il dispatch degli IOCTL.

 Collapse
NTSTATUS Example_IoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
    PIO_STACK_LOCATION pIoStackIrp = NULL;
    UINT dwDataWritten = 0;

    DbgPrint("Example_IoControl Called \r\n");

    pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);    

    if(pIoStackIrp) /* Should Never Be NULL! */
    {
        switch(pIoStackIrp->Parameters.DeviceIoControl.IoControlCode)
        {
            case IOCTL_EXAMPLE_SAMPLE_DIRECT_IN_IO:
                 NtStatus = Example_HandleSampleIoctl_DirectInIo(Irp, 
                                 pIoStackIrp, &dwDataWritten);
                 break;

            case IOCTL_EXAMPLE_SAMPLE_DIRECT_OUT_IO:
                 NtStatus = Example_HandleSampleIoctl_DirectOutIo(Irp, 
                                 pIoStackIrp, &dwDataWritten);
                 break;

            case IOCTL_EXAMPLE_SAMPLE_BUFFERED_IO:
                 NtStatus = Example_HandleSampleIoctl_BufferedIo(Irp, 
                                 pIoStackIrp, &dwDataWritten);
                 break;

            case IOCTL_EXAMPLE_SAMPLE_NEITHER_IO:
                 NtStatus = Example_HandleSampleIoctl_NeitherIo(Irp, 
                                 pIoStackIrp, &dwDataWritten);
                 break;
        }
    }

    Irp->IoStatus.Status = NtStatus;
    Irp->IoStatus.Information = dwDataWritten;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return NtStatus;

}

Come per le implementazioni precedenti di ReadFile e WriteFile viste negli esempi precedenti, anche qui troviamo una struttura molto semplice che svolge quei compiti. 

In realtà l'utilizzo degli IOCTLs rende più flessibile la definizione delle funzioni messe a disposisione dal driver perché ci evita di effettuare PARSE di stringhe (che dovrebbero essere passate tramite WriteFile) per comandare il Driver. Chiaramente gli IOCTL possono essere usati per leggere, scrivere e sia per inviare comandi al driver. (nessuno ci vieta però di comandarlo unicamente tramite gli IRP_MJ_READ e WRITE)

METHOD_x_DIRECT (IN e OUT) 

I metodi METHOD_IN_DIRECT e METHOD_OUT_DIRECT possono essere spiegati allo stesso tempo. In realtà sono quasi la stessa cosa. Il buffer di INPUT è passato utilizzando una implementazione di tipo BUFFERED. Il buffer di OUTPUT invece viene passato utilizzando una implementazione con MdlAddress. 

La differenza tra l'utilizzo i IN e OUT è che con IN è possibile utilizzare il buffer di output per passare dati al driver. "OUT" è utilizzato solo per ritornare i dati. L'esempio che segue non usa questa distinzione tra IN e OUT e li utilizza come se fossero la stessa cosa. (Commettendo un errore che l'autore spiega nei commenti del codice ma noi riportiamo qui per evitare confusione)

"Usando METHOD_IN_DIRECT l'I/O manager crea un MDL con accesso di tipo READ che l'applicazione può usare per inviare grandi quantità di dati al driver. Usando OUT_DIRECT viene creato un MDL con accesso WRITE dal quale l'applicazione può ricevere grandi quantità di dati dal driver."


The difference is that METHOD_IN_DIRECT creates
     * an MDL for the outputbuffer with
     * *READ* access so the user mode application
     * can send large amounts of data to the driver for reading.
     *
     * METHOD_OUT_DIRECT creates an MDL
     * for the outputbuffer with *WRITE* access so the user mode
     * application can recieve large amounts of data from the driver!



 Collapse
NTSTATUS Example_HandleSampleIoctl_DirectOutIo(PIRP Irp, 
      PIO_STACK_LOCATION pIoStackIrp, UINT *pdwDataWritten)
{
    NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
    PCHAR pInputBuffer;
    PCHAR pOutputBuffer;
    UINT dwDataRead = 0, dwDataWritten = 0;
    PCHAR pReturnData = "IOCTL - Direct Out I/O From Kernel!";
    UINT dwDataSize = sizeof("IOCTL - Direct Out I/O From Kernel!");
    DbgPrint("Example_HandleSampleIoctl_DirectOutIo Called \r\n");

    /*
     * METHOD_OUT_DIRECT
     *
     *    Input Buffer = Irp->AssociatedIrp.SystemBuffer
     *    Ouput Buffer = Irp->MdlAddress
     *
     *    Input Size   =  Parameters.DeviceIoControl.InputBufferLength
     *    Output Size  =  Parameters.DeviceIoControl.OutputBufferLength
     *
     * What's the difference between METHOD_IN_DIRECT && METHOD_OUT_DIRECT?
     *
     * The function which we implemented METHOD_IN_DIRECT
     * is actually *WRONG*!!!!  We are using the output buffer
     * as an output buffer!  The difference is that METHOD_IN_DIRECT creates
     * an MDL for the outputbuffer with
     * *READ* access so the user mode application
     * can send large amounts of data to the driver for reading.
     *
     * METHOD_OUT_DIRECT creates an MDL
     * for the outputbuffer with *WRITE* access so the user mode
     * application can recieve large amounts of data from the driver!
     *
     * In both cases, the Input buffer is in the same place,
     * the SystemBuffer.  There is a lot
     * of consfusion as people do think that
     * the MdlAddress contains the input buffer and this
     * is not true in either case.     
     */


    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
    pOutputBuffer = NULL;

    if(Irp->MdlAddress)
    {
        pOutputBuffer = 
          MmGetSystemAddressForMdlSafe(Irp->MdlAddress, 
          NormalPagePriority);
    }

    if(pInputBuffer && pOutputBuffer)
    {
                                                             
        /*
         * We need to verify that the string
         * is NULL terminated. Bad things can happen
         * if we access memory not valid while in the Kernel.
         */
       if(Example_IsStringTerminated(pInputBuffer, 
         pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, 
         &dwDataRead)) {
            DbgPrint("UserModeMessage = '%s'", pInputBuffer);
            DbgPrint("%i >= %i", 
              pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength, 
              dwDataSize);
            if(pIoStackIrp->
              Parameters.DeviceIoControl.OutputBufferLength >= dwDataSize)
            {
                /*
                 * We use "RtlCopyMemory" in the kernel instead of memcpy.
                 * RtlCopyMemory *IS* memcpy, however it's best to use the
                 * wrapper in case this changes in the future.
                 */
                RtlCopyMemory(pOutputBuffer, pReturnData, dwDataSize);
                *pdwDataWritten = dwDataSize;
                NtStatus = STATUS_SUCCESS;
            }
            else
            {
                *pdwDataWritten = dwDataSize;
                NtStatus = STATUS_BUFFER_TOO_SMALL;
            }

       }
    }

    return NtStatus;
}


METHOD_BUFFERED

The METHOD_BUFFERED implementation does essentially the same thing as the Read and Write implementations. A buffer is allocated and the data is copied from this buffer. The buffer is created as the larger of the two sizes, the input or output buffer. Then the read buffer is copied to this new buffer. Before you return, you simply copy the return data into the same buffer. The return value is put into the IO_STATUS_BLOCK and the I/O Manager copies the data into the output buffer.

 Collapse
NTSTATUS Example_HandleSampleIoctl_BufferedIo(PIRP Irp, 
        PIO_STACK_LOCATION pIoStackIrp, UINT *pdwDataWritten)
{
    NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
    PCHAR pInputBuffer;
    PCHAR pOutputBuffer;
    UINT dwDataRead = 0, dwDataWritten = 0;
    PCHAR pReturnData = "IOCTL - Buffered I/O From Kernel!";
    UINT dwDataSize = sizeof("IOCTL - Buffered I/O From Kernel!");
    DbgPrint("Example_HandleSampleIoctl_BufferedIo Called \r\n");

    /*
     * METHOD_BUFFERED
     *
     *    Input Buffer = Irp->AssociatedIrp.SystemBuffer
     *    Ouput Buffer = Irp->AssociatedIrp.SystemBuffer
     *
     *    Input Size   =  Parameters.DeviceIoControl.InputBufferLength
     *    Output Size  =  Parameters.DeviceIoControl.OutputBufferLength
     *
     *    Since they both use the same location
     *    so the "buffer" allocated by the I/O
     *    manager is the size of the larger value (Output vs. Input)
     */


    pInputBuffer = Irp->AssociatedIrp.SystemBuffer;
    pOutputBuffer = Irp->AssociatedIrp.SystemBuffer;

    if(pInputBuffer && pOutputBuffer)
    {
                                                             
        /*
         * We need to verify that the string
         * is NULL terminated. Bad things can happen
         * if we access memory not valid while in the Kernel.
         */
       if(Example_IsStringTerminated(pInputBuffer, 
         pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, 
         &dwDataRead)) {
            DbgPrint("UserModeMessage = '%s'", pInputBuffer);
            DbgPrint("%i >= %i", 
              pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength, 
              dwDataSize);
            if(pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength
                                                            >= dwDataSize)
            {
                /*
                 * We use "RtlCopyMemory" in the kernel instead of memcpy.
                 * RtlCopyMemory *IS* memcpy, however it's best to use the
                 * wrapper in case this changes in the future.
                 */
                RtlCopyMemory(pOutputBuffer, pReturnData, dwDataSize);
                *pdwDataWritten = dwDataSize;
                NtStatus = STATUS_SUCCESS;
            }
            else
            {
                *pdwDataWritten = dwDataSize;
                NtStatus = STATUS_BUFFER_TOO_SMALL;
            }

       }
    }

    return NtStatus;
}

METHOD_NEITHER

This is also the same as implementing neither I/O. The original user mode buffers are passed into the driver.

 Collapse
NTSTATUS Example_HandleSampleIoctl_NeitherIo(PIRP Irp, 
       PIO_STACK_LOCATION pIoStackIrp, UINT *pdwDataWritten)
{
    NTSTATUS NtStatus = STATUS_UNSUCCESSFUL;
    PCHAR pInputBuffer;
    PCHAR pOutputBuffer;
    UINT dwDataRead = 0, dwDataWritten = 0;
    PCHAR pReturnData = "IOCTL - Neither I/O From Kernel!";
    UINT dwDataSize = sizeof("IOCTL - Neither I/O From Kernel!");

    DbgPrint("Example_HandleSampleIoctl_NeitherIo Called \r\n");

    /*
     * METHOD_NEITHER
     *
     *    Input Buffer = Parameters.DeviceIoControl.Type3InputBuffer
     *    Ouput Buffer = Irp->UserBuffer
     *
     *    Input Size   =  Parameters.DeviceIoControl.InputBufferLength
     *    Output Size  =  Parameters.DeviceIoControl.OutputBufferLength
     *
     */


    pInputBuffer = pIoStackIrp->Parameters.DeviceIoControl.Type3InputBuffer;
    pOutputBuffer = Irp->UserBuffer;

    if(pInputBuffer && pOutputBuffer)
    {

        /*
         * We need this in an exception handler or else we could trap.
         */
        __try {
                ProbeForRead(pInputBuffer,
                     pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength, 
                     TYPE_ALIGNMENT(char));                                 
                /*
                 * We need to verify that the string
                 * is NULL terminated. Bad things can happen
                 * if we access memory not valid while in the Kernel.
                 */
               if(Example_IsStringTerminated(pInputBuffer,
                    pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength,
                    &dwDataRead))
               {
                    DbgPrint("UserModeMessage = '%s'", pInputBuffer);
                    
                    ProbeForWrite(pOutputBuffer, 
                      pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength,
                      TYPE_ALIGNMENT(char));
                    if(pIoStackIrp->
                      Parameters.DeviceIoControl.OutputBufferLength 
                      >= dwDataSize)
                    {
                        /*
                         * We use "RtlCopyMemory"
                         * in the kernel instead of memcpy.
                         * RtlCopyMemory *IS* memcpy,
                         * however it's best to use the
                         * wrapper in case this changes in the future.
                         */
                        RtlCopyMemory(pOutputBuffer,
                                      pReturnData,
                                      dwDataSize);
                        *pdwDataWritten = dwDataSize;
                        NtStatus = STATUS_SUCCESS;
                    }
                    else
                    {
                        *pdwDataWritten = dwDataSize;
                        NtStatus = STATUS_BUFFER_TOO_SMALL;
                    }

               }
                

        } __except( EXCEPTION_EXECUTE_HANDLER ) {

              NtStatus = GetExceptionCode();     
        }

    }


    return NtStatus;
}

Calling DeviceIoControl

This is a very simple implementation.

 Collapse
        ZeroMemory(szTemp, sizeof(szTemp));
        DeviceIoControl(hFile,
                        IOCTL_EXAMPLE_SAMPLE_DIRECT_IN_IO, 
                        "** Hello from User Mode Direct IN I/O", 
                        sizeof("** Hello from User Mode Direct IN I/O"), 
                        szTemp, 
                        sizeof(szTemp), 
                        &dwReturn, 
                        NULL);
        printf(szTemp);
        printf("\n");

        ZeroMemory(szTemp, sizeof(szTemp));
        DeviceIoControl(hFile,
                         IOCTL_EXAMPLE_SAMPLE_DIRECT_OUT_IO, 
                         "** Hello from User Mode Direct OUT I/O", 
                         sizeof("** Hello from User Mode Direct OUT I/O"), 
                         szTemp, 
                         sizeof(szTemp), 
                         &dwReturn, 
                         NULL);
        printf(szTemp);
        printf("\n");

        ZeroMemory(szTemp, sizeof(szTemp));
        DeviceIoControl(hFile,
                        IOCTL_EXAMPLE_SAMPLE_BUFFERED_IO, 
                        "** Hello from User Mode Buffered I/O", 
                        sizeof("** Hello from User Mode Buffered I/O"), 
                        szTemp, 
                        sizeof(szTemp), 
                        &dwReturn, 
                        NULL);
        printf(szTemp);
        printf("\n");

        ZeroMemory(szTemp, sizeof(szTemp));
        DeviceIoControl(hFile,
                        IOCTL_EXAMPLE_SAMPLE_NEITHER_IO, 
                        "** Hello from User Mode Neither I/O", 
                        sizeof("** Hello from User Mode Neither I/O"), 
                        szTemp, 
                        sizeof(szTemp), 
                        &dwReturn, 
                        NULL);
        printf(szTemp);
        printf("\n");


lunedì, 14 set 2009 Ore. 14.38
Statistiche
  • Views Home Page: 27.212
  • Views Posts: 51.085
  • Views Gallerie: 0
  • n° Posts: 41
  • n° Commenti: 33
Archivio Posts
Anno 2012

Anno 2011

Anno 2010

Anno 2009

Anno 2008
Copyright © 2002-2007 - Blogs 2.0
dotNetHell.it | Home Page Blogs
ASP.NET 2.0 Windows 2003