| // Copyright (c) 2008, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| // crash_generation_app.cpp : Defines the entry point for the application. |
| // |
| |
| #include "client/windows/tests/crash_generation_app/crash_generation_app.h" |
| |
| #include <windows.h> |
| #include <tchar.h> |
| |
| #include "client/windows/crash_generation/client_info.h" |
| #include "client/windows/crash_generation/crash_generation_server.h" |
| #include "client/windows/handler/exception_handler.h" |
| #include "client/windows/common/ipc_protocol.h" |
| |
| #include "client/windows/tests/crash_generation_app/abstract_class.h" |
| |
| namespace google_breakpad { |
| |
| const int kMaxLoadString = 100; |
| const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer"; |
| |
| const DWORD kEditBoxStyles = WS_CHILD | |
| WS_VISIBLE | |
| WS_VSCROLL | |
| ES_LEFT | |
| ES_MULTILINE | |
| ES_AUTOVSCROLL | |
| ES_READONLY; |
| |
| // Maximum length of a line in the edit box. |
| const size_t kMaximumLineLength = 256; |
| |
| // CS to access edit control in a thread safe way. |
| static CRITICAL_SECTION* cs_edit = NULL; |
| |
| // Edit control. |
| static HWND client_status_edit_box; |
| |
| HINSTANCE current_instance; // Current instance. |
| TCHAR title[kMaxLoadString]; // Title bar text. |
| TCHAR window_class[kMaxLoadString]; // Main window class name. |
| |
| ATOM MyRegisterClass(HINSTANCE instance); |
| BOOL InitInstance(HINSTANCE, int); |
| LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); |
| INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); |
| |
| static size_t kCustomInfoCount = 2; |
| static CustomInfoEntry kCustomInfoEntries[] = { |
| CustomInfoEntry(L"prod", L"CrashTestApp"), |
| CustomInfoEntry(L"ver", L"1.0"), |
| }; |
| |
| static ExceptionHandler* handler = NULL; |
| static CrashGenerationServer* crash_server = NULL; |
| |
| // Registers the window class. |
| // |
| // This function and its usage are only necessary if you want this code |
| // to be compatible with Win32 systems prior to the 'RegisterClassEx' |
| // function that was added to Windows 95. It is important to call this |
| // function so that the application will get 'well formed' small icons |
| // associated with it. |
| ATOM MyRegisterClass(HINSTANCE instance) { |
| WNDCLASSEX wcex; |
| wcex.cbSize = sizeof(WNDCLASSEX); |
| wcex.style = CS_HREDRAW | CS_VREDRAW; |
| wcex.lpfnWndProc = WndProc; |
| wcex.cbClsExtra = 0; |
| wcex.cbWndExtra = 0; |
| wcex.hInstance = instance; |
| wcex.hIcon = LoadIcon(instance, |
| MAKEINTRESOURCE(IDI_CRASHGENERATIONAPP)); |
| wcex.hCursor = LoadCursor(NULL, IDC_ARROW); |
| wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); |
| wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP); |
| wcex.lpszClassName = window_class; |
| wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); |
| |
| return RegisterClassEx(&wcex); |
| } |
| |
| // Saves instance handle and creates main window |
| // |
| // In this function, we save the instance handle in a global variable and |
| // create and display the main program window. |
| BOOL InitInstance(HINSTANCE instance, int command_show) { |
| current_instance = instance; |
| HWND wnd = CreateWindow(window_class, |
| title, |
| WS_OVERLAPPEDWINDOW, |
| CW_USEDEFAULT, |
| 0, |
| CW_USEDEFAULT, |
| 0, |
| NULL, |
| NULL, |
| instance, |
| NULL); |
| |
| if (!wnd) { |
| return FALSE; |
| } |
| |
| ShowWindow(wnd, command_show); |
| UpdateWindow(wnd); |
| |
| return TRUE; |
| } |
| |
| static void AppendTextToEditBox(TCHAR* text) { |
| EnterCriticalSection(cs_edit); |
| SYSTEMTIME current_time; |
| GetLocalTime(¤t_time); |
| TCHAR line[kMaximumLineLength]; |
| int result = swprintf_s(line, |
| kMaximumLineLength, |
| L"[%.2d-%.2d-%.4d %.2d:%.2d:%.2d] %s", |
| current_time.wMonth, |
| current_time.wDay, |
| current_time.wYear, |
| current_time.wHour, |
| current_time.wMinute, |
| current_time.wSecond, |
| text); |
| |
| if (result == -1) { |
| return; |
| } |
| |
| int length = GetWindowTextLength(client_status_edit_box); |
| SendMessage(client_status_edit_box, |
| EM_SETSEL, |
| (WPARAM)length, |
| (LPARAM)length); |
| SendMessage(client_status_edit_box, |
| EM_REPLACESEL, |
| (WPARAM)FALSE, |
| (LPARAM)line); |
| LeaveCriticalSection(cs_edit); |
| } |
| |
| static DWORD WINAPI AppendTextWorker(void* context) { |
| TCHAR* text = reinterpret_cast<TCHAR*>(context); |
| |
| AppendTextToEditBox(text); |
| delete[] text; |
| |
| return 0; |
| } |
| |
| bool ShowDumpResults(const wchar_t* dump_path, |
| const wchar_t* minidump_id, |
| void* context, |
| EXCEPTION_POINTERS* exinfo, |
| MDRawAssertionInfo* assertion, |
| bool succeeded) { |
| TCHAR* text = new TCHAR[kMaximumLineLength]; |
| text[0] = _T('\0'); |
| int result = swprintf_s(text, |
| kMaximumLineLength, |
| TEXT("Dump generation request %s\r\n"), |
| succeeded ? TEXT("succeeded") : TEXT("failed")); |
| if (result == -1) { |
| delete [] text; |
| } |
| |
| QueueUserWorkItem(AppendTextWorker, text, WT_EXECUTEDEFAULT); |
| return succeeded; |
| } |
| |
| static void ShowClientConnected(void* context, |
| const ClientInfo* client_info) { |
| TCHAR* line = new TCHAR[kMaximumLineLength]; |
| line[0] = _T('\0'); |
| int result = swprintf_s(line, |
| kMaximumLineLength, |
| L"Client connected:\t\t%d\r\n", |
| client_info->pid()); |
| |
| if (result == -1) { |
| delete[] line; |
| return; |
| } |
| |
| QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); |
| } |
| |
| static void ShowClientCrashed(void* context, |
| const ClientInfo* client_info, |
| const wstring* dump_path) { |
| TCHAR* line = new TCHAR[kMaximumLineLength]; |
| line[0] = _T('\0'); |
| int result = swprintf_s(line, |
| kMaximumLineLength, |
| TEXT("Client requested dump:\t%d\r\n"), |
| client_info->pid()); |
| |
| if (result == -1) { |
| delete[] line; |
| return; |
| } |
| |
| QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); |
| |
| CustomClientInfo custom_info = client_info->GetCustomInfo(); |
| if (custom_info.count <= 0) { |
| return; |
| } |
| |
| wstring str_line; |
| for (size_t i = 0; i < custom_info.count; ++i) { |
| if (i > 0) { |
| str_line += L", "; |
| } |
| str_line += custom_info.entries[i].name; |
| str_line += L": "; |
| str_line += custom_info.entries[i].value; |
| } |
| |
| line = new TCHAR[kMaximumLineLength]; |
| line[0] = _T('\0'); |
| result = swprintf_s(line, |
| kMaximumLineLength, |
| L"%s\n", |
| str_line.c_str()); |
| if (result == -1) { |
| delete[] line; |
| return; |
| } |
| QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); |
| } |
| |
| static void ShowClientExited(void* context, |
| const ClientInfo* client_info) { |
| TCHAR* line = new TCHAR[kMaximumLineLength]; |
| line[0] = _T('\0'); |
| int result = swprintf_s(line, |
| kMaximumLineLength, |
| TEXT("Client exited:\t\t%d\r\n"), |
| client_info->pid()); |
| |
| if (result == -1) { |
| delete[] line; |
| return; |
| } |
| |
| QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT); |
| } |
| |
| void CrashServerStart() { |
| // Do not create another instance of the server. |
| if (crash_server) { |
| return; |
| } |
| |
| std::wstring dump_path = L"C:\\Dumps\\"; |
| |
| if (_wmkdir(dump_path.c_str()) && (errno != EEXIST)) { |
| MessageBoxW(NULL, L"Unable to create dump directory", L"Dumper", MB_OK); |
| return; |
| } |
| |
| crash_server = new CrashGenerationServer(kPipeName, |
| NULL, |
| ShowClientConnected, |
| NULL, |
| ShowClientCrashed, |
| NULL, |
| ShowClientExited, |
| NULL, |
| NULL, |
| NULL, |
| true, |
| &dump_path); |
| |
| if (!crash_server->Start()) { |
| MessageBoxW(NULL, L"Unable to start server", L"Dumper", MB_OK); |
| delete crash_server; |
| crash_server = NULL; |
| } |
| } |
| |
| void CrashServerStop() { |
| delete crash_server; |
| crash_server = NULL; |
| } |
| |
| void DerefZeroCrash() { |
| int* x = 0; |
| *x = 1; |
| } |
| |
| void InvalidParamCrash() { |
| printf(NULL); |
| } |
| |
| void PureCallCrash() { |
| Derived derived; |
| } |
| |
| void RequestDump() { |
| if (!handler->WriteMinidump()) { |
| MessageBoxW(NULL, L"Dump request failed", L"Dumper", MB_OK); |
| } |
| kCustomInfoEntries[1].set_value(L"1.1"); |
| } |
| |
| void CleanUp() { |
| if (cs_edit) { |
| DeleteCriticalSection(cs_edit); |
| delete cs_edit; |
| } |
| |
| if (handler) { |
| delete handler; |
| } |
| |
| if (crash_server) { |
| delete crash_server; |
| } |
| } |
| |
| // Processes messages for the main window. |
| // |
| // WM_COMMAND - process the application menu. |
| // WM_PAINT - Paint the main window. |
| // WM_DESTROY - post a quit message and return. |
| LRESULT CALLBACK WndProc(HWND wnd, |
| UINT message, |
| WPARAM w_param, |
| LPARAM l_param) { |
| int message_id; |
| int message_event; |
| PAINTSTRUCT ps; |
| HDC hdc; |
| |
| HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(wnd, GWLP_HINSTANCE); |
| |
| switch (message) { |
| case WM_COMMAND: |
| // Parse the menu selections. |
| message_id = LOWORD(w_param); |
| message_event = HIWORD(w_param); |
| switch (message_id) { |
| case IDM_ABOUT: |
| DialogBox(current_instance, |
| MAKEINTRESOURCE(IDD_ABOUTBOX), |
| wnd, |
| About); |
| break; |
| case IDM_EXIT: |
| DestroyWindow(wnd); |
| break; |
| case ID_SERVER_START: |
| CrashServerStart(); |
| break; |
| case ID_SERVER_STOP: |
| CrashServerStop(); |
| break; |
| case ID_CLIENT_DEREFZERO: |
| DerefZeroCrash(); |
| break; |
| case ID_CLIENT_INVALIDPARAM: |
| InvalidParamCrash(); |
| break; |
| case ID_CLIENT_PURECALL: |
| PureCallCrash(); |
| break; |
| case ID_CLIENT_REQUESTEXPLICITDUMP: |
| RequestDump(); |
| break; |
| default: |
| return DefWindowProc(wnd, message, w_param, l_param); |
| } |
| break; |
| case WM_CREATE: |
| client_status_edit_box = CreateWindow(TEXT("EDIT"), |
| NULL, |
| kEditBoxStyles, |
| 0, |
| 0, |
| 0, |
| 0, |
| wnd, |
| NULL, |
| instance, |
| NULL); |
| break; |
| case WM_SIZE: |
| // Make the edit control the size of the window's client area. |
| MoveWindow(client_status_edit_box, |
| 0, |
| 0, |
| LOWORD(l_param), // width of client area. |
| HIWORD(l_param), // height of client area. |
| TRUE); // repaint window. |
| break; |
| case WM_SETFOCUS: |
| SetFocus(client_status_edit_box); |
| break; |
| case WM_PAINT: |
| hdc = BeginPaint(wnd, &ps); |
| EndPaint(wnd, &ps); |
| break; |
| case WM_DESTROY: |
| CleanUp(); |
| PostQuitMessage(0); |
| break; |
| default: |
| return DefWindowProc(wnd, message, w_param, l_param); |
| } |
| |
| return 0; |
| } |
| |
| // Message handler for about box. |
| INT_PTR CALLBACK About(HWND dlg, |
| UINT message, |
| WPARAM w_param, |
| LPARAM l_param) { |
| UNREFERENCED_PARAMETER(l_param); |
| switch (message) { |
| case WM_INITDIALOG: |
| return (INT_PTR)TRUE; |
| |
| case WM_COMMAND: |
| if (LOWORD(w_param) == IDOK || LOWORD(w_param) == IDCANCEL) { |
| EndDialog(dlg, LOWORD(w_param)); |
| return (INT_PTR)TRUE; |
| } |
| break; |
| } |
| |
| return (INT_PTR)FALSE; |
| } |
| |
| } // namespace google_breakpad |
| |
| int APIENTRY _tWinMain(HINSTANCE instance, |
| HINSTANCE previous_instance, |
| LPTSTR command_line, |
| int command_show) { |
| using namespace google_breakpad; |
| |
| UNREFERENCED_PARAMETER(previous_instance); |
| UNREFERENCED_PARAMETER(command_line); |
| |
| cs_edit = new CRITICAL_SECTION(); |
| InitializeCriticalSection(cs_edit); |
| |
| CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount}; |
| |
| CrashServerStart(); |
| // This is needed for CRT to not show dialog for invalid param |
| // failures and instead let the code handle it. |
| _CrtSetReportMode(_CRT_ASSERT, 0); |
| handler = new ExceptionHandler(L"C:\\dumps\\", |
| NULL, |
| google_breakpad::ShowDumpResults, |
| NULL, |
| ExceptionHandler::HANDLER_ALL, |
| MiniDumpNormal, |
| kPipeName, |
| &custom_info); |
| |
| // Initialize global strings. |
| LoadString(instance, IDS_APP_TITLE, title, kMaxLoadString); |
| LoadString(instance, |
| IDC_CRASHGENERATIONAPP, |
| window_class, |
| kMaxLoadString); |
| MyRegisterClass(instance); |
| |
| // Perform application initialization. |
| if (!InitInstance(instance, command_show)) { |
| return FALSE; |
| } |
| |
| HACCEL accel_table = LoadAccelerators( |
| instance, |
| MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP)); |
| |
| // Main message loop. |
| MSG msg; |
| while (GetMessage(&msg, NULL, 0, 0)) { |
| if (!TranslateAccelerator(msg.hwnd, accel_table, &msg)) { |
| TranslateMessage(&msg); |
| DispatchMessage(&msg); |
| } |
| } |
| |
| return static_cast<int>(msg.wParam); |
| } |