Fix mapgen using unitialised height map values
[oweals/minetest.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 assert_fail(const char *assertion, const char *file,
137                 unsigned int line, const char *function)
138 {
139         DEBUGPRINT("\nIn thread %lx:\n"
140                         "%s:%u: %s: Assertion '%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 /*
153         DebugStack
154 */
155
156 struct DebugStack
157 {
158         DebugStack(threadid_t id);
159         void print(FILE *file, bool everything);
160         void print(std::ostream &os, bool everything);
161
162         threadid_t threadid;
163         char stack[DEBUG_STACK_SIZE][DEBUG_STACK_TEXT_SIZE];
164         int stack_i; // Points to the lowest empty position
165         int stack_max_i; // Highest i that was seen
166 };
167
168 DebugStack::DebugStack(threadid_t id)
169 {
170         threadid = id;
171         stack_i = 0;
172         stack_max_i = 0;
173         memset(stack, 0, DEBUG_STACK_SIZE*DEBUG_STACK_TEXT_SIZE);
174 }
175
176 void DebugStack::print(FILE *file, bool everything)
177 {
178         fprintf(file, "DEBUG STACK FOR THREAD %lx:\n",
179                         (unsigned long)threadid);
180
181         for(int i=0; i<stack_max_i; i++)
182         {
183                 if(i == stack_i && everything == false)
184                         break;
185
186                 if(i < stack_i)
187                         fprintf(file, "#%d  %s\n", i, stack[i]);
188                 else
189                         fprintf(file, "(Leftover data: #%d  %s)\n", i, stack[i]);
190         }
191
192         if(stack_i == DEBUG_STACK_SIZE)
193                 fprintf(file, "Probably overflown.\n");
194 }
195
196 void DebugStack::print(std::ostream &os, bool everything)
197 {
198         os<<"DEBUG STACK FOR THREAD "<<(unsigned long)threadid<<": "<<std::endl;
199
200         for(int i=0; i<stack_max_i; i++)
201         {
202                 if(i == stack_i && everything == false)
203                         break;
204
205                 if(i < stack_i)
206                         os<<"#"<<i<<"  "<<stack[i]<<std::endl;
207                 else
208                         os<<"(Leftover data: #"<<i<<"  "<<stack[i]<<")"<<std::endl;
209         }
210
211         if(stack_i == DEBUG_STACK_SIZE)
212                 os<<"Probably overflown."<<std::endl;
213 }
214
215 std::map<threadid_t, DebugStack*> g_debug_stacks;
216 JMutex g_debug_stacks_mutex;
217
218 void debug_stacks_init()
219 {
220 }
221
222 void debug_stacks_print_to(std::ostream &os)
223 {
224         JMutexAutoLock lock(g_debug_stacks_mutex);
225
226         os<<"Debug stacks:"<<std::endl;
227
228         for(std::map<threadid_t, DebugStack*>::iterator
229                         i = g_debug_stacks.begin();
230                         i != g_debug_stacks.end(); ++i)
231         {
232                 i->second->print(os, false);
233         }
234 }
235
236 void debug_stacks_print()
237 {
238         JMutexAutoLock lock(g_debug_stacks_mutex);
239
240         DEBUGPRINT("Debug stacks:\n");
241
242         for(std::map<threadid_t, DebugStack*>::iterator
243                         i = g_debug_stacks.begin();
244                         i != g_debug_stacks.end(); ++i)
245         {
246                 DebugStack *stack = i->second;
247
248                 for(int i=0; i<DEBUGSTREAM_COUNT; i++)
249                 {
250                         if(g_debugstreams[i] != NULL)
251                                 stack->print(g_debugstreams[i], true);
252                 }
253         }
254 }
255
256 DebugStacker::DebugStacker(const char *text)
257 {
258         threadid_t threadid = get_current_thread_id();
259
260         JMutexAutoLock lock(g_debug_stacks_mutex);
261
262         std::map<threadid_t, DebugStack*>::iterator n;
263         n = g_debug_stacks.find(threadid);
264         if(n != g_debug_stacks.end())
265         {
266                 m_stack = n->second;
267         }
268         else
269         {
270                 /*DEBUGPRINT("Creating new debug stack for thread %x\n",
271                                 (unsigned int)threadid);*/
272                 m_stack = new DebugStack(threadid);
273                 g_debug_stacks[threadid] = m_stack;
274         }
275
276         if(m_stack->stack_i >= DEBUG_STACK_SIZE)
277         {
278                 m_overflowed = true;
279         }
280         else
281         {
282                 m_overflowed = false;
283
284                 snprintf(m_stack->stack[m_stack->stack_i],
285                                 DEBUG_STACK_TEXT_SIZE, "%s", text);
286                 m_stack->stack_i++;
287                 if(m_stack->stack_i > m_stack->stack_max_i)
288                         m_stack->stack_max_i = m_stack->stack_i;
289         }
290 }
291
292 DebugStacker::~DebugStacker()
293 {
294         JMutexAutoLock lock(g_debug_stacks_mutex);
295
296         if(m_overflowed == true)
297                 return;
298
299         m_stack->stack_i--;
300
301         if(m_stack->stack_i == 0)
302         {
303                 threadid_t threadid = m_stack->threadid;
304                 /*DEBUGPRINT("Deleting debug stack for thread %x\n",
305                                 (unsigned int)threadid);*/
306                 delete m_stack;
307                 g_debug_stacks.erase(threadid);
308         }
309 }
310
311 #ifdef _MSC_VER
312
313 const char *Win32ExceptionCodeToString(DWORD exception_code)
314 {
315         switch (exception_code) {
316         case EXCEPTION_ACCESS_VIOLATION:
317                 return "Access violation";
318         case EXCEPTION_DATATYPE_MISALIGNMENT:
319                 return "Misaligned data access";
320         case EXCEPTION_BREAKPOINT:
321                 return "Breakpoint reached";
322         case EXCEPTION_SINGLE_STEP:
323                 return "Single debug step";
324         case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
325                 return "Array access out of bounds";
326         case EXCEPTION_FLT_DENORMAL_OPERAND:
327                 return "Denormal floating point operand";
328         case EXCEPTION_FLT_DIVIDE_BY_ZERO:
329                 return "Floating point division by zero";
330         case EXCEPTION_FLT_INEXACT_RESULT:
331                 return "Inaccurate floating point result";
332         case EXCEPTION_FLT_INVALID_OPERATION:
333                 return "Invalid floating point operation";
334         case EXCEPTION_FLT_OVERFLOW:
335                 return "Floating point exponent overflow";
336         case EXCEPTION_FLT_STACK_CHECK:
337                 return "Floating point stack overflow or underflow";
338         case EXCEPTION_FLT_UNDERFLOW:
339                 return "Floating point exponent underflow";
340         case EXCEPTION_INT_DIVIDE_BY_ZERO:
341                 return "Integer division by zero";
342         case EXCEPTION_INT_OVERFLOW:
343                 return "Integer overflow";
344         case EXCEPTION_PRIV_INSTRUCTION:
345                 return "Privileged instruction executed";
346         case EXCEPTION_IN_PAGE_ERROR:
347                 return "Could not access or load page";
348         case EXCEPTION_ILLEGAL_INSTRUCTION:
349                 return "Illegal instruction encountered";
350         case EXCEPTION_NONCONTINUABLE_EXCEPTION:
351                 return "Attempted to continue after fatal exception";
352         case EXCEPTION_STACK_OVERFLOW:
353                 return "Stack overflow";
354         case EXCEPTION_INVALID_DISPOSITION:
355                 return "Invalid disposition returned to the exception dispatcher";
356         case EXCEPTION_GUARD_PAGE:
357                 return "Attempted guard page access";
358         case EXCEPTION_INVALID_HANDLE:
359                 return "Invalid handle";
360         }
361
362         return "Unknown exception";
363 }
364
365 long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo)
366 {
367         char buf[512];
368         MINIDUMP_EXCEPTION_INFORMATION mdei;
369         MINIDUMP_USER_STREAM_INFORMATION mdusi;
370         MINIDUMP_USER_STREAM mdus;
371         bool minidump_created = false;
372         std::string version_str("Minetest ");
373
374         std::string dumpfile = porting::path_user + DIR_DELIM "minetest.dmp";
375
376         HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE,
377                 FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
378         if (hFile == INVALID_HANDLE_VALUE)
379                 goto minidump_failed;
380
381         if (SetEndOfFile(hFile) == FALSE)
382                 goto minidump_failed;
383
384         mdei.ClientPointers        = NULL;
385         mdei.ExceptionPointers = pExceptInfo;
386         mdei.ThreadId              = GetCurrentThreadId();
387
388         version_str += minetest_version_hash;
389
390         mdus.Type       = CommentStreamA;
391         mdus.BufferSize = version_str.size();
392         mdus.Buffer     = (PVOID)version_str.c_str();
393
394         mdusi.UserStreamArray = &mdus;
395         mdusi.UserStreamCount = 1;
396
397         if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
398                         MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE)
399                 goto minidump_failed;
400
401         minidump_created = true;
402
403 minidump_failed:
404
405         CloseHandle(hFile);
406
407         DWORD excode = pExceptInfo->ExceptionRecord->ExceptionCode;
408         _snprintf(buf, sizeof(buf),
409                 " >> === FATAL ERROR ===\n"
410                 " >> %s (Exception 0x%08X) at 0x%p\n",
411                 Win32ExceptionCodeToString(excode), excode,
412                 pExceptInfo->ExceptionRecord->ExceptionAddress);
413         dstream << buf;
414
415         if (minidump_created)
416                 dstream << " >> Saved dump to " << dumpfile << std::endl;
417         else
418                 dstream << " >> Failed to save dump" << std::endl;
419
420         return EXCEPTION_EXECUTE_HANDLER;
421 }
422
423 #endif
424
425 void debug_set_exception_handler()
426 {
427 #ifdef _MSC_VER
428         SetUnhandledExceptionFilter(Win32ExceptionHandler);
429 #endif
430 }
431