다음을 통해 공유


지연 로드 도우미 함수 이해

링커 지원 지연 로드에 대한 도우미 함수는 실제로 런타임에 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 . 이 시점에서 후크 함수는 기본적으로 새 도우미 함수가 될 수 있습니다. 기본 도우미로의 성공적인 반환은 기본 도우미의 모든 추가 처리를 무시하기 때문입니다.

참고 항목

지연 로드된 DLL에 대한 링커 지원