Omówienie funkcji pomocnika opóźniania ładowania

Funkcja pomocnika do ładowania opóźnionego obsługiwanego przez konsolidatora jest tym, co faktycznie ładuje bibliotekę DLL w czasie wykonywania. Możesz zmodyfikować funkcję pomocnika, aby dostosować jej zachowanie. Zamiast używać dostarczonej funkcji pomocniczej w programie delayimp.lib, napisz własną funkcję i połącz ją z programem. Jedna funkcja pomocnika obsługuje wszystkie opóźnione biblioteki DLL.

Możesz podać własną wersję funkcji pomocniczej, jeśli chcesz wykonać określone przetwarzanie na podstawie nazw bibliotek DLL lub importów.

Funkcja pomocnika wykonuje następujące akcje:

  • Sprawdza, czy przechowywany uchwyt do biblioteki został już załadowany

  • Wywołania LoadLibrary próby załadowania biblioteki DLL

  • Wywołania GetProcAddress próby pobrania adresu procedury

  • Zwraca opóźnienie ładowania importu thunk w celu wywołania teraz załadowanego punktu wejścia

Funkcja pomocnika może wywołać element zaczepienia powiadomień w programie po każdej z następujących akcji:

  • Po uruchomieniu funkcji pomocnika

  • Tuż przed LoadLibrary wywołaniem funkcji pomocnika

  • Tuż przed GetProcAddress wywołaniem funkcji pomocnika

  • Jeśli wywołanie metody LoadLibrary w funkcji pomocniczej zakończy się niepowodzeniem

  • Jeśli wywołanie metody GetProcAddress w funkcji pomocniczej zakończy się niepowodzeniem

  • Po zakończeniu przetwarzania funkcji pomocnika

Każdy z tych punktów zaczepienia może zwrócić wartość, która zmienia normalne przetwarzanie procedury pomocnika w jakiś sposób, z wyjątkiem powrotu do opóźnienia obciążenia importu thunk.

Domyślny kod pomocnika można znaleźć w delayhlp.cpp katalogu MSVC include i delayimp.h . Jest on kompilowany delayimp.lib w katalogu MSVC lib dla architektury docelowej. Musisz dołączyć tę bibliotekę do kompilacji, chyba że napiszesz własną funkcję pomocnika.

Opóźnianie wywoływania konwencji, parametrów i zwracanych typów pomocnika ładowania

Prototypem procedury pomocniczej opóźniania obciążenia jest:

FARPROC WINAPI __delayLoadHelper2(
    PCImgDelayDescr pidd,
    FARPROC * ppfnIATEntry
);

Parametry

pidd
Wskaźnik const do elementu ImgDelayDescr , który zawiera przesunięcia różnych danych związanych z importowaniem, sygnaturę czasową informacji o powiązaniu i zestaw atrybutów, które zawierają dalsze informacje o zawartości deskryptora. Obecnie istnieje tylko jeden atrybut , dlattrRvaktóry wskazuje, że adresy w deskryptorze są względnymi adresami wirtualnymi. Aby uzyskać więcej informacji, zobacz deklaracje w pliku delayimp.h.

Wskaźniki w deskryptorze opóźnienia (ImgDelayDescr w delayimp.h) używają względnych adresów wirtualnych (RVA) do działania zgodnie z oczekiwaniami zarówno w programach 32-bitowych, jak i 64-bitowych. Aby ich używać, przekonwertuj te RVA z powrotem na wskaźniki przy użyciu funkcji PFromRva, znajdującej się w pliku delayhlp.cpp. Tej funkcji można użyć w każdym polu deskryptora, aby przekonwertować je z powrotem na 32-bitowe lub 64-bitowe wskaźniki. Domyślna funkcja pomocnika ładowania opóźnień jest dobrym szablonem do użycia jako przykład.

Aby zapoznać się z definicją PCImgDelayDescr struktury, zobacz Definicje struktury i stałej.

ppfnIATEntry
Wskaźnik do miejsca w tabeli adresów importu ładowania opóźnienia (IAT). Jest to miejsce zaktualizowane przy użyciu adresu zaimportowanej funkcji. Procedury pomocnika muszą przechowywać tę samą wartość, która jest zwracana do tej lokalizacji.

Oczekiwane wartości zwracane

Jeśli funkcja pomocnika zakończy się pomyślnie, zwraca adres zaimportowanej funkcji.

Jeśli funkcja nie powiedzie się, zgłasza wyjątek ze strukturą i zwraca wartość 0. Można zgłaszać trzy typy wyjątków:

  • Nieprawidłowy parametr, który występuje, jeśli atrybuty w pliku pidd nie są poprawnie określone. Traktuj to jako nieodwracalny błąd.

  • LoadLibrary nie powiodło się w określonej dll.

  • Błąd .GetProcAddress

Twoim obowiązkiem jest obsługa tych wyjątków. Aby uzyskać więcej informacji, zobacz Obsługa błędów i powiadamianie.

Uwagi

Konwencją wywoływania funkcji pomocniczej jest __stdcall. Typ wartości zwracanej nie jest istotny, więc FARPROC jest używany. Ta funkcja ma połączenie języka C, co oznacza, że musi być opakowana po extern "C" zadeklarowaniu w kodzie C++. Makro ExternC zajmuje się tą otoczką.

Aby użyć procedury pomocnika jako punktu zaczepienia powiadomień, kod musi określić odpowiedni wskaźnik funkcji, aby zwrócić. Kod thunk, który generuje konsolidator, pobiera wtedy wartość zwracaną jako rzeczywisty element docelowy importu i przechodzi bezpośrednio do niego. Jeśli nie chcesz używać procedury pomocniczej jako elementu zaczepienia powiadomień, zapisz wartość zwracaną funkcji pomocnika w lokalizacji ppfnIATEntryprzekazywanej wskaźnika funkcji.

Przykładowa funkcja haka

Poniższy kod pokazuje, jak zaimplementować podstawową funkcję haka.

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;
*/

Opóźnij strukturę obciążenia i stałe definicje

Domyślna rutyna pomocnika opóźnienia używa kilku struktur do komunikowania się z funkcjami haka i podczas wszelkich wyjątków. Te struktury są definiowane w pliku delayimp.h. Poniżej przedstawiono makra, definicje typów, wartości powiadomień i błędów, struktury informacji i typ funkcji wskaźnik-hak przekazywany do punktów zaczepienia:

#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
    );

Obliczanie niezbędnych wartości dla ładowania opóźnionego

Procedury pomocnika opóźniania obciążenia muszą obliczyć dwie krytyczne informacje. Aby pomóc, istnieją dwie wbudowane funkcje w delayhlp.cpp celu obliczenia tych informacji.

  • Pierwszy, IndexFromPImgThunkData, oblicza indeks bieżącego importu do trzech różnych tabel (import address table (IAT), bound import address table (BIAT) i unbound import address table (UIAT)).

  • Druga, CountOfImports, zlicza liczbę importów w prawidłowym 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;
    }

Obsługa zwalniania biblioteki DLL załadowanej z opóźnieniem

Po załadowaniu biblioteki DLL ładowanej z opóźnieniem domyślny pomocnik opóźniania ładowania sprawdza, czy deskryptory opóźniania ładowania mają wskaźnik i kopię oryginalnej tabeli adresów importu (IAT) w pUnloadIAT polu. Jeśli tak, pomocnik zapisuje wskaźnik na liście do deskryptora opóźnienia importu. Ten wpis umożliwia funkcji pomocnika znalezienie biblioteki DLL według nazwy w celu obsługi jawnego zwolnienia tej biblioteki DLL.

Poniżej przedstawiono skojarzone struktury i funkcje służące do jawnego zwalniania biblioteki DLL ładowanej z opóźnieniem:

//
// 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;

Struktura UnloadInfo jest implementowana przy użyciu klasy C++, która używa LocalAlloc i implementacji odpowiednio jako i LocalFree operator new operator delete. Te opcje są przechowywane na standardowej liście połączonej, która używa __puiHead jako szef listy.

Podczas wywoływania __FUnloadDelayLoadedDLLmetody program próbuje znaleźć nazwę podaną na liście załadowanych bibliotek DLL. (Wymagane jest dokładne dopasowanie). Jeśli zostanie znaleziona, kopia pliku IAT w pUnloadIAT pliku zostanie skopiowana na górę działającego systemu IAT w celu przywrócenia wskaźników thunk. Następnie biblioteka zostanie zwolniona przy użyciu polecenia FreeLibrary, pasujący UnloadInfo rekord zostanie odłączony od listy i usunięty i TRUE zostanie zwrócony.

Argument funkcji __FUnloadDelayLoadedDLL2 uwzględnia wielkość liter. Można na przykład określić:

__FUnloadDelayLoadedDLL2("user32.dll");

a nie:

__FUnloadDelayLoadedDLL2("User32.DLL");

Aby zapoznać się z przykładem zwalniania biblioteki DLL ładowanej z opóźnieniem, zobacz Jawne zwalnianie biblioteki DLL załadowanej z opóźnieniem.

Opracowywanie własnej funkcji pomocnika ładowania opóźnień

Możesz podać własną wersję procedury pomocnika opóźniania ładowania. We własnej procedurze można wykonywać określone przetwarzanie na podstawie nazw bibliotek DLL lub importów. Istnieją dwa sposoby wstawiania własnego kodu: Koduj własną funkcję pomocnika, prawdopodobnie na podstawie podanego kodu. Możesz też podłączyć dostarczonego pomocnika, aby wywołać własną funkcję przy użyciu haków powiadomień.

Kod własnego pomocnika

Tworzenie własnej procedury pomocniczej jest proste. Możesz użyć istniejącego kodu jako przewodnika dla nowej funkcji. Funkcja musi używać tych samych konwencji wywoływania co istniejący pomocnik. A jeśli powróci do konsolidatora wygenerowanego przez konsolidator thunks, musi zwrócić prawidłowy wskaźnik funkcji. Po utworzeniu kodu możesz wykonać wywołanie lub wydostać się z wywołania, jednak chcesz.

Używanie przypinania powiadomień rozpoczęcia przetwarzania

Prawdopodobnie najłatwiej jest podać nowy wskaźnik dla funkcji zaczepienia powiadomień dostarczonej przez użytkownika, która przyjmuje te same wartości co domyślny pomocnik dla dliStartProcessing powiadomienia. W tym momencie funkcja haka może zasadniczo stać się nową funkcją pomocnika, ponieważ udany powrót do domyślnego pomocnika pomija wszystkie dalsze przetwarzanie w domyślnym pomocniku.

Zobacz też

Obsługa konsolidatora dla bibliotek DLL ładowanych z opóźnieniem