1 /*
2 * Debug.cpp
3 * OpenLieroX
4 *
5 * Created by Albert Zeyer on 01.01.09.
6 * code under LGPL
7 *
8 */
9
10 #include "Debug.h"
11 #include "StringUtils.h"
12 #include "CrashHandler.h"
13 #include "Command.h"
14
15
16 #include <time.h>
17
18 #ifdef WIN32
19
20 // TODO implement
AmIBeingDebugged()21 bool AmIBeingDebugged() { return false; }
22
RaiseDebugger()23 void RaiseDebugger() {
24 #ifdef DEBUG
25 #if !defined(__MINGW32__)
26 // HINT: ignored when not in debugger
27 // If it just does nothing then, remove the surrounding #ifdef DEBUG
28 // I read about a Win32's IsDebuggerPresent() function, perhaps you should use that one here.
29 __asm { int 3 };
30 #endif
31 #endif
32 }
33
34 #else
35
36 #if defined(__APPLE__)
37
38 #include <cassert>
39 #include <stdbool.h>
40 #include <sys/types.h>
41 #include <unistd.h>
42 #include <sys/sysctl.h>
43
44 // Based on Apple's recommended method as described in
45 // http://developer.apple.com/qa/qa2004/qa1361.html
AmIBeingDebugged()46 bool AmIBeingDebugged()
47 // Returns true if the current process is being debugged (either
48 // running under the debugger or has a debugger attached post facto).
49 {
50 // Initialize mib, which tells sysctl what info we want. In this case,
51 // we're looking for information about a specific process ID.
52 int mib[] =
53 {
54 CTL_KERN,
55 KERN_PROC,
56 KERN_PROC_PID,
57 getpid()
58 };
59
60 // Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and
61 // binary interfaces may change.
62 struct kinfo_proc info;
63 size_t info_size = sizeof ( info );
64
65 int sysctl_result = sysctl ( mib, sizeof(mib) / sizeof(*mib), &info, &info_size, NULL, 0 );
66 if ( sysctl_result != 0 )
67 return false;
68
69 // This process is being debugged if the P_TRACED flag is set.
70 return ( info.kp_proc.p_flag & P_TRACED ) != 0;
71 }
72
73 #else
74
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <fcntl.h>
78 #include <cstring>
79 #include <unistd.h>
80
81
AmIBeingDebugged()82 bool AmIBeingDebugged() {
83 // We can look in /proc/self/status for TracerPid. We are likely used in crash
84 // handling, so we are careful not to use the heap or have side effects.
85 int status_fd = open("/proc/self/status", O_RDONLY);
86 if (status_fd == -1)
87 return false;
88
89 // We assume our line will be in the first 1024 characters and that we can
90 // read this much all at once. In practice this will generally be true.
91 // This simplifies and speeds up things considerably.
92 char buf[1024];
93
94 ssize_t num_read = read(status_fd, buf, sizeof(buf));
95 fix_markend(buf);
96 close(status_fd);
97 if (num_read <= 0) return false;
98
99 const char* searchStr = "TracerPid:\t";
100 const char* f = strstr(buf, searchStr);
101 if(f == NULL) return false;
102
103 // Our pid is 0 without a debugger, assume this for any pid starting with 0.
104 f += strlen(searchStr);
105 return f < &buf[num_read] && *f != '0';
106 }
107
108 #endif // OSX/LINUX
109
110 #include <signal.h>
111
RaiseDebugger()112 void RaiseDebugger() {
113 if(AmIBeingDebugged()) {
114 printf("I am being debugged, raising debugger ...\n");
115 if(CrashHandler::get()) CrashHandler::get()->disable();
116 // TODO: We need a way to set another ucontext here. (And that should be specified via a parameter
117 // to RaiseDebugger().) E.g. when we use this function in the debugger thread, we want to set the
118 // ucontext of the main loop thread.
119 raise(SIGABRT);
120 if(CrashHandler::get()) CrashHandler::get()->enable();
121 } else
122 printf("I am not being debugged, ignoring debugger raise.\n");
123 }
124
125 #endif
126
127
128 #ifdef WIN32
129
130 #include "AuxLib.h" // for Windows.h
131
132 #include <dbghelp.h>
133 #include <shlobj.h>
134
135 #include "LieroX.h"
136 #include "CClient.h"
137 #include "CServer.h"
138 #include "DedicatedControl.h"
139 #include "StringUtils.h"
140 #include "ConversationLogger.h"
141 #include "CGameMode.h"
142
143 #define itoa _itoa
144
ReadGameStateForReport(char * buffer,size_t bufsize)145 void *ReadGameStateForReport(char *buffer, size_t bufsize)
146 {
147 memset(buffer, 0, bufsize);
148 #if !defined(__MINGW32__)
149 __try {
150 #endif
151 if (cClient) {
152 strncat(buffer, "Game state:\n", bufsize);
153 if (cClient->getStatus() == NET_CONNECTED) {
154 if (cClient->getGameReady())
155 strncat(buffer, "In game, selecting weapons.", bufsize);
156 else
157 strncat(buffer, "In lobby.", bufsize);
158 } else if (cClient->getStatus() == NET_PLAYING) {
159 strncat(buffer, "In game, playing.", bufsize);
160 } else if (cClient->getStatus() == NET_CONNECTING) {
161 strncat(buffer, "Connecting to a server.", bufsize);
162 } else if (cClient->getStatus() == NET_DISCONNECTED) {
163 strncat(buffer, "Disconnected.\n", bufsize);
164 } else {
165 strncat(buffer, "Unknown state.\n", bufsize);
166 }
167 }
168 buffer[bufsize - 1] = '\0';
169 #if !defined(__MINGW32__)
170 } __except (EXCEPTION_EXECUTE_HANDLER)
171 { return buffer; }
172 #endif
173
174 return buffer;
175 }
176
ReadGameInfoForReport(char * buffer,size_t bufsize)177 void *ReadGameInfoForReport(char *buffer, size_t bufsize)
178 {
179 memset(buffer, 0, bufsize);
180 if (!tLXOptions || !tLX)
181 return buffer;
182 char tmp[32];
183 #if !defined(__MINGW32__)
184 __try {
185 #endif
186 // Game type
187 strncat(buffer, "iGameType = ", bufsize);
188 switch (tLX->iGameType) {
189 case GME_LOCAL:
190 strncat(buffer, "GME_LOCAL", bufsize);
191 break;
192 case GME_HOST:
193 strncat(buffer, "GME_HOST", bufsize);
194 break;
195 case GME_JOIN:
196 strncat(buffer, "GME_JOIN", bufsize);
197 break;
198 default:
199 itoa(tLX->iGameType, tmp, 10);
200 fix_markend(tmp);
201 strncat(buffer, "UNKNOWN ", bufsize); strncat(buffer, tmp, bufsize);
202 }
203 strncat(buffer, "\n", bufsize);
204
205 // Game mode
206 strncat(buffer, "GameMode = ", bufsize);
207 //char tmp[16];
208 itoa(tLXOptions->tGameInfo.gameMode->GeneralGameType(), tmp, 10);
209 fix_markend(tmp);
210 strncat(buffer, tmp, bufsize);
211 strncat(buffer, "\n", bufsize);
212
213 // Mod name
214 strncat(buffer, "sModName = ", bufsize);
215 if (tLXOptions->tGameInfo.sModName.size())
216 strncat(buffer, tLXOptions->tGameInfo.sModName.c_str(), bufsize);
217 strncat(buffer, "\n", bufsize);
218
219 // Map file
220 strncat(buffer, "sMapFile = ", bufsize);
221 if (tLXOptions->tGameInfo.sMapFile.size())
222 strncat(buffer, tLXOptions->tGameInfo.sMapFile.c_str(), bufsize);
223 strncat(buffer, "\n", bufsize);
224
225 // Map name
226 strncat(buffer, "sMapName = ", bufsize);
227 if (tLXOptions->tGameInfo.sMapName.size())
228 strncat(buffer, tLXOptions->tGameInfo.sMapName.c_str(), bufsize);
229 strncat(buffer, "\n", bufsize);
230
231 // Mod dir
232 strncat(buffer, "sModDir = ", bufsize);
233 if (tLXOptions->tGameInfo.sModDir.size())
234 strncat(buffer, tLXOptions->tGameInfo.sModDir.c_str(), bufsize);
235 strncat(buffer, "\n", bufsize);
236
237 // Loading time
238 itoa(tLXOptions->tGameInfo.iLoadingTime, tmp, 10);
239 fix_markend(tmp);
240 strncat(buffer, "iLoadingTimes = ", bufsize);
241 strncat(buffer, tmp, bufsize);
242 strncat(buffer, "\n", bufsize);
243
244 // Server name
245 strncat(buffer, "sServerName = ", bufsize);
246 if (tLXOptions->sServerName.size())
247 strncat(buffer, tLXOptions->sServerName.c_str(), bufsize);
248 strncat(buffer, "\n", bufsize);
249
250 // Welcome message
251 strncat(buffer, "sWelcomeMessage = ", bufsize);
252 if (tLXOptions->sWelcomeMessage.size())
253 strncat(buffer, tLXOptions->sWelcomeMessage.c_str(), bufsize);
254 strncat(buffer, "\n", bufsize);
255
256 // Lives
257 itoa(tLXOptions->tGameInfo.iLives, tmp, 10);
258 fix_markend(tmp);
259 strncat(buffer, "iLives = ", bufsize);
260 strncat(buffer, tmp, bufsize);
261 strncat(buffer, "\n", bufsize);
262
263 // Max kills
264 itoa(tLXOptions->tGameInfo.iKillLimit, tmp, 10);
265 fix_markend(tmp);
266 strncat(buffer, "iKillLimit = ", bufsize);
267 strncat(buffer, tmp, bufsize);
268 strncat(buffer, "\n", bufsize);
269
270 // Time limit
271 itoa((int)(tLXOptions->tGameInfo.fTimeLimit * 10), tmp, 10);
272 fix_markend(tmp);
273 strncat(buffer, "fTimeLimit = ", bufsize);
274 strncat(buffer, tmp, bufsize);
275 strncat(buffer, "\n", bufsize);
276
277 // Bonuses on
278 strncat(buffer, "bBonusesOn = ", bufsize);
279 strncat(buffer, tLXOptions->tGameInfo.bBonusesOn ? "true" : "false", bufsize);
280 strncat(buffer, "\n", bufsize);
281
282 // Bonus names
283 strncat(buffer, "bShowBonusName = ", bufsize);
284 strncat(buffer, tLXOptions->tGameInfo.bShowBonusName ? "true" : "false", bufsize);
285 strncat(buffer, "\n", bufsize);
286
287 // Number of players
288 if (cServer) {
289 itoa(cServer->getNumPlayers(), tmp, 10);
290 fix_markend(tmp);
291 strncat(buffer, "iNumPlayers = ", bufsize);
292 strncat(buffer, tmp, bufsize);
293 strncat(buffer, "\n", bufsize);
294 }
295
296 buffer[bufsize - 1] = '\0';
297 #if !defined(__MINGW32__)
298 } __except (EXCEPTION_EXECUTE_HANDLER) {
299 return buffer;
300 }
301 #endif
302 return buffer;
303 }
304
305 // This function also used in CrashHandler.cpp
OlxWriteCoreDump_Win32(const char * fileName,PEXCEPTION_POINTERS pExInfo)306 void OlxWriteCoreDump_Win32(const char* fileName, PEXCEPTION_POINTERS pExInfo )
307 {
308 #if !defined(__MINGW32__) // Does not load on Windows machine, but loads fine on Wine
309 // MSVC-compatible core dump, GDB cannot read it :(
310 // Set the exception info for the minidump
311 MINIDUMP_EXCEPTION_INFORMATION eInfo;
312 eInfo.ThreadId = GetCurrentThreadId();
313 eInfo.ExceptionPointers = pExInfo;
314 eInfo.ClientPointers = FALSE;
315
316 // Set the minidump info
317 MINIDUMP_CALLBACK_INFORMATION cbMiniDump;
318 cbMiniDump.CallbackRoutine = NULL;
319 cbMiniDump.CallbackParam = 0;
320
321 // Additional data
322 MINIDUMP_USER_STREAM pExtraInfo[3];
323
324 // Version info
325 char version[64];
326 strcpy(version, GetFullGameName());
327 pExtraInfo[0].Type = LastReservedStream + 1;
328 pExtraInfo[0].BufferSize = sizeof(version);
329 pExtraInfo[0].Buffer = (void *)&version[0];
330
331 // Current game info
332 char game_info[1024];
333 pExtraInfo[1].Type = LastReservedStream + 2;
334 pExtraInfo[1].BufferSize = sizeof(game_info);
335 pExtraInfo[1].Buffer = ReadGameInfoForReport(game_info, sizeof(game_info));
336
337 // Current game state
338 char game_state[1024];
339 pExtraInfo[2].Type = LastReservedStream + 3;
340 pExtraInfo[2].BufferSize = sizeof(game_state);
341 pExtraInfo[2].Buffer = ReadGameStateForReport(game_state, sizeof(game_state));
342
343 MINIDUMP_USER_STREAM_INFORMATION iStreams;
344 iStreams.UserStreamCount = sizeof(pExtraInfo)/sizeof(MINIDUMP_USER_STREAM);
345 iStreams.UserStreamArray = pExtraInfo;
346
347 // Open the file
348 HANDLE hFile = CreateFile((LPCSTR)fileName,GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
349
350 // Write the minidump
351 if (hFile) {
352 MINIDUMP_TYPE type = (MINIDUMP_TYPE)(MiniDumpScanMemory | MiniDumpWithIndirectlyReferencedMemory);
353 MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile, type, &eInfo,&iStreams,&cbMiniDump);
354 }
355
356 // Close the file
357 CloseHandle(hFile);
358 #endif // !defined(__MINGW32__)
359 }
360
OlxWriteCoreDump(const char * fileName)361 void OlxWriteCoreDump(const char* fileName)
362 {
363 OlxWriteCoreDump_Win32(fileName, NULL);
364 }
365
366 #else
367
368 #ifdef GCOREDUMPER
369 #include <google/coredumper.h>
370 #endif
371 #include <unistd.h>
372 #include <sys/types.h>
373 #include <sys/wait.h>
374 #include <cstring>
375 #include <cstdio>
376
377 #ifndef GCOREDUMPER
GdbWriteCoreDump(const char * fname)378 static void GdbWriteCoreDump(const char* fname) {
379 // WARNING: this is terribly slow like this
380 char gdbparam[PATH_MAX + 200];
381 sprintf(gdbparam,
382 "attach %i \n"
383 "gcore %s \n"
384 "detach \n"
385 "quit \n",
386 getpid(), fname);
387 FILE* p = popen("gdb -q", "w");
388 if(p) {
389 fprintf(p, "%s", gdbparam);
390 fflush(p);
391 int status = 0; wait(&status);
392 pclose(p);
393 }
394 }
395 #endif
396
OlxWriteCoreDump(const char * file_postfix)397 void OlxWriteCoreDump(const char* file_postfix) {
398 char corefile[PATH_MAX + 100];
399 if(getcwd(corefile, PATH_MAX) == NULL) strcpy(corefile, "");
400 strcat(corefile, "/core.OpenLieroX");
401 if(file_postfix) { strcat(corefile, "."); strcat(corefile, file_postfix); }
402 printf("writing coredump to %s\n", corefile);
403
404 printf("dumping core ... "); fflush(0);
405 #ifdef GCOREDUMPER
406 WriteCoreDump(corefile);
407 #else
408 GdbWriteCoreDump(corefile);
409 #endif
410 printf("ready\n");
411 }
412
413 #endif
414
415 // ----------------- core dump code end -------------
416
417
418 // --------------- backtrace stuff start --------------
419
420 #ifndef HAVE_EXECINFO
421 # if defined(__linux__)
422 # define HAVE_EXECINFO 1
423 # elif defined(__DARWIN_VERS_1050)
424 # define HAVE_EXECINFO 1
425 # else
426 # define HAVE_EXECINFO 0
427 # endif
428 #endif
429
430 #if HAVE_EXECINFO
431
432 #include <execinfo.h>
433 #include <stdio.h>
434 #include <stdlib.h>
435
DumpCallstackPrintf(void * callpnt)436 void DumpCallstackPrintf(void* callpnt) {
437 void *callstack[128];
438 int framesC = backtrace(callstack, sizeof(callstack));
439 printf("backtrace() returned %d addresses\n", framesC);
440 if(callpnt != NULL && framesC > 3) callstack[3] = callpnt; // expected to be called from signal handler
441 char** strs = backtrace_symbols(callstack, framesC);
442 for(int i = 0; i < framesC; ++i) {
443 if(strs[i])
444 printf("%s\n", strs[i]);
445 else
446 break;
447 }
448 free(strs);
449 }
450
DumpCallstack(const PrintOutFct & printer)451 void DumpCallstack(const PrintOutFct& printer) {
452 void *callstack[128];
453 int framesC = backtrace(callstack, sizeof(callstack));
454 printer.print("DumpCallstack: " + itoa(framesC) + " addresses:");
455 char** strs = backtrace_symbols(callstack, framesC);
456 for(int i = 0; i < framesC; ++i) {
457 if(strs[i])
458 printer.print(std::string(" ") + strs[i] + "\n");
459 else
460 break;
461 }
462 free(strs);
463 }
464
465 #elif defined(WIN32)
466
467 #include "StackWalker.h" // Call Luke Stackwalker for help
468
469 typedef void (*DbgPrintOutFct) (const std::string&);
470
471 // Override the default stackwalker with our own print functions
472 class PrintStackWalker : public StackWalker {
473 private:
474 DbgPrintOutFct m_print;
475 PrintOutFct *m_print2;
476
477 public:
PrintStackWalker(DbgPrintOutFct fct=NULL)478 PrintStackWalker(DbgPrintOutFct fct = NULL) : StackWalker(RetrieveVerbose) { m_print = fct; m_print2 = NULL; }
PrintStackWalker(PrintOutFct * fct)479 PrintStackWalker(PrintOutFct *fct) : StackWalker(RetrieveVerbose) { m_print2 = fct; m_print = NULL; }
OnLoadModule(LPCSTR img,LPCSTR mod,DWORD64 baseAddr,DWORD size,DWORD result,LPCSTR symType,LPCSTR pdbName,ULONGLONG fileVersion)480 void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion)
481 {
482
483 }
484
OnOutput(LPCSTR szText)485 void OnOutput(LPCSTR szText)
486 {
487 if (m_print)
488 m_print(std::string(szText));
489 else if (m_print2)
490 m_print2->print(std::string(szText));
491 else
492 printf(szText);
493
494 StackWalker::OnOutput(szText);
495 }
496 };
497
DumpCallstackPrintf(void * callpnt)498 void DumpCallstackPrintf(void* callpnt)
499 {
500 PrintStackWalker sw;
501 sw.ShowCallstack();
502
503 }
DumpCallstack(void (* LineOutFct)(const std::string &))504 void DumpCallstack(void (*LineOutFct) (const std::string&))
505 {
506 PrintStackWalker sw(LineOutFct);
507 sw.ShowCallstack();
508 }
509
DumpCallstack(const PrintOutFct & printer)510 void DumpCallstack(const PrintOutFct& printer) {
511 PrintStackWalker sw(const_cast<PrintOutFct *>(&printer));
512 sw.ShowCallstack();
513 }
514
515 #else
516
517 #warning No DumpCallstack implementation for this arch/sys
518
DumpCallstackPrintf(void * callpnt)519 void DumpCallstackPrintf(void* callpnt) {
520 printf("DumpCallstackPrintf not implemented\n");
521 }
522
DumpCallstack(const PrintOutFct & printer)523 void DumpCallstack(const PrintOutFct& printer) {
524 printf("DumpCallstack not implemented\n");
525 }
526
527 #endif
528
529 #ifdef __ANDROID__
530 #include <android/log.h>
531 #endif
532
GetLogTimeStamp()533 std::string GetLogTimeStamp()
534 {
535 // TODO: please recode this, don't use C-strings!
536 char buf[64];
537 const time_t unif_time = time(NULL);
538 struct tm *t = localtime(&unif_time);
539 if (t == NULL)
540 return "";
541
542 strftime(buf, sizeof(buf), "[%H:%M:%S] ", t);
543 fix_markend(buf);
544 return std::string(buf);
545 }
546
547 Logger notes(0,2,1000, "n: ");
548 Logger hints(0,1,100, "H: ");
549 Logger warnings(0,0,10, "W: ");
550 Logger errors(-1,-1,1, "E: ");
551
552 #include <iostream>
553 #include <sstream>
554 #include "ThreadPool.h"
555 #include "Options.h"
556 #include "console.h"
557 #include "StringUtils.h"
558
559 static SDL_mutex* globalCoutMutex = NULL;
560
Logger(int o,int ingame,int callst,const std::string & p)561 Logger::Logger(int o, int ingame, int callst, const std::string& p)
562 : minCoutVerb(o), minIngameConVerb(ingame), minCallstackVerb(callst), prefix(p), lastWasNewline(true), mutex(NULL) {
563 mutex = SDL_CreateMutex();
564 if(!globalCoutMutex)
565 globalCoutMutex = SDL_CreateMutex();
566 }
567
~Logger()568 Logger::~Logger() {
569 SDL_DestroyMutex(mutex); mutex = NULL;
570 if(globalCoutMutex) {
571 SDL_DestroyMutex(globalCoutMutex);
572 globalCoutMutex = NULL;
573 }
574 }
575
lock()576 void Logger::lock() {
577 SDL_mutexP(mutex);
578 }
579
unlock()580 void Logger::unlock() {
581 SDL_mutexV(mutex);
582 }
583
584 struct CoutPrint : PrintOutFct {
printCoutPrint585 void print(const std::string& str) const {
586 // TODO: We have used std::cout here before but it doesn't seem to work after a while for some reason.
587 printf("%s", str.c_str());
588 #ifdef __ANDROID__
589 __android_log_print(ANDROID_LOG_INFO, "OpenLieroX", "%s", str.c_str());
590 #endif
591 }
592 };
593
594 template<int col>
595 struct ConPrint : PrintOutFct {
printConPrint596 void print(const std::string& str) const {
597 // TODO: Con_AddText adds a line but we only want to add str
598 std::string buf = str;
599 if(buf.size() > 0 && buf[buf.size()-1] == '\n') buf.erase(buf.size()-1);
600 Con_AddText(col, buf, false);
601 }
602 };
603
604 // true if last was newline
logger_output(Logger & log,const std::string & buf)605 static bool logger_output(Logger& log, const std::string& buf) {
606 bool ret = true;
607
608 std::string prefix = log.prefix;
609 if (tLXOptions && tLXOptions->bLogTimestamps)
610 prefix = GetLogTimeStamp() + prefix;
611
612 if(!tLXOptions || tLXOptions->iVerbosity >= log.minCoutVerb) {
613 SDL_mutexP(globalCoutMutex);
614 ret = PrettyPrint(prefix, buf, CoutPrint(), log.lastWasNewline);
615 //std::cout.flush();
616 SDL_mutexV(globalCoutMutex);
617 }
618 if(tLXOptions && tLXOptions->iVerbosity >= log.minCallstackVerb) {
619 DumpCallstackPrintf();
620 }
621 if(tLXOptions && Con_IsInited() && tLXOptions->iVerbosity >= log.minIngameConVerb) {
622 // the check is a bit hacky (see Con_AddText) but I really dont want to overcomplicate this
623 if(!strStartsWith(buf, "Ingame console: ")) {
624 // we are not safing explicitly a color in the Logger, thus we try to assume a good color from the verbosity level
625 if(log.minIngameConVerb < 0)
626 ret = PrettyPrint(prefix, buf, ConPrint<CNC_ERROR>(), log.lastWasNewline);
627 else if(log.minIngameConVerb == 0)
628 ret = PrettyPrint(prefix, buf, ConPrint<CNC_WARNING>(), log.lastWasNewline);
629 else if(log.minIngameConVerb == 1)
630 ret = PrettyPrint(prefix, buf, ConPrint<CNC_NOTIFY>(), log.lastWasNewline);
631 else if(log.minIngameConVerb < 5)
632 ret = PrettyPrint(prefix, buf, ConPrint<CNC_NORMAL>(), log.lastWasNewline);
633 else // >=5
634 ret = PrettyPrint(prefix, buf, ConPrint<CNC_DEV>(), log.lastWasNewline);
635 }
636 if(tLXOptions->iVerbosity >= log.minCallstackVerb) {
637 DumpCallstack(ConPrint<CNC_DEV>());
638 }
639 }
640 return ret;
641 }
642
flush()643 Logger& Logger::flush() {
644 lock();
645 lastWasNewline = logger_output(*this, buffer);
646 buffer = "";
647 unlock();
648 return *this;
649 }
650