Add DISABLE_CLASS_COPY macro (and use it)
[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 <sstream>
30 #include "threading/mutex.h"
31 #include "threading/mutex_auto_lock.h"
32 #include "config.h"
33
34 #ifdef _MSC_VER
35         #include <dbghelp.h>
36         #include "version.h"
37         #include "filesys.h"
38 #endif
39
40 /*
41         Assert
42 */
43
44 void sanity_check_fn(const char *assertion, const char *file,
45                 unsigned int line, const char *function)
46 {
47         errorstream << std::endl << "In thread " << std::hex
48                 << (unsigned long)thr_get_current_thread_id() << ":" << std::endl;
49         errorstream << file << ":" << line << ": " << function
50                 << ": An engine assumption '" << assertion << "' failed." << std::endl;
51
52         debug_stacks_print_to(errorstream);
53
54         abort();
55 }
56
57 void fatal_error_fn(const char *msg, const char *file,
58                 unsigned int line, const char *function)
59 {
60         errorstream << std::endl << "In thread " << std::hex
61                 << (unsigned long)thr_get_current_thread_id() << ":" << std::endl;
62         errorstream << file << ":" << line << ": " << function
63                 << ": A fatal error occured: " << msg << std::endl;
64
65         debug_stacks_print_to(errorstream);
66
67         abort();
68 }
69
70 /*
71         DebugStack
72 */
73
74 struct DebugStack
75 {
76         DebugStack(threadid_t id);
77         void print(FILE *file, bool everything);
78         void print(std::ostream &os, bool everything);
79
80         threadid_t threadid;
81         char stack[DEBUG_STACK_SIZE][DEBUG_STACK_TEXT_SIZE];
82         int stack_i; // Points to the lowest empty position
83         int stack_max_i; // Highest i that was seen
84 };
85
86 DebugStack::DebugStack(threadid_t id)
87 {
88         threadid = id;
89         stack_i = 0;
90         stack_max_i = 0;
91         memset(stack, 0, DEBUG_STACK_SIZE*DEBUG_STACK_TEXT_SIZE);
92 }
93
94 void DebugStack::print(FILE *file, bool everything)
95 {
96         fprintf(file, "DEBUG STACK FOR THREAD %lx:\n",
97                         (unsigned long)threadid);
98
99         for(int i=0; i<stack_max_i; i++)
100         {
101                 if(i == stack_i && everything == false)
102                         break;
103
104                 if(i < stack_i)
105                         fprintf(file, "#%d  %s\n", i, stack[i]);
106                 else
107                         fprintf(file, "(Leftover data: #%d  %s)\n", i, stack[i]);
108         }
109
110         if(stack_i == DEBUG_STACK_SIZE)
111                 fprintf(file, "Probably overflown.\n");
112 }
113
114 void DebugStack::print(std::ostream &os, bool everything)
115 {
116         os<<"DEBUG STACK FOR THREAD "<<(unsigned long)threadid<<": "<<std::endl;
117
118         for(int i=0; i<stack_max_i; i++)
119         {
120                 if(i == stack_i && everything == false)
121                         break;
122
123                 if(i < stack_i)
124                         os<<"#"<<i<<"  "<<stack[i]<<std::endl;
125                 else
126                         os<<"(Leftover data: #"<<i<<"  "<<stack[i]<<")"<<std::endl;
127         }
128
129         if(stack_i == DEBUG_STACK_SIZE)
130                 os<<"Probably overflown."<<std::endl;
131 }
132
133 // Note:  Using pthread_t (that is, threadid_t on POSIX platforms) as the key to
134 // a std::map is naughty.  Formally, a pthread_t may only be compared using
135 // pthread_equal() - pthread_t lacks the well-ordered property needed for
136 // comparisons in binary searches.  This should be fixed at some point by
137 // defining a custom comparator with an arbitrary but stable ordering of
138 // pthread_t, but it isn't too important since none of our supported platforms
139 // implement pthread_t as a non-ordinal type.
140 std::map<threadid_t, DebugStack*> g_debug_stacks;
141 Mutex g_debug_stacks_mutex;
142
143 void debug_stacks_init()
144 {
145 }
146
147 void debug_stacks_print_to(std::ostream &os)
148 {
149         MutexAutoLock lock(g_debug_stacks_mutex);
150
151         os<<"Debug stacks:"<<std::endl;
152
153         for(std::map<threadid_t, DebugStack*>::iterator
154                         i = g_debug_stacks.begin();
155                         i != g_debug_stacks.end(); ++i)
156         {
157                 i->second->print(os, false);
158         }
159 }
160
161 void debug_stacks_print()
162 {
163         debug_stacks_print_to(errorstream);
164 }
165
166 DebugStacker::DebugStacker(const char *text)
167 {
168         threadid_t threadid = thr_get_current_thread_id();
169
170         MutexAutoLock lock(g_debug_stacks_mutex);
171
172         std::map<threadid_t, DebugStack*>::iterator n;
173         n = g_debug_stacks.find(threadid);
174         if(n != g_debug_stacks.end())
175         {
176                 m_stack = n->second;
177         }
178         else
179         {
180                 /*DEBUGPRINT("Creating new debug stack for thread %x\n",
181                                 (unsigned int)threadid);*/
182                 m_stack = new DebugStack(threadid);
183                 g_debug_stacks[threadid] = m_stack;
184         }
185
186         if(m_stack->stack_i >= DEBUG_STACK_SIZE)
187         {
188                 m_overflowed = true;
189         }
190         else
191         {
192                 m_overflowed = false;
193
194                 snprintf(m_stack->stack[m_stack->stack_i],
195                                 DEBUG_STACK_TEXT_SIZE, "%s", text);
196                 m_stack->stack_i++;
197                 if(m_stack->stack_i > m_stack->stack_max_i)
198                         m_stack->stack_max_i = m_stack->stack_i;
199         }
200 }
201
202 DebugStacker::~DebugStacker()
203 {
204         MutexAutoLock lock(g_debug_stacks_mutex);
205
206         if(m_overflowed == true)
207                 return;
208
209         m_stack->stack_i--;
210
211         if(m_stack->stack_i == 0)
212         {
213                 threadid_t threadid = m_stack->threadid;
214                 /*DEBUGPRINT("Deleting debug stack for thread %x\n",
215                                 (unsigned int)threadid);*/
216                 delete m_stack;
217                 g_debug_stacks.erase(threadid);
218         }
219 }
220
221 #ifdef _MSC_VER
222
223 const char *Win32ExceptionCodeToString(DWORD exception_code)
224 {
225         switch (exception_code) {
226         case EXCEPTION_ACCESS_VIOLATION:
227                 return "Access violation";
228         case EXCEPTION_DATATYPE_MISALIGNMENT:
229                 return "Misaligned data access";
230         case EXCEPTION_BREAKPOINT:
231                 return "Breakpoint reached";
232         case EXCEPTION_SINGLE_STEP:
233                 return "Single debug step";
234         case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
235                 return "Array access out of bounds";
236         case EXCEPTION_FLT_DENORMAL_OPERAND:
237                 return "Denormal floating point operand";
238         case EXCEPTION_FLT_DIVIDE_BY_ZERO:
239                 return "Floating point division by zero";
240         case EXCEPTION_FLT_INEXACT_RESULT:
241                 return "Inaccurate floating point result";
242         case EXCEPTION_FLT_INVALID_OPERATION:
243                 return "Invalid floating point operation";
244         case EXCEPTION_FLT_OVERFLOW:
245                 return "Floating point exponent overflow";
246         case EXCEPTION_FLT_STACK_CHECK:
247                 return "Floating point stack overflow or underflow";
248         case EXCEPTION_FLT_UNDERFLOW:
249                 return "Floating point exponent underflow";
250         case EXCEPTION_INT_DIVIDE_BY_ZERO:
251                 return "Integer division by zero";
252         case EXCEPTION_INT_OVERFLOW:
253                 return "Integer overflow";
254         case EXCEPTION_PRIV_INSTRUCTION:
255                 return "Privileged instruction executed";
256         case EXCEPTION_IN_PAGE_ERROR:
257                 return "Could not access or load page";
258         case EXCEPTION_ILLEGAL_INSTRUCTION:
259                 return "Illegal instruction encountered";
260         case EXCEPTION_NONCONTINUABLE_EXCEPTION:
261                 return "Attempted to continue after fatal exception";
262         case EXCEPTION_STACK_OVERFLOW:
263                 return "Stack overflow";
264         case EXCEPTION_INVALID_DISPOSITION:
265                 return "Invalid disposition returned to the exception dispatcher";
266         case EXCEPTION_GUARD_PAGE:
267                 return "Attempted guard page access";
268         case EXCEPTION_INVALID_HANDLE:
269                 return "Invalid handle";
270         }
271
272         return "Unknown exception";
273 }
274
275 long WINAPI Win32ExceptionHandler(struct _EXCEPTION_POINTERS *pExceptInfo)
276 {
277         char buf[512];
278         MINIDUMP_EXCEPTION_INFORMATION mdei;
279         MINIDUMP_USER_STREAM_INFORMATION mdusi;
280         MINIDUMP_USER_STREAM mdus;
281         bool minidump_created = false;
282
283         std::string dumpfile = porting::path_user + DIR_DELIM PROJECT_NAME ".dmp";
284
285         std::string version_str(PROJECT_NAME " ");
286         version_str += g_version_hash;
287
288         HANDLE hFile = CreateFileA(dumpfile.c_str(), GENERIC_WRITE,
289                 FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
290         if (hFile == INVALID_HANDLE_VALUE)
291                 goto minidump_failed;
292
293         if (SetEndOfFile(hFile) == FALSE)
294                 goto minidump_failed;
295
296         mdei.ClientPointers        = NULL;
297         mdei.ExceptionPointers = pExceptInfo;
298         mdei.ThreadId              = GetCurrentThreadId();
299
300         mdus.Type       = CommentStreamA;
301         mdus.BufferSize = version_str.size();
302         mdus.Buffer     = (PVOID)version_str.c_str();
303
304         mdusi.UserStreamArray = &mdus;
305         mdusi.UserStreamCount = 1;
306
307         if (MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hFile,
308                         MiniDumpNormal, &mdei, &mdusi, NULL) == FALSE)
309                 goto minidump_failed;
310
311         minidump_created = true;
312
313 minidump_failed:
314
315         CloseHandle(hFile);
316
317         DWORD excode = pExceptInfo->ExceptionRecord->ExceptionCode;
318         _snprintf(buf, sizeof(buf),
319                 " >> === FATAL ERROR ===\n"
320                 " >> %s (Exception 0x%08X) at 0x%p\n",
321                 Win32ExceptionCodeToString(excode), excode,
322                 pExceptInfo->ExceptionRecord->ExceptionAddress);
323         dstream << buf;
324
325         if (minidump_created)
326                 dstream << " >> Saved dump to " << dumpfile << std::endl;
327         else
328                 dstream << " >> Failed to save dump" << std::endl;
329
330         return EXCEPTION_EXECUTE_HANDLER;
331 }
332
333 #endif
334
335 void debug_set_exception_handler()
336 {
337 #ifdef _MSC_VER
338         SetUnhandledExceptionFilter(Win32ExceptionHandler);
339 #endif
340 }
341