Archive for February, 2008

KeWaitForMultipleObjects, pero no mucho!

27 de February de 2008

Há um tempo atrás, meu amigo Lesma publicou um post sobre suas aventuras Kernel a dentro em busca do motivo de uma tela azul. O problema foi causado por uma chamada incorreta à função KeWaitForMultipleObjects na tentativa de esperar por 4 objetos. Pois é Wanderley, isso que é dá herdar código de Deus e o mundo. Enfim é aquela coisa toda. Só porque a função tem a palavra “Múltiplo” no nome, não significa que podemos esperar por essa quantidade absurda de 4 objetos. Neste post vou dar um simples exemplo de como utilizar esta função.

Onde mora o limite?

A função KeWaitForMultipleObjects utiliza de um array de elementos do tipo KWAIT_BLOCK que contém os dados referentes aos múliplos objetos da espera que será realizada. Em muitos casos, essa quantidade de múltiplos objetos que participam da espera não passa de três objetos. A fim de evitar de sempre ter de construir um array de estruturas de KWAIT_BLOCK para realizar uma espera múltipla, a estrutura que representa uma thread em Kernel mode, a KTHREAD, contém um array de quatro elementos desta estrutura embutida nela, como se pode observar abaixo.

kd> dt nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 Teb              : Ptr32 Void
   +0x024 TlsArray         : Ptr32 Void
   +0x028 KernelStack      : Ptr32 Void
   +0x02c DebugActive      : UChar
   +0x02d State            : UChar
   +0x02e Alerted          : [2] UChar
   +0x030 Iopl             : UChar
   +0x031 NpxState         : UChar
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   +0x051 Spare0           : [3] UChar
   +0x054 WaitStatus       : Int4B
   +0x058 WaitIrql         : UChar
   +0x059 WaitMode         : Char
   +0x05a WaitNext         : UChar
   +0x05b WaitReason       : UChar
   +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : Uint4B
   +0x06c BasePriority     : Char
   +0x06d DecrementCount   : UChar
   +0x06e PriorityDecrement : Char
   +0x06f Quantum          : Char
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : Ptr32 Void
   +0x0d4 KernelApcDisable : Uint4B
   +0x0d8 UserAffinity     : Uint4B
   +0x0dc SystemAffinityActive : UChar
   +0x0dd PowerState       : UChar
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY
   +0x120 SoftAffinity     : Uint4B
   +0x124 Affinity         : Uint4B
   +0x128 Preempted        : UChar
   +0x129 ProcessReadyQueue : UChar
   +0x12a KernelStackResident : UChar
   +0x12b NextProcessor    : UChar
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x140 PreviousMode     : Char
   +0x141 EnableStackSwap  : UChar
   +0x142 LargeStack       : UChar
   +0x143 ResourceIndex    : UChar
   +0x144 KernelTime       : Uint4B
   +0x148 UserTime         : Uint4B
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : UChar
   +0x165 ApcStateIndex    : UChar
   +0x166 ApcQueueable     : UChar
   +0x167 AutoAlignment    : UChar
   +0x168 StackBase        : Ptr32 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY
   +0x1b8 FreezeCount      : Char
   +0x1b9 SuspendCount     : Char
   +0x1ba IdealProcessor   : UChar
   +0x1bb DisableBoost     : UChar

Destes quatro elementos, três são destinados a serem utilizados pela KeWaitForMultipleObjects, enquanto o que quarto elemento é de uso reservado do sistema para implementar a espera com timeout.

Para esperar até 3 objetos, o código da espera ficaria como segue abaixo:

    //-f--> Array de PVOIDs
    PVOID       pObjects[3];
 
    //-f--> Temos que inicializar um array de ponteiros para
    //      os objetos os quais a espera será baseada.
    pObjects[0] = &kEvent1;
    pObjects[1] = &kEvent2;
    pObjects[2] = &kEvent3;
 
    //-f--> Chamada simples para uma espera múltipla de
    //      3 objetos.
    nts = KeWaitForMultipleObjects(3,
                                   pObjects,
                                   WaitAll,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL,
                                   NULL);

Isso significa que não podemos esperar por mais de 3 objetos por vez? Também não é bem assim. Quando for necessário esperar por mais de três objetos, teremos que alocar um buffer grande o suficiente para manter um array de N elementos da estrutura KWAIT_BLOCK. Onde N é menor ou igual a 64. Se houver mais objetos que isso, teremos um belo MAXIMUM_WAIT_OBJECTS_EXCEEDED.

“If a buffer is supplied, the Count parameter may not exceed MAXIMUM_WAIT_OBJECTS. If no buffer is supplied, the Count parameter may not exceed THREAD_WAIT_OBJECTS.”

//-f--> From wdm.h
 
#define THREAD_WAIT_OBJECTS 3       // Builtin usable wait blocks
 
#define MAXIMUM_WAIT_OBJECTS 64     // Maximum number of wait objects

Neste caso, para uma espera de 10 objetos teríamos o seguinte código:

   //-f--> Array de PVOIDs
    PVOID           pObjects[10];
 
    //-f--> Array adicional de KWAIT_BLOCKs
    PKWAIT_BLOCK    pWaitBlocks = NULL;
 
    //-f--> Temos que inicializar um array de ponteiros para
    //      os objetos os quais a espera será baseada.
    pObjects[0] = &kEvent1;
    pObjects[1] = &kEvent2;
    pObjects[2] = &kEvent3;
    pObjects[3] = &kEvent4;
    pObjects[4] = &kEvent5;
 
    //-f--> Por encreça que parível, eu sei usar loops e arrays,
    //      mas fica mais didático mostrar que os objetos não
    //      precisam necessariamente estar em um array, enquanto
    //      que os ponteiros para eles sim.
    pObjects[5] = &kEvent6;
    pObjects[6] = &kEvent7;
    pObjects[7] = &kEvent8;
    pObjects[8] = &kEvent9;
    pObjects[9] = &kEvent10;
 
    //-f--> Não poderemos utilizar o array de KWAIT_BLOCKS
    //      embutido na KTHREAD, teremos que alocar o nosso array.
    pWaitBlocks = (PKWAIT_BLOCK) ExAllocatePool(NonPagedPool,
                                                sizeof(KWAIT_BLOCK) * 10);
 
    //-f--> Testar alocação não mata ninguém, mas tenta não
    //      testar para ver quantos morrem.
    ASSERT(pWaitBlocks != NULL);
 
    //-f--> Chamada simples para uma espera múltipla de
    //      10 objetos e sem bug-check.
    nts = KeWaitForMultipleObjects(10,
                                   pObjects,
                                   WaitAll,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL,
                                   pWaitBlocks);
 
    //-f--> Memory leak é falta de educação
    ExFreePool(pWaitBlocks);

Resumidamente é isso aí.
Até mais mais…