지연 로드 도우미 함수 이해
링커 지원 지연 로드에 대한 도우미 함수는 실제로 런타임에 DLL을 로드합니다. 도우미 함수를 수정하여 동작을 사용자 지정할 수 있습니다. 제공된 도우미 함수 delayimp.lib
를 사용하는 대신 사용자 고유의 함수를 작성하고 프로그램에 연결합니다. 하나의 도우미 함수는 로드된 모든 지연 DLL을 제공합니다.
DLL 또는 가져오기의 이름을 기반으로 특정 처리를 수행하려는 경우 고유한 버전의 도우미 함수를 제공할 수 있습니다.
도우미 함수는 다음 작업을 수행합니다.
라이브러리에 저장된 핸들을 확인하여 라이브러리가 이미 로드되었는지 확인합니다.
DLL 로드를 시도하는 호출
LoadLibrary
프로시저의 주소를 가져오려고 시도하는 호출
GetProcAddress
지연 가져오기 로드 펑크로 돌아와 현재 로드된 진입점을 호출합니다.
도우미 함수는 다음 각 작업 후에 프로그램에서 알림 후크로 다시 호출할 수 있습니다.
도우미 함수가 시작되는 경우
도우미 함수에서 바로 전에
LoadLibrary
호출됩니다.도우미 함수에서 바로 전에
GetProcAddress
호출됩니다.도우미 함수에 대한 호출
LoadLibrary
이 실패하는 경우도우미 함수에 대한 호출
GetProcAddress
이 실패하는 경우도우미 함수가 처리를 완료한 후
이러한 각 후크 지점은 지연 가져오기 로드 펑크로의 반환을 제외하고 어떤 방식으로 도우미 루틴의 정상적인 처리를 변경하는 값을 반환할 수 있습니다.
기본 도우미 코드는 MSVC 디렉터리 및 delayimp.h
MSVC include
디렉터리에서 delayhlp.cpp
찾을 수 있습니다. 대상 아키텍처에 대한 MSVC lib
디렉터리로 delayimp.lib
컴파일됩니다. 사용자 고유의 도우미 함수를 작성하지 않는 한 컴파일에 이 라이브러리를 포함해야 합니다.
로드 도우미 호출 규칙, 매개 변수 및 반환 형식 지연
지연 로드 도우미 루틴의 프로토타입은 다음과 같습니다.
FARPROC WINAPI __delayLoadHelper2(
PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry
);
매개 변수
pidd
const
다양한 가져오기 관련 데이터의 오프셋, 바인딩 정보에 대한 타임스탬프 및 설명자 콘텐츠에 대한 추가 정보를 제공하는 특성 집합을 포함하는 포인터 ImgDelayDescr
입니다. 현재 설명자의 주소가 상대 가상 주소임을 나타내는 특성 dlattrRva
이 하나만 있습니다. 자세한 내용은 의 선언을 참조하세요 delayimp.h
.
지연 설명자(ImgDelayDescr
in)의 delayimp.h
포인터는 상대 RVA(가상 주소)를 사용하여 32비트 및 64비트 프로그램에서 모두 예상대로 작동합니다. 이를 사용하려면 다음에서 찾은 함수 PFromRva
를 사용하여 이러한 RVA를 포인터로 다시 변환합니다 delayhlp.cpp
. 설명자의 각 필드에서 이 함수를 사용하여 32비트 또는 64비트 포인터로 다시 변환할 수 있습니다. 기본 지연 로드 도우미 함수는 예제로 사용하기에 좋은 템플릿입니다.
구조체의 정의는 PCImgDelayDescr
구조 및 상수 정의를 참조 하세요.
ppfnIATEntry
지연 로드 IAT(가져오기 주소 테이블)의 슬롯에 대한 포인터입니다. 가져온 함수의 주소로 업데이트되는 슬롯입니다. 도우미 루틴은 이 위치에 반환하는 것과 동일한 값을 저장해야 합니다.
예상 반환 값
도우미 함수가 성공하면 가져온 함수의 주소를 반환합니다.
함수가 실패하면 구조화된 예외가 발생하며 0을 반환합니다. 다음과 같은 3가지 형식의 예외가 발생할 수 있습니다.
잘못된 매개 변수.
pidd
의 특성을 제대로 지정하지 않으면 발생합니다. 이를 복구할 수 없는 오류로 처리합니다.지정된 DLL에서 실패한
LoadLibrary
GetProcAddress
의 실패
이러한 예외를 처리하는 것은 사용자의 책임입니다. 자세한 내용은 오류 처리 및 알림을 참조하세요.
설명
도우미 함수의 호출 규칙은 __stdcall
입니다. 반환 값의 형식은 관련이 없으므로 FARPROC
사용됩니다. 이 함수에는 C++ 코드로 extern "C"
선언될 때 래핑되어야 하는 C 링크가 있습니다. 매크로는 ExternC
이 래퍼를 처리합니다.
도우미 루틴을 알림 후크로 사용하려면 코드에서 반환할 적절한 함수 포인터를 지정해야 합니다. 그러면 링커가 생성한 썽크 코드가 해당 반환 값을 가져오기의 실제 대상으로 인식하여 해당 대상으로 직접 이동합니다. 도우미 루틴을 알림 후크로 사용하지 않으려면 도우미 함수 ppfnIATEntry
의 반환 값인 전달된 함수 포인터 위치에 저장합니다.
샘플 후크 함수
다음 코드는 기본 후크 함수를 구현하는 방법을 보여줍니다.
FARPROC WINAPI delayHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
switch (dliNotify) {
case dliStartProcessing :
// If you want to return control to the helper, return 0.
// Otherwise, return a pointer to a FARPROC helper function
// that will be used instead, thereby bypassing the rest
// of the helper.
break;
case dliNotePreLoadLibrary :
// If you want to return control to the helper, return 0.
// Otherwise, return your own HMODULE to be used by the
// helper instead of having it call LoadLibrary itself.
break;
case dliNotePreGetProcAddress :
// If you want to return control to the helper, return 0.
// If you choose you may supply your own FARPROC function
// address and bypass the helper's call to GetProcAddress.
break;
case dliFailLoadLib :
// LoadLibrary failed.
// If you don't want to handle this failure yourself, return 0.
// In this case the helper will raise an exception
// (ERROR_MOD_NOT_FOUND) and exit.
// If you want to handle the failure by loading an alternate
// DLL (for example), then return the HMODULE for
// the alternate DLL. The helper will continue execution with
// this alternate DLL and attempt to find the
// requested entrypoint via GetProcAddress.
break;
case dliFailGetProc :
// GetProcAddress failed.
// If you don't want to handle this failure yourself, return 0.
// In this case the helper will raise an exception
// (ERROR_PROC_NOT_FOUND) and exit.
// If you choose, you may handle the failure by returning
// an alternate FARPROC function address.
break;
case dliNoteEndProcessing :
// This notification is called after all processing is done.
// There is no opportunity for modifying the helper's behavior
// at this point except by longjmp()/throw()/RaiseException.
// No return value is processed.
break;
default :
return NULL;
}
return NULL;
}
/*
and then at global scope somewhere:
ExternC const PfnDliHook __pfnDliNotifyHook2 = delayHook;
ExternC const PfnDliHook __pfnDliFailureHook2 = delayHook;
*/
지연 부하 구조 및 상수 정의
기본 지연 로드 도우미 루틴은 여러 구조를 사용하여 후크 함수와 통신하고 예외를 발생합니다. 이러한 구조체는 에 정의되어 있습니다 delayimp.h
. 다음은 매크로, typedefs, 알림 및 실패 값, 정보 구조 및 후크에 전달된 포인터-후크 함수 형식입니다.
#define _DELAY_IMP_VER 2
#if defined(__cplusplus)
#define ExternC extern "C"
#else
#define ExternC extern
#endif
typedef IMAGE_THUNK_DATA * PImgThunkData;
typedef const IMAGE_THUNK_DATA * PCImgThunkData;
typedef DWORD RVA;
typedef struct ImgDelayDescr {
DWORD grAttrs; // attributes
RVA rvaDLLName; // RVA to dll name
RVA rvaHmod; // RVA of module handle
RVA rvaIAT; // RVA of the IAT
RVA rvaINT; // RVA of the INT
RVA rvaBoundIAT; // RVA of the optional bound IAT
RVA rvaUnloadIAT; // RVA of optional copy of original IAT
DWORD dwTimeStamp; // 0 if not bound,
// O.W. date/time stamp of DLL bound to (Old BIND)
} ImgDelayDescr, * PImgDelayDescr;
typedef const ImgDelayDescr * PCImgDelayDescr;
enum DLAttr { // Delay Load Attributes
dlattrRva = 0x1, // RVAs are used instead of pointers
// Having this set indicates a VC7.0
// and above delay load descriptor.
};
//
// Delay load import hook notifications
//
enum {
dliStartProcessing, // used to bypass or note helper only
dliNoteStartProcessing = dliStartProcessing,
dliNotePreLoadLibrary, // called just before LoadLibrary, can
// override w/ new HMODULE return val
dliNotePreGetProcAddress, // called just before GetProcAddress, can
// override w/ new FARPROC return value
dliFailLoadLib, // failed to load library, fix it by
// returning a valid HMODULE
dliFailGetProc, // failed to get proc address, fix it by
// returning a valid FARPROC
dliNoteEndProcessing, // called after all processing is done, no
// bypass possible at this point except
// by longjmp()/throw()/RaiseException.
};
typedef struct DelayLoadProc {
BOOL fImportByName;
union {
LPCSTR szProcName;
DWORD dwOrdinal;
};
} DelayLoadProc;
typedef struct DelayLoadInfo {
DWORD cb; // size of structure
PCImgDelayDescr pidd; // raw form of data (everything is there)
FARPROC * ppfn; // points to address of function to load
LPCSTR szDll; // name of dll
DelayLoadProc dlp; // name or ordinal of procedure
HMODULE hmodCur; // the hInstance of the library we have loaded
FARPROC pfnCur; // the actual function that will be called
DWORD dwLastError;// error received (if an error notification)
} DelayLoadInfo, * PDelayLoadInfo;
typedef FARPROC (WINAPI *PfnDliHook)(
unsigned dliNotify,
PDelayLoadInfo pdli
);
지연 로드에 필요한 값 계산
지연 로드 도우미 루틴은 중요한 두 가지 정보를 계산해야 합니다. 이를 돕기 위해 이 정보를 계산하는 두 가지 인라인 함수 delayhlp.cpp
가 있습니다.
첫째,
IndexFromPImgThunkData
현재 가져오기의 인덱스를 세 개의 다른 테이블(IAT(가져오기 주소 테이블), 바인딩된 BIAT(가져오기 주소 테이블) 및 UIAT(언바운드 가져오기 주소 테이블)로 계산합니다.두 번째,
CountOfImports
유효한 IAT의 가져오기 수를 계산합니다.
// utility function for calculating the index of the current import
// for all the tables (INT, BIAT, UIAT, and IAT).
__inline unsigned
IndexFromPImgThunkData(PCImgThunkData pitdCur, PCImgThunkData pitdBase) {
return pitdCur - pitdBase;
}
// utility function for calculating the count of imports given the base
// of the IAT. NB: this only works on a valid IAT!
__inline unsigned
CountOfImports(PCImgThunkData pitdBase) {
unsigned cRet = 0;
PCImgThunkData pitd = pitdBase;
while (pitd->u1.Function) {
pitd++;
cRet++;
}
return cRet;
}
지연 로드된 DLL의 언로드 지원
지연 로드된 DLL이 로드되면 기본 지연 로드 도우미는 지연 로드 설명자에 필드에 포인터와 원래 IAT(가져오기 주소 테이블) pUnloadIAT
의 복사본이 있는지 확인합니다. 이 경우 도우미는 목록의 포인터를 가져오기 지연 설명자에 저장합니다. 이 항목을 사용하면 도우미 함수가 DLL을 명시적으로 언로드할 수 있도록 이름으로 DLL을 찾을 수 있습니다.
지연 로드된 DLL을 명시적으로 언로드하기 위한 관련 구조 및 함수는 다음과 같습니다.
//
// Unload support from delayimp.h
//
// routine definition; takes a pointer to a name to unload
ExternC
BOOL WINAPI
__FUnloadDelayLoadedDLL2(LPCSTR szDll);
// structure definitions for the list of unload records
typedef struct UnloadInfo * PUnloadInfo;
typedef struct UnloadInfo {
PUnloadInfo puiNext;
PCImgDelayDescr pidd;
} UnloadInfo;
// from delayhlp.cpp
// the default delay load helper places the unloadinfo records in the
// list headed by the following pointer.
ExternC
PUnloadInfo __puiHead;
구조체는 UnloadInfo
각각 C++ 클래스를 사용하고 LocalAlloc
해당 클래스와 LocalFree
operator delete
구현 operator new
을 사용하여 구현됩니다. 이러한 옵션은 목록의 머리글로 사용하는 __puiHead
표준 연결된 목록에 유지됩니다.
호출 __FUnloadDelayLoadedDLL
할 때 로드된 DLL 목록에서 제공하는 이름을 찾으려고 시도합니다. (정확한 일치 항목이 필요합니다.) 발견되면 IAT의 복사본이 실행 중인 IAT pUnloadIAT
의 맨 위에 복사되어 thunk 포인터를 복원합니다. 그런 다음 라이브러리를 사용하여 FreeLibrary
해제하고 일치하는 UnloadInfo
레코드가 목록에서 연결 해제되고 삭제되고 TRUE
반환됩니다.
함수 __FUnloadDelayLoadedDLL2
에 대한 인수는 대/소문자를 구분합니다. 예를 들어 다음을 지정합니다.
__FUnloadDelayLoadedDLL2("user32.dll");
및 not:
__FUnloadDelayLoadedDLL2("User32.DLL");
지연 로드된 DLL을 언로드하는 예제는 지연 로드된 DLL을 명시적으로 언로드하는 것을 참조 하세요.
사용자 고유의 지연 로드 도우미 함수 개발
고유한 버전의 지연 로드 도우미 루틴을 제공할 수 있습니다. 사용자 고유의 루틴에서 DLL 또는 가져오기의 이름을 기반으로 특정 처리를 수행할 수 있습니다. 사용자 고유의 코드를 삽입하는 방법에는 제공된 코드에 따라 사용자 고유의 도우미 함수를 코딩하는 두 가지 방법이 있습니다. 또는 제공된 도우미를 후크하여 알림 후크를 사용하여 사용자 고유의 함수를 호출합니다.
사용자 고유의 도우미 코딩
사용자 고유의 도우미 루틴을 만드는 것은 간단합니다. 기존 코드를 새 함수에 대한 지침으로 사용할 수 있습니다. 함수는 기존 도우미와 동일한 호출 규칙을 사용해야 합니다. 그리고 링커에서 생성된 thunks로 돌아가면 적절한 함수 포인터를 반환해야 합니다. 코드를 만든 후에는 통화를 충족하거나 통화에서 벗어날 수 있습니다.
시작 처리 알림 후크 사용
알림의 기본 도우미와 동일한 값을 사용하는 사용자 제공 알림 후크 함수에 대한 새 포인터를 제공하는 것이 가장 쉽습니다 dliStartProcessing
. 이 시점에서 후크 함수는 기본적으로 새 도우미 함수가 될 수 있습니다. 기본 도우미로의 성공적인 반환은 기본 도우미의 모든 추가 처리를 무시하기 때문입니다.