| // Copyright (c) 2013, Kenton Varda <[email protected]> |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // 1. Redistributions of source code must retain the above copyright notice, this |
| // list of conditions and the following disclaimer. |
| // 2. Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
| // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "exception.h" |
| #include "string.h" |
| #include "debug.h" |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <exception> |
| |
| #ifndef __CYGWIN__ |
| #include <execinfo.h> |
| #endif |
| |
| #if defined(__linux__) && defined(KJ_DEBUG) |
| #include <stdio.h> |
| #include <pthread.h> |
| #endif |
| |
| namespace kj { |
| |
| namespace { |
| |
| String getStackSymbols(ArrayPtr<void* const> trace) { |
| #if defined(__linux__) && defined(KJ_DEBUG) |
| // We want to generate a human-readable stack trace. |
| |
| // TODO(someday): It would be really great if we could avoid farming out to addr2line and do |
| // this all in-process, but that may involve onerous requirements like large library |
| // dependencies or using -rdynamic. |
| |
| // The environment manipulation is not thread-safe, so lock a mutex. This could still be |
| // problematic if another thread is manipulating the environment in unrelated code, but there's |
| // not much we can do about that. This is debug-only anyway and only an issue when LD_PRELOAD |
| // is in use. |
| static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; |
| pthread_mutex_lock(&mutex); |
| |
| // Don't heapcheck / intercept syscalls for addr2line. |
| const char* preload = getenv("LD_PRELOAD"); |
| String oldPreload; |
| if (preload != nullptr) { |
| oldPreload = heapString(preload); |
| unsetenv("LD_PRELOAD"); |
| } |
| |
| // Get executable name from /proc/self/exe, then pass it and the stack trace to addr2line to |
| // get file/line pairs. |
| char exe[512]; |
| ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe)); |
| if (n < 0 || n >= static_cast<ssize_t>(sizeof(exe))) { |
| return nullptr; |
| } |
| exe[n] = '\0'; |
| |
| String lines[8]; |
| |
| FILE* p = popen(str("addr2line -e ", exe, ' ', strArray(trace, " ")).cStr(), "r"); |
| if (p == nullptr) { |
| return nullptr; |
| } |
| |
| char line[512]; |
| size_t i = 0; |
| while (i < KJ_ARRAY_SIZE(lines) && fgets(line, sizeof(line), p) != nullptr) { |
| // Don't include exception-handling infrastructure in stack trace. |
| if (i == 0 && |
| (strstr(line, "kj/common.c++") != nullptr || |
| strstr(line, "kj/exception.") != nullptr || |
| strstr(line, "kj/debug.") != nullptr)) { |
| continue; |
| } |
| |
| size_t len = strlen(line); |
| if (len > 0 && line[len-1] == '\n') line[len-1] = '\0'; |
| lines[i++] = str("\n", line, ": called here"); |
| } |
| |
| // Skip remaining input. |
| while (fgets(line, sizeof(line), p) != nullptr) {} |
| |
| pclose(p); |
| |
| if (oldPreload != nullptr) { |
| setenv("LD_PRELOAD", oldPreload.cStr(), true); |
| } |
| |
| pthread_mutex_unlock(&mutex); |
| |
| return strArray(arrayPtr(lines, i), ""); |
| #else |
| return nullptr; |
| #endif |
| } |
| |
| } // namespace |
| |
| ArrayPtr<const char> KJ_STRINGIFY(Exception::Nature nature) { |
| static const char* NATURE_STRINGS[] = { |
| "requirement not met", |
| "bug in code", |
| "error from OS", |
| "network failure", |
| "error" |
| }; |
| |
| const char* s = NATURE_STRINGS[static_cast<uint>(nature)]; |
| return arrayPtr(s, strlen(s)); |
| } |
| |
| ArrayPtr<const char> KJ_STRINGIFY(Exception::Durability durability) { |
| static const char* DURABILITY_STRINGS[] = { |
| "temporary", |
| "permanent" |
| }; |
| |
| const char* s = DURABILITY_STRINGS[static_cast<uint>(durability)]; |
| return arrayPtr(s, strlen(s)); |
| } |
| |
| String KJ_STRINGIFY(const Exception& e) { |
| uint contextDepth = 0; |
| |
| Maybe<const Exception::Context&> contextPtr = e.getContext(); |
| for (;;) { |
| KJ_IF_MAYBE(c, contextPtr) { |
| ++contextDepth; |
| contextPtr = c->next; |
| } else { |
| break; |
| } |
| } |
| |
| Array<String> contextText = heapArray<String>(contextDepth); |
| |
| contextDepth = 0; |
| contextPtr = e.getContext(); |
| for (;;) { |
| KJ_IF_MAYBE(c, contextPtr) { |
| contextText[contextDepth++] = |
| str(c->file, ":", c->line, ": context: ", c->description, "\n"); |
| contextPtr = c->next; |
| } else { |
| break; |
| } |
| } |
| |
| return str(strArray(contextText, ""), |
| e.getFile(), ":", e.getLine(), ": ", e.getNature(), |
| e.getDurability() == Exception::Durability::TEMPORARY ? " (temporary)" : "", |
| e.getDescription() == nullptr ? "" : ": ", e.getDescription(), |
| e.getStackTrace().size() > 0 ? "\nstack: " : "", strArray(e.getStackTrace(), " "), |
| getStackSymbols(e.getStackTrace())); |
| } |
| |
| Exception::Exception(Nature nature, Durability durability, const char* file, int line, |
| String description) noexcept |
| : file(file), line(line), nature(nature), durability(durability), |
| description(mv(description)) { |
| #ifdef __CYGWIN__ |
| traceCount = 0; |
| #else |
| traceCount = backtrace(trace, 16); |
| #endif |
| } |
| |
| Exception::Exception(Nature nature, Durability durability, String file, int line, |
| String description) noexcept |
| : ownFile(kj::mv(file)), file(ownFile.cStr()), line(line), nature(nature), |
| durability(durability), description(mv(description)) { |
| #ifdef __CYGWIN__ |
| traceCount = 0; |
| #else |
| traceCount = backtrace(trace, 16); |
| #endif |
| } |
| |
| Exception::Exception(const Exception& other) noexcept |
| : file(other.file), line(other.line), nature(other.nature), durability(other.durability), |
| description(heapString(other.description)), traceCount(other.traceCount) { |
| if (file == other.ownFile.cStr()) { |
| ownFile = heapString(other.ownFile); |
| file = ownFile.cStr(); |
| } |
| |
| memcpy(trace, other.trace, sizeof(trace[0]) * traceCount); |
| |
| KJ_IF_MAYBE(c, other.context) { |
| context = heap(*c); |
| } |
| } |
| |
| Exception::~Exception() noexcept {} |
| |
| Exception::Context::Context(const Context& other) noexcept |
| : file(other.file), line(other.line), description(str(other.description)) { |
| KJ_IF_MAYBE(n, other.next) { |
| next = heap(*n); |
| } |
| } |
| |
| void Exception::wrapContext(const char* file, int line, String&& description) { |
| context = heap<Context>(file, line, mv(description), mv(context)); |
| } |
| |
| class ExceptionImpl: public Exception, public std::exception { |
| public: |
| inline ExceptionImpl(Exception&& other): Exception(mv(other)) {} |
| ExceptionImpl(const ExceptionImpl& other): Exception(other) { |
| // No need to copy whatBuffer since it's just to hold the return value of what(). |
| } |
| |
| const char* what() const noexcept override; |
| |
| private: |
| mutable String whatBuffer; |
| }; |
| |
| const char* ExceptionImpl::what() const noexcept { |
| whatBuffer = str(*this); |
| return whatBuffer.begin(); |
| } |
| |
| // ======================================================================================= |
| |
| namespace { |
| |
| #if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 8) |
| #define thread_local __thread |
| #endif |
| |
| thread_local ExceptionCallback* threadLocalCallback = nullptr; |
| |
| class RepeatChar { |
| // A pseudo-sequence of characters that is actually just one character repeated. |
| // |
| // TODO(cleanup): Put this somewhere reusable. Maybe templatize it too. |
| |
| public: |
| inline RepeatChar(char c, uint size): c(c), size_(size) {} |
| |
| class Iterator { |
| public: |
| Iterator() = default; |
| inline Iterator(char c, uint index): c(c), index(index) {} |
| |
| inline Iterator& operator++() { ++index; return *this; } |
| inline Iterator operator++(int) { ++index; return Iterator(c, index - 1); } |
| |
| inline char operator*() const { return c; } |
| |
| inline bool operator==(const Iterator& other) const { return index == other.index; } |
| inline bool operator!=(const Iterator& other) const { return index != other.index; } |
| |
| private: |
| char c; |
| uint index; |
| }; |
| |
| inline uint size() const { return size_; } |
| inline Iterator begin() const { return Iterator(c, 0); } |
| inline Iterator end() const { return Iterator(c, size_); } |
| |
| private: |
| char c; |
| uint size_; |
| }; |
| inline RepeatChar KJ_STRINGIFY(RepeatChar value) { return value; } |
| |
| } // namespace |
| |
| ExceptionCallback::ExceptionCallback(): next(getExceptionCallback()) { |
| char stackVar; |
| ptrdiff_t offset = reinterpret_cast<char*>(this) - &stackVar; |
| KJ_ASSERT(offset < 4096 && offset > -4096, |
| "ExceptionCallback must be allocated on the stack."); |
| |
| threadLocalCallback = this; |
| } |
| |
| ExceptionCallback::ExceptionCallback(ExceptionCallback& next): next(next) {} |
| |
| ExceptionCallback::~ExceptionCallback() noexcept(false) { |
| if (&next != this) { |
| threadLocalCallback = &next; |
| } |
| } |
| |
| void ExceptionCallback::onRecoverableException(Exception&& exception) { |
| next.onRecoverableException(mv(exception)); |
| } |
| |
| void ExceptionCallback::onFatalException(Exception&& exception) { |
| next.onFatalException(mv(exception)); |
| } |
| |
| void ExceptionCallback::logMessage(const char* file, int line, int contextDepth, String&& text) { |
| next.logMessage(file, line, contextDepth, mv(text)); |
| } |
| |
| class ExceptionCallback::RootExceptionCallback: public ExceptionCallback { |
| public: |
| RootExceptionCallback(): ExceptionCallback(*this) {} |
| |
| void onRecoverableException(Exception&& exception) override { |
| #if KJ_NO_EXCEPTIONS |
| logException(mv(exception)); |
| #else |
| throw ExceptionImpl(mv(exception)); |
| #endif |
| } |
| |
| void onFatalException(Exception&& exception) override { |
| #if KJ_NO_EXCEPTIONS |
| logException(mv(exception)); |
| #else |
| throw ExceptionImpl(mv(exception)); |
| #endif |
| } |
| |
| void logMessage(const char* file, int line, int contextDepth, String&& text) override { |
| text = str(RepeatChar('_', contextDepth), file, ":", line, ": ", mv(text)); |
| |
| StringPtr textPtr = text; |
| |
| while (text != nullptr) { |
| ssize_t n = write(STDERR_FILENO, textPtr.begin(), textPtr.size()); |
| if (n <= 0) { |
| // stderr is broken. Give up. |
| return; |
| } |
| textPtr = textPtr.slice(n); |
| } |
| } |
| |
| private: |
| void logException(Exception&& e) { |
| // We intentionally go back to the top exception callback on the stack because we don't want to |
| // bypass whatever log processing is in effect. |
| // |
| // We intentionally don't log the context since it should get re-added by the exception callback |
| // anyway. |
| getExceptionCallback().logMessage(e.getFile(), e.getLine(), 0, str( |
| e.getNature(), e.getDurability() == Exception::Durability::TEMPORARY ? " (temporary)" : "", |
| e.getDescription() == nullptr ? "" : ": ", e.getDescription(), |
| "\nstack: ", strArray(e.getStackTrace(), " "), "\n")); |
| } |
| }; |
| |
| ExceptionCallback& getExceptionCallback() { |
| static ExceptionCallback::RootExceptionCallback defaultCallback; |
| ExceptionCallback* scoped = threadLocalCallback; |
| return scoped != nullptr ? *scoped : defaultCallback; |
| } |
| |
| // ======================================================================================= |
| |
| namespace _ { // private |
| |
| #if __GNUC__ |
| |
| // Horrible -- but working -- hack: We can dig into __cxa_get_globals() in order to extract the |
| // count of uncaught exceptions. This function is part of the C++ ABI implementation used on Linux, |
| // OSX, and probably other platforms that use GCC. Unfortunately, __cxa_get_globals() is only |
| // actually defined in cxxabi.h on some platforms (e.g. Linux, but not OSX), and even where it is |
| // defined, it returns an incomplete type. Here we use the same hack used by Evgeny Panasyuk: |
| // https://github.com/panaseleus/stack_unwinding/blob/master/boost/exception/uncaught_exception_count.hpp |
| // |
| // Notice that a similar hack is possible on MSVC -- if its C++11 support ever gets to the point of |
| // supporting KJ in the first place. |
| // |
| // It appears likely that a future version of the C++ standard may include an |
| // uncaught_exception_count() function in the standard library, or an equivalent language feature. |
| // Some discussion: |
| // https://groups.google.com/a/isocpp.org/d/msg/std-proposals/HglEslyZFYs/kKdu5jJw5AgJ |
| |
| struct FakeEhGlobals { |
| // Fake |
| |
| void* caughtExceptions; |
| uint uncaughtExceptions; |
| }; |
| |
| // Because of the 'extern "C"', the symbol name is not mangled and thus the namespace is effectively |
| // ignored for linking. Thus it doesn't matter that we are declaring __cxa_get_globals() in a |
| // different namespace from the ABI's definition. |
| extern "C" { |
| FakeEhGlobals* __cxa_get_globals(); |
| } |
| |
| uint uncaughtExceptionCount() { |
| // TODO(perf): Use __cxa_get_globals_fast()? Requires that __cxa_get_globals() has been called |
| // from somewhere. |
| return __cxa_get_globals()->uncaughtExceptions; |
| } |
| |
| #else |
| #error "This needs to be ported to your compiler / C++ ABI." |
| #endif |
| |
| } // namespace _ (private) |
| |
| UnwindDetector::UnwindDetector(): uncaughtCount(_::uncaughtExceptionCount()) {} |
| |
| bool UnwindDetector::isUnwinding() const { |
| return _::uncaughtExceptionCount() > uncaughtCount; |
| } |
| |
| void UnwindDetector::catchExceptionsAsSecondaryFaults(_::Runnable& runnable) const { |
| // TODO(someday): Attach the secondary exception to whatever primary exception is causing |
| // the unwind. For now we just drop it on the floor as this is probably fine most of the |
| // time. |
| runCatchingExceptions(runnable); |
| } |
| |
| namespace _ { // private |
| |
| class RecoverableExceptionCatcher: public ExceptionCallback { |
| // Catches a recoverable exception without using try/catch. Used when compiled with |
| // -fno-exceptions. |
| |
| public: |
| virtual ~RecoverableExceptionCatcher() noexcept(false) {} |
| |
| void onRecoverableException(Exception&& exception) override { |
| if (caught == nullptr) { |
| caught = mv(exception); |
| } else { |
| // TODO(someday): Consider it a secondary fault? |
| } |
| } |
| |
| Maybe<Exception> caught; |
| }; |
| |
| Maybe<Exception> runCatchingExceptions(Runnable& runnable) { |
| #if KJ_NO_EXCEPTIONS |
| RecoverableExceptionCatcher catcher; |
| runnable.run(); |
| return mv(catcher.caught); |
| #else |
| try { |
| runnable.run(); |
| return nullptr; |
| } catch (Exception& e) { |
| return kj::mv(e); |
| } catch (std::exception& e) { |
| return Exception(Exception::Nature::OTHER, Exception::Durability::PERMANENT, |
| "(unknown)", -1, str("std::exception: ", e.what())); |
| } catch (...) { |
| return Exception(Exception::Nature::OTHER, Exception::Durability::PERMANENT, |
| "(unknown)", -1, str("Unknown non-KJ exception.")); |
| } |
| #endif |
| } |
| |
| } // namespace _ (private) |
| |
| } // namespace kj |