/** * @file llwindebug.cpp * @brief Windows debugging functions * * Copyright (c) 2004-2007, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. */ #include "llviewerprecompiledheaders.h" #ifdef LL_WINDOWS #include "llwindebug.h" #include "llviewercontrol.h" #include "lldir.h" // From viewer.h extern BOOL gInProductionGrid; extern void (*gCrashCallback)(void); extern void write_debug(const char *str); extern void write_debug(const std::string &str); // based on dbghelp.h typedef BOOL (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam ); MINIDUMPWRITEDUMP f_mdwp = NULL; class LLMemoryReserve { public: LLMemoryReserve(); ~LLMemoryReserve(); void reserve(); void release(); protected: unsigned char *mReserve; static const size_t MEMORY_RESERVATION_SIZE; }; LLMemoryReserve::LLMemoryReserve() : mReserve(NULL) { }; LLMemoryReserve::~LLMemoryReserve() { release(); } // I dunno - this just seemed like a pretty good value. const size_t LLMemoryReserve::MEMORY_RESERVATION_SIZE = 5 * 1024 * 1024; void LLMemoryReserve::reserve() { if(NULL == mReserve) mReserve = new unsigned char[MEMORY_RESERVATION_SIZE]; }; void LLMemoryReserve::release() { delete [] mReserve; mReserve = NULL; }; static LLMemoryReserve gEmergencyMemoryReserve; // static BOOL LLWinDebug::setupExceptionHandler() { #ifdef LL_RELEASE_FOR_DOWNLOAD static BOOL s_first_run = TRUE; // Load the dbghelp dll now, instead of waiting for the crash. // Less potential for stack mangling BOOL ok = TRUE; if (s_first_run) { // First, try loading from the directory that the app resides in. std::string local_dll_name = gDirUtilp->findFile("dbghelp.dll", gDirUtilp->getWorkingDir(), gDirUtilp->getExecutableDir()); HMODULE hDll = NULL; hDll = LoadLibraryA(local_dll_name.c_str()); if (!hDll) { hDll = LoadLibrary(L"dbghelp.dll"); } if (!hDll) { llwarns << "Couldn't find dbghelp.dll!" << llendl; std::string msg = "Couldn't find dbghelp.dll at "; msg += local_dll_name; msg += "!\n"; write_debug(msg.c_str()); ok = FALSE; } else { f_mdwp = (MINIDUMPWRITEDUMP) GetProcAddress(hDll, "MiniDumpWriteDump"); if (!f_mdwp) { write_debug("No MiniDumpWriteDump!\n"); FreeLibrary(hDll); hDll = NULL; ok = FALSE; } } gEmergencyMemoryReserve.reserve(); } LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; prev_filter = SetUnhandledExceptionFilter(LLWinDebug::handleException); if (s_first_run) { // We're fine, this is the first run. s_first_run = FALSE; return ok; } if (!prev_filter) { llwarns << "Our exception handler (" << (void *)LLWinDebug::handleException << ") replaced with NULL!" << llendl; ok = FALSE; } if (prev_filter != LLWinDebug::handleException) { llwarns << "Our exception handler (" << (void *)LLWinDebug::handleException << ") replaced with " << prev_filter << "!" << llendl; ok = FALSE; } return ok; #else // Internal builds don't mess with exception handling. return TRUE; #endif } void LLWinDebug::writeDumpToFile(MINIDUMP_TYPE type, MINIDUMP_EXCEPTION_INFORMATION *ExInfop, const char *filename) { if(f_mdwp == NULL) { write_debug("No way to generate a minidump, no MiniDumpWriteDump function!\n"); } else if(gDirUtilp == NULL) { write_debug("No way to generate a minidump, no gDirUtilp!\n"); } else { std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename); HANDLE hFile = CreateFileA(dump_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { // Write the dump, ignoring the return value f_mdwp(GetCurrentProcess(), GetCurrentProcessId(), hFile, type, ExInfop, NULL, NULL); CloseHandle(hFile); } } } // static LONG LLWinDebug::handleException(struct _EXCEPTION_POINTERS *exception_infop) { // // Let go of a bunch of reserved memory to give library calls etc // a chance to execute normally in the case that we ran out of // memory. // gEmergencyMemoryReserve.release(); BOOL userWantsMaxiDump = (stricmp(gSavedSettings.getString("LastName").c_str(), "linden") == 0) || (stricmp(gSavedSettings.getString("LastName").c_str(), "tester") == 0); BOOL alsoSaveMaxiDump = userWantsMaxiDump && !gInProductionGrid; /* Calculate alsoSaveMaxiDump here */ if (exception_infop) { _MINIDUMP_EXCEPTION_INFORMATION ExInfo; ExInfo.ThreadId = ::GetCurrentThreadId(); ExInfo.ExceptionPointers = exception_infop; ExInfo.ClientPointers = NULL; writeDumpToFile(MiniDumpNormal, &ExInfo, "SecondLife.dmp"); if(alsoSaveMaxiDump) writeDumpToFile((MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory), &ExInfo, "SecondLifePlus.dmp"); } else { writeDumpToFile(MiniDumpNormal, NULL, "SecondLife.dmp"); if(alsoSaveMaxiDump) writeDumpToFile((MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory), NULL, "SecondLifePlus.dmp"); } if (!exception_infop) { // We're calling this due to a network error, not due to an actual exception. // It doesn't realy matter what we return. return EXCEPTION_CONTINUE_SEARCH; } // // Call the newview crash callback, which will spawn the crash // reporter. It may or may not spawn a dialog. // if (gCrashCallback) { gCrashCallback(); } // // At this point, we always want to exit the app. There's no graceful // recovery for an unhandled exception. // // Just kill the process. LONG retval = EXCEPTION_EXECUTE_HANDLER; return retval; } #endif