diff options
Diffstat (limited to 'src/dlls/mscoree/delayload.cpp')
-rw-r--r-- | src/dlls/mscoree/delayload.cpp | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/src/dlls/mscoree/delayload.cpp b/src/dlls/mscoree/delayload.cpp new file mode 100644 index 0000000000..0226f7da2e --- /dev/null +++ b/src/dlls/mscoree/delayload.cpp @@ -0,0 +1,456 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// +//***************************************************************************** +// DelayLoad.cpp +// +// This code defines the dealy load helper notification routines that will be +// invoked when a dll marked for delay load is processed. A DLL is marked as +// delay load by using the DELAYLOAD=foo.dll directive in your sources file. +// This tells the linker to generate helpers for the imports of this dll instead +// of loading it directly. If your application never touches those functions, +// the the dll is never loaded. This improves (a) startup time each time the +// app runs, and (b) overall working set size in the case you never use the +// functionality. +// +// +// +// This module provides a hook helper and exception handler. The hook helper +// is used primarily in debug mode right now to determine what call stacks +// force a delay load of a dll. If these call stacks are very common, then +// you should reconsider using a delay load. +// +// The exception handler is used to catch fatal errors like library not found +// or entry point missing. If this happens you are dead and need to fail +// gracefully. +// +//***************************************************************************** +#include "stdafx.h" // Standard header. + +#if !defined(FEATURE_CORESYSTEM) + +#include "delayimp.h" // Delay load header file. +#include "winwrap.h" // Wrappers for Win32 api's. +#include "utilcode.h" // Debug helpers. +#include "corerror.h" // Error codes from this EE. +#include "shimload.h" +#include "ex.h" +#include "strsafe.h" + +//********** Locals. ********************************************************** +static DWORD _FormatMessage(__out_ecount(chMsg) __out_z LPWSTR szMsg, DWORD chMsg, DWORD dwLastError, ...); +static void _FailLoadLib(unsigned dliNotify, DelayLoadInfo *pdli); +static void _FailGetProc(unsigned dliNotify, DelayLoadInfo *pdli); + +#if defined (_DEBUG) || defined (__delay_load_trace__) +static void _DbgPreLoadLibrary(int bBreak, DelayLoadInfo *pdli); +#endif + + +//********** Globals. ********************************************************* + +// Override __pfnDllFailureHook. This will give the delay code a callback +// for when a load failure occurs. This failure hook is implemented below. +FARPROC __stdcall CorDelayErrorHook(unsigned dliNotify, DelayLoadInfo *pdli); +ExternC extern PfnDliHook __pfnDliFailureHook = CorDelayErrorHook; + +// In trace mode, override the delay load hook. Our hook does nothing but +// provide some diagnostic information for debugging. +FARPROC __stdcall CorDelayLoadHook(unsigned dliNotify, DelayLoadInfo *pdli); +ExternC extern PfnDliHook __pfnDliNotifyHook = CorDelayLoadHook; + + +//********** Code. ************************************************************ + +#undef ExitProcess + +extern void DECLSPEC_NORETURN ThrowOutOfMemory(); + +//***************************************************************************** +// Called for errors that might have occured. +//***************************************************************************** +FARPROC __stdcall CorDelayErrorHook( // Always 0. + unsigned dliNotify, // What event has occured, dli* flag. + DelayLoadInfo *pdli) // Description of the event. +{ + + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SO_TOLERANT; + + // Chose operation to perform based on operation. + switch (dliNotify) + { + // Failed to load the library. Need to fail gracefully. + case dliFailLoadLib: + //_FailLoadLib(dliNotify, pdli); + break; + + // Failed to get the address of the given function, fail gracefully. + case dliFailGetProc: +#ifndef FEATURE_CORECLR + _FailGetProc(dliNotify, pdli); +#endif // !FEATURE_CORECLR + break; + + // Unknown failure code. + default: + _ASSERTE(!"Unknown delay load failure code."); + break; + } + +#ifndef FEATURE_CORECLR + if (_stricmp(pdli->szDll, "ole32.dll") == 0) + { + // TODO: after interop team fixes delayload related to ole32.dll, we can throw OOM instead. + // For now, SQL preloads ole32.dll before starting CLR, so OOM for ole32 is not a concern. + ExitProcess(pdli->dwLastError); + } + else +#endif // !FEATURE_CORECLR +#ifdef MSDIS_DLL + // MSDIS_DLL is a macro defined in SOURCES.INC + if (_stricmp(pdli->szDll, MSDIS_DLL) == 0) + { + // msdisxxx.dll is used in GCStress 4 on chk/dbg builds, if it fails to load then the + // process will stack-overflow or terminate with no obvious reason of the root cause. + _ASSERTE(!"Failed to delay load " MSDIS_DLL); + } + else +#endif // MSDIS_DLL + { +#ifndef FEATURE_CORECLR + // We do not own the process. ExitProcess is bad. + // We will try to recover next time. + ThrowWin32 (pdli->dwLastError); +#endif // !FEATURE_CORECLR + } + + return (0); +} + + +//***************************************************************************** +// Format an error message using a system error (supplied through GetLastError) +// and any subtitution values required. +//***************************************************************************** +DWORD _FormatMessage( // How many characters written. + __out_ecount(chMsg) __out_z LPWSTR szMsg, // Buffer for formatted data. + DWORD chMsg, // How big is the buffer. + DWORD dwLastError, // The last error code we got. + ...) // Substitution values. +{ + WRAPPER_NO_CONTRACT; + + DWORD iRtn; + va_list marker; + + va_start(marker, dwLastError); + iRtn = WszFormatMessage( + FORMAT_MESSAGE_FROM_SYSTEM, // Flags. + 0, // No source, use system. + dwLastError, // Error code. + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Use default langauge. + szMsg, // Output buffer. + dwLastError, // Size of buffer. + &marker); // Substitution text. + va_end(marker); + return (iRtn); +} + + +//***************************************************************************** +// A library failed to load. This is always a bad thing. +//***************************************************************************** +void _FailLoadLib( + unsigned dliNotify, // What event has occured, dli* flag. + DelayLoadInfo *pdli) // Description of the event. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + // We're allocating strings for the purposes of putting up a critical error box. + // Obviously, OOM's aren't going to be passed up to the caller. + FAULT_NOT_FATAL(); + + + WCHAR rcMessage[_MAX_PATH+500]; // Message for display. + WCHAR rcFmt[500]; // 500 is the number used by excep.cpp for mscorrc resources. + HRESULT hr; + + // Load a detailed error message from the resource file. + if (SUCCEEDED(hr = UtilLoadStringRC(MSEE_E_LOADLIBFAILED, rcFmt, NumItems(rcFmt)))) + { + StringCchPrintf(rcMessage, COUNTOF(rcMessage), rcFmt, pdli->szDll, pdli->dwLastError); + } + else + { + // Foramt the Windows error first. + if (!_FormatMessage(rcMessage, NumItems(rcMessage), pdli->dwLastError, pdli->szDll)) + { + // Default to a hard coded error otherwise. + StringCchPrintf(rcMessage, COUNTOF(rcMessage), W("ERROR! Failed to delay load library %hs, Win32 error %d, Delay error: %d\n"), + pdli->szDll, pdli->dwLastError, dliNotify); + } + } + +#ifndef _ALPHA_ + // for some bizarre reason, calling OutputDebugString during delay load in non-debug mode on Alpha + // kills program, so only do it when in debug mode () +#if defined (_DEBUG) || defined (__delay_load_trace__) + // Give some feedback to the developer. + wprintf(W("%s\n"), rcMessage); + WszOutputDebugString(rcMessage); +#endif +#endif + + // Inform the user that we cannot continue execution anymore. + UtilMessageBoxCatastrophicNonLocalized(rcMessage, W("MSCOREE.DLL"), MB_ICONERROR | MB_OK, TRUE); + _ASSERTE(!"Failed to delay load library"); +} + + +//***************************************************************************** +// A library failed to load. This is always a bad thing. +//***************************************************************************** +void _FailGetProc( + unsigned dliNotify, // What event has occured, dli* flag. + DelayLoadInfo *pdli) // Description of the event. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + STATIC_CONTRACT_SO_TOLERANT; + + // We're allocating strings for the purposes of putting up a critical error box. + // Obviously, OOM's aren't going to be passed up to the caller. + FAULT_NOT_FATAL(); + + WCHAR rcMessage[_MAX_PATH+756]; // Message for display. + WCHAR rcProc[257] = {0}; // Name of procedure with error. + WCHAR rcFmt[500]; // 500 is the number used by excep.cpp for mscorrc resources. + HRESULT hr; + + // Get a display name for debugging information. + if (pdli->dlp.fImportByName) + Wsz_mbstowcs(rcProc, pdli->dlp.szProcName, sizeof(rcProc)/sizeof(rcProc[0])-1); + else + StringCchPrintf(rcProc, COUNTOF(rcProc), W("Ordinal: %d"), pdli->dlp.dwOrdinal); + + // Load a detailed error message from the resource file. + if (SUCCEEDED(hr = UtilLoadStringRC(MSEE_E_GETPROCFAILED, rcFmt, NumItems(rcFmt)))) + { + StringCchPrintf(rcMessage, COUNTOF(rcMessage), rcFmt, rcProc, pdli->szDll, pdli->dwLastError); + } + else + { + if (!_FormatMessage(rcMessage, NumItems(rcMessage), pdli->dwLastError, pdli->szDll)) + { + // Default to a hard coded error otherwise. + StringCchPrintf(rcMessage, COUNTOF(rcMessage), W("ERROR! Failed GetProcAddress() for %s, Win32 error %d, Delay error %d\n"), + rcProc, pdli->dwLastError, dliNotify); + } + } + +#ifndef ALPHA + // for some bizarre reason, calling OutputDebugString during delay load in non-debug mode on Alpha + // kills program, so only do it when in debug mode () +#if defined (_DEBUG) || defined (__delay_load_trace__) + // Give some feedback to the developer. + wprintf(W("%s"),rcMessage); + WszOutputDebugString(rcMessage); +#endif +#endif + + { + // We are already in a catastrophic situation so we can tolerate faults as well as SO & GC mode violations to keep going. + CONTRACT_VIOLATION(FaultNotFatal | GCViolation | ModeViolation | SOToleranceViolation); + + // Inform the user that we cannot continue execution anymore. + UtilMessageBoxCatastrophicNonLocalized(rcMessage, W("MSCOREE.DLL"), MB_ICONERROR | MB_OK, TRUE); + } + _ASSERTE(!"Failed to delay load GetProcAddress()"); +} + + + +HMODULE DoPreloadLibraryThrowing(LPCSTR szLibrary) +{ + STATIC_CONTRACT_THROWS; + STATIC_CONTRACT_SO_TOLERANT; + + HMODULE result=NULL; + BEGIN_SO_INTOLERANT_CODE_NO_THROW_CHECK_THREAD(ThrowHR(COR_E_STACKOVERFLOW)); + DWORD dwLength = _MAX_PATH; + WCHAR pName[_MAX_PATH]; + IfFailThrow(GetInternalSystemDirectory(pName, &dwLength)); + + MAKE_WIDEPTR_FROMANSI_NOTHROW(pwLibrary, szLibrary); + if ((pwLibrary == NULL) || ovadd_ge(dwLength, __lpwLibrary, _MAX_PATH-1)) + ThrowHR(E_INVALIDARG); + + wcscpy_s(pName+dwLength-1, COUNTOF(pName) - dwLength + 1, pwLibrary); + result = CLRLoadLibraryEx(pName, NULL, GetLoadWithAlteredSearchPathFlag()); + END_SO_INTOLERANT_CODE; + return result; +} + +// +//********** Tracing code. **************************************************** +// + +//***************************************************************************** +// This routine is our Delay Load Helper. It will get called for every delay +// load event that occurs while the application is running. +//***************************************************************************** +FARPROC __stdcall CorDelayLoadHook( // Always 0. + unsigned dliNotify, // What event has occured, dli* flag. + DelayLoadInfo *pdli) // Description of the event. +{ +#ifdef _DEBUG + if (dliNotify == dliStartProcessing) + { + BOOL fThrows = TRUE; + if (_stricmp(pdli->szDll, "ole32.dll") == 0) + { + // SQL loads ole32.dll before starting CLR. For Whidbey release, + // we do not have time to get ole32.dll delay load cleaned. + fThrows = FALSE; + } + else if (_stricmp(pdli->szDll, "oleaut32.dll") == 0) + { + extern BOOL DelayLoadOleaut32CheckDisabled(); + if (DelayLoadOleaut32CheckDisabled()) + { + fThrows = FALSE; + } + else if ((!pdli->dlp.fImportByName && pdli->dlp.dwOrdinal == 6) || + (pdli->dlp.fImportByName && strcmp(pdli->dlp.szProcName, "SysFreeString") == 0)) + { + // BSTR has been created, which means oleaut32 should have been loaded. + // Delay load will not fail. + _ASSERTE (GetModuleHandleA("oleaut32.dll") != NULL); + fThrows = FALSE; + } + } + else if (_stricmp(pdli->szDll, "mscoree.dll") == 0) // If we are attempting to delay load mscoree.dll + { + if (GetModuleHandleA("mscoree.dll") != NULL) // and mscoree.dll has already been loaded + fThrows = FALSE; // then the delay load will not fail (and hence will not throw). + } + if (fThrows) + { + CONTRACTL + { + SO_TOLERANT; + THROWS; + } + CONTRACTL_END; + } + } +#endif + + //STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FAULT; + STATIC_CONTRACT_SO_TOLERANT; + + // We're allocating strings for the purposes of putting up a critical error box. + // Obviously, OOM's aren't going to be passed up to the caller. + HMODULE result = NULL; + CONTRACT_VIOLATION(FaultNotFatal); + + + switch(dliNotify) { + case dliNotePreLoadLibrary: + if(pdli->szDll) { + result=DoPreloadLibraryThrowing(pdli->szDll); + } + break; + default: + break; + } + +#if defined (_DEBUG) || defined (__delay_load_trace__) + SO_NOT_MAINLINE_FUNCTION; + static int bBreak = false; // true to break on events. + static int bInit = false; // true after we've checked environment. + // If we've not yet looked at our environment, then do so. + if (!bInit) + { + WCHAR rcBreak[16]; + + // set DelayLoadBreak=[0|1] + if (WszGetEnvironmentVariable(W("DelayLoadBreak"), rcBreak, NumItems(rcBreak))) + { + // "1" means to break hard and display errors. + if (*rcBreak == '1') + bBreak = 1; + // "2" means no break, but display errors. + else if (*rcBreak == '2') + bBreak = 2; + else + bBreak = false; + } + bInit = true; + } + + // Chose operation to perform based on operation. + switch (dliNotify) + { + // Called just before a load library takes place. Use this opportunity + // to display a debug trace message, and possible break if desired. + case dliNotePreLoadLibrary: + _DbgPreLoadLibrary(bBreak, pdli); + break; + } +#endif + return (FARPROC) result; +} + + +#if defined (_DEBUG) || defined (__delay_load_trace__) + +//***************************************************************************** +// Display a debug message so we know what's going on. Offer to break in +// debugger if you want to see what call stack forced this library to load. +//***************************************************************************** +void _DbgPreLoadLibrary( + int bBreak, // true to break in debugger. + DelayLoadInfo *pdli) // Description of the event. +{ + STATIC_CONTRACT_NOTHROW; + STATIC_CONTRACT_FORBID_FAULT; + + // We're allocating strings for the purposes of putting up a critical error box. + // Obviously, OOM's aren't going to be passed up to the caller. + FAULT_NOT_FATAL(); + + +#ifdef _ALPHA_ + // for some bizarre reason, calling OutputDebugString during delay load in non-debug mode on Alpha + // kills program, so only do it when in debug mode () + if (! IsDebuggerPresent()) + return; +#endif + + WCHAR rcMessage[_MAX_PATH*2]; // Message for display. + + // Give some feedback to the developer. + StringCchPrintf(rcMessage, COUNTOF(rcMessage), W("Delay loading %hs\n"), pdli->szDll); + WszOutputDebugString(rcMessage); + + if (bBreak) + { + wprintf(W("%s"), rcMessage); + + if (bBreak == 1) + { + _ASSERTE(!"fyi - Delay loading library. Set DelayLoadBreak=0 to disable this assert."); + } + } +} + + +#endif // _DEBUG + +#endif // !FEATURE_CORESYSTEM |