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 DLLWywołania
GetProcAddress
próby pobrania adresu proceduryZwraca 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 pomocnikaTuż przed
GetProcAddress
wywołaniem funkcji pomocnikaJeśli wywołanie metody
LoadLibrary
w funkcji pomocniczej zakończy się niepowodzeniemJeśli wywołanie metody
GetProcAddress
w funkcji pomocniczej zakończy się niepowodzeniemPo 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 , dlattrRva
któ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 ppfnIATEntry
przekazywanej 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 __FUnloadDelayLoadedDLL
metody 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