/** * @file llcrashloggerwindows.cpp * @brief Windows crash logger implementation * * $LicenseInfo:firstyear=2003&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "linden_common.h" #include "stdafx.h" #include "resource.h" #include "llcrashloggerwindows.h" #include <sstream> #include "boost/tokenizer.hpp" #include "indra_constants.h" // CRASH_BEHAVIOR_ASK, CRASH_SETTING_NAME #include "llerror.h" #include "llfile.h" #include "lltimer.h" #include "llstring.h" #include "lldxhardware.h" #include "lldir.h" #include "llsdserialize.h" #include "llsdutil.h" #include "stringize.h" #include <client/windows/crash_generation/crash_generation_server.h> #include <client/windows/crash_generation/client_info.h> #define MAX_LOADSTRING 100 #define MAX_STRING 2048 const char* const SETTINGS_FILE_HEADER = "version"; const S32 SETTINGS_FILE_VERSION = 101; // Windows Message Handlers // Global Variables: HINSTANCE hInst= NULL; // current instance TCHAR szTitle[MAX_LOADSTRING]; /* Flawfinder: ignore */ // The title bar text TCHAR szWindowClass[MAX_LOADSTRING]; /* Flawfinder: ignore */ // The title bar text std::string gProductName; HWND gHwndReport = NULL; // Send/Don't Send dialog HWND gHwndProgress = NULL; // Progress window HCURSOR gCursorArrow = NULL; HCURSOR gCursorWait = NULL; BOOL gFirstDialog = TRUE; // Are we currently handling the Send/Don't Send dialog? std::stringstream gDXInfo; bool gSendLogs = false; LLCrashLoggerWindows* LLCrashLoggerWindows::sInstance = NULL; //Conversion from char* to wchar* //Replacement for ATL macros, doesn't allocate memory //For more info see: http://www.codeguru.com/forum/showthread.php?t=337247 void ConvertLPCSTRToLPWSTR (const char* pCstring, WCHAR* outStr) { if (pCstring != NULL) { int nInputStrLen = strlen (pCstring); // Double NULL Termination int nOutputStrLen = MultiByteToWideChar(CP_ACP, 0, pCstring, nInputStrLen, NULL, 0) + 2; if (outStr) { memset (outStr, 0x00, sizeof (WCHAR)*nOutputStrLen); MultiByteToWideChar (CP_ACP, 0, pCstring, nInputStrLen, outStr, nInputStrLen); } } } void write_debug(const char *str) { gDXInfo << str; /* Flawfinder: ignore */ } void write_debug(std::string& str) { write_debug(str.c_str()); } void show_progress(const std::string& message) { std::wstring msg = wstring_to_utf16str(utf8str_to_wstring(message)); if (gHwndProgress) { SendDlgItemMessage(gHwndProgress, // handle to destination window IDC_LOG, WM_SETTEXT, // message to send FALSE, // undo option (LPARAM)msg.c_str()); } } void update_messages() { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) { exit(0); } TranslateMessage(&msg); DispatchMessage(&msg); } } void sleep_and_pump_messages( U32 seconds ) { const U32 CYCLES_PER_SECOND = 10; U32 cycles = seconds * CYCLES_PER_SECOND; while( cycles-- ) { update_messages(); ms_sleep(1000 / CYCLES_PER_SECOND); } } // Include product name in the window caption. void LLCrashLoggerWindows::ProcessCaption(HWND hWnd) { TCHAR templateText[MAX_STRING]; /* Flawfinder: ignore */ TCHAR header[MAX_STRING]; std::string final; GetWindowText(hWnd, templateText, sizeof(templateText)); final = llformat(ll_convert_wide_to_string(templateText, CP_ACP).c_str(), gProductName.c_str()); ConvertLPCSTRToLPWSTR(final.c_str(), header); SetWindowText(hWnd, header); } // Include product name in the diaog item text. void LLCrashLoggerWindows::ProcessDlgItemText(HWND hWnd, int nIDDlgItem) { TCHAR templateText[MAX_STRING]; /* Flawfinder: ignore */ TCHAR header[MAX_STRING]; std::string final; GetDlgItemText(hWnd, nIDDlgItem, templateText, sizeof(templateText)); final = llformat(ll_convert_wide_to_string(templateText, CP_ACP).c_str(), gProductName.c_str()); ConvertLPCSTRToLPWSTR(final.c_str(), header); SetDlgItemText(hWnd, nIDDlgItem, header); } bool handle_button_click(WORD button_id) { // Is this something other than Send or Don't Send? if (button_id != IDOK && button_id != IDCANCEL) { return false; } // See if "do this next time" is checked and save state S32 crash_behavior = CRASH_BEHAVIOR_ASK; LRESULT result = SendDlgItemMessage(gHwndReport, IDC_CHECK_AUTO, BM_GETCHECK, 0, 0); if (result == BST_CHECKED) { if (button_id == IDOK) { crash_behavior = CRASH_BEHAVIOR_ALWAYS_SEND; } else if (button_id == IDCANCEL) { crash_behavior = CRASH_BEHAVIOR_NEVER_SEND; } ((LLCrashLoggerWindows*)LLCrashLogger::instance())->saveCrashBehaviorSetting(crash_behavior); } // We're done with this dialog. gFirstDialog = FALSE; // Send the crash report if requested if (button_id == IDOK) { gSendLogs = TRUE; WCHAR wbuffer[20000]; GetDlgItemText(gHwndReport, // handle to dialog box IDC_EDIT1, // control identifier wbuffer, // pointer to buffer for text 20000 // maximum size of string ); std::string user_text(ll_convert_wide_to_string(wbuffer, CP_ACP)); // Activate and show the window. ShowWindow(gHwndProgress, SW_SHOW); // Try doing this second to make the progress window go frontmost. ShowWindow(gHwndReport, SW_HIDE); ((LLCrashLoggerWindows*)LLCrashLogger::instance())->setUserText(user_text); ((LLCrashLoggerWindows*)LLCrashLogger::instance())->sendCrashLogs(); } // Quit the app LLApp::setQuitting(); return true; } LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) { switch( message ) { case WM_CREATE: return 0; case WM_COMMAND: if( gFirstDialog ) { WORD button_id = LOWORD(wParam); bool handled = handle_button_click(button_id); if (handled) { return 0; } } break; case WM_DESTROY: // Closing the window cancels LLApp::setQuitting(); PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } LLCrashLoggerWindows::LLCrashLoggerWindows(void) { if (LLCrashLoggerWindows::sInstance==NULL) { sInstance = this; } } LLCrashLoggerWindows::~LLCrashLoggerWindows(void) { sInstance = NULL; } bool LLCrashLoggerWindows::getMessageWithTimeout(MSG *msg, UINT to) { bool res; UINT_PTR timerID = SetTimer(NULL, NULL, to, NULL); res = GetMessage(msg, NULL, 0, 0); KillTimer(NULL, timerID); if (!res) return false; if (msg->message == WM_TIMER && msg->hwnd == NULL && msg->wParam == 1) return false; //TIMEOUT! You could call SetLastError() or something... return true; } int LLCrashLoggerWindows::processingLoop() { const int millisecs=1000; int retries = 0; const int max_retries = 60; LL_DEBUGS("CRASHREPORT") << "Entering processing loop for OOP server" << LL_ENDL; LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); MSG msg; bool result; while (1) { result = getMessageWithTimeout(&msg, millisecs); if ( result ) { TranslateMessage(&msg); DispatchMessage(&msg); } if ( retries < max_retries ) //Wait up to 1 minute for the viewer to say hello. { if (mClientsConnected == 0) { LL_DEBUGS("CRASHREPORT") << "Waiting for client to connect." << LL_ENDL; ++retries; } else { LL_INFOS("CRASHREPORT") << "Client has connected!" << LL_ENDL; retries = max_retries; } } else { if (mClientsConnected == 0) { break; } if (!mKeyMaster.isProcessAlive(mPID, mProcName) ) { break; } } } LL_INFOS() << "session ending.." << LL_ENDL; std::string per_run_dir = options["dumpdir"].asString(); std::string per_run_file = per_run_dir + "\\SecondLife.log"; std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.log"); if (gDirUtilp->fileExists(per_run_dir)) { LL_INFOS ("CRASHREPORT") << "Copying " << log_file << " to " << per_run_file << LL_ENDL; LLFile::copy(log_file, per_run_file); } return 0; } void LLCrashLoggerWindows::OnClientConnected(void* context, const google_breakpad::ClientInfo* client_info) { sInstance->mClientsConnected++; LL_INFOS("CRASHREPORT") << "Client connected. pid = " << client_info->pid() << " total clients " << sInstance->mClientsConnected << LL_ENDL; } void LLCrashLoggerWindows::OnClientExited(void* context, const google_breakpad::ClientInfo* client_info) { sInstance->mClientsConnected--; LL_INFOS("CRASHREPORT") << "Client disconnected. pid = " << client_info->pid() << " total clients " << sInstance->mClientsConnected << LL_ENDL; } void LLCrashLoggerWindows::OnClientDumpRequest(void* context, const google_breakpad::ClientInfo* client_info, const std::wstring* file_path) { if (!file_path) { LL_WARNS() << "dump with no file path" << LL_ENDL; return; } if (!client_info) { LL_WARNS() << "dump with no client info" << LL_ENDL; return; } LLCrashLoggerWindows* self = static_cast<LLCrashLoggerWindows*>(context); if (!self) { LL_WARNS() << "dump with no context" << LL_ENDL; return; } //DWORD pid = client_info->pid(); } bool LLCrashLoggerWindows::initCrashServer() { //For Breakpad on Windows we need a full Out of Process service to get good data. //This routine starts up the service on a named pipe that the viewer will then //communicate with. using namespace google_breakpad; LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE ); std::string dump_path = options["dumpdir"].asString(); mClientsConnected = 0; mPID = options["pid"].asInteger(); mProcName = options["procname"].asString(); //Generate a quasi-uniq name for the named pipe. For our purposes //this is unique-enough with least hassle. Worst case for duplicate name //is a second instance of the viewer will not do crash reporting. std::wstring wpipe_name; wpipe_name = mCrashReportPipeStr + std::wstring(wstringize(mPID)); std::wstring wdump_path( wstringize(dump_path) ); //Pipe naming conventions: http://msdn.microsoft.com/en-us/library/aa365783%28v=vs.85%29.aspx mCrashHandler = new CrashGenerationServer( wpipe_name, NULL, &LLCrashLoggerWindows::OnClientConnected, this, /*NULL, NULL, */ &LLCrashLoggerWindows::OnClientDumpRequest, this, &LLCrashLoggerWindows::OnClientExited, this, NULL, NULL, true, &wdump_path); if (!mCrashHandler) { //Failed to start the crash server. LL_WARNS() << "Failed to init crash server." << LL_ENDL; return false; } // Start servicing clients. if (!mCrashHandler->Start()) { LL_WARNS() << "Failed to start crash server." << LL_ENDL; return false; } LL_INFOS("CRASHREPORT") << "Initialized OOP server with pipe named " << stringize(wpipe_name) << LL_ENDL; return true; } bool LLCrashLoggerWindows::init(void) { bool ok = LLCrashLogger::init(); if(!ok) return false; initCrashServer(); /* mbstowcs( gProductName, mProductName.c_str(), LL_ARRAY_SIZE(gProductName) ); gProductName[ LL_ARRY_SIZE(gProductName) - 1 ] = 0; swprintf(gProductName, L"Second Life"); */ LL_INFOS() << "Loading dialogs" << LL_ENDL; // Initialize global strings LoadString(mhInst, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(mhInst, IDC_WIN_CRASH_LOGGER, szWindowClass, MAX_LOADSTRING); gCursorArrow = LoadCursor(NULL, IDC_ARROW); gCursorWait = LoadCursor(NULL, IDC_WAIT); // Register a window class that will be used by our dialogs WNDCLASS wndclass; wndclass.style = CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc = WndProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = DLGWINDOWEXTRA; // Required, since this is used for dialogs! wndclass.hInstance = mhInst; wndclass.hIcon = LoadIcon(hInst, MAKEINTRESOURCE( IDI_WIN_CRASH_LOGGER ) ); wndclass.hCursor = gCursorArrow; wndclass.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = szWindowClass; RegisterClass( &wndclass ); return true; } void LLCrashLoggerWindows::gatherPlatformSpecificFiles() { updateApplication("Gathering hardware information. App may appear frozen."); // DX hardware probe blocks, so we can't cancel during it //Generate our dx_info.log file SetCursor(gCursorWait); // At this point we're responsive enough the user could click the close button SetCursor(gCursorArrow); //mDebugLog["DisplayDeviceInfo"] = gDXHardware.getDisplayInfo(); //Not initialized. } bool LLCrashLoggerWindows::mainLoop() { LL_INFOS() << "CrashSubmitBehavior is " << mCrashBehavior << LL_ENDL; // Note: parent hwnd is 0 (the desktop). No dlg proc. See Petzold (5th ed) HexCalc example, Chapter 11, p529 // win_crash_logger.rc has been edited by hand. // Dialogs defined with CLASS "WIN_CRASH_LOGGER" (must be same as szWindowClass) gProductName = mProductName; gHwndProgress = CreateDialog(hInst, MAKEINTRESOURCE(IDD_PROGRESS), 0, NULL); ProcessCaption(gHwndProgress); ShowWindow(gHwndProgress, SW_HIDE ); if (mCrashBehavior == CRASH_BEHAVIOR_ALWAYS_SEND) { LL_INFOS() << "Showing crash report submit progress window." << LL_ENDL; //ShowWindow(gHwndProgress, SW_SHOW ); Maint-5707 sendCrashLogs(); } else if (mCrashBehavior == CRASH_BEHAVIOR_ASK) { gHwndReport = CreateDialog(hInst, MAKEINTRESOURCE(IDD_PREVREPORTBOX), 0, NULL); // Ignore result (void) SendDlgItemMessage(gHwndReport, IDC_CHECK_AUTO, BM_SETCHECK, 0, 0); // Include the product name in the caption and various dialog items. ProcessCaption(gHwndReport); ProcessDlgItemText(gHwndReport, IDC_STATIC_MSG); // Update the header to include whether or not we crashed on the last run. std::string headerStr; TCHAR header[MAX_STRING]; if (mCrashInPreviousExec) { headerStr = llformat("%s appears to have crashed or frozen the last time it ran.", mProductName.c_str()); } else { headerStr = llformat("%s appears to have crashed.", mProductName.c_str()); } ConvertLPCSTRToLPWSTR(headerStr.c_str(), header); SetDlgItemText(gHwndReport, IDC_STATIC_HEADER, header); ShowWindow(gHwndReport, SW_SHOW ); MSG msg; memset(&msg, 0, sizeof(msg)); while (!LLApp::isQuitting() && GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } else { LL_WARNS() << "Unknown crash behavior " << mCrashBehavior << LL_ENDL; return 1; } return 0; } void LLCrashLoggerWindows::updateApplication(const std::string& message) { LLCrashLogger::updateApplication(message); if(!message.empty()) show_progress(message); update_messages(); } bool LLCrashLoggerWindows::cleanup() { if(gSendLogs) { if(mSentCrashLogs) show_progress("Done"); else show_progress("Could not connect to servers, logs not sent"); sleep_and_pump_messages(3); } PostQuitMessage(0); commonCleanup(); mKeyMaster.releaseMaster(); return true; }