1
0
forked from 0ad/0ad
0ad/source/lib/sysdep/win/wdll_delay_load.cpp
janwas d802b73d94 winit: rename register macros for more clarity
wdbg: cleanup, improve exception catcher (previously potentially failed
if __try block came in non-main thread). required since wstartup no
longer commandeers the entry point.
winit, wstartup: update documentation

This was SVN commit r5141.
2007-06-04 22:59:14 +00:00

463 lines
14 KiB
C++

/**
* =========================================================================
* File : wdll_delay_load.cpp
* Project : 0 A.D.
* Description : DLL delay loading and notification
* =========================================================================
*/
// license: GPL; see lib/license.txt
#include "precompiled.h"
#include "wdll_delay_load.h"
#include "lib/sysdep/cpu.h"
#include "win.h"
#include "winit.h"
WINIT_REGISTER_LATE_SHUTDOWN2(wdll_Shutdown); // last - DLLs are unloaded here
//-----------------------------------------------------------------------------
// delay loading (modified from VC7 DelayHlp.cpp and DelayImp.h)
#define FACILITY_VISUALCPP ((LONG)0x6d)
#define VcppException(sev,err) ((sev) | (FACILITY_VISUALCPP<<16) | err)
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.
};
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
// 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);
//-----------------------------------------------------------------------------
// load notification
static WdllLoadNotify* notify_list;
void wdll_add_notify(WdllLoadNotify* notify)
{
notify->next = notify_list;
notify_list = notify;
}
static FARPROC WINAPI notify_hook(unsigned dliNotify, PDelayLoadInfo pdli)
{
if(dliNotify != dliNoteEndProcessing)
return 0;
for(WdllLoadNotify* n = notify_list; n; n = n->next)
if(strncasecmp(pdli->szDll, n->dll_name, strlen(n->dll_name)) == 0)
n->func();
return 0;
}
//-----------------------------------------------------------------------------
// hook
// The "notify hook" gets called for every call to the
// delay load helper. This allows a user to hook every call and
// skip the delay load helper entirely.
//
// dliNotify == {
// dliStartProcessing |
// dliNotePreLoadLibrary |
// dliNotePreGetProc |
// dliNoteEndProcessing}
// on this call.
//
EXTERN_C PfnDliHook __pfnDliNotifyHook2 = notify_hook;
// This is the failure hook, dliNotify = {dliFailLoadLib|dliFailGetProc}
EXTERN_C PfnDliHook __pfnDliFailureHook2 = 0;
#pragma intrinsic(strlen,memcmp,memcpy)
// 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 (unsigned) (pitdCur - pitdBase);
}
// C++ template utility function for converting RVAs to pointers
//
extern "C" const IMAGE_DOS_HEADER __ImageBase;
template <class X>
X PFromRva(RVA rva) {
return X(PBYTE(&__ImageBase) + rva);
}
// structure definitions for the list of unload records
typedef struct UnloadInfo * PUnloadInfo;
typedef struct UnloadInfo {
PUnloadInfo puiNext;
PCImgDelayDescr pidd;
} UnloadInfo;
// 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;
}
extern "C" PUnloadInfo __puiHead = 0;
#include "lib/nommgr.h"
struct ULI : public UnloadInfo
{
ULI(PCImgDelayDescr pidd_)
{
pidd = pidd_;
Link();
}
~ULI() { Unlink(); }
void* operator new(size_t cb) { return ::LocalAlloc(LPTR, cb); }
void operator delete(void* pv) { ::LocalFree(pv); }
void Unlink()
{
PUnloadInfo* ppui = &__puiHead;
while (*ppui && *ppui != this)
ppui = &((*ppui)->puiNext);
if (*ppui == this)
*ppui = puiNext;
}
void Link()
{
puiNext = __puiHead;
__puiHead = this;
}
};
#include "lib/mmgr.h"
// For our own internal use, we convert to the old
// format for convenience.
//
struct InternalImgDelayDescr {
DWORD grAttrs; // attributes
LPCSTR szName; // pointer to dll name
HMODULE * phmod; // address of module handle
PImgThunkData pIAT; // address of the IAT
PCImgThunkData pINT; // address of the INT
PCImgThunkData pBoundIAT; // address of the optional bound IAT
PCImgThunkData pUnloadIAT; // address of optional copy of original IAT
DWORD dwTimeStamp; // 0 if not bound,
// O.W. date/time stamp of DLL bound to (Old BIND)
};
typedef InternalImgDelayDescr * PIIDD;
typedef const InternalImgDelayDescr * PCIIDD;
static inline PIMAGE_NT_HEADERS WINAPI
PinhFromImageBase(HMODULE hmod) {
return PIMAGE_NT_HEADERS(PBYTE(hmod) + PIMAGE_DOS_HEADER(hmod)->e_lfanew);
}
static inline void WINAPI
OverlayIAT(PImgThunkData pitdDst, PCImgThunkData pitdSrc) {
cpu_memcpy(pitdDst, pitdSrc, CountOfImports(pitdDst) * sizeof IMAGE_THUNK_DATA);
}
static inline DWORD WINAPI
TimeStampOfImage(PIMAGE_NT_HEADERS pinh) {
return pinh->FileHeader.TimeDateStamp;
}
static inline bool WINAPI
FLoadedAtPreferredAddress(PIMAGE_NT_HEADERS pinh, HMODULE hmod) {
return UINT_PTR(hmod) == pinh->OptionalHeader.ImageBase;
}
extern "C" FARPROC WINAPI __delayLoadHelper2(PCImgDelayDescr pidd, FARPROC* ppfnIATEntry)
{
// Set up some data we use for the hook procs but also useful for
// our own use
//
InternalImgDelayDescr idd = {
pidd->grAttrs,
PFromRva<LPCSTR>(pidd->rvaDLLName),
PFromRva<HMODULE*>(pidd->rvaHmod),
PFromRva<PImgThunkData>(pidd->rvaIAT),
PFromRva<PCImgThunkData>(pidd->rvaINT),
PFromRva<PCImgThunkData>(pidd->rvaBoundIAT),
PFromRva<PCImgThunkData>(pidd->rvaUnloadIAT),
pidd->dwTimeStamp
};
DelayLoadInfo dli = {
sizeof(DelayLoadInfo), pidd, ppfnIATEntry, idd.szName,
{ 0 }, 0, 0, 0
};
if (!(idd.grAttrs & dlattrRva))
{
PDelayLoadInfo rgpdli[1] = { &dli };
RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_INVALID_PARAMETER), 0, 1, PULONG_PTR(rgpdli));
return 0;
}
HMODULE hmod = *idd.phmod;
// Calculate the index for the IAT entry in the import address table
// N.B. The INT entries are ordered the same as the IAT entries so
// the calculation can be done on the IAT side.
//
const unsigned iIAT = IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), idd.pIAT);
const unsigned iINT = iIAT;
PCImgThunkData pitd = &(idd.pINT[iINT]);
dli.dlp.fImportByName = !IMAGE_SNAP_BY_ORDINAL(pitd->u1.Ordinal);
if (dli.dlp.fImportByName)
dli.dlp.szProcName = LPCSTR(PFromRva<PIMAGE_IMPORT_BY_NAME>(RVA(UINT_PTR(pitd->u1.AddressOfData)))->Name);
else
dli.dlp.dwOrdinal = DWORD(IMAGE_ORDINAL(pitd->u1.Ordinal));
// Call the initial hook. If it exists and returns a function pointer,
// abort the rest of the processing and just return it for the call.
//
FARPROC pfnRet = NULL;
if (__pfnDliNotifyHook2) {
pfnRet = ((*__pfnDliNotifyHook2)(dliStartProcessing, &dli));
if (pfnRet != NULL)
goto HookBypass;
}
// Check to see if we need to try to load the library.
//
if (hmod == 0) {
if (__pfnDliNotifyHook2) {
hmod = HMODULE(((*__pfnDliNotifyHook2)(dliNotePreLoadLibrary, &dli)));
}
if (hmod == 0) {
hmod = ::LoadLibrary(dli.szDll);
}
if (hmod == 0) {
dli.dwLastError = ::GetLastError();
if (__pfnDliFailureHook2) {
// when the hook is called on LoadLibrary failure, it will
// return 0 for failure and an hmod for the lib if it fixed
// the problem.
//
hmod = HMODULE((*__pfnDliFailureHook2)(dliFailLoadLib, &dli));
}
if (hmod == 0) {
PDelayLoadInfo rgpdli[1] = { &dli };
RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND),
0, 1, PULONG_PTR(rgpdli));
// If we get to here, we blindly assume that the handler of the exception
// has magically fixed everything up and left the function pointer in
// dli.pfnCur.
//
return dli.pfnCur;
}
}
// Store the library handle. If it is already there, we infer
// that another thread got there first, and we need to do a
// FreeLibrary() to reduce the refcount
//
HMODULE hmodT = HMODULE(InterlockedExchangePointer((PVOID *) idd.phmod, PVOID(hmod)));
if (hmodT != hmod) {
// add lib to unload list if we have unload data
if (pidd->rvaUnloadIAT) {
#include "lib/nommgr.h"
new ULI(pidd);
#include "lib/mmgr.h"
}
}
else {
::FreeLibrary(hmod);
}
}
// Go for the procedure now.
//
dli.hmodCur = hmod;
if (__pfnDliNotifyHook2) {
pfnRet = (*__pfnDliNotifyHook2)(dliNotePreGetProcAddress, &dli);
}
if (pfnRet == 0) {
if (pidd->rvaBoundIAT && pidd->dwTimeStamp) {
// bound imports exist...check the timestamp from the target image
//
PIMAGE_NT_HEADERS pinh(PinhFromImageBase(hmod));
if (pinh->Signature == IMAGE_NT_SIGNATURE &&
TimeStampOfImage(pinh) == idd.dwTimeStamp &&
FLoadedAtPreferredAddress(pinh, hmod)) {
// Everything is good to go, if we have a decent address
// in the bound IAT!
//
pfnRet = FARPROC(UINT_PTR(idd.pBoundIAT[iIAT].u1.Function));
if (pfnRet != 0) {
goto SetEntryHookBypass;
}
}
}
pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName);
}
if (pfnRet == 0) {
dli.dwLastError = ::GetLastError();
if (__pfnDliFailureHook2) {
// when the hook is called on GetProcAddress failure, it will
// return 0 on failure and a valid proc address on success
//
pfnRet = (*__pfnDliFailureHook2)(dliFailGetProc, &dli);
}
if (pfnRet == 0) {
PDelayLoadInfo rgpdli[1] = { &dli };
RaiseException(VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND),
0, 1, PULONG_PTR(rgpdli));
// If we get to here, we blindly assume that the handler of the exception
// has magically fixed everything up and left the function pointer in
// dli.pfnCur.
//
pfnRet = dli.pfnCur;
}
}
SetEntryHookBypass:
*ppfnIATEntry = pfnRet;
HookBypass:
if (__pfnDliNotifyHook2) {
dli.dwLastError = 0;
dli.hmodCur = hmod;
dli.pfnCur = pfnRet;
(*__pfnDliNotifyHook2)(dliNoteEndProcessing, &dli);
}
return pfnRet;
}
static void UnloadAllDlls()
{
PUnloadInfo pui;
// free all DLLs (avoid BoundsChecker warning)
while((pui = __puiHead) != 0)
if(pui->pidd->rvaUnloadIAT)
{
PCImgDelayDescr pidd = pui->pidd;
HMODULE* phmod = PFromRva<HMODULE*>(pidd->rvaHmod);
HMODULE hmod = *phmod;
OverlayIAT(
PFromRva<PImgThunkData>(pidd->rvaIAT),
PFromRva<PCImgThunkData>(pidd->rvaUnloadIAT)
);
::FreeLibrary(hmod);
*phmod = NULL;
delete reinterpret_cast<ULI*> (pui);
// changes __puiHead!
}
}
//-----------------------------------------------------------------------------
static LibError wdll_Shutdown()
{
UnloadAllDlls();
return INFO::OK;
}