La mañana de ese día comenzó con el hecho de que "si" se rompió. Esta expresión fue una vez acuñada por uno de mis colegas que demostró cómo su depurador entra en el bloque if al recorrer el código, mientras que la condición de que if se verificó era exactamente falsa. El problema en ese momento resultó ser trivial: utilizó una versión optimizada de lanzamiento y, en este escenario, la confianza en la depuración paso a paso, por supuesto, es imposible. Pero la misma expresión "si se rompió" se ha arraigado y se ha utilizado aquí desde entonces para indicar una situación en la que algo tan fundamental dejó de funcionar que apenas se creía en él.
Entonces, ese día, la función
NtQuerySystemInformation se descompuso, una de las funciones más importantes del sistema operativo Windows, que devuelve información sobre procesos, hilos, descriptores del sistema, etc. Sobre los beneficios de usar esta función, una vez escribí
este artículo . Pero resultó que incluso esas piedras angulares de un sistema a veces pueden fallar.
Entonces que paso.
Durante un tiempo bastante largo (durante varios años), utilizamos la llamada a la función NtQuerySystemInformation con el argumento SystemHandleInformation para obtener información sobre todos los descriptores en el sistema. Sí, este argumento se refiere formalmente a los indocumentados, pero si comienza a buscar información sobre cómo enumerar todos los descriptores en todas las aplicaciones de Windows que se ejecutan actualmente, la combinación de NtQuerySystemInformation + SystemHandleInformation será la opción propuesta con más frecuencia. Y realmente funciona, en todos los sistemas operativos que comienzan con Windows NT.
¿Por qué podría necesitar buscar descriptores en todos los procesos? Bueno, por varias razones. Las utilidades como
Process Hacker solo las muestran con fines informativos. Hay programas que hacen esto para buscar un recurso que actualmente está bloqueado por alguien (por ejemplo, un archivo). Y puede, por ejemplo, encontrar en un proceso externo un mutex que se utiliza para permitir que solo se ejecute una copia del programa, cerrarlo y permitir que se inicien dos instancias de dicha aplicación. O enumere los descriptores para duplicarlos para organizar el entorno limitado. En general, hay muchas tareas.
No daré la descripción completa de la enumeración del descriptor aquí, solo diré que, en general, fue similar a ejemplos comunes, como
este :
while ((status = NtQuerySystemInformation( SystemHandleInformation, handleInfo, handleInfoSize, NULL )) == STATUS_INFO_LENGTH_MISMATCH) handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
Pero luego inicio nuestra aplicación, y de repente resulta que el descriptor que necesito (¡y estoy seguro de que existe!) No está en la lista devuelta por la función NtQuerySystemInformation (). Eso es todo, vinieron: "si está roto".
Intentando reproducir el problema en otras computadoras en la oficina. En algunos se reproduce, en la mayoría, no. Estamos tratando de entender cómo aquellos en los que se reproduce difieren de aquellos en los que todo es bueno. La versión de Windows es la misma en todas partes, las actualizaciones, la construcción de nuestro programa: todo es idéntico. De repente, alguien se da cuenta de que todas las computadoras portátiles en las que se reprodujo el problema son del mismo modelo. Incompatibilidad de hardware? Pero por qué de repente ahora, solía funcionar ... Además, hay otras computadoras portátiles del mismo modelo en la oficina que funcionan ahora. Incluso se compararon las versiones de los controladores de dispositivos: todo parece ser el mismo. Pero todo funciona en algunas computadoras portátiles, pero no en otras.
Tirar del cabello en la cabeza duró aproximadamente medio día, hasta que accidentalmente noté dos cosas:
- Los PID de proceso, que generalmente son números de tres, cuatro o cinco dígitos en mi computadora por alguna razón, se han convertido en seis dígitos. Era bastante extraño ver un PID del tipo 780936. No los había notado antes. Además, el número total de procesos en ejecución fue bastante adecuado (hasta cien).
- El administrador de tareas en la pestaña CPU mostró el número total de descriptores en el sistema, y fue enorme, más de 800,000.
Para una aplicación normal, es normal abrir cien o dos descriptores. Pues mil. Con el uso activo, Chrome puede abrir aproximadamente 2000, Visual Studio puede abrir 3000 en proyectos grandes, pero ¿quién abrió 800 000? Afortunadamente, el Process Hacker mencionado anteriormente le permite mostrar la cantidad de descriptores para cada proceso e incluso ordenar la lista de procesos por la cantidad de descriptores utilizados.
Y que vemos Y vemos algo como esta imagen:

Debo decir que acabo de hacer la captura de pantalla anterior, por lo que el primero en la lista de procesos tiene "solo" unos 20,000 descriptores. Y luego, cuando vi el problema por primera vez, había unos 650,000 allí. ¿Y quién es nuestro héroe? Bingo! Este es el proceso SynTPEnhService.exe.
Y luego todo el rompecabezas se desarrolla en mi cabeza. SynTPEnhService.exe es parte del controlador del panel táctil Synaptics. Se instaló solo en computadoras portátiles de cierto modelo en nuestra oficina, en las que ocurrió el problema. Una breve observación mostró que cada 5 segundos este proceso inicia el proceso secundario SynTPEnh.exe, que se cierra después de 1-2 segundos. Al mismo tiempo, el proceso padre continúa manteniendo el descriptor del proceso hijo, lo que conduce a la fuga de descriptores. Uno a la vez cada 5 segundos. Esto es 17.280 descriptores por día. Deje la computadora encendida durante una semana y ahora tiene más de cien mil descriptores de bloqueo. Mi computadora personal no se reinició durante más de un mes, de ahí los PID de los nuevos procesos con números superiores a medio millón. Esto también explica por qué el problema se reprodujo en algunas computadoras portátiles en nuestra oficina, pero no ocurrió en otras computadoras portátiles: algunos de mis colegas reiniciaron sus computadoras todos los días, y alguien, como yo, las dejó encendidas durante la noche. .
Por cierto, en este lugar recordé que ya había leído sobre algún tipo de problema con los controladores del panel táctil Synaptics. Después de un poco de investigación, encontré
este artículo escrito por Bruce Dawson (muchas traducciones de sus artículos fueron publicadas en diferentes momentos en Habré, pero no esta específica). Allí describe el problema de pérdida de memoria debido a este reinicio sin fin del proceso SynTPEnh.exe, pero no dice nada sobre el problema de pérdida de identificador, por lo que mi hallazgo sigue siendo diferente.
Resolución de problemas
Entonces, el controlador del panel táctil "come" cientos de miles de descriptores, ¿y qué? Y el hecho de que la función NtQuerySystemInformation (SystemHandleInformation, ...) escrita en los días de Windows NT tenía (y tiene) un búfer interno bastante limitado. No pude encontrar una indicación exacta de su tamaño en ningún lado, pero, obviamente, no fue diseñado para un millón de descriptores. Como resultado, la función los devuelve "tanto como pueden", lo que significa que entre ellos puede o no ser el deseado.
Que hacer Como Rick de la serie animada "Rick and Morty" dijo: "Cuando inventas la teletransportación, inmediatamente descubres algo desagradable: eres la última persona en el universo que lo inventó". Al final resultó que, Microsoft se dio cuenta de este problema con el búfer limitado en NtQuerySystemInformation cuando lo llamó con el argumento SystemHandleInformation hace 20 años, y por lo tanto, a partir de WindowsXP, agregaron la función NtQuerySystemInformation otro argumento (y también indocumentado) SystemExtendedHandleInformation. Cuando llame a NtQuerySystemInformation (SystemExtendedHandleInformation, ...), se le devolverán todos los descriptores del sistema, sin importar cuántos haya. Bueno, o más bien, no lo sé con certeza, tal vez hay algunas restricciones para este argumento, pero es seguro que puede devolver 800,000 descriptores en un estado.
En la red puede encontrar ejemplos del uso de SystemExtendedHandleInformation, por ejemplo,
este . En general, todo es similar allí, simplemente se usan otras estructuras, y eso es todo.
Fue una historia instructiva sobre el uso de argumentos no documentados del sistema operativo Widnows, que puede ser muy útil, pero requiere pruebas cuidadosas y preparación para problemas no estándar.