]> git.lizzy.rs Git - dragonfireclient.git/blob - src/debug.cpp
queued_commands must be a std::deque. RunCommandQueues needs to push packet on front...
[dragonfireclient.git] / src / debug.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20
21 #include "porting.h"
22 #include "debug.h"
23 #include "exceptions.h"
24 #include "threads.h"
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <cstring>
28 #include <map>
29 #include "jthread/jmutex.h"
30 #include "jthread/jmutexautolock.h"
31 #include "config.h"
32
33 #ifdef _MSC_VER
34         #include <dbghelp.h>
35         #include "version.h"
36         #include "filesys.h"
37 #endif
38
39 /*
40         Debug output
41 */
42
43 #define DEBUGSTREAM_COUNT 2
44
45 FILE *g_debugstreams[DEBUGSTREAM_COUNT] = {stderr, NULL};
46
47 #define DEBUGPRINT(...)\
48 {\
49         for(int i=0; i<DEBUGSTREAM_COUNT; i++)\
50         {\
51                 if(g_debugstreams[i] != NULL){\
52                         fprintf(g_debugstreams[i], __VA_ARGS__);\
53                         fflush(g_debugstreams[i]);\
54                 }\
55         }\
56 }
57
58 void debugstreams_init(bool disable_stderr, const char *filename)
59 {
60         if(disable_stderr)
61                 g_debugstreams[0] = NULL;
62         else
63                 g_debugstreams[0] = stderr;
64
65         if(filename)
66                 g_debugstreams[1] = fopen(filename, "a");
67
68         if(g_debugstreams[1])
69         {
70                 fprintf(g_debugstreams[1], "\n\n-------------\n");
71                 fprintf(g_debugstreams[1],     "  Separator  \n");
72                 fprintf(g_debugstreams[1],     "-------------\n\n");
73         }
74 }
75
76 void debugstreams_deinit()
77 {
78         if(g_debugstreams[1] != NULL)
79                 fclose(g_debugstreams[1]);
80 }
81
82 class Debugbuf : public std::streambuf
83 {
84 public:
85         Debugbuf(bool disable_stderr)
86         {
87                 m_disable_stderr = disable_stderr;
88         }
89
90         int overflow(int c)
91         {
92                 for(int i=0; i<DEBUGSTREAM_COUNT; i++)
93                 {
94                         if(g_debugstreams[i] == stderr && m_disable_stderr)
95                                 continue;
96                         if(g_debugstreams[i] != NULL)
97                                 (void)fwrite(&c, 1, 1, g_debugstreams[i]);
98                         //TODO: Is this slow?
99                         fflush(g_debugstreams[i]);
100                 }
101
102                 return c;
103         }
104         std::streamsize xsputn(const char *s, std::streamsize n)
105         {
106 #ifdef __ANDROID__
107                 __android_log_print(ANDROID_LOG_VERBOSE, PROJECT_NAME, "%s", s);
108 #endif
109                 for(int i=0; i<DEBUGSTREAM_COUNT; i++)
110                 {
111                         if(g_debugstreams[i] == stderr && m_disable_stderr)
112                                 continue;
113                         if(g_debugstreams[i] != NULL)
114                                 (void)fwrite(s, 1, n, g_debugstreams[i]);
115                         //TODO: Is this slow?
116                         fflush(g_debugstreams[i]);
117                 }
118
119                 return n;
120         }
121
122 private:
123         bool m_disable_stderr;
124 };
125
126 Debugbuf debugbuf(false);
127 std::ostream dstream(&debugbuf);
128 Debugbuf debugbuf_no_stderr(true);
129 std::ostream dstream_no_stderr(&debugbuf_no_stderr);
130 Nullstream dummyout;
131
132 /*
133         Assert
134 */
135
136 void sanity_check_fn(const char *assertion, const char *file,
137                 unsigned int line, const char *function)
138 {
139         DEBUGPRINT("\nIn thread %lx:\n"
140                         "%s:%u: %s: An engine assumption '%s' failed.\n",
141                         (unsigned long)get_current_thread_id(),
142                         file, line, function, assertion);
143
144         debug_stacks_print();
145
146         if(g_debugstreams[1])
147                 fclose(g_debugstreams[1]);
148
149         abort();
150 }
151
152 void fatal_error_fn(const char *msg, const char *file,
153                 unsigned int line, const char *function)
154 {
155         DEBUGPRINT("\nIn thread %lx:\n"
156                         "%s:%u: %s: A fatal error occurred: %s\n",
157                         (unsigned long)get_current_thread_id(),
158                         file, line, function, msg);
159
160         debug_stacks_print();
161
162         if(g_debugstreams[1])
163                 fclose(g_debugstreams[1]);
164
165         abort();
166 }
167
168 /*
169         DebugStack
170 */
171
172 struct DebugStack
173 {
174         DebugStack(threadid_t id);
175         void print(FILE *file, bool everything);
176         void print(std::ostream &os, bool everything);
177
178         threadid_t threadid;
179         char stack[DEBUG_STACK_SIZE][DEBUG_STACK_TEXT_SIZE];
180         int stack_i; // Points to the lowest empty position
181         int stack_max_i; // Highest i that was seen
182 };
183
184 DebugStack::DebugStack(threadid_t id)
185 {
186         threadid = id;
187         stack_i = 0;
188         stack_max_i = 0;
189         memset(stack, 0, DEBUG_STACK_SIZE*DEBUG_STACK_TEXT_SIZE);
190 }
191
192 void DebugStack::print(FILE *file, bool everything)
193 {
194         fprintf(file, "DEBUG STACK FOR THREAD %lx:\n",
195                         (unsigned long)threadid);
196
197         for(int i=0; i<stack_max_i; i++)
198         {
199                 if(i == stack_i && everything == false)
200                         break;
201
202                 if(i < stack_i)
203                         fprintf(file, "#%d  %s\n", i, stack[i]);
204                 else
205                         fprintf(file, "(Leftover data: #%d  %s)\n", i, stack[i]);
206         }
207
208         if(stack_i == DEBUG_STACK_SIZE)
209                 fprintf(file, "Probably overflown.\n");
210 }
211
212 void DebugStack::print(std::ostream &os, bool everything)
213 {
214         os<<"DEBUG STACK FOR THREAD "<<(unsigned long)threadid<<": "<<std::endl;
215
216         for(int i=0; i<stack_max_i; i++)
217         {
218                 if(i == stack_i && everything == false)
219                         break;
220
221                 if(i < stack_i)
222                         os<<"#"<<i<<"  "<<stack[i]<<std::endl;
223                 else
224                         os<<"(Leftover data: #"<<i<<"  "<<stack[i]<<")"<<std::endl;
225         }
226
227         if(stack_i == DEBUG_STACK_SIZE)
228                 os<<"Probably overflown."<<std::endl;
229 }
230
231 std::map<threadid_t, DebugStack*> g_debug_stacks;
232 JMutex g_debug_stacks_mutex;
233
234 void debug_stacks_init()
235 {
236 }
237
238 void debug_stacks_print_to(std::ostream &os)
239 {
240         JMutexAutoLock lock(g_debug_stacks_mutex);
241
242         os<<"Debug stacks:"<<std::endl;
243
244         for(std::map<threadid_t, DebugStack*>::iterator
245                         i = g_debug_stacks.begin();
246                         i != g_debug_stacks.end(); ++i)
247         {
248                 i->second->print(os, false);
249         }
250 }
251
252 void debug_stacks_print()
253 {
254         JMutexAutoLock lock(g_debug_stacks_mutex);
255
256         DEBUGPRINT("Debug stacks:\n");
257
258         for(std::map<threadid_t, DebugStack*>::iterator
259                         i = g_debug_stacks.begin();
260                         i != g_debug_stacks.end(); ++i)
261         {
262                 DebugStack *stack = i->second;
263
264                 for(int i=0; i<DEBUGSTREAM_COUNT; i++)
265                 {
266                         if(g_debugstreams[i] != NULL)
267                                 stack->print(g_debugstreams[i], true);
268                 }
269         }
270 }
271
272 DebugStacker::DebugStacker(const char *text)
273 {
274         threadid_t threadid = get_current_thread_id();
275
276         JMutexAutoLock lock(g_debug_stacks_mutex);
277
278         std::map<threadid_t, DebugStack*>::iterator n;
279         n = g_debug_stacks.find(threadid);
280         if(n != g_debug_stacks.end())
281         {
282                 m_stack = n->second;
283         }
284         else
285         {
286                 /*DEBUGPRINT("Creating new debug stack for thread %x\n",
287                                 (unsigned int)threadid);*/
288                 m_stack = new DebugStack(threadid);
289                 g_debug_stacks[threadid] = m_stack;
290         }
291
292         if(m_stack->stack_i >= DEBUG_STACK_SIZE)
293         {
294                 m_overflowed = true;
295         }
296         else
297         {
298                 m_overflowed = false;
299
300                 snprintf(m_stack->stack[m_stack->stack_i],
301                                 DEBUG_STACK_TEXT_SIZE, "%s", text);
302                 m_stack->stack_i++;
303                 if(m_stack->stack_i > m_stack->stack_max_i)
304                         m_stack->stack_max_i = m_stack->stack_i;
305         }
306 }
307
308 DebugStacker::~DebugStacker()
309 {
310         JMutexAutoLock lock(g_debug_stacks_mutex);
311
312         if(m_overflowed == true)
313                 return;
314
315         m_stack->stack_i--;
316
317         if(m_stack->stack_i == 0)
318         {
319                 threadid_t threadid = m_stack->threadid;
320                 /*DEBUGPRINT("Deleting debug stack for thread %x\n",
321                                 (unsigned int)threadid);*/
322                 delete m_stack;
323                 g_debug_stacks.erase(threadid);
324         }
325 }
326
327 #ifdef _MSC_VER
328
329 const char *Win32ExceptionCodeToString(DWORD exception_code)
330 {
331         switch (exception_code) {
332         case EXCEPTION_ACCESS_VIOLATION:
333                 return "Access violation";
334         case EXCEPTION_DATATYPE_MISALIGNMENT:
335                 return "Misaligned data access";
336         case EXCEPTION_BREAKPOINT:
337                 return "Breakpoint reached";
338         case EXCEPTION_SINGLE_STEP:
339                 return "Single debug step";
340         case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
341                 return "Array access out of bounds";
342         case EXCEPTION_FLT_DENORMAL_OPERAND:
343                 return "Denormal floating point operand";
344         case EXCEPTION_FLT_DIVIDE_BY_ZERO:
345                 return "Floating point division by zero";
346         case EXCEPTION_FLT_INEXACT_RESULT:
347                 return "Inaccurate floating point result";
348         case EXCEPTION_FLT_INVALID_OPERATION:
349                 return "Invalid floating point operation";
350         case EXCEPTION_FLT_OVERFLOW:
351                 return "Floating point exponent overflow";
352         case EXCEPTION_FLT_STACK_CHECK:
353                 return "Floating point stack overflow or underflow";
354         case EXCEPTION_FLT_UNDERFLOW:
355                 return "Floating point exponent underflow";
356         case EXCEPTION_INT_DIVIDE_BY_ZERO:
357                 return "Integer division by zero";
358         case EXCEPTION_INT_OVERFLOW:
359                 return "Integer overflow";
360         case EXCEPTION_PRIV_INSTRUCTION:
361                 return "Privileged instruction executed";
362         case EXCEPTION_IN_PAGE_ERROR:
363                 return "Could not access or load page";
364         case EXCEPTION_ILLEGAL_INSTRUCTION:
365                 return "Illegal instruction encountered";
366         case EXCEPTION_NONCONTINUABLE_EXCEPTION:
367                 return "Attempted to continue after fatal exception";
368         case EXCEPTION_STACK_OVERFLOW:
369                 return "Stack overflow";
370         case EXCEPTION_INVALID_DISPOSITION:
371                 return "Invalid disposition returned to the exception dispatcher";
372         case EXCEPTION_GUARD_PAGE:
373                 return "Attempted guard page access";
374         case EXCEPTION_INVALID_HANDLE:
375                 return "Invalid handle";
376         }
377
378         return "Unknown exception";
379 }
380
381 long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo)
382 {
383         char buf[512];
384         MINIDUMP_EXCEPTION_INFORMATION mdei;
385         MINIDUMP_USER_STREAM_INFORMATION mdusi;
386         MINIDUMP_USER_STREAM mdus;
387         bool minidump_created = false;
388
389         std::string dumpfile = porting::path_user + DIR_DELIM PROJECT_NAME ".dmp";
390
391         std::string version_str(PROJECT_NAME " ");
392         version_str += g_version_hash;
393
394         HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE,
395                 FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
396         if (hFile == INVALID_HANDLE_VALUE)
397                 goto minidump_failed;
398
399         if (SetEndOfFile(hFile) == FALSE)
400                 goto minidump_failed;
401
402         mdei.ClientPointers        = NULL;
403         mdei.ExceptionPointers = pExceptInfo;
404         mdei.ThreadId              = GetCurrentThreadId();
405
406         mdus.Type       = CommentStreamA;
407         mdus.BufferSize = version_str.size();
408         mdus.Buffer     = (PVOID)version_str.c_str();
409
410         mdusi.UserStreamArray = &mdus;
411         mdusi.UserStreamCount = 1;
412
413         if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
414                         MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE)
415                 goto minidump_failed;
416
417         minidump_created = true;
418
419 minidump_failed:
420
421         CloseHandle(hFile);
422
423         DWORD excode = pExceptInfo->ExceptionRecord->ExceptionCode;
424         _snprintf(buf, sizeof(buf),
425                 " >> === FATAL ERROR ===\n"
426                 " >> %s (Exception 0x%08X) at 0x%p\n",
427                 Win32ExceptionCodeToString(excode), excode,
428                 pExceptInfo->ExceptionRecord->ExceptionAddress);
429         dstream << buf;
430
431         if (minidump_created)
432                 dstream << " >> Saved dump to " << dumpfile << std::endl;
433         else
434                 dstream << " >> Failed to save dump" << std::endl;
435
436         return EXCEPTION_EXECUTE_HANDLER;
437 }
438
439 #endif
440
441 void debug_set_exception_handler()
442 {
443 #ifdef _MSC_VER
444         SetUnhandledExceptionFilter(Win32ExceptionHandler);
445 #endif
446 }
447