From a4420af04a9ace7fff9bb14d3cfd1511c6b641b8 Mon Sep 17 00:00:00 2001 From: hjk Date: Tue, 13 Oct 2015 12:39:35 +0200 Subject: QmlDebug: Add a debug connector for use with native debuggers This debug connector offers a few simple C functions a native debugger can hook into to communicate with QML debug services. It is the base of the 'native mixed' debugging mode in Qt Creator. Change-Id: I32ce1f21be8e3522dccf1210d4de3b131b819bc5 Reviewed-by: Ulf Hermann --- .../qmltooling/qmldbg_native/qmldbg_native.pro | 12 + .../qmldbg_native/qqmlnativedebugconnector.cpp | 383 +++++++++++++++++++++ .../qmldbg_native/qqmlnativedebugconnector.json | 3 + src/plugins/qmltooling/qmltooling.pro | 1 + 4 files changed, 399 insertions(+) create mode 100644 src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro create mode 100644 src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp create mode 100644 src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json (limited to 'src/plugins') diff --git a/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro b/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro new file mode 100644 index 0000000000..7dc16b8c44 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qmldbg_native.pro @@ -0,0 +1,12 @@ +TARGET = qmldbg_native +QT += qml-private core-private + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlNativeDebugConnectorFactory +load(qt_plugin) + +SOURCES += \ + $$PWD/qqmlnativedebugconnector.cpp + +OTHER_FILES += \ + $$PWD/qqmlnativedebugconnector.json diff --git a/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp new file mode 100644 index 0000000000..2528d993ad --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.cpp @@ -0,0 +1,383 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt 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 http://www.qt.io/terms-conditions. For further +** information use the contact form at http://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 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +//#define TRACE_PROTOCOL(s) qDebug() << s +#define TRACE_PROTOCOL(s) + +QT_USE_NAMESPACE + +static bool expectSyncronousResponse = false; +Q_GLOBAL_STATIC(QByteArray, responseBuffer) + +extern "C" { + +Q_DECL_EXPORT const char *qt_qmlDebugMessageBuffer; +Q_DECL_EXPORT int qt_qmlDebugMessageLength; +Q_DECL_EXPORT bool qt_qmlDebugConnectionBlocker; + +// In blocking mode, this will busy wait until the debugger sets block to false. +Q_DECL_EXPORT void qt_qmlDebugConnectorOpen(); + +// First thing, set the debug stream version. Please use this function as we might move the version +// member to some other place. +Q_DECL_EXPORT void qt_qmlDebugSetStreamVersion(int version) +{ + QQmlDebugStream::s_dataStreamVersion = version; +} + + +// Break in this one to process output from an asynchronous message/ +Q_DECL_EXPORT void qt_qmlDebugMessageAvailable() +{ +} + + +// Break in this one to get notified about construction and destruction of +// interesting objects, such as QmlEngines. +Q_DECL_EXPORT void qt_qmlDebugObjectAvailable() +{ +} + +Q_DECL_EXPORT void qt_qmlDebugClearBuffer() +{ + responseBuffer->clear(); +} + +// Send a message to a service. +Q_DECL_EXPORT bool qt_qmlDebugSendDataToService(const char *serviceName, const char *hexData) +{ + QByteArray msg = QByteArray::fromHex(hexData); + + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QQmlDebugService *recipient = instance->service(serviceName); + if (!recipient) + return false; + + TRACE_PROTOCOL("Recipient: " << recipient << " got message: " << msg); + expectSyncronousResponse = true; + recipient->messageReceived(msg); + expectSyncronousResponse = false; + + return true; +} + +// Enable a service. +Q_DECL_EXPORT bool qt_qmlDebugEnableService(const char *data) +{ + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QString name = QString::fromLatin1(data); + QQmlDebugService *service = instance->service(name); + if (!service || service->state() == QQmlDebugService::Enabled) + return false; + + service->stateAboutToBeChanged(QQmlDebugService::Enabled); + service->setState(QQmlDebugService::Enabled); + service->stateChanged(QQmlDebugService::Enabled); + return true; +} + +Q_DECL_EXPORT bool qt_qmlDebugDisableService(const char *data) +{ + QQmlDebugConnector *instance = QQmlDebugConnector::instance(); + if (!instance) + return false; + + QString name = QString::fromLatin1(data); + QQmlDebugService *service = instance->service(name); + if (!service || service->state() == QQmlDebugService::Unavailable) + return false; + + service->stateAboutToBeChanged(QQmlDebugService::Unavailable); + service->setState(QQmlDebugService::Unavailable); + service->stateChanged(QQmlDebugService::Unavailable); + return true; +} + +quintptr qt_qmlDebugTestHooks[] = { + quintptr(1), // Internal Version + quintptr(6), // Number of entries following + quintptr(&qt_qmlDebugMessageBuffer), + quintptr(&qt_qmlDebugMessageLength), + quintptr(&qt_qmlDebugSendDataToService), + quintptr(&qt_qmlDebugEnableService), + quintptr(&qt_qmlDebugDisableService), + quintptr(&qt_qmlDebugObjectAvailable) +}; + +// In blocking mode, this will busy wait until the debugger sets block to false. +Q_DECL_EXPORT void qt_qmlDebugConnectorOpen() +{ + TRACE_PROTOCOL("Opening native debug connector"); + + // FIXME: Use a dedicated hook. Startup is a safe workaround, though, + // as we are already beyond its only use. + qtHookData[QHooks::Startup] = quintptr(&qt_qmlDebugTestHooks); + + while (qt_qmlDebugConnectionBlocker) + ; + + TRACE_PROTOCOL("Opened native debug connector"); +} + +} // extern "C" + +QT_BEGIN_NAMESPACE + +class QQmlNativeDebugConnector : public QQmlDebugConnector +{ + Q_OBJECT + +public: + QQmlNativeDebugConnector(); + ~QQmlNativeDebugConnector(); + + bool blockingMode() const; + QQmlDebugService *service(const QString &name) const; + void addEngine(QQmlEngine *engine); + void removeEngine(QQmlEngine *engine); + bool addService(const QString &name, QQmlDebugService *service); + bool removeService(const QString &name); + bool open(const QVariantHash &configuration); + +private slots: + void sendMessage(const QString &name, const QByteArray &message); + void sendMessages(const QString &name, const QList &messages); + +private: + void announceObjectAvailability(const QString &objectType, QObject *object, bool available); + + QVector m_services; + bool m_blockingMode; +}; + +QQmlNativeDebugConnector::QQmlNativeDebugConnector() + : m_blockingMode(false) +{ + const QString args = commandLineArguments(); + const QStringList lstjsDebugArguments = args.split(QLatin1Char(',')); + QStringList services; + QStringList::const_iterator argsItEnd = lstjsDebugArguments.cend(); + QStringList::const_iterator argsIt = lstjsDebugArguments.cbegin(); + for (; argsIt != argsItEnd; ++argsIt) { + const QString strArgument = *argsIt; + if (strArgument == QLatin1String("block")) { + m_blockingMode = true; + } else if (strArgument == QLatin1String("native")) { + // Ignore. This is used to signal that this connector + // should be loaded and that has already happened. + } else if (strArgument.startsWith(QLatin1String("services:"))) { + services.append(strArgument.mid(9)); + } else if (!services.isEmpty()) { + services.append(strArgument); + } else { + qWarning() << tr("QML Debugger: Invalid argument \"%1\" detected. Ignoring the same.") + .arg(strArgument); + } + } + setServices(services); +} + +QQmlNativeDebugConnector::~QQmlNativeDebugConnector() +{ + foreach (QQmlDebugService *service, m_services) { + service->stateAboutToBeChanged(QQmlDebugService::NotConnected); + service->setState(QQmlDebugService::NotConnected); + service->stateChanged(QQmlDebugService::NotConnected); + } +} + +bool QQmlNativeDebugConnector::blockingMode() const +{ + return m_blockingMode; +} + +QQmlDebugService *QQmlNativeDebugConnector::service(const QString &name) const +{ + for (QVector::ConstIterator i = m_services.begin(); i != m_services.end(); + ++i) { + if ((*i)->name() == name) + return *i; + } + return 0; +} + +void QQmlNativeDebugConnector::addEngine(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Add engine to connector:" << engine); + foreach (QQmlDebugService *service, m_services) + service->engineAboutToBeAdded(engine); + + announceObjectAvailability(QLatin1String("qmlengine"), engine, true); + + foreach (QQmlDebugService *service, m_services) + service->engineAdded(engine); +} + +void QQmlNativeDebugConnector::removeEngine(QQmlEngine *engine) +{ + TRACE_PROTOCOL("Remove engine from connector:" << engine); + foreach (QQmlDebugService *service, m_services) + service->engineAboutToBeRemoved(engine); + + announceObjectAvailability(QLatin1String("qmlengine"), engine, false); + + foreach (QQmlDebugService *service, m_services) + service->engineRemoved(engine); +} + +void QQmlNativeDebugConnector::announceObjectAvailability(const QString &objectType, + QObject *object, bool available) +{ + QJsonObject ob; + ob.insert(QLatin1String("objecttype"), objectType); + ob.insert(QLatin1String("object"), QString::number(quintptr(object))); + ob.insert(QLatin1String("available"), available); + QJsonDocument doc; + doc.setObject(ob); + + QByteArray ba = doc.toJson(QJsonDocument::Compact); + qt_qmlDebugMessageBuffer = ba.constData(); + qt_qmlDebugMessageLength = ba.size(); + TRACE_PROTOCOL("Reporting engine availabilty"); + qt_qmlDebugObjectAvailable(); // Trigger native breakpoint. +} + +bool QQmlNativeDebugConnector::addService(const QString &name, QQmlDebugService *service) +{ + TRACE_PROTOCOL("Add service to connector: " << qPrintable(name) << service); + for (QVector::ConstIterator i = m_services.begin(); i != m_services.end(); + ++i) { + if ((*i)->name() == name) + return false; + } + + connect(service, &QQmlDebugService::messageToClient, + this, &QQmlNativeDebugConnector::sendMessage); + connect(service, &QQmlDebugService::messagesToClient, + this, &QQmlNativeDebugConnector::sendMessages); + + service->setState(QQmlDebugService::Unavailable); + + m_services << service; + return true; +} + +bool QQmlNativeDebugConnector::removeService(const QString &name) +{ + for (QVector::Iterator i = m_services.begin(); i != m_services.end(); ++i) { + if ((*i)->name() == name) { + QQmlDebugService *service = *i; + m_services.erase(i); + service->setState(QQmlDebugService::NotConnected); + + disconnect(service, &QQmlDebugService::messagesToClient, + this, &QQmlNativeDebugConnector::sendMessages); + disconnect(service, &QQmlDebugService::messageToClient, + this, &QQmlNativeDebugConnector::sendMessage); + + return true; + } + } + return false; +} + +bool QQmlNativeDebugConnector::open(const QVariantHash &configuration) +{ + m_blockingMode = configuration.value(QStringLiteral("block"), m_blockingMode).toBool(); + qt_qmlDebugConnectionBlocker = m_blockingMode; + qt_qmlDebugConnectorOpen(); + return true; +} + +void QQmlNativeDebugConnector::sendMessage(const QString &name, const QByteArray &message) +{ + (*responseBuffer) += name.toUtf8() + ' ' + QByteArray::number(message.size()) + ' ' + message; + qt_qmlDebugMessageBuffer = responseBuffer->constData(); + qt_qmlDebugMessageLength = responseBuffer->size(); + // Responses are allowed to accumulate, the buffer will be cleared by + // separate calls to qt_qmlDebugClearBuffer() once the synchronous + // function return ('if' branch below) or in the native breakpoint handler + // ('else' branch below). + if (expectSyncronousResponse) { + TRACE_PROTOCOL("Expected synchronous response in " << message); + // Do not trigger the native breakpoint on qt_qmlDebugMessageFromService. + } else { + TRACE_PROTOCOL("Found asynchronous message in " << message); + // Trigger native breakpoint. + qt_qmlDebugMessageAvailable(); + } +} + +void QQmlNativeDebugConnector::sendMessages(const QString &name, const QList &messages) +{ + for (int i = 0; i != messages.size(); ++i) + sendMessage(name, messages.at(i)); +} + +class QQmlNativeDebugConnectorFactory : public QQmlDebugConnectorFactory +{ + Q_OBJECT + + Q_PLUGIN_METADATA(IID QQmlDebugConnectorFactory_iid FILE "qqmlnativedebugconnector.json") + +public: + QQmlNativeDebugConnectorFactory() {} + + QQmlDebugConnector *create(const QString &key) + { + return key == QLatin1String("QQmlNativeDebugConnector") ? new QQmlNativeDebugConnector : 0; + } +}; + +QT_END_NAMESPACE + +#include "qqmlnativedebugconnector.moc" diff --git a/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json new file mode 100644 index 0000000000..925e6a665c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_native/qqmlnativedebugconnector.json @@ -0,0 +1,3 @@ +{ + "Keys": [ "QQmlNativeDebugConnector" ] +} diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index e00e75599b..263e76e016 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs # Connectors SUBDIRS += \ + qmldbg_native \ qmldbg_server \ qmldbg_local \ qmldbg_tcp -- cgit v1.2.3