1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "sandbox/win/sandbox_poc/main_ui_window.h"
6
7 #include <windows.h>
8 #include <CommCtrl.h>
9 #include <commdlg.h>
10 #include <stdarg.h>
11 #include <stddef.h>
12 #include <time.h>
13 #include <windowsx.h>
14
15 #include <algorithm>
16 #include <sstream>
17
18 #include "base/logging.h"
19 #include "base/win/atl.h"
20 #include "sandbox/win/sandbox_poc/resource.h"
21 #include "sandbox/win/src/acl.h"
22 #include "sandbox/win/src/sandbox.h"
23 #include "sandbox/win/src/win_utils.h"
24
25 HWND MainUIWindow::list_view_ = NULL;
26
27 const wchar_t MainUIWindow::kDefaultDll_[] = L"\\POCDLL.dll";
28 const wchar_t MainUIWindow::kDefaultEntryPoint_[] = L"Run";
29 const wchar_t MainUIWindow::kDefaultLogFile_[] = L"";
30
MainUIWindow()31 MainUIWindow::MainUIWindow()
32 : broker_(nullptr),
33 spawn_target_(L""),
34 instance_handle_(NULL),
35 dll_path_(L""),
36 entry_point_(L"") {}
37
~MainUIWindow()38 MainUIWindow::~MainUIWindow() {
39 }
40
CreateMainWindowAndLoop(HINSTANCE instance,wchar_t * command_line,int show_command,sandbox::BrokerServices * broker)41 unsigned int MainUIWindow::CreateMainWindowAndLoop(
42 HINSTANCE instance,
43 wchar_t* command_line,
44 int show_command,
45 sandbox::BrokerServices* broker) {
46 DCHECK(instance);
47 DCHECK(command_line);
48 DCHECK(broker);
49
50 instance_handle_ = instance;
51 spawn_target_ = command_line;
52 broker_ = broker;
53
54 // We'll use spawn_target_ later for creating a child process, but
55 // CreateProcess doesn't like double quotes, so we remove them along with
56 // tabs and spaces from the start and end of the string
57 const wchar_t *trim_removal = L" \r\t\"";
58 spawn_target_.erase(0, spawn_target_.find_first_not_of(trim_removal));
59 spawn_target_.erase(spawn_target_.find_last_not_of(trim_removal) + 1);
60
61 WNDCLASSEX window_class = {0};
62 window_class.cbSize = sizeof(WNDCLASSEX);
63 window_class.style = CS_HREDRAW | CS_VREDRAW;
64 window_class.lpfnWndProc = MainUIWindow::WndProc;
65 window_class.cbClsExtra = 0;
66 window_class.cbWndExtra = 0;
67 window_class.hInstance = instance;
68 window_class.hIcon =
69 ::LoadIcon(instance, MAKEINTRESOURCE(IDI_SANDBOX));
70 window_class.hCursor = ::LoadCursor(NULL, IDC_ARROW);
71 window_class.hbrBackground = GetStockBrush(WHITE_BRUSH);
72 window_class.lpszMenuName = MAKEINTRESOURCE(IDR_MENU_MAIN_UI);
73 window_class.lpszClassName = L"sandbox_ui_1";
74 window_class.hIconSm = NULL;
75
76 INITCOMMONCONTROLSEX controls = {
77 sizeof(INITCOMMONCONTROLSEX),
78 ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES
79 };
80 ::InitCommonControlsEx(&controls);
81
82 if (!::RegisterClassEx(&window_class))
83 return ::GetLastError();
84
85 // Create a main window of size 600x400
86 HWND window = ::CreateWindowW(window_class.lpszClassName,
87 L"", // window name
88 WS_OVERLAPPEDWINDOW,
89 CW_USEDEFAULT, // x
90 CW_USEDEFAULT, // y
91 600, // width
92 400, // height
93 NULL, // parent
94 NULL, // NULL = use class menu
95 instance,
96 0); // lpParam
97
98 if (NULL == window)
99 return ::GetLastError();
100
101 ::SetWindowLongPtr(window,
102 GWLP_USERDATA,
103 reinterpret_cast<LONG_PTR>(this));
104
105 ::SetWindowText(window, L"Sandbox Proof of Concept");
106
107 ::ShowWindow(window, show_command);
108
109 MSG message;
110 // Now lets start the message pump retrieving messages for any window that
111 // belongs to the current thread
112 while (::GetMessage(&message, NULL, 0, 0)) {
113 ::TranslateMessage(&message);
114 ::DispatchMessage(&message);
115 }
116
117 return 0;
118 }
119
WndProc(HWND window,UINT message_id,WPARAM wparam,LPARAM lparam)120 LRESULT CALLBACK MainUIWindow::WndProc(HWND window,
121 UINT message_id,
122 WPARAM wparam,
123 LPARAM lparam) {
124 MainUIWindow* host = FromWindow(window);
125
126 #define HANDLE_MSG(hwnd, message, fn) \
127 case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
128
129 switch (message_id) {
130 case WM_CREATE:
131 // 'host' is not yet available when we get the WM_CREATE message
132 return HANDLE_WM_CREATE(window, wparam, lparam, OnCreate);
133 case WM_DESTROY:
134 return HANDLE_WM_DESTROY(window, wparam, lparam, host->OnDestroy);
135 case WM_SIZE:
136 return HANDLE_WM_SIZE(window, wparam, lparam, host->OnSize);
137 case WM_COMMAND: {
138 // Look at which menu item was clicked on (or which accelerator)
139 int id = LOWORD(wparam);
140 switch (id) {
141 case ID_FILE_EXIT:
142 host->OnFileExit();
143 break;
144 case ID_COMMANDS_SPAWNTARGET:
145 host->OnCommandsLaunch(window);
146 break;
147 default:
148 // Some other menu item or accelerator
149 break;
150 }
151
152 return ERROR_SUCCESS;
153 }
154
155 default:
156 // Some other WM_message, let it pass to DefWndProc
157 break;
158 }
159
160 return DefWindowProc(window, message_id, wparam, lparam);
161 }
162
SpawnTargetWndProc(HWND dialog,UINT message_id,WPARAM wparam,LPARAM lparam)163 INT_PTR CALLBACK MainUIWindow::SpawnTargetWndProc(HWND dialog,
164 UINT message_id,
165 WPARAM wparam,
166 LPARAM lparam) {
167 // Grab a reference to the main UI window (from the window handle)
168 MainUIWindow* host = FromWindow(GetParent(dialog));
169 DCHECK(host);
170
171 switch (message_id) {
172 case WM_INITDIALOG: {
173 // Initialize the window text for DLL name edit box
174 HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
175 wchar_t current_dir[MAX_PATH];
176 if (GetCurrentDirectory(MAX_PATH, current_dir)) {
177 std::wstring dll_path =
178 std::wstring(current_dir) + std::wstring(kDefaultDll_);
179 ::SetWindowText(edit_box_dll_name, dll_path.c_str());
180 }
181
182 // Initialize the window text for Entry Point edit box
183 HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
184 ::SetWindowText(edit_box_entry_point, kDefaultEntryPoint_);
185
186 // Initialize the window text for Log File edit box
187 HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
188 ::SetWindowText(edit_box_log_file, kDefaultLogFile_);
189
190 return static_cast<INT_PTR>(TRUE);
191 }
192 case WM_COMMAND:
193 // If the user presses the OK button (Launch)
194 if (LOWORD(wparam) == IDOK) {
195 if (host->OnLaunchDll(dialog)) {
196 if (host->SpawnTarget()) {
197 ::EndDialog(dialog, LOWORD(wparam));
198 }
199 }
200 return static_cast<INT_PTR>(TRUE);
201 } else if (LOWORD(wparam) == IDCANCEL) {
202 // If the user presses the Cancel button
203 ::EndDialog(dialog, LOWORD(wparam));
204 return static_cast<INT_PTR>(TRUE);
205 } else if (LOWORD(wparam) == IDC_BROWSE_DLL) {
206 // If the user presses the Browse button to look for a DLL
207 std::wstring dll_path = host->OnShowBrowseForDllDlg(dialog);
208 if (dll_path.length() > 0) {
209 // Initialize the window text for Log File edit box
210 HWND edit_box_dll_path = ::GetDlgItem(dialog, IDC_DLL_NAME);
211 ::SetWindowText(edit_box_dll_path, dll_path.c_str());
212 }
213 return static_cast<INT_PTR>(TRUE);
214 } else if (LOWORD(wparam) == IDC_BROWSE_LOG) {
215 // If the user presses the Browse button to look for a log file
216 std::wstring log_path = host->OnShowBrowseForLogFileDlg(dialog);
217 if (log_path.length() > 0) {
218 // Initialize the window text for Log File edit box
219 HWND edit_box_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
220 ::SetWindowText(edit_box_log_file, log_path.c_str());
221 }
222 return static_cast<INT_PTR>(TRUE);
223 }
224
225 break;
226 }
227
228 return static_cast<INT_PTR>(FALSE);
229 }
230
FromWindow(HWND main_window)231 MainUIWindow* MainUIWindow::FromWindow(HWND main_window) {
232 // We store a 'this' pointer using SetWindowLong in CreateMainWindowAndLoop
233 // so that we can retrieve it with this function later. This prevents us
234 // from having to define all the message handling functions (that we refer to
235 // in the window proc) as static
236 ::GetWindowLongPtr(main_window, GWLP_USERDATA);
237 return reinterpret_cast<MainUIWindow*>(
238 ::GetWindowLongPtr(main_window, GWLP_USERDATA));
239 }
240
OnCreate(HWND parent_window,LPCREATESTRUCT)241 BOOL MainUIWindow::OnCreate(HWND parent_window, LPCREATESTRUCT) {
242 // Create the listview that will the main app UI
243 list_view_ = ::CreateWindow(WC_LISTVIEW, // Class name
244 L"", // Window name
245 WS_CHILD | WS_VISIBLE | LVS_REPORT |
246 LVS_NOCOLUMNHEADER | WS_BORDER,
247 0, // x
248 0, // y
249 0, // width
250 0, // height
251 parent_window, // parent
252 NULL, // menu
253 ::GetModuleHandle(NULL),
254 0); // lpParam
255
256 DCHECK(list_view_);
257 if (!list_view_)
258 return FALSE;
259
260 LVCOLUMN list_view_column = {0};
261 list_view_column.mask = LVCF_FMT | LVCF_WIDTH;
262 list_view_column.fmt = LVCFMT_LEFT;
263 list_view_column.cx = 10000; // Maximum size of an entry in the list view.
264 ListView_InsertColumn(list_view_, 0, &list_view_column);
265
266 // Set list view to show green font on black background
267 ListView_SetBkColor(list_view_, CLR_NONE);
268 ListView_SetTextColor(list_view_, RGB(0x0, 0x0, 0x0));
269 ListView_SetTextBkColor(list_view_, CLR_NONE);
270
271 return TRUE;
272 }
273
OnDestroy(HWND window)274 void MainUIWindow::OnDestroy(HWND window) {
275 // Post a quit message because our application is over when the
276 // user closes this window.
277 ::PostQuitMessage(0);
278 }
279
OnSize(HWND window,UINT state,int cx,int cy)280 void MainUIWindow::OnSize(HWND window, UINT state, int cx, int cy) {
281 // If we have a valid inner child, resize it to cover the entire
282 // client area of the main UI window.
283 if (list_view_) {
284 ::MoveWindow(list_view_,
285 0, // x
286 0, // y
287 cx, // width
288 cy, // height
289 TRUE); // repaint
290 }
291 }
292
OnPaint(HWND window)293 void MainUIWindow::OnPaint(HWND window) {
294 PAINTSTRUCT paintstruct;
295 ::BeginPaint(window, &paintstruct);
296 // add painting code here if required
297 ::EndPaint(window, &paintstruct);
298 }
299
OnFileExit()300 void MainUIWindow::OnFileExit() {
301 ::PostQuitMessage(0);
302 }
303
OnCommandsLaunch(HWND window)304 void MainUIWindow::OnCommandsLaunch(HWND window) {
305 // User wants to see the Select DLL dialog box
306 ::DialogBox(instance_handle_,
307 MAKEINTRESOURCE(IDD_LAUNCH_DLL),
308 window,
309 SpawnTargetWndProc);
310 }
311
OnLaunchDll(HWND dialog)312 bool MainUIWindow::OnLaunchDll(HWND dialog) {
313 HWND edit_box_dll_name = ::GetDlgItem(dialog, IDC_DLL_NAME);
314 HWND edit_box_entry_point = ::GetDlgItem(dialog, IDC_ENTRY_POINT);
315 HWND edit_log_file = ::GetDlgItem(dialog, IDC_LOG_FILE);
316
317 wchar_t dll_path[MAX_PATH];
318 wchar_t entry_point[MAX_PATH];
319 wchar_t log_file[MAX_PATH];
320
321 int dll_name_len = ::GetWindowText(edit_box_dll_name, dll_path, MAX_PATH);
322 int entry_point_len = ::GetWindowText(edit_box_entry_point,
323 entry_point, MAX_PATH);
324 // Log file is optional (can be blank)
325 ::GetWindowText(edit_log_file, log_file, MAX_PATH);
326
327 if (0 >= dll_name_len) {
328 ::MessageBox(dialog,
329 L"Please specify a DLL for the target to load",
330 L"No DLL specified",
331 MB_ICONERROR);
332 return false;
333 }
334
335 if (GetFileAttributes(dll_path) == INVALID_FILE_ATTRIBUTES) {
336 ::MessageBox(dialog,
337 L"DLL specified was not found",
338 L"DLL not found",
339 MB_ICONERROR);
340 return false;
341 }
342
343 if (0 >= entry_point_len) {
344 ::MessageBox(dialog,
345 L"Please specify an entry point for the DLL",
346 L"No entry point specified",
347 MB_ICONERROR);
348 return false;
349 }
350
351 // store these values in the member variables for use in SpawnTarget
352 log_file_ = std::wstring(L"\"") + log_file + std::wstring(L"\"");
353 dll_path_ = dll_path;
354 entry_point_ = entry_point;
355
356 return true;
357 }
358
ListenPipeThunk(void * param)359 DWORD WINAPI MainUIWindow::ListenPipeThunk(void *param) {
360 return reinterpret_cast<MainUIWindow*>(param)->ListenPipe();
361 }
362
WaitForTargetThunk(void * param)363 DWORD WINAPI MainUIWindow::WaitForTargetThunk(void *param) {
364 return reinterpret_cast<MainUIWindow*>(param)->WaitForTarget();
365 }
366
367 // Thread waiting for the target application to die. It displays
368 // a message in the list view when it happens.
WaitForTarget()369 DWORD MainUIWindow::WaitForTarget() {
370 WaitForSingleObject(target_.hProcess, INFINITE);
371
372 DWORD exit_code = 0;
373 if (!GetExitCodeProcess(target_.hProcess, &exit_code)) {
374 exit_code = 0xFFFF; // Default exit code
375 }
376
377 ::CloseHandle(target_.hProcess);
378 ::CloseHandle(target_.hThread);
379
380 AddDebugMessage(L"Targed exited with return code %d", exit_code);
381 return 0;
382 }
383
384 // Thread waiting for messages on the log pipe. It displays the messages
385 // in the listview.
ListenPipe()386 DWORD MainUIWindow::ListenPipe() {
387 HANDLE logfile_handle = NULL;
388 ATL::CString file_to_open = log_file_.c_str();
389 file_to_open.Remove(L'\"');
390 if (file_to_open.GetLength()) {
391 logfile_handle = ::CreateFile(file_to_open.GetBuffer(),
392 GENERIC_WRITE,
393 FILE_SHARE_READ | FILE_SHARE_WRITE,
394 NULL, // Default security attributes
395 CREATE_ALWAYS,
396 FILE_ATTRIBUTE_NORMAL,
397 NULL); // No template
398 if (INVALID_HANDLE_VALUE == logfile_handle) {
399 AddDebugMessage(L"Failed to open \"%ls\" for logging. Error %d",
400 file_to_open.GetBuffer(), ::GetLastError());
401 logfile_handle = NULL;
402 }
403 }
404
405 const int kSizeBuffer = 1024;
406 BYTE read_buffer[kSizeBuffer] = {0};
407 ATL::CStringA read_buffer_global;
408 ATL::CStringA string_to_print;
409
410 DWORD last_error = 0;
411 while (last_error == ERROR_SUCCESS || last_error == ERROR_PIPE_LISTENING ||
412 last_error == ERROR_NO_DATA) {
413 DWORD read_data_length;
414 if (::ReadFile(pipe_handle_,
415 read_buffer,
416 kSizeBuffer - 1, // Max read size
417 &read_data_length,
418 NULL)) { // Not overlapped
419 if (logfile_handle) {
420 DWORD write_data_length;
421 ::WriteFile(logfile_handle,
422 read_buffer,
423 read_data_length,
424 &write_data_length,
425 FALSE); // Not overlapped
426 }
427
428 // Append the new buffer to the current buffer
429 read_buffer[read_data_length] = NULL;
430 read_buffer_global += reinterpret_cast<char *>(read_buffer);
431 read_buffer_global.Remove(10); // Remove the CRs
432
433 // If we completed a new line, output it
434 int endline = read_buffer_global.Find(13); // search for LF
435 while (-1 != endline) {
436 string_to_print = read_buffer_global;
437 string_to_print.Delete(endline, string_to_print.GetLength());
438 read_buffer_global.Delete(0, endline);
439
440 // print the line (with the ending LF)
441 OutputDebugStringA(string_to_print.GetBuffer());
442
443 // Remove the ending LF
444 read_buffer_global.Delete(0, 1);
445
446 // Add the line to the log
447 AddDebugMessage(L"%S", string_to_print.GetBuffer());
448
449 endline = read_buffer_global.Find(13);
450 }
451 last_error = ERROR_SUCCESS;
452 } else {
453 last_error = GetLastError();
454 Sleep(100);
455 }
456 }
457
458 if (read_buffer_global.GetLength()) {
459 AddDebugMessage(L"%S", read_buffer_global.GetBuffer());
460 }
461
462 CloseHandle(pipe_handle_);
463
464 if (logfile_handle) {
465 CloseHandle(logfile_handle);
466 }
467
468 return 0;
469 }
470
SpawnTarget()471 bool MainUIWindow::SpawnTarget() {
472 // Generate the pipe name
473 GUID random_id;
474 CoCreateGuid(&random_id);
475
476 wchar_t log_pipe[MAX_PATH] = {0};
477 wnsprintf(log_pipe, MAX_PATH - 1,
478 L"\\\\.\\pipe\\sbox_pipe_log_%lu_%lu_%lu_%lu",
479 random_id.Data1,
480 random_id.Data2,
481 random_id.Data3,
482 random_id.Data4);
483
484 // We concatenate the four strings, add three spaces and a zero termination
485 // We use the resulting string as a param to CreateProcess (in SpawnTarget)
486 // Documented maximum for command line in CreateProcess is 32K (msdn)
487 size_t size_call = spawn_target_.length() + entry_point_.length() +
488 dll_path_.length() + wcslen(log_pipe) + 6;
489 if (32 * 1024 < (size_call * sizeof(wchar_t))) {
490 AddDebugMessage(L"The length of the arguments exceeded 32K. "
491 L"Aborting operation.");
492 return false;
493 }
494
495 wchar_t * arguments = new wchar_t[size_call];
496 wnsprintf(arguments, static_cast<int>(size_call), L"%ls %ls \"%ls\" %ls",
497 spawn_target_.c_str(), entry_point_.c_str(),
498 dll_path_.c_str(), log_pipe);
499
500 arguments[size_call - 1] = L'\0';
501
502 scoped_refptr<sandbox::TargetPolicy> policy = broker_->CreatePolicy();
503 policy->SetJobLevel(sandbox::JOB_LOCKDOWN, 0);
504 policy->SetTokenLevel(sandbox::USER_RESTRICTED_SAME_ACCESS,
505 sandbox::USER_LOCKDOWN);
506 policy->SetAlternateDesktop(true);
507 policy->SetDelayedIntegrityLevel(sandbox::INTEGRITY_LEVEL_LOW);
508
509 // Set the rule to allow the POC dll to be loaded by the target. Note that
510 // the rule allows 'all access' to the DLL, which could mean that the target
511 // could modify the DLL on disk.
512 policy->AddRule(sandbox::TargetPolicy::SUBSYS_FILES,
513 sandbox::TargetPolicy::FILES_ALLOW_ANY, dll_path_.c_str());
514
515 sandbox::ResultCode warning_result = sandbox::SBOX_ALL_OK;
516 DWORD last_error = ERROR_SUCCESS;
517 sandbox::ResultCode result =
518 broker_->SpawnTarget(spawn_target_.c_str(), arguments, policy,
519 &warning_result, &last_error, &target_);
520
521 policy.reset();
522
523 bool return_value = false;
524 if (sandbox::SBOX_ALL_OK != result) {
525 AddDebugMessage(
526 L"Failed to spawn target %ls w/args (%ls), sandbox error code: %d",
527 spawn_target_.c_str(), arguments, result);
528 return_value = false;
529 } else {
530 DWORD thread_id;
531 ::CreateThread(NULL, // Default security attributes
532 NULL, // Default stack size
533 &MainUIWindow::WaitForTargetThunk,
534 this,
535 0, // No flags
536 &thread_id);
537
538 pipe_handle_ = ::CreateNamedPipe(log_pipe,
539 PIPE_ACCESS_INBOUND | WRITE_DAC,
540 PIPE_TYPE_MESSAGE | PIPE_NOWAIT,
541 1, // Number of instances.
542 512, // Out buffer size.
543 512, // In buffer size.
544 NMPWAIT_USE_DEFAULT_WAIT,
545 NULL); // Default security descriptor
546
547 if (INVALID_HANDLE_VALUE == pipe_handle_)
548 AddDebugMessage(L"Failed to create pipe. Error %d", ::GetLastError());
549
550 if (!sandbox::AddKnownSidToObject(pipe_handle_, SE_KERNEL_OBJECT,
551 WinWorldSid, GRANT_ACCESS,
552 FILE_ALL_ACCESS))
553 AddDebugMessage(L"Failed to set security on pipe. Error %d",
554 ::GetLastError());
555
556 ::CreateThread(NULL, // Default security attributes
557 NULL, // Default stack size
558 &MainUIWindow::ListenPipeThunk,
559 this,
560 0, // No flags
561 &thread_id);
562
563 ::ResumeThread(target_.hThread);
564
565 AddDebugMessage(L"Successfully spawned target w/args (%ls)", arguments);
566 return_value = true;
567 }
568
569 delete[] arguments;
570 return return_value;
571 }
572
OnShowBrowseForDllDlg(HWND owner)573 std::wstring MainUIWindow::OnShowBrowseForDllDlg(HWND owner) {
574 wchar_t filename[MAX_PATH];
575 wcscpy_s(filename, MAX_PATH, L"");
576
577 OPENFILENAMEW file_info = {0};
578 file_info.lStructSize = sizeof(file_info);
579 file_info.hwndOwner = owner;
580 file_info.lpstrFile = filename;
581 file_info.nMaxFile = MAX_PATH;
582 file_info.lpstrFilter = L"DLL files (*.dll)\0*.dll\0All files\0*.*\0\0\0";
583
584 file_info.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST;
585
586 if (GetOpenFileName(&file_info)) {
587 return file_info.lpstrFile;
588 }
589
590 return L"";
591 }
592
OnShowBrowseForLogFileDlg(HWND owner)593 std::wstring MainUIWindow::OnShowBrowseForLogFileDlg(HWND owner) {
594 wchar_t filename[MAX_PATH];
595 wcscpy_s(filename, MAX_PATH, L"");
596
597 OPENFILENAMEW file_info = {0};
598 file_info.lStructSize = sizeof(file_info);
599 file_info.hwndOwner = owner;
600 file_info.lpstrFile = filename;
601 file_info.nMaxFile = MAX_PATH;
602 file_info.lpstrFilter = L"Log file (*.txt)\0*.txt\0All files\0*.*\0\0\0";
603
604 file_info.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
605
606 if (GetSaveFileName(&file_info)) {
607 return file_info.lpstrFile;
608 }
609
610 return L"";
611 }
612
AddDebugMessage(const wchar_t * format,...)613 void MainUIWindow::AddDebugMessage(const wchar_t* format, ...) {
614 DCHECK(format);
615 if (!format)
616 return;
617
618 const int kMaxDebugBuffSize = 1024;
619
620 va_list arg_list;
621 va_start(arg_list, format);
622
623 wchar_t text[kMaxDebugBuffSize + 1];
624 vswprintf_s(text, kMaxDebugBuffSize, format, arg_list);
625 text[kMaxDebugBuffSize] = L'\0';
626
627 InsertLineInListView(text);
628 va_end(arg_list);
629 }
630
631
InsertLineInListView(wchar_t * debug_message)632 void MainUIWindow::InsertLineInListView(wchar_t* debug_message) {
633 DCHECK(debug_message);
634 if (!debug_message)
635 return;
636
637 // Prepend the time to the message
638 const int kSizeTime = 100;
639 size_t size_message_with_time = wcslen(debug_message) + kSizeTime;
640 wchar_t * message_time = new wchar_t[size_message_with_time];
641
642 time_t time_temp;
643 time_temp = time(NULL);
644
645 struct tm time = {0};
646 localtime_s(&time, &time_temp);
647
648 size_t return_code;
649 return_code = wcsftime(message_time, kSizeTime, L"[%H:%M:%S] ", &time);
650
651 wcscat_s(message_time, size_message_with_time, debug_message);
652
653 // We add the debug message to the top of the listview
654 LVITEM item;
655 item.iItem = ListView_GetItemCount(list_view_);
656 item.iSubItem = 0;
657 item.mask = LVIF_TEXT | LVIF_PARAM;
658 item.pszText = message_time;
659 item.lParam = 0;
660
661 ListView_InsertItem(list_view_, &item);
662
663 delete[] message_time;
664 }
665