1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /*
21  * Debug.c
22  *
23  * Various debugging output functions.
24  *
25  */
26 
27 #include "frame.h"
28 #include "wztime.h"
29 
30 #include <string.h>
31 #include <stdio.h>
32 #include <time.h>
33 #include "string_ext.h"
34 #include "wzapp.h"
35 #include <map>
36 #include <string>
37 
38 #if defined(WZ_OS_LINUX) && defined(__GLIBC__)
39 #include <execinfo.h>  // Nonfatal runtime backtraces.
40 #endif // defined(WZ_OS_LINUX) && defined(__GLIBC__)
41 
42 #if defined(WZ_OS_UNIX)
43 # include <fcntl.h>
44 # ifndef _POSIX_C_SOURCE
45 #  define _POSIX_C_SOURCE 1
46 # endif
47 # ifndef _XOPEN_SOURCE
48 #  define _XOPEN_SOURCE
49 # endif
50 # ifndef _POSIX_SOURCE
51 #  define _POSIX_SOURCE
52 # endif
53 # include <stdio.h>
54 #endif
55 
56 #define MAX_LEN_LOG_LINE 512
57 
58 char last_called_script_event[MAX_EVENT_NAME_LEN];
59 UDWORD traceID = -1;
60 
61 static debug_callback *callbackRegistry = nullptr;
62 bool enabled_debug[LOG_LAST]; // global
63 #ifdef DEBUG
64 bool assertEnabled = true;
65 #else
66 bool assertEnabled = false;
67 #endif
68 #ifdef WZ_OS_MAC
69 #include "cocoa_wrapper.h"
70 #endif
71 /*
72  * This list _must_ match the enum in debug.h!
73  * Names must be 8 chars long at max!
74  */
75 static const char *code_part_names[] =
76 {
77 	"all",
78 	"main",
79 	"sound",
80 	"video",
81 	"wz",
82 	"3d",
83 	"texture",
84 	"net",
85 	"memory",
86 	"warning",
87 	"error",
88 	"never",
89 	"script",
90 	"movement",
91 	"attack",
92 	"fog",
93 	"sensor",
94 	"gui",
95 	"map",
96 	"save",
97 	"sync",
98 	"death",
99 	"life",
100 	"gateway",
101 	"message",
102 	"info",
103 	"terrain",
104 	"feature",
105 	"fatal",
106 	"input",
107 	"popup",
108 	"console",
109 	"activity",
110 	"last"
111 };
112 
113 static char inputBuffer[2][MAX_LEN_LOG_LINE];
114 static bool useInputBuffer1 = false;
115 static bool debug_flush_stderr = false;
116 
117 static std::map<std::string, int> warning_list;	// only used for LOG_WARNING
118 
119 /**
120  * Convert code_part names to enum. Case insensitive.
121  *
122  * \return	Codepart number or LOG_LAST if can't match.
123  */
code_part_from_str(const char * str)124 static code_part code_part_from_str(const char *str)
125 {
126 	unsigned int i;
127 
128 	for (i = 0; i < LOG_LAST; i++)
129 	{
130 		if (strcasecmp(code_part_names[i], str) == 0)
131 		{
132 			return (code_part)i;
133 		}
134 	}
135 	return LOG_LAST;
136 }
137 
138 
139 /**
140  * Callback for outputing to stderr
141  *
142  * \param	data			Ignored. Use NULL.
143  * \param	outputBuffer	Buffer containing the preprocessed text to output.
144  */
debug_callback_stderr(WZ_DECL_UNUSED void ** data,const char * outputBuffer)145 void debug_callback_stderr(WZ_DECL_UNUSED void **data, const char *outputBuffer)
146 {
147 	if (outputBuffer[strlen(outputBuffer) - 1] != '\n')
148 	{
149 		fprintf(stderr, "%s\n", outputBuffer);
150 	}
151 	else
152 	{
153 		fprintf(stderr, "%s", outputBuffer);
154 	}
155 
156 	// Make sure that all output is flushed to stderr when requested by the user
157 	if (debug_flush_stderr)
158 	{
159 		fflush(stderr);
160 	}
161 }
162 
163 
164 /**
165  * Callback for outputting to a win32 debugger
166  *
167  * \param	data			Ignored. Use NULL.
168  * \param	outputBuffer	Buffer containing the preprocessed text to output.
169  */
170 #if defined WIN32 && defined DEBUG
debug_callback_win32debug(WZ_DECL_UNUSED void ** data,const char * outputBuffer)171 void debug_callback_win32debug(WZ_DECL_UNUSED void **data, const char *outputBuffer)
172 {
173 	char tmpStr[MAX_LEN_LOG_LINE];
174 
175 	sstrcpy(tmpStr, outputBuffer);
176 	if (!strchr(tmpStr, '\n'))
177 	{
178 		sstrcat(tmpStr, "\n");
179 	}
180 
181 	OutputDebugStringA(tmpStr);
182 }
183 #endif // WIN32
184 
185 
186 /**
187  * Callback for outputing to a file
188  *
189  * \param	data			Filehandle to output to.
190  * \param	outputBuffer	Buffer containing the preprocessed text to output.
191  */
debug_callback_file(void ** data,const char * outputBuffer)192 void debug_callback_file(void **data, const char *outputBuffer)
193 {
194 	FILE *logfile = (FILE *)*data;
195 
196 	if (!strchr(outputBuffer, '\n'))
197 	{
198 		fprintf(logfile, "%s\n", outputBuffer);
199 	}
200 	else
201 	{
202 		fprintf(logfile, "%s", outputBuffer);
203 	}
204 }
205 
206 char WZ_DBGFile[PATH_MAX] = {0};	//Used to save path of the created log file
207 /**
208  * Setup the file callback
209  *
210  * Sets data to the filehandle opened for the filename found in data.
211  *
212  * \param[in,out]	data	In: 	The filename to output to.
213  * 							Out:	The filehandle.
214  */
215 WzString WZDebugfilename;
debug_callback_file_init(void ** data)216 bool debug_callback_file_init(void **data)
217 {
218 	WzString * pFileName = (WzString *)*data;
219 	ASSERT(pFileName, "Debug filename required");
220 	WZDebugfilename = *pFileName;
221 
222 #if defined(WZ_OS_WIN)
223 	// On Windows, path strings passed to fopen() are interpreted using the ANSI or OEM codepage
224 	// (and not as UTF-8). To support Unicode paths, the string must be converted to a wide-char
225 	// string and passed to _wfopen.
226 	int wstr_len = MultiByteToWideChar(CP_UTF8, 0, WZDebugfilename.toUtf8().c_str(), -1, NULL, 0);
227 	if (wstr_len <= 0)
228 	{
229 		fprintf(stderr, "Could not not convert string from UTF-8; MultiByteToWideChar failed with error %d: %s\n", GetLastError(), WZDebugfilename.toUtf8().c_str());
230 		return false;
231 	}
232 	std::vector<wchar_t> wstr_filename(wstr_len, 0);
233 	if (MultiByteToWideChar(CP_UTF8, 0, WZDebugfilename.toUtf8().c_str(), -1, &wstr_filename[0], wstr_len) == 0)
234 	{
235 		fprintf(stderr, "Could not not convert string from UTF-8; MultiByteToWideChar[2] failed with error %d: %s\n", GetLastError(), WZDebugfilename.toUtf8().c_str());
236 		return false;
237 	}
238 	FILE *const logfile = _wfopen(&wstr_filename[0], L"w");
239 #else
240 	FILE *const logfile = fopen(WZDebugfilename.toUtf8().c_str(), "w");
241 #endif
242 	if (!logfile)
243 	{
244 		fprintf(stderr, "Could not open %s for appending!\n", WZDebugfilename.toUtf8().c_str());
245 		return false;
246 	}
247 #if defined(WZ_OS_UNIX)
248 	int fd = fileno(logfile);
249 	if (fd != -1)
250 	{
251 		fcntl(fd, F_SETFD, FD_CLOEXEC);
252 	}
253 #endif
254 	snprintf(WZ_DBGFile, sizeof(WZ_DBGFile), "%s", WZDebugfilename.toUtf8().c_str());
255 	setbuf(logfile, nullptr);
256 	fprintf(logfile, "--- Starting log [%s]---\n", WZDebugfilename.toUtf8().c_str());
257 	*data = logfile;
258 
259 	return true;
260 }
261 
262 
263 /**
264  * Shutdown the file callback
265  *
266  * Closes the logfile.
267  *
268  * \param	data	The filehandle to close.
269  */
debug_callback_file_exit(void ** data)270 void debug_callback_file_exit(void **data)
271 {
272 	FILE *logfile = (FILE *)*data;
273 	fclose(logfile);
274 	*data = nullptr;
275 }
276 
debugFlushStderr()277 void debugFlushStderr()
278 {
279 	debug_flush_stderr = true;
280 }
281 // MSVC specific rotuines to set/clear allocation tracking
282 #if defined(WZ_CC_MSVC) && defined(DEBUG)
debug_MEMCHKOFF()283 void debug_MEMCHKOFF()
284 {
285 	// Disable allocation tracking
286 	int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
287 	flags &= ~_CRTDBG_ALLOC_MEM_DF;
288 	_CrtSetDbgFlag(flags);
289 }
debug_MEMCHKON()290 void debug_MEMCHKON()
291 {
292 	// Enable allocation tracking
293 	int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
294 	flags |= _CRTDBG_ALLOC_MEM_DF;
295 	_CrtSetDbgFlag(flags);
296 }
debug_MEMSTATS()297 void debug_MEMSTATS()
298 {
299 	_CrtMemState state;
300 	_CrtMemCheckpoint(&state);
301 	_CrtMemDumpStatistics(&state);
302 }
303 #endif
304 
debug_init()305 void debug_init()
306 {
307 	/*** Initialize the debug subsystem ***/
308 #if defined(WZ_CC_MSVC) && defined(DEBUG)
309 	int tmpDbgFlag;
310 	_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG);   // Output CRT info to debugger
311 
312 	tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);   // Grab current flags
313 # if defined(DEBUG_MEMORY)
314 	tmpDbgFlag |= _CRTDBG_CHECK_ALWAYS_DF; // Check every (de)allocation
315 # endif // DEBUG_MEMORY
316 	tmpDbgFlag |= _CRTDBG_ALLOC_MEM_DF; // Check allocations
317 	tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF; // Check for memleaks
318 	_CrtSetDbgFlag(tmpDbgFlag);
319 #endif // WZ_CC_MSVC && DEBUG
320 
321 	STATIC_ASSERT(ARRAY_SIZE(code_part_names) - 1 == LOG_LAST); // enums start at 0
322 
323 	memset(enabled_debug, false, sizeof(enabled_debug));
324 	enabled_debug[LOG_ERROR] = true;
325 	enabled_debug[LOG_INFO] = true;
326 	enabled_debug[LOG_FATAL] = true;
327 	enabled_debug[LOG_POPUP] = true;
328 	inputBuffer[0][0] = '\0';
329 	inputBuffer[1][0] = '\0';
330 #ifdef DEBUG
331 	enabled_debug[LOG_WARNING] = true;
332 #endif
333 }
334 
335 
debug_exit()336 void debug_exit()
337 {
338 	debug_callback *curCallback = callbackRegistry, * tmpCallback = nullptr;
339 
340 	while (curCallback)
341 	{
342 		if (curCallback->exit)
343 		{
344 			curCallback->exit(&curCallback->data);
345 		}
346 		tmpCallback = curCallback->next;
347 		free(curCallback);
348 		curCallback = tmpCallback;
349 	}
350 	warning_list.clear();
351 	callbackRegistry = nullptr;
352 }
353 
354 
debug_register_callback(debug_callback_fn callback,debug_callback_init init,debug_callback_exit exit,void * data)355 void debug_register_callback(debug_callback_fn callback, debug_callback_init init, debug_callback_exit exit, void *data)
356 {
357 	debug_callback *curCallback = callbackRegistry, * tmpCallback = nullptr;
358 
359 	tmpCallback = (debug_callback *)malloc(sizeof(*tmpCallback));
360 
361 	tmpCallback->next = nullptr;
362 	tmpCallback->callback = callback;
363 	tmpCallback->init = init;
364 	tmpCallback->exit = exit;
365 	tmpCallback->data = data;
366 
367 	if (tmpCallback->init
368 	    && !tmpCallback->init(&tmpCallback->data))
369 	{
370 		fprintf(stderr, "Failed to initialise debug callback, debugfile set to [%s]!\n", WZDebugfilename.toUtf8().c_str());
371 		free(tmpCallback);
372 		return;
373 	}
374 
375 	if (!curCallback)
376 	{
377 		callbackRegistry = tmpCallback;
378 		return;
379 	}
380 
381 	while (curCallback->next)
382 	{
383 		curCallback = curCallback->next;
384 	}
385 
386 	curCallback->next = tmpCallback;
387 }
388 
389 
debug_enable_switch(const char * str)390 bool debug_enable_switch(const char *str)
391 {
392 	code_part part = code_part_from_str(str);
393 
394 	if (part != LOG_LAST)
395 	{
396 		enabled_debug[part] = !enabled_debug[part];
397 	}
398 	if (part == LOG_ALL)
399 	{
400 		memset(enabled_debug, true, sizeof(enabled_debug));
401 	}
402 	return (part != LOG_LAST);
403 }
404 
405 /** Send the given string to all debug callbacks.
406  *
407  *  @param str The string to send to debug callbacks.
408  */
printToDebugCallbacks(const char * const str)409 static void printToDebugCallbacks(const char *const str)
410 {
411 	debug_callback *curCallback;
412 
413 	// Loop over all callbacks, invoking them with the given data string
414 	for (curCallback = callbackRegistry; curCallback != nullptr; curCallback = curCallback->next)
415 	{
416 		curCallback->callback(&curCallback->data, str);
417 	}
418 }
419 
_realObjTrace(int id,const char * function,const char * str,...)420 void _realObjTrace(int id, const char *function, const char *str, ...)
421 {
422 	char vaBuffer[MAX_LEN_LOG_LINE];
423 	char outputBuffer[MAX_LEN_LOG_LINE];
424 	va_list ap;
425 
426 	va_start(ap, str);
427 	vssprintf(vaBuffer, str, ap);
428 	va_end(ap);
429 
430 	ssprintf(outputBuffer, "[%6d]: [%s] %s", id, function, vaBuffer);
431 	printToDebugCallbacks(outputBuffer);
432 }
433 
434 // Thread local to prevent a race condition on read and write to this buffer if multiple
435 // threads log errors. This means we will not be reporting any errors to console from threads
436 // other than main. If we want to fix this, make sure accesses are protected by a mutex.
437 static WZ_DECL_THREAD char errorStore[512];
438 static WZ_DECL_THREAD bool errorWaiting = false;
debugLastError()439 const char *debugLastError()
440 {
441 	if (errorWaiting)
442 	{
443 		errorWaiting = false;
444 		return errorStore;
445 	}
446 	else
447 	{
448 		return nullptr;
449 	}
450 }
451 
_debug(int line,code_part part,const char * function,const char * str,...)452 void _debug(int line, code_part part, const char *function, const char *str, ...)
453 {
454 	va_list ap;
455 	static char outputBuffer[MAX_LEN_LOG_LINE];
456 	static unsigned int repeated = 0; /* times current message repeated */
457 	static unsigned int next = 2;     /* next total to print update */
458 	static unsigned int prev = 0;     /* total on last update */
459 
460 	va_start(ap, str);
461 	vssprintf(outputBuffer, str, ap);
462 	va_end(ap);
463 
464 	if (part == LOG_WARNING)
465 	{
466 		std::pair<std::map<std::string, int>::iterator, bool> ret;
467 		ret = warning_list.insert(std::pair<std::string, int>(std::string(function) + "-" + std::string(outputBuffer), line));
468 		if (ret.second)
469 		{
470 			ssprintf(inputBuffer[useInputBuffer1 ? 1 : 0], "[%s:%d] %s (**Further warnings of this type are suppressed.)", function, line, outputBuffer);
471 		}
472 		else
473 		{
474 			return;	// don't bother adding any more
475 		}
476 	}
477 	else
478 	{
479 		ssprintf(inputBuffer[useInputBuffer1 ? 1 : 0], "[%s:%d] %s", function, line, outputBuffer);
480 	}
481 
482 	if (sstrcmp(inputBuffer[0], inputBuffer[1]) == 0)
483 	{
484 		// Received again the same line
485 		repeated++;
486 		if (repeated == next)
487 		{
488 			if (repeated > 2)
489 			{
490 				ssprintf(outputBuffer, "last message repeated %u times (total %u repeats)", repeated - prev, repeated);
491 			}
492 			else
493 			{
494 				ssprintf(outputBuffer, "last message repeated %u times", repeated - prev);
495 			}
496 			printToDebugCallbacks(outputBuffer);
497 			prev = repeated;
498 			next *= 2;
499 		}
500 	}
501 	else
502 	{
503 		// Received another line, cleanup the old
504 		if (repeated > 0 && repeated != prev && repeated != 1)
505 		{
506 			/* just repeat the previous message when only one repeat occurred */
507 			if (repeated > 2)
508 			{
509 				ssprintf(outputBuffer, "last message repeated %u times (total %u repeats)", repeated - prev, repeated);
510 			}
511 			else
512 			{
513 				ssprintf(outputBuffer, "last message repeated %u times", repeated - prev);
514 			}
515 			printToDebugCallbacks(outputBuffer);
516 		}
517 		repeated = 0;
518 		next = 2;
519 		prev = 0;
520 	}
521 
522 	if (!repeated)
523 	{
524 		time_t rawtime;
525 		struct tm timeinfo = {};
526 		char ourtime[15];		//HH:MM:SS
527 
528 		time(&rawtime);
529 		timeinfo = getLocalTime(rawtime);
530 		strftime(ourtime, 15, "%H:%M:%S", &timeinfo);
531 
532 		// Assemble the outputBuffer:
533 		ssprintf(outputBuffer, "%-8s|%s: %s", code_part_names[part], ourtime, useInputBuffer1 ? inputBuffer[1] : inputBuffer[0]);
534 
535 		printToDebugCallbacks(outputBuffer);
536 
537 		if (part == LOG_ERROR)
538 		{
539 			// used to signal user that there was a error condition, and to check the logs.
540 			sstrcpy(errorStore, useInputBuffer1 ? inputBuffer[1] : inputBuffer[0]);
541 			errorWaiting = true;
542 		}
543 
544 		// Throw up a dialog box for users since most don't have a clue to check the dump file for information. Use for (duh) Fatal errors, that force us to terminate the game.
545 		if (part == LOG_FATAL)
546 		{
547 			if (wzIsFullscreen())
548 			{
549 				wzChangeWindowMode(WINDOW_MODE::windowed);
550 			}
551 #if defined(WZ_OS_WIN)
552 			char wbuf[1024];
553 			ssprintf(wbuf, "%s\n\nPlease check the file (%s) in your configuration directory for more details. \
554 				\nDo not forget to upload the %s file, WZdebuginfo.txt and the warzone2100.rpt files in your bug reports at https://github.com/Warzone2100/warzone2100/issues/new!", useInputBuffer1 ? inputBuffer[1] : inputBuffer[0], WZ_DBGFile, WZ_DBGFile);
555 			wzDisplayDialog(Dialog_Error, "Warzone has terminated unexpectedly", wbuf);
556 #elif defined(WZ_OS_MAC)
557 			char wbuf[1024];
558 			ssprintf(wbuf, "%s\n\nPlease check your logs and attach them along with a bug report. Thanks!", useInputBuffer1 ? inputBuffer[1] : inputBuffer[0]);
559 			int clickedIndex = \
560 			                   cocoaShowAlert("Warzone has quit unexpectedly.",
561 			                                  wbuf,
562 			                                  2, "Show Log Files & Open Bug Reporter", "Ignore", NULL);
563 			if (clickedIndex == 0)
564 			{
565 				if (!cocoaOpenURL("https://github.com/Warzone2100/warzone2100/issues/new"))
566                 {
567                     cocoaShowAlert("Failed to open URL",
568                                    "Could not open URL: https://github.com/Warzone2100/warzone2100/issues/new\nPlease open this URL manually in your web browser.",
569                                    2, "Continue", NULL);
570                 }
571                 if (strnlen(WZ_DBGFile, sizeof(WZ_DBGFile)/sizeof(WZ_DBGFile[0])) <= 0)
572 				{
573 					cocoaShowAlert("Unable to open debug log.",
574 					               "The debug log subsystem has not yet been initialised.",
575 					               2, "Continue", NULL);
576 				}
577 				else
578 				{
579                     if (!cocoaSelectFileInFinder(WZ_DBGFile))
580                     {
581                         cocoaShowAlert("Cannot Display Log File",
582                                        "The attempt to open a Finder window highlighting the log file from this run failed.",
583                                        2, "Continue", NULL);
584                     }
585 				}
586 				cocoaOpenUserCrashReportFolder();
587 			}
588 #else
589 			const char *popupBuf = useInputBuffer1 ? inputBuffer[1] : inputBuffer[0];
590 			wzDisplayDialog(Dialog_Error, "Warzone has terminated unexpectedly", popupBuf);
591 #endif
592 		}
593 
594 		// Throw up a dialog box for windows users since most don't have a clue to check the stderr.txt file for information
595 		// This is a popup dialog used for times when the error isn't fatal, but we still need to notify user what is going on.
596 		if (part == LOG_POPUP)
597 		{
598 			wzDisplayDialog(Dialog_Information, "Warzone has detected a problem.", inputBuffer[useInputBuffer1 ? 1 : 0]);
599 		}
600 
601 	}
602 	useInputBuffer1 = !useInputBuffer1; // Swap buffers
603 }
604 
_debugBacktrace(code_part part)605 void _debugBacktrace(code_part part)
606 {
607 #if defined(WZ_OS_LINUX) && defined(__GLIBC__)
608 	void *btv[20];
609 	unsigned num = backtrace(btv, sizeof(btv) / sizeof(*btv));
610 	char **btc = backtrace_symbols(btv, num);
611 	unsigned i;
612 	for (i = 1; i + 2 < num; ++i)  // =1: Don't print "src/warzone2100(syncDebugBacktrace+0x16) [0x6312d1]". +2: Don't print last two lines of backtrace such as "/lib/libc.so.6(__libc_start_main+0xe6) [0x7f91e040ea26]", since the address varies (even with the same binary).
613 	{
614 		_debug(0, part, "BT", "%s", btc[i]);
615 	}
616 	free(btc);
617 #else
618 	// debugBacktrace not implemented.
619 #endif
620 }
621 
debugPartEnabled(code_part codePart)622 bool debugPartEnabled(code_part codePart)
623 {
624 	return enabled_debug[codePart];
625 }
626 
debugDisableAssert()627 void debugDisableAssert()
628 {
629 	assertEnabled = false;
630 }
631 
632 #include <sstream>
633 
_debug_multiline(int line,code_part part,const char * function,const std::string & multilineString)634 void _debug_multiline(int line, code_part part, const char *function, const std::string &multilineString)
635 {
636 	std::stringstream ss(multilineString);
637 	std::string to;
638 
639 	while(std::getline(ss,to,'\n')){
640 		_debug(line, part, function, "%s", to.c_str());
641 	}
642 }
643 
644