diff options
author | Bernd Weimer <bernd.weimer@pelagicore.com> | 2018-09-11 14:21:08 +0200 |
---|---|---|
committer | Robert Griebl <robert.griebl@pelagicore.com> | 2018-09-17 11:50:16 +0000 |
commit | 1feaa3eeb0a4b8466ce8e89d97b7019fa6612397 (patch) | |
tree | be07e9621acd7ae1d209e0b7acc79f97fdb3d623 | |
parent | c4dd61a7063e9b201fc49df62322a3f6c8f8b1d8 (diff) | |
download | qtapplicationmanager-1feaa3eeb0a4b8466ce8e89d97b7019fa6612397.tar.gz |
Add DLT crash logging and QML stack trace
When the System-UI or an application crashes we try to get a QML
stack trace in addition to the (C++) backtrace. Both traces will be
written to DLT if enabled, in addition to the terminal output.
Crash tests have been added, as well.
Change-Id: Ief6058c7d767f4a7a748aa68d7b658e733cd2cd9
Reviewed-by: Robert Griebl <robert.griebl@pelagicore.com>
-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | doc/configuration.qdoc | 5 | ||||
-rw-r--r-- | src/common-lib/crashhandler.cpp | 136 | ||||
-rw-r--r-- | src/common-lib/crashhandler.h | 3 | ||||
-rw-r--r-- | src/common-lib/logging.cpp | 12 | ||||
-rw-r--r-- | src/common-lib/logging.h | 2 | ||||
-rw-r--r-- | src/launchers/qml/main.cpp | 1 | ||||
-rw-r--r-- | src/main-lib/main.cpp | 1 | ||||
-rw-r--r-- | tests/qml/crash/am-config.yaml | 17 | ||||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/app.qml | 67 | ||||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/icon.png | bin | 0 -> 1486 bytes | |||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/info.yaml | 12 | ||||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/terminator2/qmldir | 2 | ||||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.cpp | 105 | ||||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.h | 73 | ||||
-rw-r--r-- | tests/qml/crash/apps/tld.test.crash/terminator2/terminator2.pro | 18 | ||||
-rw-r--r-- | tests/qml/crash/crash.pro | 4 | ||||
-rw-r--r-- | tests/qml/crash/tst_crash.qml | 98 | ||||
-rw-r--r-- | tests/qml/qml.pro | 4 |
19 files changed, 534 insertions, 31 deletions
@@ -37,9 +37,14 @@ src/**/*.sh doc/**/*.sh src/tools/*/*_interface.* src/tools/*/*_adaptor.* +src/dbus-lib/*_adaptor.h +src/launcher-lib/*-qtam-extension* +src/window-lib/*-qtam-extension* src/tools/testrunner/build-config.yaml src/tools/testrunner/config.qrc src/tools/appman/build-config.yaml src/tools/appman/config.qrc doc/codeattributions.qdoc +examples/applicationmanager/custom-appman/custom-appman +tests/qml/crash/apps/tld.test.crash/terminator2/Terminator/ diff --git a/doc/configuration.qdoc b/doc/configuration.qdoc index f5d7d9ad..2e549a92 100644 --- a/doc/configuration.qdoc +++ b/doc/configuration.qdoc @@ -586,6 +586,11 @@ The following conditions will be handled: \li Try to print a readable backtrace. Will use the primitive backtrace functionality from glibc, unless libbacktrace was enabled at configure time (default: true). \row + \li \c printQmlStack + \li bool + \li Try to print a readable QML stack trace. This is similar to \c printBacktrace above, but + prints the current QML function stack when the crash occurred (default: true). +\row \li \c dumpCore \li bool \li Will end the process via \c abort instead of \c _exit. This will dump a \c core file, diff --git a/src/common-lib/crashhandler.cpp b/src/common-lib/crashhandler.cpp index 3b0ffb97..885aa3d7 100644 --- a/src/common-lib/crashhandler.cpp +++ b/src/common-lib/crashhandler.cpp @@ -51,10 +51,19 @@ void CrashHandler::setCrashActionConfiguration(const QVariantMap &config) Q_UNUSED(config) } +void CrashHandler::setQmlEngine(QQmlEngine *engine) +{ + Q_UNUSED(engine) +} + QT_END_NAMESPACE_AM #else +#include <QQmlEngine> +#include <QtQml/private/qv4engine_p.h> +#include <QtQml/private/qv8engine_p.h> + #include <cxxabi.h> #include <execinfo.h> #include <setjmp.h> @@ -67,12 +76,16 @@ QT_END_NAMESPACE_AM #endif #include "unixsignalhandler.h" +#include "logging.h" #include "utilities.h" #include "processtitle.h" QT_BEGIN_NAMESPACE_AM +enum PrintDestination { Console, Dlt }; + static bool printBacktrace; +static bool printQmlStack; static bool useAnsiColor; static bool dumpCore; static int waitForGdbAttach; @@ -80,6 +93,8 @@ static int waitForGdbAttach; static char *demangleBuffer; static size_t demangleBufferSize; +static QQmlEngine *qmlEngine; + // this will make it run before all other static constructor functions static void initBacktrace() __attribute__((constructor(101))); @@ -88,11 +103,17 @@ static Q_NORETURN void crashHandler(const char *why, int stackFramesToIgnore); void CrashHandler::setCrashActionConfiguration(const QVariantMap &config) { printBacktrace = config.value(qSL("printBacktrace"), printBacktrace).toBool(); + printQmlStack = config.value(qSL("printQmlStack"), printQmlStack).toBool(); waitForGdbAttach = config.value(qSL("waitForGdbAttach"), waitForGdbAttach).toInt() * timeoutFactor(); dumpCore = config.value(qSL("dumpCore"), dumpCore).toBool(); } +void CrashHandler::setQmlEngine(QQmlEngine *engine) +{ + qmlEngine = engine; +} + static void initBacktrace() { // This can catch and pretty-print all of the following: @@ -112,6 +133,7 @@ static void initBacktrace() // throw std::logic_error("test output"); printBacktrace = true; + printQmlStack = true; dumpCore = true; waitForGdbAttach = 0; @@ -161,15 +183,37 @@ static void initBacktrace() }); } -static void crashHandler(const char *why, int stackFramesToIgnore) +static void printMsgToConsole(const char *format, ...) Q_ATTRIBUTE_FORMAT_PRINTF(1, 2); +static void printMsgToConsole(const char *format, ...) +{ + va_list arglist; + va_start(arglist, format); + vfprintf(stderr, format, arglist); + va_end(arglist); + fputs("\n", stderr); +} + +static void printMsgToDlt(const char *format, ...) Q_ATTRIBUTE_FORMAT_PRINTF(1, 2); +static void printMsgToDlt(const char *format, ...) +{ + va_list arglist; + va_start(arglist, format); + Logging::logToDlt(QtMsgType::QtFatalMsg, QMessageLogContext(), QString::vasprintf(format, arglist)); + va_end(arglist); +} + +static void printCrashInfo(PrintDestination dest, const char *why, int stackFramesToIgnore) { - pid_t pid = getpid(); char who[256]; int whoLen = readlink("/proc/self/exe", who, sizeof(who) -1); who[qMax(0, whoLen)] = '\0'; const char *title = ProcessTitle::title(); - fprintf(stderr, "\n*** process %s (%d) crashed ***\n\n > why: %s\n", title ? title : who, pid, why); + using printMsgType = void (*)(const char *format, ...); + static printMsgType printMsg; + printMsg = (dest == Dlt) ? printMsgToDlt : printMsgToConsole; + + printMsg("\n*** process %s (%d) crashed ***\n\n > why: %s", title ? title : who, getpid(), why); if (printBacktrace) { #if defined(AM_USE_LIBBACKTRACE) && defined(BACKTRACE_SUPPORTED) @@ -179,29 +223,28 @@ static void crashHandler(const char *why, int stackFramesToIgnore) int level; }; - static auto printBacktraceLine = [](int level, const char *symbol, uintptr_t offset, const char *file = nullptr, int line = -1) { - const char *fmt1 = " %3d: %s [%" PRIxPTR "]"; - const char *fmt2 = " in %s:%d"; - if (useAnsiColor) { - fmt1 = " %3d: \x1b[1m%s\x1b[0m [\x1b[36m%" PRIxPTR "\x1b[0m]"; - fmt2 = " in \x1b[35m%s\x1b[0m:\x1b[35;1m%d\x1b[0m"; + static auto printBacktraceLine = [](int level, const char *symbol, uintptr_t offset, + const char *file = nullptr, int line = -1) { + if (file) { + printMsg(useAnsiColor ? " %3d: \x1b[1m%s\x1b[0m [\x1b[36m%" PRIxPTR "\x1b[0m]" + " in \x1b[35m%s\x1b[0m:\x1b[35;1m%d\x1b[0m" + : " %3d: %s [%" PRIxPTR "] in %s:%d", level, symbol, offset, file, line); + } else { + printMsg(useAnsiColor ? " %3d: \x1b[1m%s\x1b[0m [\x1b[36m%" PRIxPTR "\x1b[0m]" + : " %3d: %s [%" PRIxPTR "]", level, symbol, offset); } - - fprintf(stderr, fmt1, level, symbol, offset); - if (file) - fprintf(stderr, fmt2, file, line); - fputs("\n", stderr); }; static auto errorCallback = [](void *data, const char *msg, int errnum) { const char *fmt = " %3d: ERROR: %s (%d)\n"; if (useAnsiColor) - fmt = " %3d: \x1b[31;1mERROR: \x1b[0;1m%s (%d)\x1b[0m\n"; + fmt = " %3d: \x1b[31;1mERROR: \x1b[0;1m%s (%d)\x1b[0m"; - fprintf(stderr, fmt, static_cast<btData *>(data)->level, msg, errnum); + printMsg(fmt, static_cast<btData *>(data)->level, msg, errnum); }; - static auto syminfoCallback = [](void *data, uintptr_t pc, const char *symname, uintptr_t symval, uintptr_t symsize) { + static auto syminfoCallback = [](void *data, uintptr_t pc, const char *symname, uintptr_t symval, + uintptr_t symsize) { Q_UNUSED(symval) Q_UNUSED(symsize) @@ -219,7 +262,8 @@ static void crashHandler(const char *why, int stackFramesToIgnore) } }; - static auto fullCallback = [](void *data, uintptr_t pc, const char *filename, int lineno, const char *function) -> int { + static auto fullCallback = [](void *data, uintptr_t pc, const char *filename, int lineno, + const char *function) -> int { if (function) { int status; abi::__cxa_demangle(function, demangleBuffer, &demangleBufferSize, &status); @@ -242,7 +286,7 @@ static void crashHandler(const char *why, int stackFramesToIgnore) struct backtrace_state *state = backtrace_create_state(nullptr, BACKTRACE_SUPPORTS_THREADS, errorCallback, nullptr); - fprintf(stderr, "\n > backtrace:\n"); + printMsg("\n > backtrace:"); btData data = { state, 0 }; //backtrace_print(state, stackFramesToIgnore, stderr); backtrace_simple(state, stackFramesToIgnore, simpleCallback, errorCallback, &data); @@ -252,15 +296,15 @@ static void crashHandler(const char *why, int stackFramesToIgnore) int addrCount = backtrace(addrArray, sizeof(addrArray) / sizeof(*addrArray)); if (!addrCount) { - fprintf(stderr, " > no backtrace available\n"); + printMsg(" > no backtrace available"); } else { char **symbols = backtrace_symbols(addrArray, addrCount); //backtrace_symbols_fd(addrArray, addrCount, 2); if (!symbols) { - fprintf(stderr, " > no symbol names available\n"); + printMsg(" > no symbol names available"); } else { - fprintf(stderr, " > backtrace:\n"); + printMsg("\n > backtrace:"); for (int i = 1; i < addrCount; ++i) { char *function = nullptr; char *offset = nullptr; @@ -283,22 +327,47 @@ static void crashHandler(const char *why, int stackFramesToIgnore) abi::__cxa_demangle(function, demangleBuffer, &demangleBufferSize, &status); if (status == 0 && *demangleBuffer) { - fprintf(stderr, " %3d: %s [+%s]\n", i, demangleBuffer, offset + 1); + printMsg(" %3d: %s [+%s]", i, demangleBuffer, offset + 1); } else { - fprintf(stderr, " %3d: %s [+%s]\n", i, function, offset + 1); + printMsg(" %3d: %s [+%s]", i, function, offset + 1); } } else { - fprintf(stderr, " %3d: %s\n", i, symbols[i]); + printMsg(" %3d: %s", i, symbols[i]); } } - fprintf(stderr, "\n"); } } #endif // defined(AM_USE_LIBBACKTRACE) && defined(BACKTRACE_SUPPORTED) } + + if (printQmlStack && qmlEngine) { + const QV4::ExecutionEngine *qv4engine = qmlEngine->handle(); + if (qv4engine) { + const QV4::StackTrace stackTrace = qv4engine->stackTrace(); + if (stackTrace.size()) { + printMsg("\n > qml stack:"); + int frame = 0; + for (const auto &stackFrame : stackTrace) { + printMsg(useAnsiColor ? " %3d: \x1b[1m%s\x1b[0m in \x1b[35m%s\x1b[0m:\x1b[35;1m%d\x1b[0m" + : " %3d: %s in %s:%d", + frame, stackFrame.function.toLocal8Bit().constData(), + stackFrame.source.toLocal8Bit().constData(), stackFrame.line); + frame++; + } + } else { + printMsg("\n > qml stack: empty"); + } + } + } +} + +static void crashHandler(const char *why, int stackFramesToIgnore) +{ + printCrashInfo(Console, why, stackFramesToIgnore); + if (waitForGdbAttach > 0) { - fprintf(stderr, "\n > the process will be suspended for %d seconds and you can attach a debugger to it via\n\n gdb -p %d\n", - waitForGdbAttach, pid); + fprintf(stderr, "\n > the process will be suspended for %d seconds and you can attach a debugger" + " to it via\n\n gdb -p %d\n", waitForGdbAttach, getpid()); static jmp_buf jmpenv; signal(SIGALRM, [](int) { longjmp(jmpenv, 1); @@ -314,14 +383,21 @@ static void crashHandler(const char *why, int stackFramesToIgnore) fprintf(stderr, "\n > no gdb attached\n"); } } + + if (Logging::isDltEnabled()) { + useAnsiColor = false; + printCrashInfo(Dlt, why, stackFramesToIgnore); + } + if (dumpCore) { - fprintf(stderr, "\n > the process will be aborted (core dump)\n\n"); + fprintf(stderr, "\n > the process will be aborted (core dumped)\n\n"); UnixSignalHandler::instance()->resetToDefault({ SIGFPE, SIGSEGV, SIGILL, SIGBUS, SIGPIPE, SIGABRT }); abort(); } + _exit(-1); } QT_END_NAMESPACE_AM -#endif // !Q_OS_LINUX +#endif // !Q_OS_LINUX || defined(Q_OS_ANDROID) diff --git a/src/common-lib/crashhandler.h b/src/common-lib/crashhandler.h index 6e331f12..ed84d354 100644 --- a/src/common-lib/crashhandler.h +++ b/src/common-lib/crashhandler.h @@ -44,11 +44,14 @@ #include <QtAppManCommon/global.h> #include <QVariantMap> +QT_FORWARD_DECLARE_CLASS(QQmlEngine) + QT_BEGIN_NAMESPACE_AM namespace CrashHandler { void setCrashActionConfiguration(const QVariantMap &config); +void setQmlEngine(QQmlEngine *engine); } diff --git a/src/common-lib/logging.cpp b/src/common-lib/logging.cpp index 67fa22bf..6788f170 100644 --- a/src/common-lib/logging.cpp +++ b/src/common-lib/logging.cpp @@ -363,6 +363,18 @@ void Logging::setDltApplicationId(const QByteArray &dltAppId, const QByteArray & #endif } +void Logging::logToDlt(QtMsgType msgType, const QMessageLogContext &context, const QString &message) +{ +#if defined(QT_GENIVIEXTRAS_LIB) + if (s_dltEnabled) + QDltRegistration::messageHandler(msgType, context, message); +#else + Q_UNUSED(msgType) + Q_UNUSED(context) + Q_UNUSED(message) +#endif +} + void am_trace(QDebug) { } diff --git a/src/common-lib/logging.h b/src/common-lib/logging.h index fcf84ef1..2acbb100 100644 --- a/src/common-lib/logging.h +++ b/src/common-lib/logging.h @@ -74,6 +74,8 @@ public: static void registerUnregisteredDltContexts(); static void setDltApplicationId(const QByteArray &dltAppId, const QByteArray &dltAppDescription); + static void logToDlt(QtMsgType msgType, const QMessageLogContext &context, const QString &message); + private: static bool s_dltEnabled; static bool s_useDefaultQtHandler; diff --git a/src/launchers/qml/main.cpp b/src/launchers/qml/main.cpp index a9ddc6a9..79fd08af 100644 --- a/src/launchers/qml/main.cpp +++ b/src/launchers/qml/main.cpp @@ -216,6 +216,7 @@ Controller::Controller(LauncherMain *a, bool quickLaunched, const QString &direc , m_quickLaunched(quickLaunched) { connect(&m_engine, &QObject::destroyed, a, &QCoreApplication::quit); + CrashHandler::setQmlEngine(&m_engine); #if !defined(AM_HEADLESS) qmlRegisterType<ApplicationManagerWindow>("QtApplicationManager", 1, 0, "ApplicationManagerWindow"); diff --git a/src/main-lib/main.cpp b/src/main-lib/main.cpp index e4837106..8f2e73dc 100644 --- a/src/main-lib/main.cpp +++ b/src/main-lib/main.cpp @@ -605,6 +605,7 @@ void Main::setupQmlEngine(const QStringList &importPaths, const QString &quickCo disconnect(m_engine, &QQmlEngine::exit, qApp, nullptr); connect(m_engine, &QQmlEngine::quit, this, [this]() { shutDown(); }); connect(m_engine, &QQmlEngine::exit, this, [this](int retCode) { shutDown(retCode); }); + CrashHandler::setQmlEngine(m_engine); new QmlLogger(m_engine); m_engine->setOutputWarningsToStandardError(false); m_engine->setImportPathList(m_engine->importPathList() + importPaths); diff --git a/tests/qml/crash/am-config.yaml b/tests/qml/crash/am-config.yaml new file mode 100644 index 00000000..b9c17b5e --- /dev/null +++ b/tests/qml/crash/am-config.yaml @@ -0,0 +1,17 @@ +formatVersion: 1 +formatType: am-configuration +--- +applications: + builtinAppsManifestDir: "${CONFIG_PWD}/apps" + installedAppsManifestDir: "/tmp/am-crash-test/manifests" + appImageMountDir: "/tmp/am-crash-test/image-mounts" + database: "/tmp/am-crash-test/apps.db" + +installationLocations: +- id: "internal-0" + installationPath: "/tmp/am/apps" + documentPath: "/tmp/am/docs" + mountPoint: "/tmp" + +flags: + noUiWatchdog: yes diff --git a/tests/qml/crash/apps/tld.test.crash/app.qml b/tests/qml/crash/apps/tld.test.crash/app.qml new file mode 100644 index 00000000..fb909b62 --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/app.qml @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +import QtQuick 2.3 +import QtApplicationManager 1.0 +import Terminator 2.0 + +ApplicationManagerWindow { + function accessIllegalMemory() + { + Terminator.accessIllegalMemory(); + } + + Connections { + target: ApplicationInterface + onOpenDocument: { + switch (documentUrl) { + case "illegalMemory": accessIllegalMemory(); break; + case "illegalMemoryInThread": Terminator.accessIllegalMemoryInThread(); break; + case "stackOverflow": Terminator.forceStackOverflow(); break; + case "divideByZero": Terminator.divideByZero(); break; + case "unhandledException": Terminator.throwUnhandledException(); break; + case "abort": Terminator.abort(); break; + case "raise": Terminator.raise(7); break; + case "gracefully": Terminator.exitGracefully(); break; + } + } + } +} diff --git a/tests/qml/crash/apps/tld.test.crash/icon.png b/tests/qml/crash/apps/tld.test.crash/icon.png Binary files differnew file mode 100644 index 00000000..c1397153 --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/icon.png diff --git a/tests/qml/crash/apps/tld.test.crash/info.yaml b/tests/qml/crash/apps/tld.test.crash/info.yaml new file mode 100644 index 00000000..77ca2e76 --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/info.yaml @@ -0,0 +1,12 @@ +formatVersion: 1 +formatType: am-application +--- +id: 'tld.test.crash' +icon: 'icon.png' +code: 'app.qml' +runtime: 'qml' +version: '1.0' +name: + en: 'Crash test' +runtimeParameters: + importPaths: [ "terminator2" ] diff --git a/tests/qml/crash/apps/tld.test.crash/terminator2/qmldir b/tests/qml/crash/apps/tld.test.crash/terminator2/qmldir new file mode 100644 index 00000000..ac15a495 --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/terminator2/qmldir @@ -0,0 +1,2 @@ +module Terminator +plugin terminator2plugin diff --git a/tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.cpp b/tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.cpp new file mode 100644 index 00000000..69ab4c99 --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.cpp @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QQmlEngine> +#include <QJSEngine> + +#include "qmlterminator2.h" + +#include <signal.h> + + +static QObject *terminator_provider(QQmlEngine *engine, QJSEngine *scriptEngine) +{ + Q_UNUSED(scriptEngine) + return new Terminator(engine); +} + +void TerminatorPlugin::registerTypes(const char *uri) +{ + qmlRegisterSingletonType<Terminator>(uri, 2, 0, "Terminator", terminator_provider); +} + + +void Terminator::accessIllegalMemory() const +{ + *(int*)1 = 42; +} + +void Terminator::accessIllegalMemoryInThread() +{ + TerminatorThread *t = new TerminatorThread(this); + t->start(); +} + +void Terminator::forceStackOverflow() const +{ + static constexpr int len = 100000; + volatile char buf[len]; + buf[len-1] = 42; + if (buf[len-1] == 42) + forceStackOverflow(); +} + +void Terminator::divideByZero() const +{ + int d = 0; + volatile int x = 42 / d; + Q_UNUSED(x) +} + +void Terminator::abort() const +{ + ::abort(); +} + +void Terminator::raise(int sig) const +{ + ::raise(sig); +} + +void Terminator::throwUnhandledException() const +{ + throw 42; +} + +void Terminator::exitGracefully() const +{ + exit(5); +} + + +TerminatorThread::TerminatorThread(Terminator *parent) + : QThread(parent), m_terminator(parent) +{ +} + +void TerminatorThread::run() +{ + m_terminator->accessIllegalMemory();; +} diff --git a/tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.h b/tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.h new file mode 100644 index 00000000..e4a7b30a --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/terminator2/qmlterminator2.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include <QQmlExtensionPlugin> +#include <QThread> + + +class TerminatorPlugin : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") + +public: + void registerTypes(const char *uri) override; +}; + + +class Terminator : public QObject +{ + Q_OBJECT + +public: + Terminator(QObject *parent) : QObject(parent) {} + + Q_INVOKABLE void accessIllegalMemory() const; + Q_INVOKABLE void accessIllegalMemoryInThread(); + Q_INVOKABLE void forceStackOverflow() const; + Q_INVOKABLE void divideByZero() const; + Q_INVOKABLE void abort() const; + Q_INVOKABLE void raise(int sig) const; + Q_INVOKABLE void throwUnhandledException() const; + Q_INVOKABLE void exitGracefully() const; +}; + + +class TerminatorThread : public QThread +{ + Q_OBJECT + +public: + explicit TerminatorThread(Terminator *parent); + +private: + void run() override; + Terminator *m_terminator; +}; diff --git a/tests/qml/crash/apps/tld.test.crash/terminator2/terminator2.pro b/tests/qml/crash/apps/tld.test.crash/terminator2/terminator2.pro new file mode 100644 index 00000000..3c482379 --- /dev/null +++ b/tests/qml/crash/apps/tld.test.crash/terminator2/terminator2.pro @@ -0,0 +1,18 @@ +TEMPLATE = lib +CONFIG += plugin exceptions +QT += qml quick + +TARGET = $$qtLibraryTarget(terminator2plugin) + +HEADERS += qmlterminator2.h +SOURCES += qmlterminator2.cpp + +DESTDIR = $$_PRO_FILE_PWD_/Terminator +target.path=$$DESTDIR +qmldir.files=$$PWD/qmldir +qmldir.path=$$DESTDIR +INSTALLS += target qmldir + +OTHER_FILES += qmldir + +QMAKE_POST_LINK += $$QMAKE_COPY $$replace($$list($$quote($$PWD/qmldir) $$DESTDIR), /, $$QMAKE_DIR_SEP) diff --git a/tests/qml/crash/crash.pro b/tests/qml/crash/crash.pro new file mode 100644 index 00000000..160c90af --- /dev/null +++ b/tests/qml/crash/crash.pro @@ -0,0 +1,4 @@ +AM_CONFIG = am-config.yaml +TEST_FILES = tst_crash.qml + +load(am-qml-testcase) diff --git a/tests/qml/crash/tst_crash.qml b/tests/qml/crash/tst_crash.qml new file mode 100644 index 00000000..699b79db --- /dev/null +++ b/tests/qml/crash/tst_crash.qml @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2018 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +import QtQuick 2.3 +import QtTest 1.0 +import QtApplicationManager 1.0 +import QtApplicationManager 1.0 as AM + + +TestCase { + id: testCase + when: windowShown + name: "Crashtest" + + property string appId: "tld.test.crash" + property var app: ApplicationManager.application(appId); + + SignalSpy { + id: runStateChangedSpy + target: ApplicationManager + signalName: "applicationRunStateChanged" + } + + function initTestCase() { + compare(app.lastExitStatus, AM.ApplicationObject.NormalExit) + } + + function test_crash_data() { + return [ { tag: "gracefully" }, + { tag: "illegalMemory" }, + { tag: "illegalMemoryInThread" }, + { tag: "unhandledException" } ]; + //{ tag: "stackOverflow" }, + //{ tag: "divideByZero" }, + //{ tag: "abort" }, + //{ tag: "raise" } ]; + } + + function test_crash(data) { + if (ApplicationManager.singleProcess) + skip("Application crash recovery not supported in single-process mode"); + + ApplicationManager.startApplication(appId); + runStateChangedSpy.wait(3000); + runStateChangedSpy.wait(3000); + compare(app.runState, AM.ApplicationObject.Running); + ApplicationManager.startApplication(appId, data.tag); + runStateChangedSpy.wait(3000); + compare(app.runState, AM.ApplicationObject.NotRunning); + if (data.tag === "gracefully") { + compare(app.lastExitStatus, AM.ApplicationObject.NormalExit); + compare(app.lastExitCode, 5); + } else { + compare(app.lastExitStatus, AM.ApplicationObject.CrashExit); + console.info("================================"); + console.info("=== INTENDED CRASH (TESTING) ==="); + console.info("================================"); + } + } +} diff --git a/tests/qml/qml.pro b/tests/qml/qml.pro index 98ac3a5b..d0865487 100644 --- a/tests/qml/qml.pro +++ b/tests/qml/qml.pro @@ -6,4 +6,6 @@ SUBDIRS = \ windowitem2 \ installer \ monitoring \ - quicklaunch + quicklaunch \ + crash/apps/tld.test.crash/terminator2 \ + crash |