Finding GUI Event-Handling Crashes



  • This morning I added a post about a crash I'm trying to find (see "Read access violation in QAbstractItemView::event -> QAbstractScrollArea::event -> QPainter::QPainter -> QPainter::begin"). Since there have been no replies I figure either nobody has any ideas on how I should go about finding the cause of the crash, or my post was too wordy/long for anyone to read.

    In case it is the second of the two, I'll resubmit in shorter form.

    I'm getting an intermittent crash in Qt event processing for a QEvent::Paint. The crash is happening in the begin method of a QPainter object that's being instantiated by a QAbstractScrollArea object.

    The crash happens during QPainter::begin when the QPaintDevice* parameter is assigned an invalid pointer by a QPaintDevice->redirected call.

    It looks like the redirected method returns a QPaintDevice* that has been deleted in garbage collection.

    Question: Is there a good place to set a breakpoint in the Qt code to try to find where/who/why the redirected QPaintDevice pointer is becoming invalid?

    Background Summary:

    1. I didn't write the program - I've just been tasked with debugging it

    2. Platform: Windows XP (originally), now Windows 7, 8 & 10

    3. Qt version: 5.2.1

    4. VS studio used: VS 2008, C++ Solution, uses MFC in a shared DLL, not using ATL, uses Multi-Byte character set, links in lots of libraries but for Qt: Qt5Core.lib, Qt5Gui.lib, Qt5Sql.lib, Qt5Network.lib, Qt5Widgets.lib

    5. How you installed Qt. ( self compiled, from Qt online installer ): I don't know. It's included as a distribution project (submodule) via git/Gerrit. You check out this program and it pulls in Qt header files and DLL's.

    See my earlier post for a more detailed explanation: https://forum.qt.io/topic/74952/read-access-violation-in-qabstractitemview-event-qabstractscrollarea-event-qpainter-qpainter-qpainter-begin

    Stack Trace Follows...thanks in advance for any suggestions you can supply!

    Stack Trace:

    Qt5Guid.dll!QPainter::begin(QPaintDevice * pd=0x0d1d050c) Line 1726 + 0xd bytes
    Qt5Guid.dll!QPainter::QPainter(QPaintDevice * pd=0x101de450) Line 1469
    Qt5Widgetsd.dll!QAbstractScrollArea::event(QEvent * e=0x0018c5bc) Line 1017
    Qt5Widgetsd.dll!QAbstractItemView::event(QEvent * event=0x0018c5bc) Line 1623
    Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x101de448, QEvent * e=0x0018c5bc) Line 3482 + 0x11 bytes
    Qt5Widgetsd.dll!QApplication::notify(QObject * receiver=0x101de448, QEvent * e=0x0018c5bc) Line 3447 + 0x10 bytes
    Qt5Cored.dll!QCoreApplication::notifyInternal(QObject * receiver=0x101de448, QEvent * event=0x0018c5bc) Line 881 + 0x15 bytes
    Qt5Cored.dll!QCoreApplication::sendSpontaneousEvent(QObject * receiver=0x101de448, QEvent * event=0x0018c5bc) Line 235 + 0x38 bytes
    Qt5Widgetsd.dll!QWidgetPrivate::drawWidget(QPaintDevice * pdev=0x0d1d050c, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5138 + 0xe bytes
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001a, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5333
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001c, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001d, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001e, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x0000001f, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000020, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000021, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000022, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000023, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000024, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000025, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::paintSiblingsRecursive(QPaintDevice * pdev=0x0d1d050c, const QList<QObject *> & siblings={...}, int index=0x00000028, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000004, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5320
    Qt5Widgetsd.dll!QWidgetPrivate::drawWidget(QPaintDevice * pdev=0x0d1d050c, const QRegion & rgn={...}, const QPoint & offset={...}, int flags=0x00000005, QPainter * sharedPainter=0x00000000, QWidgetBackingStore * backingStore=0x102a8ee0) Line 5191
    Qt5Widgetsd.dll!QWidgetBackingStore::sync() Line 1084
    Qt5Widgetsd.dll!QWidgetPrivate::syncBackingStore() Line 1688
    Qt5Widgetsd.dll!QWidget::event(QEvent * event=0x0d4e6d60) Line 8237
    Qt5Widgetsd.dll!QApplicationPrivate::notify_helper(QObject * receiver=0x0d2573e8, QEvent * e=0x0d4e6d60) Line 3482 + 0x11 bytes
    Qt5Widgetsd.dll!QApplication::notify(QObject * receiver=0x0d2573e8, QEvent * e=0x0d4e6d60) Line 3447 + 0x10 bytes
    Qt5Cored.dll!QCoreApplication::notifyInternal(QObject * receiver=0x0d2573e8, QEvent * event=0x0d4e6d60) Line 881 + 0x15 bytes
    Qt5Cored.dll!QCoreApplication::sendEvent(QObject * receiver=0x0d2573e8, QEvent * event=0x0d4e6d60) Line 232 + 0x39 bytes
    Qt5Cored.dll!QCoreApplicationPrivate::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000, QThreadData * data=0x059b2bc8) Line 1485 + 0xd bytes
    Qt5Cored.dll!QCoreApplication::sendPostedEvents(QObject * receiver=0x00000000, int event_type=0x00000000) Line 1343 + 0x11 bytes
    qwindowsd.dll!0f0ee6a1()
    [Frames below may be incorrect and/or missing, no symbols loaded for qwindowsd.dll]
    Qt5Cored.dll!qt_internal_proc(HWND__ * hwnd=0x002106ee, unsigned int message=0x00000401, unsigned int wp=0x00000000, long lp=0x00000000) Line 423
    user32.dll!760962fa()
    user32.dll!76096d3a()
    user32.dll!76096ce9()
    user32.dll!760a0d3f()
    user32.dll!760a853a()
    mfc90d.dll!AfxActivationWndProc(HWND_ * hWnd=0x002106ee, unsigned int nMsg=0x00000401, unsigned int wParam=0x00000000, long lParam=0x00000000) Line 463 + 0x1a bytes
    user32.dll!760962fa()
    user32.dll!76096d3a()
    user32.dll!76096ce9()



  • We faced similar intermittent crash. Issue was in compatibility wit library. We were using C++ library written by us. We kept on adding new functions to library. But did not change header file while compiling. Since we were not using new functions it did not give issue. After changing appropriate header file it worked fine.


  • Lifetime Qt Champion

    @JohnF-n-Kansas you also have to take into account that this is a community forum so having answers several hours to one day later is to be expected since people coming here lives in several different timezones.



  • @SGaist -- oh, fair enough, and thank you for pointing that out. I definitely understand that scenario. We have offices on the other side of the world where emails sent/received betwixt us will be a full 24-hour response cycle at a minmum.

    In this case I was simply making sure I hadn't written so verbosely or ambiguously that I scared a potential lifeline away. :)



  • @dheerendra - was your crash similar in that it was a bad or invalid (like deallocated) redirected QPaintDevice pointer in the handling of a Paint event? Each time this particular crash happens it appears to be from the same cause in the same location of QPainter::begin.

    Also with respect to the library, was your library essentially a Qt wrapper? In this case the application does have Qt-derived dialogs, containers, and other classes, but they are recompiled each time you build the application. The Qt runtime resides in libraries (.lib files) that are statically-linked at link-edit. I do know what you mean regarding changes to a C++ object's runtime memory footprint and how that can cause a lot of weird things.

    I know I've also seen weird crashes if you mix libraries or DLL's built with different versions of Visual Studio (or C++ runtimes), but I don't believe that's the case here.


  • Lifetime Qt Champion

    @JohnF-n-Kansas Better verbose than doesn't work ;)



  • Maybe I could ask this another way that would be more effective.

    The crash appears to be the setting of the QPaintDevice to a an invalid pointer to a redirected QPaintDevice.

    Here's a couple of questions that might be enough for me to track this down:

    1. Why does Qt use a redirected QPaintDevice and in what cases would Qt redirect a QPaintDevice?

    2. Is it common for Qt to destroy (delete) and re-instantiate a QPaintDevice?

    3. If so, what events would tend to cause the recycling of redirected QPaintDevice's?

    I could create mutex logic all over the place, but I'm thinking maybe if I know what I'm trying to prevent I could be a bit more savvy in how to find it and prevent it from crashing the application.

    Thanks!


  • Lifetime Qt Champion

    1. From the doc:
    Sometimes it is desirable to make someone else paint on an unusual QPaintDevice. QPainter supports a static function to do this, setRedirected().
    
    Warning: When the paintdevice is a widget, QPainter can only be used inside a paintEvent() function or in a function called by paintEvent().
    
    1. From a quick look at the Qt sources, I'd say no.
    2. Misbehaving code ;) More seriously, 0 doesn't mean it was delete, you may not have a redirected object and that would be normal e.g. an QOpenGLWindow which doesn't have the current context..


    1. I had seen the setRedirected() documentation and searched the source code (non-Qt sources, i.e. the code written here) to see if the app was intentionally doing this (and in a non-safe way), but couldn't find it, and didn't see any attempted chicanery on the part of the app to try to tweak the output device. That said I'll set a breakpoint in the setRedirected() entrypoint to see who's calling it.

    2. Thank you for checking that!

    3. Right, however, the 0xfdfd and 0xabababab in the member variables which looked like possibly memory clearing constants...e.g., I believe the C++ Windows runtime memory management code sets unused variables to strings of 0xcd, but if those values didn't jump out at anyone then my presumption about deallocated-memory-filling was wrong. Interestingly yesterday afternoon I did notice in what appeared to be adjacent memory those same values, so maybe there is some kind of unintended memory corruption or maybe even a pointer++ that I haven't seen. My entire premise was that the original author decided it would be a good idea to use Qt, or maybe had an intellectual curiosity about Qt, and thus cobbled together a chunk of code that "seems to work" but violates some of the Qt "no no's" in regards to stack variables, heap variables, or something along those lines.

    I really appreciate your responses. Thank you for taking time to do so!


  • Lifetime Qt Champion

    One thing I would do, since you can't rewrite everything is to first cleanup the model related code. If the model is properly implemented, it should trigger the update to the view without additional help even if it's modified from somewhere else it the code.

    That might help clear the issue you are having.



  • Thank you. I definitely believe that would help even if all it did was reduce the fog factor. By the same token, because of the size of the app and the way it's been implemented, correcting flow and components to be MVC/Delegate-pattern compliant is a bigger job than the return on the investment to do that. Users like the Find dialog and the functionality it provides. They just want it to stop crashing. If I could figure out a way to quickly/inexpensively rewrite or re-architect I certainly would, but I just can't get there painlessly.

    So I'm hoping to find (or stumble over) badly-written code using pointers or stack space incorrectly, or maybe code that simply needs to be wrapped in a mutex. Since this beast works most of the time, I'm inclined to think the crashes occur because of an race condition or bad memory or thread misuse; I'm hoping I can find that place and make a small tweak that circumvents the issue.

    A couple of general usage questions that may help.

    1. Is there anything wrong in declaring a CWnd-derived class member variable as a QPointer variable declared as:
    QPointer<DlgFindObjectU> m_pFindDialog;
    

    The View class, which is where the main app displays objects, is not a Qt class but rather a CView class that's derived off an MFC CWnd class. There's a method that does the following which is called from code scattered all over and around the application:

    if ( ! m_pFindDialog.isNull() )
    {
        m_pFindDialog->Restart();
        ...
    

    Note that this code could be getting called from threads other than the main thread.

    1. When Find is clicked from the main window, a new instance is created as follows -- from purely a Qt standpoint, does the following look okay:
    // Use a new "win" to prevent "Debug Assertion Failed!" in the end.
    QWinWidget *win = new QWinWidget(theApp.GetMainWnd());
    win->showCentered();
    m_pFindDialog = new DlgFindObjectU( this, win );
    m_pFindDialog->setAttribute(Qt::WA_DeleteOnClose);
    m_pFindDialog->show();
    m_pFindDialog->Restart();
    

    Note that the creation of the instance of the Qt Find dialog is happening on the Main (GUI) thread, but it happens each time you click the "Find" menu option. The variable named theApp is a staticly-declared instance of the main application which is derived from CWinApp and utlimately becomes the "parent" object in the QDialog constructor. The DlgFindObjectU is derived from QDialog.

    1. is it acceptable to allocate the Find dialog as a member variable in a CWnd object, then fire off calls like the following that could at times possibly originate from background threads in the CWnd-derived class:
    m_pFindDialog->Restart();
    

    which then calls:

    void DlgFindByObjectU::Restart()
    {
        ScheduleDialogRestart();
    }
    

    which then calls the following code that gets generated in the moc_DlgFindByObjectU.cpp file:

    // SIGNAL 1
    void DlgFindByObjectNameU::ScheduleDialogRestart()
    {
        QMetaObject::activate(this, &staticMetaObject, 1, 0);
    }
    

    Is that kind of usage okay? Or should it move the request over to the GUI thread, akin to the way Windows Forms apps do an Invoke() on Windows?

    I searched to determine if there's anything problematic with what's being done, but I couldn't find anything obvious. If I did then I missed it when reading.

    Thanks for any comments you have time for and are willing to provide!


  • Lifetime Qt Champion

    Warning: I haven't use the QtWinMigrate.

    That said, the way it is used looks a bit weird.

    1. AFAIK, there's nothing wrong in having a QPointer member of a class however, the dialog has the WA_DeleteOnClose attribute set so I fail to see the usefulness of that member.
    2. From a quick look at the example from the QtWinMigrate module, it looks a bit fishy.
    3. That restart call doesn't look clean at all. By the way, is it really activate ? I can't find it in Qt 4 nor Qt 5 QMetaObject doc.and I don't have a Qt 3 handy to check.

    Everything involving a GUI related activity should be called on the GUI thread however model related stuff can be multithreaded as long as you put in place the proper protections.

    I can understand the motivation of creating that dialog only once but the implementation doesn't look to be doing that.

    I'd rather start by cleaning up the dialog handling itself using a dummy QDialog just to ensure that showing, dismissing and showing again works correctly. Then I'd gradually add back the searching functionality.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.