[DDK] Windows NT드라이버 개발자들을 위한 정보 - 피해야 할 사항들

요약

loadTOCNode(1, 'summary');
다음은 Windows NT 장치 드라이버들을 만들기 위한 정보 입니다. 나타난 정보는 모든 기술에 적용됩니다. 이것은 드라이버 문제를 해결하기 위한 점검 목록으로도 사용될 수 있습니다.

아래에 기술된 정보를 효과적으로 사용하기 위해서 Windows NT 구조에 대한 기본 지식과 일부 장치 드라이버 개발 경험이 필요합니다. 장치 드라이버 개발에 대한 추가 정보를 보시려면 MSDN Professional 멤버쉽을 통해서 사용 가능한 Windows NT 장치 드라이버 킷(DDK)을 보시기 바랍니다.

추가 정보

loadTOCNode(1, 'moreinformation');
다음은 Windows NT 장치 드라이버를 가지고 개발자가 작업할 때 피해야 할 것들에 대한 목록입니다:
1. I/O Request Packet (IRP) Pending(IoMarkIrpPending)의 표시 없이 Dispatch 루틴에서 STATUS_PENDING 을 반환하지 않도록 합니다.
2. 인터럽트 서비스 루틴(ISR)에서 KeSynchronizeExecution 을 호출하지 않도록 합니다. 그것은 시스템 교착 상태의 원인이 됩니다.
3. DeviceObject->Flags 을 DO_BUFFERED_IO 와 DO_DIRECT_IO 양쪽에 설정하지 않도록 합니다. 시스템에 혼동을 줄 수 있으며 결국에는 치명적인 오류의 원인이 됩니다. 또한 DeviceObject->Flags 내에서 METHOD_BUFFERED, METHOD_NEITHER, METHOD_IN_DIRECT 또는 METHOD_OUT_DIRECT 를 설정하지 않도록 합니다. 왜냐하면 이들 값들은 IOCTL들을 정의하는데에만 사용되기 때문입니다.
4. 페이지 된 풀(Paged Pool)에서 Dispatcher 개체를 할당하지 않습니다. 만일 그렇게 한다면 그것은 우발적인 시스템 커널 모드 오류(Bugchecks)의 원인이 됩니다.
5. IRQL >= DISPATCH_LEVEL 에서 실행하는 동안 페이지 된 풀(Paged Pool)에서 메모리를 할당하지 않도록 하거나 메모리를 액세스하지 않도록 합니다. 그것은 치명적인 오류의 원인이 됩니다.
6. IRQL >= DISPATCH_LEVEL 에서 "0" 이 아닌 간격에 대한 커널 Dispatcher 개체상에서 대기하지 않도록 합니다. 치명적인 오류의 원인이 됩니다.
7. IRQL >= DISPATCH_LEVEL 에서 실행되는 동안 직접 또는 간접적으로 스레드 호출이 대기하게 될 원인이 되는 어떠한 함수도 호출하지 않도록 합니다. 치명적인 오류의 원인이 됩니다.
8. Top-Level 루틴이 발생되었던 레벨 아래에 있는 Interrupt Request Level (IRQL) 을 낮추지 않도록 합니다.
9. KeRaiseIrql()을 호출하지 않았다면 KeLowerIrql()을 호출하지 않도록 합니다.
10. 50 microseconds 보다 긴 프로세서(KeStallExecutionProcessor)을 교착 상태에 빠지지 않도록 합니다.
11. 필요이상으로 어떠한 Spin Lock 들을 유지하지 않도록 합니다. 전체적인 시스템 성능 향상을 위해서는 25 microseconds 이상의 어떠한 시스템 관련 Spin Lock들도 유지하지 않도록 합니다.
12. DISPATCH_LEVEL 보다 큰 IRQL 에서 실행되는 동안에 KeAcquireSpinLock 과 KeReleaseSpinLock, 또는 KeAcquireSpinLockAtDpcLevel 과 KeReleaseSpinLockFromDpcLevel을 호출하지 않도록 합니다.
13. KeAcquireSpinLock 를 이용하여 얻어지는 Spin Lock 을 KeReleaseSpinLockFromDpcLevel 을 호출하여 릴리즈하지 않도록 합니다. 왜냐하면 원래의 IRQL 는 복구되지 않을 것이기 때문입니다.
14. KeAcquireSpinLock 와 KeReleaseSpinLock 또는 ISR 이나 SynchCritSection 루틴으로부터 실행 Spin Lock 을 사용하는 다른 모든 루틴을 호출하지 않도록 합니다.
15. DriverEntry 이외의 루틴에서 장치 개체를 만들었을 때 DO_DEVICE_INITIALIZING 플래그를 Clear 하는 것을 잊지 않도록 합니다.
16. 동시에 다른 프로세서들 상에 있는 다중 스레드가 있는 상태에서 Deferred Procedure Call (DPC) 개체 (KeInsertQueueDpc 이용하여) 를 큐에 넣지 않도록 합니다. 그것은 치명적인 오류의 원인이 될 수 있습니다.
17. CutomerTimerDPC 루틴에서 주기적인 타이머를 할당하도록 합니다. DPC 루틴에서 비주기적인 타이머를 할당하지 않을 수도 있습니다.
18. 동일한 DPC 포인터를 KeSetTimer 또는 KeSetTimerEx (CustomTimerDpc) 그리고 KeInsertQueueDpc (CustomDpc)으로 넘기지 않도록 합니다. 왜냐하면 그것은 Race Condition 의 원인이 됩니다.
19. Spin Lock 이 유지되는 동안에는 IoStartNextPacket 을 호출하지 않도록 합니다. 그것은 시스템을 교착 상태에 빠지도록 만들 수 있습니다.
20. Spin Lock 이 유지되는 동안에는 IoCompleteRequest 를 호출하지 않도록 합니다. 그것은 시스템을 교착 상태에 빠지도록 만들 수 있습니다.
21. 드라이버가 Completion 루틴을 설정하였다면 Completion 루틴을 NULL 로 설정하지 않고 IoCompleteRequest 를 호출하지 않도록 합니다.
22. IoCompleteRequest 호출하기 이전에 IRP 내에서 I/O Status Block 설정하는 것을 잊지 않도록 합니다.
23. IRP 를 큐에 넣거나 다른 드라이버(IoCallDriver)로 그것을 보낸 후에 IoMarkPending 을 호출하지 않도록 합니다. 그 드라이버가 IoMarkPending 를 호출하고 커널 모드 오류(Bugcheck) 가 일어나기 이전에 해당 IRP 가 완료될 수 있습니다. Completion 루틴을 가진 드라이버에 있어서 Irp->PendingReturned 로 설정된다면 그 Completion 루틴은 반드시 IoMarkPending 를 호출하여야 합니다.
24. IoCompleteRequest 를 IRP 상에서 호출하고 난 후 IRP 를 수정하지 않도록 합니다.
25. 해당 IRP 가 작업을 완료하였는지 알 수 없을 경우에 드라이버에 의해서 소유되지 않은 IRP 상에서 IoCancelIrp 를 호출하지 않도록 합니다.
26. Dispatch Routine 이 작동하고 있는 IRP 에 대해서 Dispatch 루틴이 호출기로 반환될 때 까지는 IoCancelIrp 를 호출하지 않도록 합니다.
27. Intermediate 드라이버로부터 Lower 드라이버들에 대한 IRP 들을 생성하기 위해서 IoMakeAssociatedIrp 를 호출하지 않도록 합니다. Intermediate 드라이버에서 얻은 해당 IRP 는 IRP와 연결될 수 있으며 이미 연결된 IRP 로 다른 IRP를 연결할 수는 없습니다.
28. 버퍼링 된 I/O 를 수행하도록 설정된 IRP 상에서 IoMakeAssociatedIrp 를 호출하지 않도록 합니다.
29. 가상 포인터를 절대로 장치 I/O 에 참조되지 않도록 하며 액세스 하지 않도록 합니다. 장치에 액세스하려면 항상 올바른 Hardware Abstraction Layer (HAL) 기능을 사용하시기 바랍니다.
30. DISPATCH_LEVEL 에서 수정될 수도 있는 ISR 에서 IRP 또는 장치 개체 필드를 액세스하지 않도록 합니다. 대칭적인 다중 프로세서 시스템에서는 이것이 데이터 충돌을 일으킬 수도 있습니다.
31. Low-IRQL 코드에 의해서 쓰여졌을 수도 있는 데이터가 있다면 High-IRQL 에서는 그 데이터를 수정하지 않도록 합니다. KeSynchronizeExecution 루틴을 사용하시기 바랍니다.
32. 시스템 관련한 취소(Cancel) Spin Lock 을 받기(IoAcquireCancelSpinLock) 이전에 DispatchCleanup 루틴내에(만일 가지고 있다면) 드라이버 자체의 Spin Locks 들 중 하나를 받지 않도록 합니다. 드라이버 전체를 통해서 지속적인 Lock Acquisition Hierarchy 를 따르는 것은 잠재적인 교착 상태를 피하기 위해서 필수적 입니다.
33. 취소 루틴에서 IoAcquireCancelSpinLock 을 호출하지 않도록 합니다. 왜냐하면 그것은 항상 그것을 위해서 유지하고 있는 시스템 Cancel Spin Lock 과 같이 호출되기 때문입니다.
34. 취소 루틴에서 반환되기 이전에 IoReleaseCancelSpinLock 을 호출하는 것을 잊지 않도록 합니다.
35. IRQL 기반의 동기화를 사용하지 않도록 합니다. 왜냐하면 이것은 단일 프로세서 시스템에서만 작동하기 때문입니다. 하나의 프로세서에서 IRQL 을 올리는 것은 다른 프로세서상의 인터럽트를 마스크(Mask)하지 못합니다.
36. 오버랩 된 메모리 주소 영역에서는 RtlCopyMemory 를 사용하지 않도록 합니다. RtlMoveMemory 를 사용하시기 바랍니다.
37. 주어진 CPU에 대해서 조차도 페이지 크기가 일정하다고 가정하지 않도록 합니다. PAGE_SIZE 와 이동성(Portability)을 유지하기 위해서 헤더 파일에서 정의된 상수들과 관련된 다른 페이지를 사용하시기 바랍니다.
38. Boot\System 초기화 단계에 있는 로드 된 드라이버의 DriverEntry 루틴에서 Registry\Machine\Hardware 와 Registry\Machine\System 이외의 다른 모든 레지스트리 키들에 대해서 접근하지 않도록 합니다.
39. 드라이버 레지스트리 키(Registry\Machine\System\CurrentControlSet\Services) 아래에서 드라이버를 로딩하기 위해서 Enum 키를 만들기 않도록 합니다. 시스템은 동적으로 이 키를 생성합니다.
40. 처음에 레지스트리 내에서 필요한 버스-관련 I/O 포트, 메모리 범위, 인터럽트 또는 Direct Memory Access (DMA) 채널/ 포트 하드웨어 리소스의 요구 없이 물리적 장치를 초기화하려는 시도를 하지 않도록 합니다.
41. STATUS_SUCCESS 를 반환하지 않는다면 DriverEntry 루틴에서 IoRegisterDriverReinitialization 을 호출하지 않도록 합니다.
42. 페이징 가능한 스레드 또는 IRQL PASSIVE_LEVEL 에서 실행하는 페이징 가능한 드라이버 루틴에서 TRUE 로 설정된 Wait 매개 변수 이용하여 KeSetEvent 를 호출하지 않도록 합니다. 루틴이 KeSetEvent 와 KeWait..Object 의 호출들 사이에서 페이지 아웃 되었다면 호출의 이러한 유형은 치명적인 페이지 오류를 일으킵니다.
43. 페이징 가능한 스레드 또는 IRQL PASSIVE_LEVEL 에서 실행하는 페이징 가능한 드라이버 루틴에서 TRUE 로 설정된 Wait 매개 변수 이용하여 KeReleaseSemaphore 를 호출하지 않도록 합니다. 루틴이 KeReleaseSemaphore 와 KeWait..Object 의 호출들 사이에서 페이지 아웃 되었다면 호출의 이러한 유형은 치명적인 페이지 오류를 일으킵니다.
44. 페이징 가능한 스레드 또는 IRQL PASSIVE_LEVEL 에서 실행하는 페이징 가능한 드라이버 루틴에서 TRUE 로 설정된 Wait 매개 변수 이용하여 KeReleaseMutex 를 호출하지 않도록 합니다. 루틴이 KeReleaseMutex 와 KeWait..Object 의 호출들 사이에서 페이지 아웃 되었다면 호출의 이러한 유형은 치명적인 페이지 오류를 일으킵니다.
45. 정식으로 출시된 Windows NT 드라이버에서 KeBugCheckEx 또는 KeBugCheck 을 호출하지 않도록 합니다. 시스템 메모리와 충돌 또는 결과적으로 시스템 버그 점검의 원인이 될 수 있는 발생한 오류가 치명적이지 않다면 항상 오류 조건을 처리하도록 시도하시기 바랍니다.
46. IoTimer 루틴 1초마다 정확하게 호출될 것이라는 가정을 하지 않도록 합니다. 왜냐하면 어떤 특정한 IoTimer 루틴이 호출되는 그 간격은 전적으로 시스템 클럭에 의존하고 있기 때문입니다.
47. 커널 모드 장치 드라이버로부터 Win32 응용 프로그램 인터페이스(API)를 호출하지 않도록 합니다.
48. 스레드의 커널 모드 스택을 호출하는 것은 그것이 커널 모드에서 실행하는 동안에는 동적으로 증가하지 않기 때문에 스택 오버 플로우를 일으킬 수 있는 재귀함수를 사용하지 않도록 합니다.
49. 하나 이상의 인터럽트를 처리하는 ISR 에서 인터럽트들을 확인하기 위해서 인터럽트 개체 포인터(PKINTERRUPT)들을 사용하지 않도록 합니다. 왜냐하면 ISR 에서 구해진 인터럽트 개체의 주소는 IoConnectInterrupt 의 그것과 항상 동일하지 않기 때문입니다. 현재 인터럽트가 걸려 있는 장치를 확인하기 위해서 IoConnectInterrupt 에 지정해 놓은 ServiceContext 값을 사용하여야만 합니다.
50. CustomTimerDpc (KeCancelTimer) 를 Clear 하지 않고서는 드라이버를 내리지 않도록 합니다. 그 드라이버가 내려진 후에 DPC 가 종료되었다면 존재하지 않는 코드를 조회할 수 있으며 시스템은 커널 모드 오류(Bugcheck)을 하는 원인이 될 수 있습니다.
51. 드라이버 세트의 I/O CompletionRoutine을 갖는 모든 IRP들이 완료되기 전에는 드라이버가 Unload 될 수 없습니다. 만약 해당 드라이버가 Unload 된 후에 IRP가 하위 드라이버에 의해서 완료가 완료된다면 시스템은 존재하지 않는 코드 수행을 시도할 수 있고 이로 인해 시스템 충돌(Crash)을 야기시킬 수 있습니다.
52. 드라이버가 장치 인터럽트를 처리할 수 있도록 준비되기 이전까지 장치 인터럽트를 활성화하지 않도록 합니다. 드라이버가 완전하게 설치된 후 활성화 해야 하며 시스템이 ISR 과 DPC 에 있는 해당 드라이버의 내부 구조를 수정하는 것은 안전합니다.
53. Spin lock 이 유지되고 있는 동안에는 드라이버 외부에서 호출하지 않도록 합니다. 왜냐하면 그것은 교착 상태의 원인이 될 수 있기 때문입니다.
54. IoBuildAsynchronousFsdRequest/IoAllocateIrp 를 가진 드라이버에 의해서 생성된 IRP 에 대한 I/O Completion 루틴에서 STATUS_MORE_PROCESSING_REQUIRED 을 반환하는 것을 잊지 않도록 합니다. 왜냐하면 해당 IRP 는 I/O 관리자에 의해서 Completion Related Post-Processing 을 준비하지 않기 때문입니다. 그와 같은 IRP 는 해당 드라이버에 의해서 분명히 제어가 풀려 있어야(IoFreeIrp) 합니다. 해당 IRP 가 재사용되지 않을 것이라면 STATUS_MORE_PROCESSING_REQUIRED 상태를 반환하기 이전에 Completion 루틴에서 제어가 풀리게 될 수 있습니다.
55. 임의의 스레드 Context 내에서 IRP 에 IoBuildSynchronousFsdRequest/IoBuildDeviceIoControlRequest 를 할당하지 않도록 합니다. 왜냐하면 해당 IRP 는 그것이 제어가 풀리게 될 때까지 해당 스레드(Irp->ThreadListEntry)와의 연결을 유지하기 때문입니다.
56.

IoAllocateIrp 와 연결되어 있는 IRP 상에서 IoInitializeIrp 를 호출하지 않도록 합니다. IoAllocateIrp 의해서 반환되는 IRP 상에서 IoInitializeIrp 를 호출해야 한다고 설명하고 있는 DDK 참고 문서는 올바르지 않은 것입니다. 이 문서는 올바르지 않으며 올바른 Knowledge Base 문서가 곧 제공될 것입니다.

 

출처 : http://ssmhz.tistory.com/154

위로 스크롤