천재태지의 세상 돌려보기

seoz.egloos.com

- About Me... - Enlightenment, EFL - 타이젠 Tizen



NDIS IM Passthru에서 패킷 수신 시, Fast Mutex가 안 되는 이유 └ WDM

오랜만에 WDM 관련 글을 쓰네요.
글 하나 쓰려면 시간이 많이 걸려서 쓸거리는 많지만, 막상 쓰기는 쉽지 않군요.
원래는 A to Z 형식으로 순서대로 작성하려고 했는데, 일단 쓰고 싶은 것 부터 쓸 생각입니다.
빈 자리는 나중에 채워넣도록 하겠습니다.

-----------------------------------------------------------

그럼, 오늘 알아볼 것은
NDIS IM Passthru 드라이버에서 패킷을 수신할 때, Fast Mutex를 사용하면 안 되는 이유입니다.
이 내용은 저의 지식을 바탕으로 추측한 것이므로, 정확하지 않을 수 있습니다.

여러 책과 WDK Document, 그리고 소스 코드 수정을 통하여 추측한 결과입니다.
정확한 원인을 아시는 분이 계시면 알려주셨으면 합니다.

결론부터 이야기 하면,
일반적으로 Device Driver 코드가 실행중일 때, IRQL은 PASSIVE Level 인데, (PASSIVE Level = 0)
Fast Mutex는 현재 IRQL Level을 APC Level 로 상승 시킵니다. (APC Level = 1)
그런데, 패킷을 수신할 때는 IRQL Level이 DPC Level로 코드가 실행됩니다. (DPC Level = 2)
패킷 수신 시, IRQL Level이DPCLevel인 상태에서 Fast Mutex를 획득하게 되면,
IRQL Level이 APC Level로 떨어집니다.
이 때, 메모리 접근 오류가 발생하고 블루스크린이 뜨게됩니다.

그런데 패킷 송신 시에는 IRQL Level이 DPC Level인 경우가 있고, PASSIVE Level인 경우가 있습니다.
그래서 패킷을 송신한다고 바로 오류가 발생하는 것은 아니나 결국엔 오류가 발생하게 됩니다.

물론, 이 원인이 맞는지는 정확하지는 않지만 원인이 궁금하여 추측해보았습니다.


0. 용어 정리


여기까지 읽으셨다면 WDM에 대해서 어느정도 아는 분일텐데,
혹시나 하는 마음에 필요한 용어를 간단히 정리해 봤습니다.

- NDIS(Network Driver Interface Specification) : 네트워크 드라이버 계층 사이의 인터페이스 드라이버
- Passthru : NDIS 드라이버 중 InterMediate Driver 예제
- IRQL(Interrup Request Level) : Windows에서 정의한 논리적 의미의 인터럽트 레벨
- Fast Mutex : 동기화(synchronization) 방법의 하나로 fast mutex object를 사용


1. 궁금증 발생

Passthru의 패킷 수신 루틴(PtReceive(), PtReceivePacket())에서 로그를 남기기 위해,
Filemon의 LogRecord() 루틴을 참고하여, 로그 루틴을 작성했으나, 오류가 발생했습니다.

Fast Mutex를 획득하기 위해 위와 같이 ExAcquireFastMutex() 함수를 사용했습니다.

WinDbg에서 확인해보니, 블루 스크린이 뜰 때, 위와 같은 에러가 나더군요.
에러 번호 0x000000d1 입니다.

!analyze -v 를 이용해서 더 자세한 정보를 살펴봤습니다.
DRIVER_IRQL_NOT_LESS_OR_EQUAL 이라는 에러 메시지입니다.
IRQL이 높은 곳에서 페이징 가능한 메모리에 접근했다는 군요.
페이징이 가능한 메모리는 IRQL이 높지 않은 곳에서 접근해야 하는 모양입니다.

그런데 여러번 실행해보니 위와 같은 오류가 발생할 때도 있고,
에러번호 0x000000b8ATTEMPTED_SWITCH_FROM_DPC 라는 오류가 나는 경우도 있었습니다.
DPC 루틴에서 wait operation 혹은 attach process 혹은 yield 가 발생했다는군요.

IRQL이랑 Virtual Machine에 대해서 좀 더 공부해 둘 껄 하는 후회가 듭니다.
하지만, 지금 천천히 공부할 여유는 안 되니 일단 다음 기회로... ㅠ.ㅠ

위와 같이 발생하는 오류가 하나인 것도 아니고, 오류 발생 지점도 계속 바뀌더군요.
게다가 한번에 수정한 코드가 워낙 많아서 어려움 끝에, 
Fast Mutex가 문제였음을 알고 Spin Lock으로 바꾸었더니 잘 되더군요.
그런데 패킷 수신 시, 로그를 남기는 부분 이외에 다른 부분에서 Fast Mutex를 사용하면 잘 됩니다 -_-;
물론 Fast Mutex 초기화도 다 했는데 그러더군요.
그래서 수사는 더욱 미궁으로 빠졌었습니다.


2. 오류 메시지 분석

블루스크린이 뜨면서 발생한 오류 메시지를 찬찬히 들여다보니,
분명히 IRQL이 문제이고 현재 IRQL이 DPC Level인데,
Fast Mutex를 썼기 때문에 DPC Level 보다 낮은 수준의 메모리를 접근하게 되는것 같았습니다.

여기서 잠깐.

디바이스 드라이버는 평소에 IRQL 레벨이 PASSIVE Level (=0) 인데,
Fast Mutex를 얻기 위해, ExAcquireFastMutex() 함수를 사용하면
현재 IRQL 레벨을 APC Level (=1)로 상승시켜 줍니다.

여기까지 보면 DPC Level은 아무 상관도 없습니다.
그런데 오류메시지에 언급된걸 보면 무슨 관계가 있나 봅니다.

그래서 위와 같이 PtReceive() 함수에서 IRQL을 확인하기 위해,
KeGetCurrentIrql() 함수를사용해보았습니다.

확인해보니, 처음 IRQL은 2 이고, ExAcquireFastMutex() 함수를 호출한 다음의 IRQL은 1 이더군요.
아하!
패킷을 수신했을 때에는 평소와 달리 IRQL이 2 이군요. 즉, DPC Level 이라는 겁니다.
현재 IRQL 이 DPC Level 인 상태에서 IRQL 을 APC Level 로 낮추는게 문제가 된 것 같습니다.
그래서 다른 루틴에서는 Fast Mutex가 잘 돌아가도 패킷 수신 시에는 잘 안 되는 거였군요.


평소같으면 ExAcquireFastMutex() 함수는 IRQL Level 을 PASSIVE Level (=0) 에서 APC Level (=1) 로 상승시켜 주는데, 패킷 수신 시에는 반대로 IRQL Level 이 DPC Level (=2) 에서 APC Level (=1) 로 내려갑니다.

이로 인해서 커널 내부에서 어찌어찌한 문제가 발생하게 되겠지만,
그 부분까지는 자세히 모르겠습니다.

아무튼 이렇게 원인을 찾았습니다.


3. 해결책

자, 그럼 이제 어떻게 할까요.
그럼 패킷을 수신할 때는 동기화를 하지 말라?
물론 아니겠죠.

이 경우 두 가지 방법이 있습니다.

첫번째, Spin Lock 을 사용하면 됩니다.

Spin Lock 은 IRQL Level 을 DPC Level (=2) 로 상승시켜줍니다.
패킷 수신 시 IRQL Level 이 DPC Level 이었으므로, 다시 DPC Level 로 변경되어도 문제가 없습니다.
실제로, passthru 에서는 NdisAcquireSpinLock() 이라는 함수를 사용하고 있었습니다.
그런데 NdisAcquireFastMutex() 라는 함수가 없는걸 봐서는,
NDIS 에서는 그냥 Spin Lock 을 쓰라는 말 같습니다.

참고로, NDIS 에서 Spin Lock 을 사용하기 위해 다음 과정이 필요합니다.

NDIS_SPIN_LOCK  GlobalLock;     // Spin Lock object 로 사용할 변수를 선언합니다.
NdisAllocateSpinLock(&GlobalLock);   // Spin Lock object 를 초기화합니다.
NdisAcquireSpinLock(&GlobalLock);   // Spin Lock object 를 획득합니다.
NdisReleaseSpinLock(&GlobalLock);  // Spin Lock object 를 해제합니다.

두번째, ExAcquireFastMutexUnsafe() 함수를 사용하면 됩니다.
이 함수는 IRQL Level 을 변경하지 않고 Fast Mutex 를 사용하게 해줍니다.
그런데 unsafe 라는 말이 있어서 그런지, 사용하기에 뭔가 찝찝합니다.
참고로 이 함수를 사용한 후에는 ExReleaseFastMutexUnsafe() 함수를 사용해서
Mutex Object 를 해제해야 합니다.

자, 이렇게 NDIS Passthru 에서 패킷 수신 시에 Fast Mutex 를 썼을 때 발생하는 문제에 대해서 알아보고, 해결책에 대해서도 알아봤습니다.
물론, 정확한 것은 아니지만 정황상 저의 추측을 바탕으로 글을 작성했습니다.
이번 사건을 계기로 Virtual Memory와 IRQL에 대해서 더 공부해야 겠다는 생각을 했습니다.

이번달에 Coder's Oasis에서 디바이스 드라이버 세미나를 한다고 하던데, 아직 공지가 안 떴네요.
시간이 되면 꼭 가보고 싶습니다.
그리고 역시 이번달에 하제 소프트이봉석 대표이사님이 디바이스 드라이버 책을 출판한다고 하시던데, 나오면 꼭 사서 보고 싶군요.
디바이스 드라이버는 정말 흥미로운 분야입니다.

그럼 오늘은 여기까지!


덧글

  • john6 2008/12/10 13:37 # 삭제 답글

    적절한 추측? ㅋㄷ

  • 천재태지서주영 2008/12/11 08:06 #

    답을 알고 싶은데 말이야 ㅎㅎ
  • vbdream 2008/12/16 23:24 # 삭제 답글

    안녕하세요...^^ 처음 뵙겠습니다~~ ㅋㅋ

    재미있는 글 잘 보고 갑니다~~

    작성하신 글을 읽어보니 IRQL 에 대한 문제인것 같다는 생각도 드네요 ㅋㅋ

    제가 알기로는 Windows 2K3 때는 이런 단점을 보완하기 위해 Guarded Mutex 라는게 추가됐던데~

    이 놈도 Unsafe 함수처럼 IRQL을 바꾸지 않더라구요~~ 혹시 이거랑 관련이 있을까요??
  • 천재태지서주영 2008/12/17 13:09 #

    앗! vbdream 님 안녕하세요?
    좋은 정보 감사합니다 ^^ Guarded Mutex라는게 있군요.
    한번 써보려고 했는데, 함수가 선언이 안 되어 있다고 하네요 -_-;
    헤더 파일도 다 포함시키고, WDK도 최신 버전을 사용했는데 뭐가 문제인지...
    더 살펴봐야겠네요.
    암튼 반갑습니다 ^^
  • 한용현 2009/06/11 14:54 # 삭제 답글

    안녕하세요.
    저는 한국기술교육대학교 인터넷미디어 공학부에 다니는 4학년 학생입니다.
    사실은 제가 NDIS를 이용한 패킷 차단 프로그램을 만드려하는데
    이론적인 개념 위주로 계속 보다가, 실제로 드라이브를 만드려하는데
    실제로 개발환경조차도 제대로 못하고 있어 좌절하고 있는 학생입니다.
    다름이 아니라, 제가 조언을 구할 수 있을까 해서 이렇게 글을 남깁니다.

    어디 다른 곳 세미나를 가도, 확실한 답을 구할 수도 없고,
    일단 기본적인 환경이라도 구현해서 제가 하고자 하는 것을 만들고 싶은데
    제가 컴퓨터를 전공하긴 해도, 실제 단순 응용프로그램만 만든터라
    너무 낯설어 너무 당황하는 것 같습니다.

    조언을 해주시면, 정말 정말 감사할것 같습니다.
    그럼 좋은하루되세요.
  • 승네군 2010/10/19 00:19 # 답글

    어려운 내용이네요.
    결국 irq level 문제.. 후..
    개발자 입장에서는 wdm 도움말에 나와있는 함수별 irql레벨을 잘 맞춰서 작업해 주는 수 밖에 없는거죠 뭐...;;
  • 천재태지서주영 2010/10/20 00:32 #

    네... 문서를 참고하는 것이 거의 대부분이고,
    나머지는 경험에 의존해야 하는 현실입니다.
    그래도 노하우를 담은 책이 간간히 나오긴 하는것 같아요.
댓글 입력 영역