/*++ Copyright (c) 1998 Microsoft Corporation Module Name : randfail.c Abstract : This module implements the initialization function for the random failure library, plus the code to determine if it's time to fail. Author : Sam Neely Revision History : --*/ #include #include #include "traceint.h" #include "randint.h" #include "exchmem.h" static long s_nCount = 0; long nFailRate = kDontFail; DWORD dwRandFailTlsIndex=0xffffffff; const DWORD g_dwMaxCallStack = 1024; // // Call stack buffer array // CHAR **g_ppchCallStack = NULL; // // Randfail call stack file and its handle // CHAR g_szRandFailFile[MAX_PATH+1]; HANDLE g_hRandFailFile = INVALID_HANDLE_VALUE; HANDLE g_hRandFailMutex = INVALID_HANDLE_VALUE; // // Number of buffers allocated for randfail call stack // LONG g_cCallStack = 1; // // Current index in the buffer array // LONG g_iCallStack = 0; VOID DumpCallStack( DWORD_PTR *rgdwCall, DWORD dwCallers, PBYTE pbCallstack, DWORD& cbCallstack ) /*++ Routine description: Dump call stack into the given buffer. Arguments: rgdwCall - Array of caller's address dwCallers - Number of callers pbCallstack - The buffer to put the call stack string into cbCallstack - In: How big the buffer is, Out: how much stuff I have put in there Return value: None. --*/ { DWORD i; CHAR Buffer[g_dwMaxCallStack]; DWORD dwLine = 0; DWORD dwBufferAvail = cbCallstack - 2*sizeof(CHAR); PBYTE pbStart = pbCallstack; DWORD dwBytesWritten = 0; BOOL fRetry = TRUE; char szModuleName[MAX_PATH]; char* pszFileName; char* pszExtension; _ASSERT( pbStart ); _ASSERT( cbCallstack > 0 ); cbCallstack = 0; // // Get the executable's filename and point past the last slash // in the path, if it's present. Also, whack off the extension // if it's .EXE // if (GetModuleFileName(NULL, szModuleName, MAX_PATH) == 0) { strcpy (szModuleName, "Unknown"); } pszFileName = strrchr(szModuleName, '\\'); if (pszFileName == NULL) { pszFileName = szModuleName; } else { pszFileName++; } pszExtension = strrchr(pszFileName, '.'); if (pszExtension) { if (_stricmp(pszExtension+1, "exe") == 0) { *pszExtension = NULL; } } // // Format a header line // dwBytesWritten = _snprintf((char*)pbStart, g_dwMaxCallStack, "*** %s, Process: %d(%#x), Thread: %d(%#x) ***\r\n", pszFileName, GetCurrentProcessId(), GetCurrentProcessId(), GetCurrentThreadId(), GetCurrentThreadId()); cbCallstack += dwBytesWritten; pbStart += dwBytesWritten; dwBufferAvail -= dwBytesWritten; // // Dump call stack // Note that we skip the first two entries. These are the internal // calls to ExchmemGetCallStack and g_TestTrace for (i = 2; i < dwCallers && rgdwCall[i] != 0; i++) { ExchmemFormatSymbol( GetCurrentProcess(), rgdwCall[i], Buffer, g_dwMaxCallStack ); dwLine = strlen( Buffer ); if ( dwLine+2 < dwBufferAvail ) { CopyMemory( pbStart, Buffer, dwLine ); *(pbStart+dwLine) = '\r'; *(pbStart+dwLine+1) = '\n'; dwBufferAvail -= (dwLine + 2*sizeof(CHAR)); pbStart += (dwLine + 2*sizeof(CHAR)); cbCallstack +=( dwLine + 2*sizeof(CHAR)); } else { break; } } // // Add an extra \r\n at the end // *(pbCallstack + cbCallstack) = '\r'; *(pbCallstack + cbCallstack + 1) = '\n'; cbCallstack += 2; // // Dump it to the log file as well, if we do have a log file // if ( INVALID_HANDLE_VALUE != g_hRandFailFile && INVALID_HANDLE_VALUE != g_hRandFailMutex ) { WaitForSingleObject (g_hRandFailMutex, INFINITE); DWORD dwOffset = SetFilePointer( g_hRandFailFile, 0, 0, FILE_END ); // // if the file is too big then we need to truncate it // if (dwOffset > dwMaxFileSize) { SetFilePointer(g_hRandFailFile, 0, 0, FILE_BEGIN); SetEndOfFile(g_hRandFailFile); } try_again: BOOL b = WriteFile( g_hRandFailFile, pbCallstack, cbCallstack, &dwBytesWritten, NULL ); if ( b == FALSE || dwBytesWritten != cbCallstack ) { DWORD dwError = GetLastError(); if( dwError && fRetry ) { fRetry = FALSE; Sleep( 100 ); goto try_again; } INT_TRACE( "Error writing to file: %d, number of bytes %d:%d\n", dwError, cbCallstack, dwBytesWritten ); } ReleaseMutex(g_hRandFailMutex); } } // // See if it's time for this API to fail // // Note: This routine was renamed from fTimeToFail to g_TestTrace // to hide the symbol from someone dumping the dll // extern "C" __declspec(dllexport) int __stdcall g_TestTrace(void) { /*++ Routine Description: Check to see if it's time for an instrumented API to fail. Note: This routine was renamed from fTimeToFail to g_TestTrace to hide the symbol from someone dumping the dll Arguments: None Return Value: true if it's time for us to fail, false if not or we're disabled. --*/ LONG l; // Never fail? if (nFailRate == kDontFail) return 0; // Have failures been suspended? if (dwRandFailTlsIndex != 0xffffffff && TlsGetValue (dwRandFailTlsIndex) != NULL) return 0; // This is good enough for now.. l = InterlockedIncrement(&s_nCount) % nFailRate; if ( l == 0 ) { // We are going to fail if ( g_ppchCallStack ) { LONG i = 0; const DWORD dwMaxCallStack = 20; DWORD dwCallStackBuffer = g_dwMaxCallStack; DWORD_PTR rgdwCaller[dwMaxCallStack]; i = InterlockedIncrement( &g_iCallStack ); if ( i <= g_cCallStack ) { i--; if ( g_ppchCallStack[i] ) { ZeroMemory( rgdwCaller, sizeof(DWORD_PTR)*dwMaxCallStack ); ExchmemGetCallStack(rgdwCaller, dwMaxCallStack); DumpCallStack( rgdwCaller, dwMaxCallStack, (PBYTE)g_ppchCallStack[i], dwCallStackBuffer ); } } else { InterlockedExchange( &g_iCallStack, g_cCallStack ); } } return TRUE; } else return FALSE; } extern "C" __declspec(dllexport) void __stdcall g_TestTraceDisable(void) { /*++ Routine Description: Function to temporarily suspend g_TestTrace's ability to return a failure. This is used when you want to call one of the instrumented APIs that you don't want to fail. This function is nestable up to 128(abitrary) levels deep. Arguments: None Return Value: None --*/ if (dwRandFailTlsIndex == 0xffffffff) return; SIZE_T OldValue = (SIZE_T)TlsGetValue(dwRandFailTlsIndex); ASSERT (OldValue <= 128); TlsSetValue(dwRandFailTlsIndex, (LPVOID)(OldValue+1)); } extern "C" __declspec(dllexport) void __stdcall g_TestTraceEnable(void) { /*++ Routine Description: Resume g_TestTrace's normal functionality if the nesting level has returned to zero. Arguments: None Return Value: None --*/ if (dwRandFailTlsIndex == 0xffffffff) return; SIZE_T OldValue = (SIZE_T)TlsGetValue(dwRandFailTlsIndex); ASSERT (OldValue > 0 && OldValue <= 128); TlsSetValue(dwRandFailTlsIndex, (LPVOID)(OldValue-1)); }