Procedimientos recomendados de desarrollo de controladores de Surface Team

Introducción

Estas directrices de desarrollo de controladores se desarrollaron a lo largo de muchos años por parte de los desarrolladores de controladores en Microsoft. Con el tiempo, cuando los conductores se comportaban mal y se aprendían 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 cometidos por los desarrolladores de controladores

Control de E/S

  1. Acceso a los búferes recuperados de ITL 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. Vea 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. Uso de io IOCTLs sin comprender las implicaciones de seguridad. Consulte Uso de E/S directa ni almacenado en búfer.
  5. No comprueba el estado devuelto de WdfRequestForwardToIoQueue o no controla correctamente el error y da lugar a 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 framework.
  8. No conocer la diferencia entre las operaciones Limpieza y Cierre del identificador de archivo. Consulte Errores en Control de 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 los controladores WDF. Cuando se quita el dispositivo, la E/S del marco purga la E/S de la cola administrada por energía y la cola administrada sin 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 administrada sin energía, se recomienda purgar explícitamente la E/S en el contexto de EvtDeviceSelfManagedIoFlush para evitar interbloqueo.
  11. No sigue las reglas de control de IRP. Consulte Errores en Control de operaciones de limpieza y cierre.

Synchronization

  1. Mantener bloqueados para el código que no necesita protección. No mantenga 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. El uso de primitivos interbloqueados para crear un esquema de bloqueo en lugar de usar los primitivos de bloqueo adecuados proporcionados por el sistema, como la exclusión mutua, el semáforo y los 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. Consulte Exclusión mutua rápida 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 la sincronización.
  5. Participar en el modelo de nivel de ejecución y sincronización de WDF sin comprender completamente 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. Vea 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 la llamada de estas llamadas debe asegurarse de que no se desenreden de la pila hasta que el administrador de E/S haya señalado el evento cuando se complete el IRP.
  8. Espera indefinidamente en rutinas de envío. En general, cualquier tipo de espera en la rutina de envío es una mala práctica.
  9. Comprobar incorrectamente la validez de un objeto (si blah == NULL) antes de eliminarlo. Normalmente, esto significa que el autor no tiene conocimientos completos del código que controla la duración del objeto.

Administración de objetos

  1. No es explícitamente el elemento primario de objetos WDF. Consulte Introducción a los objetos framework.
  2. El objeto WDF primario a WDFDRIVER en lugar de a un objeto que proporciona una mejor administración de la duración y optimiza el uso de memoria. Por ejemplo, el elemento primario WDFREQUEST a un WDFDEVICE en lugar de IOTARGET. Consulte Uso de objetos de marco general, Ciclo de vida de objetos de marco de trabajo y Resumen de objetos framework.
  3. No se está ejecutando la protecció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 realiza una suposición de que todos los elementos de trabajo en cola se van a ejecutar. Consulte Using Framework WorkItems (Uso de Framework WorkItems). Para obtener más información sobre la puesta en cola de WorkItems, vea el módulo DMF_QueuedWorkitem en el proyecto https://github.com/Microsoft/DMFMarco de módulo de controladores (DMF): .
  5. Temporizador de puesta en cola antes de publicar el mensaje que se espera que el temporizador procese. Consulte Uso de temporizadores.
  6. Realizar una operación en un objeto workitem que puede 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 no responde o un ataque DOS si el tipo malo puede controlar la acción (por ejemplo, bombear E/S en un controlador que pone en cola un nuevo elemento de trabajo para cada E/S). Consulte Uso de elementos de trabajo del marco de trabajo.
  8. No es necesario que las devoluciones de llamada DPC del elemento de trabajo se ejecuten hasta su 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 para tareas de corta duración o no sondeo. Consulte Subprocesos de trabajo del sistema.
  10. No se 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, examine 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 que usan 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 se podría llamar a en IRQL elevado. Vea Cuándo debe ser paginable el código y los datos 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 System-Space memoria.
  3. Contexto de objeto WDF innecesariamente sin cero. Esto puede indicar una falta de claridad sobre cuándo se eliminará la memoria automáticamente.

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. Asigne nombres de DPO y cree 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. Considere el uso del código del marco de módulo de controladores (DMF) código abierto 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. Uso del Registro como mecanismo de notificación entre procesos o como buzón de correo. Para obtener una alternativa, consulte DMF_NotifyUserWithEvent y DMF_NotifyUserWithRequest módulos 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 temprana del sistema.
  7. Tomar dependencias 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 más adelante 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 en 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 (Usar interfaces de bus para controlador a controlador).

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 función.
  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. Suponiendo que el controlador nunca se descargará. Consulte La 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 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 de WdfDeviceStopIdle/ResumeIdle y, finalmente, a la comprobación de errores 9F.
  10. No saber 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 seguras de cadena e entero. Consulte Uso de funciones de cadena segura y Uso de funciones de enteros seguros.
  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 de objetos 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 existente de codificación al realizar actualizaciones en el código existente. Por ejemplo, el uso de nombres de variable diferentes para estructuras comunes en distintas funciones.
  6. Sin comentar las 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 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 sólida.

Tratamiento de errores

  1. No notificar errores críticos del controlador y marcar correctamente el dispositivo no funcional.
  2. No 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. Compruebe si el puntero es válido antes de acceder a él para solucionar las condiciones de carrera.
  6. ASSERTING en punteros NULL. Si intenta usar un puntero NULL para acceder a la memoria, Windows comprobará los errores. Los parámetros de la comprobación de errores proporcionarán la información necesaria para corregir el puntero nulo. Con 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 del objeto. El marco de trabajo del controlador garantiza que el objeto siempre se asignará con contexto.

Seguimiento

  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 la luz (IFR) en los controladores KMDF y 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 otra experiencia crítica del usuario que afecta a los eventos. Consulte Agregar seguimiento de eventos a controladores de Kernel-Mode.
  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 la clase de dispositivo de la que forma parte el controlador con la configuración avanzada del comprobador habilitada. Consulte Cómo ejecutar las 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 !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.