구본탁
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 첫 번째 방법은 애플리케이션이 DLL 에 포함되어 있는 심벌을 단순 참조하는 경우이다.
• 애플리케이션이 수행되면 운영체제의 로더가 필요한 DLL을 묵시적으로 로드하고 링크 한다.
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 두 번째 방법은 애플리케이션이 필요한 DLL을 명시적으로 로드하도록 하는 것인데,
애플리케이션이 수행 중인 상황에서 필요한 심벌을 명시적으로 링크히는 방법을 말한다.
• 다르게 표현하면, 애플리케이션이 수행되고 있는 상황에서 특정 스레드가 DLL 내에 포함되어
있는 함수를 호출하기로 한 경우 프로세스의 주소 공간에 필요한 DLL 파일을 명시적으로
로드하여 DLL 내에 포함되어 있는 함수의 가상 메모리 주소를 획득한 후, 이 값을 이용하여
함수를 호출하는 것을 말한다. 이 기법의 매력은 모든 과정들이 애플리케이션이 수행 중인
상황에서 이루어진다는 것이다.
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 명시적인 DLL 모듈 로딩
프로세스 내의 스레드는 언제든지 다음의 두 가지 함수 중 하나를 호출하여
프로세스의 주소 공간에 DLL을 매핑할 수 있다.
flag : 707페이지 참고
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 명시적인 DLL 모듈 언로딩
프로세스 내의 스레드에서 더 이상 DLL 파일 내의 심벌을 사용할 필요가 없게 되면 FreeLibrary 함 수를
호출하여 프로세스의 주소 공간으로부터 DLL 파일을 명시적으로 언로드 할 수 있다
프로세스의 주소 공간으로부터 DLL 모듈을 언로드하기 위해 FreeLibraryAndExitThread 함수를 사용할
수도 있다.
이 함수는 Kerne132. dll 파일에 다음과 같이 구현되어 있다.
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 명시적인 DLL 모듈 언로딩
시스템은 DLL의 사용 카운트를 프로세스별로 유지한다. 즉, A 프로세스의 스레드가 다음과 같이 함수를 호출한
이후에 B 프로세스가 동일하게 함수를 호출하게 되면, MyLib.dll은 두 개의 프로세스 주소 공간에 각기 매핑된다.
이 경우 A 프로세스와 B 프로세스에 로드되어 있는 DLL의 사용 카운트는 모두 1로 유지된다.
B 프로세스의 스레드가 다음 함수를 호출하게 되면 B 프로세스에 로드된 DLL의 사용 카운트는 0이 될 것이다. 이
경우 B 프로세스의 주소 공간으로부터 이 DLL은 매핑 해제될 것이지만 A 프로세스 내 의 DLL은 어떠한 영향도
받지 않으며, 사용자 카운트는 계속해서로 유지된다.
스레드는 GetModuleHandle 함수를 호출하여 특정 DLL 파일이 프로세스 주소 공간에 매핑되어 있는지를
확인할 수 있다.
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 명시적인 DLL 모듈 언로딩
• 다음 코드는 프로세스의 주소 공간에 MyLib.dll 파일이 로드되어 있지 않은
경우에만 해당 DLL을 로드한다.
• GetModuleHandle 에 NULL 값을 전달하면 애플리케이션 실행 모듈에 대한 핸들을 얻을 수 있다.
• DLL 파일에 대한 HINSTANCE/HMODULE 값을 알고 있다면 GetModuleFileName 함수를 호출 하여
DLL(혹은 .exe) 에 대한 전체 경로명을 얻을 수 있다.
1. 명시적인 DLL 모듈 로딩과 심벌 링킹
• 익스포트된 심벌을 명시적으로 링킹하기
• 스레드가 DLL 모듈을 명시적으로 로드하였다면, 이제 GetProcAddress 함수를
호출하여 익스포트 된 심벌에 대한시작주소를 얻어 와야 한다.
• pszSymbolName 매개변수의 자료형이 PCTSTR 이 아니라 PCSTR.
• 이는 함수가 ANSI문자열만을 인자로 취함을 뜻함.
• 이는 컴파일러/링커가 심벌의 이름을 DLL의 익스포트 섹션에 항상 ANSI 문자열로 기록하기 때문이다.
2. DLL의 진입점 함수
• DLL은 하나의 진입점 함수를 가질 수 있다.
• 시스템은 여러 번에 걸쳐 진입점 함수를 호출해 준다.
• 입점 함수가 되는 이유는 정보를 제공하기 위한 목적도 있으며, DLL 이 프로세스별로 혹은 스레드별로
초기화를 수행하거나 정리할 목적으로 사용되기도 한다
• DLL을 구현할 때 진입점 함수를 반드시 구현해야 하는 것은 아니다.(ex 리소스만 가지고 있는 DLL)
2. DLL의 진입점 함수
1. DLL_PROCESS_ATTACH 통지
2. DLL_PROCESS_DETACH 통지
3. DLL_THREAD_ATTACH 통지
4. DLL_THREAD_DETACH 통지
2. DLL의 진입점 함수
1. DLL_PROCESS_ATTACH 통지
2. DLL의 진입점 함수
2. DLL_PROCESS_DETACH 통지
2. DLL의 진입점 함수
3. DLL_THREAD_ATTACH통지
• 프로세스 내에 새로운 스레드가 생성되면 시스템은 fdwReason 매개변수에 DLL_THREAD_ATTACH
값을 전달하여 현재 프로세스 주소 공간에 매핑되어 있는 모든 DLL의 DllMain 함수를 호출해 준다.
• 이러한 통지 과정을 통해 모든 DLL들은 스레드별로 초기화 과정을 수행할 수 있게 된다. 새롭게 생성된
스레드는 모든 DLL들의 DllMain 함수를 호출할 책임이 있다. 모든 DLL들이 이러한 통지를 전달받은
이후에야 비로소 시스템은 스레드가 자신의 스레드 함수를 수행할 수 있도록 해 준다.
• 프로세스 내에 이미 여러 개의 스레드들을 수행 중인 상태에서 새로운 DLL 이 주소 공간 내에 매핑되는
경우에는 기존 스레드들에 대해 DLL_THREAD_ATTACH를 인자로 DllMain 함수를 호출하지는 않으며,
DLL이 이미 프로세스의 주소 공간에 매핑된 상황에서 새로운 스레드가 만들어진 경우에만
DLL_TRHEAD_ATTACH를 인자로 DllMain함수를 호출해 준다.
• 또한 시스템은 프로세스의 주 스레드에 대해서는 DLL_TRHEAD_ATTACH 값을 전달하여 DllMain 함수를
호출하지 않는다는 점에 주의하기 바란다. 프로세스가 처음으로 시작되어서 DLL들이 프로세스의 주소 공간에
매핑될 때에는 DLL_THREAD_ATTACH 통지가 아니라 DLL_PROCESS_ATTACH 통지가 전달될 것 이다.
2. DLL의 진입점 함수
4. DLL_THREAD_DETACH 통지
• 스레드를 종료하는 가장 좋은 방법은 스레드 함수가 반환되도록 하는 것이다. 스레드 함수가
반환되면 스레드를 종료하기 위해 ExitThread 함수를 호출하게 되는데 이 함수는 시스템에게
스레드가 종료되길 원한다는 사실을 전달하게 된다.
하지만 시스템은 스레드를 이 함수가 호출되는 순간에 바로 죽이지는 않는다.
• 대신, 프로세스의 주소 공간에 매핑된 모든 DLL 의 DllMain 함수를 DLL_THREAD_DETACH 값을 인자로
하여 호출한 후에 스레드를 종료한다. 이러한 통지를 이용하여 DLL은 스레드 단위의 정리 작업을 수행할
수 있다.
• DLL 이 스레드가 종료되는 것을 방해할 수도 있다는 점에 주목하기 바란다. 예를 들어 DllMain 함수로
DLL_THREAD_DETACH 통지가 전달되었을 때 무한 루프로 진입하게 되면 스레드는 종료되지 못한다.
운영체제는 실제로 모든 DLL이 DLL_THREAD_DETACH 통지를 처리한 이후에야 비로소 스레드를 종료시킨다.
3. DLL의 지연 로딩
• 지연 로드 DLL은 암시적으로 링크되는 DLL 이지만 실제로 코드에서 해당 심벌을
참조하기 전까지는 로드가 수행되지 않는다.
일부 제한사항.
• 지연 로드 DLL은 필드를 익스포트할 수 없다 .
• LoadLibrary와 GetProcAddress를 호출해야 하기 때문에 Kernel32.dll 모듈은 지연 로드될 수
없다 .
• DIIMain 진입점 함수 내에서 지연 로드될 함수를 호출하게 되면 프로세스가 손상된다.
3. DLL의 지연 로딩
지연 로드 DLL을 작성하려면 먼저 일반적인 방식으로 DLL을 생성하면 된다.
실행 파일도 동일하게 일반적인 방식으로 생성하면 되지만 일부 링커 스위치를 수정하여
실행 파일을 다시 링크해야 한다.
아래에 반드시 추가되어야 하는 두 개의 링커 스위치를 나타냈다.
3. DLL의 지연 로딩
지연 로드되는 DLL들을 선택하려면 구성 속성 /
링커 / 입력 페이지에서 다음과 같이 설정하면
된다.
지연 로드 DLL 옵션은 구성 속성 / 링커 / 고급 페이지
에서 다음과 같이 설정하면 된다.
3. DLL의 지연 로딩
/Lib 스위치를 사용하면 링커는 __delayLoadHelper2 라는 특수한 함수를 실행 파일 내에
포함시킨다.
두 번째 스위치는 링커에게 다음과 같은 작업을 수행하도록 한다.
• 실행 모듈의 임포트 섹션으로부터 MyDll.dll 파일을 제거하여 운영체제가 프로세스를 초기화할 때 해당 DLL을
암시적으로 로드하지 못하게 한다.
• 실행 파일 내의 새로운 지연 로드 임포트 섹션을 포함시켜 어떤 함수들이 MyDll.dll 파일로부터 지연 임포트
되는지에 대한 정보를 추가한다.
• 지연 로드되는 함수를 호출하는 코드를 __delayLoadHelper2 함수를 호출하도록 변경한다.
애플리케이션이 수행되어 지연 로드된 함수를 호출하게 되면 __ delayLoadHelper2 함수가 실제로
호출된다.
이 함수는 지연 임포트 섹션을 참조하며, 해당함수를 호출하려면 LoadLibrary와 GetProcAddress를
호출해야 한다는 사실을 알 수 있다.
__ delayLoadHelper2 함수가 지연 로드된 함수의 메모리 주소를 획득하게 되면 다음번에 동일한 함수를
호출할 경우 직접 해당 함수를 호출할 수 있도록 함수 호출부를 수정한다.
3. DLL의 지연 로딩
일부 다른 문제에 대해 고민
보통 운영체제의 로더는 실행 파일을 로드하고 필요한 DLL들을 로드하려고 시도한다. 만일 필요한 DLL들 을
로드할 수 없으면 로더는 에러 메시지를 출력하지만 지연 로드된 DLL들은 초기화시에 DLL 의 존재유무가
확인되지 않는다. 만일 지연 로드된 함수가 호출되었을 때 해당 함수를 익스포트하고 있는 DLL 이 존재하지
않으면 __delayLoadHelper2 함수는 소프트웨어 예외를 발생시킨다.
구조적 예외처리를 이용하여 이러한 예외를 잡아내면 애플리케이션을 계속해서 수행할 수 있지만, 예외를
잡아내지 않으면 프로세스는 종료된다.
다른 문제는 __delayLoadHelper2 가 로드할 DLL을 찾았지만 호출하려고 했던 함수가 해당 DLL 내 에
포함되어 있지 않을 경우에 발생한다. 이러한 문제는 로더가 이전 버전의 DLL을 로드한 경우에 발생할 수
있다.
이 경우 __delayLoadHelper2 함수는 마찬가지로 소프트웨어 예외를 발생시키고 앞서와 동일한 규칙이
적용된다.
5. 알려진 DLL
• 운영체제가 제공하는 몇몇 DLL들은 아주 특별하게 취급된다.
• 이러한 DLL들을 알려진 DLL이라고 부른다.
• 알려진 DLL들은 다른 DLL과 그 구조는 동일하지만 해당 DLL을 로드할 때 항상 일정한
디렉터리로부터 DLL을 찾게 된다.
• 이와 관련하여 레지스트리 내부에는 다음과 같은 키가 존재한다.
5. 알려진 DLL
• 보는 바와 같이 이 키 이하에는 DLL들의 이름으로 설정된
값들이 존재함을 알 수 있다.
• 이러한 값들의 이름은 .dll 확장자를 제외한 이름 값으로
설정되어 있다.
• LoadLibrary나 LoadLibraryEx 함수가 호출되변 이러한
함수들은 가장 먼저 인자로 전달된 DLL 이름에 .dll 이라는
확장자가 포함되어 있는지 확인해 본다.
• 만일 확장자가 포함되어 있지 않다면 일반적인 검색 규칙에
따라 DLL을 찾게 된다.
• 사용자가 로드할 DLL의 이름에 .dll 확장자가 포함되어
있으면 이러한 함수들은 확장자를 제거 한 후 KnownDlls
레지스트리 키 이하에 동일 이름의 값이 존재하는지를
확인한다.
• 일치히는 이름이 없으면 일반적인 검색 규칙에 따라 DLL을
찾게 된다.
• 만일 일치하는 이름이 있으면 시스템은 해당 값 의 데이터를
읽어 와서 이 데이터를 이용하여 DLL을 로드하게 된다.
• 시스템은 해당 레지스트리 키 이 하의 DllDirectory 값의
데이터로 주어진 디렉터리에서부터 DLL 검색을 시작한다.
• 비스타에서 DllDirectory 값의 기본
데이터는 %SystemRoot%System32 이다.
6. DLL 리다이렉션
• 윈도우가 처음 개발되었을 때는 램과 디스크 공간이 가장 중요한 자원이었기에 이것들을 절약하기 위해
가능하면 많은 자원들을 공유하도록 설계하였다.
• 이러한 이유로 마이크로소프트는 C/C++ 라이브러리와 MFC DLL과 같이 여러 애플리케이션에 의해 공유될 수
있는 모듈들을 윈도우의 시스템 디렉터리에 둘 것은 장려하였으며 이것은 시스템이 공유 파일의 위치를 쉽게
찾을 수 있도록 하기 위함이었다고 한다.
• 시간이 지남에 따라 설치 프로그램이 시스템 디렉터리에 있던 기존의 파일들을 하위 호환성이 없는 새로운
파일로 덮어써 버림으로써 이전에 정상적으로 동작하던 애플리케이션들이 동작하지 않는 것 과 같은 심각한
문제가 발생하기 시작했다.
• 마이크로소프트는 지금까지 장려하던 이 같은 사항을 완전히 뒤집어서 애플리케이션이 사용하는 모든 파일을
가능하면 자신의 디렉터리에 두고, 윈도우시스템 디렉터리에는 접근하지 말 것을 강력히 권고하고 있다. 이렇게
함으로써 우리가 개발한 애플리케이션이 다른 애플리케이션에 나쁜 영향을 미치는 것을 방지하고, 반대로
다른 애플리케이션이 우리가 개발한 애플리케이션에 영향을 미치는 것을 막을 수 있다.
•
• 개발자들에게 도움을 주기 위해 마이크로소프트는 윈도우 2000부터 DLL 리다이렉션 기능을 운영체제에
포함시켰다. 이 기능을 이용하면 운영체제의 로더는 애플리케이션이 설치된 디렉터리에서 가장 먼저 로드할
모듈을 찾게 된다. 만일 로더가 이 디렉터리로부터 파일을 찾지 못한 경우에만 다른 디렉터리에서 파일을 찾게
된다.
7. 모듈의 시작 위치 변경
• 모든 실행 파일과 DLL 모듈은 프로세스의 주소 공간에 매핑될 때 가장 이상적인 위치를
나타내기 위해 선호하는 시작 주소 값을 가지고 있다.
• 실행 모듈을 생성하게 되면 링커는 기본적으로 선호하는 시작 주소 값을 Ox00400000으로
설정한다 DLL 모듈에 대해서는 선호하는 시작 주소 값을 0x10000000 으로 설정한다.
마이크로소프트 Visual Studio 의 DumpBin 도구를(/ eader 스위치와 함께) 사용하 면 해당
이미지가 선호하는 시작 주소의 값을 알 수 있다.
• 모듈이 자신이 선호히는 시작 주소에 로드되지 못하면 두 가지 중요한 문제가 발생한다.
• 로더는 재배치 섹션을 검토하여 상당량의 코드를 수정해야 한다. 이는 애플리케이션 초기화 시간에 상당한 영향을
미치는 성능 저해 요소이다.
• 로더가 모듈의 코드가 포함되어 있는 페이지 내용을 수정하게 되면 시스템의 카피 온 라이트 매커니즘에 의해
시스템 페이징 파일을 사용하게 된다.
7. 모듈의 시작 위치 변경
• 하나의 주소 공간에 로드되어야 하는
여러 개의 모듈이 있는 경우 각 모듈이
선호하는 시작 주소를 각기 다르게
설정하는 것이 좋다.
• VS의 프로젝트 속성 창을 이용하면 이
같은 작업을 쉽게 수행할 수 있다.
• 구성 속성  링커  고급 섹션을
선택하고 기본적으로 아무런 값도
입력되어 있지 않은 기준 주소 필드에
값을 입력하면 된다.
• 가능하면 높은 주소에서 낮은 주소
쪽으로 DLL을 로드해 가는것이
주소공간의 단편화를 줄일 수 있다고
한다.
• 단일의 프로세스 공간에 많은 모듈을
로드해야 하는 경우에는 VS의
Rebase.exe라는 도구를 사용한다.
8. 모듈 바인딩
• 시작 주소 변경 작업은 매우 중요하고 전체 시스템의 성능을 상당히 개선시키는 효과를 가지고 있다.
뿐만 아니라 이 작업은 성능개선 이상의 역할을 수행한다.
• 만일 애플리케이션을 구성하는 모든 모듈이 적절하게 시작주소 변경 작업이 이루어졌다고 해 보자.
로더는 실행 파일의 임포트 섹션 내에 있는 심벌의 가상 주소를 기록하게 된다.
이를 통해 실행 파일이 사용하는 심벌이 올바른 메모리 위치를 참조할 수 있도록 해준다.
• 만일 로더가 실행 파일 내의 임포트 섹션 내에 있는 임포트된 심벌의 가상 주소를 기록한다면 페이지의
카피 온 라이트 메커니즘에 의해 임포트 섹션의 내용은 시스템 페이징 파일을 이용하게 될 것이다.
• 이 경우 시작 주소 변경에서와 유사한 문제가 발생하게 된다.
• 즉, 이미지 파일의 일부는 메모리의 시스템 페이징 파일로 스와핑되며, 사용되지 않는 페이지를 폐기하
였다가 필요시 다시 로드해 올 수 없게 된다. 또한 로더는 모든 임포트된 심벌의 주소들을 변경해야
하는데, 이는 상당한 시간을 필요로 하는 작업이다.
• 애플리케이션이 좀 더 빠르게 초기화 되고, 적은 저장소를 사용할 수 있도록 하기 위해 모듈 바인딩
기법을 사용할 수 있다. 초기화 시간을 개선하고 적은 저장소를 사용하려면 모듈을 로딩하기 전에 이러한
작업을 미리 수행해 두어야 한다.
8. 모듈 바인딩
• Visual Studio는 Bind.exe 라는 도구를 포함하고 있다. 아무런 파라미터를 주지 않고 프로그램을 수행하면
다음과 같은 정보가 출력된다.
8. 모듈 바인딩
• ImageHlp API 내의 BindlmageEx 함수를 이용하면 동일한 기능을 아주 쉽게 구현할 수 있다.
마지막 매개변수인 pfnStatusRoutine으로는 BindlmageEx 함수를 호출하여 바인딩이 진행되는 과정을 주기적으로
전달받을 수 있는 콜백함수의 주소를 지정하면 된다.
콜백함수의 원형.
8. 모듈 바인딩
Bind를 수행할 때 이미지의 이름을 전달하면 다음과 같은 작업이 수행된다.
1. 지정한 이미지 파일의 임포트 섹션을 연다.
2. 임포트 섹션에 포함된 모든 DLL을 나열하고 각각의 DLL 파일을 열어서 헤더 내에 포함되어
있는 선호하는 시작 주소를 얻어온다.
3. .DLL의 임포트 섹션 내에서 임포트된 심벌을 찾는다.
4. 심벌의 RVA 값을 가져와서 모듈의 선호하는 시작 주소 값과 더한다. 이미지 파일의 임포트
섹션 내에 있는 임포트된 심벌의 가상 주소 값을 계산된 결과 값으로 기록한다.
5. 이미지 파일의 임포트 섹션에 이미지 파일이 바인딩하는 모든 DLL 모듈의 과 모듈의 시간
정보 등을 추가한다.
8. 모듈 바인딩
전체 진행 과정에 있어서 Bind는 두 가지 중요한 가정을 하고 있다.
1. 프로세스가 초기화될 때 필요한 DLL들은 실제로 자신이 선호하는 시작 주소에 로드될 것이다.
2. DLL의 익스포트 섹션 내의 심벌들 중 참조되는 심벌들의 위치는 바인딩이 수행된 이후
변경되지 않을 것이다. 로더는 5번 단계를 통해 저장하였던 DLL의 시간 정보를 이용하여 이를
확인한다.
물론 로더는 모듈을 로드하는 과정에서 이 두 가지 가정이 모두 틀렸다고 판단할 수도 있다.
Bind는 도움이 될 만한 작업을 전혀 수행하지 못했을 수도 있고 이 경우 로더는 일반적인 경우와 동일하게 실행
모듈의 임포트 섹션을 손수 수정해 준다.
하지만 모듈이 바인딩 되었고 해당 모듈이 자신이 선호하는 시작 주소에 로드되었으며 시간 정보가 일치한다면,
로더가 추가적으로 해야 할 작업이 전혀 없다.
모듈을 재배치해야 할 필요도 없고, 임포트된 함수의 가상 주소를 찾아야 할 필요도 없다.
뿐만 아니라, 이 경우 시스템 페이징 파일을 필요로 하지도 않는다.
감사합니다

Windosw via c 스터디20장.pptx

  • 1.
  • 2.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 첫 번째 방법은 애플리케이션이 DLL 에 포함되어 있는 심벌을 단순 참조하는 경우이다. • 애플리케이션이 수행되면 운영체제의 로더가 필요한 DLL을 묵시적으로 로드하고 링크 한다.
  • 3.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹
  • 4.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 두 번째 방법은 애플리케이션이 필요한 DLL을 명시적으로 로드하도록 하는 것인데, 애플리케이션이 수행 중인 상황에서 필요한 심벌을 명시적으로 링크히는 방법을 말한다. • 다르게 표현하면, 애플리케이션이 수행되고 있는 상황에서 특정 스레드가 DLL 내에 포함되어 있는 함수를 호출하기로 한 경우 프로세스의 주소 공간에 필요한 DLL 파일을 명시적으로 로드하여 DLL 내에 포함되어 있는 함수의 가상 메모리 주소를 획득한 후, 이 값을 이용하여 함수를 호출하는 것을 말한다. 이 기법의 매력은 모든 과정들이 애플리케이션이 수행 중인 상황에서 이루어진다는 것이다.
  • 5.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 명시적인 DLL 모듈 로딩 프로세스 내의 스레드는 언제든지 다음의 두 가지 함수 중 하나를 호출하여 프로세스의 주소 공간에 DLL을 매핑할 수 있다. flag : 707페이지 참고
  • 6.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 명시적인 DLL 모듈 언로딩 프로세스 내의 스레드에서 더 이상 DLL 파일 내의 심벌을 사용할 필요가 없게 되면 FreeLibrary 함 수를 호출하여 프로세스의 주소 공간으로부터 DLL 파일을 명시적으로 언로드 할 수 있다 프로세스의 주소 공간으로부터 DLL 모듈을 언로드하기 위해 FreeLibraryAndExitThread 함수를 사용할 수도 있다. 이 함수는 Kerne132. dll 파일에 다음과 같이 구현되어 있다.
  • 7.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 명시적인 DLL 모듈 언로딩 시스템은 DLL의 사용 카운트를 프로세스별로 유지한다. 즉, A 프로세스의 스레드가 다음과 같이 함수를 호출한 이후에 B 프로세스가 동일하게 함수를 호출하게 되면, MyLib.dll은 두 개의 프로세스 주소 공간에 각기 매핑된다. 이 경우 A 프로세스와 B 프로세스에 로드되어 있는 DLL의 사용 카운트는 모두 1로 유지된다. B 프로세스의 스레드가 다음 함수를 호출하게 되면 B 프로세스에 로드된 DLL의 사용 카운트는 0이 될 것이다. 이 경우 B 프로세스의 주소 공간으로부터 이 DLL은 매핑 해제될 것이지만 A 프로세스 내 의 DLL은 어떠한 영향도 받지 않으며, 사용자 카운트는 계속해서로 유지된다. 스레드는 GetModuleHandle 함수를 호출하여 특정 DLL 파일이 프로세스 주소 공간에 매핑되어 있는지를 확인할 수 있다.
  • 8.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 명시적인 DLL 모듈 언로딩 • 다음 코드는 프로세스의 주소 공간에 MyLib.dll 파일이 로드되어 있지 않은 경우에만 해당 DLL을 로드한다. • GetModuleHandle 에 NULL 값을 전달하면 애플리케이션 실행 모듈에 대한 핸들을 얻을 수 있다. • DLL 파일에 대한 HINSTANCE/HMODULE 값을 알고 있다면 GetModuleFileName 함수를 호출 하여 DLL(혹은 .exe) 에 대한 전체 경로명을 얻을 수 있다.
  • 9.
    1. 명시적인 DLL모듈 로딩과 심벌 링킹 • 익스포트된 심벌을 명시적으로 링킹하기 • 스레드가 DLL 모듈을 명시적으로 로드하였다면, 이제 GetProcAddress 함수를 호출하여 익스포트 된 심벌에 대한시작주소를 얻어 와야 한다. • pszSymbolName 매개변수의 자료형이 PCTSTR 이 아니라 PCSTR. • 이는 함수가 ANSI문자열만을 인자로 취함을 뜻함. • 이는 컴파일러/링커가 심벌의 이름을 DLL의 익스포트 섹션에 항상 ANSI 문자열로 기록하기 때문이다.
  • 10.
    2. DLL의 진입점함수 • DLL은 하나의 진입점 함수를 가질 수 있다. • 시스템은 여러 번에 걸쳐 진입점 함수를 호출해 준다. • 입점 함수가 되는 이유는 정보를 제공하기 위한 목적도 있으며, DLL 이 프로세스별로 혹은 스레드별로 초기화를 수행하거나 정리할 목적으로 사용되기도 한다 • DLL을 구현할 때 진입점 함수를 반드시 구현해야 하는 것은 아니다.(ex 리소스만 가지고 있는 DLL)
  • 11.
    2. DLL의 진입점함수 1. DLL_PROCESS_ATTACH 통지 2. DLL_PROCESS_DETACH 통지 3. DLL_THREAD_ATTACH 통지 4. DLL_THREAD_DETACH 통지
  • 12.
    2. DLL의 진입점함수 1. DLL_PROCESS_ATTACH 통지
  • 13.
    2. DLL의 진입점함수 2. DLL_PROCESS_DETACH 통지
  • 14.
    2. DLL의 진입점함수 3. DLL_THREAD_ATTACH통지 • 프로세스 내에 새로운 스레드가 생성되면 시스템은 fdwReason 매개변수에 DLL_THREAD_ATTACH 값을 전달하여 현재 프로세스 주소 공간에 매핑되어 있는 모든 DLL의 DllMain 함수를 호출해 준다. • 이러한 통지 과정을 통해 모든 DLL들은 스레드별로 초기화 과정을 수행할 수 있게 된다. 새롭게 생성된 스레드는 모든 DLL들의 DllMain 함수를 호출할 책임이 있다. 모든 DLL들이 이러한 통지를 전달받은 이후에야 비로소 시스템은 스레드가 자신의 스레드 함수를 수행할 수 있도록 해 준다. • 프로세스 내에 이미 여러 개의 스레드들을 수행 중인 상태에서 새로운 DLL 이 주소 공간 내에 매핑되는 경우에는 기존 스레드들에 대해 DLL_THREAD_ATTACH를 인자로 DllMain 함수를 호출하지는 않으며, DLL이 이미 프로세스의 주소 공간에 매핑된 상황에서 새로운 스레드가 만들어진 경우에만 DLL_TRHEAD_ATTACH를 인자로 DllMain함수를 호출해 준다. • 또한 시스템은 프로세스의 주 스레드에 대해서는 DLL_TRHEAD_ATTACH 값을 전달하여 DllMain 함수를 호출하지 않는다는 점에 주의하기 바란다. 프로세스가 처음으로 시작되어서 DLL들이 프로세스의 주소 공간에 매핑될 때에는 DLL_THREAD_ATTACH 통지가 아니라 DLL_PROCESS_ATTACH 통지가 전달될 것 이다.
  • 15.
    2. DLL의 진입점함수 4. DLL_THREAD_DETACH 통지 • 스레드를 종료하는 가장 좋은 방법은 스레드 함수가 반환되도록 하는 것이다. 스레드 함수가 반환되면 스레드를 종료하기 위해 ExitThread 함수를 호출하게 되는데 이 함수는 시스템에게 스레드가 종료되길 원한다는 사실을 전달하게 된다. 하지만 시스템은 스레드를 이 함수가 호출되는 순간에 바로 죽이지는 않는다. • 대신, 프로세스의 주소 공간에 매핑된 모든 DLL 의 DllMain 함수를 DLL_THREAD_DETACH 값을 인자로 하여 호출한 후에 스레드를 종료한다. 이러한 통지를 이용하여 DLL은 스레드 단위의 정리 작업을 수행할 수 있다. • DLL 이 스레드가 종료되는 것을 방해할 수도 있다는 점에 주목하기 바란다. 예를 들어 DllMain 함수로 DLL_THREAD_DETACH 통지가 전달되었을 때 무한 루프로 진입하게 되면 스레드는 종료되지 못한다. 운영체제는 실제로 모든 DLL이 DLL_THREAD_DETACH 통지를 처리한 이후에야 비로소 스레드를 종료시킨다.
  • 16.
    3. DLL의 지연로딩 • 지연 로드 DLL은 암시적으로 링크되는 DLL 이지만 실제로 코드에서 해당 심벌을 참조하기 전까지는 로드가 수행되지 않는다. 일부 제한사항. • 지연 로드 DLL은 필드를 익스포트할 수 없다 . • LoadLibrary와 GetProcAddress를 호출해야 하기 때문에 Kernel32.dll 모듈은 지연 로드될 수 없다 . • DIIMain 진입점 함수 내에서 지연 로드될 함수를 호출하게 되면 프로세스가 손상된다.
  • 17.
    3. DLL의 지연로딩 지연 로드 DLL을 작성하려면 먼저 일반적인 방식으로 DLL을 생성하면 된다. 실행 파일도 동일하게 일반적인 방식으로 생성하면 되지만 일부 링커 스위치를 수정하여 실행 파일을 다시 링크해야 한다. 아래에 반드시 추가되어야 하는 두 개의 링커 스위치를 나타냈다.
  • 18.
    3. DLL의 지연로딩 지연 로드되는 DLL들을 선택하려면 구성 속성 / 링커 / 입력 페이지에서 다음과 같이 설정하면 된다. 지연 로드 DLL 옵션은 구성 속성 / 링커 / 고급 페이지 에서 다음과 같이 설정하면 된다.
  • 19.
    3. DLL의 지연로딩 /Lib 스위치를 사용하면 링커는 __delayLoadHelper2 라는 특수한 함수를 실행 파일 내에 포함시킨다. 두 번째 스위치는 링커에게 다음과 같은 작업을 수행하도록 한다. • 실행 모듈의 임포트 섹션으로부터 MyDll.dll 파일을 제거하여 운영체제가 프로세스를 초기화할 때 해당 DLL을 암시적으로 로드하지 못하게 한다. • 실행 파일 내의 새로운 지연 로드 임포트 섹션을 포함시켜 어떤 함수들이 MyDll.dll 파일로부터 지연 임포트 되는지에 대한 정보를 추가한다. • 지연 로드되는 함수를 호출하는 코드를 __delayLoadHelper2 함수를 호출하도록 변경한다. 애플리케이션이 수행되어 지연 로드된 함수를 호출하게 되면 __ delayLoadHelper2 함수가 실제로 호출된다. 이 함수는 지연 임포트 섹션을 참조하며, 해당함수를 호출하려면 LoadLibrary와 GetProcAddress를 호출해야 한다는 사실을 알 수 있다. __ delayLoadHelper2 함수가 지연 로드된 함수의 메모리 주소를 획득하게 되면 다음번에 동일한 함수를 호출할 경우 직접 해당 함수를 호출할 수 있도록 함수 호출부를 수정한다.
  • 20.
    3. DLL의 지연로딩 일부 다른 문제에 대해 고민 보통 운영체제의 로더는 실행 파일을 로드하고 필요한 DLL들을 로드하려고 시도한다. 만일 필요한 DLL들 을 로드할 수 없으면 로더는 에러 메시지를 출력하지만 지연 로드된 DLL들은 초기화시에 DLL 의 존재유무가 확인되지 않는다. 만일 지연 로드된 함수가 호출되었을 때 해당 함수를 익스포트하고 있는 DLL 이 존재하지 않으면 __delayLoadHelper2 함수는 소프트웨어 예외를 발생시킨다. 구조적 예외처리를 이용하여 이러한 예외를 잡아내면 애플리케이션을 계속해서 수행할 수 있지만, 예외를 잡아내지 않으면 프로세스는 종료된다. 다른 문제는 __delayLoadHelper2 가 로드할 DLL을 찾았지만 호출하려고 했던 함수가 해당 DLL 내 에 포함되어 있지 않을 경우에 발생한다. 이러한 문제는 로더가 이전 버전의 DLL을 로드한 경우에 발생할 수 있다. 이 경우 __delayLoadHelper2 함수는 마찬가지로 소프트웨어 예외를 발생시키고 앞서와 동일한 규칙이 적용된다.
  • 21.
    5. 알려진 DLL •운영체제가 제공하는 몇몇 DLL들은 아주 특별하게 취급된다. • 이러한 DLL들을 알려진 DLL이라고 부른다. • 알려진 DLL들은 다른 DLL과 그 구조는 동일하지만 해당 DLL을 로드할 때 항상 일정한 디렉터리로부터 DLL을 찾게 된다. • 이와 관련하여 레지스트리 내부에는 다음과 같은 키가 존재한다.
  • 22.
    5. 알려진 DLL •보는 바와 같이 이 키 이하에는 DLL들의 이름으로 설정된 값들이 존재함을 알 수 있다. • 이러한 값들의 이름은 .dll 확장자를 제외한 이름 값으로 설정되어 있다. • LoadLibrary나 LoadLibraryEx 함수가 호출되변 이러한 함수들은 가장 먼저 인자로 전달된 DLL 이름에 .dll 이라는 확장자가 포함되어 있는지 확인해 본다. • 만일 확장자가 포함되어 있지 않다면 일반적인 검색 규칙에 따라 DLL을 찾게 된다. • 사용자가 로드할 DLL의 이름에 .dll 확장자가 포함되어 있으면 이러한 함수들은 확장자를 제거 한 후 KnownDlls 레지스트리 키 이하에 동일 이름의 값이 존재하는지를 확인한다. • 일치히는 이름이 없으면 일반적인 검색 규칙에 따라 DLL을 찾게 된다. • 만일 일치하는 이름이 있으면 시스템은 해당 값 의 데이터를 읽어 와서 이 데이터를 이용하여 DLL을 로드하게 된다. • 시스템은 해당 레지스트리 키 이 하의 DllDirectory 값의 데이터로 주어진 디렉터리에서부터 DLL 검색을 시작한다. • 비스타에서 DllDirectory 값의 기본 데이터는 %SystemRoot%System32 이다.
  • 23.
    6. DLL 리다이렉션 •윈도우가 처음 개발되었을 때는 램과 디스크 공간이 가장 중요한 자원이었기에 이것들을 절약하기 위해 가능하면 많은 자원들을 공유하도록 설계하였다. • 이러한 이유로 마이크로소프트는 C/C++ 라이브러리와 MFC DLL과 같이 여러 애플리케이션에 의해 공유될 수 있는 모듈들을 윈도우의 시스템 디렉터리에 둘 것은 장려하였으며 이것은 시스템이 공유 파일의 위치를 쉽게 찾을 수 있도록 하기 위함이었다고 한다. • 시간이 지남에 따라 설치 프로그램이 시스템 디렉터리에 있던 기존의 파일들을 하위 호환성이 없는 새로운 파일로 덮어써 버림으로써 이전에 정상적으로 동작하던 애플리케이션들이 동작하지 않는 것 과 같은 심각한 문제가 발생하기 시작했다. • 마이크로소프트는 지금까지 장려하던 이 같은 사항을 완전히 뒤집어서 애플리케이션이 사용하는 모든 파일을 가능하면 자신의 디렉터리에 두고, 윈도우시스템 디렉터리에는 접근하지 말 것을 강력히 권고하고 있다. 이렇게 함으로써 우리가 개발한 애플리케이션이 다른 애플리케이션에 나쁜 영향을 미치는 것을 방지하고, 반대로 다른 애플리케이션이 우리가 개발한 애플리케이션에 영향을 미치는 것을 막을 수 있다. • • 개발자들에게 도움을 주기 위해 마이크로소프트는 윈도우 2000부터 DLL 리다이렉션 기능을 운영체제에 포함시켰다. 이 기능을 이용하면 운영체제의 로더는 애플리케이션이 설치된 디렉터리에서 가장 먼저 로드할 모듈을 찾게 된다. 만일 로더가 이 디렉터리로부터 파일을 찾지 못한 경우에만 다른 디렉터리에서 파일을 찾게 된다.
  • 24.
    7. 모듈의 시작위치 변경 • 모든 실행 파일과 DLL 모듈은 프로세스의 주소 공간에 매핑될 때 가장 이상적인 위치를 나타내기 위해 선호하는 시작 주소 값을 가지고 있다. • 실행 모듈을 생성하게 되면 링커는 기본적으로 선호하는 시작 주소 값을 Ox00400000으로 설정한다 DLL 모듈에 대해서는 선호하는 시작 주소 값을 0x10000000 으로 설정한다. 마이크로소프트 Visual Studio 의 DumpBin 도구를(/ eader 스위치와 함께) 사용하 면 해당 이미지가 선호하는 시작 주소의 값을 알 수 있다. • 모듈이 자신이 선호히는 시작 주소에 로드되지 못하면 두 가지 중요한 문제가 발생한다. • 로더는 재배치 섹션을 검토하여 상당량의 코드를 수정해야 한다. 이는 애플리케이션 초기화 시간에 상당한 영향을 미치는 성능 저해 요소이다. • 로더가 모듈의 코드가 포함되어 있는 페이지 내용을 수정하게 되면 시스템의 카피 온 라이트 매커니즘에 의해 시스템 페이징 파일을 사용하게 된다.
  • 25.
    7. 모듈의 시작위치 변경 • 하나의 주소 공간에 로드되어야 하는 여러 개의 모듈이 있는 경우 각 모듈이 선호하는 시작 주소를 각기 다르게 설정하는 것이 좋다. • VS의 프로젝트 속성 창을 이용하면 이 같은 작업을 쉽게 수행할 수 있다. • 구성 속성 링커 고급 섹션을 선택하고 기본적으로 아무런 값도 입력되어 있지 않은 기준 주소 필드에 값을 입력하면 된다. • 가능하면 높은 주소에서 낮은 주소 쪽으로 DLL을 로드해 가는것이 주소공간의 단편화를 줄일 수 있다고 한다. • 단일의 프로세스 공간에 많은 모듈을 로드해야 하는 경우에는 VS의 Rebase.exe라는 도구를 사용한다.
  • 26.
    8. 모듈 바인딩 •시작 주소 변경 작업은 매우 중요하고 전체 시스템의 성능을 상당히 개선시키는 효과를 가지고 있다. 뿐만 아니라 이 작업은 성능개선 이상의 역할을 수행한다. • 만일 애플리케이션을 구성하는 모든 모듈이 적절하게 시작주소 변경 작업이 이루어졌다고 해 보자. 로더는 실행 파일의 임포트 섹션 내에 있는 심벌의 가상 주소를 기록하게 된다. 이를 통해 실행 파일이 사용하는 심벌이 올바른 메모리 위치를 참조할 수 있도록 해준다. • 만일 로더가 실행 파일 내의 임포트 섹션 내에 있는 임포트된 심벌의 가상 주소를 기록한다면 페이지의 카피 온 라이트 메커니즘에 의해 임포트 섹션의 내용은 시스템 페이징 파일을 이용하게 될 것이다. • 이 경우 시작 주소 변경에서와 유사한 문제가 발생하게 된다. • 즉, 이미지 파일의 일부는 메모리의 시스템 페이징 파일로 스와핑되며, 사용되지 않는 페이지를 폐기하 였다가 필요시 다시 로드해 올 수 없게 된다. 또한 로더는 모든 임포트된 심벌의 주소들을 변경해야 하는데, 이는 상당한 시간을 필요로 하는 작업이다. • 애플리케이션이 좀 더 빠르게 초기화 되고, 적은 저장소를 사용할 수 있도록 하기 위해 모듈 바인딩 기법을 사용할 수 있다. 초기화 시간을 개선하고 적은 저장소를 사용하려면 모듈을 로딩하기 전에 이러한 작업을 미리 수행해 두어야 한다.
  • 27.
    8. 모듈 바인딩 •Visual Studio는 Bind.exe 라는 도구를 포함하고 있다. 아무런 파라미터를 주지 않고 프로그램을 수행하면 다음과 같은 정보가 출력된다.
  • 28.
    8. 모듈 바인딩 •ImageHlp API 내의 BindlmageEx 함수를 이용하면 동일한 기능을 아주 쉽게 구현할 수 있다. 마지막 매개변수인 pfnStatusRoutine으로는 BindlmageEx 함수를 호출하여 바인딩이 진행되는 과정을 주기적으로 전달받을 수 있는 콜백함수의 주소를 지정하면 된다. 콜백함수의 원형.
  • 29.
    8. 모듈 바인딩 Bind를수행할 때 이미지의 이름을 전달하면 다음과 같은 작업이 수행된다. 1. 지정한 이미지 파일의 임포트 섹션을 연다. 2. 임포트 섹션에 포함된 모든 DLL을 나열하고 각각의 DLL 파일을 열어서 헤더 내에 포함되어 있는 선호하는 시작 주소를 얻어온다. 3. .DLL의 임포트 섹션 내에서 임포트된 심벌을 찾는다. 4. 심벌의 RVA 값을 가져와서 모듈의 선호하는 시작 주소 값과 더한다. 이미지 파일의 임포트 섹션 내에 있는 임포트된 심벌의 가상 주소 값을 계산된 결과 값으로 기록한다. 5. 이미지 파일의 임포트 섹션에 이미지 파일이 바인딩하는 모든 DLL 모듈의 과 모듈의 시간 정보 등을 추가한다.
  • 30.
    8. 모듈 바인딩 전체진행 과정에 있어서 Bind는 두 가지 중요한 가정을 하고 있다. 1. 프로세스가 초기화될 때 필요한 DLL들은 실제로 자신이 선호하는 시작 주소에 로드될 것이다. 2. DLL의 익스포트 섹션 내의 심벌들 중 참조되는 심벌들의 위치는 바인딩이 수행된 이후 변경되지 않을 것이다. 로더는 5번 단계를 통해 저장하였던 DLL의 시간 정보를 이용하여 이를 확인한다. 물론 로더는 모듈을 로드하는 과정에서 이 두 가지 가정이 모두 틀렸다고 판단할 수도 있다. Bind는 도움이 될 만한 작업을 전혀 수행하지 못했을 수도 있고 이 경우 로더는 일반적인 경우와 동일하게 실행 모듈의 임포트 섹션을 손수 수정해 준다. 하지만 모듈이 바인딩 되었고 해당 모듈이 자신이 선호하는 시작 주소에 로드되었으며 시간 정보가 일치한다면, 로더가 추가적으로 해야 할 작업이 전혀 없다. 모듈을 재배치해야 할 필요도 없고, 임포트된 함수의 가상 주소를 찾아야 할 필요도 없다. 뿐만 아니라, 이 경우 시스템 페이징 파일을 필요로 하지도 않는다.
  • 31.

Editor's Notes

  • #3 (시작)스레드가 DLL 모듈 내의 함수를 호출하려면 스레드가 포함되어 있는 프로세스의 주소 공간에 DLL 파일 이미지가 매핑되어 있어야 한다 DLL 파일 이미지를 프로세스의 주소 공간에 매핑하는 방법에는 두 가지가 있다.
  • #4 DLL 생성 익스포트할 원형/구조체/ 심벌 등을 정의하고 있는 헤더 익스포트할 함수/변수를 구현하고 있는 C/C++ 소스 파일. 컴파일러는 C/C++ 소스 파일 각각에 대해 obj 따일 생성 링커는 obj 모둘을 결합하여 DLL 생성 링커는 하나 이상의 힘수/변수가 익스포트된 경우 lib 파일 생성 Iib 따일은 명시적인 링킹 과정에서는 사용되지 않는다 EXE 생성 6) 임포트할 원형/구조체/ 심벌 등을 정의하고 있는 헤더 7) 임포트할 함수/변수를 사용하지 않고 있는 C/C++ 소스 파일 8) 컴파일러는 C/C++ 소스 파일 각각에 대해 obj 파일 생성. 9) 링커는 obj 모듈을 결합하여 exe 모듈을 생성 익스포트된 심벌을 직접적으로 사용 하지 않는 이상 lib 파일은 필요하지 않다. exe 파일은 임포트 테이블을 포함하고 있지 않다
  • #6 (끝나고) 이 함수들은 사용자의 시스템에서 파일 이미지를 검색하고 함수를 호출한 프로세스의 주소 공간에 DLL 파일 이미지를 매핑하려고 시도한다. 두 함수 는 파일 이미지가 매핑된 가상 메모리 주소를 나타내는 HMODULE 값을 반환한다 HMODULE형은 HINSTANCE형과 완전히 동일하며, 상호간에 혼용되어 사용될 수 있다. 실제로 DllMain 진입점 함수의 HINSTANCE 인자 또한 파일 이미지가 매핑된 가상 메모리 주소를 가지게 된다. 이러한 함수들은 지정한 DLL을 프로세스의 주소공간에 매핑할 수 없으면 NULL 값을 반환한다. 에러 발생 원인을 좀 더 자세히 알고 싶다면 GetLastError 함수를 호출해 보면 된다. LoadLibraryEx는 hFile 과 dwFlags 라는 두 개의 추가적인 매개변수를 펼요로 한다 hFile 매개변수는 미래에 사용하기 위해 예약된 매개변수로, 반드시 NULL을 전달해야 한다. dwFlags 매개변수로는 0을 지정하거나 플래그들을 조합하여 사용하면 된다. 707페이지 참고
  • #7 끝) 이 함수가 추가된 이유는 다음과 같은 시나리오에 활용하기 위함이다. 프로세스의 주소 공간에 매핑되어 스레드를 생성하는 DLL이 있다고 가정 헸을 때. 스레드가 작업을 마치면 프로세스 주소 공간으로부터 DLL 매핑을 해제하기 위해 FreeLibrary 를 호출한 후 ExitThread 함수를 호출해야 한다. 하지만 FreeLibrary와 ExitThread 함수를 각각 호출하게 되면 심각한 문제가 발생하게 된다고 하는데 FreeLibrary 함수를 호출하게 되면 프로세스의 주소 공간으로부터 DLL 파일이 지체 없이 해제되어 버린다는 것입니다. 즉, FreeLibrary가 반환되게 되면 ExitThread 함수를 호출하고자 했던 코드는 더 이상 메모리 상에 남아 있지 않게 되므로, 스레드가 수행할 코드가 사라지게 된다. 이렇게 되면 접근위반이 발생하게 될 것이고, 이는 전체 프로세스의 종료로 이어지게 된다. 하지만 스레드가 FreeLibraryAndExitThread 함수를 호출하게 되면 이 함수는 FreeLibrary 함수를 호출하여 DLL을 지체 없이 해제한다 하더라도 다음에 수행할 코드는 해제된 DLL 파일 내에 존재하는 것이 아니라 Kernel32.dll 파일 내에 존재하게 된다. 따라서 스레드는 지속적으로 수행될 수 있으며, ExitThread 함수를 호출할 수 있게 된다. ExitThread 함수가 호출되면 스레드는 정지될 것이며, 이 함수는 반환되지 않는다. 실제로, LoadLibrary와 LoadLibraryEx 함수를 사용하게 되면 프로세스별로 라이브러리에 대한 사용 카운트 값을 증가시키며 , FreeLibrary나 FreeLibrary AndExitThread 함수를 호출하게 되면 이 값을 감소시키게 된다. 예를 들어 DLL을 로드하기 위해 LoadLibrary 함수를 최초로 수행한 경우, 시스템은 DLL 파일 이미지를 프로세스의 주소 공간에 매핑하고 DLL의 사용 카운트 값을 1로 설 정한다. 만일 동일 프로세스 내의 스레드가 통일한 DLL 파일 이미지에 대해 LoadLibrary를 또다시 호출하게 되면, 시스템은 DLL 파일 이미지를 프로세스의 주소 공간에 두 번 매핑하지 않고, 관련 DLL 의 사용 카운트 값만 증가시킨다. 이 경우 프로세스 주소공간으로부터 DLL 파일 이미지를 매핑 해제하려면 FreeLibrary 함수를 두 번 호출해 주어야한다. 첫 번째 함수호출은 DLL의 사용카운트를 1로 감소시킬 것이고, 두 번째 함수 호출은 DLL 의 사용 카운트를 0으로 감소시킬 것이다. 시스템은 DLL의 사용 카운트가 0이 되면 비로소 프로세스의 주소 공간으로부터 DLL 파일 이미지를 매핑 해제한다. 이후 스레드가 DLL 파일 내 의 함수를 호출하려고 하면 이미 DLL 파일 이미지가 프로세스 주소 공간으로부터 해제된 이후이므로 접근 위반을 유발하게 된다.
  • #11 끝)hlnstDll 매개변수로는 DLL의 인스턴스 핸들 값이 전달된다. DLL 파일 이미지가 가상 주소 공간의 어디로 매핑되었는지를 알려주는 가상 메모리 주소값이다. 보통의 경우 이 값을 전역변수에 저장해 두었다가 리소스를 로드해야 하는 함수들을 호출할 때 사용하는 것이 일반적이라고 한다. 마지막 매개변수인 fImpLoad는 DLL이 암시적으로 로드된 경우에는 0이 아닌 값이 전달되고, 명시적으로 로드된 경우에는 0이 전달된다고 한다. 두번째 매개변수인 fdwReason 매개변수로는 시스템이 이 함수를 호출한 이유를 나타내는 값이 전달된다.
  • #12  (시작)두번째 매개변수인 fdwReason 의 값으로는 네가지의 값중 하나가 전달된다.
  • #13 DLL이 프로세스의 주소 공간에 최초로 매핑되면,  fdwReason 매개변수에 DLL_PROCESS_ATTACH 값을 전달하여 해당 DLL의 DllMain 함수를 호출해 준다. 이러한 동작은 DLL 파일이 처음으로 매핑될 때에 한해서만 발생한다. 만일 쓰레드가 프로세스의 주소 공간에 이미 매핑되어 있는 DLL에 대해 추가적으로 LoadLibrary(Ex)를 호출하게 되면,  운영체제는 단순히 DLL의 usage count 값을 증가시키는 작업만을 수행할 뿐이며, DLL_PROCESS_ATTACH를 인자 값으로 DllMain 함수를 다시 호출해 주지는 않는다. DllMain에 DLL_PROCESS_ATTACH가 전달된 경우 DllMain 함수의 반환 값은  DLL의 초기화가 성공적으로 수행되었는지의 여부를 나타내게 된다. (fdwReason이 DLL_PROCESS_ATTACH이 아닌 경우에는 DllMain의 반환값은 아무런 의미를 가지지 않는다) 새로운 프로세스가 생성되면 시스템은 프로세스 주소 공간을 생성하고,  .exe 파일과 수행에 필요한 모든 DLL 파일을 프로세스 주소 공간에 매핑한다. 이후 프로세스의 메인 쓰레드를 생성하게 되는데, 암시적 로드타임 링킹에서는 프로세스의 메인 쓰레드가  로드된 DLL들이 가지고 있는 DllMain 함수 각각을 DLL_PROCESS_ATTACH 값을 인자로 호출하게 된다. 프로세스 주소 공간에 매핑된 모든 DLL들이 DLL_PROCESS_ATTACH 통지에 대해 정상적으로 회신하게 되면, 시스템은 프로세스의 메인 쓰레드가 실행 모듈에 포함되어 있는 C/C++ 런타임 시작 코드를 수행할 수 있도록 하여, 이는 결국 실행 모듈의 진입점 함수를 호출하게 된다. 위 과정 중 하나의 DLL이라도 초기화에 실패하여, DllMain 함수가 FALSE를 반환하게 되면, 시스템은 해당 프로세스를 종료하게 된다. 이제 DLL을 명시적 런타임 링크하는 경우에 어떤 일이 일어나는지에 대해 살펴보면. 프로세스의 특정 쓰레드가 LoadLibrary(Ex)를 호출하게 되면,  시스템은 지정한 DLL을 찾아서 프로세스의 주소 공간에 해당 DLL을 매핑하게 된다. 이후 시스템은 LoadLibrary(Ex) 함수를 호출하였던 쓰레드로 하여금, 해당 DLL 내에 포함되어 있는 DllMain 함수를 DLL_PROCESS_ATTACH 값을 인자로 하여 호출하도록 한다. DllMain 함수가 이러한 통지를 완전히 처리하고 나면 LoadLibrary(Ex) 함수가 반환되고, 쓰레드는 자신이 수행하던 코드를 계속 수행할 수 있게 된다. 암시적 로드타임 링크 방식이든, 명시적 런타임 링크 방식이든 DllMain을 호출하는 쓰레드는 DllMain 함수가 반환될 때까지 쓰레드 수행이 블록됨을 기억해야 한다.
  • #14 DLL이 프로세스의 주소 공간으로부터 매핑 해제되면 fdwReason 매개변수에 DLL_PROCESS_DETACH 값을 전달하여 해당 DLL의 DllMain 함수를 호출해 준다. 이 경우 DllMain은 프로세스와 관련이 있는 정리 작업만을 수행해야한다. 예를 들면 DLL_PROCESS_ATTACH 통지가 전달되었을 때 생성했던 힙을 파괴하기 위해 HeapDestroy를 호출해야만 한다. 시스템이 DLL PROCESS_ATTACH 값을 인자로 DIIMain 함수를 호출하였을 때 반환 값으로 FALSE 가 돌아온 경우에는 DLL PROCESS DETACH 값을 인자로 DllMain 함수를 호출하지 않는다는 점에 주의해야 한다. 프로세스가 종료 되어 DLL을 매핑 해제해야 하는 경우에는 ExitProcess 함수를 호출한 스레드가 DllMain 함수의 코드를 수행할 책임이 있다. 보통의 상황에서는 이러한 스레드가 애플리케이션의 주 스레드가 될 것이다. 시용자가 정의한 진입점 함수가 반환되어 C/C++ 런타임 라이브러리의 시작 코드로 제어가 돌아오게 되면, 시작 코드는 프로세스를 종료하기 위해 명시적으로 ExitProcess를 호출해 주기 때문이다. 프로세스 내의 스레드가 FreeLibrary 나 FreeLibrary AndExitThread 함수를 호출하여 DLL을 매핑 해제하는 경우에는 해당 함수를 호출한 스레드가 DllMain 함수를 수행하게 된다. FreeLibrary 함수를 호출한 경우에는 DllMain 함수가 DLLPROCESS DETACH 통지에 대한 처리를 완료하기 전까지 해당 스레드는 반환되지 않는다. DLL이 프로세스가 종료되는 것을 방해할 수도 있다는 점에 주목해야 한다. 예를 들어 DllMain 함수로 DLL_PROCESS_DETACH 통지가 전달되었을 때 무한 루프로 진입하게 되면 프로세스는 종료 되지 못한다. 운영체제는 실제로 모든 DLL 이 DLL PROCESS DETACH 통지를 완전히 처리한 이후에야 비로소 프로세스를 종료시킨다.