a8183d3444c7c426f47fcbcc3d60b70548039f47
[dcpomatic.git] / src / lib / stack.cpp
1 // Copyright 2007 Edd Dawson.
2 // Distributed under the Boost Software License, Version 1.0.
3 // (See accompanying file LICENSE_1_0.txt or copy at
4 // http://www.boost.org/LICENSE_1_0.txt)
5
6 #include <cassert>
7 #include <cstring>
8 #include <cstdlib>
9 #include <iomanip>
10 #include <ostream>
11 #include <stdexcept>
12 #include <sstream>
13
14 #include "stack.hpp"
15
16 #if defined(_WIN32)
17 #   include <windows.h>
18 #   include <imagehlp.h>
19
20 #   if defined(__MINGW32__)
21 #       define PACKAGE 1
22 #       define PACKAGE_VERSION 1
23 #       include <bfd.h> // link against libbfd and libiberty
24 #       include <psapi.h> // link against psapi
25 #       include <cxxabi.h>
26 #   endif
27
28 #elif defined(__GNUC__)
29 #   include <dlfcn.h>
30 #   include <cxxabi.h>
31 #endif
32
33 namespace
34 {
35     const char * const unknown_function = "[unknown function]";
36     const char * const unknown_module = "[unknown module]";
37
38 #if defined(__GNUC__)
39     std::string demangle(const char *name)
40     {
41         if (!name)
42             return unknown_function;
43
44         int status = 0;
45         char *d = 0;
46         std::string ret = name;
47         try
48         {
49             if ((d = abi::__cxa_demangle(name, 0, 0, &status)))
50                 ret = d;
51         }
52         catch (const std::bad_alloc &) {  }
53
54         std::free(d);
55         return ret;
56     }
57 #endif
58
59 #if defined(_WIN32)
60
61     // Derive from this to disallow copying of your class.
62     // c.f. boost::noncopyable
63     class uncopyable
64     {
65         protected:
66             uncopyable() { }
67
68         private:
69             uncopyable(const uncopyable &);
70             uncopyable &operator= (const uncopyable &);
71     };
72
73 #if defined(__MINGW32__)
74
75     // Provides a means to translate a program counter offset in to the name of the corresponding function.
76     class bfd_context : uncopyable
77     {
78         private:
79             struct find_data
80             {
81                 std::string func;
82                 unsigned int line;
83                 asymbol **symbol_table;
84                 bfd_vma counter;
85             };
86
87         public:
88             bfd_context() :
89                 abfd_(0),
90                 sec_(0),
91                 symbol_table_(0)
92             {
93                 char procname[MAX_PATH];
94                 GetModuleFileNameA(NULL, procname, sizeof procname);
95
96                 bfd_init();
97                 abfd_ = bfd_openr(procname, 0);
98                 if (!abfd_)
99                     throw std::runtime_error("Failed to parse object data for the executable");
100
101                 char **formats = 0;
102                 bool b1 = bfd_check_format(abfd_, bfd_object);
103                 bool b2 = bfd_check_format_matches(abfd_, bfd_object, &formats);
104                 bool b3 = bfd_get_file_flags(abfd_) & HAS_SYMS;
105
106                 if (!(b1 && b2 && b3))
107                 {
108                     bfd_close(abfd_);
109                     free(formats);
110                     throw std::runtime_error("Failed to parse object data for the executable");
111                 }
112                 free(formats);
113
114                 // Load symbol table
115                 unsigned dummy = 0;
116                 if (bfd_read_minisymbols(abfd_, FALSE, reinterpret_cast<void **>(&symbol_table_), &dummy) == 0 &&
117                     bfd_read_minisymbols(abfd_, TRUE, reinterpret_cast<void **>(&symbol_table_), &dummy) < 0)
118                 {
119                     free(symbol_table_);
120                     bfd_close(abfd_);
121                     throw std::runtime_error("Failed to parse object data for the executable");
122                 }
123             }
124
125             ~bfd_context()
126             {
127                 free(symbol_table_);
128                 bfd_close(abfd_);
129             }
130
131             std::pair<std::string, unsigned int> get_function_name_and_line(DWORD offset)
132             {
133                 find_data data;
134                 data.symbol_table = symbol_table_;
135                 data.counter = offset;
136
137                 bfd_map_over_sections(abfd_, &find_function_name_in_section, &data);
138
139                 return std::make_pair(data.func, data.line);
140             }
141
142         private:
143             static void find_function_name_in_section(bfd *abfd, asection *sec, void *opaque_data)
144             {
145                 assert(sec);
146                 assert(opaque_data);
147                 find_data &data = *static_cast<find_data *>(opaque_data);
148
149                 if (!data.func.empty()) return; // already found it
150
151                 if (!(bfd_get_section_flags(abfd, sec) & SEC_ALLOC)) return;
152
153                 bfd_vma vma = bfd_get_section_vma(abfd, sec);
154                 if (data.counter < vma || vma + bfd_get_section_size(sec) <= data.counter) return;
155
156                 const char *func = 0;
157                 const char *file = 0;
158                 unsigned line = 0;
159
160                 if (bfd_find_nearest_line(abfd, sec, data.symbol_table, data.counter - vma, &file, &func, &line) && func) {
161                     data.func = demangle(func);
162                     data.line = line;
163                 }
164             }
165
166         private:
167             bfd *abfd_;
168             asection *sec_;
169             asymbol **symbol_table_;
170     };
171
172 #endif // __MINGW32__
173
174     // g++ spouts warnings if you use {0} to initialize PODs. So we use this instead:
175     const struct
176     {
177         template<typename POD>
178         operator POD () const { POD p; std::memset(&p, 0, sizeof p); return p; }
179     }
180     empty_pod = { };
181
182     // Wraps a FARPROC. Implicitly convertible to any kind of pointer-to-function.
183     // Avoids having reinterpret casts all over the place.
184     struct auto_cast_function_ptr
185     {
186         auto_cast_function_ptr(FARPROC f) : fptr_(f) { }
187
188         template<typename FuncPtr>
189         operator FuncPtr() const { return reinterpret_cast<FuncPtr>(fptr_); }
190
191         FARPROC fptr_;
192     };
193
194     // A wrapper around a DLL. Can dynamically get function pointers with the function() function!
195     class windows_dll : uncopyable
196     {
197         public:
198             explicit windows_dll(const std::string &libname) :
199                 name_(libname),
200                 lib_(LoadLibraryA(name_.c_str()))
201             {
202                 if (!lib_) throw std::runtime_error("Failed to load dll " + name_);
203             }
204
205             ~windows_dll() { FreeLibrary(lib_); }
206
207             const std::string &name() const { return name_; }
208
209             auto_cast_function_ptr function(const char *func_name) const
210             {
211                 FARPROC proc = GetProcAddress(lib_, func_name);
212                 if (!proc) throw std::runtime_error(std::string("failed to load function ") + func_name + " from library " + name_);
213
214                 return proc;
215             }
216
217         private:
218             std::string name_;
219             HMODULE lib_;
220     };
221
222     // An object that makes sure debugging symbols are available
223     class symbol_context : uncopyable
224     {
225         public:
226             symbol_context()
227             {
228                 if (!SymInitialize(GetCurrentProcess(), 0, TRUE))
229                     throw std::runtime_error("Failed to initialize symbol context");
230             }
231             ~symbol_context() { SymCleanup(GetCurrentProcess()); }
232     };
233
234     // A simple Windows mutex class. Use a lock object to lock the mutex for the duration of a scope.
235     class mutex : uncopyable
236     {
237         public:
238             mutex() { InitializeCriticalSection(&cs_); }
239             ~mutex() { DeleteCriticalSection(&cs_); }
240
241         private:
242             friend class lock;
243             void lock() { EnterCriticalSection(&cs_); }
244             void unlock() { LeaveCriticalSection(&cs_); }
245
246             CRITICAL_SECTION cs_;
247     }
248     g_fill_frames_mtx;
249
250     // A lock for the mutex
251     class lock : uncopyable
252     {
253         public:
254             lock(mutex &m) : m_(m) { m.lock(); }
255             ~lock() { m_.unlock(); }
256         private:
257             mutex &m_;
258     };
259
260
261     void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
262     {
263         lock lk(g_fill_frames_mtx);
264
265         symbol_context sc;
266 #ifdef __MINGW32__
267         bfd_context bfdc;
268 #endif
269
270         STACKFRAME frame = empty_pod;
271         CONTEXT context = empty_pod;
272         context.ContextFlags = CONTEXT_FULL;
273
274         windows_dll kernel32("kernel32.dll");
275         void (WINAPI *RtlCaptureContext_)(CONTEXT*) = kernel32.function("RtlCaptureContext");
276
277         RtlCaptureContext_(&context);
278
279 #if defined(_M_AMD64)
280         frame.AddrPC.Offset = context.Rip;
281         frame.AddrPC.Mode = AddrModeFlat;
282         frame.AddrStack.Offset = context.Rsp;
283         frame.AddrStack.Mode = AddrModeFlat;
284         frame.AddrFrame.Offset = context.Rbp;
285         frame.AddrFrame.Mode = AddrModeFlat;
286 #else
287         frame.AddrPC.Offset = context.Eip;
288         frame.AddrPC.Mode = AddrModeFlat;
289         frame.AddrStack.Offset = context.Esp;
290         frame.AddrStack.Mode = AddrModeFlat;
291         frame.AddrFrame.Offset = context.Ebp;
292         frame.AddrFrame.Mode = AddrModeFlat;
293 #endif
294
295         HANDLE process = GetCurrentProcess();
296         HANDLE thread = GetCurrentThread();
297
298         bool skip = true;
299         bool has_limit = limit != 0;
300         char symbol_buffer[sizeof(IMAGEHLP_SYMBOL) + 255];
301         char module_name_raw[MAX_PATH];
302
303 #if defined(_M_AMD64)
304         const DWORD machine = IMAGE_FILE_MACHINE_AMD64;
305 #else
306         const DWORD machine = IMAGE_FILE_MACHINE_I386;
307 #endif
308
309         while(StackWalk(machine, process, thread, &frame, &context, 0, SymFunctionTableAccess, SymGetModuleBase, 0))
310         {
311             if (skip)
312             {
313                 skip = false;
314                 continue;
315             }
316
317             if (has_limit && limit-- == 0) break;
318
319             IMAGEHLP_SYMBOL *symbol = reinterpret_cast<IMAGEHLP_SYMBOL *>(symbol_buffer);
320             symbol->SizeOfStruct = (sizeof *symbol) + 255;
321             symbol->MaxNameLength = 254;
322
323 #if defined(_WIN64)
324             DWORD64 module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
325 #else
326             DWORD module_base = SymGetModuleBase(process, frame.AddrPC.Offset);
327 #endif
328             std::string module_name = unknown_module;
329             if (module_base && GetModuleFileNameA(reinterpret_cast<HINSTANCE>(module_base), module_name_raw, MAX_PATH))
330                 module_name = module_name_raw;
331
332 #if defined(__MINGW32__)
333                 std::pair<std::string, unsigned int> func_and_line = bfdc.get_function_name_and_line(frame.AddrPC.Offset);
334
335                 if (func_and_line.first.empty())
336                 {
337 #if defined(_WIN64)
338                     DWORD64 dummy = 0;
339 #else               
340                     DWORD dummy = 0;
341 #endif              
342                     BOOL got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
343                     func_and_line.first = got_symbol ? symbol->Name : unknown_function;
344                 }
345 #else
346                 DWORD dummy = 0;
347                 BOOL got_symbol = SymGetSymFromAddr(process, frame.AddrPC.Offset, &dummy, symbol);
348                 std::string func = got_symbol ? symbol->Name : unknown_function;
349 #endif
350
351             dbg::stack_frame f(reinterpret_cast<const void *>(frame.AddrPC.Offset), func_and_line.first, func_and_line.second, module_name);
352             frames.push_back(f);
353         }
354     }
355 #elif defined(__GNUC__)
356 #   if defined(__i386__) || defined(__amd64__)
357
358     void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
359     {
360         // Based on code found at:
361         // http://www.tlug.org.za/wiki/index.php/Obtaining_a_stack_trace_in_C_upon_SIGSEGV
362
363         Dl_info info;
364         void **frame = static_cast<void **>(__builtin_frame_address(0));
365         void **bp = static_cast<void **>(*frame);
366         void *ip = frame[1];
367
368         bool has_limit = limit != 0;
369         bool skip = true;
370
371         while(bp && ip && dladdr(ip, &info))
372         {
373             if (skip)
374                 skip = false;
375             else
376             {
377                 if (has_limit && limit-- == 0) break;
378                 frames.push_back(dbg::stack_frame(ip, demangle(info.dli_sname), info.dli_fname));
379
380                 if(info.dli_sname && !std::strcmp(info.dli_sname, "main")) break;
381             }
382
383             ip = bp[1];
384             bp = static_cast<void**>(bp[0]);
385         }
386     }
387
388 #   elif defined(__ppc__)
389
390     void fill_frames(std::list<dbg::stack_frame> &frames, dbg::stack::depth_type limit)
391     {
392         // Based on code found at:
393         // http://www.informit.com/articles/article.aspx?p=606582&seqNum=4&rl=1
394
395         void *ip = __builtin_return_address(0);
396         void **frame = static_cast<void **>(__builtin_frame_address(1));
397         bool has_limit = limit != 0;
398         Dl_info info;
399
400         do
401         {
402             if (has_limit && limit-- == 0) break;
403
404             if (dladdr(ip, &info))
405                 frames.push_back(dbg::stack_frame(ip, demangle(info.dli_sname), info.dli_fname));
406
407             if (frame && (frame = static_cast<void**>(*frame))) ip = *(frame + 2);
408         }
409         while (frame && ip);
410     }
411
412 #   else
413         // GNU, but not x86, x64 nor PPC
414 #       error "Sorry but dbg::stack is not supported on this architecture"
415 #   endif
416 #else
417     // Unsupported compiler
418 #   error "Sorry but dbg::stack is not supported on this compiler"
419 #endif
420
421 } // close anonymous namespace
422
423
424
425 namespace dbg
426 {
427     stack_frame::stack_frame(const void *instruction, const std::string &function, unsigned int line, const std::string &module) :
428         instruction(instruction),
429         function(function),
430         line(line),
431         module(module)
432     {
433     }
434
435     std::ostream &operator<< (std::ostream &out, const stack_frame &frame)
436     {
437         return out << frame.instruction << ": " << frame.function << ":" << frame.line << " in " << frame.module;
438     }
439
440     stack::stack(depth_type limit)
441     {
442         fill_frames(frames_, limit);
443     }
444
445     stack::const_iterator stack::begin() const
446     {
447         return frames_.begin();
448     }
449
450     stack::const_iterator stack::end() const
451     {
452         return frames_.end();
453     }
454
455     stack::depth_type stack::depth() const
456     {
457         return frames_.size();
458     }
459
460 } // close namespace dbg
461