Surface 팀 드라이버 개발 모범 사례

소개

이러한 드라이버 개발 지침은 Microsoft의 드라이버 개발자가 수년 동안 개발했습니다. 운전자가 잘못 동작하고 교훈을 배웠을 때 시간이 지남에 따라 이러한 교훈을 포착하고 이 지침 집합으로 발전시켰습니다. 이러한 모범 사례는 Microsoft Surface 하드웨어 팀에서 고유한 Surface 하드웨어 환경을 지원하는 디바이스 드라이버 코드를 개발하고 기본 데 사용됩니다.

모든 지침 집합과 마찬가지로 합법적인 예외 및 동일하게 유효한 대체 방법이 있습니다. 개발 표준에 이러한 지침을 통합하거나 이를 사용하여 개발 환경 및 고유한 요구 사항에 대한 특정 지침기본 작업을 시작하는 것이 좋습니다.

드라이버 개발자의 일반적인 실수

I/O 처리

  1. 길이를 확인하지 않고 IOCTL에서 검색된 버퍼에 액세스합니다. 버퍼 크기를 확인하지 못했습니다.
  2. 사용자 스레드 또는 임의 스레드 컨텍스트의 컨텍스트에서 차단 I/O를 수행합니다. 커널 디스패처 개체 소개를 참조하세요.
  3. 시간 제한 없이 동기 I/O를 다른 드라이버에 보냅니다. 동기적으로 I/O 요청 보내기를 참조 하세요.
  4. 보안에 미치는 영향을 이해하지 않고 둘 다 io IOCTL을 사용합니다. 버퍼링 또는 직접 I/O 사용을 참조하세요.
  5. WdfRequestForwardToIoQueue반환 상태 검사 않거나 오류를 올바르게 처리하지 않아 WDFREQUEST가 중단되었습니다.
  6. WDFREQUEST를 취소할 수 없는 상태로 큐 외부에 유지합니다. I/O 큐 관리, I/O 요청 완료 및 I/O 요청 취소를 참조하세요.
  7. IoQueues를 사용하는 대신 Mark/UnmarkCancelable 함수를 사용하여 취소를 관리하려고 합니다. 프레임워크 큐 개체를 참조 하세요.
  8. 파일 핸들 정리와 닫기 작업의 차이점을 알 수 없습니다. 정리 및 닫기 작업 처리의 오류를 참조 하세요.
  9. I/O 완료 및 완료 루틴에서 다시 제출을 통해 잠재적 재귀를 간과합니다.
  10. WDFQUEUE의 전원 관리 특성에 대해 명시적이지 않습니다. 전원 관리 선택을 명확하게 문서화하지 않습니다. 이것이 버그 확인 0x9F 주요 원인 입니다. WDF 드라이버의 DRIVER_POWER_STATE_FAILURE . 디바이스가 제거되면 프레임워크는 다른 제거 프로세스 단계에서 전원 관리 큐 및 비전력 관리 큐에서 IO를 제거합니다. 비전력 관리 큐는 최종 IRP_MN_REMOVE_DEVICE 수신될 때 제거됩니다. 따라서 비전력 관리 큐에서 I/O를 보유하는 경우 교착 상태를 방지하기 위해 EvtDeviceSelfManagedIoFlush컨텍스트에서 I/O를 명시적으로 제거하는 것이 좋습니다.
  11. IRP 처리 규칙을 따르지 않습니다. 정리 및 닫기 작업 처리의 오류를 참조 하세요.

동기화

  1. 보호가 필요하지 않은 코드에 대한 잠금을 유지합니다. 적은 수의 작업만 보호해야 하는 경우 전체 함수에 대한 잠금을 유지하지 마세요.
  2. 자물쇠가 고정 된 드라이버에서 호출. 이것이 교착 상태의 주요 원인입니다.
  3. 뮤텍스, 세마포 및 스핀 잠금과 같은 적절한 시스템 제공 잠금 기본 형식을 사용하는 대신 인터로킹 기본 형식을 사용하여 잠금 체계를 만듭니다. 뮤텍스 개체 소개, 세마포 개체스핀 잠금 소개를 참조하세요.
  4. 일부 유형의 수동 잠금이 더 적합한 스핀 잠금을 사용합니다. 빠른 뮤텍스 및 보호된 뮤텍스 및 이벤트 개체를 참조하세요. 잠금에 대한 추가 관점은 OSR 문서 - 동기화 상태를 검토하세요.
  5. 의미를 완전히 이해하지 않고 WDF 동기화 및 실행 수준 모델에 옵트인합니다. 프레임워크 잠금 사용을 참조하세요. 드라이버가 하드웨어와 직접 상호 작용하는 모놀리식 최상위 드라이버가 아니면 재귀로 인해 교착 상태가 발생할 수 있으므로 WDF 동기화를 옵트인하지 마십시오.
  6. 중요한 영역을 입력하지 않고 여러 스레드의 컨텍스트에서 KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex를 획득합니다. 이 작업을 수행하면 이러한 잠금 중 하나를 보유하는 스레드가 일시 중단될 수 있으므로 DOS 공격이 발생할 수 있습니다. 커널 디스패처 개체 소개를 참조하세요.
  7. 스레드 스택에 KEVENT를 할당하고 EVENT가 계속 사용 중인 동안 호출자에게 반환합니다. 일반적으로 IoBuildSyncronousFsdRequest 또는 IoBuildDeviceIoControlRequest와 함께 사용할 때 수행됩니다. 이러한 호출의 호출자는 I/O 관리자가 IRP가 완료될 때 이벤트를 알릴 때까지 스택에서 해제되지 않도록 해야 합니다.
  8. 디스패치 루틴에서 무기한 대기. 일반적으로 디스패치 루틴에서 모든 종류의 대기는 나쁜 관행입니다.
  9. 개체를 삭제하기 전에 개체의 유효성을 부적절하게 검사(nULL이면). 이는 일반적으로 작성자가 개체의 수명을 제어하는 코드를 완전히 이해하지 못했음을 의미합니다.

개체 관리

  1. WDF 개체를 명시적으로 부모로 지정하지 않습니다. 프레임워크 개체 소개를 참조하세요.
  2. WDF 개체를 더 나은 수명 관리를 제공하고 메모리 사용량을 최적화하는 개체로 양육하는 대신 WDFDRIVER로 육아합니다. 예를 들어 WDFREQUEST를 IOTARGET 대신 WDFDEVICE로 육아합니다. 일반 프레임워크 개체, 프레임워크 개체 수명 주기프레임워크 개체 요약 사용을 참조하세요.
  3. 드라이버에서 액세스하는 공유 메모리 리소스의 런다운 보호를 수행하지 않습니다. ExInitializeRundownProtection 함수를 참조하세요.
  4. 이전 항목이 이미 큐에 있거나 이미 실행 중인 동안 동일한 작업 항목을 실수로 큐에 대기합니다. 클라이언트가 대기 중인 모든 작업 항목이 실행될 것이라고 가정하는 경우 문제가 될 수 있습니다. Framework WorkItems 사용을 참조 하세요. WorkItems 큐에 대한 자세한 내용은 DMF(드라이버 모듈 프레임워크) 프로젝트의 DMF_QueuedWorkitem 모듈을 https://github.com/Microsoft/DMF참조하세요.
  5. 타이머가 처리해야 하는 메시지를 게시하기 전에 타이머를 큐에 대기합니다. 타이머 사용을 참조하세요.
  6. 완료하는 데 무기한 시간이 걸리거나 차단할 수 있는 작업 영역에서 작업을 수행합니다.
  7. 대기 중인 작업 항목의 홍수를 초래하는 솔루션을 디자인합니다. 악의적인 사용자가 작업을 제어할 수 있는 경우 응답하지 않는 시스템 또는 DOS 공격으로 이어질 수 있습니다(예: 모든 I/O에 대해 새 작업 항목을 큐에 대기하는 드라이버에 I/O를 펌핑). 프레임워크 작업 항목 사용을 참조 하세요.
  8. 개체를 삭제하기 전에 작업 항목 DPC 콜백이 완료될 때 계속 실행되지 않습니다. DPC 루틴WdfDpcCancel 함수 작성 지침을 참조하세요.
  9. 짧은 기간/비 폴링 작업에 작업 항목을 사용하는 대신 스레드를 만듭니다. 시스템 작업자 스레드를 참조 하세요.
  10. 드라이버를 삭제하거나 언로드하기 전에 스레드가 완료될 수 있도록 보장하지 않습니다. 스레드 런다운 동기화에 대한 자세한 내용은 DMF(드라이버 모듈 프레임워크) 프로젝트의 https://github.com/Microsoft/DMFDMF_Thread 모듈과 연결된 코드를 확인하세요.
  11. 단일 드라이버를 사용하여 서로 다르지만 상호 종속된 디바이스를 관리하고 전역 변수를 사용하여 정보를 공유합니다.

메모리

  1. 가능한 경우 패시브 실행 코드를 PAGEABLE로 표시하지 않습니다. 드라이버 코드 페이징은 드라이버의 코드 공간 크기를 줄여 다른 용도로 시스템 공간을 확보할 수 있습니다. IRQL = DISPATCH_LEVEL 발생하거나 발생된 IRQL >에서 호출할 수 있는 페이즐 가능 코드를 신중하게 표시해야 합니다. 코드 및 데이터를 페이징할 수 있어야 하는 경우와 드라이버를 페이징 가능 으로 만들고 페이징할 수 있는 코드를 검색해야 하는 경우를 확인합니다.
  2. 스택에서 큰 구조를 선언합니다. 힙/poolinstead를 사용해야 합니다. KernelStack 사용 및 시스템 공간 메모리 할당을 참조하세요.
  3. 불필요하게 WDF 개체 컨텍스트를 0으로 설정합니다. 이는 메모리가 자동으로 0이 되는 시기에 대한 명확성이 없음을 나타낼 수 있습니다.

일반 드라이버 지침

  1. WDM 및 WDF 기본 형식을 혼합합니다. WDF 기본 형식을 사용할 수 있는 WDM 기본 형식을 사용합니다. WDF 기본 형식을 사용하면 gotchas로부터 보호하고, 디버깅을 개선하며, 더 중요한 것은 드라이버를 usermode에 이식할 수 있게 해줍니다.
  2. FDO 이름을 지정하고 필요하지 않은 경우 기호 링크를 만듭니다. 드라이버 액세스 제어 관리를 참조하세요.
  3. 샘플 드라이버에서 GUID 및 기타 상수 값을 붙여넣고 사용하여 복사합니다.
  4. 드라이버 프로젝트에서 DMF(드라이버 모듈 프레임워크) 오픈 소스 코드를 사용하는 것이 좋습니다. DMF는 WDF 드라이버 개발자가 추가 기능을 사용할 수 있도록 하는 WDF 확장입니다. 드라이버 모듈 프레임워크 소개를 참조하세요.
  5. 레지스트리를 프로세스 간 알림 메커니즘 또는 사서함으로 사용합니다. 대안은 DMF 프로젝트에서 https://github.com/Microsoft/DMF사용할 수 있는 DMF_NotifyUserWithEvent 및 DMF_NotifyUserWithRequest 모듈을 참조 하세요.
  6. 레지스트리의 모든 부분을 시스템의 초기 부팅 단계에서 액세스할 수 있다고 가정합니다.
  7. 다른 드라이버 또는 서비스의 부하 순서에 종속됩니다. 부하 순서가 드라이버 제어 외부에서 변경될 수 있으므로 처음에는 작동하지만 나중에 예측할 수 없는 패턴에서 실패하는 드라이버가 발생할 수 있습니다.
  8. WDF와 같이 이미 사용할 수 있는 드라이버 라이브러리를 다시 만들면 드라이버에서 PnP 및 전원 관리 지원에 설명된 PnP 또는 드라이버 간 통신에 대한 버스 인터페이스 사용 OSR 문서에 설명된 대로 버스 인터페이스에 제공된 PnP가 제공됩니다.

PnP/Power

  1. pnp 디바이스 변경 알림에 등록하지 않고 pnp에 친숙하지 않은 방식으로 다른 드라이버와 상호 작용합니다. 디바이스 인터페이스 변경 알림 등록을 참조하세요.
  2. 버스 드라이버 또는 시스템을 사용하는 대신 ACPI 노드를 만들어 디바이스를 열거하고 전원 종속성을 만드는 대신 PNP 및 전원 종속성에 대한 소프트웨어 디바이스 생성 인터페이스를 세련된 방식으로 제공합니다. 함수 드라이버에서 PnP 및 전원 관리 지원을 참조 하세요.
  3. 디바이스를 사용하지 않도록 설정할 수 없음 표시 - 드라이버 업데이트에서 강제로 다시 부팅합니다.
  4. 디바이스 관리자에서 디바이스 숨기기 장치 관리자 디바이스 숨기기를 참조하세요.
  5. 디바이스의 인스턴스 하나에만 드라이버를 사용할 것이라고 가정합니다.
  6. 드라이버가 언로드되지 않을 것이라고 가정합니다. PnP 드라이버의 언로드 루틴을 참조 하세요.
  7. 가짜 인터페이스 도착 알림을 처리하지 않습니다. 이 문제는 발생할 수 있으며 드라이버는 이 조건을 안전하게 처리해야 합니다.
  8. DRIPS 제약 조건 또는 자식 디바이스에 중요한 S0 유휴 전원 정책을 구현하지 않습니다. 지원 유휴 전원 중지를 참조하세요.
  9. WdfDeviceStopIdle 반환 상태 검사 않으면 WdfDeviceStopIdle/ResumeIdle 불균형 및 결국 9F 버그 검사 인해 전원 참조 누수로 이어집니다.
  10. 리소스 리밸런싱으로 인해 PrepareHardware/ReleaseHardware를 두 번 이상 호출할 수 있는지 모릅니다. 이러한 콜백은 하드웨어 리소스 초기화로 제한되어야 합니다. EVT_WDF_DEVICE_PREPARE_HARDWARE 참조하세요.
  11. 소프트웨어 리소스를 할당하기 위해 PrepareHardware/ReleaseHardware 사용. 하드웨어와 상호 작용하는 데 필요한 리소스의 할당이 필요한 경우 AddDevice 또는 SelfManagedIoInit에서 디바이스에 정적 소프트웨어 리소스 할당을 수행해야 합니다. EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT 참조하세요.

코딩 지침

  1. 안전한 문자열 및 정수 함수를 사용하지 않습니다. 금고 문자열 함수 사용 및 금고 정수 함수 사용을 참조하세요.
  2. 상수 정의에 typedef를 사용하지 않습니다.
  3. 전역 변수 및 정적 변수 사용 전역에서 디바이스 컨텍스트당 저장을 방지합니다. 전역은 여러 디바이스 인스턴스에서 정보를 공유하기 위한 것입니다. 또는 WDFDRIVER 개체 컨텍스트를 사용하여 여러 디바이스 인스턴스에서 정보를 공유하는 것이 좋습니다.
  4. 변수에 설명이 포함된 이름을 사용하지 않습니다.
  5. 명명 변수에서 일관되지 않음 - 대/소문자 일관성. 기존 코드를 업데이트할 때 기존 코딩 스타일을 따르지 않습니다. 예를 들어 다른 함수의 공통 구조에 다른 변수 이름을 사용합니다.
  6. 전원 관리, 잠금, 상태 관리, 작업 영역 사용, DPC, 타이머, 전역 리소스 사용, 리소스 사전 할당, 복잡한 식/조건문 등 중요한 디자인 선택 사항을 주석으로 처리하지 않습니다.
  7. 호출되는 API의 이름에서 명백한 항목에 대해 주석으로 처리합니다. 주석을 함수 이름에 해당하는 영어로 만듭니다(예: WdfDeviceCreate를 호출할 때 "디바이스 개체 만들기" 주석 작성).
  8. 반환 호출이 있는 매크로는 만들지 마세요. 함수(C++)를 참조하세요.
  9. SAL(소스 코드 주석)이 없거나 불완전합니다. Windows 드라이버에 대한 SAL 2.0 주석을 참조 하세요.
  10. 인라인 함수 대신 매크로 사용
  11. C++를 사용하는 경우 constexpr 대신 상수에 매크로 사용
  12. 강력한 형식 검사 얻을 수 있도록 C++ 컴파일러 대신 C 컴파일러를 사용하여 드라이버를 컴파일합니다.

오류 처리

  1. 중요한 드라이버 오류를 보고하지 않고 디바이스를 정상적으로 표시하지 않습니다.
  2. 의미 있는 WIN32 오류 상태 변환하는 적절한 NT 오류 상태 반환하지 않습니다. NTSTATUS 값 사용을 참조 하세요.
  3. 시스템 함수의 반환된 상태 검사 위해 NTSTATUS 매크로를 사용하지 않습니다.
  4. 필요한 경우 상태 변수 또는 플래그에 대해 어설션하지 않습니다.
  5. 경합 조건을 해결하기 위해 포인터에 액세스하기 전에 포인터가 유효한지 확인합니다.
  6. NULL 포인터에 대한 ASSERTING입니다. NULL 포인터를 사용하여 메모리에 액세스하려고 하면 Windows에서 검사 버그가 발생합니다. 버그 검사 매개 변수는 null 포인터를 수정하는 데 필요한 정보를 제공합니다. 초과 작업, 많은 불필요한 ASSERT 문이 코드에 추가되면 메모리를 사용하고 시스템을 느리게 합니다.
  7. 개체 컨텍스트 포인터에 대한 ASSERTING입니다. 드라이버 프레임워크는 개체가 항상 컨텍스트로 할당되도록 보장합니다.

추적

  1. WPP 사용자 지정 형식을 정의하지 않고 추적 호출에 사용하여 사람이 읽을 수 있는 추적 메시지를 가져옵니다. Windows 드라이버에 WPP 소프트웨어 추적 추가를 참조 하세요.
  2. IFR 추적을 사용하지 않습니다. KMDF 및 UMDF 2 드라이버에서 IFR(Inflight Trace Recorder) 사용을 참조 하세요.
  3. WPP 추적 호출에서 함수 이름을 호출합니다. WPP는 이미 함수 이름과 줄 번호를 추적합니다.
  4. ETW 이벤트를 사용하여 이벤트에 영향을 미치는 성능 및 기타 중요한 사용자 환경을 측정하지 않습니다. 커널 모드 드라이버에 이벤트 추적 추가를 참조 하세요.
  5. 이벤트 로그에서 중요한 오류를 보고하지 않고 디바이스를 정상적으로 표시하지 않습니다.

확인

  1. 개발 및 테스트 중에 표준 및 고급 설정을 모두 사용하여 드라이버 검증 도구를 실행하지 않습니다. 드라이버 검증 도구를 참조하세요. 고급 설정에서는 낮은 리소스 시뮬레이션과 관련된 규칙을 제외한 모든 규칙을 사용하도록 설정하는 것이 좋습니다. 문제를 더 쉽게 디버그할 수 있도록 낮은 리소스 시뮬레이션 테스트를 격리된 상태로 실행하는 것이 좋습니다.
  2. 드라이버 또는 드라이버가 고급 검증 도구 설정이 사용하도록 설정된 디바이스 클래스에서 DevFund 테스트를 실행하지 않습니다. 명령줄을 통해 DevFund 테스트를 실행하는 방법을 참조 하세요.
  3. 드라이버가 HVCI 규격인지 확인하지 않습니다. HVCI 호환성 코드 구현을 참조하세요.
  4. 사용자 모드 드라이버를 개발 및 테스트하는 동안 WUDFhost.exe AppVerifier를 실행하지 않습니다. 애플리케이션 검증 도구를 참조하세요.
  5. WDF 개체가 중단되지 않도록 런타임에 !wdfpoolusage 디버거 확장을 사용하여 메모리 사용을 검사 않습니다. 메모리, 요청 및 작업 영역은 이러한 문제의 일반적인 피해자입니다.
  6. !wdfkd 디버거 확장을 사용하여 개체 트리를 검사하여 개체가 올바르게 부모인지 확인하고 WDFDRIVER, WDFDEVICE, IO와 같은 주요 개체의 특성을 검사.