디바이스 드라이버를 만들 때 우리는 DPC루틴을 많이 사용합니다.
interrupt가 발생했을 때, 또는 KeSetTimerEx를 사용해 일정 시간마다 모듈을 호출할 때 .. 등등이 있죠.
저도 많은 경우 사용해 보질 않아서 예를 많이 들진 못하겠네요.
이 때 DPC루틴 호출을 할 때 DPC 루틴을 DPC 큐에 넣게 되는데 이러한 DPC 루틴의 종류는 2가지가 있습니다.
1. CustomDpc
이 Dpc루틴은 KDEFERRED_ROUTINE 형태를 따라야 합니다.
VOID CustomDpc(
__in struct _KDPC *Dpc,
__in_opt PVOID DeferredContext,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2
)
{ ... }
이런식으로 만들어 주면 되죠.
이 DPC 루틴을 사용할 때는 KeInitializeDpc, KeInsertQueueDpc 이 2가지 함수를 사용하게 됩니다.
VOID KeInitializeDpc(
__out PRKDPC Dpc,
__in PKDEFERRED_ROUTINE DeferredRoutine,
__in_opt PVOID DeferredContext
);
Dpc 오브젝트를 초기화 해주는 함수입니다.
BOOLEAN KeInsertQueueDpc(
__inout PRKDPC Dpc,
__in_opt PVOID SystemArgument1,
__in_opt PVOID SystemArgument2
);
초기화된 Dpc 오브젝트 안에 있는 CustomDpc를 요청하는 함수입니다.
함수 이름만 봐도 CustomDpc를 DPC큐에 넣는 작업을 하는 함수인 것을 알수 있습니다.
이 CustomDpc는 커널이 제공하는 함수이고 일반적으로 사용되는 DPC 루틴입니다.
2. DpcForIsr
이 Dpc루틴은 IO_DPC_ROUTINE 형태를 따라야 합니다.
VOID DpcForIsr(
__in PKDPC Dpc,
__in struct _DEVICE_OBJECT *DeviceObject,
__in struct _IRP *Irp,
__in_opt PVOID Context
)
{ ... }
위와 같이 되죠.
이 DPC 루틴을 사용할 때는 IoInitializeDpcRequest, IoRequestDpc 두 함수를 사용하게 됩니다.
앞에 Io 붙은걸로봐서 해당 함수들은 IO관리자가 제공하는 함수라는 것을 알수 있습니다.
VOID IoInitializeDpcRequest(
__in PDEVICE_OBJECT DeviceObject,
__in PIO_DPC_ROUTINE DpcRoutine
);
Dpc루틴을 호출하기 위한 초기화 작업
VOID IoRequestDpc(
__in PDEVICE_OBJECT DeviceObject,
__in PIRP Irp,
__in PVOID Context
);
초기화된 Dpc루틴을 Dpc큐에 넣는다.
이 DPC루틴의 사용 용도는 이렇습니다.
보통 하드웨어에서 CPU에게 처리를 요구할 때 interrupt라는 것을 보냅니다.
이 때 interrupt를 받은 CPU는 현재 작업 중인 것을 중단하고 해당 interrupt를 처리하게 됩니다.
보통 하드웨어 interrupt의 IRQL은 3 이상이므로 스케쥴러보다 더 우선권을 가지고 있습니다.
그러면 어느정도 interrupt도 양심이 있으면 최대한 빨리 자기 할 것만 하고 다시 CPU를 반납해야 합니다.
그렇지 않으면 아마 해당 interrupt가 발생할 때마다 컴퓨터가 버벅 댈 것입니다.
그렇기 때문에 해당 interrupt가 발생했을 때 정말 그 시기 꼭 해야하는 작업이 아니면
DPC루틴을 만들어 해당 DPC루틴을 DPC큐에 넣어버립니다. 이러면 모든 하드웨어 interrupt 처리가 끝났을 때
DPC 처리기가 DPC큐에 있는 루틴들을 처리 할 것입니다.
이 때 사용되는 루틴이 바로 DpcForIsr 입니다.
참고로 IoInitializeDpcRequest 함수는 PASSIVE_LEVEL에서 호출되어야 하며 IoRequestDpc 함수는 DIRQL에서 호출되어야 합니다. DIRQL은 IRQL 0~31 중에서 3~26 값을 뜻합니다. 즉, 하드웨어 인터럽트 루틴에서 사용해야 된다는 말입니다.
또한 DPC는 2번 이상 요청이 이뤄질 수 없습니다. 즉, IoRequestDpc함수가 호출 됬을 때 DPC큐에 들어간 DPC 루틴이 처리가 안된 상황에서 IoRequestDpc함수가 또 호출 될 경우 나중에 등록된 DPC루틴은 유효하지가 않습니다.
아무튼 이 경우에 사용되는 루틴이지만 다른 경우에도 사용될것 같습니다. 이 부분은 잘 모르므로 패스... =_=;
보통 IDT후킹을 해 키로거를 만드는 경우 DpcForIsr을 필히 사용해야 합니다. 그렇지 않을 경우..!
키보드로 글씨를 쓰면 컴퓨터가 버벅대는 현상을 볼 수 있습니다. :)
웃긴건 IoInitializeDpcRequest 함수 내부에서는 KeInitializeDpc 함수를 사용하고 있고 IoRequestDpc 메크로 내부에서도 KeInsertQueueDpc 함수를 사용하고 있습니다.
추가적으로 PS/2 키로거를 대충 만들어 보고 있는데 IDT 후킹을 한 후 후킹된 함수 내부로 들어오게 되면 그 때 IRQL은 2 값을 가지게 됩니다. DISPATCH_LEVEL인거죠. 보통 거기서 IoRequestDpc 함수를 호출하는데 IoRequestDpc함수는 DIRQL에서 호출되어야 안정되게 작동합니다. 싱글 코어 기반 환경에서는 무리 없이 잘 돌아가지만 듀얼 코어 이상 즉, SMP환경에서 IoRequestDpc함수를 호출하게 되면 일정 확율로 IRQL_NOT_LESS_OR_EQUAL : BSOD가 뜨게 됩니다.
이 현상 때문에 키로거를 제대로 못 만들고 있습니다.
혹시 이에대한 해결방안이나 아이디어가 있으시다면 도움 좀 부탁드리겠습니다~ ^^;
감사합니다.
'My Study > Programming&Theory' 카테고리의 다른 글
Named Pipe 간단한 예제 (4) | 2010.12.24 |
---|---|
WorkItem Routine (0) | 2010.12.18 |
Hardware Manipulation ( Keyboard LED ) (0) | 2010.12.15 |
Driver에서 시스템 스레드 사용하기 ( 동기화 이벤트 오브젝트 ) (0) | 2010.12.14 |
프로그래밍 시 변수명 ( Hungarian notation ) (1) | 2010.12.10 |