Procedimientos recomendados de desarrollo de controladores de Surface Team

Introducción

Estas directrices de desarrollo de controladores fueron desarrolladas durante muchos años por los desarrolladores de controladores en Microsoft. Con el tiempo, cuando los conductores se comportaban mal y se habían aprendido las lecciones, esas lecciones se capturaron y evolucionaron para ser este conjunto de instrucciones. El equipo de hardware de Microsoft Surface usa estos procedimientos recomendados para desarrollar y mantener el código del controlador de dispositivo que admite las experiencias únicas de hardware de Surface.

Al igual que cualquier conjunto de directrices, habrá excepciones legítimas y enfoques alternativos que serán igualmente válidos. Considere la posibilidad de incorporar estas directrices a los estándares de desarrollo o usarlas para iniciar las directrices específicas del dominio para su entorno de desarrollo y sus requisitos únicos.

Errores comunes que cometen los desarrolladores de controladores

Control de E/S

  1. Acceso a los búferes recuperados de ICTLs sin validar la longitud. Consulte Error al comprobar el tamaño de los búferes.
  2. Realizar E/S de bloqueo en el contexto de un subproceso de usuario o contexto de subproceso aleatorio. Consulte Introducción a los objetos de distribuidor de kernel.
  3. Envío de E/S sincrónica a otro controlador sin tiempo de espera. Consulte Envío de solicitudes de E/S de forma sincrónica.
  4. Usar ioCTLs sin comprender las implicaciones de seguridad. Consulte Usar ni búfer ni E/S directa.
  5. No se comprueba el estado devuelto de WdfRequestForwardToIoQueue o no se controla correctamente y se produce un error en WDFREQUESTs abandonados.
  6. Mantener WDFREQUEST fuera de la cola en un estado no cancelable. Consulte Administración de colas de E/S, Finalización de solicitudes de E/S y Cancelación de solicitudes de E/S.
  7. Intentar administrar la cancelación mediante la función Mark/UnmarkCancelable en lugar de usar IoQueues. Consulte Objetos de cola de marco.
  8. No se sabe la diferencia entre las operaciones Limpieza y Cierre del identificador de archivo. Consulte Errores en el control de las operaciones de limpieza y cierre.
  9. Con vistas a posibles recursiones con finalización de E/S y reenvío desde la rutina de finalización.
  10. No ser explícito sobre los atributos de administración de energía de WDFQUEUEs. No documentar claramente la elección de administración de energía. Esta es la causa principal de la comprobación de errores 0x9F: DRIVER_POWER_STATE_FAILURE en controladores WDF. Cuando se quita el dispositivo, el marco purga la E/S de la cola administrada por energía y la cola no administrada por energía en distintas fases del proceso de eliminación. Las colas no administradas por energía se purgan cuando se recibe el IRP_MN_REMOVE_DEVICE final. Por lo tanto, si mantiene la E/S en una cola no administrada por energía, es recomendable purgar explícitamente la E/S en el contexto de EvtDeviceSelfManagedIoFlush para evitar interbloqueos.
  11. No sigue las reglas de control de IRP. Consulte Errores en el control de las operaciones de limpieza y cierre.

Synchronization

  1. Mantener bloqueados para el código que no necesita protección. No contenga un bloqueo para toda una función cuando solo es necesario proteger un pequeño número de operaciones.
  2. Llamando a los conductores con bloqueos mantenidos. Esta es la principal causa de interbloqueos.
  3. Usar primitivos interbloqueados para crear un esquema de bloqueo en lugar de usar primitivos de bloqueo adecuados proporcionados por el sistema, como exclusión mutua, semáforo y interbloqueos. Vea Introduction to Mutex Objects, Semaphore Objects and Introduction to Spin Locks.
  4. Usar un bloqueo por subproceso en el que algún tipo de bloqueo pasivo sería más adecuado. Vea Mutexes rápidos y exclusión mutua protegida y objetos de eventos. Para obtener una perspectiva adicional sobre los bloqueos, revise el artículo de OSR: el estado de sincronización.
  5. Participar en el modelo de nivel de ejecución y sincronización de WDF sin tener en cuenta las implicaciones. Consulte Uso de bloqueos de marco. A menos que el controlador sea monolítico de nivel superior que interactúe directamente con el hardware, evite optar por la sincronización de WDF, ya que puede provocar interbloqueos debido a la recursividad.
  6. Adquirir KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex en el contexto de varios subprocesos sin entrar en la región crítica. Esto puede provocar un ataque dos porque se puede suspender un subproceso que contiene uno de estos bloqueos. Consulte Introducción a los objetos de distribuidor de kernel.
  7. Asignar KEVENT en la pila de subprocesos y volver al autor de la llamada mientras el EVENTO todavía está en uso. Normalmente, cuando se usa con IoBuildSyncronousFsdRequest o IoBuildDeviceIoControlRequest. El autor de llamada de estas llamadas debe asegurarse de que no se desenredan de la pila hasta que el administrador de E/S haya señalado el evento cuando se complete el IRP.
  8. Esperar indefinidamente en rutinas de envío. En general, cualquier tipo de espera en la rutina de envío es una mala práctica.
  9. Compruebe de forma inapropiada la validez de un objeto (si blah == NULL) antes de eliminarlo. Normalmente, esto significa que el autor no tiene una comprensión completa del código que controla la duración del objeto.

Administración de objetos

  1. No se han primariodo explícitamente objetos WDF. Consulte Introducción a los objetos framework.
  2. El objeto WDF primario en WDFDRIVER en lugar de la creación de elementos primarios en un objeto que proporciona una mejor administración de la duración y optimiza el uso de memoria. Por ejemplo, el elemento primario WDFREQUEST en un WDFDEVICE en lugar de IOTARGET. Consulte Uso de objetos de marco de trabajo generales, ciclo de vida de objetos de marco de trabajo y resumen de objetos de marco.
  3. No se realiza la protección de la desaprotección de los recursos de memoria compartidos a los que se accede entre los controladores. Consulte la función ExInitializeRundownProtection.
  4. Poner en cola erróneamente el mismo elemento de trabajo mientras que el anterior ya está en la cola o ya está en ejecución. Esto puede ser un problema si el cliente supone que cada elemento de trabajo en cola se va a ejecutar. Consulte Uso de WorkItems de framework. Para obtener más información sobre la puesta en cola de WorkItems, consulte el módulo DMF_QueuedWorkitem en el proyecto https://github.com/Microsoft/DMFDriver Module Framework (DMF): .
  5. Temporizador de puesta en cola antes de publicar el mensaje que se espera que procese el temporizador. Consulte Uso de temporizadores.
  6. Realizar una operación en un objeto workitem que pueda bloquear o tardar indefinidamente mucho tiempo en completarse.
  7. Diseñar una solución que da como resultado una inundación de elementos de trabajo que se van a poner en cola. Puede provocar un sistema que no responde o un ataque DOS si el tipo malo puede controlar la acción (por ejemplo, la bomba de E/S en un controlador que pone en cola un nuevo elemento de trabajo para cada E/S). Consulte Uso de elementos de trabajo de framework.
  8. No es necesario que las devoluciones de llamada DPC del elemento de trabajo se hayan ejecutado hasta la finalización antes de eliminar el objeto. Consulte Directrices para escribir rutinas DPC y la función WdfDpcCancel.
  9. Crear subprocesos en lugar de usar elementos de trabajo durante una duración corta o tareas que no sondean. Consulte Subprocesos de trabajo del sistema.
  10. No garantiza que los subprocesos se hayan ejecutado hasta su finalización antes de eliminar o descargar el controlador. Para obtener más información sobre la sincronización de la ejecución de subprocesos, consulte el código asociado con el código asociado con DMF_Thread módulo en el proyecto https://github.com/Microsoft/DMFDriver Module Framework (DMF): .
  11. Usar un único controlador para administrar dispositivos que son diferentes, pero interdependientes, y usar variables globales para compartir información.

Memoria

  1. No marque el código de ejecución pasiva como PAGEABLE, siempre que sea posible. El código del controlador de paginación puede reducir el tamaño de la superficie de código del controlador, lo que libera espacio del sistema para otros usos. Tenga cuidado al marcar el código paginable que genera IRQL >= DISPATCH_LEVEL o podría llamarse a en IRQL generado. Vea Cuándo se deben paginar los datos y codificar y hacer que los controladores se puedan paginar y detectar código que puede ser paginable.
  2. Declarar estructuras grandes en la pila, debe usar el montón o poolinstead. Consulte Uso de KernelStack y asignación de memoria del espacio del sistema.
  3. Contexto de objeto WDF innecesariamente cero innecesariamente. Esto puede indicar una falta de claridad sobre cuándo se eliminará automáticamente la memoria.

Directrices generales para controladores

  1. Mezcla de primitivos WDM y WDF. Uso de primitivos WDM donde se pueden usar primitivos de WDF. El uso de primitivos de WDF le protege de gotchas, mejora la depuración y, lo que es más importante, hace que el controlador sea portátil para el modo de usuario.
  2. Asignación de nombres de dominio completo y creación de vínculos simbólicos cuando no sea necesario. Consulte Administración del control de acceso del controlador.
  3. Copie el pegado y el uso de GUID y otros valores constantes de controladores de ejemplo.
  4. Tenga en cuenta el uso de driver Module Framework (DMF) código abierto código en el proyecto de controlador. DMF es una extensión de WDF que permite una funcionalidad adicional para un desarrollador de controladores WDF. Consulte Introducción a Driver Module Framework.
  5. Usar el Registro como un mecanismo de notificación entre procesos o como buzón de correo. Para obtener una alternativa, consulte DMF_NotifyUserWithEvent y módulos de DMF_NotifyUserWithRequest disponibles en el proyecto DMF: https://github.com/Microsoft/DMF.
  6. Suponiendo que todas las partes del registro estarán disponibles para el acceso durante la fase de arranque inicial del sistema.
  7. Tomar dependencia en el orden de carga de otro controlador o servicio. Dado que el orden de carga se puede cambiar fuera del control del controlador, esto puede dar lugar a un controlador que funciona inicialmente, pero posteriormente se produce un error en un patrón imprevisible.
  8. Volver a crear bibliotecas de controladores que ya están disponibles, como WDF, proporciona para PnP que se describe en Compatibilidad con PnP y administración de energía en el controlador o las proporcionadas en la interfaz de bus, tal como se describe en el artículo de OSR Using Bus Interfaces for Driver to Driver Communication.

PnP/Power

  1. Interacción con otro controlador de una manera no compatible con pnp: no se registra para las notificaciones de cambio de dispositivo pnp. Consulte Registro para notificación de cambio de interfaz de dispositivo.
  2. Crear nodos ACPI para enumerar dispositivos y crear dependencias de energía entre ellos en lugar de usar interfaces de creación de dispositivos de software proporcionados por el controlador de bus o sistema a PNP y dependencias de energía de una manera elegante. Consulte Compatibilidad con PnP y administración de energía en controladores de funciones.
  3. Marcar el dispositivo no deshabilitable: forzar un reinicio en la actualización del controlador.
  4. Ocultar el dispositivo en el administrador de dispositivos. Consulte Ocultar dispositivos de Administrador de dispositivos.
  5. Suponiendo que el controlador se usará solo para una instancia del dispositivo.
  6. Realizar suposiciones de que el controlador nunca se descargará. Consulte Rutina de descarga del controlador PnP.
  7. No se controla la notificación de llegada de la interfaz falsa. Esto puede ocurrir y se espera que los controladores controle esta condición de forma segura.
  8. No se implementa una directiva de energía inactiva de S0, que es importante para los dispositivos que son restricciones DRIPS o elementos secundarios. Consulte Compatibilidad con el apagado inactivo.
  9. Si no se comprueba el estado de retorno de WdfDeviceStopIdle , se produce una fuga de referencia de energía debido al desequilibrio WdfDeviceStopIdle/ResumeIdle y, finalmente, la comprobación de errores 9F.
  10. No se sabe que se puede llamar a PrepareHardware/ReleaseHardware más de una vez debido al reequilibrio de recursos. Estas devoluciones de llamada deben restringirse a la inicialización de recursos de hardware. Consulte EVT_WDF_DEVICE_PREPARE_HARDWARE.
  11. Uso de PrepareHardware/ReleaseHardware para asignar recursos de software. La asignación de recursos de software estática al dispositivo debe realizarse en AddDevice o en SelfManagedIoInit si la asignación de recursos necesarios para interactuar con el hardware. Consulte EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT.

Instrucciones de codificación

  1. No se usan funciones de cadena segura e enteros. Consulte Uso de funciones de cadena de Caja fuerte y Uso de funciones de entero de Caja fuerte.
  2. No se usan definiciones de tipo para definir constantes.
  3. Uso de variables globales y estáticas. Evite almacenar por contexto de dispositivo en los globales. Los globales están diseñados para compartir información en varias instancias de dispositivos. Como alternativa, considere la posibilidad de usar el contexto del objeto WDFDRIVER para compartir información en varias instancias de dispositivos.
  4. No se usan nombres descriptivos para variables.
  5. No ser coherente en las variables de nomenclatura: coherencia entre mayúsculas y minúsculas. No sigue el estilo de codificación existente al realizar actualizaciones en el código existente. Por ejemplo, usar nombres de variable diferentes para estructuras comunes en distintas funciones.
  6. No comentar opciones de diseño importantes: administración de energía, bloqueos, administración de estado, uso de elementos de trabajo, DPCs, temporizadores, uso de recursos globales, asignación previa de recursos, expresiones complejas o instrucciones condicionales.
  7. Comentarios sobre cosas que son obvias del nombre de la API a la que se llama. Convertir el comentario en el idioma inglés equivalente al nombre de la función (por ejemplo, escribir el comentario "Crear el objeto de dispositivo" al llamar a WdfDeviceCreate).
  8. No cree macros que tengan una llamada de devolución. Consulte Funciones (C++).
  9. Anotaciones de código fuente (SAL) no o incompletas. Consulte Anotaciones de SAL 2.0 para controladores de Windows.
  10. Uso de macros en lugar de funciones insertadas.
  11. Uso de macros para constantes en lugar de constexpr al usar C++
  12. Compilar el controlador con el compilador de C, en lugar del compilador de C++ para asegurarse de obtener una comprobación de tipos segura.

Tratamiento de errores

  1. No notificar errores críticos del controlador y marcar correctamente el dispositivo no funcional.
  2. No se devuelve el estado de error NT adecuado que se traduce en un estado de error WIN32 significativo. Consulte Uso de valores NTSTATUS.
  3. No se usan macros NTSTATUS para comprobar el estado devuelto de las funciones del sistema.
  4. No aserción en variables de estado o marcas cuando sea necesario.
  5. Comprobar si el puntero es válido antes de acceder a él para solucionar las condiciones de carrera.
  6. ASERCIÓN en punteros NULL. Si intenta usar un puntero NULL para acceder a la memoria Windows, se producirá un error en la comprobación. Los parámetros de la comprobación de errores proporcionarán la información necesaria para corregir el puntero nulo. Horas extra, cuando se agregan muchas instrucciones ASSERT innecesarias al código, consumen memoria y ralentizan el sistema.
  7. ASSERTING en el puntero de contexto de objeto. El marco de trabajo del controlador garantiza que el objeto siempre se asignará con el contexto.

Traza

  1. No definir tipos personalizados de WPP y usarlos en llamadas de seguimiento para obtener mensajes de seguimiento legibles para humanos. Consulta Agregar seguimiento de software de WPP a un controlador de Windows.
  2. No se usa el seguimiento de IFR. Consulte Uso de la grabadora de seguimiento de luz (IFR) en KMDF y controladores UMDF 2.
  3. Llamar a nombres de función en llamadas de seguimiento de WPP. WPP ya realiza un seguimiento de los nombres de función y los números de línea.
  4. No se usan eventos ETW para medir el rendimiento y otras experiencias críticas del usuario que afectan a los eventos. Consulte Agregar seguimiento de eventos a controladores en modo kernel.
  5. No notificar errores críticos en el registro de eventos y marcar correctamente el dispositivo no funcional.

Comprobación

  1. No se ejecuta el comprobador de controladores con la configuración estándar y avanzada durante el desarrollo y las pruebas. Consulte Comprobador de controladores. En la configuración avanzada, se recomienda habilitar todas las reglas, excepto las reglas relacionadas con la simulación de recursos bajos. Es preferible ejecutar las pruebas de simulación de recursos bajos de forma aislada para facilitar la depuración de problemas.
  2. No se ejecuta la prueba de DevFund en el controlador o en la clase de dispositivo de la que forma parte la configuración avanzada del comprobador habilitada. Consulte Ejecución de pruebas de DevFund a través de la línea de comandos.
  3. No se comprueba que el controlador es compatible con HVCI. Consulte Implementación del código de compatibilidad de HVCI.
  4. No se ejecuta AppVerifier en WUDFhost.exe durante el desarrollo y las pruebas de controladores de modo de usuario. Consulte Comprobador de aplicaciones.
  5. No se comprueba el uso de memoria mediante la extensión del depurador de !wdfpoolusage en tiempo de ejecución para asegurarse de que los objetos WDF no se abandonan. La memoria, las solicitudes y los elementos de trabajo son víctimas comunes de estos problemas.
  6. No se usa la extensión del depurador !wdfkd para inspeccionar el árbol de objetos para asegurarse de que los objetos están primarios correctamente y de comprobar los atributos de los objetos principales, como WDFDRIVER, WDFDEVICE, E/S.