2014. 5. 7. 16:51

지연된 DLL 로드(delay load dll)

응용 프록그램을 개발할 때, dll이나 so 같이 모듈을 라이브러리로 만들어 사용하는 경우가 많습니다. 윈도우같은 경우에는 dll이 이용되기도 하는데, 이러한 dll을 로드하기 위한 방법은 다음과 같습니다.

  • 암묵적 방법 (link)
  • 명시적 방법 (LoadLibrary)
  • 지연된 DLL 로드 방법

보통, Windows System API인 Win32 API가 구성된 kernel32.dll이나 user32.dll 같은 경우, 암묵적 방법이 주로 사용됩니다. 예를 들어, C:\Windows\System32\msvcrt.dll의 strcpy를 사용할 때, LoadLibrary나 GetProcAddress를 사용하지 않고 바로 - strcpy(...) - 와 같이 호출해도 빌드가 되는 이유는, 암묵적 방법이 사용되었기 때문입니다. 즉, 직접 strcpy를 구현하지 않아도 strcpy를 만나면 msvcrt.dll의 함수로 바로 호출해 줍니다. 만일 Vista 이상에서만 지원되는 kernel32.dll::CopyFileTransactedW(...)를 위와 같이 암묵적으로 사용한다면, XP 같은 시스템에서는 진입지점(EntryPoint, dllmain, winmain)에 들어가지 못한채, 바로 모듈 로드가 실패가 됩니다. 그러니, 경우에 따라서는 Win32 API도 GetProcAddress가 사용되기도 합니다(주로, Vista 이상에서 지원되는 API 호은 X64에서만 지원되는 API가 해당).


Win32 API와 같은 system call 함수 이외, 사용자가 정의한 모듈도 자유롭게 선택이 가능합니다. 다만, 저는 Computer science에 늘 등장하는 쌍인 암묵적/명시적(implicit/explicit) 방법중 명시적인 방법을 선호합니다. 즉, "좀 귀찮더라도, 나의 명령으로 control되"기를 바라기 때문입니다. 암묵저인 방법은 편리하기는 하지만, 가끔씩 예상하지 못한 이슈가 생겨 고생하게 됩니다. 물론, 코딩 스타일도 테크닉이 난무한 암묵적 방법 보다는, 길지만 명확한 명시적 방법과 주석이 풍부한 것을 선호합니다.


여하튼, 이러한 명시적, 암묵적 방법 이외에도 지연된 방법이 있는데(delay load dll),

이는 자세한 논리나 내용을 walkthrough로 설명합니다.


아래와 같은 코드를 준비합니다.

#include "stdafx.h"

#include <windows.h>

int APIENTRY _tWinMain(HINSTANCE hInstance,

   HINSTANCE hPrevInstance,

   LPTSTR    lpCmdLine,

   int       nCmdShow)

{

int test;


test = 10;


MessageBoxA(NULL, "Hello", "Hello", MB_OK);


test = 11;

}

test = 10; 부분에 Break point를 걸고, 디버깅을 시작한뒤, 모듈을 보면 아래와 같습니다.

즉, user32.dll::MessageBoxA를 실행하지 않았음에도 프로그램 로드 시작 지점(EntryPoint)에서 이미 user32.dll이 로드되어 버렸습니다.


어찌보면 당연하게 느껴지는 것이지만, 이를 필요할 때 로드할 수 있도록 하는 것이 지연된 방법입니다.

이를 위해 아래와 같이 코드를 수정합니다.

#include "stdafx.h"

#include <windows.h>

#pragma comment(lib, "delayimp")

#pragma comment(lib, "user32")

int APIENTRY _tWinMain(HINSTANCE hInstance,

  HINSTANCE hPrevInstance,

  LPTSTR    lpCmdLine,

  int       nCmdShow)

{

int test;


test = 10;


MessageBoxA(NULL, "Hello", "Hello", MB_OK);


test = 11;

}

delayimp와 user32를 pragma comment 했는데, 코드 수정이 싫다면, Linker->Input->Additional Dependencies에 delayimp.lib와 user32.lib를 직접 추가해도 됩니다. 그리고, 아래와 같이 Linker->Command Line에 "/DELAYLOAD:user32.dll"를 추가하여 줍니다.

그런다음 빌드한뒤, 이전과 같이 디버깅을 하면, 아래와 같이 로드된 모듈이 다르게 나타납니다.

즉, user32.dll이 아직 로드되지 않았습니다.

계속 디버깅을 시작하여, "Hello" 메시지창을 닫으면, 아래와 같습니다.

즉, MessageBoxA 호출직전에 user32.dll을 포함하여 관련된 모든 dll들이 한꺼번에 로딩됩니다.

이러한 지연 로드 방법은 기본적으로 암묵적 방법이지만, LoadLibrary와 GetProcAddress를 사용하는 명시적 방법과 유사하게 보이기도 합니다. 그리고, __FUnloadDelayLoadedDLL2("user32.dll")과 같이 명시적 언로드도 가능합니다. (/DELAY:UNLOAD 옵션 추가). 물론, 해당 부분은 실제로 의도한것 처럼 잘 동작하는지는 의문입니다.


어떻든, 이러한 지연 로드 기법은 user32.dll 뿐만 아니라, 사용자 정의 dll도 가능하며, case에 맞춰 잘 응용해야 합니다. 참고로, dependency walker로 지연된 로드 사용 여부 구별법은 아래와 같습니다.

위 그림은 지연된 로드를 사용하지 않는 일반적인 경우입니다.

위 그림은 지연된 로드를 사용한 경우로, USER32.DLL에 Hour Glass 아이콘이 표시됩니다.