DllMain 사용, DllMainCRTStartup , DllMain , fdwReason , DLL_PROCESS_ATTACH , DLL_PROCESS_DETACH

반응형

[DllMain 의 사용]

IMAGE_OPTIONAL_HEADER에서 AddressOfEntryPoint 필드에 해당하는 값이 
독자가 확인결과 0x00001230 였다. 그곳은 책에서 말하기로는
DllMainCRTStartup 함수에 대한 진입점 이라고 한다. 
독자가 역어셈블리프로그램을 통해서 프로그램 시작부분을 봤는데 책의 설명대로
프로그램의 시작 부분이었다.하지만 DllMainCRTStartup 이라는 이름은 보이지 않고
그냥 함수 시작이라고 나오는데 솔직히 좀 애매모했다. 
확실한건 이부분이 프로그램의 첫실행부분이라는 점이다.
exe파일의 경우 WinMainCRTStartup 이나 mainCRTStartup의 진입점이고
프로그램이 WinMain이나 main을 호출한다.  하지만 우리가 만든
DLL의 경우는 DllMainCRTStartup의 진입점이라고 했는데
그렇다면 DllMainCRTStartup는 무슨 역할을 할가??

예상대로 DllMain을 호출한다. 
DllMain은 WinMain 함수처럼 Dll엔트리 포인트 함수로
불린다. 즉 DLL 로드 시 제일 먼저 호출되는 함수이다.  
만약 dll에서 DllMain 함수를 쓰는 경우는
 아래와같다.


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

BOOL WINAPI DllMain(HINSTANCE hMoudle, DWORD dwReason, LPVOID)
{
 switch(dwReason)
 {
  case DLL_PROCESS_ATTACH: break;
  case DLL_THREAD_ATTACH: break;
  case DLL_THREAD_DETHACH: break;
  case DLL_PROCESS_DETACH: break;
  default :break;
 }

 return TRUE;
}

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

하지만 독자가 (및 책에서) 에서는 DllMain을 전혀 정의하지 않았는데
사실 DllMain을 정의하지 않더라고 DLL의 생성에는 하등 지장이 없다. 

정의하지도 않은 DLL스타트업 루틴이 왜 들어가는것일까??
DllMain을 구현하지 않으면 컴파일러가 미리 자동으로 구현해둔 DllMain 더미를 코드에 삽입한다고 한다.

 crt0.c가 있는 폴더를 살펴보면 dllcrt0.c 라는 소스 파일이 들어 있을 것인데 여기서
DllMainCRTStartup가 정의되어 있다.   

(하지만 독자는 crt0.c 파일을 찾을 수가 없었다... 그리고 이상한 점은
AddressOfEntryPoint  가 가리키는 곳으로 가면 E9 xx xx xx xx   라는 코드가 있고
E9 xx xx xx xx  으로 해당 하는 곳으로 가면 그 때 Dll 메인 스타트업 코드가 있다고 했는데 
자는  의 경우는 AddressOfEntryPoint  가 가리키는 곳으로 가면  바로 Dll 메인 스타트업 코드가 있었다.

E9 xx xx xx xx (jmp xx xx xx xx xx) 가 없었다.
책에서는 DllMainCRTStartup 코드가 dllcrt0.c 에 구현되어있고 ( 컴파일시 포함되며)
책에서는 DllMain 코드가 DllMain.c 에 구현되어있다고 (컴파일시 포함) 한다.
하지만독자는 해당  dllcrt0.c 파일과 DllMain.c 파일을 찾을 수 없었으며 (독자가 잘못 찾은것 일수도 있지만 -_-ㅋ)

 내용을 추척하는과정에서 조금 달랐다.. 
요점은 DllMain 부분을 별도로 정의하지 않았다면 컴파일러는 이 더미 DllMain 아무것도 하지 않고 TRUE를 
리턴하는 (DllMain.c에있는코드)  
코드가 자동으로 컴파일하면서 끼워들어가게 된다 그래서 여러분은 굳이 DllMain을 정의할 필요가 없게 된다고 한다.
(하지만 독자의 경우 Dll 로드시 특별한 초기화가 작업을 할때 DllMain을 직접 구현할 필요가 있던적이 있었다. ) 
---------------------------------------------------------------------------------------------------- 

이제부터 DllMain이 언제, 왜 호출되는지에 대하여 알아보자.
DllMain의 정의는 아래와 같다.

-------------------------- DllMain의 원형 -------------------------------

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);

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

1. HINSTANCE hinstDll
   - DLL이 가상주소에 매핑되었을 때의 핸들이다. WinMain의 hInstance 와 동일한 셩격을 갖고있다.
   - IMAGE_OPTIONAL_HEADER 의 ImageBase 필드에 지정된 값이다.  보통 디폴트로 0x10000000 번지에 로드된다.

2. DWORD fdwReason
   - DllMain 이 호출한 이유를 나타낸다.
   - fdwReason 의 값으로는
     -> DLL_PROCESS_ATTACH : DLL이 프로세스의 주소 공간에 최초로 매핑됬을때

     -> DLL_PROCESS_DETACH : DLL이 프로세스의 주소 공간으로 부터 해제될 때

     -> DLL_THREAD_ATTACH : 어떤 프로세스 내에서 스레드가 생성될 때 해당 프로세스 주소공간에 매핑되어 있는 모든 DLL의 파일이미지검사하여 DLL_THREAD_ATTACH 로 호출

     -> DLL_THREAD_DETACH : 스레드 엔트리 포인트 함수로부터 리턴될때 시스템은 ExitThread를 호출한다 이때 스레드를 바로 없애지 않고 매핑되어 있는 모든 DLL의 DllMain에 DLL_THREAD_DETACH를 호출

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

DllMain을 정의할 때 주의할 점이 있는데 DllMain을 호출할때 동기화를 맞추어야 한다.

예를 들어 스레드가 생성될 때 DLL_THREAD_ATTACH 를 매개 변수로 하여 DllMain이 호출된다. 이때 다른 스레드가 종료되는 상황이라면

DllMain이 동시에 두 개의 스레드에 대하여 호출되는 상황이 발생할 것앋. 이 경우 동기화 문제를 고려할 필요가 없도록 시스템이 알아서

앞에 호출된 DllMain의 실행이 끝날 때까지 DllMain의 실행을 대기시키게 된다. 이런 상황 때문에 DllMain 내에서 대기 함수(WaitForXXX 함수군들)를 호출할 때 신중을 기해야 한다.

아니 이런 상황이 발생하지 않도록 코딩해야 한다.  제프리 책 Programming Applications for Microsoft Windows 2000에 이런 상황이 발생할 수 있는 문제점에 대한 예제가 나오는데

확인해보자

------------------------------------- 데드락이 발생하는 DllMain의 예) ----------------------------------------

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID lpvReserved)
{
 HWND hThread;
 DWOIRD dwThreadld;

 switch(fdwReason)
 {
 case DLL_PROCESS_ATTACH:
 hThred = CreateThread(NULL,0,SomeFunction,NULL,0,&dwThreadld); // 스레드하나 생성
 
  WaitForSingleObject(hThread, INFINITE);  // 스레드가 끝날때 까지 DllMain 실행을 대기 시킨다.

  CloseHandle(hThread);
 break;

 case DLL_PROCESS_DETACH:
 break;

 case DLL_THREAD_ATTACH:
 break;

 case DLL_THREAD_DETACH:
 break;

 }

}

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

위 코드를 보면 Dll 로드시에 호출되는 DllMain에서 스레드를 하나 생성하였다.

이 스레드가 종료할때까지 메인 스레드를 대기하는 코드다

이 프로그램은 데드락 상태에 빠지게 되는데  메인스레드는 SomeFuction을 실행하는 스레드를 생성키기고

WaitForSingleObject 를 통해 SomeFuction 의 실행이 종료될 때까지 대기한다.

하지만 여기서 문제가 되는것은 스레드 생성시에도 DLL_THREAD_ATTACHG를 매개변수로 하여 DllMain이 호출된다는 것이다.

그리고 앞서 언급한 것처럼 이미 DLL_PROCESS_ATTACH를 매개 변수로 하여 메인 스레드에 의해 DllMain이 호출 중이기 때문에

시스템은 메인 스레드가 실행 중인 DllMain 실행이 끝날때까지 SomFunction을 실행하는 스레드를 위한

DllMain 실행을 대기시킨다. 이 상황, 즉 메인 스레드에 의한 DllMain 실행은 SomeFunction 을 실행하는 스레드가 종료되기를 기다리고 있으며

SomeFuction 을 실행할 스레드는 SomeFunction을 실행하기 전에 DllMain을 호출하기 위하여 메인 스레드에 의한 DllMain의 호출이 끝나기를 기다리고 있다.

서로가 서로를 끝나기만 기다리고 있는 것이다. 이런 실수를 범하지 않도록 DllMain정의 시에 신중을 기해야 한다.

 

TAGS.

Comments