4 This file is part of GammaRay, the Qt application inspection and
7 Copyright (C) 2010-2011 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
8 Author: Volker Krause <volker.krause@kdab.com>
9 Author: Stephen Kelly <stephen.kelly@kdab.com>
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program. If not, see <http://www.gnu.org/licenses/>.
24 //krazy:excludeall=null,captruefalse,staticobjects
27 #include "mainwindow.h"
28 #include "objectlistmodel.h"
29 #include "objecttreemodel.h"
30 #include "connectionmodel.h"
31 #include "toolmodel.h"
32 #include "readorwritelocker.h"
33 #include "tools/modelinspector/modeltest.h"
34 #include "hooking/functionoverwriterfactory.h"
36 #include <QtCore/QCoreApplication>
37 #include <QtCore/QThread>
38 #include <QMouseEvent>
40 #include <QtCore/QTimer>
41 #include <QApplication>
58 #include <sys/types.h>
59 #include <sys/errno.h>
64 using namespace GammaRay
;
67 Probe
*Probe::s_instance
= 0;
68 bool functionsOverwritten
= false;
70 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
75 static bool probeConnectCallback(void ** args
)
77 QObject
*sender
= reinterpret_cast<QObject
*>(args
[0]);
78 const char *signal
= reinterpret_cast<const char*>(args
[1]);
79 QObject
*receiver
= reinterpret_cast<QObject
*>(args
[2]);
80 const char *method
= reinterpret_cast<const char*>(args
[3]);
81 const Qt::ConnectionType
*type
= reinterpret_cast<Qt::ConnectionType
*>(args
[4]);
82 Probe::connectionAdded(sender
, signal
, receiver
, method
, *type
);
86 static bool probeDisconnectCallback(void ** args
)
88 QObject
*sender
= reinterpret_cast<QObject
*>(args
[0]);
89 const char *signal
= reinterpret_cast<const char*>(args
[1]);
90 QObject
*receiver
= reinterpret_cast<QObject
*>(args
[2]);
91 const char *method
= reinterpret_cast<const char*>(args
[3]);
92 Probe::connectionRemoved(sender
, signal
, receiver
, method
);
99 // useful for debugging, dumps the object and all it's parents
100 // also useable from GDB!
101 void dumpObject(QObject
*obj
)
104 cout
<< "QObject(0x0)" << endl
;
109 cout
<< obj
->metaObject()->className() << "(" << hex
<< obj
<< ")";
125 QThread
*filterThread
;
129 Q_GLOBAL_STATIC(Listener
, s_listener
)
130 Q_GLOBAL_STATIC(QVector
<QObject
*>, s_addedBeforeProbeInsertion
)
132 // ensures proper information is returned by isValidObject by
133 // locking it in objectAdded/Removed
134 class ObjectLock
: public QReadWriteLock
138 : QReadWriteLock(QReadWriteLock::Recursive
)
141 Q_GLOBAL_STATIC(ObjectLock
, s_lock
)
143 ProbeCreator::ProbeCreator(Type type
)
146 //push object into the main thread, as windows creates a
147 //different thread where this runs in
148 moveToThread(QApplication::instance()->thread());
149 // delay to foreground thread
150 QMetaObject::invokeMethod(this, "createProbe", Qt::QueuedConnection
);
153 void ProbeCreator::createProbe()
155 QWriteLocker
lock(s_lock());
156 // make sure we are in the ui thread
157 Q_ASSERT(QThread::currentThread() == qApp
->thread());
159 if (!qApp
|| Probe::isInitialized()) {
160 // never create it twice
164 // Exit early instead of asserting in QWidgetPrivate::init()
165 const QApplication
* const qGuiApplication
= qApp
; // qobject_cast<const QApplication *>(qApp);
166 if (!qGuiApplication
|| qGuiApplication
->type() == QApplication::Tty
) {
167 cerr
<< "Unable to attach to a non-GUI application.\n"
168 << "Your application needs to use QApplication, "
169 << "otherwise GammaRay can not work." << endl
;
173 IF_DEBUG(cout
<< "setting up new probe instance" << endl
;)
174 s_listener()->filterThread
= QThread::currentThread();
175 Q_ASSERT(!Probe::s_instance
);
176 Probe::s_instance
= new Probe
;
177 s_listener()->filterThread
= 0;
178 IF_DEBUG(cout
<< "done setting up new probe instance" << endl
;)
180 Q_ASSERT(Probe::instance());
181 QMetaObject::invokeMethod(Probe::instance(), "delayedInit", Qt::QueuedConnection
);
182 foreach (QObject
*obj
, *(s_addedBeforeProbeInsertion())) {
183 Probe::objectAdded(obj
);
185 s_addedBeforeProbeInsertion()->clear();
187 if (m_type
== CreateAndFindExisting
) {
188 Probe::findExistingObjects();
194 Probe::Probe(QObject
*parent
):
196 m_objectListModel(new ObjectListModel(this)),
197 m_objectTreeModel(new ObjectTreeModel(this)),
198 m_connectionModel(new ConnectionModel(this)),
199 m_toolModel(new ToolModel(this)),
201 m_queueTimer(new QTimer(this))
203 Q_ASSERT(thread() == qApp
->thread());
204 IF_DEBUG(cout
<< "attaching GammaRay probe" << endl
;)
206 if (qgetenv("GAMMARAY_MODELTEST") == "1") {
207 new ModelTest(m_objectListModel
, m_objectListModel
);
208 new ModelTest(m_objectTreeModel
, m_objectTreeModel
);
209 new ModelTest(m_connectionModel
, m_connectionModel
);
210 new ModelTest(m_toolModel
, m_toolModel
);
213 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
214 QInternal::registerCallback(QInternal::ConnectCallback
, &GammaRay::probeConnectCallback
);
215 QInternal::registerCallback(QInternal::DisconnectCallback
, &GammaRay::probeDisconnectCallback
);
218 m_queueTimer
->setSingleShot(true);
219 m_queueTimer
->setInterval(0);
220 connect(m_queueTimer
, SIGNAL(timeout()),
221 this, SLOT(queuedObjectsFullyConstructed()));
226 IF_DEBUG(cerr
<< "detaching GammaRay probe" << endl
;)
228 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
229 QInternal::unregisterCallback(QInternal::ConnectCallback
, &GammaRay::probeConnectCallback
);
230 QInternal::unregisterCallback(QInternal::DisconnectCallback
, &GammaRay::probeDisconnectCallback
);
236 void Probe::setWindow(GammaRay::MainWindow
*window
)
241 GammaRay::MainWindow
*Probe::window() const
246 Probe
*GammaRay::Probe::instance()
255 bool Probe::isInitialized()
257 return s_instance
&& qApp
;
260 void Probe::delayedInit()
262 if (qgetenv("GAMMARAY_UNSET_PRELOAD") == "1") {
263 qputenv("LD_PRELOAD", "");
265 if (qgetenv("GAMMARAY_UNSET_DYLD") == "1") {
266 qputenv("DYLD_INSERT_LIBRARIES", "");
267 qputenv("DYLD_FORCE_FLAT_NAMESPACE", "");
270 QCoreApplication::instance()->installEventFilter(s_instance
);
272 IF_DEBUG(cout
<< "creating GammaRay::MainWindow" << endl
;)
273 s_listener()->filterThread
= QThread::currentThread();
274 GammaRay::MainWindow
*window
= new GammaRay::MainWindow
;
275 s_listener()->filterThread
= 0;
276 IF_DEBUG(cout
<< "creation done" << endl
;)
278 window
->setAttribute(Qt::WA_DeleteOnClose
);
279 instance()->setWindow(window
);
280 instance()->setParent(window
);
284 bool Probe::filterObject(QObject
*obj
)
286 Probe
*p
= Probe::instance();
288 if (obj
->thread() != p
->thread()) {
289 // shortcut, never filter objects from a different thread
292 return obj
== p
|| obj
== p
->window() ||
293 Util::descendantOf(p
, obj
) ||
294 Util::descendantOf(p
->window(), obj
);
297 QAbstractItemModel
*Probe::objectListModel() const
299 return m_objectListModel
;
302 QAbstractItemModel
*Probe::objectTreeModel() const
304 return m_objectTreeModel
;
307 QAbstractItemModel
*Probe::connectionModel() const
309 return m_connectionModel
;
312 ToolModel
*Probe::toolModel() const
317 QObject
*Probe::probe() const
319 return const_cast<GammaRay::Probe
*>(this);
322 bool Probe::isValidObject(QObject
*obj
) const
324 ///TODO: can we somehow assert(s_lock().isLocked()) ?!
325 return m_validObjects
.contains(obj
);
328 QReadWriteLock
*Probe::objectLock() const
333 void Probe::objectAdded(QObject
*obj
, bool fromCtor
)
335 QWriteLocker
lock(s_lock());
336 if (s_listener()->filterThread
== obj
->thread()) {
339 << "objectAdded Ignore: "
341 << (fromCtor
? " (from ctor)" : "") << endl
;)
343 } else if (isInitialized()) {
344 if (filterObject(obj
)) {
346 << "objectAdded Filter: "
348 << (fromCtor
? " (from ctor)" : "") << endl
;)
350 } else if (instance()->m_validObjects
.contains(obj
)) {
351 // this happens when we get a child event before the objectAdded call from the ctor
352 // or when we add an item from s_addedBeforeProbeInsertion who got added already
353 // due to the add-parent-before-child logic
355 << "objectAdded Known: "
357 << (fromCtor
? " (from ctor)" : "") << endl
;)
358 Q_ASSERT(fromCtor
|| s_addedBeforeProbeInsertion()->contains(obj
));
362 // make sure we already know the parent
363 if (obj
->parent() && !instance()->m_validObjects
.contains(obj
->parent())) {
364 objectAdded(obj
->parent(), fromCtor
);
366 Q_ASSERT(!obj
->parent() || instance()->m_validObjects
.contains(obj
->parent()));
368 instance()->m_validObjects
<< obj
;
369 if (s_listener()->trackDestroyed
) {
370 // when we did not use a preload variant that
371 // overwrites qt_removeObject we must track object
373 connect(obj
, SIGNAL(destroyed(QObject
*)),
374 instance(), SLOT(handleObjectDestroyed(QObject
*)),
375 Qt::DirectConnection
);
378 if (!fromCtor
&& obj
->parent() && instance()->m_queuedObjects
.contains(obj
->parent())) {
379 // when a child event triggers a call to objectAdded while inside the ctor
380 // the parent is already tracked but it's call to objectFullyConstructed
381 // was delayed. hence we must do the same for the child for integrity
385 IF_DEBUG(cout
<< "objectAdded: " << hex
<< obj
386 << (fromCtor
? " (from ctor)" : "")
387 << ", p: " << obj
->parent() << endl
;)
390 Q_ASSERT(!instance()->m_queuedObjects
.contains(obj
));
391 instance()->m_queuedObjects
<< obj
;
392 if (!instance()->m_queueTimer
->isActive()) {
393 // timers must not be started from a different thread
394 QMetaObject::invokeMethod(instance()->m_queueTimer
, "start", Qt::AutoConnection
);
397 instance()->objectFullyConstructed(obj
);
401 << "objectAdded Before: "
403 << (fromCtor
? " (from ctor)" : "") << endl
;)
404 s_addedBeforeProbeInsertion()->push_back(obj
);
408 void Probe::queuedObjectsFullyConstructed()
410 QWriteLocker
lock(s_lock());
412 IF_DEBUG(cout
<< Q_FUNC_INFO
<< " " << m_queuedObjects
.size() << endl
;)
414 // must be called from the main thread via timeout
415 Q_ASSERT(QThread::currentThread() == thread());
417 // when this is called no object must be in the queue twice
418 // otherwise the cleanup procedures failed
419 Q_ASSERT(m_queuedObjects
.size() == m_queuedObjects
.toSet().size());
421 foreach (QObject
*obj
, m_queuedObjects
) {
422 objectFullyConstructed(obj
);
425 IF_DEBUG(cout
<< Q_FUNC_INFO
<< " done" << endl
;)
427 m_queuedObjects
.clear();
430 void Probe::objectFullyConstructed(QObject
*obj
)
432 // must be write locked
433 Q_ASSERT(!s_lock()->tryLockForRead());
435 if (!m_validObjects
.contains(obj
)) {
437 IF_DEBUG(cout
<< "stale fully constructed: " << hex
<< obj
<< endl
;)
439 } else if (filterObject(obj
)) {
440 // when the call was delayed from the ctor construction,
441 // the parent might not have been set properly yet. hence
442 // apply the filter again
443 m_validObjects
.remove(obj
);
444 IF_DEBUG(cout
<< "now filtered fully constructed: " << hex
<< obj
<< endl
;)
448 IF_DEBUG(cout
<< "fully constructed: " << hex
<< obj
<< endl
;)
450 // ensure we know the parent already
451 if (obj
->parent() && !m_validObjects
.contains(obj
->parent())) {
452 objectAdded(obj
->parent());
454 Q_ASSERT(!obj
->parent() || m_validObjects
.contains(obj
->parent()));
456 m_objectListModel
->objectAdded(obj
);
457 m_toolModel
->objectAdded(obj
);
459 emit
objectCreated(obj
);
462 void Probe::objectRemoved(QObject
*obj
)
464 QWriteLocker
lock(s_lock());
465 if (isInitialized()) {
466 IF_DEBUG(cout
<< "object removed:" << hex
<< obj
<< " " << obj
->parent() << endl
;)
468 bool success
= instance()->m_validObjects
.remove(obj
);
470 // object was not tracked by the probe, probably a gammaray object
474 instance()->m_queuedObjects
.removeOne(obj
);
476 instance()->m_objectListModel
->objectRemoved(obj
);
478 instance()->connectionRemoved(obj
, 0, 0, 0);
479 instance()->connectionRemoved(0, 0, obj
, 0);
481 emit
instance()->objectDestroyed(obj
);
482 } else if (s_addedBeforeProbeInsertion()) {
483 for (QVector
<QObject
*>::iterator it
= s_addedBeforeProbeInsertion()->begin();
484 it
!= s_addedBeforeProbeInsertion()->end();) {
486 it
= s_addedBeforeProbeInsertion()->erase(it
);
494 void Probe::handleObjectDestroyed(QObject
*obj
)
499 void Probe::connectionAdded(QObject
*sender
, const char *signal
, QObject
*receiver
,
500 const char *method
, Qt::ConnectionType type
)
502 if (!isInitialized() || !sender
|| !receiver
||
503 s_listener()->filterThread
== QThread::currentThread())
508 ReadOrWriteLocker
lock(s_lock());
509 if (filterObject(sender
) || filterObject(receiver
)) {
513 instance()->m_connectionModel
->connectionAdded(sender
, signal
, receiver
, method
, type
);
516 void Probe::connectionRemoved(QObject
*sender
, const char *signal
,
517 QObject
*receiver
, const char *method
)
519 if (!isInitialized() || !s_listener() ||
520 s_listener()->filterThread
== QThread::currentThread())
525 ReadOrWriteLocker
lock(s_lock());
526 if ((sender
&& filterObject(sender
)) || (receiver
&& filterObject(receiver
))) {
530 instance()->m_connectionModel
->connectionRemoved(sender
, signal
, receiver
, method
);
533 bool Probe::eventFilter(QObject
*receiver
, QEvent
*event
)
535 if (s_listener()->filterThread
== receiver
->thread()) {
536 return QObject::eventFilter(receiver
, event
);
539 if (event
->type() == QEvent::ChildAdded
|| event
->type() == QEvent::ChildRemoved
) {
540 QChildEvent
*childEvent
= static_cast<QChildEvent
*>(event
);
541 QObject
*obj
= childEvent
->child();
543 QWriteLocker
lock(s_lock());
544 const bool tracked
= m_validObjects
.contains(obj
);
545 const bool filtered
= filterObject(obj
);
547 IF_DEBUG(cout
<< "child event: " << hex
<< obj
<< ", p: " << obj
->parent() << dec
548 << ", tracked: " << tracked
549 << ", filtered: " << filtered
550 << ", type: " << (childEvent
->added() ? "added" : "removed") << endl
;)
552 if (!filtered
&& childEvent
->added()) {
554 // was not tracked before, add to all models
555 // child added events are sent before qt_addObject is called,
556 // so we assumes this comes from the ctor
557 objectAdded(obj
, true);
558 } else if (!m_queuedObjects
.contains(obj
)) {
559 // object is known already, just update the position in the tree
560 // BUT: only when we did not queue this item before
561 IF_DEBUG(cout
<< "update pos: " << hex
<< obj
<< endl
;)
562 emit
objectReparanted(obj
);
564 } else if (tracked
) {
568 if (event
->type() == QEvent::MouseButtonRelease
) {
569 QMouseEvent
*mouseEv
= static_cast<QMouseEvent
*>(event
);
570 if (mouseEv
->button() == Qt::LeftButton
&&
571 mouseEv
->modifiers() & (Qt::ControlModifier
| Qt::ShiftModifier
)) {
572 QWidget
*widget
= QApplication::widgetAt(mouseEv
->globalPos());
574 emit
widgetSelected(widget
, widget
->mapFromGlobal(mouseEv
->globalPos()));
579 // make modal dialogs non-modal so that the gammaray window is still reachable
580 if (event
->type() == QEvent::Show
) {
581 QDialog
*dlg
= qobject_cast
<QDialog
*>(receiver
);
583 dlg
->setWindowModality(Qt::NonModal
);
587 // we have no preloading hooks, so recover all objects we see
588 if (s_listener()->trackDestroyed
&& event
->type() != QEvent::ChildAdded
&&
589 event
->type() != QEvent::ChildRemoved
&& !filterObject(receiver
)) {
590 QWriteLocker
lock(s_lock());
591 const bool tracked
= m_validObjects
.contains(receiver
);
593 objectAdded(receiver
);
596 return QObject::eventFilter(receiver
, event
);
599 void Probe::findExistingObjects()
601 addObjectRecursive(QCoreApplication::instance());
602 foreach (QObject
*obj
, QApplication::topLevelWidgets()) {
603 addObjectRecursive(obj
);
607 void Probe::addObjectRecursive(QObject
*obj
)
612 objectRemoved(obj
); // in case we find it twice
614 foreach (QObject
*child
, obj
->children()) {
615 addObjectRecursive(child
);
619 // taken from qobject.cpp
620 const int gammaray_flagged_locations_count
= 2;
621 static const char *gammaray_flagged_locations
[gammaray_flagged_locations_count
] = {0};
623 const char *Probe::connectLocation(const char *member
)
625 for (int i
= 0; i
< gammaray_flagged_locations_count
; ++i
) {
626 if (member
== gammaray_flagged_locations
[i
]) {
627 // signature includes location information after the first null-terminator
628 const char *location
= member
+ qstrlen(member
) + 1;
629 if (*location
!= '\0') {
638 extern "C" Q_DECL_EXPORT
void qt_startup_hook()
640 s_listener()->trackDestroyed
= false;
642 new ProbeCreator(ProbeCreator::CreateOnly
);
643 #if !defined Q_OS_WIN && !defined Q_OS_MAC
644 if (!functionsOverwritten
) {
645 static void(*next_qt_startup_hook
)() = (void (*)()) dlsym(RTLD_NEXT
, "qt_startup_hook");
646 next_qt_startup_hook();
651 extern "C" Q_DECL_EXPORT
void qt_addObject(QObject
*obj
)
653 Probe::objectAdded(obj
, true);
654 #if !defined Q_OS_WIN && !defined Q_OS_MAC
655 if (!functionsOverwritten
) {
656 static void (*next_qt_addObject
)(QObject
*obj
) =
657 (void (*)(QObject
*obj
)) dlsym(RTLD_NEXT
, "qt_addObject");
658 next_qt_addObject(obj
);
663 extern "C" Q_DECL_EXPORT
void qt_removeObject(QObject
*obj
)
665 Probe::objectRemoved(obj
);
666 #if !defined Q_OS_WIN && !defined Q_OS_MAC
667 if (!functionsOverwritten
) {
668 static void (*next_qt_removeObject
)(QObject
*obj
) =
669 (void (*)(QObject
*obj
)) dlsym(RTLD_NEXT
, "qt_removeObject");
670 next_qt_removeObject(obj
);
675 #ifndef GAMMARAY_UNKNOWN_CXX_MANGLED_NAMES
677 Q_DECL_EXPORT
const char *qFlagLocation(const char *method
)
679 Q_DECL_EXPORT
const char *myFlagLocation(const char *method
)
682 static int gammaray_idx
= 0;
683 gammaray_flagged_locations
[gammaray_idx
] = method
;
684 gammaray_idx
= (gammaray_idx
+1) % gammaray_flagged_locations_count
;
687 static const char *(*next_qFlagLocation
)(const char *method
) =
688 (const char * (*)(const char *method
)) dlsym(RTLD_NEXT
, "_Z13qFlagLocationPKc");
690 Q_ASSERT_X(next_qFlagLocation
, "",
691 "Recompile with GAMMARAY_UNKNOWN_CXX_MANGLED_NAMES enabled, "
692 "your compiler uses an unsupported C++ name mangling scheme");
693 return next_qFlagLocation(method
);
700 void overwriteQtFunctions()
702 functionsOverwritten
= true;
703 AbstractFunctionOverwriter
*overwriter
= FunctionOverwriterFactory::createFunctionOverwriter();
705 overwriter
->overwriteFunction(QLatin1String("qt_startup_hook"), (void*)qt_startup_hook
);
706 overwriter
->overwriteFunction(QLatin1String("qt_addObject"), (void*)qt_addObject
);
707 overwriter
->overwriteFunction(QLatin1String("qt_removeObject"), (void*)qt_removeObject
);
708 #if defined(Q_OS_WIN)
710 overwriter
->overwriteFunction(
711 QLatin1String("?qFlagLocation@@YAPEBDPEBD@Z"), (void*)myFlagLocation
);
713 overwriter
->overwriteFunction(
714 QLatin1String("?qFlagLocation@@YAPBDPBD@Z"), (void*)myFlagLocation
);
720 extern "C" Q_DECL_EXPORT
void gammaray_probe_inject();
722 extern "C" BOOL WINAPI
DllMain(HINSTANCE
/*hInstance*/, DWORD dwReason
, LPVOID
/*lpvReserved*/)
725 case DLL_PROCESS_ATTACH
:
727 overwriteQtFunctions();
729 gammaray_probe_inject();
732 case DLL_PROCESS_DETACH
:
734 //Unloading does not work, because we overwrite existing code
743 extern "C" Q_DECL_EXPORT
void gammaray_probe_inject()
748 printf("gammaray_probe_inject()\n");
749 // make it possible to re-attach
750 new ProbeCreator(ProbeCreator::CreateAndFindExisting
);
754 // we need a way to execute some code upon load, so let's abuse
755 // static initialization
756 class HitMeBabyOneMoreTime
759 HitMeBabyOneMoreTime()
761 overwriteQtFunctions();
765 static HitMeBabyOneMoreTime britney
;