A manhã desse dia começou com o fato de que "se" quebrou. Essa expressão foi cunhada por um de meus colegas que demonstrou como seu depurador entra no bloco if ao percorrer o código, enquanto a condição de que o if verificado foi exatamente falsa. O problema naquele momento acabou sendo trivial - ele usou uma versão otimizada da versão e, nesse cenário, a confiança na depuração passo a passo, é claro, é impossível. Mas a própria expressão "se quebrou" se enraizou e foi usada aqui desde então para indicar uma situação em que algo tão fundamental deixou de funcionar que mal se acreditava nela.
Portanto, naquele dia, a função
NtQuerySystemInformation foi interrompida - uma das funções mais importantes do sistema operacional Windows que retorna informações sobre processos, threads, descritores de sistema etc. Sobre os benefícios do uso desse recurso, uma vez eu escrevi
este artigo . Mas acabou que mesmo esses pilares de um sistema podem às vezes falhar.
Então o que aconteceu?
Por um longo tempo (já há vários anos), usamos a chamada para a função NtQuerySystemInformation com o argumento SystemHandleInformation para obter informações sobre todos os descritores no sistema. Sim, esse argumento refere-se formalmente aos não documentados, mas se você começar a procurar informações sobre como listar todos os descritores em todos os aplicativos com SO Windows atualmente em execução, a combinação NtQuerySystemInformation + SystemHandleInformation será a opção proposta com mais frequência. E realmente funciona, em todos os sistemas operacionais iniciados no Windows NT.
Por que você pode precisar procurar descritores em todos os processos? Bem, por várias razões. Utilitários como o
Process Hacker apenas os mostram para fins informativos. Existem programas que fazem isso para procurar um recurso atualmente bloqueado por alguém (por exemplo, um arquivo). E você pode, por exemplo, encontrar um mutex no processo de outra pessoa, usado para permitir a execução de apenas uma cópia do programa, fechá-lo e permitir o lançamento de duas instâncias desse aplicativo. Ou liste os descritores para duplicá-los, a fim de organizar a sandbox. Em geral, existem muitas tarefas.
Não darei a descrição completa da enumeração do descritor aqui, apenas direi que, em geral, era semelhante a exemplos comuns, como
este :
while ((status = NtQuerySystemInformation( SystemHandleInformation, handleInfo, handleInfoSize, NULL )) == STATUS_INFO_LENGTH_MISMATCH) handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
Mas, então, inicio nosso aplicativo - e de repente acontece que o descritor de que preciso (e sei com certeza que ele existe!) Não está na lista retornada pela função NtQuerySystemInformation (). É isso aí, eles vieram - "se está quebrado".
Tentando reproduzir o problema em outros computadores no escritório. Em alguns, é reproduzido, na maioria - não. Estamos tentando entender como aqueles em que se reproduz diferem daqueles em que tudo é bom. A versão do Windows é a mesma em todos os lugares, atualizações, construção do nosso programa - tudo é idêntico. De repente, alguém percebe que todos os laptops nos quais o problema foi reproduzido são do mesmo modelo. Incompatibilidade de hardware? Mas por que agora, de repente, costumava funcionar ... Além disso, existem outros laptops do mesmo modelo no escritório que funcionam agora. Até as versões dos drivers de dispositivo foram comparadas - tudo parece ser o mesmo. Mas tudo funciona em alguns laptops, mas não em outros.
Puxar o cabelo na cabeça durou cerca de meio dia, até que notei acidentalmente duas coisas:
- Os PIDs de processo, que geralmente são números de três, quatro ou cinco dígitos no meu computador, por algum motivo, tornaram-se seis dígitos. Era estranho o suficiente ver um PID do tipo 780936. Eu não tinha percebido isso antes. Além disso, o número total de processos em execução era bastante adequado (até cem).
- O gerenciador de tarefas na guia CPU mostrou o número total de descritores no sistema - e era enorme, mais de 800.000.
Para uma aplicação normal, é normal abrir cem ou dois descritores. Bem, mil. Com o uso ativo, o chrome pode abrir cerca de 2000, o Visual Studio pode abrir 3000 em grandes projetos, mas quem abriu 800.000? Felizmente, o Process Hacker mencionado anteriormente permite mostrar o número de descritores para cada processo e até classificar a lista de processos pelo número de descritores usados.
E o que vemos? E vemos algo como esta imagem:

Devo dizer que acabei de fazer a captura de tela acima, portanto a primeira na lista de processos possui "apenas" cerca de 20.000 descritores. E então, quando vi o problema pela primeira vez, havia cerca de 650.000 lá.E quem é o nosso herói? Bingo! Este é o processo SynTPEnhService.exe.
E então todo o quebra-cabeça se desenvolve na minha cabeça. SynTPEnhService.exe faz parte do driver Synaptics touchpad. Ele foi instalado apenas em laptops de um determinado modelo em nosso escritório, no qual o problema ocorreu. Uma breve observação mostrou que a cada 5 segundos esse processo inicia o processo filho SynTPEnh.exe, que fecha após 1-2 segundos. Ao mesmo tempo, o processo pai continua mantendo o descritor do processo filho, o que leva ao vazamento de descritores. Um de cada vez a cada 5 segundos. São 17.280 descritores por dia. Deixe o computador ligado por uma semana e agora você tem mais de cem mil descritores de interrupção. Meu computador pessoal não foi reiniciado por mais de um mês - daí os PIDs de novos processos com números acima de meio milhão. Isso também explica por que o problema foi reproduzido em alguns laptops em nosso escritório, mas não ocorreu em outros laptops: alguns dos meus colegas reiniciaram seus PCs todos os dias e alguém como eu os deixava ligados durante a noite. .
A propósito, nesse local, lembrei que já havia lido sobre algum tipo de problema com os drivers do touchpad Synaptics. Depois de um pouco de pesquisa, encontrei
este artigo escrito por Bruce Dawson (muitas traduções de seus artigos foram publicadas em momentos diferentes em Habré, mas não neste específico). Lá, ele descreve o problema de vazamento de memória devido a esse reinício interminável do processo SynTPEnh.exe, mas não diz nada sobre o problema de vazamento de identificador, portanto, minha descoberta ainda é diferente.
Resolução de problemas
Então, o driver do touchpad "come" centenas de milhares de descritores - e daí? E o fato de que a função NtQuerySystemInformation (SystemHandleInformation, ...) gravada nos dias do Windows NT tinha (e tem) algum buffer interno bastante limitado. Não consegui encontrar uma indicação exata do tamanho em nenhum lugar, mas, obviamente, ele não foi projetado para um milhão de descritores. Como resultado, a função os retorna "o máximo que podem", o que significa que entre eles pode ou não ser o desejado.
O que fazer? Como Rick da série animada “Rick and Morty” disse: “Quando você inventa o teletransporte, imediatamente descobre uma coisa desagradável: você é a última pessoa no universo que o inventou”. Como se viu, a Microsoft percebeu esse problema com o buffer limitado em NtQuerySystemInformation ao chamá-lo com o argumento SystemHandleInformation já há 20 anos e, portanto, começando com o WindowsXP, eles adicionaram a função NtQuerySystemInformation outro argumento (e também não documentado) SystemExtendedHandleInformation. Quando você chama NtQuerySystemInformation (SystemExtendedHandleInformation, ...), todos os descritores do sistema serão retornados a você, independentemente de quantos houver. Bem, ou melhor, não sei ao certo, talvez haja algumas restrições para esse argumento, mas é certo que ele pode retornar 800.000 descritores em um estado.
Na rede, você pode encontrar exemplos de como usar SystemExtendedHandleInformation, por exemplo,
este . Em geral, tudo é semelhante lá, outras estruturas são simplesmente usadas, e isso é tudo.
Foi uma história instrutiva sobre o uso de argumentos não documentados do sistema operacional Widnows, que pode ser muito útil, mas requer testes cuidadosos e preparação para problemas fora do padrão.