diff options
author | Simon Hausmann <simon.hausmann@digia.com> | 2013-05-12 12:07:15 +0200 |
---|---|---|
committer | Lars Knoll <lars.knoll@digia.com> | 2013-05-15 23:30:42 +0200 |
commit | 69c6209e1c337af1cc55f48c78937083dd2eba7e (patch) | |
tree | b42d7d902b7a255d7c5cdd83213d38f3434e3f0e | |
parent | 727bdfa0d56cfdc3f327e4cd268b5e2ea10c5931 (diff) |
Fix exception handling not working reliably on x86/x86-64 Linux (Part 1)
The registration of the unwind tables is done through the interposition
of the _Unwind_Find_FDE symbol from libgcc. Unfortunately that interposition
breaks when libgcc happens to come first in the linker scope. As it turns
out, the order is not for us to control, therefore the interposition may not
always work and our JIT generated functions may not get their unwind information
found at exception throwing time. That results in the program aborting with
an uncaught exception.
The proposed solution of replacing the interposition approach is two-fold:
(1) Go back to calling __register_frame explicitly, but only for functions that
exception _may_ pass through. In addition the performance of scalability of the
objects registered with __register_frame is a known issue upstream, as the LLVM
JIT also triggers the issue. It is being tracked at
http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56460
(2) Instead of registering one FDE per function, we can theoretically register
one FDE for _all_ JIT generate functions, because they all use the same stack
layout that has exactly the same unwinding requirements from any function call
site. Since we can't guarantee the presence of all JIT generated code within
the same contiguous memory area, we can at least do it per executable memory
allocation chunk (page size). One chunk can contain many functions.
This patch implements part (1) by delaying the registration of the FDE to right
before the exception gets thrown and then walks up the context chain. The test
cases that were previously slowed down before commit
b83696f9896401dd5e99eed35e36628710e44eeb are not affected because the many
temporary "eval" functions are never subject to exceptions.
Change-Id: I7cb8262e3e1f27d6de71a1e42be8a538cba6513f
Reviewed-by: Lars Knoll <lars.knoll@digia.com>
-rw-r--r-- | src/qml/qml/v4/qv4runtime.cpp | 14 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4unwindhelper.cpp | 1 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4unwindhelper_p-arm.h | 5 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4unwindhelper_p-dw2.h | 59 | ||||
-rw-r--r-- | src/qml/qml/v4/qv4unwindhelper_p.h | 1 |
5 files changed, 32 insertions, 48 deletions
diff --git a/src/qml/qml/v4/qv4runtime.cpp b/src/qml/qml/v4/qv4runtime.cpp index 4d66312f95..f0df925d3c 100644 --- a/src/qml/qml/v4/qv4runtime.cpp +++ b/src/qml/qml/v4/qv4runtime.cpp @@ -49,6 +49,7 @@ #include "qv4stringobject_p.h" #include "qv4lookup_p.h" #include "qv4function_p.h" +#include "qv4unwindhelper_p.h" #include "private/qlocale_tools_p.h" #include <QtCore/qmath.h> @@ -951,6 +952,19 @@ void __qmljs_throw(ExecutionContext *context, const Value &value) if (context->engine->debugger) context->engine->debugger->aboutToThrow(value); + for (ExecutionContext *ctx = context; ctx; ctx = ctx->parent) { + if (CallContext *callCtx = ctx->asCallContext()) + if (FunctionObject *fobj = callCtx->function) + if (Function *fun = fobj->function) + UnwindHelper::ensureUnwindInfo(fun); + for (ExecutionContext::EvalCode *code = ctx->currentEvalCode; + code; code = code->next) + UnwindHelper::ensureUnwindInfo(code->function); + } + + if (context->engine->globalCode) + UnwindHelper::ensureUnwindInfo(context->engine->globalCode); + #if USE(LIBUNWIND_DEBUG) printf("about to throw exception. walking stack first with libunwind:\n"); unw_cursor_t cursor; unw_context_t uc; diff --git a/src/qml/qml/v4/qv4unwindhelper.cpp b/src/qml/qml/v4/qv4unwindhelper.cpp index 0476eb324c..90750e9fe3 100644 --- a/src/qml/qml/v4/qv4unwindhelper.cpp +++ b/src/qml/qml/v4/qv4unwindhelper.cpp @@ -29,6 +29,7 @@ #ifdef USE_NULL_HELPER using namespace QV4; +void UnwindHelper::ensureUnwindInfo(Function *function) {Q_UNUSED(function);} void UnwindHelper::registerFunction(Function *function) {Q_UNUSED(function);} void UnwindHelper::registerFunctions(QVector<Function *> functions) {Q_UNUSED(functions);} void UnwindHelper::deregisterFunction(Function *function) {Q_UNUSED(function);} diff --git a/src/qml/qml/v4/qv4unwindhelper_p-arm.h b/src/qml/qml/v4/qv4unwindhelper_p-arm.h index 69d02a2ba8..92e38f7c80 100644 --- a/src/qml/qml/v4/qv4unwindhelper_p-arm.h +++ b/src/qml/qml/v4/qv4unwindhelper_p-arm.h @@ -116,6 +116,11 @@ void UnwindHelper::registerFunctions(QVector<Function *> functions) allFunctions.insert(reinterpret_cast<quintptr>(f->code), f); } +void UnwindHelper::ensureUnwindInfo(Function *function) +{ + Q_UNUSED(function); +} + int UnwindHelper::unwindInfoSize() { return 2 * sizeof(unsigned int) // 2 extbl entries diff --git a/src/qml/qml/v4/qv4unwindhelper_p-dw2.h b/src/qml/qml/v4/qv4unwindhelper_p-dw2.h index 9760631494..841636d912 100644 --- a/src/qml/qml/v4/qv4unwindhelper_p-dw2.h +++ b/src/qml/qml/v4/qv4unwindhelper_p-dw2.h @@ -81,8 +81,13 @@ void writeIntPtrValue(unsigned char *addr, intptr_t val) } } // anonymous namespace -static void ensureUnwindInfo(Function *f) +extern "C" void __register_frame(void *fde); +extern "C" void __deregister_frame(void *fde); + +void UnwindHelper::ensureUnwindInfo(Function *f) { + if (!f->codeRef) + return; // Not a JIT generated function if (!f->unwindInfo.isEmpty()) return; QByteArray info; @@ -97,29 +102,22 @@ static void ensureUnwindInfo(Function *f) writeIntPtrValue(cie_and_fde + address_range_offset, f->codeSize); f->unwindInfo = info; + __register_frame(f->unwindInfo.data() + fde_offset); } -#if defined(Q_OS_DARWIN) -extern "C" void __register_frame(void *fde); -extern "C" void __deregister_frame(void *fde); -#endif - static void registerFunctionUnlocked(Function *f) { - allFunctions.insert(reinterpret_cast<quintptr>(f->code), f); -#if defined(Q_OS_DARWIN) - ensureUnwindInfo(f); - __register_frame(f->unwindInfo.data() + fde_offset); -#endif + quintptr addr = reinterpret_cast<quintptr>(f->code); + if (allFunctions.contains(addr)) + return; + allFunctions.insert(addr, f); } static void deregisterFunctionUnlocked(Function *f) { allFunctions.remove(reinterpret_cast<quintptr>(f->code)); -#if defined(Q_OS_DARWIN) if (!f->unwindInfo.isEmpty()) __deregister_frame(f->unwindInfo.data() + fde_offset); -#endif } void UnwindHelper::registerFunction(Function *function) @@ -150,39 +148,4 @@ void UnwindHelper::deregisterFunctions(QVector<Function *> functions) } -#if defined(Q_OS_LINUX) -extern "C" { - -struct bases -{ - void *tbase; - void *dbase; - void *func; -}; - -Q_QML_EXPORT void *_Unwind_Find_FDE(void *pc, struct bases *bases) -{ - typedef void *(*Old_Unwind_Find_FDE)(void *pc, struct bases *bases); - static Old_Unwind_Find_FDE oldFunction = 0; - if (!oldFunction) - oldFunction = (Old_Unwind_Find_FDE)dlsym(RTLD_NEXT, "_Unwind_Find_FDE"); - - { - QMutexLocker locker(&QV4::functionProtector); - QV4::Function *function = QV4::lookupFunction(pc); - if (function) { - bases->tbase = 0; - bases->dbase = 0; - bases->func = reinterpret_cast<void*>(function->code); - QV4::ensureUnwindInfo(function); - return function->unwindInfo.data() + QV4::fde_offset; - } - } - - return oldFunction(pc, bases); -} - -} -#endif - #endif // QV4UNWINDHELPER_PDW2_H diff --git a/src/qml/qml/v4/qv4unwindhelper_p.h b/src/qml/qml/v4/qv4unwindhelper_p.h index 04c8c0ac94..72bf56c531 100644 --- a/src/qml/qml/v4/qv4unwindhelper_p.h +++ b/src/qml/qml/v4/qv4unwindhelper_p.h @@ -10,6 +10,7 @@ struct Function; class UnwindHelper { public: + static void ensureUnwindInfo(Function *function); static void registerFunction(Function *function); static void registerFunctions(QVector<Function *> functions); static void deregisterFunction(Function *function); |