aboutsummaryrefslogtreecommitdiff
path: root/external/glim/exception.hpp
blob: 5fbfc5aa1bb7e3546e4def830b8d8ec833af1929 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#ifndef _GLIM_EXCEPTION_HPP_INCLUDED
#define _GLIM_EXCEPTION_HPP_INCLUDED

/// \file
/// Exceptions with configurable behaviour.
/// Requires `thread_local` support introduced in [gcc-4.8](http://gcc.gnu.org/gcc-4.8/changes.html)
/// (`__thread` is not reliable with GCC 4.7.2 across shared libraries).

#include <stdexcept>
#include <string>
#include <sstream>
#include <stdint.h>
#include <stdlib.h> // free
#include <unistd.h> // write

/// Throws `::glim::Exception` passing the current file and line into constructor.
#define GTHROW(message) throw ::glim::Exception (message, __FILE__, __LINE__)
/// Throws a `::glim::Exception` derived exception `name` passing the current file and line into constructor.
#define GNTHROW(name, message) throw name (message, __FILE__, __LINE__)
/// Helps defining new `::glim::Exception`-based exceptions.
/// Named exceptions might be useful in a debugger.
#define G_DEFINE_EXCEPTION(name) \
  struct name: public ::glim::Exception { \
    name (const ::std::string& message, const char* file, int line): ::glim::Exception (message, file, line) {} \
  }

// Workaround to compile under GCC 4.7.
#if defined (__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 7 && !defined (thread_local)
# define thread_local __thread
#endif

namespace glim {

// Ideas:
// RAII control via thread-local integer (with bits): option to capture stack trace (printed on "what()")
// see http://stacktrace.svn.sourceforge.net/viewvc/stacktrace/stacktrace/call_stack_gcc.cpp?revision=40&view=markup
// A handler to log exception with VALGRIND (with optional trace)
// A handler to log thread id and *pause* the thread in exception constructor (user can attach GDB and investigate)
// (or we might call an empty function: "I once used something similar,
//  but with an empty function debug_breakpoint. When debugging, I simply entered "bre debug_breakpoint"
//  at the gdb prompt - no asembler needed (compile debug_breakpoint in a separate compilation unit to avoid having the call optimized away)."
//  - http://stackoverflow.com/a/4720403/257568)
// A handler to call a debugger? (see: http://stackoverflow.com/a/4732119/257568)

// todo: Try a helper which uses cairo's backtrace-symbols.c
// http://code.ohloh.net/file?fid=zUOUdEl-Id-ijyPOmCkVnBJt2d8&cid=zGpizbyIjEw&s=addr2line&browser=Default#L7

// todo: Try a helper which uses cairo's lookup-symbol.c
// http://code.ohloh.net/file?fid=Je2jZqsOxge_SvWVrvywn2I0TIs&cid=zGpizbyIjEw&s=addr2line&browser=Default#L0

// todo: A helper converting backtrace to addr2line invocation, e.g.
// bin/test_exception() [0x4020cc];bin/test_exception(__cxa_throw+0x47) [0x402277];bin/test_exception() [0x401c06];/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x57f0ead];bin/test_exception() [0x401fd1];
// should be converted to
// addr2line -pifCa -e bin/test_exception 0x4020cc 0x402277 0x401c06 0x57f0ead 0x401fd1
//
// The helper should read the shared library addresses from /proc/.../map and generate separate addr2line invocations
// for groups of addresses inside the same shared library.
// => dladdr instead of /proc/../map; http://stackoverflow.com/a/2606152/257568
//
// Shared libraries (http://stackoverflow.com/a/7557756/257568).
// Example, backtrace: /usr/local/lib/libfrople.so(_ZN5mongo14BSONObjBuilder8appendAsERKNS_11BSONElementERKNS_10StringDataE+0x1ca) [0x2aef5b45eb8a]
// cat /proc/23630/maps | grep libfrople
//   -> 2aef5b363000-2aef5b53e000
// 0x2aef5b45eb8a - 2aef5b363000 = FBB8A
// addr2line -pifCa -e /usr/local/lib/libfrople.so 0xFBB8A
//
// cat /proc/`pidof FropleAndImg2`/maps | grep libfrople
// addr2line -pifCa -e /usr/local/lib/libfrople.so `perl -e 'printf ("%x", 0x2aef5b45eb8a - 0x2aef5b363000)'`

inline void captureBacktrace (void* stdStringPtr);

typedef void (*exception_handler_fn)(void*);

/// Exception with file and line information and optional stack trace capture.
/// Requires `thread_local` support ([gcc-4.8](http://gcc.gnu.org/gcc-4.8/changes.html)).
class Exception: public std::runtime_error {
 protected:
  const char* _file; int32_t _line;
  std::string _what;
  uint32_t _options;

  /// Append [{file}:{line}] into `buf`.
  void appendLine (std::string& buf) const {
    if (_file || _line > 0) {
      std::ostringstream oss;
      oss << '[';
      if (_file) oss << _file;
      if (_line >= 0) oss << ':' << _line;
      oss << "] ";
      buf.append (oss.str());
    }
  }

  /// Append a stack trace to `_what`.
  void capture() {
    if (_options & RENDEZVOUS) rendezvous();
    if (_options & CAPTURE_TRACE) {
      appendLine (_what);
      _what += "[at ";
      captureBacktrace (&_what);
      _what.append ("] ");
      _what += std::runtime_error::what();
    }
  }

 public:
  /** The reference to the thread-local options. */
  inline static uint32_t& options() {
    static thread_local uint32_t EXCEPTION_OPTIONS = 0;
    return EXCEPTION_OPTIONS;
  }
  enum Options: uint32_t {
    PLAIN_WHAT = 1,  ///< Pass `what` as is, do not add any information to it.
    HANDLE_ALL = 1 << 1,  ///< Run the custom handler from `__cxa_throw`.
    CAPTURE_TRACE = 1 << 2,  ///< Append a stack trace into the `Exception::_what` (with the help of the `captureBacktrace`).
    RENDEZVOUS = 1 << 3  ///< Call the rendezvous function in `throw` and in `what`, so that the GDB can catch it (break glim::Exception::rendezvous).
  };

  /** The pointer to the thread-local exception handler. */
  inline static exception_handler_fn* handler() {
    static thread_local exception_handler_fn EXCEPTION_HANDLER = nullptr;
    return &EXCEPTION_HANDLER;
  }
  /** The pointer to the thread-local argument for the exception handler. */
  inline static void** handlerArg() {
    static thread_local void* EXCEPTION_HANDLER_ARG = nullptr;
    return &EXCEPTION_HANDLER_ARG;
  }

  /// Invoked when the `RENDEZVOUS` option is set in order to help the debugger catch the exception (break glim::Exception::rendezvous).
  static void rendezvous() __attribute__((noinline)) {
    asm ("");  // Prevents the function from being optimized away.
  }

  Exception (const std::string& message):
    std::runtime_error (message), _file (0), _line (-1), _options (options()) {
    capture();}
  Exception (const std::string& message, const char* file, int32_t line):
    std::runtime_error (message), _file (file), _line (line), _options (options()) {
    capture();}
  ~Exception() throw() {}
  virtual const char* what() const throw() {
    if (_options & RENDEZVOUS) rendezvous();
    if (_options & PLAIN_WHAT) return std::runtime_error::what();
    std::string& buf = const_cast<std::string&> (_what);
    if (buf.empty()) {
      appendLine (buf);
      buf.append (std::runtime_error::what());
    }
    return buf.c_str();
  }
};

/// RAII control of thrown `Exception`s.
/// Example: \code
///   glim::ExceptionControl trace (glim::Exception::Options::CAPTURE_TRACE);
/// \endcode
/// Modifies the `Exception` options via a thread-local variable and restores them back upon destruction.\n
/// Currently uses http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Thread_002dLocal.html
/// (might use C++11 `thread_local` in the future).
class ExceptionControl {
 protected:
  uint32_t _savedOptions;
 public:
  ExceptionControl (uint32_t newOptions) {
    uint32_t& options = Exception::options();
    _savedOptions = options;
    options = newOptions;
  }
  ~ExceptionControl() {
    Exception::options() = _savedOptions;
  }
};

class ExceptionHandler {
protected:
  uint32_t _savedOptions;
  exception_handler_fn _savedHandler;
  void* _savedHandlerArg;
public:
  ExceptionHandler (uint32_t newOptions, exception_handler_fn handler, void* handlerArg) {
    uint32_t& options = Exception::options(); _savedOptions = options; options = newOptions;
    exception_handler_fn* handler_ = Exception::handler(); _savedHandler = *handler_; *handler_ = handler;
    void** handlerArg_ = Exception::handlerArg(); _savedHandlerArg = *handlerArg_; *handlerArg_ = handlerArg;
  }
  ~ExceptionHandler() {
    Exception::options() = _savedOptions;
    *Exception::handler() = _savedHandler;
    *Exception::handlerArg() = _savedHandlerArg;
  }
};

} // namespace glim

#if defined(__GNUC__) && (defined (__linux__) || defined (_SYSTYPE_BSD))
# include <execinfo.h> // backtrace; http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
# define _GLIM_USE_EXECINFO
#endif

namespace glim {

/** If `stdStringPtr` is not null then backtrace is saved there (must point to an std::string instance),
 * otherwise printed to write(2). */
void captureBacktrace (void* stdStringPtr) {
#ifdef _GLIM_USE_EXECINFO
  const int arraySize = 10; void *array[arraySize];
  int got = ::backtrace (array, arraySize);
  if (stdStringPtr) {
    std::string* out = (std::string*) stdStringPtr;
    char **strings = ::backtrace_symbols (array, got);
    for (int tn = 0; tn < got; ++tn) {out->append (strings[tn]); out->append (1, ';');}
    ::free (strings);
  } else ::backtrace_symbols_fd (array, got, 2);
#else
# warning captureBacktrace: I do not know how to capture backtrace there. Patches welcome.
#endif
}

} // namespace glim

#endif // _GLIM_EXCEPTION_HPP_INCLUDED

/**
 * Special handler for ALL exceptions. Usage:
 * 1) In the `main` module inject this code with:
 *   #define _GLIM_ALL_EXCEPTIONS_CODE
 *   #include <glim/exception.hpp>
 * 2) Link with "-ldl" (for `dlsym`).
 * 3) Use the ExceptionHandler to enable special behaviour in the current thread:
 *   glim::ExceptionHandler traceExceptions (glim::Exception::Options::HANDLE_ALL, glim::captureBacktrace, nullptr);
 *
 * About handing all exceptions see:
 *   http://stackoverflow.com/a/11674810/257568
 *   http://blog.sjinks.pro/c-cpp/969-track-uncaught-exceptions/
 */
#ifdef _GLIM_ALL_EXCEPTIONS_CODE

#include <dlfcn.h> // dlsym

typedef void(*cxa_throw_type)(void*, void*, void(*)(void*)); // Tested with GCC 4.7.
static cxa_throw_type NATIVE_CXA_THROW = 0;

extern "C" void __cxa_throw (void* thrown_exception, void* tinfo, void (*dest)(void*)) {
  if (!NATIVE_CXA_THROW) NATIVE_CXA_THROW = reinterpret_cast<cxa_throw_type> (::dlsym (RTLD_NEXT, "__cxa_throw"));
  if (!NATIVE_CXA_THROW) ::std::terminate();

  using namespace glim;
  uint32_t options = Exception::options();
  if (options & Exception::RENDEZVOUS) Exception::rendezvous();
  if (options & Exception::HANDLE_ALL) {
    exception_handler_fn handler = *Exception::handler();
    if (handler) handler (*Exception::handlerArg());
  }

  NATIVE_CXA_THROW (thrown_exception, tinfo, dest);
}

#undef _GLIM_ALL_EXCEPTIONS_CODE
#endif // _GLIM_ALL_EXCEPTIONS_CODE