[WinAPI] DllMain() 주의점 그리고 DONT_RESOLVE_DLL_REFERENCES
출처 : http://isecurity-textcube.blogspot.com/2010/04/dll%EC%9D%98-%EA%B3%A0%EA%B8%89%EA%B8%B0%EB%B2%95.html
또한 전역이나 정적으로 선언된 C++오브젝트를 생성하는 것도 이와 유사한 문제를 야기할 수 있음을 알아두어야 한다. 왜냐하면 이러한 오브젝트들의 생성자와 파괴자가 우리가 작성한 DllMain()함수가 호출되는 시점에 동시에 수행될 수도 있기 때문이다.
- DONT_RESOLVE_DLL_REFERENCES
DONT_RESOLVE_DLL_REFERENCES 플래그를 사용하면 시스템은 DLL파일이미지가 매핑되는 작업까지만 수행하고 DllMain()함수는 호출하지 않는다. 또한 DLL 파일은 다른 DLL이 포함하고 있는 함수들을 임포트하기도 하는데, 시스템이 DLL을 프로세스 주소공간에 매핑할 때 다른 DLL이 필요한지를 확인하여 자동적으로 이러한 dll들을 로드해준다. 이 플래그를 사용하면 매핑할 DLL이 필요로 하는 추가적인 DLL을 프로세스의 주소공간에 자동으로 로드하지 않는다(DONT_RESOLVE_DLL_REFERENCES가 지정이 되면 매핑만 하고 DllMain() 호출하지 않고, 해당 DLL과 연관되어 있는 다른 DLL들을 자동으로 로드해주지 않는 특성을 갖고 있다)
DLL이 익스포트하고 있는 함수들은 내부적인 자료구조가 완전히 초기화되고 추가적으로 필요로 하는 DLL파일들이 로드되기 전까지는 호출될 수 없다. 따라서 가능하면 이플래그는 사용하지 않는 것이 좋다.잘못만들어진 플래그라고 한다(레이몬드첸의 블로그에서 확인, http://blogs.msdn.com/oldnewthing/archive/2005/02/14/372266.aspx)로 부터 "LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES)는 완전히 잘못만들어졌다"는 글을 살펴보기 바란다
DLL이 리소스만 가지고 있고 어떤 함수도 가지고 있지 않아서 DLL파일 이미지를 프로세스의 주소공간에 매핑하기만 하면 되는 경우에 유용하다. 일반적으로 exe파일은 새로운 프로세스를 수행하기 위해 로딩 되지만 LoadLibraryEx()함수를 이플래그와 함께 사용하면 exe파일 이미지를 프로세스의 주소공간에 단순매핑만 한다. 이렇게 exe파일을 매핑한 후 반환된 HMODULE을 이용하면 exe파일내에 포함되어 있는 리소스에 접근할 수 있다. exe 파일은 DllMain()을 가지고 있지 않기 때문에 LoadLibraryEx()함수를 이용하여 exe파일을 매핑하려는 경우에는 반드시 LOAD_LIBRARY_AS_DATAFILE 플래그를 사용해야 한다.(LoadLibraryEx로 EXE 파일을 로드하는 경우를 설명)
DllMain의 매개변수 와 주의점들
hInstDll
DLL의 인스턴스 핸들 값, DLL 파일 이미지가 가상 주소공간의 어디로 매핑되었는지를 알려주는 가상 메모리 주소값이다. 보통의 경우 이 값을 전역변수에 저장해 두었다가 DialogBoz나 LoadString과 같이 리소스를 로드해야 하는 함수들을 호출할때 사용하는 것이 일반적이다.
fImpLoad
DLL이 암시적으로 로드된 경우에는 0이 아닌 값이 전달되고, DLL이 명시적으로 로드된 경우에는 0이 전달된다.
fdwReason
시스템이 이 함수를 호출한 이유를 나타내는 값이 전달된다. 이 이 매개변수로는 DLL_PROCESS_ATTACH,DLL_PROCESS_DETACH,DLL_THREAD_ATTACH,DLL_THREAD_DETACH 값이 올 수 있다.
DLL은 자기 자신을 초기화 하기 위해 DllMain()함수를 사용한다는 것을 기억하라.
DLL이 익스포트하고 있는 함수들은 내부적인 자료구조가 완전히 초기화되고 추가적으로 필요로 하는 DLL파일들이 로드되기 전까지는 호출될 수 없다. 따라서 가능하면 이플래그는 사용하지 않는 것이 좋다.잘못만들어진 플래그라고 한다(레이몬드첸의 블로그에서 확인, http://blogs.msdn.com/oldnewthing/archive/2005/02/14/372266.aspx)로 부터 "LoadLibraryEx(DONT_RESOLVE_DLL_REFERENCES)는 완전히 잘못만들어졌다"는 글을 살펴보기 바란다
- LOAD_LIBRARY_AS_DATAFILE
DLL이 리소스만 가지고 있고 어떤 함수도 가지고 있지 않아서 DLL파일 이미지를 프로세스의 주소공간에 매핑하기만 하면 되는 경우에 유용하다. 일반적으로 exe파일은 새로운 프로세스를 수행하기 위해 로딩 되지만 LoadLibraryEx()함수를 이플래그와 함께 사용하면 exe파일 이미지를 프로세스의 주소공간에 단순매핑만 한다. 이렇게 exe파일을 매핑한 후 반환된 HMODULE을 이용하면 exe파일내에 포함되어 있는 리소스에 접근할 수 있다. exe 파일은 DllMain()을 가지고 있지 않기 때문에 LoadLibraryEx()함수를 이용하여 exe파일을 매핑하려는 경우에는 반드시 LOAD_LIBRARY_AS_DATAFILE 플래그를 사용해야 한다.(LoadLibraryEx로 EXE 파일을 로드하는 경우를 설명)
- LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
- LOAD_LIBRARY_AS_IMAGE_RESOURCE
DllMain의 매개변수 와 주의점들
hInstDll
DLL의 인스턴스 핸들 값, DLL 파일 이미지가 가상 주소공간의 어디로 매핑되었는지를 알려주는 가상 메모리 주소값이다. 보통의 경우 이 값을 전역변수에 저장해 두었다가 DialogBoz나 LoadString과 같이 리소스를 로드해야 하는 함수들을 호출할때 사용하는 것이 일반적이다.
fImpLoad
DLL이 암시적으로 로드된 경우에는 0이 아닌 값이 전달되고, DLL이 명시적으로 로드된 경우에는 0이 전달된다.
fdwReason
시스템이 이 함수를 호출한 이유를 나타내는 값이 전달된다. 이 이 매개변수로는 DLL_PROCESS_ATTACH,DLL_PROCESS_DETACH,DLL_THREAD_ATTACH,DLL_THREAD_DETACH 값이 올 수 있다.
DLL은 자기 자신을 초기화 하기 위해 DllMain()함수를 사용한다는 것을 기억하라.
아래의 문구는 유념해서 기억 또 기억할 것!!
특정 DLL에 대해 DllMain()함수가 호출된 시점에 동일 주소공간에 로드된 다른 DLL들은 자신의 DllMain()함수를 미처 호출하지 못했을 수도 있다. 다른 DLL들은 아직 초기화 되지 않은 상태일 수도 있으므로 DllMain()함수에서 다른 DLL이 익스포트하고 있는 함수를 호출해서는 안된다. 또한 DllMain() 내에서는 LoadLibrary(Ex)나 FreeLibrary와 같은 함수를 호출해서도 안되는데 만일 이러한 함수를 호출하면 여러 DLL들 사이에 의존관계 루프가 생길 수 있다.
특정 DLL에 대해 DllMain()함수가 호출된 시점에 동일 주소공간에 로드된 다른 DLL들은 자신의 DllMain()함수를 미처 호출하지 못했을 수도 있다. 다른 DLL들은 아직 초기화 되지 않은 상태일 수도 있으므로 DllMain()함수에서 다른 DLL이 익스포트하고 있는 함수를 호출해서는 안된다. 또한 DllMain() 내에서는 LoadLibrary(Ex)나 FreeLibrary와 같은 함수를 호출해서도 안되는데 만일 이러한 함수를 호출하면 여러 DLL들 사이에 의존관계 루프가 생길 수 있다.
플랫폼 SDK문서에는 DllMain()함수를 TLS,커널오브젝트생성,파일열기 작업 수행 등의 초기화를 위해서만 활용할 것을 명기하고 있다.
또한 User, Shell, ODBC, COM, RPC, 소켓함수 등을 호출해서는 안된다. 왜냐하면 이러한 함수들을 호출하였을때
해당 함수들의 기능을 구현하고 있는 DLL들이 아직 초기화되지 못했을 수도 있고, 해당 기능을 수행하는 함수들이 내부적으로
LoadLibrary(Ex) 함수를 호출하는 경우 의존 관계 루프가 생길수도 있기 때문이다.
또한 전역이나 정적으로 선언된 C++오브젝트를 생성하는 것도 이와 유사한 문제를 야기할 수 있음을 알아두어야 한다. 왜냐하면 이러한 오브젝트들의 생성자와 파괴자가 우리가 작성한 DllMain()함수가 호출되는 시점에 동시에 수행될 수도 있기 때문이다.
프로세스 이미지가 실행이 될때의 순서도
신규
프로세스 생성 -> 프로세스 주소 공간 생성 -> EXE 파일 & 모든 Dll 파일 주소공간에 매핑 ->
메인스레드 생성 -> 메인 스레드가 모드 Dll에 대해서 DLL_PROCESS_ATTACH값으로 DllMain 호출
-> 모든 Dll에서 해당 통지 메시지에 대해서 정상회신하면 -> 메인 스레드의 시작은 C/C++ 런타임 시작 코드를
가리키도록 설정
위 과정 이후에 특정 스레드가 dll을 로드할때의 순서도
10번
스레드가 LoadLibraryEx를 호출 -> 해당 dll이 주소 공간에 매핑 -> 10번 스레드가 dll의
DllMain()함수를 DLL_ATTACH_PROCESS 인자값으로 호출 -> LoadLibraryEx 반환
위 과정 이후에 특정 스레드가 생성될때의 순서도
11번
스레드가 생성됨(CreateThread) -> 11번 스레드는 현재 프로세스 공간에 매핑되어 있는 모든 Dll들에 대해서
DllMain(DLL_THREAD_ATTACH) 호출 -> 모든 dll에 대해서 DllMain 호출이 반환되고 나서
-> 11번 스레드가 재개
만약 11번 스레드가 생성된 이후에 특정 dll이 로드되면
11번 스레드는 DllMain(DLL_THREAD_ATTACH)를 받지 못한다.
a.dll의 DLLMain은 순차적으로 호출이 된다는 점을 유념
a.dll이 매핑이 되어 있고 -> 1번/2번 스레드가 각각 21번/22번 스레드를 생성 ->
21번
스레드는 a.dll의 DllMain(DLL_THREAD_ATTACH) 호출 -> 동시에 22번 스레드도 a.dll의
DllMain(DLL_TRHEAD_ATTACH) 호출 -> 여기서 시스템이 관여를 하는데 -> 21번 스레드의
a.dll의 DllMain() 의 호출이 완료되기까지 22번 스레드를 일시 정지 -> 21번 스레드의 DllMain() 호출
완료가 되면 22번 스레드를 resume시킨다.
댓글
댓글 쓰기