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