1// Copyright (c) 2008, Google Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright
9// notice, this list of conditions and the following disclaimer.
10//     * Redistributions in binary form must reproduce the above
11// copyright notice, this list of conditions and the following disclaimer
12// in the documentation and/or other materials provided with the
13// distribution.
14//     * Neither the name of Google Inc. nor the names of its
15// contributors may be used to endorse or promote products derived from
16// this software without specific prior written permission.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30// crash_generation_app.cpp : Defines the entry point for the application.
31//
32
33#include "client/windows/tests/crash_generation_app/crash_generation_app.h"
34
35#include <windows.h>
36#include <tchar.h>
37
38#include "client/windows/crash_generation/client_info.h"
39#include "client/windows/crash_generation/crash_generation_server.h"
40#include "client/windows/handler/exception_handler.h"
41#include "client/windows/common/ipc_protocol.h"
42
43#include "client/windows/tests/crash_generation_app/abstract_class.h"
44
45namespace google_breakpad {
46
47const int kMaxLoadString = 100;
48const wchar_t kPipeName[] = L"\\\\.\\pipe\\BreakpadCrashServices\\TestServer";
49
50const DWORD kEditBoxStyles = WS_CHILD |
51                             WS_VISIBLE |
52                             WS_VSCROLL |
53                             ES_LEFT |
54                             ES_MULTILINE |
55                             ES_AUTOVSCROLL |
56                             ES_READONLY;
57
58// Maximum length of a line in the edit box.
59const size_t kMaximumLineLength = 256;
60
61// CS to access edit control in a thread safe way.
62static CRITICAL_SECTION* cs_edit = NULL;
63
64// Edit control.
65static HWND client_status_edit_box;
66
67HINSTANCE current_instance;             // Current instance.
68TCHAR title[kMaxLoadString];            // Title bar text.
69TCHAR window_class[kMaxLoadString];     // Main window class name.
70
71ATOM MyRegisterClass(HINSTANCE instance);
72BOOL InitInstance(HINSTANCE, int);
73LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
74INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
75
76static int kCustomInfoCount = 2;
77static CustomInfoEntry kCustomInfoEntries[] = {
78    CustomInfoEntry(L"prod", L"CrashTestApp"),
79    CustomInfoEntry(L"ver", L"1.0"),
80};
81
82static ExceptionHandler* handler = NULL;
83static CrashGenerationServer* crash_server = NULL;
84
85// Registers the window class.
86//
87// This function and its usage are only necessary if you want this code
88// to be compatible with Win32 systems prior to the 'RegisterClassEx'
89// function that was added to Windows 95. It is important to call this
90// function so that the application will get 'well formed' small icons
91// associated with it.
92ATOM MyRegisterClass(HINSTANCE instance) {
93  WNDCLASSEX wcex;
94  wcex.cbSize = sizeof(WNDCLASSEX);
95  wcex.style = CS_HREDRAW | CS_VREDRAW;
96  wcex.lpfnWndProc = WndProc;
97  wcex.cbClsExtra = 0;
98  wcex.cbWndExtra = 0;
99  wcex.hInstance = instance;
100  wcex.hIcon = LoadIcon(instance,
101                        MAKEINTRESOURCE(IDI_CRASHGENERATIONAPP));
102  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
103  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
104  wcex.lpszMenuName = MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP);
105  wcex.lpszClassName = window_class;
106  wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
107
108  return RegisterClassEx(&wcex);
109}
110
111// Saves instance handle and creates main window
112//
113// In this function, we save the instance handle in a global variable and
114//   create and display the main program window.
115BOOL InitInstance(HINSTANCE instance, int command_show) {
116  current_instance = instance;
117  HWND wnd = CreateWindow(window_class,
118                          title,
119                          WS_OVERLAPPEDWINDOW,
120                          CW_USEDEFAULT,
121                          0,
122                          CW_USEDEFAULT,
123                          0,
124                          NULL,
125                          NULL,
126                          instance,
127                          NULL);
128
129  if (!wnd) {
130    return FALSE;
131  }
132
133  ShowWindow(wnd, command_show);
134  UpdateWindow(wnd);
135
136  return TRUE;
137}
138
139static void AppendTextToEditBox(TCHAR* text) {
140  EnterCriticalSection(cs_edit);
141  SYSTEMTIME current_time;
142  GetLocalTime(&current_time);
143  TCHAR line[kMaximumLineLength];
144  int result = swprintf_s(line,
145                          kMaximumLineLength,
146                          L"[%.2d-%.2d-%.4d %.2d:%.2d:%.2d] %s",
147                          current_time.wMonth,
148                          current_time.wDay,
149                          current_time.wYear,
150                          current_time.wHour,
151                          current_time.wMinute,
152                          current_time.wSecond,
153                          text);
154
155  if (result == -1) {
156    return;
157  }
158
159  int length = GetWindowTextLength(client_status_edit_box);
160  SendMessage(client_status_edit_box,
161              EM_SETSEL,
162              (WPARAM)length,
163              (LPARAM)length);
164  SendMessage(client_status_edit_box,
165              EM_REPLACESEL,
166              (WPARAM)FALSE,
167              (LPARAM)line);
168  LeaveCriticalSection(cs_edit);
169}
170
171static DWORD WINAPI AppendTextWorker(void* context) {
172  TCHAR* text = reinterpret_cast<TCHAR*>(context);
173
174  AppendTextToEditBox(text);
175  delete[] text;
176
177  return 0;
178}
179
180bool ShowDumpResults(const wchar_t* dump_path,
181                     const wchar_t* minidump_id,
182                     void* context,
183                     EXCEPTION_POINTERS* exinfo,
184                     MDRawAssertionInfo* assertion,
185                     bool succeeded) {
186  TCHAR* text = new TCHAR[kMaximumLineLength];
187  text[0] = _T('\0');
188  int result = swprintf_s(text,
189                          kMaximumLineLength,
190                          TEXT("Dump generation request %s\r\n"),
191                          succeeded ? TEXT("succeeded") : TEXT("failed"));
192  if (result == -1) {
193    delete [] text;
194  }
195
196  QueueUserWorkItem(AppendTextWorker, text, WT_EXECUTEDEFAULT);
197  return succeeded;
198}
199
200static void ShowClientConnected(void* context,
201                                const ClientInfo* client_info) {
202  TCHAR* line = new TCHAR[kMaximumLineLength];
203  line[0] = _T('\0');
204  int result = swprintf_s(line,
205                          kMaximumLineLength,
206                          L"Client connected:\t\t%d\r\n",
207                          client_info->pid());
208
209  if (result == -1) {
210    delete[] line;
211    return;
212  }
213
214  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
215}
216
217static void ShowClientCrashed(void* context,
218                              const ClientInfo* client_info,
219                              const wstring* dump_path) {
220  TCHAR* line = new TCHAR[kMaximumLineLength];
221  line[0] = _T('\0');
222  int result = swprintf_s(line,
223                          kMaximumLineLength,
224                          TEXT("Client requested dump:\t%d\r\n"),
225                          client_info->pid());
226
227  if (result == -1) {
228    delete[] line;
229    return;
230  }
231
232  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
233
234  CustomClientInfo custom_info = client_info->GetCustomInfo();
235  if (custom_info.count <= 0) {
236    return;
237  }
238
239  wstring str_line;
240  for (size_t i = 0; i < custom_info.count; ++i) {
241    if (i > 0) {
242      str_line += L", ";
243    }
244    str_line += custom_info.entries[i].name;
245    str_line += L": ";
246    str_line += custom_info.entries[i].value;
247  }
248
249  line = new TCHAR[kMaximumLineLength];
250  line[0] = _T('\0');
251  result = swprintf_s(line,
252                      kMaximumLineLength,
253                      L"%s\n",
254                      str_line.c_str());
255  if (result == -1) {
256    delete[] line;
257    return;
258  }
259  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
260}
261
262static void ShowClientExited(void* context,
263                             const ClientInfo* client_info) {
264  TCHAR* line = new TCHAR[kMaximumLineLength];
265  line[0] = _T('\0');
266  int result = swprintf_s(line,
267                          kMaximumLineLength,
268                          TEXT("Client exited:\t\t%d\r\n"),
269                          client_info->pid());
270
271  if (result == -1) {
272    delete[] line;
273    return;
274  }
275
276  QueueUserWorkItem(AppendTextWorker, line, WT_EXECUTEDEFAULT);
277}
278
279void CrashServerStart() {
280  // Do not create another instance of the server.
281  if (crash_server) {
282    return;
283  }
284
285  std::wstring dump_path = L"C:\\Dumps\\";
286
287  if (_wmkdir(dump_path.c_str()) && (errno != EEXIST)) {
288    MessageBoxW(NULL, L"Unable to create dump directory", L"Dumper", MB_OK);
289    return;
290  }
291
292  crash_server = new CrashGenerationServer(kPipeName,
293                                           NULL,
294                                           ShowClientConnected,
295                                           NULL,
296                                           ShowClientCrashed,
297                                           NULL,
298                                           ShowClientExited,
299                                           NULL,
300                                           NULL,
301                                           NULL,
302                                           true,
303                                           &dump_path);
304
305  if (!crash_server->Start()) {
306    MessageBoxW(NULL, L"Unable to start server", L"Dumper", MB_OK);
307    delete crash_server;
308    crash_server = NULL;
309  }
310}
311
312void CrashServerStop() {
313  delete crash_server;
314  crash_server = NULL;
315}
316
317void DerefZeroCrash() {
318  int* x = 0;
319  *x = 1;
320}
321
322void InvalidParamCrash() {
323  printf(NULL);
324}
325
326void PureCallCrash() {
327  Derived derived;
328}
329
330void RequestDump() {
331  if (!handler->WriteMinidump()) {
332    MessageBoxW(NULL, L"Dump request failed", L"Dumper", MB_OK);
333  }
334  kCustomInfoEntries[1].set_value(L"1.1");
335}
336
337void CleanUp() {
338  if (cs_edit) {
339    DeleteCriticalSection(cs_edit);
340    delete cs_edit;
341  }
342
343  if (handler) {
344    delete handler;
345  }
346
347  if (crash_server) {
348    delete crash_server;
349  }
350}
351
352// Processes messages for the main window.
353//
354// WM_COMMAND - process the application menu.
355// WM_PAINT   - Paint the main window.
356// WM_DESTROY - post a quit message and return.
357LRESULT CALLBACK WndProc(HWND wnd,
358                         UINT message,
359                         WPARAM w_param,
360                         LPARAM l_param) {
361  int message_id;
362  int message_event;
363  PAINTSTRUCT ps;
364  HDC hdc;
365
366  HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(wnd, GWLP_HINSTANCE);
367
368  switch (message) {
369    case WM_COMMAND:
370      // Parse the menu selections.
371      message_id = LOWORD(w_param);
372      message_event = HIWORD(w_param);
373      switch (message_id) {
374        case IDM_ABOUT:
375          DialogBox(current_instance,
376                    MAKEINTRESOURCE(IDD_ABOUTBOX),
377                    wnd,
378                    About);
379          break;
380        case IDM_EXIT:
381          DestroyWindow(wnd);
382          break;
383        case ID_SERVER_START:
384          CrashServerStart();
385          break;
386        case ID_SERVER_STOP:
387          CrashServerStop();
388          break;
389        case ID_CLIENT_DEREFZERO:
390          DerefZeroCrash();
391          break;
392        case ID_CLIENT_INVALIDPARAM:
393          InvalidParamCrash();
394          break;
395        case ID_CLIENT_PURECALL:
396          PureCallCrash();
397          break;
398        case ID_CLIENT_REQUESTEXPLICITDUMP:
399          RequestDump();
400          break;
401        default:
402          return DefWindowProc(wnd, message, w_param, l_param);
403      }
404      break;
405    case WM_CREATE:
406      client_status_edit_box = CreateWindow(TEXT("EDIT"),
407                                            NULL,
408                                            kEditBoxStyles,
409                                            0,
410                                            0,
411                                            0,
412                                            0,
413                                            wnd,
414                                            NULL,
415                                            instance,
416                                            NULL);
417      break;
418    case WM_SIZE:
419      // Make the edit control the size of the window's client area.
420      MoveWindow(client_status_edit_box,
421                 0,
422                 0,
423                 LOWORD(l_param),        // width of client area.
424                 HIWORD(l_param),        // height of client area.
425                 TRUE);                  // repaint window.
426      break;
427    case WM_SETFOCUS:
428      SetFocus(client_status_edit_box);
429      break;
430    case WM_PAINT:
431      hdc = BeginPaint(wnd, &ps);
432      EndPaint(wnd, &ps);
433      break;
434    case WM_DESTROY:
435      CleanUp();
436      PostQuitMessage(0);
437      break;
438    default:
439      return DefWindowProc(wnd, message, w_param, l_param);
440  }
441
442  return 0;
443}
444
445// Message handler for about box.
446INT_PTR CALLBACK About(HWND dlg,
447                       UINT message,
448                       WPARAM w_param,
449                       LPARAM l_param) {
450  UNREFERENCED_PARAMETER(l_param);
451  switch (message) {
452    case WM_INITDIALOG:
453      return (INT_PTR)TRUE;
454
455    case WM_COMMAND:
456      if (LOWORD(w_param) == IDOK || LOWORD(w_param) == IDCANCEL) {
457        EndDialog(dlg, LOWORD(w_param));
458        return (INT_PTR)TRUE;
459      }
460      break;
461  }
462
463  return (INT_PTR)FALSE;
464}
465
466}  // namespace google_breakpad
467
468int APIENTRY _tWinMain(HINSTANCE instance,
469                       HINSTANCE previous_instance,
470                       LPTSTR command_line,
471                       int command_show) {
472  using namespace google_breakpad;
473
474  UNREFERENCED_PARAMETER(previous_instance);
475  UNREFERENCED_PARAMETER(command_line);
476
477  cs_edit = new CRITICAL_SECTION();
478  InitializeCriticalSection(cs_edit);
479
480  CustomClientInfo custom_info = {kCustomInfoEntries, kCustomInfoCount};
481
482  CrashServerStart();
483  // This is needed for CRT to not show dialog for invalid param
484  // failures and instead let the code handle it.
485  _CrtSetReportMode(_CRT_ASSERT, 0);
486  handler = new ExceptionHandler(L"C:\\dumps\\",
487                                 NULL,
488                                 google_breakpad::ShowDumpResults,
489                                 NULL,
490                                 ExceptionHandler::HANDLER_ALL,
491                                 MiniDumpNormal,
492                                 kPipeName,
493                                 &custom_info);
494
495  // Initialize global strings.
496  LoadString(instance, IDS_APP_TITLE, title, kMaxLoadString);
497  LoadString(instance,
498             IDC_CRASHGENERATIONAPP,
499             window_class,
500             kMaxLoadString);
501  MyRegisterClass(instance);
502
503  // Perform application initialization.
504  if (!InitInstance(instance, command_show)) {
505    return FALSE;
506  }
507
508  HACCEL accel_table = LoadAccelerators(
509      instance,
510      MAKEINTRESOURCE(IDC_CRASHGENERATIONAPP));
511
512  // Main message loop.
513  MSG msg;
514  while (GetMessage(&msg, NULL, 0, 0)) {
515    if (!TranslateAccelerator(msg.hwnd, accel_table, &msg)) {
516      TranslateMessage(&msg);
517      DispatchMessage(&msg);
518    }
519  }
520
521  return static_cast<int>(msg.wParam);
522}
523