phoenix-firestorm/indra/win_crash_logger/llcrashloggerwindows.cpp

548 lines
15 KiB
C++

/**
* @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 <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;
const int timerID=37;
SetTimer(NULL, timerID, 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;
static int first_connect = 1;
LLSD options = getOptionData( LLApp::PRIORITY_COMMAND_LINE );
MSG msg;
bool result;
while (1)
{
result = getMessageWithTimeout(&msg, millisecs);
if ( result )
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (first_connect )
{
if ( mClientsConnected > 0)
{
first_connect = 0;
}
}
else
{
if (mClientsConnected == 0)
{
break;
}
if (!mKeyMaster.isProcessAlive(mPID, mProcName) )
{
break;
}
}
}
llinfos << "session ending.." << llendl;
llinfos << "clients connected :" << mClientsConnected << llendl;
return 0;
}
void LLCrashLoggerWindows::OnClientConnected(void* context,
const google_breakpad::ClientInfo* client_info)
{
llinfos << "client start. pid = " << client_info->pid() << llendl;
sInstance->mClientsConnected++;
}
void LLCrashLoggerWindows::OnClientExited(void* context,
const google_breakpad::ClientInfo* client_info)
{
llinfos << "client end. pid = " << client_info->pid() << llendl;
sInstance->mClientsConnected--;
}
/*
void LLCrashLoggerWindows::OnClientDumpRequest(void* context,
const google_breakpad::ClientInfo* client_info,
const std::wstring* file_path)
{
ProcessingLock lock;
if (!file_path)
{
llwarns << "dump with no file path" << llendl;
return;
}
if (!client_info)
{
llwarns << "dump with no client info" << llendl;
return;
}
LLCrashLoggerWindows* self = static_cast<LLCrashLoggerWindows*>(context);
if (!self)
{
llwarns << "dump with no context" << llendl;
return;
}
DWORD pid = client_info->pid();
// Send the crash dump using a worker thread. This operation has retry
// logic in case there is no internet connection at the time.
DumpJobInfo* dump_job = new DumpJobInfo(pid, self, map,
dump_location.value());
if (!::QueueUserWorkItem(&CrashService::AsyncSendDump,
dump_job, WT_EXECUTELONGFUNCTION)) {
LOG(ERROR) << "could not queue job";
}
}
*/
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();
std::wostringstream ws;
//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.
ws << mCrashReportPipeStr << mPID;
std::wstring wpipe_name = ws.str();
std::wstring wdump_path;
wdump_path.assign(dump_path.begin(), dump_path.end());
//Pipe naming conventions: http://msdn.microsoft.com/en-us/library/aa365783%28v=vs.85%29.aspx
mCrashHandler = new CrashGenerationServer( (WCHAR *)wpipe_name.c_str(),
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.
llwarns << "Failed to init crash server." << llendl;
return false;
}
// Start servicing clients.
if (!mCrashHandler->Start()) {
llwarns << "Failed to start crash server." << llendl;
return false;
}
return true;
}
bool LLCrashLoggerWindows::init(void)
{
initCrashServer();
bool ok = LLCrashLogger::init();
if(!ok) return false;
/*
mbstowcs( gProductName, mProductName.c_str(), LL_ARRAY_SIZE(gProductName) );
gProductName[ LL_ARRY_SIZE(gProductName) - 1 ] = 0;
swprintf(gProductName, L"Second Life");
*/
llinfos << "Loading dialogs" << llendl;
// 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()
{
llinfos << "CrashSubmitBehavior is " << mCrashBehavior << llendl;
// 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)
{
llinfos << "Showing crash report submit progress window." << llendl;
ShowWindow(gHwndProgress, SW_SHOW );
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
{
llwarns << "Unknown crash behavior " << mCrashBehavior << llendl;
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;
}