├── .gitignore ├── LICENSE ├── Makefile.unittest ├── README.md ├── backtrace.cpp ├── backtrace.h ├── backtrace.pro ├── backtrace.sln ├── backtrace.vcxproj ├── barrier.cpp ├── barrier.h ├── cva_list.h ├── demangle.cpp ├── demangle.h ├── detectgdb.cpp ├── detectgdb.h ├── exceptionassert.cpp ├── exceptionassert.h ├── expectexception.h ├── main └── main.cpp ├── prettifysegfault.cpp ├── prettifysegfault.h ├── prettifysegfaultnoinline.cpp ├── shared_state.cpp ├── shared_state.h ├── shared_state.pdf ├── shared_state_mutex.h ├── shared_state_traits_backtrace.cpp ├── shared_state_traits_backtrace.h ├── shared_timed_mutex_polyfill.h ├── signalname.cpp ├── signalname.h ├── tasktimer.cpp ├── tasktimer.h ├── timer.cpp ├── timer.h ├── trace_perf.cpp ├── trace_perf.h ├── trace_perf ├── barrier.cpp.db ├── make_dump_summary.py ├── rebuild_and_evaluate.sh ├── shared_state.cpp.db ├── shared_state.cpp.db-debug ├── shared_state_traits_backtrace.cpp.db ├── shared_state_traits_backtrace.cpp.db-debug ├── timer.cpp.db └── timer.cpp.db-debug ├── unittest.cpp ├── unittest.h ├── unused.h ├── verifyexecutiontime.cpp ├── verifyexecutiontime.h └── windows ├── StackWalker.cpp └── StackWalker.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | backtrace-unittest 3 | Debug 4 | Release 5 | x64 6 | *.suo 7 | *.vcxproj.user 8 | *.vcxproj.filters 9 | .depend 10 | trace_perf/dump 11 | trace_perf/summary*/* 12 | Makefile 13 | *.a 14 | libbacktrace.h 15 | -------------------------------------------------------------------------------- /Makefile.unittest: -------------------------------------------------------------------------------- 1 | # The intentaion is not to build these snippets as a .lib, just include them in your source. 2 | # This serves to run a unit test. 3 | 4 | 5 | CXX = clang++ 6 | LINK = clang++ 7 | 8 | 9 | BACKTRACE_CXXFLAGS = -fno-omit-frame-pointer 10 | BACKTRACE_LFLAGS = -rdynamic 11 | # no-omit-frame-pointer and -rdynamic aren't strictly needed for backtrace to work 12 | # but it makes info in the backtrace more likely to correspond to your code. 13 | 14 | 15 | # pick one, release or debug 16 | #DEBUG_RELEASE = -D_DEBUG 17 | DEBUG_RELEASE = -O3 18 | 19 | 20 | # shared_state configuration 21 | # 22 | # shared_state supports concurrent reads by default. The overhead of enabling 23 | # concurrent reads is about 5% larger than when concurernt reads are disabled 24 | # if there is no lock contention. The overhead is about 0.8 microseconds. 25 | # 26 | #SHARED_STATE = -DSHARED_STATE_NO_SHARED_MUTEX 27 | #SHARED_STATE = -DSHARED_STATE_NO_TIMEOUT 28 | #SHARED_STATE = -DSHARED_STATE_NO_SHARED_MUTEX -DSHARED_STATE_NO_TIMEOUT 29 | 30 | # Boost mutexes 31 | # 32 | # std (libc++) is 20% faster than boost with concurrent reads enabled. 33 | # boost (pthreads) is 1% faster than std with concurrent reads disabled. 34 | # 35 | #LIBS += -lboost_system-mt -lboost_chrono-mt -lboost_thread-mt 36 | #SHARED_STATE += -DSHARED_STATE_BOOST_MUTEX 37 | 38 | # MacPorts 39 | # 40 | #INCPATH += -I/opt/local/include 41 | #LIBS += -L/opt/local/lib 42 | 43 | 44 | TARGET = ./backtrace-unittest 45 | CXXFLAGS = -std=c++11 -W -Wall -g $(BACKTRACE_CXXFLAGS) $(DEBUG_RELEASE) $(SHARED_STATE) $(INCPATH) 46 | LFLAGS = $(BACKTRACE_LFLAGS) 47 | SRCS = $(wildcard *.cpp) 48 | OBJS = $(SRCS:%.cpp=%.o) main/main.o 49 | 50 | all: $(TARGET) 51 | 52 | clean: 53 | rm -f $(OBJS) $(TARGET) 54 | 55 | .depend: *.cpp *.h 56 | mkdep $(CXXFLAGS) *.cpp 57 | 58 | $(OBJS): Makefile.unittest 59 | 60 | $(TARGET): $(OBJS) .depend 61 | $(LINK) $(LFLAGS) -o $(TARGET) $(OBJS) $(LIBS) 62 | $(TARGET) || true 63 | 64 | include .depend 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Backtraces rationale 2 | ==================== 3 | _The Backtrace class should store a backtrace of the call stack in 1 ms on OS X. Windows and linux was supported but are not currently maintained._ 4 | 5 | To make bugs go squish you want some sort of indication as to where it is. This is a bunch of small classes that makes use of runtime backtraces in C++ to decorate exceptions and segfaults with info about their origin. Each class header comment defines its expected behaviour. 6 | 7 | 8 | #### Backtrace example #### 9 | 10 | ````cpp 11 | string backtrace = Backtrace::make_string (); 12 | cout << backtrace; 13 | ```` 14 | 15 | Example output (compiled with clang-500.2.79): 16 | 17 | backtrace (5 frames) 18 | Backtrace::make_string(int) (in backtrace-unittest) (backtrace.cpp:264) 19 | Backtrace::test() (in backtrace-unittest) (backtrace.cpp:283) 20 | BacktraceTest::UnitTest::test() (in backtrace-unittest) (unittest.cpp:34) 21 | start (in libdyld.dylib) + 1 22 | 0x0000000000000001 23 | 24 | 25 | #### PrettifySegfault example #### 26 | _The PrettifySegfault class should attempt to capture any null-pointer exception (SIGSEGV and SIGILL) in the program, log a backtrace, and then throw a regular C++ exception from the function causing the signal._ 27 | 28 | ````cpp 29 | void nasty_function() { 30 | *(int*) NULL = 0; 31 | } 32 | ```` 33 | 34 | Somewhere else 35 | 36 | ````cpp 37 | try { 38 | nasty_function(); 39 | } catch (const exception& x) { 40 | string backtrace_and_signal_name = boost::diagnostic_information(x); 41 | } 42 | ```` 43 | 44 | #### shared_state example #### 45 | _The shared\_state\ class is a smart pointer that guarantees thread-safe access to objects of type T._ 46 | 47 | Make an instance of an object thread-safe by storing it in a shared\_state smart pointer. 48 | 49 | ````cpp 50 | shared_state a {new MyType}; 51 | a->foo (); 52 | 53 | shared_state ac {a}; 54 | ac->bar (); 55 | ```` 56 | 57 | The call to foo() will have mutually exclusive write access and the call to bar() will have shared read-only const access. Given 58 | 59 | ````cpp 60 | class MyType { 61 | public: 62 | void foo(); 63 | void bar() const; 64 | }; 65 | ```` 66 | 67 | To keep the lock over multiple method calls, do: 68 | 69 | ````cpp 70 | shared_state a {new MyType}; 71 | 72 | { 73 | auto w = a.write(); 74 | w->foo(); 75 | w->bar(); 76 | } 77 | 78 | { 79 | auto r = a.read(); 80 | r->bar(); 81 | r->bar(); 82 | } 83 | ```` 84 | 85 | .read() and .write() are implicitly called by the -> operator on shared_state and create thread-safe critical sections. For shared read-only access when using .read() and for mutually exclusive read-and-write access using .write(). They both throw exceptions on lock timeout, embed a backtrace in an exception object like this: 86 | 87 | ````cpp 88 | #include "shared_state_traits_backtrace.h" 89 | 90 | class MyType { 91 | public: 92 | typedef shared_state_traits_backtrace shared_state_traits; 93 | ... 94 | }; 95 | 96 | 97 | ... { 98 | shared_state a(new MyType); 99 | ... 100 | try { 101 | auto w = a.write(); 102 | ... 103 | } catch (lock_failed& x) { 104 | const Backtrace* backtrace = boost::get_error_info(x); 105 | ... 106 | } 107 | ... } 108 | ```` 109 | 110 | See shared\_state.pdf and shared\_state.h for details. 111 | 112 | #### ExceptionAssert example #### 113 | _The ExceptionAssert class should store details about an assertion that failed._ 114 | 115 | ````cpp 116 | try { 117 | EXCEPTION_ASSERT_EQUALS( 1, 2 ); 118 | } catch (const exception& x) { 119 | string what = boost::diagnostic_information(x); 120 | } 121 | ```` 122 | 123 | #### More examples #### 124 | Please refer to the static test() function in each class for more complete examples. 125 | 126 | 127 | ## How to use this in your own code ## 128 | 129 | The .pro file for QMAKE builds a static library. The project depends on the boost library. 130 | 131 | 132 | ## License ## 133 | 134 | GPL v3.0 135 | 136 | 137 | ## Other utilities ## 138 | 139 | - Demangle should perform a system specific demangling of compiled C++ names. 140 | - The DetectGdb class should detect whether the current process was started through, or is running through, gdb (or as a child of another process). 141 | - The Timer class should measure time with a high accuracy. 142 | - The TaskTimer class should log how long time it takes to execute a scope while distinguishing nested scopes and different threads. 143 | -------------------------------------------------------------------------------- /backtrace.cpp: -------------------------------------------------------------------------------- 1 | #include "backtrace.h" 2 | #include "exceptionassert.h" 3 | #include "demangle.h" 4 | #include "timer.h" 5 | 6 | 7 | #include 8 | 9 | #ifdef __APPLE__ 10 | #include 11 | #include 12 | #endif 13 | 14 | #ifndef _MSC_VER 15 | #include 16 | #else 17 | #include 18 | #include "StackWalker.h" 19 | #include "TlHelp32.h" 20 | #endif 21 | 22 | using namespace boost; 23 | using namespace std; 24 | 25 | typedef error_info failed_condition_type; 26 | typedef error_info failed_to_parse_backtrace_string_type; 27 | 28 | void *bt_array[256]; 29 | size_t array_size; 30 | void printSignalInfo(int sig); 31 | 32 | #ifdef __APPLE__ 33 | string exec_get_output(string cmd) { 34 | FILE* pipe = popen(cmd.c_str (), "r"); 35 | if (!pipe) { 36 | return ""; 37 | } 38 | 39 | char buffer[128]; 40 | string result = ""; 41 | while(!feof(pipe)) { 42 | if(fgets(buffer, 128, pipe) != NULL) 43 | result += buffer; 44 | } 45 | pclose(pipe); 46 | return result; 47 | } 48 | #endif 49 | 50 | void Backtrace:: 51 | malloc_free_log() 52 | { 53 | fflush(stdout); 54 | 55 | #ifndef _MSC_VER 56 | // GCC supports malloc-free backtrace which is kind of neat 57 | // It does require the stack to grow though as this is a function call. 58 | 59 | // get void*'s for all entries on the stack 60 | array_size = backtrace(bt_array, 256); 61 | 62 | // print out all the frames to stderr 63 | backtrace_symbols_fd(bt_array, array_size, 2); 64 | fputs("\n",stderr); 65 | #else 66 | // If we can't do a malloc-free backtrace. Just attempt a regular one and see what happens 67 | fputs(Backtrace::make_string ().c_str(), stderr); 68 | fputs("\n",stderr); 69 | #endif 70 | 71 | fflush(stderr); 72 | } 73 | 74 | #if defined(_WIN32) 75 | 76 | class StackWalkerStringHelper: private StackWalker 77 | { 78 | public: 79 | string getStackTrace(int skipframes, HANDLE hThread) // = GetCurrentThread()) 80 | { 81 | fflush(stdout); 82 | if (!str_.empty()) 83 | str_ += "\n\n"; 84 | str_.clear(); 85 | skipframes_ = skipframes; 86 | ShowCallstack(hThread); 87 | return str_; 88 | } 89 | 90 | string str() { return str_; } 91 | 92 | private: 93 | virtual void OnOutput(LPCSTR szText) 94 | { 95 | //fputs(szText, stderr); 96 | 97 | //StackWalker::OnOutput(szText); 98 | } 99 | 100 | virtual void OnCallStackOutput(LPCSTR szText) 101 | { 102 | if (0 < skipframes_) 103 | --skipframes_; 104 | else 105 | str_ += szText; 106 | } 107 | 108 | virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr) 109 | { 110 | if (gle==487 && 0==strcmp(szFuncName, "SymGetLineFromAddr64")) 111 | ; // ignore 112 | else if (gle==487 && 0==strcmp(szFuncName, "SymGetSymFromAddr64")) 113 | ; // ignore 114 | else 115 | StackWalker::OnDbgHelpErr(szFuncName, gle, addr); 116 | } 117 | 118 | string str_; 119 | int skipframes_; 120 | }; 121 | 122 | 123 | class StackWalkerString { 124 | public: 125 | static string getStackTrace(int skipframes, HANDLE hThread = GetCurrentThread()) 126 | { 127 | static StackWalkerStringHelper swsi; 128 | static boost::mutex mymutex; 129 | 130 | unique_lock l(mymutex); 131 | 132 | return swsi.getStackTrace(skipframes, hThread); 133 | } 134 | }; 135 | 136 | 137 | string prettyBackTrace(int skipframes) 138 | { 139 | // http://stackoverflow.com/questions/590160/how-to-log-stack-frames-with-windows-x64 140 | // http://www.codeproject.com/Articles/11132/Walking-the-callstack 141 | // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684335%28v=vs.85%29.aspx 142 | // http://stackoverflow.com/questions/9965784/how-to-obtain-list-of-thread-handles-from-a-win32-process 143 | 144 | stringstream str; 145 | str << StackWalkerString::getStackTrace(skipframes+1); 146 | 147 | // Get the backtrace of all other threads in this process as well 148 | 149 | DWORD currentProcessId = GetCurrentProcessId(); 150 | DWORD currentThreadId = GetCurrentThreadId(); 151 | HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); 152 | if (h != INVALID_HANDLE_VALUE) { 153 | THREADENTRY32 te; 154 | te.dwSize = sizeof(te); 155 | if (Thread32First(h, &te)) { 156 | do { 157 | if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + 158 | sizeof(te.th32OwnerProcessID)) { 159 | if (currentProcessId == te.th32OwnerProcessID && currentThreadId != te.th32ThreadID) 160 | { 161 | HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, TRUE, te.th32ThreadID); 162 | str << "Thread " << te.th32ThreadID << " is at: " << endl << StackWalkerString::getStackTrace(0, hThread); 163 | CloseHandle(hThread); 164 | fflush(stdout); 165 | } 166 | } 167 | te.dwSize = sizeof(te); 168 | } while (Thread32Next(h, &te)); 169 | } 170 | CloseHandle(h); 171 | } 172 | 173 | return str.str(); 174 | } 175 | 176 | 177 | Backtrace::info Backtrace:: 178 | make(int skipFrames) 179 | { 180 | Backtrace b; 181 | b.pretty_print_ = prettyBackTrace(skipFrames+2); 182 | return Backtrace::info(b); 183 | } 184 | 185 | string Backtrace:: 186 | to_string() const 187 | { 188 | return pretty_print_; 189 | } 190 | 191 | #else 192 | 193 | Backtrace::info Backtrace:: 194 | make(int skipFrames) 195 | { 196 | Backtrace b; 197 | if (skipFrames < 0) 198 | skipFrames = 0; 199 | 200 | void *array[256]; 201 | int size; 202 | 203 | // get void*'s for all entries on the stack 204 | size = backtrace(array, 256); 205 | 206 | if (size <= skipFrames) 207 | b.pretty_print_ = str(format("Backtrace::make(%s) failed") % skipFrames); 208 | else 209 | { 210 | b.frames_.resize (size-skipFrames); 211 | copy(array + skipFrames, array + size, b.frames_.begin ()); 212 | } 213 | 214 | return Backtrace::info(b); 215 | } 216 | 217 | 218 | string Backtrace:: 219 | to_string() const 220 | { 221 | if (!pretty_print_.empty ()) 222 | return pretty_print_; 223 | 224 | char** msg = backtrace_symbols(&frames_[0], frames_.size()); 225 | if (0 == msg) 226 | return "Couldn't get backtrace symbol names for pretty print"; 227 | 228 | 229 | string bt = str(format("backtrace (%d frames)\n") % (frames_.size ())); 230 | bool found_pretty = false; 231 | 232 | #ifdef __APPLE__ 233 | int p = 2+sizeof(void*)*2; 234 | string addrs; 235 | for (unsigned i=0; i < frames_.size(); ++i) 236 | { 237 | string s = msg[i]; 238 | 239 | string addr = s.substr (40, p); 240 | addrs += addr + " "; 241 | } 242 | 243 | int id = getpid(); 244 | 245 | // 'atos' should be invoked through 'xcrun atos', but that crashes every 246 | // now and then, and takes much more time to execute. 247 | //string cmd = str(format("xcrun atos -p %1% %2%") % id % addrs); 248 | string cmd = str(format("atos -p %1% %2%") % id % addrs); 249 | 250 | string op = exec_get_output(cmd); 251 | found_pretty = !op.empty(); 252 | bt += op; 253 | #endif 254 | 255 | if (!found_pretty) 256 | for (unsigned i=0; i < frames_.size (); ++i) 257 | { 258 | string s = msg[i]; 259 | try 260 | { 261 | #ifdef __APPLE__ 262 | size_t n = s.find_first_of (' ', 42+p); 263 | string name = s.substr (41+p, n-41-p); 264 | string last = s.substr (n); 265 | #else 266 | size_t n1 = s.find_last_of ('('); 267 | size_t n2 = s.find_last_of ('+'); 268 | size_t n3 = s.find_last_of ('/'); 269 | string name = s.substr (n1+1, n2-n1-1); 270 | string last = " (" + s.substr(n2) + " " + s.substr(n3+1,n1-n3-1); 271 | #endif 272 | bt += str(format("%-5d%s%s\n") % i % demangle(name.c_str ()) % last ); 273 | } 274 | catch(const std::exception&) 275 | { 276 | bt += s; 277 | bt += "\n"; 278 | } 279 | } 280 | 281 | free(msg); 282 | 283 | bt += "\n"; 284 | 285 | return bt; 286 | } 287 | #endif 288 | 289 | 290 | string Backtrace:: 291 | to_string() 292 | { 293 | return pretty_print_ = ((const Backtrace*)this)->to_string(); 294 | } 295 | 296 | 297 | std::string Backtrace:: 298 | make_string(int skipframes) 299 | { 300 | return make(skipframes+1).value ().to_string(); 301 | } 302 | 303 | 304 | Backtrace:: 305 | Backtrace() 306 | { 307 | } 308 | 309 | 310 | static void throwfunction() 311 | { 312 | BOOST_THROW_EXCEPTION(unknown_exception() << Backtrace::make ()); 313 | } 314 | 315 | 316 | void Backtrace:: 317 | test() 318 | { 319 | // It should store a backtrace of the call stack in 1 ms, 320 | // except for windows where it should takes 30 ms but 321 | // include a backtrace from all threads. 322 | { 323 | #ifdef _MSC_VER 324 | { 325 | // Warmpup, load modules 326 | Backtrace::info backtrace = Backtrace::make (); 327 | } 328 | #endif 329 | 330 | Timer t; 331 | Backtrace::info backtrace = Backtrace::make (); 332 | double T = t.elapsed (); 333 | #ifdef _MSC_VER 334 | EXCEPTION_ASSERT_LESS( T, 0.030f ); 335 | #else 336 | EXCEPTION_ASSERT_LESS( T, 0.001f ); 337 | #endif 338 | EXCEPTION_ASSERT_LESS( 0u, backtrace.value ().frames_.size() + backtrace.value ().pretty_print_.size() ); 339 | } 340 | 341 | // It should work as error info to boost::exception 342 | { 343 | try { 344 | BOOST_THROW_EXCEPTION(unknown_exception() << Backtrace::make ()); 345 | } catch (const std::exception&) { 346 | } 347 | } 348 | 349 | // It should translate to a pretty backtrace when asked for a string representation 350 | { 351 | do try { 352 | throwfunction(); 353 | } catch (const boost::exception& x) { 354 | string s = diagnostic_information(x); 355 | 356 | try { 357 | #ifdef _MSC_VER 358 | EXCEPTION_ASSERTX( s.find ("throwfunction") != string::npos, s ); 359 | EXCEPTION_ASSERTX( s.find ("backtrace.cpp(312)") != string::npos, s ); 360 | EXCEPTION_ASSERTX( s.find ("Backtrace::test") != string::npos, s ); 361 | EXCEPTION_ASSERTX( s.find ("main") != string::npos, s ); 362 | EXCEPTION_ASSERTX( s.find ("backtrace.cpp (312): throwfunction") != string::npos, s ); 363 | if(4==sizeof(void*) && !debug) // WoW64 w/ optimization behaves differently 364 | EXCEPTION_ASSERTX( s.find ("backtrace.cpp (352): Backtrace::test") != string::npos, s ); 365 | else 366 | EXCEPTION_ASSERTX( s.find ("backtrace.cpp (350): Backtrace::test") != string::npos, s ); 367 | #else 368 | EXCEPTION_ASSERTX( s.find ("throwfunction()") != string::npos, s ); 369 | EXCEPTION_ASSERTX( s.find ("Backtrace::test()") != string::npos, s ); 370 | EXCEPTION_ASSERTX( s.find ("start") != string::npos, s ); 371 | 372 | #ifdef _DEBUG 373 | // The backtrace.cpp file and line numbers will be removed by optimization 374 | EXCEPTION_ASSERTX( s.find ("backtrace.cpp(312)") != string::npos, s ); 375 | EXCEPTION_ASSERTX( s.find ("(backtrace.cpp:312)") != string::npos, s ); 376 | // The call to throwfunction will be removed by optimization 377 | EXCEPTION_ASSERTX( s.find ("main") != string::npos, s ); 378 | EXCEPTION_ASSERTX( s.find ("(backtrace.cpp:352)") != string::npos, s ); 379 | #else 380 | EXCEPTION_ASSERTX( s.find ("(backtrace.cpp:352)") == string::npos, s ); 381 | #endif 382 | #endif 383 | break; 384 | } catch (const ExceptionAssert&) { 385 | x << failed_condition_type(boost::current_exception()); 386 | x << failed_to_parse_backtrace_string_type(s); 387 | } 388 | throw; 389 | } while (false); 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /backtrace.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKTRACE_H 2 | #define BACKTRACE_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | /** 10 | * @brief The Backtrace class should store a backtrace of the call stack in 1 ms, 11 | * except for windows where it should takes 30 ms but include a backtrace from all threads. 12 | * 13 | * It should work as error info to boost::exception 14 | * 15 | * It should translate to a pretty backtrace when asked for a string representation. 16 | * 17 | * Include debug info '-g' for this to work. 18 | */ 19 | class Backtrace 20 | { 21 | public: 22 | typedef boost::error_info info; 23 | 24 | static info make(int skipframes=1); 25 | static std::string make_string(int skipframes=1); 26 | static void malloc_free_log(); 27 | std::string to_string(); 28 | std::string to_string() const; 29 | 30 | private: 31 | Backtrace(); 32 | 33 | std::string pretty_print_; 34 | std::vector frames_; 35 | 36 | public: 37 | static void test(); 38 | }; 39 | 40 | 41 | inline std::string to_string( Backtrace::info const & x ) 42 | { 43 | return x.value().to_string(); 44 | } 45 | 46 | 47 | inline std::string to_string( Backtrace::info & x ) 48 | { 49 | return x.value().to_string(); 50 | } 51 | 52 | 53 | // This is a bad idea because it takes a lot of time. 54 | //#define BACKTRACE() 55 | // (str(boost::format("%s:%d %s\n%s") % __FILE__ % __LINE__ % BOOST_CURRENT_FUNCTION % Backtrace::make_string())) 56 | 57 | 58 | #endif // BACKTRACE_H 59 | -------------------------------------------------------------------------------- /backtrace.pro: -------------------------------------------------------------------------------- 1 | # This builds a static library 2 | # Use Makefile.unittest to build and run a unit test 3 | 4 | TARGET = backtrace 5 | TEMPLATE = lib 6 | win32:TEMPLATE = vclib 7 | win32:CONFIG += debug_and_release 8 | 9 | CONFIG += staticlib warn_on 10 | CONFIG += c++11 buildflags 11 | CONFIG += tmpdir 12 | CONFIG += precompile_header_with_all_headers 13 | 14 | QT += opengl 15 | 16 | SOURCES += *.cpp 17 | HEADERS += *.h 18 | 19 | win32: INCLUDEPATH += ../sonicawe-winlib 20 | 21 | macx:exists(/opt/local/include/): INCLUDEPATH += /opt/local/include/ # macports 22 | macx:exists(/usr/local/include/): INCLUDEPATH += /usr/local/include/ # homebrew 23 | 24 | OTHER_FILES += \ 25 | LICENSE \ 26 | *.pro \ 27 | 28 | win32 { 29 | othersources.input = OTHER_FILES 30 | othersources.output = ${QMAKE_FILE_NAME} 31 | QMAKE_EXTRA_COMPILERS += othersources 32 | } 33 | -------------------------------------------------------------------------------- /backtrace.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "backtrace", "backtrace.vcxproj", "{5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Debug|x64 = Debug|x64 10 | Release|Win32 = Release|Win32 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Debug|Win32.ActiveCfg = Debug|Win32 15 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Debug|Win32.Build.0 = Debug|Win32 16 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Debug|x64.ActiveCfg = Debug|x64 17 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Debug|x64.Build.0 = Debug|x64 18 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Release|Win32.ActiveCfg = Release|Win32 19 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Release|Win32.Build.0 = Release|Win32 20 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Release|x64.ActiveCfg = Release|x64 21 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3}.Release|x64.Build.0 = Release|x64 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /backtrace.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {5EDB8CF5-4F0A-4B6B-8F51-6709CDE8B1E3} 23 | Win32Proj 24 | backtrace 25 | 26 | 27 | 28 | Application 29 | true 30 | Unicode 31 | 32 | 33 | Application 34 | true 35 | Unicode 36 | 37 | 38 | Application 39 | false 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | false 46 | true 47 | Unicode 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | true 67 | 68 | 69 | true 70 | 71 | 72 | false 73 | 74 | 75 | false 76 | 77 | 78 | 79 | 80 | 81 | Level3 82 | Disabled 83 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 84 | C:\Users\jogu6405\dev\boost_1_55_0;%(AdditionalIncludeDirectories) 85 | 86 | 87 | Console 88 | true 89 | C:\local\boost_1_55_0\lib32-msvc-10.0;%(AdditionalLibraryDirectories) 90 | 91 | 92 | 93 | 94 | 95 | 96 | Level3 97 | Disabled 98 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 99 | C:\local\boost_1_55_0;%(AdditionalIncludeDirectories) 100 | 101 | 102 | Console 103 | true 104 | C:\Users\jogu6405\dev\boost_1_55_0\lib64-msvc-10.0;%(AdditionalLibraryDirectories) 105 | 106 | 107 | 108 | 109 | Level3 110 | 111 | 112 | MaxSpeed 113 | true 114 | true 115 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 116 | C:\Users\jogu6405\dev\boost_1_55_0;%(AdditionalIncludeDirectories) 117 | 118 | 119 | Console 120 | true 121 | true 122 | true 123 | C:\local\boost_1_55_0\lib32-msvc-10.0;%(AdditionalLibraryDirectories) 124 | 125 | 126 | 127 | 128 | Level3 129 | 130 | 131 | MaxSpeed 132 | true 133 | true 134 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | C:\local\boost_1_55_0;%(AdditionalIncludeDirectories) 136 | 137 | 138 | Console 139 | true 140 | true 141 | true 142 | C:\Users\jogu6405\dev\boost_1_55_0\lib64-msvc-10.0;%(AdditionalLibraryDirectories) 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 | -------------------------------------------------------------------------------- /barrier.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | This file only contains unit tests for spinning_barrier and locking_barrier. 3 | This file is not required for using either. 4 | */ 5 | 6 | #include "barrier.h" 7 | #include "exceptionassert.h" 8 | #include "timer.h" 9 | #include "trace_perf.h" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | using namespace std; 17 | 18 | template 19 | void simple_barrier_test() { 20 | bool a = true; 21 | barrier b{2}; 22 | 23 | future f = async(launch::async, [&a,&b]() 24 | { 25 | b.wait (); 26 | a = false; 27 | b.wait (); 28 | }); 29 | 30 | this_thread::sleep_for (chrono::microseconds{10}); 31 | 32 | EXCEPTION_ASSERT(a); 33 | b.wait (); 34 | b.wait (); 35 | EXCEPTION_ASSERT(!a); 36 | f.get (); 37 | } 38 | 39 | 40 | static void evalate(int N) { 41 | vector> f(N); 42 | spinning_barrier sb (N+1); 43 | locking_barrier lb (N+1); 44 | 45 | int M = 20; 46 | 47 | for (unsigned i=0; i(); 95 | } 96 | } 97 | 98 | 99 | void locking_barrier:: 100 | test () 101 | { 102 | // It should behave like spinning_barrier but use a lock instead of spinning 103 | { 104 | simple_barrier_test(); 105 | } 106 | 107 | { 108 | // A spinning lock is always fast if the barriers are reached simultaneous 109 | unsigned concurentThreadsSupported = std::max(1u, std::thread::hardware_concurrency()); 110 | 111 | evalate(10*concurentThreadsSupported); 112 | evalate((concurentThreadsSupported+1)/2); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /barrier.h: -------------------------------------------------------------------------------- 1 | #ifndef BARRIER_H 2 | #define BARRIER_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * @brief The spinning_barrier class should provide a lock-free spinning 10 | * barrier. 11 | * 12 | * Based on chill @ http://stackoverflow.com/a/8120707 13 | */ 14 | class spinning_barrier 15 | { 16 | public: 17 | /** 18 | * @brief spinning_barrier 19 | * @param n number of threads to participate in the barrier. If 'n' is 20 | * larger than the number of cores, consider using locking_barrier 21 | * instead. 22 | */ 23 | spinning_barrier (unsigned int n) : n_ (n), nwait_ (0), step_(0), yield_(n > std::thread::hardware_concurrency()) {} 24 | spinning_barrier (unsigned int n, bool yield) : n_ (n), nwait_ (0), step_(0), yield_(yield) {} 25 | 26 | bool wait () 27 | { 28 | unsigned int step = step_.load (); 29 | 30 | if (nwait_.fetch_add (1) == n_ - 1) 31 | { 32 | // OK, last thread to come. 33 | nwait_.store (0); 34 | step_.fetch_add (1); 35 | return true; 36 | } 37 | else if (yield_) 38 | { 39 | while (step_.load () == step) 40 | std::this_thread::yield(); 41 | return false; 42 | } 43 | else 44 | { 45 | while (step_.load () == step) 46 | ; 47 | return false; 48 | } 49 | } 50 | 51 | private: 52 | // Number of synchronized threads. 53 | const unsigned int n_; 54 | 55 | // Number of threads currently spinning. 56 | std::atomic nwait_; 57 | 58 | // Number of barrier syncronizations completed so far, it's OK to wrap. 59 | std::atomic step_; 60 | 61 | // Whether to yield or not 62 | bool yield_; 63 | 64 | public: 65 | static void test(); 66 | }; 67 | 68 | 69 | /** 70 | * @brief The locking_barrier class should provide a non-spinning barrier. 71 | */ 72 | class locking_barrier 73 | { 74 | public: 75 | /** 76 | * @brief locking_barrier 77 | * @param n number of threads to participate in the barrier. If 'n' is 78 | * smaller than or equal to the number of cores, consider using 79 | * locking_barrier instead. 80 | */ 81 | locking_barrier (unsigned int n) : n_ (n), nwait_ (0), step_(0) {} 82 | 83 | bool wait () 84 | { 85 | std::unique_lock l(m_); 86 | unsigned int step = step_; 87 | 88 | if (nwait_++ == n_ - 1) 89 | { 90 | // OK, last thread to come. 91 | nwait_ = 0; 92 | step_++; 93 | cv_.notify_all (); 94 | return true; 95 | } 96 | else 97 | { 98 | while (step_ == step) 99 | cv_.wait (l); 100 | return false; 101 | } 102 | } 103 | 104 | private: 105 | // Number of synchronized threads. 106 | const unsigned int n_; 107 | 108 | // Number of threads currently spinning. 109 | unsigned int nwait_; 110 | 111 | // Number of barrier syncronizations completed so far, it's OK to wrap. 112 | unsigned int step_; 113 | 114 | std::mutex m_; 115 | std::condition_variable cv_; 116 | 117 | public: 118 | static void test(); 119 | }; 120 | 121 | #endif // BARRIER_H 122 | -------------------------------------------------------------------------------- /cva_list.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | /** 6 | Please note, this class is not at all more safe than to use the 7 | va_list macros directly, it's just a slightly more convenient way 8 | to first start and then subsequently end a va_list. 9 | 10 | And please do keep in mind the comment above, just as with va_arg, 11 | the only possible error indication you'd ever get from this class 12 | is a crash because of memory access violation (unless you're using 13 | clr:pure). 14 | */ 15 | 16 | #ifdef _MSC_VER 17 | #define Cva_start(name, argument) Cva_list name; va_start((va_list)name,argument); 18 | #else 19 | #define Cva_start(name, argument) Cva_list name; va_start(name,argument); 20 | #endif 21 | 22 | class Cva_list 23 | { 24 | public: 25 | #ifdef _MSC_VER 26 | Cva_list(va_list cheatlist):list(cheatlist) {} 27 | #else 28 | Cva_list(va_list cheatlist) { va_copy(list, cheatlist);} 29 | #endif 30 | Cva_list() 31 | { 32 | } 33 | 34 | ~Cva_list( ) 35 | { 36 | } 37 | 38 | template 39 | type& cva_arg() 40 | { 41 | return va_arg( list, type ); 42 | } 43 | 44 | operator va_list&(){ return list; } 45 | private: 46 | va_list list; 47 | }; 48 | -------------------------------------------------------------------------------- /demangle.cpp: -------------------------------------------------------------------------------- 1 | #include "demangle.h" 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | #ifdef __GNUC__ 8 | 9 | #include 10 | string demangle(const char* d) { 11 | int status; 12 | char * a = abi::__cxa_demangle(d, 0, 0, &status); 13 | if (a) { 14 | string s(a); 15 | free(a); 16 | return s; 17 | } 18 | return string(d); 19 | } 20 | 21 | #elif defined(_MSC_VER) 22 | 23 | extern "C" 24 | char * __unDName( 25 | char * outputString, 26 | const char * name, 27 | int maxStringLength, 28 | void * (__cdecl * pAlloc )(size_t), 29 | void (__cdecl * pFree )(void *), 30 | unsigned short disableFlags); 31 | 32 | string demangle(const char* d) { 33 | char * const pTmpUndName = __unDName(0, d, 0, malloc, free, 0x2800); 34 | if (pTmpUndName) 35 | { 36 | string s(pTmpUndName); 37 | free(pTmpUndName); 38 | return s; 39 | } 40 | return string(d); 41 | } 42 | 43 | #else 44 | 45 | // TODO find solution for this compiler 46 | std::string demangle(const char* d) { 47 | return string(d); 48 | } 49 | 50 | std::string demangle(const char* d) { 51 | int pointer = 0; 52 | string s; 53 | while ('P' == *d) { pointer++; d++; } 54 | if ('f' == *d) { s += "float"; } 55 | if ('d' == *d) { s += "double"; } 56 | int i = atoi(d); 57 | if (i>0) { 58 | d++; 59 | while (i>0) { 60 | s+=*d; 61 | i--; 62 | d++; 63 | } 64 | } 65 | if (s.empty()) 66 | s+=d; 67 | if ('I' == *d) { 68 | d++; 69 | s+="<"; 70 | s+=demangle(d); 71 | s+=">"; 72 | if (0==pointer) 73 | s+=" "; 74 | } 75 | 76 | while (pointer>0) { 77 | s+="*"; 78 | pointer--; 79 | } 80 | 81 | return s; 82 | } 83 | 84 | #endif 85 | 86 | 87 | std::string demangle(const std::type_info& i) 88 | { 89 | return demangle(i.name()); 90 | } 91 | 92 | 93 | std::ostream& operator<< (std::ostream& o, const std::type_info& i) 94 | { 95 | return o << demangle(i); 96 | } 97 | -------------------------------------------------------------------------------- /demangle.h: -------------------------------------------------------------------------------- 1 | #ifndef DEMANGLE_H 2 | #define DEMANGLE_H 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Demangle should perform a system specific demangling of compiled C++ names. 9 | */ 10 | 11 | std::string demangle(const std::type_info& i); 12 | std::string demangle(const char* d); 13 | 14 | template 15 | std::string vartype(T const& t) { 16 | // suppress warning: unreferenced formal parameter 17 | // even though 't' is used by typeid below 18 | //&t?void():void(); 19 | 20 | // Note that typeid(t) might differ from typeid(T) as T is deduced 21 | // in compile time and t in runtime with RTTI. 22 | return demangle(typeid(t)); 23 | } 24 | 25 | std::ostream& operator<< (std::ostream& o, const std::type_info& i); 26 | 27 | #endif // DEMANGLE_H 28 | 29 | -------------------------------------------------------------------------------- /detectgdb.cpp: -------------------------------------------------------------------------------- 1 | #include "detectgdb.h" 2 | 3 | #ifndef _MSC_VER 4 | #include 5 | #ifndef DARWIN_NO_CARBON // skip for ios 6 | #include 7 | #endif 8 | #include 9 | #else 10 | #include // IsDebuggerPresent 11 | #endif 12 | 13 | #ifndef PTRACE_ATTACH 14 | #define PTRACE_ATTACH PT_ATTACH 15 | #define PTRACE_CONT PT_CONTINUE 16 | #define PTRACE_DETACH PT_DETACH 17 | #endif 18 | 19 | #include 20 | #include 21 | 22 | #ifdef __APPLE__ 23 | #include 24 | #endif 25 | 26 | #if defined(__GNUC__) 27 | #include 28 | #else 29 | #define fileno _fileno 30 | #endif 31 | 32 | static bool was_started_through_gdb_ = DetectGdb::is_running_through_gdb (); 33 | 34 | #ifndef _MSC_VER 35 | 36 | // http://stackoverflow.com/a/10973747/1513411 37 | // gdb apparently opens FD(s) 3,4,5 (whereas a typical program uses only stdin=0, stdout=1, stderr=2) 38 | bool is_running_through_gdb_xorl() 39 | { 40 | bool gdb = false; 41 | FILE *fd = fopen("/tmp", "r"); 42 | 43 | if (fileno(fd) >= 5) 44 | { 45 | gdb = true; 46 | } 47 | 48 | fclose(fd); 49 | return gdb; 50 | } 51 | 52 | 53 | // http://stackoverflow.com/a/3599394/1513411 54 | bool is_running_through_gdb_terminus() 55 | { 56 | int pid = fork(); 57 | int status = 0; 58 | int res; 59 | 60 | if (pid == -1) 61 | { 62 | printf("Fork failed!\n"); 63 | perror("fork"); 64 | return 1; 65 | } 66 | 67 | if (pid == 0) 68 | { 69 | /* Child fork */ 70 | int ppid = getppid(); 71 | 72 | if (ptrace(PTRACE_ATTACH, ppid, nullptr, 0) == 0) 73 | { 74 | /* Wait for the parent to stop and continue it */ 75 | waitpid(ppid, nullptr, 0); 76 | ptrace(PTRACE_CONT, ppid, nullptr, 0); 77 | 78 | /* Detach */ 79 | ptrace(PTRACE_DETACH, ppid, nullptr, 0); 80 | 81 | /* We were the tracers, so gdb is not present */ 82 | res = 0; 83 | } 84 | else 85 | { 86 | /* Trace failed so gdb is present */ 87 | res = 1; 88 | } 89 | 90 | _exit(res); 91 | } 92 | else 93 | { 94 | pid_t w = 0; 95 | do 96 | { 97 | // the first signal might be an unblocked signal, skip it 98 | w = waitpid(pid, &status, 0); 99 | } 100 | while (w < 0 && errno == EINTR); 101 | 102 | // fall-back to return "true" if the fork failed for whatever reason 103 | res = WIFEXITED(status) ? WEXITSTATUS(status) : 1; 104 | 105 | //std::cout << "WIFCONTINUED(status)="<< WIFCONTINUED(status) 106 | // << ", WSTOPSIG(status)=" << WSTOPSIG(status) << std::endl; 107 | //std::cout << "WIFSTOPPED(status)="<< WIFSTOPPED(status) 108 | // << ", WSTOPSIG(status)=" << WSTOPSIG(status) << std::endl; 109 | //std::cout << "WIFSIGNALED(status)="<< WIFSIGNALED(status) 110 | // << ", WTERMSIG(status)=" << WTERMSIG(status) << std::endl; 111 | //std::cout << "WIFEXITED(status)="<< WIFEXITED(status) 112 | // << ", WEXITSTATUS(status)=" << WEXITSTATUS(status) << std::endl; 113 | } 114 | return res; 115 | } 116 | 117 | 118 | bool DetectGdb:: 119 | is_running_through_gdb() 120 | { 121 | #if defined(__APPLE_CPP__) && (TARGET_OS_IPHONE==0) 122 | // No implementation for detecting IOS debugger 123 | #ifdef _DEBUG 124 | return false; 125 | #else 126 | return true; 127 | #endif 128 | #else 129 | bool is_attached_in_qt_creator = is_running_through_gdb_xorl(); 130 | bool is_attached_by_system_debugger = is_running_through_gdb_terminus(); 131 | return is_attached_in_qt_creator || is_attached_by_system_debugger; 132 | #endif 133 | } 134 | 135 | #else 136 | 137 | bool DetectGdb:: 138 | is_running_through_gdb() 139 | { 140 | return TRUE == IsDebuggerPresent(); 141 | } 142 | 143 | #endif 144 | 145 | 146 | bool DetectGdb:: 147 | was_started_through_gdb() 148 | { 149 | return was_started_through_gdb_; 150 | } 151 | -------------------------------------------------------------------------------- /detectgdb.h: -------------------------------------------------------------------------------- 1 | #ifndef DETECTGDB_H 2 | #define DETECTGDB_H 3 | 4 | /** 5 | * @brief The DetectGdb class should detect whether the current process was 6 | * started through, or is running through, gdb. 7 | * 8 | * It might work with lldb or other debuggers as well. 9 | */ 10 | class DetectGdb 11 | { 12 | public: 13 | static bool is_running_through_gdb(); 14 | static bool was_started_through_gdb(); 15 | }; 16 | 17 | #endif // DETECTGDB_H 18 | -------------------------------------------------------------------------------- /exceptionassert.cpp: -------------------------------------------------------------------------------- 1 | #include "exceptionassert.h" 2 | 3 | #include "tasktimer.h" 4 | #include "expectexception.h" 5 | 6 | #include 7 | 8 | using namespace std; 9 | using namespace boost; 10 | 11 | 12 | ExceptionAssert:: 13 | ExceptionAssert() 14 | { 15 | } 16 | 17 | 18 | void ExceptionAssert:: 19 | test() 20 | { 21 | EXPECT_EXCEPTION(ExceptionAssert, EXCEPTION_ASSERT(false)); 22 | } 23 | 24 | 25 | void ExceptionAssert:: 26 | throwException(const char* functionMacro, 27 | const char* fileMacro, int lineMacro, 28 | const char* condition, 29 | const std::string& callerMessage, int skipFrames) 30 | { 31 | ::boost::exception_detail::throw_exception_( 32 | ExceptionAssert() 33 | << ExceptionAssert_condition(condition) 34 | << ExceptionAssert_message(callerMessage) 35 | << Backtrace::make(2 + skipFrames), 36 | functionMacro, 37 | fileMacro, 38 | lineMacro); 39 | } 40 | 41 | 42 | void ExceptionAssert:: 43 | throwException(const char* functionMacro, 44 | const char* fileMacro, int lineMacro, 45 | const char* details, 46 | const boost::format& f) 47 | { 48 | throwException (functionMacro, fileMacro, lineMacro, details, f.str (), 1); 49 | } 50 | 51 | 52 | void ExceptionAssert:: 53 | logAndThrow(const char* functionMacro, 54 | const char* fileMacro, int lineMacro, 55 | const char* condition, 56 | const std::string& callerMessage, int skipFrames) 57 | { 58 | logError(functionMacro, fileMacro, lineMacro, condition, callerMessage); 59 | throwException (functionMacro, fileMacro, lineMacro, condition, callerMessage, ++skipFrames); 60 | } 61 | 62 | 63 | void ExceptionAssert:: 64 | logAndThrow(const char* functionMacro, 65 | const char* fileMacro, int lineMacro, 66 | const char* details, 67 | const boost::format& f) 68 | { 69 | logAndThrow (functionMacro, fileMacro, lineMacro, details, f.str ()); 70 | } 71 | 72 | 73 | void ExceptionAssert:: 74 | logError(const char* functionMacro, 75 | const char* fileMacro, int lineMacro, 76 | const char* condition, 77 | const std::string& callerMessage) 78 | { 79 | TaskInfo ti("ExceptionAssert"); 80 | 81 | TaskInfo("%s:%d %s", fileMacro, lineMacro, functionMacro); 82 | TaskInfo("condition: %s", condition); 83 | TaskInfo("message: %s", callerMessage.c_str()); 84 | 85 | TaskInfo("%s", Backtrace::make_string (2).c_str()); 86 | } 87 | 88 | 89 | void ExceptionAssert:: 90 | logError(const char* functionMacro, 91 | const char* fileMacro, int lineMacro, 92 | const char* details, 93 | const boost::format& f) 94 | { 95 | logError (functionMacro, fileMacro, lineMacro, details, f.str ()); 96 | } 97 | -------------------------------------------------------------------------------- /exceptionassert.h: -------------------------------------------------------------------------------- 1 | #ifndef EXCEPTIONASSERT_H 2 | #define EXCEPTIONASSERT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // inline type declaration in template argument to error_info without type definition doesn't work on ios 10 | struct errinfo_format_tag {}; 11 | typedef boost::error_info errinfo_format; 12 | 13 | 14 | /** 15 | * @brief The ExceptionAssert class should store details about an assertion 16 | * that failed. 17 | * 18 | * Use like so 19 | * 20 | * try { 21 | * EXCEPTION_ASSERT_EQUALS( 1, 2 ); 22 | * } catch (const exception& x) { 23 | * string what = boost::diagnostic_information(x); 24 | * } 25 | */ 26 | class ExceptionAssert: virtual public boost::exception, virtual public std::exception 27 | { 28 | public: 29 | struct message {}; 30 | typedef boost::error_info ExceptionAssert_condition; 31 | typedef boost::error_info ExceptionAssert_message; 32 | 33 | // Prevent inlining of creating exception object 34 | static void throwException(const char* functionMacro, 35 | const char* fileMacro, int lineMacro, 36 | const char* details, 37 | const std::string&, int skipFrames=0); 38 | 39 | static void throwException(const char* functionMacro, 40 | const char* fileMacro, int lineMacro, 41 | const char* details, 42 | const boost::format&); 43 | 44 | static void logAndThrow(const char* functionMacro, 45 | const char* fileMacro, int lineMacro, 46 | const char* details, 47 | const std::string&, int skipFrames=0); 48 | 49 | static void logAndThrow(const char* functionMacro, 50 | const char* fileMacro, int lineMacro, 51 | const char* details, 52 | const boost::format&); 53 | 54 | static void logError(const char* functionMacro, 55 | const char* fileMacro, int lineMacro, 56 | const char* condition, 57 | const std::string&); 58 | 59 | static void logError(const char* functionMacro, 60 | const char* fileMacro, int lineMacro, 61 | const char* condition, 62 | const boost::format&); 63 | 64 | private: 65 | ExceptionAssert(); 66 | 67 | public: 68 | static void test(); 69 | }; 70 | 71 | #define LOG_ERROR( msg ) \ 72 | do { \ 73 | ExceptionAssert::logError( BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, "LOG_ERROR", msg ); \ 74 | } while(false) 75 | 76 | // 'condition' is evaluated once, 'msg' is a boost::format, std::string or compatible type 77 | #define EXCEPTION_ASSERTX( condition, msg ) \ 78 | do { \ 79 | if (!(condition)) \ 80 | ExceptionAssert::throwException( BOOST_CURRENT_FUNCTION, __FILE__, __LINE__, #condition, msg ); \ 81 | } while(false) 82 | 83 | #define EXCEPTION_ASSERT( condition ) EXCEPTION_ASSERTX( condition, "error" ) 84 | 85 | #define EXCEPTION_ASSERT_EQUALS( a, b ) \ 86 | do { \ 87 | const auto& _x = (a); \ 88 | const auto& _y = (b); \ 89 | EXCEPTION_ASSERTX( _x == _y, boost::format("Equals failed: Got '%1%' = %3%, and '%2%' = %4%") % #a % #b % _x % _y); \ 90 | } while(false) 91 | 92 | #define EXCEPTION_ASSERT_NOTEQUALS( a, b ) \ 93 | do { \ 94 | const auto& _x = (a); \ 95 | const auto& _y = (b); \ 96 | EXCEPTION_ASSERTX( _x != _y, boost::format("Not equals failed: Got '%1%' = %3%, and '%2%' = %4%") % #a % #b % _x % _y); \ 97 | } while(false) 98 | 99 | #define EXCEPTION_ASSERT_FUZZYEQUALS( a, b, d ) \ 100 | do { \ 101 | const auto& _x = (a); \ 102 | const auto& _y = (b); \ 103 | const auto& _z = (d); \ 104 | EXCEPTION_ASSERTX( (_x > _y ? _x - _y <= _z : _y - _x <= _z), boost::format("Fuzzy equals failed: Got '%1%' = %3%, and '%2%' = %4%, with diff = %6%, tolerance = %5%") % #a % #b % _x % _y % _z % (_x > _y ? _x - _y : _y - _x)); \ 105 | } while(false) 106 | 107 | #define EXCEPTION_ASSERT_LESS( a, b ) \ 108 | do { \ 109 | const auto& _x = (a); \ 110 | const auto& _y = (b); \ 111 | EXCEPTION_ASSERTX( _x < _y, boost::format("Less failed: Got '%1%' = %3%, and '%2%' = %4%") % #a % #b % _x % _y); \ 112 | } while(false) 113 | 114 | #define EXCEPTION_ASSERT_LESS_OR_EQUAL( a, b ) \ 115 | do { \ 116 | const auto& _x = (a); \ 117 | const auto& _y = (b); \ 118 | EXCEPTION_ASSERTX( _x <= _y, boost::format("Less or equal failed: Got '%1%' = %3%, and '%2%' = %4%") % #a % #b % _x % _y); \ 119 | } while(false) 120 | 121 | #ifdef _DEBUG 122 | #define EXCEPTION_ASSERTX_DBG EXCEPTION_ASSERTX 123 | #define EXCEPTION_ASSERT_DBG EXCEPTION_ASSERT 124 | #define EXCEPTION_ASSERT_EQUALS_DBG EXCEPTION_ASSERT_EQUALS 125 | #define EXCEPTION_ASSERT_NOTEQUALS_DBG EXCEPTION_ASSERT_NOTEQUALS 126 | #define EXCEPTION_ASSERT_FUZZYEQUALS_DBG EXCEPTION_ASSERT_FUZZYEQUALS 127 | #define EXCEPTION_ASSERT_LESS_DBG EXCEPTION_ASSERT_LESS 128 | #else 129 | #define EXCEPTION_ASSERTX_DBG( condition, msg ) 130 | #define EXCEPTION_ASSERT_DBG( condition ) 131 | #define EXCEPTION_ASSERT_EQUALS_DBG( a, b ) 132 | #define EXCEPTION_ASSERT_NOTEQUALS_DBG( a, b ) 133 | #define EXCEPTION_ASSERT_FUZZYEQUALS_DBG( a, b, c ) 134 | #define EXCEPTION_ASSERT_LESS_DBG( a, b ) 135 | #endif 136 | 137 | #endif // EXCEPTIONASSERT_H 138 | -------------------------------------------------------------------------------- /expectexception.h: -------------------------------------------------------------------------------- 1 | #ifndef EXPECTEXCEPTION_H 2 | #define EXPECTEXCEPTION_H 3 | 4 | #include "backtrace.h" 5 | #include "demangle.h" 6 | 7 | /** 8 | * The EXPECT_EXCEPTION macro should ensure that an expression throws an exception of a given type. 9 | */ 10 | 11 | class expected_exception: virtual public boost::exception, virtual public std::exception {}; 12 | class unexpected_exception: virtual public boost::exception, virtual public std::exception {}; 13 | struct unexpected_exception_tag {}; 14 | typedef boost::error_info unexpected_exception_info; 15 | 16 | struct tag_expected_exception_type {}; 17 | typedef boost::error_info expected_exception_type; 18 | 19 | inline 20 | std::string 21 | to_string( expected_exception_type const & x ) 22 | { 23 | return demangle(*x.value()); 24 | } 25 | 26 | #define EXPECT_EXCEPTION(X,F) do { \ 27 | bool thrown = false; \ 28 | \ 29 | try { \ 30 | F; \ 31 | } catch (const X&) { \ 32 | thrown = true; \ 33 | } catch (const std::exception&) { \ 34 | BOOST_THROW_EXCEPTION(unexpected_exception {}\ 35 | << unexpected_exception_info {boost::current_exception ()} \ 36 | << Backtrace::make () \ 37 | << expected_exception_type {&typeid(X)}); \ 38 | } \ 39 | \ 40 | if (!thrown) \ 41 | BOOST_THROW_EXCEPTION(expected_exception {} \ 42 | << Backtrace::make () \ 43 | << expected_exception_type {&typeid(X)}); \ 44 | } while(false); 45 | 46 | 47 | #endif // EXPECTEXCEPTION_H 48 | -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "../unittest.h" 2 | #include "../prettifysegfault.h" 3 | 4 | int main(int argc, char** argv) 5 | { 6 | if (1 != argc) 7 | { 8 | printf("%s: Invalid argument\n", argv[0]); 9 | return 1; 10 | } 11 | 12 | PrettifySegfault::setup (); 13 | 14 | return BacktraceTest::UnitTest::test(false); 15 | } 16 | -------------------------------------------------------------------------------- /prettifysegfault.cpp: -------------------------------------------------------------------------------- 1 | #include "prettifysegfault.h" 2 | #include "backtrace.h" 3 | #include "signalname.h" 4 | #include "expectexception.h" 5 | #include "tasktimer.h" 6 | #include "exceptionassert.h" 7 | 8 | #include 9 | #include 10 | 11 | #ifdef _MSC_VER 12 | #include 13 | #endif 14 | 15 | bool is_doing_signal_handling = false; 16 | bool has_caught_any_signal_value = false; 17 | bool enable_signal_print = true; 18 | bool nested_signal_handling = false; 19 | int last_caught_signal = 0; 20 | 21 | void handler(int sig); 22 | void printSignalInfo(int sig, bool); 23 | 24 | #ifndef _MSC_VER 25 | void seghandle_userspace() { 26 | // note: because we set up a proper stackframe, 27 | // unwinding is safe from here. 28 | // also, by the time this function runs, the 29 | // operating system is "done" with the signal. 30 | 31 | // choose language-appropriate throw instruction 32 | // raise new MemoryAccessError "Segmentation Fault"; 33 | // throw new MemoryAccessException; 34 | // longjmp(erroneous_exit); 35 | // asm { int 3; } 36 | // *(int*) NULL = 0; 37 | 38 | if (!nested_signal_handling) 39 | printSignalInfo(last_caught_signal, false); 40 | } 41 | 42 | #ifndef DARWIN_NO_CARBON // not ios 43 | void seghandle(int sig, __siginfo*, void* unused) 44 | { 45 | nested_signal_handling = is_doing_signal_handling; 46 | has_caught_any_signal_value = true; 47 | is_doing_signal_handling = true; 48 | last_caught_signal = sig; 49 | 50 | fflush(stdout); 51 | flush(std::cout); 52 | flush(std::cerr); 53 | if (enable_signal_print) 54 | fprintf(stderr, "\nError: signal %s(%d) %s\n", SignalName::name (sig), sig, SignalName::desc (sig)); 55 | fflush(stderr); 56 | 57 | // http://feepingcreature.github.io/handling.html 58 | ucontext_t* uc = (ucontext_t*) unused; 59 | // No. I refuse to use triple-pointers. 60 | // Just pretend ref a = v; is V* ap = &v; 61 | // and then substitute a with (*ap). 62 | // ref gregs = uc->uc_mcontext.gregs; 63 | // ref eip = (void*) gregs[X86Registers.EIP], 64 | // ref esp = (void**) gregs[X86Registers.ESP]; 65 | 66 | // 32-bit 67 | // void*& eip = (void*&)uc->uc_mcontext->__ss.__eip; 68 | // void**& esp = (void**&)uc->uc_mcontext->__ss.__esp; 69 | 70 | // 64-bit 71 | // warning: dereferencing type-punned pointer will break strict-aliasing rules 72 | // how should this be handled? 73 | void*& eip = (void*&)uc->uc_mcontext->__ss.__rip; 74 | void**& esp = (void**&)uc->uc_mcontext->__ss.__rsp; 75 | 76 | // imitate the effects of "call seghandle_userspace" 77 | esp --; // decrement stackpointer. 78 | // remember: stack grows down! 79 | *esp = eip; 80 | 81 | // set up OS for call via return, like in the attack 82 | eip = (void*) &seghandle_userspace; 83 | 84 | // this is reached because 'ret' jumps to seghandle_userspace 85 | is_doing_signal_handling = false; 86 | } 87 | 88 | void setup_signal_survivor(int sig) 89 | { 90 | struct sigaction sa; 91 | sa.sa_flags = SA_SIGINFO; 92 | sigemptyset (&sa.sa_mask); 93 | sa.__sigaction_u.__sa_sigaction = &seghandle; 94 | bool sigsegv_handler = sigaction(sig, &sa, NULL) != -1; 95 | EXCEPTION_ASSERTX(sigsegv_handler, "failed to setup SIGSEGV handler"); 96 | } 97 | #endif 98 | #endif 99 | 100 | void handler(int sig) 101 | { 102 | nested_signal_handling = is_doing_signal_handling; 103 | has_caught_any_signal_value = true; 104 | is_doing_signal_handling = true; 105 | last_caught_signal = sig; 106 | 107 | // The intention is that handler(sig) should cause very few stack and heap 108 | // allocations. And 'printSignalInfo' should prints prettier info but with 109 | // the expense of a whole bunch of both stack and heap allocations. 110 | 111 | // http://feepingcreature.github.io/handling.html does not work, see note 4. 112 | 113 | fflush(stdout); 114 | if (enable_signal_print) 115 | fprintf(stderr, "\nError: signal %s(%d) %s\n", SignalName::name (sig), sig, SignalName::desc (sig)); 116 | fflush(stderr); 117 | 118 | if (enable_signal_print) 119 | Backtrace::malloc_free_log (); 120 | 121 | if (!nested_signal_handling) 122 | printSignalInfo(sig, true); 123 | 124 | // This will not be reached if an exception is thrown 125 | is_doing_signal_handling = false; 126 | } 127 | 128 | 129 | void printSignalInfo(int sig, bool noaction) 130 | { 131 | // Lots of memory allocations here. Not neat, but more helpful. 132 | 133 | if (enable_signal_print) 134 | TaskInfo("Got %s(%d) '%s'\n%s", 135 | SignalName::name (sig), sig, SignalName::desc (sig), 136 | Backtrace::make_string ().c_str()); 137 | fflush(stdout); 138 | 139 | switch(sig) 140 | { 141 | #ifndef _MSC_VER 142 | case SIGCHLD: 143 | return; 144 | 145 | case SIGWINCH: 146 | TaskInfo("Got SIGWINCH"); 147 | fflush(stdout); 148 | return; 149 | #endif 150 | case SIGABRT: 151 | TaskInfo("Got SIGABRT"); 152 | fflush(stdout); 153 | if (!noaction) 154 | exit(1); 155 | return; 156 | 157 | case SIGILL: 158 | case SIGSEGV: 159 | if (enable_signal_print) 160 | TaskInfo("Throwing segfault_sigill_exception"); 161 | fflush(stdout); 162 | 163 | if (!noaction) 164 | BOOST_THROW_EXCEPTION(segfault_sigill_exception() 165 | << signal_exception::signal(sig) 166 | << signal_exception::signalname(SignalName::name (sig)) 167 | << signal_exception::signaldesc(SignalName::desc (sig)) 168 | << Backtrace::make (2)); 169 | return; 170 | 171 | default: 172 | if (enable_signal_print) 173 | TaskInfo("Throwing signal_exception"); 174 | fflush(stdout); 175 | 176 | if (!noaction) 177 | BOOST_THROW_EXCEPTION(signal_exception() 178 | << signal_exception::signal(sig) 179 | << signal_exception::signalname(SignalName::name (sig)) 180 | << signal_exception::signaldesc(SignalName::desc (sig)) 181 | << Backtrace::make (2)); 182 | return; 183 | } 184 | } 185 | 186 | 187 | #ifdef _MSC_VER 188 | 189 | const char* ExceptionCodeName(DWORD code) 190 | { 191 | switch(code) 192 | { 193 | case WAIT_IO_COMPLETION: return "WAIT_IO_COMPLETION / STATUS_USER_APC"; 194 | case STILL_ACTIVE: return "STILL_ACTIVE, STATUS_PENDING"; 195 | case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION"; 196 | case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT"; 197 | case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT"; 198 | case EXCEPTION_SINGLE_STEP: return "EXCEPTION_SINGLE_STEP"; 199 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; 200 | case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND"; 201 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; 202 | case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT"; 203 | case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION"; 204 | case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW"; 205 | case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK"; 206 | case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW"; 207 | case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO"; 208 | case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW"; 209 | case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION"; 210 | case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR"; 211 | case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION"; 212 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; 213 | case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW"; 214 | case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION"; 215 | case EXCEPTION_GUARD_PAGE: return "EXCEPTION_GUARD_PAGE"; 216 | case EXCEPTION_INVALID_HANDLE: return "EXCEPTION_INVALID_HANDLE"; 217 | #ifdef STATUS_POSSIBLE_DEADLOCK 218 | case EXCEPTION_POSSIBLE_DEADLOCK: return "EXCEPTION_POSSIBLE_DEADLOCK"; 219 | #endif 220 | case CONTROL_C_EXIT: return "CONTROL_C_EXIT"; 221 | default: return "UNKNOWN"; 222 | } 223 | } 224 | const char* ExceptionCodeDescription(DWORD code) 225 | { 226 | switch(code) 227 | { 228 | case EXCEPTION_ACCESS_VIOLATION: return "The thread tried to read from or write to a virtual address for which it does not have the appropriate access."; 229 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "The thread tried to access an array element that is out of bounds and the underlying hardware supports bounds checking."; 230 | case EXCEPTION_BREAKPOINT: return "A breakpoint was encountered."; 231 | case EXCEPTION_DATATYPE_MISALIGNMENT: return "The thread tried to read or write data that is misaligned on hardware that does not provide alignment. For example, 16-bit values must be aligned on 2-byte boundaries; 32-bit values on 4-byte boundaries, and so on."; 232 | case EXCEPTION_FLT_DENORMAL_OPERAND: return "One of the operands in a floating-point operation is denormal. A denormal value is one that is too small to represent as a standard floating-point value."; 233 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "The thread tried to divide a floating-point value by a floating-point divisor of zero."; 234 | case EXCEPTION_FLT_INEXACT_RESULT: return "The result of a floating-point operation cannot be represented exactly as a decimal fraction."; 235 | case EXCEPTION_FLT_INVALID_OPERATION: return "This exception represents any floating-point exception not included in this list."; 236 | case EXCEPTION_FLT_OVERFLOW: return "The exponent of a floating-point operation is greater than the magnitude allowed by the corresponding type."; 237 | case EXCEPTION_FLT_STACK_CHECK: return "The stack overflowed or underflowed as the result of a floating-point operation."; 238 | case EXCEPTION_FLT_UNDERFLOW: return "The exponent of a floating-point operation is less than the magnitude allowed by the corresponding type."; 239 | case EXCEPTION_ILLEGAL_INSTRUCTION: return "The thread tried to execute an invalid instruction."; 240 | case EXCEPTION_IN_PAGE_ERROR: return "The thread tried to access a page that was not present, and the system was unable to load the page. For example, this exception might occur if a network connection is lost while running a program over the network."; 241 | case EXCEPTION_INT_DIVIDE_BY_ZERO: return "The thread tried to divide an integer value by an integer divisor of zero."; 242 | case EXCEPTION_INT_OVERFLOW: return "The result of an integer operation caused a carry out of the most significant bit of the result."; 243 | case EXCEPTION_INVALID_DISPOSITION: return "An exception handler returned an invalid disposition to the exception dispatcher. Programmers using a high-level language such as C should never encounter this exception."; 244 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "The thread tried to continue execution after a noncontinuable exception occurred."; 245 | case EXCEPTION_PRIV_INSTRUCTION: return "The thread tried to execute an instruction whose operation is not allowed in the current machine mode."; 246 | case EXCEPTION_SINGLE_STEP: return "A trace trap or other single-instruction mechanism signaled that one instruction has been executed."; 247 | case EXCEPTION_STACK_OVERFLOW: return "The thread used up its stack."; 248 | default: return "No description"; 249 | } 250 | } 251 | 252 | LONG WINAPI MyUnhandledExceptionFilter( 253 | _In_ struct _EXCEPTION_POINTERS *ExceptionInfo 254 | ) 255 | { 256 | DWORD code = ExceptionInfo->ExceptionRecord->ExceptionCode; 257 | if (enable_signal_print) 258 | TaskInfo("Caught UnhandledException %s(0x%x) %s", ExceptionCodeName(code), code, ExceptionCodeDescription(code)); 259 | 260 | // Translate from Windows Structured Exception to C signal. 261 | //C signals known in Windows: 262 | // http://msdn.microsoft.com/en-us/library/xdkz3x12(v=vs.110).aspx 263 | //#define SIGINT 2 /* interrupt */ 264 | //#define SIGILL 4 /* illegal instruction - invalid function image */ 265 | //#define SIGFPE 8 /* floating point exception */ 266 | //#define SIGSEGV 11 /* segment violation */ 267 | //#define SIGTERM 15 /* Software termination signal from kill */ 268 | //#define SIGBREAK 21 /* Ctrl-Break sequence */ 269 | //#define SIGABRT 22 /* abnormal termination triggered by abort call */ 270 | 271 | //#define SIGABRT_COMPAT 6 /* SIGABRT compatible with other platforms, same as SIGABRT */ 272 | 273 | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa363082(v=vs.85).aspx 274 | int sig=0; 275 | switch (ExceptionInfo->ExceptionRecord->ExceptionCode) 276 | { 277 | case EXCEPTION_ACCESS_VIOLATION: 278 | case EXCEPTION_DATATYPE_MISALIGNMENT: 279 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: 280 | case EXCEPTION_PRIV_INSTRUCTION: 281 | case EXCEPTION_IN_PAGE_ERROR: 282 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: 283 | case EXCEPTION_STACK_OVERFLOW: 284 | case EXCEPTION_INVALID_DISPOSITION: 285 | case EXCEPTION_GUARD_PAGE: 286 | case EXCEPTION_INVALID_HANDLE: 287 | #ifdef STATUS_POSSIBLE_DEADLOCK 288 | case EXCEPTION_POSSIBLE_DEADLOCK: 289 | #endif 290 | case CONTROL_C_EXIT: 291 | sig = SIGSEGV; 292 | break; 293 | case EXCEPTION_ILLEGAL_INSTRUCTION: 294 | sig = SIGILL; 295 | break; 296 | case EXCEPTION_FLT_DENORMAL_OPERAND: 297 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: 298 | case EXCEPTION_FLT_INEXACT_RESULT: 299 | case EXCEPTION_FLT_INVALID_OPERATION: 300 | case EXCEPTION_FLT_OVERFLOW: 301 | case EXCEPTION_FLT_STACK_CHECK: 302 | case EXCEPTION_FLT_UNDERFLOW: 303 | case EXCEPTION_INT_DIVIDE_BY_ZERO: 304 | case EXCEPTION_INT_OVERFLOW: 305 | sig = SIGFPE; 306 | break; 307 | case EXCEPTION_BREAKPOINT: 308 | case EXCEPTION_SINGLE_STEP: 309 | default: 310 | break; 311 | } 312 | 313 | if (sig) { 314 | fflush(stdout); 315 | fflush(stderr); 316 | 317 | if (enable_signal_print) 318 | Backtrace::malloc_free_log (); 319 | 320 | printSignalInfo(sig, false); 321 | // unreachable, printSignalInfo throws a C++ exception for SIGFPE, SIGSEGV and SIGILL 322 | } 323 | 324 | // carry on with default exception handling 325 | return EXCEPTION_CONTINUE_SEARCH; 326 | } 327 | #endif // _MSC_VER 328 | 329 | void my_terminate() { 330 | std::cerr << ("\n\n" 331 | "std::terminate was called with " + Backtrace::make_string ()) << std::endl; 332 | std::abort (); 333 | } 334 | 335 | void PrettifySegfault:: 336 | setup () 337 | { 338 | #ifndef _MSC_VER 339 | #ifndef DARWIN_NO_CARBON // not ios 340 | // subscribe to everything SIGSEGV and friends 341 | for (int i=1; i<=SIGUSR2; ++i) 342 | { 343 | switch(i) 344 | { 345 | case SIGWINCH: 346 | case SIGCHLD: 347 | break; 348 | case SIGILL: 349 | case SIGSEGV: 350 | setup_signal_survivor(i); 351 | break; 352 | default: 353 | if (0!=strcmp(SignalName::name (i),"UNKNOWN")) 354 | signal(i, handler); // install our handler 355 | break; 356 | } 357 | } 358 | #endif 359 | #else 360 | SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); 361 | #endif 362 | 363 | std::set_terminate (my_terminate); 364 | } 365 | 366 | 367 | PrettifySegfault::SignalHandlingState PrettifySegfault:: 368 | signal_handling_state() 369 | { 370 | return is_doing_signal_handling ? PrettifySegfault::doing_signal_handling : PrettifySegfault::normal_execution; 371 | } 372 | 373 | 374 | void PrettifySegfault:: 375 | EnableDirectPrint(bool enable) 376 | { 377 | enable_signal_print = enable; 378 | } 379 | 380 | 381 | #include "detectgdb.h" 382 | 383 | 384 | #if BOOST_CLANG 385 | #pragma clang diagnostic push 386 | #pragma clang diagnostic ignored "-Wnull-dereference" 387 | #pragma clang diagnostic ignored "-Wself-assign" 388 | #endif 389 | 390 | void throw_catchable_segfault_exception() 391 | { 392 | *(int*) NULL = 0; 393 | throw std::exception(); 394 | } 395 | 396 | 397 | void throw_catchable_segfault_exception2() 398 | { 399 | std::vector >(); // create and destroy a complex type in the same scope 400 | int a[100]; 401 | memset(a, 0, sizeof(a)); 402 | a[0] = a[1] + 10; 403 | 404 | *(int*) NULL = 0; 405 | 406 | printf("never reached %d\n", a[0]); 407 | } 408 | 409 | 410 | void throw_catchable_segfault_exception3() 411 | { 412 | *(int*) NULL = 0; 413 | } 414 | 415 | 416 | void throw_catchable_segfault_exception4() 417 | { 418 | int a=0; 419 | a=a; 420 | *(int*) NULL = 0; 421 | } 422 | 423 | 424 | class breaks_RAII_assumptions { 425 | public: 426 | breaks_RAII_assumptions() { 427 | constructor_was_called = true; 428 | } 429 | ~breaks_RAII_assumptions() { 430 | destructor_was_called = true; 431 | } 432 | 433 | static bool constructor_was_called; 434 | static bool destructor_was_called; 435 | }; 436 | 437 | bool breaks_RAII_assumptions::constructor_was_called = false; 438 | bool breaks_RAII_assumptions::destructor_was_called = false; 439 | 440 | void throw_catchable_segfault_exception5() 441 | { 442 | breaks_RAII_assumptions tst; 443 | std::vector > leaking_memory(10); // the destructor will never be called 444 | *(int*) NULL = 0; 445 | } 446 | 447 | 448 | 449 | #if BOOST_CLANG 450 | #pragma clang diagnostic pop 451 | #endif 452 | 453 | void throw_catchable_segfault_exception_noinline(); 454 | void throw_catchable_segfault_exception2_noinline(); 455 | void throw_catchable_segfault_exception3_noinline(); 456 | void throw_catchable_segfault_exception4_noinline(); 457 | void throw_catchable_segfault_exception5_noinline(); 458 | 459 | 460 | void PrettifySegfault:: 461 | test() 462 | { 463 | // Skip test if running through gdb 464 | if (DetectGdb::is_running_through_gdb() || DetectGdb::was_started_through_gdb ()) { 465 | TaskInfo("Running through debugger, skipping PrettifySegfault test"); 466 | return; 467 | } 468 | 469 | // It should attempt to capture any null-pointer exception (SIGSEGV and 470 | // SIGILL) in the program, log a backtrace, and then throw a regular C++ 471 | // exception from the location causing the signal. 472 | { 473 | enable_signal_print = false; 474 | 475 | // In order for the EXPECT_EXCEPTION macro to catch the exception the call 476 | // must not be inlined as the function causing the signal will first return and 477 | // then throw the exception. 478 | #ifdef _DEBUG 479 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception()); 480 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception2()); 481 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception3()); 482 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception4()); 483 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception5()); 484 | #else 485 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception_noinline()); 486 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception2_noinline()); 487 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception3_noinline()); 488 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception4_noinline()); 489 | EXPECT_EXCEPTION(segfault_sigill_exception, throw_catchable_segfault_exception5_noinline()); 490 | #endif 491 | 492 | enable_signal_print = true; 493 | } 494 | 495 | // It returns immediately from the signalling function without unwinding 496 | // that scope and thus break the RAII assumption that a destructor will 497 | // always be called (does not apply in windows) 498 | { 499 | EXCEPTION_ASSERT(breaks_RAII_assumptions::constructor_was_called); 500 | #ifndef _MSC_VER 501 | EXCEPTION_ASSERT(!breaks_RAII_assumptions::destructor_was_called); 502 | breaks_RAII_assumptions(); // This calls the destructor 503 | #endif 504 | EXCEPTION_ASSERT(breaks_RAII_assumptions::destructor_was_called); 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /prettifysegfault.h: -------------------------------------------------------------------------------- 1 | #ifndef PRETTIFYSEGFAULT_H 2 | #define PRETTIFYSEGFAULT_H 3 | 4 | #include 5 | 6 | /** 7 | * @brief The PrettifySegfault class should attempt to capture any null-pointer 8 | * exception (SIGSEGV and SIGILL) in the program, log a backtrace, and then 9 | * throw a regular C++ exception from the function causing the signal. 10 | * 11 | * When you attempt to recover from segfaults, 12 | * you are playing with fire. 13 | * 14 | * Once a segfault has been detected without a crash, 15 | * you should restart the process. It is likely that a segfault 16 | * will still crash the process, but there should at least be 17 | * some info in the log. 18 | * 19 | * While at it, PrettifySegfault::setup() sets up logging of all other signal 20 | * types as well if they are detected, but without taking any further action. 21 | * (except SIGCHLD which is ignored) 22 | * 23 | * 24 | * Windows specific 25 | * Backtraces works in 64-bit windows on 64-bit builds but not on 32-bit bulids (WOW64). 26 | * 27 | * 28 | * GCC/CLANG specific information below 29 | * (these concerns does not apply to MSC where the implementation is based on SEH) 30 | * 31 | * Throwing from within a signal handler is undefined behavior. This 32 | * implementation is based on a hack to rewrite the function stack. Please 33 | * refer to 'feepingcreature' for the full explanation at 34 | * http://feepingcreature.github.io/handling.html 35 | * 36 | * However, this doesn't always work. See note number 4 at the url above. And 37 | * it returns immediately from the signalling function without unwinding 38 | * the scope and thus break the RAII assumption that a destructor will 39 | * always be called. I.e leaking resources and potentially leaving the 40 | * process in an unconsistent state (and this is in addition to any harm that 41 | * happened before the original SIGSEGV/SIGILL was detected). 42 | * 43 | * So don't rely on this class, it's not a safety net, it merely serves to 44 | * quickly indicate the location of a severe error when it occurs. 45 | */ 46 | class PrettifySegfault 47 | { 48 | public: 49 | enum SignalHandlingState { 50 | normal_execution, 51 | doing_signal_handling 52 | }; 53 | 54 | /** 55 | * @brief setup enables PrettifySegfault. 56 | */ 57 | static void setup(); 58 | 59 | /** 60 | * @return If the process is in the state of signal handling you 61 | * should proceed to exit the process. 62 | */ 63 | static SignalHandlingState signal_handling_state (); 64 | static bool has_caught_any_signal (); 65 | 66 | /** 67 | * @brief PrettifySegfaultDirectPrint makes the signal handler write info 68 | * to stdout as soon as the signal is caught. Default enable=true. 69 | * @param enable 70 | */ 71 | static void EnableDirectPrint(bool enable); 72 | 73 | /** 74 | * @brief test will cause a segfault. This will put the process in 75 | * signal_handling_state and prevent further signals from being caught. 76 | * Such signals will instead halt the process. 77 | */ 78 | static void test(); 79 | }; 80 | 81 | 82 | class signal_exception: virtual public boost::exception, virtual public std::exception { 83 | public: 84 | typedef boost::error_info signal; 85 | typedef boost::error_info signalname; 86 | typedef boost::error_info signaldesc; 87 | }; 88 | class segfault_sigill_exception: public signal_exception {}; 89 | 90 | #endif // PRETTIFYSEGFAULT_H 91 | -------------------------------------------------------------------------------- /prettifysegfaultnoinline.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | void throw_catchable_segfault_exception(); 4 | void throw_catchable_segfault_exception2(); 5 | void throw_catchable_segfault_exception3(); 6 | void throw_catchable_segfault_exception4(); 7 | void throw_catchable_segfault_exception5(); 8 | 9 | // Placing a function in a different compilation unit usually prevents inlining 10 | 11 | void throw_catchable_segfault_exception_noinline() 12 | { 13 | throw_catchable_segfault_exception(); 14 | } 15 | 16 | void throw_catchable_segfault_exception2_noinline() 17 | { 18 | // try/catch needed here to prevent inlining in 32-bit builds on 64-bit windows (WOW64) 19 | try { 20 | throw_catchable_segfault_exception2(); 21 | } catch(...) { 22 | throw; 23 | } 24 | } 25 | 26 | void throw_catchable_segfault_exception3_noinline() 27 | { 28 | try { 29 | throw_catchable_segfault_exception3(); 30 | } catch(...) { 31 | throw; 32 | } 33 | } 34 | 35 | void throw_catchable_segfault_exception4_noinline() 36 | { 37 | try { 38 | throw_catchable_segfault_exception4(); 39 | } catch(...) { 40 | throw; 41 | } 42 | } 43 | 44 | void throw_catchable_segfault_exception5_noinline() 45 | { 46 | try { 47 | throw_catchable_segfault_exception5(); 48 | } catch(...) { 49 | throw; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /shared_state.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | This file only contains unit tests for shared_state. 3 | This file is not required for using shared_state. 4 | 5 | The tests are written so that they can be read as examples. 6 | Scroll down to void test (). 7 | */ 8 | 9 | #include "shared_state.h" 10 | #include "exceptionassert.h" 11 | #include "expectexception.h" 12 | #include "trace_perf.h" 13 | #include "barrier.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | namespace shared_state_test { 23 | 24 | class A 25 | { 26 | public: 27 | typedef shared_state ptr; 28 | typedef shared_state const_ptr; 29 | 30 | A () {} 31 | A (const A& b) { *this = b; } 32 | A& operator= (const A& b); 33 | 34 | int const_method () const { return a_; } 35 | void method (int v) { a_ = v; } 36 | 37 | int noinlinecall() const { cout << ""; return 0; } 38 | 39 | private: 40 | 41 | int a_; 42 | }; 43 | } // namespace shared_state_test 44 | 45 | template<> 46 | struct shared_state_traits: shared_state_traits_default { 47 | double timeout() { return 0.001; } 48 | }; 49 | 50 | namespace shared_state_test { 51 | 52 | class B 53 | { 54 | public: 55 | struct shared_state_traits: shared_state_traits_default { 56 | double timeout() { return 0.010; } 57 | }; 58 | 59 | typedef shared_state ptr; 60 | 61 | int work_a_lot(int i) const; 62 | }; 63 | 64 | 65 | struct C { 66 | void somework(int N) const { 67 | for (int i=0; i ptr; 90 | typedef shared_state const_ptr; 91 | }; 92 | 93 | 94 | class base { 95 | public: 96 | struct shared_state_traits: shared_state_traits_default { 97 | base* b; 98 | 99 | template 100 | void locked (T*) { 101 | EXCEPTION_ASSERT_EQUALS(++b->step, 1); 102 | } 103 | 104 | template 105 | void unlocked (T*) { 106 | ++b->step; 107 | } 108 | }; 109 | 110 | virtual ~base() {} 111 | 112 | int step = 0; 113 | }; 114 | 115 | 116 | class derivative: public base { 117 | public: 118 | void method() { 119 | EXCEPTION_ASSERT_EQUALS(++step, 2); 120 | } 121 | }; 122 | 123 | 124 | struct WriteWhileReadingThread 125 | { 126 | static void test(); 127 | }; 128 | 129 | 130 | void test () 131 | { 132 | // It should guarantee compile-time thread safe access to objects. 133 | shared_state mya {new A}; 134 | 135 | { 136 | // Lock for write access 137 | auto w = mya.write (); 138 | w->method (5); 139 | A& b = *w; 140 | b.method (5); 141 | // Unlock on out-of-scope 142 | } 143 | 144 | // Lock for a single call 145 | mya.write()->method (5); 146 | mya->method (5); 147 | 148 | { 149 | // Lock for read access 150 | auto r = mya.read (); 151 | EXCEPTION_ASSERT_EQUALS (r->const_method (), 5); 152 | const A& b = *r; 153 | EXCEPTION_ASSERT_EQUALS (b.const_method (), 5); 154 | 155 | // can't write to mya with the read_ptr from .read() 156 | // r->method (5); 157 | // error: member function 'method' not viable: 'this' argument has type 'const A', but function is not marked const 158 | 159 | // Unlock on out-of-scope 160 | } 161 | 162 | // Lock for a single call 163 | mya.read ()->const_method (); 164 | 165 | // Create a reference to a const instance 166 | shared_state consta {mya}; 167 | 168 | // Can get read-only access from a const_ptr. 169 | consta.read ()->const_method (); 170 | consta->const_method (); // <- read lock 171 | mya->const_method (); // <- write lock 172 | 173 | // Can get unprotected access without locks 174 | mya.raw ()->method (1); 175 | 176 | // Can not get write access to a const pointer. 177 | // consta.write (); // error 178 | 179 | // Can get a locked pointer 180 | mya.get ()->method (1); 181 | consta.get ()->const_method (); 182 | (*mya.write ()).method (1); 183 | 184 | // Conditional critical section, don't wait if the lock is not available 185 | if (auto w = mya.try_write ()) 186 | { 187 | w->method (5); 188 | } 189 | 190 | { 191 | // Example of bad practice 192 | // Assume 'a ()' is const and doesn't have any other side effects. 193 | 194 | int sum = 0; 195 | sum += mya.read ()->const_method (); 196 | sum += mya.read ()->const_method (); 197 | 198 | // A common assumption here would be that const_method () returns the 199 | // same result twice. But this is NOT guaranteed. Another thread might 200 | // change 'mya' with a .write() between the two calls. 201 | // 202 | // In general, using multiple .read() is a smell that you're doing it 203 | // wrong. 204 | } 205 | 206 | { 207 | // Example of good practice 208 | 209 | auto r = mya.read (); // Lock the data you need before you start using it. 210 | int sum = 0; 211 | sum += r->const_method (); 212 | sum += r->const_method (); 213 | 214 | // Assuming 'const_method ()' doesn't have any other side effects 215 | // 'mya' is guaranteed to not change between the two calls to 216 | // 'r->const_method ()'. 217 | } 218 | 219 | // The differences in the bad and good practices illustrated above is 220 | // especially important for write() that might modify an object in 221 | // several steps where snapshots of intermediate steps would describe 222 | // inconsistent states. Using inconsistent states in general results in 223 | // undefined behaviour (i.e crashes, or worse). 224 | 225 | // However, long routines might be blocking for longer than preferred. 226 | 227 | { 228 | // Good practice for long reading routines 229 | // Limit the time you need the lock by copying the data to a local 230 | // instance before you start using it. This requires a user specified 231 | // copy that ignores any values of shared_state. 232 | 233 | const A mylocal_a = *mya.read (); 234 | int sum = 0; 235 | sum += mylocal_a.const_method (); 236 | sum += mylocal_a.const_method (); 237 | 238 | // As 'mylocal_a' is not even known of to any other threads this will 239 | // surely behave as expected. 240 | } 241 | 242 | { 243 | // Good practice for long writing routines 244 | // Assuming you only have one producer of data (with one or multiple 245 | // consumers). This requires user specified assignment that ignores 246 | // any values of shared_state. 247 | 248 | A mylocal_a = *mya.read (); // Produce a writable copy 249 | mylocal_a.method (5); 250 | *mya.write () = mylocal_a; 251 | 252 | // This will not be as easy with multiple producers as you need to 253 | // manage merging. 254 | // 255 | // The easiest solution for multiple producers is the version proposed 256 | // in the beginning were the write_ptr from .write() is kept throughout 257 | // the scope of the work. 258 | } 259 | 260 | 261 | // An explanation of inline locks, or one-line locks, or locks for a single call. 262 | // 263 | // One-line locks are kept until the complete statement has been executed. 264 | // The destructor of write_ptr releases the lock when the instance goes 265 | // out-of-scope. Because the scope in which write_ptr is created is a 266 | // statement, the lock is released after the entire statement has finished 267 | // executing. 268 | // 269 | // So this example would create a deadlock. 270 | // int deadlock = mya->write ()->a () + mya->write ()->a (); 271 | // 272 | // The following statement may ALSO create a deadlock if another thread 273 | // requests a write() after the first read() has returned but before the 274 | // second read() (because read_ptr from the first read() doesn't go out of 275 | // scope and release its lock until the entire statement is finished): 276 | // int potential_deadlock = mya->read ()->a () + mya->read ()->a (); 277 | // 278 | // 279 | // Rule of thumb; avoid locking more than one object at a time, and never 280 | // lock the same object more than once at a time. 281 | WriteWhileReadingThread::test (); 282 | 283 | // It should be accessible from various pointer types 284 | { 285 | const A::ptr mya1{new A}; 286 | mya1.read (); 287 | // mya1.write (); // Can't write to a const shared_state 288 | 289 | A::const_ptr mya2{new A}; 290 | mya2.read (); 291 | 292 | A::ptr{new A}.read (); 293 | A::ptr{new A}.write (); 294 | } 295 | 296 | // shared_state can be used in a sorted container. 297 | { 298 | map mymap; 299 | mymap.find (A::ptr{}); 300 | } 301 | 302 | // shared_state should cause an overhead of less than 0.3 microseconds in a 303 | // 'release' build. 304 | { 305 | int N = 10000; 306 | 307 | shared_ptr a {new A}; 308 | TRACE_PERF ("shared_state should cause a low overhead : reference"); 309 | for (int i=0; inoinlinecall (); 311 | 312 | A::ptr a2 {new A}; 313 | trace_perf_.reset ("shared_state should cause a low write overhead"); 314 | for (int i=0; inoinlinecall (); 316 | 317 | trace_perf_.reset ("shared_state should cause a low read overhead"); 318 | for (int i=0; inoinlinecall (); 320 | } 321 | 322 | // shared_state should cause an overhead of less than 0.1 microseconds in a 323 | // 'release' build when using 'no_lock_failed'. 324 | // 325 | // shared_state should fail fast when using 'no_lock_failed', within 0.1 326 | // microseconds in a 'release' build. 327 | { 328 | int N = 10000; 329 | with_timeout_0::ptr a{new with_timeout_0}; 330 | with_timeout_0::const_ptr consta{a}; 331 | 332 | // Make subsequent lock attempts fail 333 | with_timeout_0::ptr::write_ptr r = a.write (); 334 | 335 | TRACE_PERF ("shared_state should fail fast with try_write"); 336 | for (int i=0; i b(new derivative); 356 | b.traits ()->b = b.raw (); 357 | dynamic_cast(b.write ().get ())->method (); 358 | EXCEPTION_ASSERT_EQUALS(b.raw ()->step, 3); 359 | 360 | condition_variable_any cond; 361 | future f = async(launch::async, [&](){ 362 | auto w = mya.write (); 363 | w->method (1); 364 | // wait unlocks w before waiting 365 | cond.wait(w); 366 | // w is locked again when wait return 367 | w->method (2); 368 | }); 369 | 370 | while (future_status::timeout == f.wait_for(std::chrono::milliseconds(1))) 371 | { 372 | mya->method (3); 373 | cond.notify_one (); 374 | } 375 | 376 | EXCEPTION_ASSERT_EQUALS(mya->const_method(), 2); 377 | 378 | auto rlock = mya.read (); 379 | auto rlock2 = std::move(rlock); // Move lock 380 | rlock2.swap (rlock); // Swap lock 381 | rlock.unlock (); 382 | 383 | auto wlock = mya.write (); 384 | auto wlock2 = std::move(wlock); // Move lock 385 | wlock2.swap (wlock); // Swap lock 386 | wlock.unlock (); 387 | } 388 | 389 | 390 | A& A:: 391 | operator= (const A& b) 392 | { 393 | a_ = b.a_; 394 | return *this; 395 | } 396 | 397 | 398 | int B:: 399 | work_a_lot(int /*i*/) const 400 | { 401 | this_thread::sleep_for (chrono::milliseconds{5}); 402 | return 0; 403 | } 404 | 405 | 406 | void readTwice(B::ptr b) { 407 | // int i = b.read()->work_a_lot(1) + b.read()->work_a_lot(2); 408 | // faster than default timeout 409 | int i = b.read ()->work_a_lot(3) 410 | + b.read ()->work_a_lot(4); 411 | (void) i; 412 | } 413 | 414 | 415 | void writeTwice(B::ptr b) { 416 | // int i = b.write()->work_a_lot(3) + b.write()->work_a_lot(4); 417 | // faster than default timeout 418 | int i = b.write ()->work_a_lot(1) 419 | + b.write ()->work_a_lot(2); 420 | (void) i; 421 | } 422 | 423 | 424 | void WriteWhileReadingThread:: 425 | test() 426 | { 427 | // It should detect deadlocks from recursive locks 428 | { 429 | B::ptr b{new B}; 430 | 431 | #ifndef SHARED_STATE_NO_TIMEOUT 432 | // can't lock for write twice (recursive locks) 433 | EXPECT_EXCEPTION(B::ptr::lock_failed, writeTwice(b)); 434 | EXPECT_EXCEPTION(lock_failed, writeTwice(b)); 435 | #endif 436 | 437 | #ifndef SHARED_STATE_NO_SHARED_MUTEX 438 | // may be able to lock for read twice if no other thread locks for write in-between, but it is not guaranteed 439 | readTwice(b); 440 | #endif 441 | 442 | #ifndef SHARED_STATE_NO_TIMEOUT 443 | // can't lock for read twice if another thread request a write in the middle 444 | // that write request will fail and then this will succeed 445 | 446 | // (this situation requires that lock-waits occur in a very specific order, 447 | // the barrier here together with the sleep in the main thread makes this 448 | // likely enough that it can be tested) 449 | spinning_barrier barrier(2); 450 | future f = async(launch::async, [&](){ 451 | // Make sure readTwice starts before this function 452 | barrier.wait (); 453 | // Write access should fail as the first thread attempts recursive locks 454 | // through multiple calls to read (). 455 | EXPECT_EXCEPTION(lock_failed, b.write (); ); 456 | }); 457 | 458 | { 459 | auto r1 = b.read (); 460 | barrier.wait (); 461 | 462 | // make the 'f' thread start waiting for a lock before this thread. 463 | this_thread::sleep_for (chrono::milliseconds{3}); 464 | 465 | #ifndef SHARED_STATE_NO_SHARED_MUTEX 466 | auto r2 = b.read (); 467 | #else 468 | EXPECT_EXCEPTION(lock_failed, b.read () ); 469 | #endif 470 | } 471 | 472 | f.get (); 473 | #endif 474 | } 475 | 476 | 477 | // it should handle lock contention efficiently 478 | // 'Mv' decides how long a lock should be kept 479 | std::vector Mv{100, 1000}; 480 | for (unsigned l=0; l W{1, 10, 100, 1000}; 487 | 488 | for (unsigned k=0; k> workers(8); 504 | 505 | shared_state c {new C}; 506 | 507 | for (unsigned i=0; isomework (M); 512 | else 513 | c.write ()->somework (M); 514 | }); 515 | 516 | for (unsigned i=0; i> workers(8); 524 | 525 | shared_ptr c {new C}; 526 | 527 | for (unsigned i=0; isomework (M); 531 | }); 532 | 533 | for (unsigned i=0; i> workers(8); 541 | 542 | // C2 doesn't have shared read-only access, so a read and a write lock are equivalent. 543 | shared_state c2 {new C2}; 544 | 545 | for (unsigned i=0; isomework (M); 549 | }); 550 | 551 | for (unsigned i=0; i a {new A}; 21 | * ... 22 | * a->foo (); // Mutually exclusive write access 23 | * 24 | * 25 | * Using shared_state 26 | * ------------------ 27 | * Use shared_state to ensure thread-safe access to otherwise unprotected data 28 | * of class MyType by wrapping all 'MyType* p = new MyType' calls as: 29 | * 30 | * shared_state p {new MyType}; 31 | * 32 | * 33 | * There are a couple of ways to access the data in 'p'. Call "p.write()" to 34 | * enter a critical section for read and write access. The critical section is 35 | * thread-safe and exception-safe through a mutex lock and RAII. p.write() can 36 | * be used either implicitly in a single function call: 37 | * 38 | * p->... 39 | * 40 | * Equivalent to: 41 | * 42 | * p.write ()->... 43 | * 44 | * Or to enter a critical section to perform a transaction over multiple method 45 | * calls: 46 | * 47 | * { 48 | * auto w = p.write (); 49 | * w->... 50 | * w->... 51 | * } 52 | * 53 | * Enter a critical section only if the lock is readily available: 54 | * 55 | * if (auto w = p.try_write ()) 56 | * { 57 | * w->... 58 | * w->... 59 | * } 60 | * 61 | * Like-wise 'p.read()' or 'p.try_read()' creates a critical section with 62 | * shared read-only access. An excepion is thrown if a lock couldn't be 63 | * obtained within a given timeout. The timeout is set, or disabled, by 64 | * shared_state_traits: 65 | * 66 | * try { 67 | * p.write ()->... 68 | * } catch (lock_failed) { 69 | * ... 70 | * } 71 | * 72 | * You can also discard the thread safety and get unprotected access to a 73 | * mutable state: 74 | * 75 | * { 76 | * MyType* m = p.raw (); 77 | * m->... 78 | * m->... 79 | * } 80 | * 81 | * For more complete examples (that actually compile) see 82 | * shared_state_test::test () in shared_state.cpp. 83 | * 84 | * 85 | * Performance and overhead 86 | * ------------------------ 87 | * shared_state should cause an overhead of less than 0.1 microseconds in a 88 | * 'release' build when using 'try_write' or 'try_read'. 89 | * 90 | * shared_state should fail within 0.1 microseconds in a 'release' build when 91 | * using 'try_write' or 'try_read' on a busy lock. 92 | * 93 | * shared_state should cause an overhead of less than 0.3 microseconds in a 94 | * 'release' build when using 'write' or 'read'. 95 | * 96 | * 97 | * Configuring timeouts and extending functionality 98 | * ------------------------------------------------ 99 | * It is possible to use different timeouts, or disable timeouts, for different 100 | * types. Create a template specialization of shared_state_traits to override 101 | * the defaults. Alternatively you can also create an internal class called 102 | * shared_state_traits within your type. See 'shared_state_traits_default' for 103 | * more details. 104 | * 105 | * 106 | * Author: johan.b.gustafsson@gmail.com 107 | */ 108 | 109 | #ifndef SHARED_STATE_H 110 | #define SHARED_STATE_H 111 | 112 | template 113 | class shared_state; 114 | 115 | #include "shared_state_mutex.h" 116 | 117 | class lock_failed: public virtual std::exception {}; 118 | 119 | 120 | struct shared_state_traits_default { 121 | /** 122 | If 'timeout >= 0' read_ptr/write_ptr will try to lock until the timeout 123 | has passed and then throw a lock_failed exception. If 'timeout < 0' 124 | they will block indefinitely until the lock becomes available. 125 | 126 | Define SHARED_STATE_NO_TIMEOUT to disable timeouts altogether, all lock 127 | attempts will then block indefinitely until the lock becomes available. 128 | 129 | timeout() must be reentrant, i.e thread-safe without the support of 130 | shared_state. 131 | */ 132 | double timeout () { return 0.100; } 133 | 134 | /** 135 | If 'timeout_failed' does not throw the attempted read_ptr or write_ptr will 136 | become a null-pointer. 137 | */ 138 | template 139 | void timeout_failed (T*) { 140 | throw typename shared_state::lock_failed {}; 141 | } 142 | 143 | /** 144 | 'locked' and 'unlocked' are called right after the mutex for this is 145 | instance is locked or unlocked, respectively. Regardless if the lock 146 | is a read-only lock or a read-and-write lock. 147 | */ 148 | template 149 | void locked (T*) {} 150 | 151 | template 152 | void unlocked (T*) {} 153 | 154 | /** 155 | * @brief shared_state_mutex defines which mutex shared_state will use to 156 | * protect this type. The default is to use a mutex with support for shared 157 | * read access but exclusive write access. Depending on your usage pattern 158 | * it might be better to use a simpler type. 159 | * 160 | * See shared_state_mutex.h for possible implementations. 161 | */ 162 | typedef ::shared_state_mutex shared_state_mutex; 163 | 164 | /** 165 | * @brief enable_implicit_lock defines if the -> operator is enough to 166 | * obtain a lock. Otherwise, explicit .write() and .read() are needed. 167 | */ 168 | struct enable_implicit_lock : std::true_type {}; 169 | }; 170 | 171 | template 172 | struct shared_state_traits: public shared_state_traits_default {}; 173 | 174 | template 175 | struct shared_state_details_helper { 176 | // The first template function "test" is used to detect if 177 | // "typename C::shared_state_traits" exists. 178 | // SFINAE skips this declaration if the subtype does not exist and instead 179 | // lets the second template declare "test". 180 | template 181 | static typename C::shared_state_traits test(typename C::shared_state_traits*); 182 | 183 | template // worst match 184 | static shared_state_traits test(...); 185 | 186 | // shared_state_details_helper::type represents the return type of the 187 | // template function "test" 188 | typedef decltype(test(0)) type; 189 | }; 190 | 191 | template 192 | struct shared_state_details: public shared_state_details_helper::type { 193 | shared_state_details(T*p) : p(p) {} 194 | shared_state_details(shared_state_details const&) = delete; 195 | shared_state_details& operator=(shared_state_details const&) = delete; 196 | ~shared_state_details() { delete p; } 197 | 198 | typedef typename shared_state_details_helper::type::shared_state_mutex shared_state_mutex; 199 | mutable shared_state_mutex lock; 200 | 201 | T* const p; 202 | }; 203 | 204 | 205 | // compare with std::enable_if 206 | template struct disable_if {}; 207 | template struct disable_if {typedef T type;}; 208 | 209 | 210 | template 211 | class shared_state final 212 | { 213 | private: 214 | typedef shared_state_details::type> details; 215 | 216 | public: 217 | typedef typename std::remove_const::type element_type; 218 | 219 | class lock_failed: public ::lock_failed {}; 220 | 221 | shared_state () {} 222 | 223 | template::value>::type> 225 | explicit shared_state ( Y* p ) 226 | { 227 | reset(p); 228 | } 229 | 230 | template::value>::type> 232 | shared_state(const shared_state& a) 233 | { 234 | d = a.d; 235 | } 236 | 237 | void reset() { 238 | d.reset (); 239 | } 240 | 241 | template::value>::type> 243 | void reset( Y* yp ) { 244 | d.reset (new details(yp)); 245 | } 246 | 247 | class weak_ptr { 248 | public: 249 | weak_ptr() {} 250 | weak_ptr(const shared_state& t) : d(t.d) {} 251 | 252 | shared_state lock() const { 253 | return shared_state{d.lock ()}; 254 | } 255 | 256 | private: 257 | std::weak_ptr
d; 258 | }; 259 | 260 | /** 261 | * For examples of usage see void shared_state_test::test (). 262 | * 263 | * The purpose of read_ptr is to provide thread-safe access to an a const 264 | * object for a thread during the lifetime of the read_ptr. This access 265 | * may be shared by multiple threads that simultaneously use their own 266 | * read_ptr to access the same object. 267 | * 268 | * The accessors without no_lock_failed always returns an accessible 269 | * instance, never null. If a lock fails a lock_failed exception is thrown. 270 | * 271 | * @see void shared_state_test::test () 272 | * @see class shared_state 273 | * @see class write_ptr 274 | */ 275 | class read_ptr { 276 | public: 277 | read_ptr() : l(0), p(0) {} 278 | 279 | read_ptr(read_ptr&& b) 280 | : read_ptr() 281 | { 282 | swap(b); 283 | } 284 | 285 | read_ptr(const read_ptr&) = delete; 286 | read_ptr& operator=(read_ptr const&) = delete; 287 | 288 | ~read_ptr() throw(...) { 289 | unlock (); 290 | } 291 | 292 | #ifdef _DEBUG 293 | const T* operator-> () const { assert(p); return p; } 294 | const T& operator* () const { assert(p); return *p; } 295 | #else 296 | const T* operator-> () const { return p; } 297 | const T& operator* () const { return *p; } 298 | #endif 299 | const T* get () const { return p; } 300 | explicit operator bool() const { return (bool)p; } 301 | 302 | void lock() { 303 | // l is not locked, but timeout is required to be reentrant 304 | double timeout = d->timeout(); 305 | 306 | // try_lock_shared_for and lock_shared are unnecessarily complex if 307 | // the lock is available right away 308 | if (l->try_lock_shared ()) 309 | { 310 | // Got lock 311 | } 312 | else if (timeout < 0) 313 | { 314 | l->lock_shared (); 315 | // Got lock 316 | } 317 | else if (l->try_lock_shared_for (shared_state_chrono::duration{timeout})) 318 | { 319 | // Got lock 320 | } 321 | else 322 | { 323 | d->template timeout_failed (d->p); 324 | // timeout_failed is expected to throw. But if it doesn't, 325 | // make this behave as a null pointer 326 | return; 327 | } 328 | 329 | p = d->p; 330 | 331 | try { 332 | d->locked (p); 333 | } catch (...) { 334 | p = 0; 335 | l->unlock_shared (); 336 | throw; 337 | } 338 | } 339 | 340 | void unlock() { 341 | if (p) 342 | { 343 | const T* q = p; 344 | p = 0; 345 | l->unlock_shared (); 346 | d->unlocked (q); 347 | } 348 | } 349 | 350 | void swap(read_ptr& b) { 351 | std::swap(l, b.l); 352 | std::swap(d, b.d); 353 | std::swap(p, b.p); 354 | } 355 | 356 | private: 357 | friend class shared_state; 358 | 359 | explicit read_ptr (const shared_state& vp) 360 | : l (&vp.d->lock), 361 | d (vp.d), 362 | p (0) 363 | { 364 | lock (); 365 | } 366 | 367 | read_ptr (const shared_state& vp, bool) 368 | : l (&vp.d->lock), 369 | d (vp.d), 370 | p (0) 371 | { 372 | if (l->try_lock_shared ()) { 373 | p = d->p; 374 | d->locked (p); 375 | } 376 | } 377 | 378 | typename details::shared_state_mutex* l; 379 | std::shared_ptr
d; 380 | const element_type* p; 381 | }; 382 | 383 | 384 | /** 385 | * For examples of usage see void shared_state_test::test (). 386 | * 387 | * The purpose of write_ptr is to provide exclusive access to an object for 388 | * a single thread during the lifetime of the write_ptr. 389 | * 390 | * @see class read_ptr 391 | */ 392 | class write_ptr { 393 | public: 394 | write_ptr() : l(0), p(0) {} 395 | 396 | write_ptr(write_ptr&& b) 397 | : write_ptr() 398 | { 399 | swap(b); 400 | } 401 | 402 | write_ptr(const write_ptr&) = delete; 403 | write_ptr& operator=(write_ptr const&) = delete; 404 | 405 | ~write_ptr() throw(...) { 406 | unlock (); 407 | } 408 | 409 | #ifdef _DEBUG 410 | T* operator-> () const { assert(p); return p; } 411 | T& operator* () const { assert(p); return *p; } 412 | #else 413 | T* operator-> () const { return p; } 414 | T& operator* () const { return *p; } 415 | #endif 416 | T* get () const { return p; } 417 | explicit operator bool() const { return (bool)p; } 418 | 419 | // See read_ptr::lock 420 | void lock() { 421 | double timeout = d->timeout(); 422 | 423 | if (l->try_lock()) 424 | { 425 | } 426 | else if (timeout < 0) 427 | { 428 | l->lock (); 429 | } 430 | else if (l->try_lock_for (shared_state_chrono::duration{timeout})) 431 | { 432 | } 433 | else 434 | { 435 | d->timeout_failed(d->p); 436 | return; 437 | } 438 | 439 | p = d->p; 440 | 441 | try { 442 | d->locked (p); 443 | } catch (...) { 444 | p = 0; 445 | l->unlock (); 446 | throw; 447 | } 448 | } 449 | 450 | void unlock() { 451 | if (p) 452 | { 453 | T* q = p; 454 | p = 0; 455 | l->unlock (); 456 | d->unlocked (q); 457 | } 458 | } 459 | 460 | void swap(write_ptr& b) { 461 | std::swap(l, b.l); 462 | std::swap(d, b.d); 463 | std::swap(p, b.p); 464 | } 465 | 466 | private: 467 | friend class shared_state; 468 | 469 | template::value>::type> 470 | explicit write_ptr (const shared_state& vp) 471 | : l (&vp.d->lock), 472 | d (vp.d), 473 | p (0) 474 | { 475 | lock (); 476 | } 477 | 478 | template::value>::type> 479 | write_ptr (const shared_state& vp, bool) 480 | : l (&vp.d->lock), 481 | d (vp.d), 482 | p (0) 483 | { 484 | if (l->try_lock ()) { 485 | p = d->p; 486 | d->locked (p); 487 | } 488 | } 489 | 490 | typename details::shared_state_mutex* l; 491 | std::shared_ptr
d; 492 | T* p; 493 | }; 494 | 495 | 496 | /** 497 | * @brief read provides thread safe read-only access. 498 | */ 499 | read_ptr read() const { return read_ptr(*this); } 500 | 501 | /** 502 | * @brief write provides thread safe read and write access. Not accessible 503 | * if T is const. 504 | */ 505 | write_ptr write() const { return write_ptr(*this); } 506 | 507 | /** 508 | * @brief try_read obtains the lock only if it is readily available. 509 | * 510 | * If the lock was not obtained it doesn't throw any exception, but the 511 | * accessors return null pointers. This function fails much faster (about 512 | * 30x faster) than setting timeout=0 and discarding any lock_failed. 513 | */ 514 | read_ptr try_read() const { return read_ptr(*this, bool()); } 515 | 516 | /** 517 | * @brief try_write. See try_read. 518 | */ 519 | write_ptr try_write() const { return write_ptr(*this, bool()); } 520 | 521 | /** 522 | * @brief mutex returns the mutex object for this instance. 523 | */ 524 | typename details::shared_state_mutex& mutex() const { return d->lock; } 525 | 526 | /** 527 | * @brief raw gives direct access to the unprotected state. The client is 528 | * responsible for using other synchornization mechanisms, consider using 529 | * read() or write() instead. 530 | */ 531 | T* raw() const { return d ? d->p : nullptr; } 532 | 533 | /** 534 | * @brief traits provides unprotected access to the instance of 535 | * shared_state_traits used for this type. 536 | * The shared_state is not released as long as the shared_ptr return from 537 | * traits is alive. 538 | */ 539 | std::shared_ptr::type>::type> traits() const { return d; } 540 | 541 | explicit operator bool() const { return (bool)d; } 542 | bool operator== (const shared_state& b) const { return d == b.d; } 543 | bool operator!= (const shared_state& b) const { return !(*this == b); } 544 | bool operator < (const shared_state& b) const { return this->d < b.d; } 545 | 546 | #ifndef SHARED_STATE_DISABLE_IMPLICIT_LOCK 547 | static write_ptr lock_type_test(element_type*); 548 | static read_ptr lock_type_test(element_type const*); 549 | typedef decltype(lock_type_test((T*)0)) lock_type; 550 | 551 | lock_type operator-> () const { 552 | typedef typename std::enable_if ::type method_is_disabled_for_this_type; 553 | return lock_type(*this); } 554 | lock_type get () const { 555 | typedef typename std::enable_if ::type method_is_disabled_for_this_type; 556 | return lock_type(*this); } 557 | #endif 558 | 559 | bool unique() const { return d.unique (); } 560 | 561 | void swap(shared_state& b) { 562 | std::swap(d, b.d); 563 | } 564 | 565 | private: 566 | template 567 | friend class shared_state; 568 | 569 | shared_state ( std::shared_ptr
d ) : d(d) {} 570 | 571 | std::shared_ptr
d; 572 | }; 573 | 574 | 575 | template 576 | inline void swap(typename shared_state::read_ptr& a, typename shared_state::read_ptr& b) 577 | { 578 | a.swap(b); 579 | } 580 | 581 | 582 | template 583 | inline void swap(typename shared_state::write_ptr& a, typename shared_state::write_ptr& b) 584 | { 585 | a.swap(b); 586 | } 587 | 588 | 589 | namespace shared_state_test { 590 | void test (); 591 | } 592 | 593 | #endif // SHARED_STATE_H 594 | -------------------------------------------------------------------------------- /shared_state.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustafsson/backtrace/7f2ae53f6ecfe8718f30bee64886ccf103ec537a/shared_state.pdf -------------------------------------------------------------------------------- /shared_state_mutex.h: -------------------------------------------------------------------------------- 1 | #ifndef SHARED_STATE_MUTEX_H 2 | #define SHARED_STATE_MUTEX_H 3 | 4 | #ifdef SHARED_STATE_BOOST_MUTEX 5 | #include 6 | 7 | namespace shared_state_chrono = boost::chrono; 8 | 9 | class shared_state_mutex_notimeout_noshared: public boost::mutex { 10 | public: 11 | // recursive read locks are allowed to dead-lock so it is valid to replace a shared_timed_mutex with a mutex 12 | void lock_shared() { lock(); } 13 | bool try_lock_shared() { return try_lock(); } 14 | void unlock_shared() { unlock(); } 15 | 16 | // Discard any timeout parameters 17 | bool try_lock_for(...) { lock(); return true; } 18 | bool try_lock_shared_for(...) { lock_shared(); return true; } 19 | }; 20 | 21 | class shared_state_mutex_notimeout: public boost::shared_mutex { 22 | public: 23 | // Discard any timeout parameters 24 | bool try_lock_for(...) { lock(); return true; } 25 | bool try_lock_shared_for(...) { lock_shared(); return true; } 26 | }; 27 | 28 | class shared_state_mutex_noshared: public boost::timed_mutex { 29 | public: 30 | void lock_shared() { lock(); } 31 | bool try_lock_shared() { return try_lock(); } 32 | void unlock_shared() { unlock(); } 33 | 34 | template 35 | bool try_lock_shared_for(const shared_state_chrono::duration& rel_time) { return try_lock_for(rel_time); } 36 | }; 37 | 38 | typedef boost::shared_mutex shared_state_mutex_default; 39 | #else 40 | //#include 41 | //#include // Requires C++14 42 | //namespace std { 43 | // typedef shared_mutex shared_timed_mutex; 44 | //} 45 | 46 | #include "shared_timed_mutex_polyfill.h" // Requires C++11 47 | namespace std { 48 | using namespace std_polyfill; 49 | } 50 | 51 | namespace shared_state_chrono = std::chrono; 52 | 53 | class shared_state_mutex_notimeout_noshared: public std::mutex { 54 | public: 55 | void lock_shared() { lock(); } 56 | bool try_lock_shared() { return try_lock(); } 57 | void unlock_shared() { unlock(); } 58 | 59 | bool try_lock_for(...) { lock(); return true; } 60 | bool try_lock_shared_for(...) { lock_shared(); return true; } 61 | }; 62 | 63 | class shared_state_mutex_notimeout: public std::shared_timed_mutex { 64 | public: 65 | bool try_lock_for(...) { lock(); return true; } 66 | bool try_lock_shared_for(...) { lock_shared(); return true; } 67 | }; 68 | 69 | class shared_state_mutex_noshared: public std::timed_mutex { 70 | public: 71 | void lock_shared() { lock(); } 72 | bool try_lock_shared() { return try_lock(); } 73 | void unlock_shared() { unlock(); } 74 | 75 | template 76 | bool try_lock_shared_for(const shared_state_chrono::duration& rel_time) { return try_lock_for(rel_time); } 77 | }; 78 | 79 | typedef std::shared_timed_mutex shared_state_mutex_default; 80 | #endif 81 | 82 | 83 | /** 84 | * @brief The shared_state_nomutex class is a dummy class than can be used with 85 | * shared_state_traits to disable mutex locking for a given type. This is meant 86 | * to be used temporarily during testing. 87 | */ 88 | class shared_state_nomutex { 89 | public: 90 | void lock() {} 91 | void unlock() {} 92 | void lock_shared() { lock(); } 93 | bool try_lock() { return true; } 94 | bool try_lock_shared() { return true; } 95 | void unlock_shared() { unlock(); } 96 | 97 | bool try_lock_for(...) { lock(); return true; } 98 | bool try_lock_shared_for(...) { lock_shared(); return true; } 99 | }; 100 | 101 | 102 | #if defined SHARED_STATE_NO_TIMEOUT 103 | #if defined SHARED_STATE_NO_SHARED_MUTEX 104 | typedef shared_state_mutex_notimeout_noshared shared_state_mutex; 105 | #else 106 | typedef shared_state_mutex_notimeout shared_state_mutex; 107 | #endif 108 | #elif defined SHARED_STATE_NO_SHARED_MUTEX 109 | typedef shared_state_mutex_noshared shared_state_mutex; 110 | #else 111 | typedef shared_state_mutex_default shared_state_mutex; 112 | #endif 113 | 114 | 115 | #endif // SHARED_STATE_MUTEX_H 116 | -------------------------------------------------------------------------------- /shared_state_traits_backtrace.cpp: -------------------------------------------------------------------------------- 1 | #include "shared_state_traits_backtrace.h" 2 | #include "barrier.h" 3 | #include "exceptionassert.h" 4 | #include "trace_perf.h" 5 | #include "demangle.h" 6 | #include "tasktimer.h" 7 | #include "detectgdb.h" 8 | 9 | #include 10 | 11 | using namespace std; 12 | 13 | std::function shared_state_traits_backtrace::default_warning = 14 | [](double T, double V, void*, const std::type_info& i) 15 | { 16 | if (!DetectGdb::was_started_through_gdb ()) 17 | { 18 | auto bt = Backtrace::make (); 19 | std::string tn = demangle(i); 20 | 21 | std::async(std::launch::async, [T, V, tn, bt]{ 22 | TaskInfo(boost::format("!!! Warning: Lock of %s was held for %s > %s. %s") % 23 | tn % TaskTimer::timeToString (T) % TaskTimer::timeToString (V) % bt.value ().to_string ()); 24 | }); 25 | } 26 | }; 27 | 28 | 29 | namespace shared_state_traits_backtrace_test { 30 | 31 | class A { 32 | public: 33 | struct shared_state_traits: shared_state_traits_backtrace { 34 | double timeout() override { return 0.002; } 35 | double verify_lock_time() override { return lock_time; } 36 | double lock_time = 0.001; 37 | }; 38 | }; 39 | 40 | } // namespace shared_state_traits_backtrace_test 41 | 42 | using namespace shared_state_traits_backtrace_test; 43 | 44 | void shared_state_traits_backtrace:: 45 | test() 46 | { 47 | #ifndef SHARED_STATE_NO_TIMEOUT 48 | // shared_state can be extended with type traits to get, for instance, 49 | // - backtraces on deadlocks from all participating threads, 50 | { 51 | typedef shared_state ptr; 52 | ptr a{new A}; 53 | ptr b{new A}; 54 | a.traits ()->lock_time = 1; 55 | b.traits ()->lock_time = 1; 56 | 57 | spinning_barrier barrier(2); 58 | 59 | std::function m = [&barrier](ptr p1, ptr p2) { 60 | try { 61 | auto w1 = p1.write (); 62 | barrier.wait (); 63 | auto w2 = p2.write (); 64 | 65 | // never reached 66 | EXCEPTION_ASSERT(false); 67 | } catch (lock_failed& x) { 68 | // cheeck that a backtrace was embedded into the lock_failed exception 69 | const Backtrace* backtrace = boost::get_error_info(x); 70 | EXCEPTION_ASSERT(backtrace); 71 | } 72 | }; 73 | 74 | // Lock a and b in opposite order in f1 and f2 75 | future f1 = async(launch::async, [&](){ m (b, a); }); 76 | future f2 = async(launch::async, [&](){ m (a, b); }); 77 | 78 | f1.get (); 79 | f2.get (); 80 | } 81 | #endif 82 | 83 | // shared_state can be extended with type traits to get, for instance, 84 | // - warnings on locks that are held too long. 85 | { 86 | shared_state a{new A}; 87 | 88 | std::string report_type; 89 | a.traits ()->exceeded_lock_time = [&report_type](float,float,void*,const std::type_info& i){ report_type = demangle (i); }; 90 | 91 | auto w = a.write (); 92 | 93 | // Wait to make VerifyExecutionTime detect that the lock was kept too long 94 | this_thread::sleep_for (chrono::milliseconds{10}); 95 | 96 | EXCEPTION_ASSERT(report_type.empty ()); 97 | w.unlock (); 98 | EXCEPTION_ASSERT_EQUALS(report_type, "shared_state_traits_backtrace_test::A"); 99 | 100 | { 101 | int N = 10000; 102 | 103 | TRACE_PERF("warnings on locks that are held too long should cause a low overhead"); 104 | for (int i=0; i 8 | #include 9 | 10 | template 11 | class lock_failed_boost 12 | : public shared_state::lock_failed 13 | , public virtual boost::exception 14 | {}; 15 | 16 | 17 | /** 18 | * @brief The shared_state_traits_backtrace struct should provide backtraces on 19 | * lock_failed exceptions. It should issue a warning if the lock is kept too long. 20 | * 21 | * class MyType { 22 | * public: 23 | * struct shared_state_traits: shared_state_traits_backtrace { 24 | * double timeout() { return 0.002; } 25 | * }; 26 | * ... 27 | * }; 28 | */ 29 | struct shared_state_traits_backtrace: shared_state_traits_default { 30 | virtual double timeout () { return shared_state_traits_default::timeout (); } 31 | virtual double verify_lock_time() { return timeout()/4.0f; } 32 | 33 | template 34 | void timeout_failed (T*) { 35 | /* 36 | When a timeout occurs on a lock, this makes an attempt to detect 37 | deadlocks. The thread with the timeout is blocked long enough (same 38 | timeout as in the failed lock attempt) for any other thread that is 39 | deadlocking with this thread to also fail its lock attempt. 40 | */ 41 | std::this_thread::sleep_for (std::chrono::duration{2*timeout()}); 42 | 43 | BOOST_THROW_EXCEPTION(lock_failed_boost() 44 | << Backtrace::make ()); 45 | } 46 | 47 | template 48 | void locked(T*) { 49 | start = std::chrono::high_resolution_clock::now (); 50 | } 51 | 52 | template 53 | void unlocked(T* t) noexcept { 54 | std::chrono::duration diff = std::chrono::high_resolution_clock::now () - start; 55 | double D = diff.count (); 56 | double V = verify_lock_time (); 57 | if (D > V && exceeded_lock_time) 58 | exceeded_lock_time (D, V, (void*)t, typeid(*t)); 59 | } 60 | 61 | static std::function default_warning; 62 | std::function exceeded_lock_time = default_warning; 63 | 64 | static void test(); 65 | 66 | private: 67 | std::chrono::high_resolution_clock::time_point start; 68 | }; 69 | 70 | #endif // SHARED_STATE_TRAITS_BACKTRACE_H 71 | -------------------------------------------------------------------------------- /shared_timed_mutex_polyfill.h: -------------------------------------------------------------------------------- 1 | #ifndef SHARED_TIMED_MUTEX_POLYFILL_H 2 | #define SHARED_TIMED_MUTEX_POLYFILL_H 3 | 4 | 5 | // Implementation of a somewhat C++14-compatible shared_timed_mutex that only 6 | // requires C++11 and the boost header library. 7 | // 8 | // 9 | // Brute derivative work of 10 | // http://www.boost.org/doc/libs/1_55_0/boost/thread/pthread/shared_mutex.hpp 11 | // 12 | // without BOOST_THREAD_PROVIDES_INTERRUPTIONS 13 | // without BOOST_THREAD_USES_DATETIME 14 | // with BOOST_THREAD_USES_CHRONO 15 | // without BOOST_THREAD_PROVIDES_SHARED_MUTEX_UPWARDS_CONVERSIONS 16 | 17 | 18 | // (C) Copyright 2006-8 Anthony Williams 19 | // (C) Copyright 2012 Vicente J. Botet Escriba 20 | // 21 | // Distributed under the Boost Software License, Version 1.0. (See 22 | // accompanying file LICENSE_1_0.txt or copy at 23 | // http://www.boost.org/LICENSE_1_0.txt) 24 | 25 | //#include 26 | //#include 27 | //#include 28 | //#include 29 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 30 | ////#include 31 | //#endif 32 | //#ifdef BOOST_THREAD_USES_CHRONO 33 | //#include 34 | //#include 35 | //#endif 36 | //#include 37 | 38 | # include 39 | # include 40 | # include 41 | # include 42 | 43 | namespace std_polyfill 44 | { 45 | class shared_timed_mutex 46 | { 47 | private: 48 | class state_data 49 | { 50 | public: 51 | state_data () : 52 | shared_count(0), 53 | exclusive(false), 54 | upgrade(false), 55 | exclusive_waiting_blocked(false) 56 | {} 57 | 58 | void assert_free() const 59 | { 60 | assert( ! exclusive ); 61 | assert( ! upgrade ); 62 | assert( shared_count==0 ); 63 | } 64 | 65 | void assert_locked() const 66 | { 67 | assert( exclusive ); 68 | assert( shared_count==0 ); 69 | assert( ! upgrade ); 70 | } 71 | 72 | void assert_lock_shared () const 73 | { 74 | assert( ! exclusive ); 75 | assert( shared_count>0 ); 76 | //assert( (! upgrade) || (shared_count>1)); 77 | // if upgraded there are at least 2 threads sharing the mutex, 78 | // except when unlock_upgrade_and_lock has decreased the number of readers but has not taken yet exclusive ownership. 79 | } 80 | 81 | void assert_lock_upgraded () const 82 | { 83 | assert( ! exclusive ); 84 | assert( upgrade ); 85 | assert( shared_count>0 ); 86 | } 87 | 88 | void assert_lock_not_upgraded () const 89 | { 90 | assert( ! upgrade ); 91 | } 92 | 93 | bool can_lock () const 94 | { 95 | return ! (shared_count || exclusive); 96 | } 97 | 98 | // void exclusive_blocked (bool blocked) 99 | // { 100 | // exclusive_waiting_blocked = blocked; 101 | // } 102 | 103 | void lock () 104 | { 105 | exclusive = true; 106 | } 107 | 108 | void unlock () 109 | { 110 | exclusive = false; 111 | exclusive_waiting_blocked = false; 112 | } 113 | 114 | bool can_lock_shared () const 115 | { 116 | return ! (exclusive || exclusive_waiting_blocked); 117 | } 118 | 119 | bool more_shared () const 120 | { 121 | return shared_count > 0 ; 122 | } 123 | unsigned get_shared_count () const 124 | { 125 | return shared_count ; 126 | } 127 | unsigned lock_shared () 128 | { 129 | return ++shared_count; 130 | } 131 | 132 | 133 | void unlock_shared () 134 | { 135 | --shared_count; 136 | } 137 | 138 | bool unlock_shared_downgrades() 139 | { 140 | if (upgrade) { 141 | upgrade=false; 142 | exclusive=true; 143 | return true; 144 | } else { 145 | exclusive_waiting_blocked=false; 146 | return false; 147 | } 148 | } 149 | 150 | void lock_upgrade () 151 | { 152 | ++shared_count; 153 | upgrade=true; 154 | } 155 | bool can_lock_upgrade () const 156 | { 157 | return ! (exclusive || exclusive_waiting_blocked || upgrade); 158 | } 159 | 160 | void unlock_upgrade () 161 | { 162 | upgrade=false; 163 | --shared_count; 164 | } 165 | 166 | //private: 167 | unsigned shared_count; 168 | bool exclusive; 169 | bool upgrade; 170 | bool exclusive_waiting_blocked; 171 | }; 172 | 173 | 174 | 175 | state_data state; 176 | std::mutex state_change; 177 | std::condition_variable shared_cond; 178 | std::condition_variable exclusive_cond; 179 | std::condition_variable upgrade_cond; 180 | 181 | void release_waiters() 182 | { 183 | exclusive_cond.notify_one(); 184 | shared_cond.notify_all(); 185 | } 186 | 187 | public: 188 | 189 | shared_timed_mutex(const shared_timed_mutex&) = delete; 190 | shared_timed_mutex& operator=(const shared_timed_mutex&) = delete; 191 | 192 | shared_timed_mutex() 193 | { 194 | } 195 | 196 | ~shared_timed_mutex() 197 | { 198 | } 199 | 200 | void lock_shared() 201 | { 202 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 203 | // std::this_thread::disable_interruption do_not_disturb; 204 | //#endif 205 | std::unique_lock lk(state_change); 206 | while(!state.can_lock_shared()) 207 | { 208 | shared_cond.wait(lk); 209 | } 210 | state.lock_shared(); 211 | } 212 | 213 | bool try_lock_shared() 214 | { 215 | std::unique_lock lk(state_change); 216 | 217 | if(!state.can_lock_shared()) 218 | { 219 | return false; 220 | } 221 | state.lock_shared(); 222 | return true; 223 | } 224 | 225 | //#if defined BOOST_THREAD_USES_DATETIME 226 | // bool timed_lock_shared(system_time const& timeout) 227 | // { 228 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 229 | // std::this_thread::disable_interruption do_not_disturb; 230 | //#endif 231 | // std::unique_lock lk(state_change); 232 | 233 | // while(!state.can_lock_shared()) 234 | // { 235 | // if(!shared_cond.timed_wait(lk,timeout)) 236 | // { 237 | // return false; 238 | // } 239 | // } 240 | // state.lock_shared(); 241 | // return true; 242 | // } 243 | 244 | // template 245 | // bool timed_lock_shared(TimeDuration const & relative_time) 246 | // { 247 | // return timed_lock_shared(get_system_time()+relative_time); 248 | // } 249 | //#endif 250 | //#ifdef BOOST_THREAD_USES_CHRONO 251 | template 252 | bool try_lock_shared_for(const std::chrono::duration& rel_time) 253 | { 254 | return try_lock_shared_until(std::chrono::steady_clock::now() + rel_time); 255 | } 256 | template 257 | bool try_lock_shared_until(const std::chrono::time_point& abs_time) 258 | { 259 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 260 | // std::this_thread::disable_interruption do_not_disturb; 261 | //#endif 262 | std::unique_lock lk(state_change); 263 | 264 | while(!state.can_lock_shared()) 265 | //while(state.exclusive || state.exclusive_waiting_blocked) 266 | { 267 | if(std::cv_status::timeout==shared_cond.wait_until(lk,abs_time)) 268 | { 269 | return false; 270 | } 271 | } 272 | state.lock_shared(); 273 | return true; 274 | } 275 | //#endif 276 | void unlock_shared() 277 | { 278 | std::unique_lock lk(state_change); 279 | state.assert_lock_shared(); 280 | state.unlock_shared(); 281 | if (! state.more_shared()) 282 | { 283 | if (state.upgrade) 284 | { 285 | // As there is a thread doing a unlock_upgrade_and_lock that is waiting for ! state.more_shared() 286 | // avoid other threads to lock, lock_upgrade or lock_shared, so only this thread is notified. 287 | state.upgrade=false; 288 | state.exclusive=true; 289 | lk.unlock(); 290 | upgrade_cond.notify_one(); 291 | } 292 | else 293 | { 294 | state.exclusive_waiting_blocked=false; 295 | lk.unlock(); 296 | } 297 | release_waiters(); 298 | } 299 | } 300 | 301 | void lock() 302 | { 303 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 304 | // std::this_thread::disable_interruption do_not_disturb; 305 | //#endif 306 | std::unique_lock lk(state_change); 307 | 308 | while (state.shared_count || state.exclusive) 309 | { 310 | state.exclusive_waiting_blocked=true; 311 | exclusive_cond.wait(lk); 312 | } 313 | state.exclusive=true; 314 | } 315 | 316 | //#if defined BOOST_THREAD_USES_DATETIME 317 | // bool timed_lock(system_time const& timeout) 318 | // { 319 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 320 | // std::this_thread::disable_interruption do_not_disturb; 321 | //#endif 322 | // std::unique_lock lk(state_change); 323 | 324 | // while(state.shared_count || state.exclusive) 325 | // { 326 | // state.exclusive_waiting_blocked=true; 327 | // if(!exclusive_cond.timed_wait(lk,timeout)) 328 | // { 329 | // if(state.shared_count || state.exclusive) 330 | // { 331 | // state.exclusive_waiting_blocked=false; 332 | // release_waiters(); 333 | // return false; 334 | // } 335 | // break; 336 | // } 337 | // } 338 | // state.exclusive=true; 339 | // return true; 340 | // } 341 | 342 | // template 343 | // bool timed_lock(TimeDuration const & relative_time) 344 | // { 345 | // return timed_lock(get_system_time()+relative_time); 346 | // } 347 | //#endif 348 | //#ifdef BOOST_THREAD_USES_CHRONO 349 | template 350 | bool try_lock_for(const std::chrono::duration& rel_time) 351 | { 352 | return try_lock_until(std::chrono::steady_clock::now() + rel_time); 353 | } 354 | template 355 | bool try_lock_until(const std::chrono::time_point& abs_time) 356 | { 357 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 358 | // std::this_thread::disable_interruption do_not_disturb; 359 | //#endif 360 | std::unique_lock lk(state_change); 361 | 362 | while(state.shared_count || state.exclusive) 363 | { 364 | state.exclusive_waiting_blocked=true; 365 | if(std::cv_status::timeout == exclusive_cond.wait_until(lk,abs_time)) 366 | { 367 | if(state.shared_count || state.exclusive) 368 | { 369 | state.exclusive_waiting_blocked=false; 370 | release_waiters(); 371 | return false; 372 | } 373 | break; 374 | } 375 | } 376 | state.exclusive=true; 377 | return true; 378 | } 379 | //#endif 380 | 381 | bool try_lock() 382 | { 383 | std::unique_lock lk(state_change); 384 | 385 | if(state.shared_count || state.exclusive) 386 | { 387 | return false; 388 | } 389 | else 390 | { 391 | state.exclusive=true; 392 | return true; 393 | } 394 | 395 | } 396 | 397 | void unlock() 398 | { 399 | std::unique_lock lk(state_change); 400 | state.assert_locked(); 401 | state.exclusive=false; 402 | state.exclusive_waiting_blocked=false; 403 | state.assert_free(); 404 | release_waiters(); 405 | } 406 | 407 | void lock_upgrade() 408 | { 409 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 410 | // std::this_thread::disable_interruption do_not_disturb; 411 | //#endif 412 | std::unique_lock lk(state_change); 413 | while(state.exclusive || state.exclusive_waiting_blocked || state.upgrade) 414 | { 415 | shared_cond.wait(lk); 416 | } 417 | state.lock_shared(); 418 | state.upgrade=true; 419 | } 420 | 421 | //#if defined BOOST_THREAD_USES_DATETIME 422 | // bool timed_lock_upgrade(system_time const& timeout) 423 | // { 424 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 425 | // std::this_thread::disable_interruption do_not_disturb; 426 | //#endif 427 | // std::unique_lock lk(state_change); 428 | // while(state.exclusive || state.exclusive_waiting_blocked || state.upgrade) 429 | // { 430 | // if(!shared_cond.timed_wait(lk,timeout)) 431 | // { 432 | // if(state.exclusive || state.exclusive_waiting_blocked || state.upgrade) 433 | // { 434 | // return false; 435 | // } 436 | // break; 437 | // } 438 | // } 439 | // state.lock_shared(); 440 | // state.upgrade=true; 441 | // return true; 442 | // } 443 | 444 | // template 445 | // bool timed_lock_upgrade(TimeDuration const & relative_time) 446 | // { 447 | // return timed_lock_upgrade(get_system_time()+relative_time); 448 | // } 449 | //#endif 450 | //#ifdef BOOST_THREAD_USES_CHRONO 451 | template 452 | bool try_lock_upgrade_for(const std::chrono::duration& rel_time) 453 | { 454 | return try_lock_upgrade_until(std::chrono::steady_clock::now() + rel_time); 455 | } 456 | template 457 | bool try_lock_upgrade_until(const std::chrono::time_point& abs_time) 458 | { 459 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 460 | // std::this_thread::disable_interruption do_not_disturb; 461 | //#endif 462 | std::unique_lock lk(state_change); 463 | while(state.exclusive || state.exclusive_waiting_blocked || state.upgrade) 464 | { 465 | if(std::cv_status::timeout == shared_cond.wait_until(lk,abs_time)) 466 | { 467 | if(state.exclusive || state.exclusive_waiting_blocked || state.upgrade) 468 | { 469 | return false; 470 | } 471 | break; 472 | } 473 | } 474 | state.lock_shared(); 475 | state.upgrade=true; 476 | return true; 477 | } 478 | //#endif 479 | bool try_lock_upgrade() 480 | { 481 | std::unique_lock lk(state_change); 482 | if(state.exclusive || state.exclusive_waiting_blocked || state.upgrade) 483 | { 484 | return false; 485 | } 486 | else 487 | { 488 | state.lock_shared(); 489 | state.upgrade=true; 490 | state.assert_lock_upgraded(); 491 | return true; 492 | } 493 | } 494 | 495 | void unlock_upgrade() 496 | { 497 | std::unique_lock lk(state_change); 498 | //state.upgrade=false; 499 | state.unlock_upgrade(); 500 | if(! state.more_shared() ) 501 | { 502 | state.exclusive_waiting_blocked=false; 503 | release_waiters(); 504 | } else { 505 | shared_cond.notify_all(); 506 | } 507 | } 508 | 509 | // Upgrade <-> Exclusive 510 | void unlock_upgrade_and_lock() 511 | { 512 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 513 | // std::this_thread::disable_interruption do_not_disturb; 514 | //#endif 515 | std::unique_lock lk(state_change); 516 | state.assert_lock_upgraded(); 517 | state.unlock_shared(); 518 | while (state.more_shared()) 519 | { 520 | upgrade_cond.wait(lk); 521 | } 522 | state.upgrade=false; 523 | state.exclusive=true; 524 | state.assert_locked(); 525 | } 526 | 527 | void unlock_and_lock_upgrade() 528 | { 529 | std::unique_lock lk(state_change); 530 | state.assert_locked(); 531 | state.exclusive=false; 532 | state.upgrade=true; 533 | state.lock_shared(); 534 | state.exclusive_waiting_blocked=false; 535 | state.assert_lock_upgraded(); 536 | release_waiters(); 537 | } 538 | 539 | bool try_unlock_upgrade_and_lock() 540 | { 541 | std::unique_lock lk(state_change); 542 | state.assert_lock_upgraded(); 543 | if( !state.exclusive 544 | && !state.exclusive_waiting_blocked 545 | && state.upgrade 546 | && state.shared_count==1) 547 | { 548 | state.shared_count=0; 549 | state.exclusive=true; 550 | state.upgrade=false; 551 | state.assert_locked(); 552 | return true; 553 | } 554 | return false; 555 | } 556 | //#ifdef BOOST_THREAD_USES_CHRONO 557 | template 558 | bool 559 | try_unlock_upgrade_and_lock_for( 560 | const std::chrono::duration& rel_time) 561 | { 562 | return try_unlock_upgrade_and_lock_until( 563 | std::chrono::steady_clock::now() + rel_time); 564 | } 565 | template 566 | bool 567 | try_unlock_upgrade_and_lock_until( 568 | const std::chrono::time_point& abs_time) 569 | { 570 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 571 | // std::this_thread::disable_interruption do_not_disturb; 572 | //#endif 573 | std::unique_lock lk(state_change); 574 | state.assert_lock_upgraded(); 575 | if (state.shared_count != 1) 576 | { 577 | for (;;) 578 | { 579 | std::cv_status status = shared_cond.wait_until(lk,abs_time); 580 | if (state.shared_count == 1) 581 | break; 582 | if(status == std::cv_status::timeout) 583 | return false; 584 | } 585 | } 586 | state.upgrade=false; 587 | state.exclusive=true; 588 | state.exclusive_waiting_blocked=false; 589 | state.shared_count=0; 590 | return true; 591 | } 592 | //#endif 593 | 594 | // Shared <-> Exclusive 595 | void unlock_and_lock_shared() 596 | { 597 | std::unique_lock lk(state_change); 598 | state.assert_locked(); 599 | state.exclusive=false; 600 | state.lock_shared(); 601 | state.exclusive_waiting_blocked=false; 602 | release_waiters(); 603 | } 604 | 605 | //#ifdef BOOST_THREAD_PROVIDES_SHARED_MUTEX_UPWARDS_CONVERSIONS 606 | // bool try_unlock_shared_and_lock() 607 | // { 608 | // std::unique_lock lk(state_change); 609 | // state.assert_lock_shared(); 610 | // if( !state.exclusive 611 | // && !state.exclusive_waiting_blocked 612 | // && !state.upgrade 613 | // && state.shared_count==1) 614 | // { 615 | // state.shared_count=0; 616 | // state.exclusive=true; 617 | // return true; 618 | // } 619 | // return false; 620 | // } 621 | //#ifdef BOOST_THREAD_USES_CHRONO 622 | // template 623 | // bool 624 | // try_unlock_shared_and_lock_for( 625 | // const chrono::duration& rel_time) 626 | // { 627 | // return try_unlock_shared_and_lock_until( 628 | // chrono::steady_clock::now() + rel_time); 629 | // } 630 | // template 631 | // bool 632 | // try_unlock_shared_and_lock_until( 633 | // const chrono::time_point& abs_time) 634 | // { 635 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 636 | // std::this_thread::disable_interruption do_not_disturb; 637 | //#endif 638 | // std::unique_lock lk(state_change); 639 | // state.assert_lock_shared(); 640 | // if (state.shared_count != 1) 641 | // { 642 | // for (;;) 643 | // { 644 | // cv_status status = shared_cond.wait_until(lk,abs_time); 645 | // if (state.shared_count == 1) 646 | // break; 647 | // if(status == cv_status::timeout) 648 | // return false; 649 | // } 650 | // } 651 | // state.upgrade=false; 652 | // state.exclusive=true; 653 | // state.exclusive_waiting_blocked=false; 654 | // state.shared_count=0; 655 | // return true; 656 | // } 657 | //#endif 658 | //#endif 659 | 660 | // Shared <-> Upgrade 661 | void unlock_upgrade_and_lock_shared() 662 | { 663 | std::unique_lock lk(state_change); 664 | state.assert_lock_upgraded(); 665 | state.upgrade=false; 666 | state.exclusive_waiting_blocked=false; 667 | release_waiters(); 668 | } 669 | 670 | //#ifdef BOOST_THREAD_PROVIDES_SHARED_MUTEX_UPWARDS_CONVERSIONS 671 | // bool try_unlock_shared_and_lock_upgrade() 672 | // { 673 | // std::unique_lock lk(state_change); 674 | // state.assert_lock_shared(); 675 | // if( !state.exclusive 676 | // && !state.exclusive_waiting_blocked 677 | // && !state.upgrade 678 | // ) 679 | // { 680 | // state.upgrade=true; 681 | // return true; 682 | // } 683 | // return false; 684 | // } 685 | //#ifdef BOOST_THREAD_USES_CHRONO 686 | // template 687 | // bool 688 | // try_unlock_shared_and_lock_upgrade_for( 689 | // const chrono::duration& rel_time) 690 | // { 691 | // return try_unlock_shared_and_lock_upgrade_until( 692 | // chrono::steady_clock::now() + rel_time); 693 | // } 694 | // template 695 | // bool 696 | // try_unlock_shared_and_lock_upgrade_until( 697 | // const chrono::time_point& abs_time) 698 | // { 699 | //#if defined BOOST_THREAD_PROVIDES_INTERRUPTIONS 700 | // std::this_thread::disable_interruption do_not_disturb; 701 | //#endif 702 | // std::unique_lock lk(state_change); 703 | // state.assert_lock_shared(); 704 | // if( state.exclusive 705 | // || state.exclusive_waiting_blocked 706 | // || state.upgrade 707 | // ) 708 | // { 709 | // for (;;) 710 | // { 711 | // cv_status status = exclusive_cond.wait_until(lk,abs_time); 712 | // if( ! state.exclusive 713 | // && ! state.exclusive_waiting_blocked 714 | // && ! state.upgrade 715 | // ) 716 | // break; 717 | // if(status == cv_status::timeout) 718 | // return false; 719 | // } 720 | // } 721 | // state.upgrade=true; 722 | // return true; 723 | // } 724 | //#endif 725 | //#endif 726 | }; 727 | 728 | typedef shared_timed_mutex upgrade_mutex; 729 | } 730 | 731 | #endif // SHARED_TIMED_MUTEX_POLYFILL_H 732 | -------------------------------------------------------------------------------- /signalname.cpp: -------------------------------------------------------------------------------- 1 | #include "signalname.h" 2 | 3 | #include 4 | 5 | const char* SignalName:: 6 | name(int signal) 7 | { 8 | switch(signal) 9 | { 10 | #ifndef _MSC_VER 11 | case SIGHUP: return "SIGHUP"; // 1, hangup 12 | #endif 13 | case SIGINT: return "SIGINT"; // 2, interrupt 14 | #ifndef _MSC_VER 15 | case SIGQUIT: return "SIGQUIT"; // 3, quit 16 | #endif 17 | case SIGILL: return "SIGILL"; // 4, illegal instruction (not reset when caught) 18 | #ifndef _MSC_VER 19 | case SIGTRAP: return "SIGTRAP"; // 5, trace trap (not reset when caught) 20 | #endif 21 | case SIGABRT: return "SIGABRT"; // 6, abort() 22 | #if (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE)) 23 | case SIGPOLL: return "SIGPOLL"; // 7, pollable event ([XSR] generated, not supported) 24 | #else // (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) 25 | #ifndef _MSC_VER 26 | case SIGEMT: return "SIGEMT"; // 7, EMT instruction 27 | #endif 28 | #endif // (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) 29 | case SIGFPE: return "SIGFPE"; // 8, floating point exception 30 | #ifndef _MSC_VER 31 | case SIGKILL: return "SIGKILL"; // 9, kill (cannot be caught or ignored) 32 | case SIGBUS: return "SIGBUS"; // 10, bus error 33 | #endif 34 | case SIGSEGV: return "SIGSEGV"; // 11, segmentation violation 35 | #ifndef _MSC_VER 36 | case SIGSYS: return "SIGSYS"; // 12, bad argument to system call 37 | case SIGPIPE: return "SIGPIPE"; // 13, write on a pipe with no one to read it 38 | case SIGALRM: return "SIGALRM"; // 14, alarm clock 39 | #endif 40 | case SIGTERM: return "SIGTERM"; // 15, software termination signal from kill 41 | #ifndef _MSC_VER 42 | case SIGURG: return "SIGURG"; // 16, urgent condition on IO channel 43 | case SIGSTOP: return "SIGSTOP"; // 17, sendable stop signal not from tty 44 | case SIGTSTP: return "SIGTSTP"; // 18, stop signal from tty 45 | case SIGCONT: return "SIGCONT"; // 19, continue a stopped process 46 | case SIGCHLD: return "SIGCHLD"; // 20, to parent on child stop or exit 47 | case SIGTTIN: return "SIGTTIN"; // 21, to readers pgrp upon background tty read 48 | case SIGTTOU: return "SIGTTOU"; // 22, like TTIN for output if (tp->t_local<OSTOP) 49 | #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) 50 | case SIGIO: return "SIGIO"; // 23, input/output possible signal 51 | #endif 52 | case SIGXCPU: return "SIGXCPU"; // 24, exceeded CPU time limit 53 | case SIGXFSZ: return "SIGXFSZ"; // 25, exceeded file size limit 54 | case SIGVTALRM: return "SIGVTALRM"; // 26, virtual time alarm 55 | case SIGPROF: return "SIGPROF"; // 27, profiling time alarm 56 | #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) 57 | case SIGWINCH: return "SIGWINCH"; // 28, window size changes 58 | case SIGINFO: return "SIGINFO"; // 29, information request 59 | #endif 60 | case SIGUSR1: return "SIGUSR1"; // 30, user defined signal 1 61 | case SIGUSR2: return "SIGUSR2"; // 31, user defined signal 2 62 | #endif 63 | default: return "UNKNOWN"; 64 | } 65 | } 66 | 67 | 68 | const char* SignalName:: 69 | desc(int signal) 70 | { 71 | switch(signal) 72 | { 73 | #ifndef _MSC_VER 74 | case SIGHUP: return "hangup"; // 1 75 | #endif 76 | case SIGINT: return "interrupt"; // 2 77 | #ifndef _MSC_VER 78 | case SIGQUIT: return "quit"; // 3 79 | #endif 80 | case SIGILL: return "illegal instruction (not reset when caught)"; // 4 81 | #ifndef _MSC_VER 82 | case SIGTRAP: return "trace trap (not reset when caught)"; // 5 83 | #endif 84 | case SIGABRT: return "abort()"; // 6 85 | #if (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE)) 86 | case SIGPOLL: return "pollable event ([XSR] generated, not supported)"; // 7 87 | #else /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ 88 | #ifndef _MSC_VER 89 | case SIGEMT: return "EMT instruction"; // 7 90 | #endif 91 | #endif /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */ 92 | case SIGFPE: return "floating point exception"; // 8 93 | #ifndef _MSC_VER 94 | case SIGKILL: return "kill (cannot be caught or ignored)"; // 9 95 | case SIGBUS: return "bus error"; // 10 96 | #endif 97 | case SIGSEGV: return "segmentation violation"; // 11 98 | #ifndef _MSC_VER 99 | case SIGSYS: return "bad argument to system call"; // 12 100 | case SIGPIPE: return "write on a pipe with no one to read it"; // 13 101 | case SIGALRM: return "alarm clock"; // 14 102 | #endif 103 | case SIGTERM: return "software termination signal from kill"; // 15 104 | #ifndef _MSC_VER 105 | case SIGURG: return "urgent condition on IO channel"; // 16 106 | case SIGSTOP: return "sendable stop signal not from tty"; // 17 107 | case SIGTSTP: return "stop signal from tty"; // 18 108 | case SIGCONT: return "continue a stopped process"; // 19 109 | case SIGCHLD: return "to parent on child stop or exit"; // 20 110 | case SIGTTIN: return "to readers pgrp upon background tty read"; // 21 111 | case SIGTTOU: return "like TTIN for output if (tp->t_local<OSTOP)"; // 22 112 | #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) 113 | case SIGIO: return "input/output possible signal"; // 23 114 | #endif 115 | case SIGXCPU: return "exceeded CPU time limit"; // 24 116 | case SIGXFSZ: return "exceeded file size limit"; // 25 117 | case SIGVTALRM: return "virtual time alarm"; // 26 118 | case SIGPROF: return "profiling time alarm"; // 27 119 | #if (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)) 120 | case SIGWINCH: return "window size changes"; // 28 121 | case SIGINFO: return "information request"; // 29 122 | #endif 123 | case SIGUSR1: return "user defined signal 1"; // 30 124 | case SIGUSR2: return "user defined signal 2"; // 31 125 | #endif 126 | default: return "unknown"; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /signalname.h: -------------------------------------------------------------------------------- 1 | #ifndef SIGNALNAME_H 2 | #define SIGNALNAME_H 3 | 4 | /** 5 | * @brief The SignalName class should translate a system signal to a short and long string representation. 6 | */ 7 | class SignalName 8 | { 9 | public: 10 | static const char* name(int signal); 11 | static const char* desc(int signal); 12 | }; 13 | 14 | #endif // SIGNALNAME_H 15 | -------------------------------------------------------------------------------- /tasktimer.cpp: -------------------------------------------------------------------------------- 1 | #include "tasktimer.h" 2 | 3 | #include "cva_list.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #ifndef _MSC_VER 15 | #define MICROSEC_TIMESTAMPS 16 | #endif 17 | 18 | #ifdef _MSC_VER 19 | #include 20 | #endif 21 | 22 | #define THREADNUMBERS 23 | //#define THREADNUMBERS if(0) 24 | 25 | #define TIMESTAMPS 26 | //#define TIMESTAMPS if(0) 27 | 28 | using namespace boost; 29 | using namespace std; 30 | 31 | bool DISABLE_TASKTIMER = false; 32 | const int thread_column_width = 4; 33 | 34 | class is_alive_t { 35 | public: 36 | is_alive_t() : is_alive(true) {} 37 | ~is_alive_t() { is_alive = false; } 38 | operator bool() { return is_alive; } 39 | private: 40 | bool is_alive; 41 | } is_alive; 42 | 43 | class staticLock_t { 44 | public: 45 | void lock() { 46 | if (is_alive) 47 | lock_.lock(); 48 | } 49 | 50 | void unlock() throw() { 51 | if (is_alive) 52 | lock_.unlock (); 53 | } 54 | 55 | private: 56 | recursive_mutex lock_; 57 | } staticLock; 58 | 59 | typedef unique_lock TaskTimerLock; 60 | 61 | 62 | struct ThreadInfo { 63 | const int threadNumber; 64 | unsigned counter[3]; 65 | 66 | ThreadInfo(int threadNumber=0) 67 | : 68 | threadNumber(threadNumber) 69 | { 70 | memset(counter, 0, sizeof(counter)); 71 | } 72 | } single; 73 | 74 | bool writeNextOnNewRow[3] = {false, false, false}; 75 | TaskTimer* lastTimer[3] = {0,0,0}; 76 | ostream* logLevelStream[] = { 77 | logLevelStream[0] = &cout, // &cerr, // LogVerbose 78 | logLevelStream[1] = &cout, 79 | logLevelStream[2] = &cout 80 | }; 81 | 82 | 83 | map thread_info_map; 84 | 85 | static ThreadInfo& T() { 86 | if (!is_alive) 87 | return single; 88 | 89 | // Assume lock is acquired. 90 | thread::id threadid = this_thread::get_id (); 91 | 92 | if (!thread_info_map.count (threadid)) 93 | thread_info_map.insert ( 94 | pair(threadid, 95 | ThreadInfo((int)thread_info_map.size ()))); 96 | 97 | return thread_info_map[threadid]; 98 | } 99 | 100 | 101 | void TaskTimer:: 102 | this_thread_quit() 103 | { 104 | TaskTimerLock scope(staticLock); 105 | thread_info_map.erase (this_thread::get_id ()); 106 | } 107 | 108 | 109 | TaskTimer::TaskTimer() { 110 | init( LogSimple, "Unlabeled task", 0 ); 111 | } 112 | 113 | TaskTimer::TaskTimer(const char* f, ...) { 114 | Cva_start(c,f); 115 | init( LogSimple, f, c ); 116 | } 117 | 118 | TaskTimer::TaskTimer(LogLevel logLevel, const char* f, ...) { 119 | Cva_start(c,f); 120 | init( logLevel, f, c ); 121 | } 122 | 123 | TaskTimer::TaskTimer(bool, const char* f, va_list args) { 124 | init( LogSimple, f, args ); 125 | } 126 | 127 | TaskTimer::TaskTimer(bool, LogLevel logLevel, const char* f, va_list args) { 128 | init( logLevel, f, args ); 129 | } 130 | 131 | TaskTimer::TaskTimer(const format& fmt) 132 | { 133 | initEllipsis (LogSimple, "%s", fmt.str ().c_str ()); 134 | } 135 | 136 | void TaskTimer::initEllipsis(LogLevel logLevel, const char* f, ...) { 137 | Cva_start(c,f); 138 | init( logLevel, f, c ); 139 | } 140 | 141 | void TaskTimer::init(LogLevel logLevel, const char* task, va_list args) { 142 | if (DISABLE_TASKTIMER) 143 | return; 144 | 145 | TaskTimerLock scope(staticLock); 146 | 147 | this->numPartlyDone = 0; 148 | this->upperLevel = 0; 149 | this->suppressTimingInfo = false; 150 | this->is_unwinding = uncaught_exception(); 151 | 152 | while (0logLevel = logLevel; 161 | 162 | if( 0logLevel]++; 168 | 169 | printIndentation(); 170 | vector strs; 171 | 172 | writeNextOnNewRow[this->logLevel] = true; 173 | 174 | int c = vsnprintf( 0, 0, task, Cva_list(args) ); 175 | vector t( c+1 ); 176 | vsnprintf( &t[0], c+1, task, Cva_list(args) ); 177 | string s; 178 | s.append ( &t[0],&t[c] ); 179 | 180 | if (strchr(s.c_str(), '\n')) 181 | split(strs, s, is_any_of("\n"), algorithm::token_compress_off); 182 | 183 | if (!strs.empty()) 184 | { if (strs.back().size() == 0 && strs.size()>1) 185 | strs.pop_back(); 186 | 187 | s = strs[0]; 188 | } 189 | 190 | logprint( s.c_str() ); 191 | 192 | for (unsigned i=1; i %s", strs[i].c_str()); 194 | 195 | timer_.restart (); 196 | } 197 | 198 | void TaskTimer::logprint(const char* txt) { 199 | if (0 == logLevelStream[ logLevel ]) { 200 | ; 201 | } else { 202 | *logLevelStream[ logLevel ] << txt; 203 | 204 | if (strchr(txt, '\n')) 205 | *logLevelStream[ logLevel ] << flush; 206 | } 207 | } 208 | 209 | void TaskTimer::info(const char* taskInfo, ...) { 210 | Cva_start(c, taskInfo); 211 | vinfo(taskInfo, c); 212 | } 213 | 214 | void TaskTimer::vinfo(const char* taskInfo, va_list args) { 215 | // The constructor creates new instances for other log levels. 216 | TaskTimer myTask( 0, logLevel, taskInfo, args ); 217 | 218 | myTask.suppressTiming(); 219 | } 220 | 221 | void TaskTimer::suppressTiming() { 222 | if (DISABLE_TASKTIMER) 223 | return; 224 | 225 | TaskTimerLock scope(staticLock); 226 | for( TaskTimer* p = this; 0 != p; p = p->upperLevel ) { 227 | p->suppressTimingInfo = true; 228 | } 229 | } 230 | 231 | bool TaskTimer::printIndentation() { 232 | TaskTimerLock scope(staticLock); 233 | const ThreadInfo& t = T(); 234 | TaskTimer* ltll = lastTimer[logLevel]; 235 | 236 | if (ltll == this) { 237 | return false; 238 | } else { 239 | if (writeNextOnNewRow[logLevel]) 240 | logprint("\n"); 241 | 242 | TIMESTAMPS { // Print timestamp 243 | stringstream ss; 244 | 245 | auto now = boost::posix_time::microsec_clock::local_time(); 246 | auto t = now.time_of_day(); 247 | 248 | #ifndef MICROSEC_TIMESTAMPS 249 | ss << setiosflags(ios::fixed) 250 | << setfill('0') << setw(2) 251 | << t.hours() << ":" << setw(2) << t.minutes() << ":" 252 | << setprecision(3) << setw(6) 253 | << t.fractional_seconds()/(float)t.ticks_per_second() + t.seconds() << " "; 254 | #else 255 | ss << setiosflags(ios::fixed) 256 | << setfill('0') << setw(2) 257 | << t.hours() << ":" << setw(2) << t.minutes() << ":" 258 | << setprecision(6) << setw(9) 259 | << t.fractional_seconds()/(float)t.ticks_per_second() + t.seconds() << " "; 260 | #endif 261 | 262 | logprint( ss.str().c_str() ); 263 | } 264 | 265 | THREADNUMBERS { // Print thread numbers 266 | stringstream ss; 267 | 268 | int width = 1; 269 | int N = thread_info_map.size (); 270 | int number = t.threadNumber; 271 | 272 | while ((N/=10) > 1) 273 | width++; 274 | 275 | if (number > 0) 276 | ss << setfill(' ') << setw(width) 277 | << number << " "; 278 | else 279 | ss << setfill(' ') << setw(width) 280 | << "" << " "; 281 | 282 | // different columns 283 | ss << setfill(' ') << setw (number*thread_column_width) << ""; 284 | 285 | logprint( ss.str ().c_str () ); 286 | } 287 | 288 | const char* separators[] = { "|", "-" }; 289 | for (unsigned i=1; ilogLevel]++; 309 | writeNextOnNewRow[logLevel] = false; 310 | 311 | bool didprint = printIndentation(); 312 | 313 | if (didprint) { 314 | logprint("> "); 315 | } 316 | 317 | t.counter[this->logLevel]--; 318 | 319 | do { 320 | numPartlyDone++; 321 | logprint("."); 322 | } while (numPartlyDone<3); 323 | 324 | writeNextOnNewRow[logLevel] = true; 325 | 326 | if (0 != logLevelStream[ logLevel ]) 327 | *logLevelStream[ logLevel ] << flush; 328 | 329 | // for all public methods, do the same action for the parent TaskTimer 330 | if (0 != upperLevel) { 331 | upperLevel->partlyDone(); 332 | } 333 | } 334 | 335 | 336 | double TaskTimer::elapsedTime() 337 | { 338 | return timer_.elapsed(); 339 | } 340 | 341 | 342 | TaskTimer::~TaskTimer() { 343 | if (DISABLE_TASKTIMER) 344 | return; 345 | 346 | double diff = elapsedTime(); 347 | 348 | TaskTimerLock scope(staticLock); 349 | 350 | bool didIdent = printIndentation(); 351 | 352 | if (didIdent) { 353 | // logprintf(": "); 354 | } 355 | 356 | bool exception_message = !is_unwinding && uncaught_exception(); 357 | string finish_message = exception_message ? "aborted, exception thrown" : "done"; 358 | 359 | if (!suppressTimingInfo) { 360 | finish_message += exception_message ? " after" : " in"; 361 | 362 | if (!didIdent) { 363 | while (numPartlyDone<3) { 364 | numPartlyDone++; 365 | logprint("."); 366 | } 367 | logprint(" "); 368 | } 369 | 370 | logprint(str(format("%s %s.\n") % finish_message % timeToString (diff)).c_str ()); 371 | } else { 372 | if (didIdent) { 373 | logprint(finish_message.c_str()); 374 | } else { 375 | while (numPartlyDone<1) { 376 | numPartlyDone++; 377 | logprint("."); 378 | } 379 | 380 | if (exception_message) 381 | { 382 | while (numPartlyDone<3) { 383 | numPartlyDone++; 384 | logprint("."); 385 | } 386 | logprint(" "); 387 | logprint(finish_message.c_str()); 388 | } 389 | } 390 | logprint("\n"); 391 | } 392 | 393 | ThreadInfo& t = T(); 394 | writeNextOnNewRow[logLevel] = false; 395 | --t.counter[logLevel]; 396 | 397 | if (didIdent && 0==t.counter[logLevel]) { 398 | logprint("\n"); 399 | } 400 | 401 | lastTimer[logLevel] = 0; 402 | 403 | 404 | if (upperLevel) { 405 | delete upperLevel; 406 | upperLevel = 0; 407 | } 408 | } 409 | 410 | void TaskTimer::setLogLevelStream( LogLevel logLevel, ostream* str ) { 411 | TaskTimerLock scope(staticLock); 412 | 413 | switch (logLevel) { 414 | case LogVerbose: 415 | case LogDetailed: 416 | case LogSimple: 417 | logLevelStream[ logLevel ] = str; 418 | break; 419 | 420 | default: 421 | throw logic_error((format("Muse be one " 422 | "of LogVerbose {%u}, LogDetailed {%u} or LogSimple {%u}.") 423 | % LogVerbose % LogDetailed % LogSimple ).str()); 424 | } 425 | } 426 | 427 | bool TaskTimer:: 428 | isEnabled(LogLevel logLevel) 429 | { 430 | return 0!=logLevelStream[logLevel]; 431 | } 432 | 433 | bool TaskTimer:: 434 | enabled() 435 | { 436 | return !DISABLE_TASKTIMER; 437 | } 438 | 439 | void TaskTimer:: 440 | setEnabled( bool enabled ) 441 | { 442 | DISABLE_TASKTIMER = !enabled; 443 | } 444 | 445 | string TaskTimer:: 446 | timeToString( double T ) 447 | { 448 | int microseconds = T*1e6 + 0.5; 449 | int milliseconds = T*1e3 + 0.5; 450 | 451 | if (microseconds < 1500 && microseconds != 1000) { 452 | return str(format("%.0f us") % (T*1e6)); 453 | } else if (milliseconds < 1500 && milliseconds != 1000) { 454 | return str(format("%.1f ms") % (T*1e3)); 455 | } else if (T < 90) { 456 | return str(format("%.1f s") % T); 457 | } else { 458 | return str(format("%.1f min") % (float)(T/60.f)); 459 | } 460 | } 461 | 462 | TaskInfo:: 463 | TaskInfo(const char* taskInfo, ...) 464 | { 465 | Cva_start(args, taskInfo); 466 | 467 | tt_ = new TaskTimer( 0, taskInfo, args ); 468 | tt_->suppressTiming (); 469 | } 470 | 471 | TaskInfo:: 472 | TaskInfo(const format& fmt) 473 | { 474 | tt_ = new TaskTimer(fmt); 475 | tt_->suppressTiming (); 476 | } 477 | 478 | TaskInfo:: 479 | ~TaskInfo() 480 | { 481 | delete tt_; 482 | } 483 | -------------------------------------------------------------------------------- /tasktimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "timer.h" 4 | #include 5 | #if defined(__cplusplus) && !defined(__CUDACC__) 6 | #include 7 | #endif 8 | #include 9 | 10 | /** 11 | The TaskTimer class should log how long time it takes to execute a scope while 12 | distinguishing nested scopes and different threads. 13 | 14 | 15 | A canonical example 16 | ------------------- 17 | { 18 | TaskTimer tt("Doing this slow thing"); 19 | doSlowThing(); 20 | } 21 | 22 | Example output: 23 | 24 | 12:49:36.241581 Doing this slow thing... done in 100 ms. 25 | 26 | Where "12:49:36.241581" is the time when creating TaskTimer and 27 | "done in 100 ms." will be sent to cout when doSlowThing has 28 | returned, and 'TaskTimer tt' is going out of scope. 29 | 30 | 31 | Nested and formatted logging 32 | ---------------------------- 33 | { 34 | int N = 2; 35 | TaskTimer tt("Doing these %d slow things", N); 36 | for (int i=0; i 7 | #endif 8 | 9 | using namespace std::chrono; 10 | 11 | Timer::Timer(bool start) 12 | { 13 | if (start) 14 | restart(); 15 | } 16 | 17 | 18 | void Timer::restart() 19 | { 20 | #ifdef _MSC_VER 21 | LARGE_INTEGER li; 22 | QueryPerformanceCounter(&li); 23 | start_ = li.QuadPart; 24 | #else 25 | start_ = high_resolution_clock::now (); 26 | #endif 27 | } 28 | 29 | 30 | double Timer::elapsed() const 31 | { 32 | #ifdef _MSC_VER 33 | LARGE_INTEGER li; 34 | static double PCfreq = 1; 35 | for(static bool doOnce=true;doOnce;doOnce=false) 36 | { 37 | QueryPerformanceFrequency(&li); 38 | PCfreq = double(li.QuadPart); 39 | } 40 | QueryPerformanceCounter(&li); 41 | return double(li.QuadPart-start_)/PCfreq; 42 | #else 43 | duration diff = high_resolution_clock::now () - start_; 44 | return diff.count(); 45 | #endif 46 | } 47 | 48 | 49 | double Timer::elapsedAndRestart() 50 | { 51 | #ifdef _MSC_VER 52 | LARGE_INTEGER li; 53 | static double PCfreq = 1; 54 | for(static bool doOnce=true;doOnce;doOnce=false) 55 | { 56 | QueryPerformanceFrequency(&li); 57 | PCfreq = double(li.QuadPart); 58 | } 59 | QueryPerformanceCounter(&li); 60 | __int64 now = li.QuadPart; 61 | double diff = double(now-start_)/PCfreq; 62 | start_ = now; 63 | return diff; 64 | #else 65 | high_resolution_clock::time_point now = high_resolution_clock::now (); 66 | duration diff = now - start_; 67 | start_ = now; 68 | return diff.count (); 69 | #endif 70 | } 71 | 72 | 73 | void Timer:: 74 | test() 75 | { 76 | // It should measure duration with a high accuracy 77 | { 78 | TRACE_PERF("it should measure short intervals as short"); 79 | 80 | trace_perf_.reset ("it should have a low overhead"); 81 | 82 | {Timer t;t.elapsed ();} 83 | } 84 | 85 | // It should have an overhead less than 1 microsecond 86 | { 87 | TRACE_PERF("it should have a low overhead 10000"); 88 | 89 | for (int i=0;i<10000;i++) { 90 | Timer t0; 91 | t0.elapsed (); 92 | } 93 | 94 | trace_perf_.reset ("it should produce stable measures 10000"); 95 | 96 | for (int i=0;i<10000;i++) { 97 | Timer t0; 98 | t0.elapsed (); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /timer.h: -------------------------------------------------------------------------------- 1 | #ifndef TIMER_H 2 | #define TIMER_H 3 | 4 | #ifndef _MSC_VER 5 | #include 6 | #endif 7 | 8 | /** 9 | * @brief The Timer class should measure duration with a high accuracy. 10 | * 11 | * It should have an overhead less than 1 microsecond. 12 | */ 13 | class Timer 14 | { 15 | public: 16 | Timer(bool start=true); 17 | 18 | void restart(); 19 | double elapsed() const; 20 | double elapsedAndRestart(); 21 | 22 | private: 23 | #ifdef _MSC_VER 24 | __int64 start_; 25 | #else 26 | std::chrono::high_resolution_clock::time_point start_; 27 | #endif 28 | 29 | public: 30 | static void test(); 31 | }; 32 | 33 | #endif // TIMER_H 34 | -------------------------------------------------------------------------------- /trace_perf.cpp: -------------------------------------------------------------------------------- 1 | #include "trace_perf.h" 2 | #include "detectgdb.h" 3 | #include "shared_state.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #ifdef _MSC_VER 15 | #define WIN32_LEAN_AND_MEAN 16 | #include // gethostname 17 | #else 18 | #include // gethostname 19 | #endif 20 | 21 | bool PRINT_ATTEMPTED_DATABASE_FILES = true; 22 | 23 | using namespace std; 24 | 25 | class performance_traces { 26 | private: 27 | struct Entry { 28 | string info; 29 | double elapsed; 30 | }; 31 | 32 | map> entries; 33 | vector database_paths; 34 | 35 | vector get_database_names(string sourcefilename); 36 | void load_db(map>& dbs, string sourcefilename); 37 | void compare_to_db(map &db, const vector& entries, string sourcefilename); 38 | 39 | void compare_to_db(); 40 | void dump_entries(); 41 | 42 | static void read_database(map& db, string filename); 43 | static void dump_entries(const vector& entries, string sourcefilaname); 44 | 45 | public: 46 | performance_traces(); 47 | ~performance_traces(); 48 | 49 | void log(string filename, string info, double elapsed) { 50 | size_t i = filename.find_last_of ('/'); 51 | if (string::npos != i) 52 | filename = filename.substr (i+1); 53 | 54 | entries[filename].push_back(Entry{info, elapsed}); 55 | } 56 | 57 | void add_path(string path) { 58 | database_paths.push_back (path); 59 | } 60 | }; 61 | 62 | static shared_state traces {new performance_traces}; 63 | 64 | 65 | performance_traces:: 66 | performance_traces() 67 | { 68 | add_path ("trace_perf"); 69 | } 70 | 71 | 72 | performance_traces:: 73 | ~performance_traces() 74 | { 75 | fflush (stdout); 76 | fflush (stderr); 77 | 78 | compare_to_db (); 79 | dump_entries (); 80 | } 81 | 82 | 83 | void performance_traces:: 84 | load_db(map>& dbs, string sourcefilename) 85 | { 86 | if (dbs.find (sourcefilename) != dbs.end ()) 87 | return; 88 | 89 | map db; 90 | 91 | vector dbnames = get_database_names(sourcefilename); 92 | for (unsigned i=0; i> dbs; 106 | for (auto a = entries.begin (); a!=entries.end (); a++) 107 | { 108 | string sourcefilename = a->first; 109 | load_db(dbs, sourcefilename); 110 | } 111 | 112 | for (auto a = dbs.begin (); a!=dbs.end (); a++) 113 | { 114 | string sourcefilename = a->first; 115 | map& db = a->second; 116 | vector& entries = this->entries[sourcefilename]; 117 | 118 | compare_to_db(db, entries, sourcefilename); 119 | } 120 | 121 | for (auto i = dbs.begin (); i!=dbs.end (); i++) 122 | { 123 | map& db = i->second; 124 | 125 | for (auto j = db.begin (); j!=db.end (); j++) 126 | cerr << i->first << ": Missing trace_perf test \'" << j->first << "\'" << endl; 127 | } 128 | } 129 | 130 | 131 | void performance_traces:: 132 | compare_to_db(map &db, const vector& entries, string sourcefilename) 133 | { 134 | bool expected_miss = false; 135 | for (unsigned i=0; isecond; 146 | db.erase (j); 147 | } 148 | 149 | if (elapsed > expected) 150 | { 151 | if (!expected_miss) { 152 | cerr << endl << sourcefilename << " wasn't fast enough ..." << endl; 153 | if (PRINT_ATTEMPTED_DATABASE_FILES) { 154 | vector dbnames = get_database_names(sourcefilename); 155 | for (unsigned i=0; i " << expected << endl; 163 | expected_miss = true; 164 | } 165 | } 166 | 167 | if (expected_miss) 168 | cerr << endl; 169 | } 170 | 171 | 172 | void performance_traces:: 173 | dump_entries() 174 | { 175 | for (auto i=entries.begin (); i!=entries.end (); i++) 176 | dump_entries (i->second, i->first); 177 | } 178 | 179 | 180 | void performance_traces:: 181 | dump_entries(const vector& entries, string sourcefilaname) 182 | { 183 | // requires boost_filesystem 184 | //boost::filesystem::create_directory("trace_perf"); 185 | //boost::filesystem::create_directory("trace_perf/dump"); 186 | 187 | // require posix 188 | mkdir("trace_perf", S_IRWXU|S_IRGRP|S_IXGRP); 189 | mkdir("trace_perf/dump", S_IRWXU|S_IRGRP|S_IXGRP); 190 | 191 | int i=0; 192 | string filename; 193 | while (true) { 194 | stringstream ss; 195 | ss << "trace_perf/dump/" << sourcefilaname << ".db" << i; 196 | filename = ss.str (); 197 | ifstream file(filename); 198 | if (!file) 199 | break; 200 | i++; 201 | } 202 | 203 | ofstream o(filename); 204 | if (!o) 205 | cerr << "Couldn't dump performance entries to " << filename << endl; 206 | 207 | for (unsigned i=0; i& db, string filename) 220 | { 221 | std::string info; 222 | double expected; 223 | 224 | ifstream a(filename); 225 | if (!a.is_open ()) 226 | return; 227 | 228 | while (!a.eof() && !a.fail()) 229 | { 230 | getline(a,info); 231 | a >> expected; 232 | db[info] = expected; 233 | 234 | getline(a,info); // finish line 235 | getline(a,info); // read empty line 236 | } 237 | } 238 | 239 | 240 | vector performance_traces:: 241 | get_database_names(string sourcefilename) 242 | { 243 | string hostname("unknown"); 244 | hostname.reserve (256); 245 | gethostname(&hostname[0], hostname.capacity ()); 246 | 247 | vector config; 248 | 249 | #if defined(__APPLE__) 250 | config.push_back ("-apple"); 251 | #elif defined(_MSC_VER) 252 | config.push_back ("-windows"); 253 | #endif 254 | 255 | #ifdef _DEBUG 256 | config.push_back ("-debug"); 257 | #endif 258 | 259 | if (DetectGdb::is_running_through_gdb()) 260 | config.push_back ("-gdb"); 261 | 262 | vector db; 263 | for (int i=0; i<(1 << config.size ()); i++) 264 | { 265 | string perm; 266 | for (unsigned j=0; j>j) % 2) 269 | perm += config[j]; 270 | } 271 | db.push_back (sourcefilename + ".db" + perm); 272 | } 273 | 274 | int n = db.size(); 275 | if (!hostname.empty ()) 276 | for (int i=0; i dbfiles; 280 | for (unsigned j=0; jlog (filename, info, d); 309 | } 310 | 311 | 312 | void trace_perf:: 313 | reset(const string& info) 314 | { 315 | reset(); 316 | 317 | this->info = info; 318 | this->timer.restart (); 319 | } 320 | 321 | 322 | void trace_perf:: 323 | add_database_path(const std::string& path) 324 | { 325 | traces->add_path(path); 326 | } 327 | -------------------------------------------------------------------------------- /trace_perf.h: -------------------------------------------------------------------------------- 1 | #ifndef TRACE_PERF_H 2 | #define TRACE_PERF_H 3 | 4 | #include 5 | #include "timer.h" 6 | 7 | /** 8 | * @brief The trace_perf class should log the execution time of a scope and 9 | * warn if it was below a threshold. 10 | * 11 | * The scope is identified by a text string, example: 12 | * { 13 | * TRACE_PERF("running thingy"); 14 | * run_thingy(); 15 | * } 16 | * 17 | * The threshold is defined for each scope in a database file in the folder 18 | * trace_perf/... 19 | * 20 | * Multiple database files can be used to overload the thresholds. 21 | * 22 | * The results stored in a complementary database file regardless of failure 23 | * or success when the process quits. 24 | */ 25 | class trace_perf 26 | { 27 | public: 28 | trace_perf(const char* filename, const std::string& info); 29 | trace_perf(const trace_perf&) = delete; 30 | trace_perf& operator=(const trace_perf&) = delete; 31 | ~trace_perf(); 32 | 33 | void reset(); 34 | void reset(const std::string& info); 35 | 36 | static void add_database_path(const std::string& path); 37 | private: 38 | Timer timer; 39 | std::string info; 40 | std::string filename; 41 | }; 42 | 43 | #define TRACE_PERF(info) trace_perf trace_perf_{__FILE__, info} 44 | 45 | #endif // TRACE_PERF_H 46 | -------------------------------------------------------------------------------- /trace_perf/barrier.cpp.db: -------------------------------------------------------------------------------- 1 | spinning_barrier 80 threads, 20 times 2 | 0.01 3 | 4 | locking_barrier 80 threads, 20 times 5 | 0.05 6 | 7 | spinning_barrier 4 threads, 20 times 8 | 30e-06 9 | 10 | locking_barrier 4 threads, 20 times 11 | 0.002 -------------------------------------------------------------------------------- /trace_perf/make_dump_summary.py: -------------------------------------------------------------------------------- 1 | #!/opt/local/bin/python 2 | 3 | from os import listdir, makedirs 4 | from os.path import isfile, join, exists 5 | import numpy 6 | 7 | # http://countergram.com/python-group-iterator-list-function 8 | def group_iter(iterator, n, strict=False): 9 | """ Transforms a sequence of values into a sequence of n-tuples. 10 | e.g. [1, 2, 3, 4, ...] => [(1, 2), (3, 4), ...] (when n == 2) 11 | If strict, then it will raise ValueError if there is a group of fewer 12 | than n items at the end of the sequence. """ 13 | accumulator = [] 14 | for item in iterator: 15 | accumulator.append(item) 16 | if len(accumulator) == n: # tested as fast as separate counter 17 | yield tuple(accumulator) 18 | accumulator = [] # tested faster than accumulator[:] = [] 19 | # and tested as fast as re-using one list object 20 | if len(accumulator) != 0: 21 | if strict: 22 | raise ValueError("Leftover values") 23 | yield tuple(accumulator) 24 | 25 | def read_dump_file(dumpfile): 26 | lines = [line.strip() for line in open(dumpfile, 'r')] 27 | db = {} 28 | for entry in group_iter(lines, 3): 29 | db[entry[0]] = float(entry[1]) 30 | 31 | return db 32 | 33 | def get_dump_files(): 34 | path = 'dump' 35 | files = [ f for f in listdir(path) if isfile(join(path,f)) ] 36 | dbs = {} 37 | 38 | for f in files: 39 | basename = f[0:f.find('.db')]; 40 | db = read_dump_file(join(path,f)) 41 | if not basename in dbs: 42 | dbs[basename] = {} 43 | 44 | dblist = dbs[basename] 45 | 46 | for text in db: 47 | if not text in dblist: 48 | dblist[text] = [] 49 | dblist[text] += [db[text]] 50 | dbs[basename] = dblist 51 | 52 | return dbs 53 | 54 | def make_dump_summary(dbs): 55 | sumdbs = {} 56 | for basename in dbs: 57 | db = dbs[basename] 58 | sumdb = {} 59 | for text in db: 60 | v = db[text] 61 | sumdb[text] = "min: %g, mean: %g, std: %g, max: %g, N: %d" % (numpy.min(v), numpy.mean(v), numpy.std(v), numpy.max(v), len(v)) 62 | 63 | sumdbs[basename] = sumdb 64 | 65 | return sumdbs 66 | 67 | def write_dump_summary(sumdbs): 68 | path = 'summary' 69 | if not exists(path): 70 | makedirs(path) 71 | 72 | for basename in sumdbs: 73 | filename = join(path,basename + ".txt") 74 | with open(filename, 'w') as f: 75 | db = sumdbs[basename] 76 | for text in db: 77 | infotext = db[text] 78 | f.write('%s\n' % text) 79 | f.write('%s\n' % infotext) 80 | f.write('\n') 81 | 82 | dbs = get_dump_files() 83 | sumdbs = make_dump_summary(dbs) 84 | write_dump_summary(sumdbs) 85 | -------------------------------------------------------------------------------- /trace_perf/rebuild_and_evaluate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ "$(basename `pwd`)" != "backtrace" ]; then 6 | echo "Run from the backtrace directory". 7 | false 8 | fi 9 | 10 | ( 11 | make clean 12 | make -j12 13 | rm -rf trace_perf/dump trace_perf/summary 14 | 15 | for a in `seq 100`; do 16 | ./backtrace-unittest 17 | done 18 | 19 | cd trace_perf 20 | ./make_dump_summary.py 21 | ) > /dev/null 22 | -------------------------------------------------------------------------------- /trace_perf/shared_state.cpp.db: -------------------------------------------------------------------------------- 1 | shared_state should cause a low write overhead 2 | 0.05 3 | --- unit 0.1 ms 4 | shared_state should cause a low read overhead 5 | 0.05 6 | --- unit 0.1 ms 7 | shared_state should cause a low overhead : reference 8 | 0.001 9 | --- unit 0.1 ms 10 | shared_state should fail fast with try_read 11 | 0.06 12 | --- unit 1 ms 13 | shared_state should fail fast with try_write 14 | 0.06 15 | --- unit 1 ms 16 | shared_state should fail fast with timeout=0 17 | 0.02 18 | --- unit 1 ms 19 | shared_state should handle lock contention efficiently N=200, M=100, w=1 20 | 0.08 21 | 22 | shared_state should handle lock contention efficiently N=200, M=100, w=10 23 | 0.05 24 | 25 | shared_state should handle lock contention efficiently N=200, M=100, w=100 26 | 0.03 27 | 28 | shared_state should handle lock contention efficiently N=200, M=100, w=1000 29 | 0.03 30 | 31 | shared_state should handle lock contention efficiently reference N=200, M=100 32 | 0.03 33 | 34 | shared_state should handle lock contention efficiently simple N=200, M=100 35 | 0.03 36 | 37 | shared_state should handle lock contention efficiently N=200, M=1000, w=1 38 | 0.03 39 | 40 | shared_state should handle lock contention efficiently N=200, M=1000, w=10 41 | 0.03 42 | 43 | shared_state should handle lock contention efficiently N=200, M=1000, w=100 44 | 0.03 45 | 46 | shared_state should handle lock contention efficiently N=200, M=1000, w=1000 47 | 0.03 48 | 49 | shared_state should handle lock contention efficiently reference N=200, M=1000 50 | 0.03 51 | 52 | shared_state should handle lock contention efficiently simple N=200, M=1000 53 | 0.03 54 | 55 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=100, w=1 56 | 0.06 57 | 58 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=100, w=10 59 | 0.06 60 | 61 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=100, w=100 62 | 0.06 63 | 64 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=100, w=1000 65 | 0.06 66 | 67 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=1 68 | 0.02 69 | 70 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=10 71 | 0.02 72 | 73 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=100 74 | 0.02 75 | 76 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=1000 77 | 0.02 78 | -------------------------------------------------------------------------------- /trace_perf/shared_state.cpp.db-debug: -------------------------------------------------------------------------------- 1 | shared_state should cause a low overhead : reference 2 | 0.002 3 | --- unit 0.1 ms 4 | shared_state should handle lock contention efficiently N=200, M=1000, w=10 5 | 0.04 6 | 7 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=1 8 | 0.03 9 | 10 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=10 11 | 0.03 12 | 13 | shared_state should handle lock contention efficiently NO_SHARED_MUTEX N=200, M=1000, w=100 14 | 0.03 -------------------------------------------------------------------------------- /trace_perf/shared_state_traits_backtrace.cpp.db: -------------------------------------------------------------------------------- 1 | warnings on locks that are held too long should cause a low overhead 2 | 0.02 3 | --- unit 0.1 ms -------------------------------------------------------------------------------- /trace_perf/shared_state_traits_backtrace.cpp.db-debug: -------------------------------------------------------------------------------- 1 | warnings on locks that are held too long should cause a low overhead 2 | 0.03 3 | --- unit 0.1 ms -------------------------------------------------------------------------------- /trace_perf/timer.cpp.db: -------------------------------------------------------------------------------- 1 | it should measure short intervals as short 2 | 4e-06 3 | --- smallest possible measure 4 | it should have a low overhead 5 | 4e-06 6 | --- The time it takes to create a timer, check elapsed and destroy it. As measured with a timer. 7 | it should have a low overhead 10000 8 | 0.02 9 | --- unit: 100 microseconds 10 | it should produce stable measures 10000 11 | 0.02 12 | --- unit: 100 microseconds -------------------------------------------------------------------------------- /trace_perf/timer.cpp.db-debug: -------------------------------------------------------------------------------- 1 | it should measure short intervals as short 2 | 5e-06 3 | --- smallest possible measure -------------------------------------------------------------------------------- /unittest.cpp: -------------------------------------------------------------------------------- 1 | #include "unittest.h" 2 | 3 | #include "backtrace.h" 4 | #include "exceptionassert.h" 5 | #include "prettifysegfault.h" 6 | #include "shared_state.h" 7 | #include "tasktimer.h" 8 | #include "timer.h" 9 | #include "verifyexecutiontime.h" 10 | #include "demangle.h" 11 | #include "barrier.h" 12 | #include "shared_state_traits_backtrace.h" 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | using namespace std; 20 | 21 | namespace BacktraceTest { 22 | 23 | string lastname; 24 | 25 | #define RUNTEST(x) do { \ 26 | TaskTimer tt("%s", #x); \ 27 | lastname = #x; \ 28 | x::test (); \ 29 | } while(false) 30 | 31 | int UnitTest:: 32 | test(bool rethrow_exceptions) 33 | { 34 | try { 35 | Timer(); // Init performance counting 36 | TaskTimer tt("Running tests"); 37 | 38 | RUNTEST(Backtrace); 39 | RUNTEST(ExceptionAssert); 40 | RUNTEST(PrettifySegfault); 41 | RUNTEST(Timer); 42 | RUNTEST(shared_state_test); 43 | RUNTEST(VerifyExecutionTime); 44 | RUNTEST(spinning_barrier); 45 | RUNTEST(locking_barrier); 46 | RUNTEST(shared_state_traits_backtrace); 47 | 48 | } catch (const ExceptionAssert& x) { 49 | if (rethrow_exceptions) 50 | throw; 51 | 52 | char const * const * f = boost::get_error_info(x); 53 | int const * l = boost::get_error_info(x); 54 | char const * const * c = boost::get_error_info(x); 55 | std::string const * m = boost::get_error_info(x); 56 | 57 | fflush(stdout); 58 | fprintf(stderr, "%s", 59 | str(boost::format("%s:%d: %s. %s\n" 60 | "%s\n" 61 | " FAILED in %s::test()\n\n") 62 | % (f?*f:0) % (l?*l:-1) % (c?*c:0) % (m?*m:0) % boost::diagnostic_information(x) % lastname ).c_str()); 63 | fflush(stderr); 64 | return 1; 65 | } catch (const exception& x) { 66 | if (rethrow_exceptions) 67 | throw; 68 | 69 | fflush(stdout); 70 | fprintf(stderr, "%s", 71 | str(boost::format("%s\n" 72 | "%s\n" 73 | " FAILED in %s::test()\n\n") 74 | % vartype(x) % boost::diagnostic_information(x) % lastname ).c_str()); 75 | fflush(stderr); 76 | return 1; 77 | } catch (...) { 78 | if (rethrow_exceptions) 79 | throw; 80 | 81 | fflush(stdout); 82 | fprintf(stderr, "%s", 83 | str(boost::format("Not an std::exception\n" 84 | "%s\n" 85 | " FAILED in %s::test()\n\n") 86 | % boost::current_exception_diagnostic_information () % lastname ).c_str()); 87 | fflush(stderr); 88 | return 1; 89 | } 90 | 91 | printf("\n OK\n\n"); 92 | return 0; 93 | } 94 | 95 | } // namespace BacktraceTest 96 | -------------------------------------------------------------------------------- /unittest.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKTRACETEST_UNITTEST_H 2 | #define BACKTRACETEST_UNITTEST_H 3 | 4 | namespace BacktraceTest { 5 | 6 | class UnitTest 7 | { 8 | public: 9 | static int test(bool rethrow_exceptions=true); 10 | }; 11 | 12 | } // namespace BacktraceTest 13 | 14 | #endif // BACKTRACETEST_UNITTEST_H 15 | -------------------------------------------------------------------------------- /unused.h: -------------------------------------------------------------------------------- 1 | #ifndef UNUSED_H 2 | #define UNUSED_H 3 | 4 | // avoid warning: unused variable 5 | #ifdef _MSC_VER 6 | #define UNUSED(X) X 7 | #else 8 | #define UNUSED(X) X __attribute__ ((unused)) 9 | #endif 10 | 11 | #endif // UNUSED_H 12 | -------------------------------------------------------------------------------- /verifyexecutiontime.cpp: -------------------------------------------------------------------------------- 1 | #include "verifyexecutiontime.h" 2 | #include "tasktimer.h" 3 | #include "backtrace.h" 4 | 5 | using namespace std; 6 | 7 | VerifyExecutionTime::report VerifyExecutionTime::default_report_func_; 8 | 9 | void VerifyExecutionTime:: 10 | default_report(float expected_time, float execution_time, const std::string& label) 11 | { 12 | string expected = TaskTimer::timeToString( expected_time ); 13 | string elapsed = TaskTimer::timeToString( execution_time ); 14 | 15 | TaskInfo(boost::format("!!! VerifyExecutionTime: Took %s > %s %s") 16 | % elapsed % expected % label); 17 | } 18 | 19 | 20 | void VerifyExecutionTime:: 21 | set_default_report( report report_func ) 22 | { 23 | default_report_func_ = report_func; 24 | } 25 | 26 | 27 | VerifyExecutionTime::ptr VerifyExecutionTime:: 28 | start( float expected_time, report report_func ) 29 | { 30 | if (!report_func) 31 | { 32 | report_func = default_report_func_; 33 | } 34 | 35 | if (!report_func) 36 | { 37 | // It should print a backtrace by default if no report func is given. 38 | report_func = [](float expected_time, float execution_time){ default_report(expected_time, execution_time, Backtrace::make_string ()); }; 39 | } 40 | 41 | return ptr(new VerifyExecutionTime( expected_time, report_func )); 42 | } 43 | 44 | 45 | VerifyExecutionTime:: 46 | VerifyExecutionTime(float expected_time, report report_func) 47 | : 48 | expected_time_(expected_time), 49 | report_func_(report_func) 50 | { 51 | } 52 | 53 | 54 | VerifyExecutionTime:: 55 | ~VerifyExecutionTime() 56 | { 57 | if (std::uncaught_exception()) 58 | return; 59 | 60 | double execution_time = timer_.elapsed (); 61 | 62 | if (expected_time_ < execution_time) 63 | { 64 | report_func_(expected_time_, execution_time); 65 | } 66 | } 67 | 68 | 69 | ////////////////////////////////// 70 | // VerifyExecutionTime::test 71 | 72 | #include "exceptionassert.h" 73 | //#include 74 | #include 75 | 76 | void VerifyExecutionTime:: 77 | test() 78 | { 79 | // It should warn if it takes longer than specified to execute a scope. 80 | { 81 | float expected_time=0.004, execution_time=0; 82 | 83 | { 84 | VerifyExecutionTime::ptr x = VerifyExecutionTime::start (expected_time, [&](float, float v){ 85 | execution_time = v; 86 | }); 87 | this_thread::sleep_for (chrono::milliseconds(1)); 88 | } 89 | 90 | EXCEPTION_ASSERT_LESS(execution_time, expected_time); 91 | 92 | bool did_report = false; 93 | { 94 | VerifyExecutionTime::ptr x = VerifyExecutionTime::start (0.001, [&did_report](float,float){did_report = true;}); 95 | this_thread::sleep_for (chrono::milliseconds(1)); 96 | 97 | EXCEPTION_ASSERT(!did_report); 98 | } 99 | EXCEPTION_ASSERT(did_report); 100 | } 101 | 102 | // It should print a backtrace by default if no report func is given. 103 | { 104 | // See VerifyExecutionTime::start 105 | } 106 | 107 | // It should not warn about execution time if unwinding from an exception. 108 | { 109 | bool did_report = false; 110 | 111 | try { 112 | VerifyExecutionTime::ptr x = VerifyExecutionTime::start (0.001, [&did_report](float,float){did_report = true;}); 113 | this_thread::sleep_for (chrono::milliseconds(2)); 114 | throw 0; 115 | } catch (int) {} 116 | 117 | EXCEPTION_ASSERT(!did_report); 118 | } 119 | 120 | // It should cause an overhead of less than 1.5 microseconds in a release 121 | // build and less than 3 microseconds in a debug build. 122 | { 123 | bool debug = false; 124 | #ifdef _DEBUG 125 | debug = true; 126 | #endif 127 | 128 | int N = 100000; 129 | Timer t; 130 | for (int i=0; i 7 | #include 8 | 9 | /** 10 | * @brief The VerifyExecutionTime class should warn if it takes longer than 11 | * specified to execute a scope. 12 | * 13 | * It should print a backtrace by default if no report func is given. 14 | * 15 | * It should not warn about execution time if unwinding from an exception. 16 | * 17 | * It should cause an overhead of less than 1.5 microseconds in a release 18 | * build and less than 3 microseconds in a debug build. 19 | * 20 | * Never throw from the report function. Doing so results in undefined 21 | * behaviour. 22 | */ 23 | class VerifyExecutionTime 24 | { 25 | public: 26 | typedef std::shared_ptr ptr; 27 | typedef std::function report; 28 | 29 | static ptr start( float expected_time_, report func=0 ); 30 | 31 | static void default_report( float expected_time, float execution_time, const std::string& label ); 32 | static void set_default_report( report func ); 33 | 34 | ~VerifyExecutionTime(); 35 | 36 | private: 37 | VerifyExecutionTime( float expected_time_, report func=0 ); 38 | 39 | Timer timer_; 40 | float expected_time_; 41 | report report_func_; 42 | static report default_report_func_; 43 | 44 | public: 45 | static void test(); 46 | }; 47 | 48 | #endif // VERIFYEXECUTIONTIME_H 49 | -------------------------------------------------------------------------------- /windows/StackWalker.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustafsson/backtrace/7f2ae53f6ecfe8718f30bee64886ccf103ec537a/windows/StackWalker.cpp -------------------------------------------------------------------------------- /windows/StackWalker.h: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * 3 | * StackWalker.h 4 | * 5 | * 6 | * History: 7 | * 2005-07-27 v1 - First public release on http://www.codeproject.com/ 8 | * (for additional changes see History in 'StackWalker.cpp'! 9 | * 10 | * from http://www.codeproject.com/Articles/11132/Walking-the-callstack 11 | * 12 | **********************************************************************/ 13 | // #pragma once is supported starting with _MCS_VER 1000, 14 | // so we need not to check the version (because we only support _MSC_VER >= 1100)! 15 | #pragma once 16 | 17 | #include 18 | 19 | // special defines for VC5/6 (if no actual PSDK is installed): 20 | #if _MSC_VER < 1300 21 | typedef unsigned __int64 DWORD64, *PDWORD64; 22 | #if defined(_WIN64) 23 | typedef unsigned __int64 SIZE_T, *PSIZE_T; 24 | #else 25 | typedef unsigned long SIZE_T, *PSIZE_T; 26 | #endif 27 | #endif // _MSC_VER < 1300 28 | 29 | class StackWalkerInternal; // forward 30 | class StackWalker 31 | { 32 | public: 33 | typedef enum StackWalkOptions 34 | { 35 | // No addition info will be retrived 36 | // (only the address is available) 37 | RetrieveNone = 0, 38 | 39 | // Try to get the symbol-name 40 | RetrieveSymbol = 1, 41 | 42 | // Try to get the line for this symbol 43 | RetrieveLine = 2, 44 | 45 | // Try to retrieve the module-infos 46 | RetrieveModuleInfo = 4, 47 | 48 | // Also retrieve the version for the DLL/EXE 49 | RetrieveFileVersion = 8, 50 | 51 | // Contains all the abouve 52 | RetrieveVerbose = 0xF, 53 | 54 | // Generate a "good" symbol-search-path 55 | SymBuildPath = 0x10, 56 | 57 | // Also use the public Microsoft-Symbol-Server 58 | SymUseSymSrv = 0x20, 59 | 60 | // Contains all the abouve "Sym"-options 61 | SymAll = 0x30, 62 | 63 | // Contains all options (default) 64 | OptionsAll = 0x3F 65 | } StackWalkOptions; 66 | 67 | StackWalker( 68 | int options = OptionsAll, // 'int' is by design, to combine the enum-flags 69 | LPCSTR szSymPath = NULL, 70 | DWORD dwProcessId = GetCurrentProcessId(), 71 | HANDLE hProcess = GetCurrentProcess() 72 | ); 73 | StackWalker(DWORD dwProcessId, HANDLE hProcess); 74 | virtual ~StackWalker(); 75 | 76 | typedef BOOL (__stdcall *PReadProcessMemoryRoutine)( 77 | HANDLE hProcess, 78 | DWORD64 qwBaseAddress, 79 | PVOID lpBuffer, 80 | DWORD nSize, 81 | LPDWORD lpNumberOfBytesRead, 82 | LPVOID pUserData // optional data, which was passed in "ShowCallstack" 83 | ); 84 | 85 | BOOL LoadModules(); 86 | 87 | BOOL ShowCallstack( 88 | HANDLE hThread = GetCurrentThread(), 89 | const CONTEXT *context = NULL, 90 | PReadProcessMemoryRoutine readMemoryFunction = NULL, 91 | LPVOID pUserData = NULL // optional to identify some data in the 'readMemoryFunction'-callback 92 | ); 93 | 94 | #if _MSC_VER >= 1300 95 | // due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public" 96 | // in older compilers in order to use it... starting with VC7 we can declare it as "protected" 97 | protected: 98 | #endif 99 | enum { STACKWALK_MAX_NAMELEN = 1024 }; // max name length for found symbols 100 | 101 | protected: 102 | // Entry for each Callstack-Entry 103 | typedef struct CallstackEntry 104 | { 105 | DWORD64 offset; // if 0, we have no valid entry 106 | CHAR name[STACKWALK_MAX_NAMELEN]; 107 | CHAR undName[STACKWALK_MAX_NAMELEN]; 108 | CHAR undFullName[STACKWALK_MAX_NAMELEN]; 109 | DWORD64 offsetFromSmybol; 110 | DWORD offsetFromLine; 111 | DWORD lineNumber; 112 | CHAR lineFileName[STACKWALK_MAX_NAMELEN]; 113 | DWORD symType; 114 | LPCSTR symTypeString; 115 | CHAR moduleName[STACKWALK_MAX_NAMELEN]; 116 | DWORD64 baseOfImage; 117 | CHAR loadedImageName[STACKWALK_MAX_NAMELEN]; 118 | } CallstackEntry; 119 | 120 | typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry}; 121 | 122 | virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName); 123 | virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion); 124 | virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry &entry); 125 | virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr); 126 | virtual void OnOutput(LPCSTR szText); 127 | virtual void OnCallStackOutput(LPCSTR szText) { OnOutput(szText); } 128 | 129 | StackWalkerInternal *m_sw; 130 | HANDLE m_hProcess; 131 | DWORD m_dwProcessId; 132 | BOOL m_modulesLoaded; 133 | LPSTR m_szSymPath; 134 | 135 | int m_options; 136 | 137 | static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead); 138 | 139 | friend StackWalkerInternal; 140 | }; 141 | 142 | 143 | // The "ugly" assembler-implementation is needed for systems before XP 144 | // If you have a new PSDK and you only compile for XP and later, then you can use 145 | // the "RtlCaptureContext" 146 | // Currently there is no define which determines the PSDK-Version... 147 | // So we just use the compiler-version (and assumes that the PSDK is 148 | // the one which was installed by the VS-IDE) 149 | 150 | // INFO: If you want, you can use the RtlCaptureContext if you only target XP and later... 151 | // But I currently use it in x64/IA64 environments... 152 | //#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400) 153 | 154 | #if defined(_M_IX86) 155 | #ifdef CURRENT_THREAD_VIA_EXCEPTION 156 | // TODO: The following is not a "good" implementation, 157 | // because the callstack is only valid in the "__except" block... 158 | #define GET_CURRENT_CONTEXT(c, contextFlags) \ 159 | do { \ 160 | memset(&c, 0, sizeof(CONTEXT)); \ 161 | EXCEPTION_POINTERS *pExp = NULL; \ 162 | __try { \ 163 | throw 0; \ 164 | } __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \ 165 | if (pExp != NULL) \ 166 | memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \ 167 | c.ContextFlags = contextFlags; \ 168 | } while(0); 169 | #else 170 | // The following should be enough for walking the callstack... 171 | #define GET_CURRENT_CONTEXT(c, contextFlags) \ 172 | do { \ 173 | memset(&c, 0, sizeof(CONTEXT)); \ 174 | c.ContextFlags = contextFlags; \ 175 | __asm call x \ 176 | __asm x: pop eax \ 177 | __asm mov c.Eip, eax \ 178 | __asm mov c.Ebp, ebp \ 179 | __asm mov c.Esp, esp \ 180 | } while(0); 181 | #endif 182 | 183 | #else 184 | 185 | // The following is defined for x86 (XP and higher), x64 and IA64: 186 | #define GET_CURRENT_CONTEXT(c, contextFlags) \ 187 | do { \ 188 | memset(&c, 0, sizeof(CONTEXT)); \ 189 | c.ContextFlags = contextFlags; \ 190 | RtlCaptureContext(&c); \ 191 | } while(0); 192 | #endif 193 | --------------------------------------------------------------------------------