Surface 팀 드라이버 개발 모범 사례
소개
이러한 드라이버 개발 지침은 Microsoft의 드라이버 개발자가 수년 동안 개발했습니다. 운전자가 잘못 동작하고 교훈을 배웠을 때 시간이 지남에 따라 이러한 교훈을 포착하고 이 지침 집합으로 발전시켰습니다. 이러한 모범 사례는 Microsoft Surface 하드웨어 팀에서 고유한 Surface 하드웨어 환경을 지원하는 디바이스 드라이버 코드를 개발하고 기본 데 사용됩니다.
모든 지침 집합과 마찬가지로 합법적인 예외 및 동일하게 유효한 대체 방법이 있습니다. 개발 표준에 이러한 지침을 통합하거나 이를 사용하여 개발 환경 및 고유한 요구 사항에 대한 특정 지침기본 작업을 시작하는 것이 좋습니다.
드라이버 개발자의 일반적인 실수
I/O 처리
- 길이를 확인하지 않고 IOCTL에서 검색된 버퍼에 액세스합니다. 버퍼 크기를 확인하지 못했습니다.
- 사용자 스레드 또는 임의 스레드 컨텍스트의 컨텍스트에서 차단 I/O를 수행합니다. 커널 디스패처 개체 소개를 참조하세요.
- 시간 제한 없이 동기 I/O를 다른 드라이버에 보냅니다. 동기적으로 I/O 요청 보내기를 참조 하세요.
- 보안에 미치는 영향을 이해하지 않고 둘 다 io IOCTL을 사용합니다. 버퍼링 또는 직접 I/O 사용을 참조하세요.
- WdfRequestForwardToIoQueue의 반환 상태 검사 않거나 오류를 올바르게 처리하지 않아 WDFREQUEST가 중단되었습니다.
- WDFREQUEST를 취소할 수 없는 상태로 큐 외부에 유지합니다. I/O 큐 관리, I/O 요청 완료 및 I/O 요청 취소를 참조하세요.
- IoQueues를 사용하는 대신 Mark/UnmarkCancelable 함수를 사용하여 취소를 관리하려고 합니다. 프레임워크 큐 개체를 참조 하세요.
- 파일 핸들 정리와 닫기 작업의 차이점을 알 수 없습니다. 정리 및 닫기 작업 처리의 오류를 참조 하세요.
- I/O 완료 및 완료 루틴에서 다시 제출을 통해 잠재적 재귀를 간과합니다.
- WDFQUEUE의 전원 관리 특성에 대해 명시적이지 않습니다. 전원 관리 선택을 명확하게 문서화하지 않습니다. 이것이 버그 확인 0x9F 주요 원인 입니다. WDF 드라이버의 DRIVER_POWER_STATE_FAILURE . 디바이스가 제거되면 프레임워크는 다른 제거 프로세스 단계에서 전원 관리 큐 및 비전력 관리 큐에서 IO를 제거합니다. 비전력 관리 큐는 최종 IRP_MN_REMOVE_DEVICE 수신될 때 제거됩니다. 따라서 비전력 관리 큐에서 I/O를 보유하는 경우 교착 상태를 방지하기 위해 EvtDeviceSelfManagedIoFlush의 컨텍스트에서 I/O를 명시적으로 제거하는 것이 좋습니다.
- IRP 처리 규칙을 따르지 않습니다. 정리 및 닫기 작업 처리의 오류를 참조 하세요.
동기화
- 보호가 필요하지 않은 코드에 대한 잠금을 유지합니다. 적은 수의 작업만 보호해야 하는 경우 전체 함수에 대한 잠금을 유지하지 마세요.
- 자물쇠가 고정 된 드라이버에서 호출. 이것이 교착 상태의 주요 원인입니다.
- 뮤텍스, 세마포 및 스핀 잠금과 같은 적절한 시스템 제공 잠금 기본 형식을 사용하는 대신 인터로킹 기본 형식을 사용하여 잠금 체계를 만듭니다. 뮤텍스 개체 소개, 세마포 개체 및 스핀 잠금 소개를 참조하세요.
- 일부 유형의 수동 잠금이 더 적합한 스핀 잠금을 사용합니다. 빠른 뮤텍스 및 보호된 뮤텍스 및 이벤트 개체를 참조하세요. 잠금에 대한 추가 관점은 OSR 문서 - 동기화 상태를 검토하세요.
- 의미를 완전히 이해하지 않고 WDF 동기화 및 실행 수준 모델에 옵트인합니다. 프레임워크 잠금 사용을 참조하세요. 드라이버가 하드웨어와 직접 상호 작용하는 모놀리식 최상위 드라이버가 아니면 재귀로 인해 교착 상태가 발생할 수 있으므로 WDF 동기화를 옵트인하지 마십시오.
- 중요한 영역을 입력하지 않고 여러 스레드의 컨텍스트에서 KEVENT, Semaphore, ERESOURCE, UnsafeFastMutex를 획득합니다. 이 작업을 수행하면 이러한 잠금 중 하나를 보유하는 스레드가 일시 중단될 수 있으므로 DOS 공격이 발생할 수 있습니다. 커널 디스패처 개체 소개를 참조하세요.
- 스레드 스택에 KEVENT를 할당하고 EVENT가 계속 사용 중인 동안 호출자에게 반환합니다. 일반적으로 IoBuildSyncronousFsdRequest 또는 IoBuildDeviceIoControlRequest와 함께 사용할 때 수행됩니다. 이러한 호출의 호출자는 I/O 관리자가 IRP가 완료될 때 이벤트를 알릴 때까지 스택에서 해제되지 않도록 해야 합니다.
- 디스패치 루틴에서 무기한 대기. 일반적으로 디스패치 루틴에서 모든 종류의 대기는 나쁜 관행입니다.
- 개체를 삭제하기 전에 개체의 유효성을 부적절하게 검사(nULL이면). 이는 일반적으로 작성자가 개체의 수명을 제어하는 코드를 완전히 이해하지 못했음을 의미합니다.
개체 관리
- WDF 개체를 명시적으로 부모로 지정하지 않습니다. 프레임워크 개체 소개를 참조하세요.
- WDF 개체를 더 나은 수명 관리를 제공하고 메모리 사용량을 최적화하는 개체로 양육하는 대신 WDFDRIVER로 육아합니다. 예를 들어 WDFREQUEST를 IOTARGET 대신 WDFDEVICE로 육아합니다. 일반 프레임워크 개체, 프레임워크 개체 수명 주기 및 프레임워크 개체 요약 사용을 참조하세요.
- 드라이버에서 액세스하는 공유 메모리 리소스의 런다운 보호를 수행하지 않습니다. ExInitializeRundownProtection 함수를 참조하세요.
- 이전 항목이 이미 큐에 있거나 이미 실행 중인 동안 동일한 작업 항목을 실수로 큐에 대기합니다. 클라이언트가 대기 중인 모든 작업 항목이 실행될 것이라고 가정하는 경우 문제가 될 수 있습니다. Framework WorkItems 사용을 참조 하세요. WorkItems 큐에 대한 자세한 내용은 DMF(드라이버 모듈 프레임워크) 프로젝트의 DMF_QueuedWorkitem 모듈을 https://github.com/Microsoft/DMF참조하세요.
- 타이머가 처리해야 하는 메시지를 게시하기 전에 타이머를 큐에 대기합니다. 타이머 사용을 참조하세요.
- 완료하는 데 무기한 시간이 걸리거나 차단할 수 있는 작업 영역에서 작업을 수행합니다.
- 대기 중인 작업 항목의 홍수를 초래하는 솔루션을 디자인합니다. 악의적인 사용자가 작업을 제어할 수 있는 경우 응답하지 않는 시스템 또는 DOS 공격으로 이어질 수 있습니다(예: 모든 I/O에 대해 새 작업 항목을 큐에 대기하는 드라이버에 I/O를 펌핑). 프레임워크 작업 항목 사용을 참조 하세요.
- 개체를 삭제하기 전에 작업 항목 DPC 콜백이 완료될 때 계속 실행되지 않습니다. DPC 루틴 및 WdfDpcCancel 함수 작성 지침을 참조하세요.
- 짧은 기간/비 폴링 작업에 작업 항목을 사용하는 대신 스레드를 만듭니다. 시스템 작업자 스레드를 참조 하세요.
- 드라이버를 삭제하거나 언로드하기 전에 스레드가 완료될 수 있도록 보장하지 않습니다. 스레드 런다운 동기화에 대한 자세한 내용은 DMF(드라이버 모듈 프레임워크) 프로젝트의 https://github.com/Microsoft/DMFDMF_Thread 모듈과 연결된 코드를 확인하세요.
- 단일 드라이버를 사용하여 서로 다르지만 상호 종속된 디바이스를 관리하고 전역 변수를 사용하여 정보를 공유합니다.
메모리
- 가능한 경우 패시브 실행 코드를 PAGEABLE로 표시하지 않습니다. 드라이버 코드 페이징은 드라이버의 코드 공간 크기를 줄여 다른 용도로 시스템 공간을 확보할 수 있습니다. IRQL = DISPATCH_LEVEL 발생하거나 발생된 IRQL >에서 호출할 수 있는 페이즐 가능 코드를 신중하게 표시해야 합니다. 코드 및 데이터를 페이징할 수 있어야 하는 경우와 드라이버를 페이징 가능 으로 만들고 페이징할 수 있는 코드를 검색해야 하는 경우를 확인합니다.
- 스택에서 큰 구조를 선언합니다. 힙/poolinstead를 사용해야 합니다. KernelStack 사용 및 시스템 공간 메모리 할당을 참조하세요.
- 불필요하게 WDF 개체 컨텍스트를 0으로 설정합니다. 이는 메모리가 자동으로 0이 되는 시기에 대한 명확성이 없음을 나타낼 수 있습니다.
일반 드라이버 지침
- WDM 및 WDF 기본 형식을 혼합합니다. WDF 기본 형식을 사용할 수 있는 WDM 기본 형식을 사용합니다. WDF 기본 형식을 사용하면 gotchas로부터 보호하고, 디버깅을 개선하며, 더 중요한 것은 드라이버를 usermode에 이식할 수 있게 해줍니다.
- FDO 이름을 지정하고 필요하지 않은 경우 기호 링크를 만듭니다. 드라이버 액세스 제어 관리를 참조하세요.
- 샘플 드라이버에서 GUID 및 기타 상수 값을 붙여넣고 사용하여 복사합니다.
- 드라이버 프로젝트에서 DMF(드라이버 모듈 프레임워크) 오픈 소스 코드를 사용하는 것이 좋습니다. DMF는 WDF 드라이버 개발자가 추가 기능을 사용할 수 있도록 하는 WDF 확장입니다. 드라이버 모듈 프레임워크 소개를 참조하세요.
- 레지스트리를 프로세스 간 알림 메커니즘 또는 사서함으로 사용합니다. 대안은 DMF 프로젝트에서 https://github.com/Microsoft/DMF사용할 수 있는 DMF_NotifyUserWithEvent 및 DMF_NotifyUserWithRequest 모듈을 참조 하세요.
- 레지스트리의 모든 부분을 시스템의 초기 부팅 단계에서 액세스할 수 있다고 가정합니다.
- 다른 드라이버 또는 서비스의 부하 순서에 종속됩니다. 부하 순서가 드라이버 제어 외부에서 변경될 수 있으므로 처음에는 작동하지만 나중에 예측할 수 없는 패턴에서 실패하는 드라이버가 발생할 수 있습니다.
- WDF와 같이 이미 사용할 수 있는 드라이버 라이브러리를 다시 만들면 드라이버에서 PnP 및 전원 관리 지원에 설명된 PnP 또는 드라이버 간 통신에 대한 버스 인터페이스 사용 OSR 문서에 설명된 대로 버스 인터페이스에 제공된 PnP가 제공됩니다.
PnP/Power
- pnp 디바이스 변경 알림에 등록하지 않고 pnp에 친숙하지 않은 방식으로 다른 드라이버와 상호 작용합니다. 디바이스 인터페이스 변경 알림 등록을 참조하세요.
- 버스 드라이버 또는 시스템을 사용하는 대신 ACPI 노드를 만들어 디바이스를 열거하고 전원 종속성을 만드는 대신 PNP 및 전원 종속성에 대한 소프트웨어 디바이스 생성 인터페이스를 세련된 방식으로 제공합니다. 함수 드라이버에서 PnP 및 전원 관리 지원을 참조 하세요.
- 디바이스를 사용하지 않도록 설정할 수 없음 표시 - 드라이버 업데이트에서 강제로 다시 부팅합니다.
- 디바이스 관리자에서 디바이스 숨기기 장치 관리자 디바이스 숨기기를 참조하세요.
- 디바이스의 인스턴스 하나에만 드라이버를 사용할 것이라고 가정합니다.
- 드라이버가 언로드되지 않을 것이라고 가정합니다. PnP 드라이버의 언로드 루틴을 참조 하세요.
- 가짜 인터페이스 도착 알림을 처리하지 않습니다. 이 문제는 발생할 수 있으며 드라이버는 이 조건을 안전하게 처리해야 합니다.
- DRIPS 제약 조건 또는 자식 디바이스에 중요한 S0 유휴 전원 정책을 구현하지 않습니다. 지원 유휴 전원 중지를 참조하세요.
- WdfDeviceStopIdle 반환 상태 검사 않으면 WdfDeviceStopIdle/ResumeIdle 불균형 및 결국 9F 버그 검사 인해 전원 참조 누수로 이어집니다.
- 리소스 리밸런싱으로 인해 PrepareHardware/ReleaseHardware를 두 번 이상 호출할 수 있는지 모릅니다. 이러한 콜백은 하드웨어 리소스 초기화로 제한되어야 합니다. EVT_WDF_DEVICE_PREPARE_HARDWARE 참조하세요.
- 소프트웨어 리소스를 할당하기 위해 PrepareHardware/ReleaseHardware 사용. 하드웨어와 상호 작용하는 데 필요한 리소스의 할당이 필요한 경우 AddDevice 또는 SelfManagedIoInit에서 디바이스에 정적 소프트웨어 리소스 할당을 수행해야 합니다. EVT_WDF_DEVICE_SELF_MANAGED_IO_INIT 참조하세요.
코딩 지침
- 안전한 문자열 및 정수 함수를 사용하지 않습니다. 금고 문자열 함수 사용 및 금고 정수 함수 사용을 참조하세요.
- 상수 정의에 typedef를 사용하지 않습니다.
- 전역 변수 및 정적 변수 사용 전역에서 디바이스 컨텍스트당 저장을 방지합니다. 전역은 여러 디바이스 인스턴스에서 정보를 공유하기 위한 것입니다. 또는 WDFDRIVER 개체 컨텍스트를 사용하여 여러 디바이스 인스턴스에서 정보를 공유하는 것이 좋습니다.
- 변수에 설명이 포함된 이름을 사용하지 않습니다.
- 명명 변수에서 일관되지 않음 - 대/소문자 일관성. 기존 코드를 업데이트할 때 기존 코딩 스타일을 따르지 않습니다. 예를 들어 다른 함수의 공통 구조에 다른 변수 이름을 사용합니다.
- 전원 관리, 잠금, 상태 관리, 작업 영역 사용, DPC, 타이머, 전역 리소스 사용, 리소스 사전 할당, 복잡한 식/조건문 등 중요한 디자인 선택 사항을 주석으로 처리하지 않습니다.
- 호출되는 API의 이름에서 명백한 항목에 대해 주석으로 처리합니다. 주석을 함수 이름에 해당하는 영어로 만듭니다(예: WdfDeviceCreate를 호출할 때 "디바이스 개체 만들기" 주석 작성).
- 반환 호출이 있는 매크로는 만들지 마세요. 함수(C++)를 참조하세요.
- SAL(소스 코드 주석)이 없거나 불완전합니다. Windows 드라이버에 대한 SAL 2.0 주석을 참조 하세요.
- 인라인 함수 대신 매크로 사용
- C++를 사용하는 경우 constexpr 대신 상수에 매크로 사용
- 강력한 형식 검사 얻을 수 있도록 C++ 컴파일러 대신 C 컴파일러를 사용하여 드라이버를 컴파일합니다.
오류 처리
- 중요한 드라이버 오류를 보고하지 않고 디바이스를 정상적으로 표시하지 않습니다.
- 의미 있는 WIN32 오류 상태 변환하는 적절한 NT 오류 상태 반환하지 않습니다. NTSTATUS 값 사용을 참조 하세요.
- 시스템 함수의 반환된 상태 검사 위해 NTSTATUS 매크로를 사용하지 않습니다.
- 필요한 경우 상태 변수 또는 플래그에 대해 어설션하지 않습니다.
- 경합 조건을 해결하기 위해 포인터에 액세스하기 전에 포인터가 유효한지 확인합니다.
- NULL 포인터에 대한 ASSERTING입니다. NULL 포인터를 사용하여 메모리에 액세스하려고 하면 Windows에서 검사 버그가 발생합니다. 버그 검사 매개 변수는 null 포인터를 수정하는 데 필요한 정보를 제공합니다. 초과 작업, 많은 불필요한 ASSERT 문이 코드에 추가되면 메모리를 사용하고 시스템을 느리게 합니다.
- 개체 컨텍스트 포인터에 대한 ASSERTING입니다. 드라이버 프레임워크는 개체가 항상 컨텍스트로 할당되도록 보장합니다.
추적
- WPP 사용자 지정 형식을 정의하지 않고 추적 호출에 사용하여 사람이 읽을 수 있는 추적 메시지를 가져옵니다. Windows 드라이버에 WPP 소프트웨어 추적 추가를 참조 하세요.
- IFR 추적을 사용하지 않습니다. KMDF 및 UMDF 2 드라이버에서 IFR(Inflight Trace Recorder) 사용을 참조 하세요.
- WPP 추적 호출에서 함수 이름을 호출합니다. WPP는 이미 함수 이름과 줄 번호를 추적합니다.
- ETW 이벤트를 사용하여 이벤트에 영향을 미치는 성능 및 기타 중요한 사용자 환경을 측정하지 않습니다. 커널 모드 드라이버에 이벤트 추적 추가를 참조 하세요.
- 이벤트 로그에서 중요한 오류를 보고하지 않고 디바이스를 정상적으로 표시하지 않습니다.
확인
- 개발 및 테스트 중에 표준 및 고급 설정을 모두 사용하여 드라이버 검증 도구를 실행하지 않습니다. 드라이버 검증 도구를 참조하세요. 고급 설정에서는 낮은 리소스 시뮬레이션과 관련된 규칙을 제외한 모든 규칙을 사용하도록 설정하는 것이 좋습니다. 문제를 더 쉽게 디버그할 수 있도록 낮은 리소스 시뮬레이션 테스트를 격리된 상태로 실행하는 것이 좋습니다.
- 드라이버 또는 드라이버가 고급 검증 도구 설정이 사용하도록 설정된 디바이스 클래스에서 DevFund 테스트를 실행하지 않습니다. 명령줄을 통해 DevFund 테스트를 실행하는 방법을 참조 하세요.
- 드라이버가 HVCI 규격인지 확인하지 않습니다. HVCI 호환성 코드 구현을 참조하세요.
- 사용자 모드 드라이버를 개발 및 테스트하는 동안 WUDFhost.exe AppVerifier를 실행하지 않습니다. 애플리케이션 검증 도구를 참조하세요.
- WDF 개체가 중단되지 않도록 런타임에 !wdfpoolusage 디버거 확장을 사용하여 메모리 사용을 검사 않습니다. 메모리, 요청 및 작업 영역은 이러한 문제의 일반적인 피해자입니다.
- !wdfkd 디버거 확장을 사용하여 개체 트리를 검사하여 개체가 올바르게 부모인지 확인하고 WDFDRIVER, WDFDEVICE, IO와 같은 주요 개체의 특성을 검사.