Partager via


Comprendre la fonction d’assistance au chargement différé

La fonction d’assistance pour le chargement différé pris en charge par l’éditeur de liens est ce qui charge réellement la DLL au moment de l’exécution. Vous pouvez modifier la fonction d’assistance pour personnaliser son comportement. Au lieu d’utiliser la fonction d’assistance fournie dans delayimp.lib, écrivez votre propre fonction et liez-la à votre programme. Une fonction d’assistance sert toutes les DLL chargées de retard.

Vous pouvez fournir votre propre version de la fonction d’assistance si vous souhaitez effectuer un traitement spécifique en fonction des noms de la DLL ou des importations.

La fonction d’assistance effectue les actions suivantes :

  • Vérifie le handle stocké dans la bibliothèque pour voir s’il a déjà été chargé

  • Appels LoadLibrary pour tenter de charger la DLL

  • Appels GetProcAddress pour tenter d’obtenir l’adresse de la procédure

  • Retourne au jeu de chargement de l’importation différée pour appeler le point d’entrée maintenant chargé

La fonction d’assistance peut rappeler un hook de notification dans votre programme après chacune des actions suivantes :

  • Lorsque la fonction d’assistance démarre

  • Juste avant LoadLibrary d’être appelé dans la fonction d’assistance

  • Juste avant GetProcAddress d’être appelé dans la fonction d’assistance

  • Si l’appel à LoadLibrary la fonction d’assistance échoue

  • Si l’appel à GetProcAddress la fonction d’assistance échoue

  • Une fois la fonction d’assistance terminée, traitement

Chacun de ces points de crochet peut retourner une valeur qui modifie le traitement normal de la routine d’assistance d’une certaine manière, à l’exception du retour au jeu de charge d’importation différé.

Le code d’assistance par défaut se trouve dans delayhlp.cpp et delayimp.h dans le répertoire MSVC include . Il est compilé delayimp.lib dans le répertoire MSVC lib pour votre architecture cible. Vous devez inclure cette bibliothèque dans vos compilations, sauf si vous écrivez votre propre fonction d’assistance.

Retarder les conventions d’appel d’assistance de chargement, les paramètres et le type de retour

Le prototype de la routine d’assistance au chargement différé est le suivant :

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

Paramètres

pidd
Pointeur const vers un ImgDelayDescr qui contient les décalages de différentes données liées à l’importation, un horodatage pour les informations de liaison et un ensemble d’attributs qui fournissent des informations supplémentaires sur le contenu du descripteur. Actuellement, il n’existe qu’un seul attribut, dlattrRvace qui indique que les adresses du descripteur sont des adresses virtuelles relatives. Pour plus d’informations, consultez les déclarations dans delayimp.h.

Les pointeurs du descripteur de délai (ImgDelayDescr en delayimp.h) utilisent des adresses virtuelles relatives (RVAs) pour fonctionner comme prévu dans les programmes 32 bits et 64 bits. Pour les utiliser, convertissez ces RVAs en pointeurs à l’aide de la fonction PFromRva, trouvée dans delayhlp.cpp. Vous pouvez utiliser cette fonction sur chacun des champs du descripteur pour les convertir en pointeurs 32 bits ou 64 bits. La fonction d’assistance de chargement de retard par défaut est un bon modèle à utiliser comme exemple.

Pour obtenir la définition de la PCImgDelayDescr structure, consultez Les définitions de structure et de constante.

ppfnIATEntry
Pointeur vers un emplacement dans la table d’adresses d’importation de chargement différée (IAT). Il s’agit de l’emplacement mis à jour avec l’adresse de la fonction importée. La routine d’assistance doit stocker la même valeur qu’elle retourne à cet emplacement.

Valeurs de retour attendues

Si la fonction d’assistance réussit, elle retourne l’adresse de la fonction importée.

Si la fonction échoue, elle déclenche une exception structurée et retourne 0. Trois types d'exceptions peuvent être levées :

  • paramètre non valide, ce qui se produit si les attributs dans pidd ne sont pas spécifiés correctement ; Traitez-le comme une erreur irrécupérable.

  • LoadLibrary a échoué sur la DLL spécifiée ;

  • échec de GetProcAddress.

Il vous incombe de gérer ces exceptions. Pour plus d’informations, consultez Gestion et notification des erreurs.

Notes

La convention d’appel pour la fonction d’assistance est __stdcall. Le type de la valeur de retour n’est pas pertinent. Il est donc FARPROC utilisé. Cette fonction a une liaison C, ce qui signifie qu’elle doit être encapsulée extern "C" lorsqu’elle est déclarée dans du code C++. La ExternC macro s’occupe de ce wrapper pour vous.

Pour utiliser votre routine d’assistance comme hook de notification, votre code doit spécifier le pointeur de fonction approprié à retourner. Le code thunk que l'éditeur de liens génère prend ensuite cette valeur de retour comme cible réelle de l'importation et y accède directement. Si vous ne souhaitez pas utiliser votre routine d’assistance en tant que hook de notification, stockez la valeur de retour de la fonction d’assistance dans ppfnIATEntry, l’emplacement du pointeur de fonction passé.

Exemple de fonction de raccordement

Le code suivant montre comment implémenter une fonction de hook de base.

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

Retarder la structure de chargement et les définitions de constantes

La routine d’assistance de chargement de retard par défaut utilise plusieurs structures pour communiquer avec les fonctions de hook et pendant toutes les exceptions. Ces structures sont définies dans delayimp.h. Voici les macros, les typesdefs, les valeurs de notification et d’échec, les structures d’informations et le type de fonction pointeur vers hook transmis aux hooks :

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

Calculer les valeurs nécessaires pour le chargement différé

La routine d’assistance de chargement de retard doit calculer deux informations critiques. Pour vous aider, il existe deux fonctions inline dans delayhlp.cpp le calcul de ces informations.

  • Le premier, IndexFromPImgThunkDatacalcule l’index de l’importation actuelle dans les trois tables différentes (table d’adresses d’importation (IAT), la table d’adresses d’importation liée (BIAT) et la table d’adresses d’importation non liée (UIAT)).

  • Le deuxième, CountOfImportscompte le nombre d’importations dans un IAT valide.

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

Prise en charge du déchargement d’une DLL chargée en retard

Lorsqu’une DLL chargée de retard est chargée, l’assistance de chargement différée par défaut vérifie si les descripteurs de chargement différé ont un pointeur et une copie de la table d’adresses d’importation d’origine (IAT) dans le pUnloadIAT champ. Dans ce cas, l’assistance enregistre un pointeur dans une liste vers le descripteur de délai d’importation. Cette entrée permet à la fonction d’assistance de rechercher la DLL par nom pour prendre en charge le déchargement explicite de cette DLL.

Voici les structures et fonctions associées pour décharger explicitement une DLL chargée de retard :

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

La UnloadInfo structure est implémentée à l’aide d’une classe C++ qui utilise et LocalFree implémente LocalAlloc respectivement ses operator new et operator deleteleurs implémentations. Ces options sont conservées dans une liste liée standard utilisée __puiHead comme tête de la liste.

Lorsque vous appelez __FUnloadDelayLoadedDLL, il tente de trouver le nom que vous fournissez dans la liste des DLL chargées. (Une correspondance exacte est requise.) Si elle est trouvée, la copie de l’IAT est pUnloadIAT copiée en haut de l’IAT en cours d’exécution pour restaurer les pointeurs thunk. Ensuite, la bibliothèque est libérée à l’aide FreeLibraryde , l’enregistrement correspondant UnloadInfo est dissocié de la liste et supprimé, et TRUE est retourné.

L’argument de la fonction respecte la casse __FUnloadDelayLoadedDLL2 . Par exemple, vous devez spécifier :

__FUnloadDelayLoadedDLL2("user32.dll");

et non :

__FUnloadDelayLoadedDLL2("User32.DLL");

Pour obtenir un exemple de déchargement d’une DLL chargée de retard, consultez Décharger explicitement une DLL chargée de retard.

Développer votre propre fonction d’assistance de chargement différée

Vous pouvez fournir votre propre version de la routine d’assistance de chargement différée. Dans votre propre routine, vous pouvez effectuer un traitement spécifique en fonction des noms de la DLL ou des importations. Il existe deux façons d’insérer votre propre code : coder votre propre fonction d’assistance, éventuellement en fonction du code fourni. Ou, raccordez l’assistance fournie pour appeler votre propre fonction à l’aide des hooks de notification.

Coder votre propre assistance

La création de votre propre routine d’assistance est simple. Vous pouvez utiliser le code existant comme guide pour votre nouvelle fonction. Votre fonction doit utiliser les mêmes conventions d’appel que l’assistance existante. Et, si elle revient aux thunks générés par l’éditeur de liens, elle doit retourner un pointeur de fonction approprié. Une fois que vous avez créé votre code, vous pouvez satisfaire l’appel ou sortir de l’appel, mais vous le souhaitez.

Utiliser le hook de notification de traitement de démarrage

Il est probablement plus simple de fournir un nouveau pointeur vers une fonction de hook de notification fournie par l’utilisateur qui prend les mêmes valeurs que l’assistance par défaut pour la dliStartProcessing notification. À ce stade, la fonction de hook peut essentiellement devenir la nouvelle fonction d’assistance, car un retour réussi à l’assistance par défaut contourne tout traitement supplémentaire dans l’assistance par défaut.

Voir aussi

Prise en charge de l’éditeur de liens pour les DLL chargées en retard