Bug 425991 - Crash in BreakpointModel::updateState() when exiting KDevelop while debugging a program
Summary: Crash in BreakpointModel::updateState() when exiting KDevelop while debugging...
Status: RESOLVED FIXED
Alias: None
Product: kdevelop
Classification: Applications
Component: CPP Debugger (show other bugs)
Version: git master
Platform: Compiled Sources Linux
: NOR crash
Target Milestone: ---
Assignee: Igor Kushnir
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2020-08-30 15:34 UTC by Igor Kushnir
Modified: 2020-10-17 19:26 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In: 5.7.0


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Igor Kushnir 2020-08-30 15:34:13 UTC
SUMMARY
When a user exits KDevelop while debugging a program, depending on the relative timing of KDevelop exit and the 5 second interval of the singleshot timer in MIDebugSession::stopDebugger(), a DebugSession may be destroyed safely in time or not destroyed at all and possibly cause a crash by accessing the already destroyed DebugController or its children. A DebugSession's state transition signals lead to accesses to DebugController and its children, such as a call to BreakpointModel::updateState(). Even though IBreakpointController::breakpointModel() returns nullptr when ICore::self() is nullptr, IBreakpointController::updateState() doesn't check for it. That is why this=0x0 in BreakpointModel::updateState() in the backtrace below.

SOFTWARE/OS VERSIONS
Manjaro GNU/Linux, Xfce
KDE Frameworks Version: 5.73.0
Qt Version: 5.15.0

ADDITIONAL INFORMATION
I am going to create a Merge Request with a fix soon.

BACKTRACE (Debug)
Application: KDevelop (kdevelop), signal: Segmentation fault

[KCrash Handler]
#4  0x00007f38447fd190 in QScopedPointer<KDevelop::BreakpointModelPrivate, QScopedPointerDeleter<KDevelop::BreakpointModelPrivate> >::operator->() const (this=0x10) at /usr/include/qt/QtCore/qscopedpointer.h:118
#5  0x00007f38447fc57b in qGetPtrHelper<QScopedPointer<KDevelop::BreakpointModelPrivate, QScopedPointerDeleter<KDevelop::BreakpointModelPrivate> > const>(QScopedPointer<KDevelop::BreakpointModelPrivate, QScopedPointerDeleter<KDevelop::BreakpointModelPrivate> > const&) (ptr=...) at /usr/include/qt/QtCore/qglobal.h:1133
#6  0x00007f38447fc442 in KDevelop::BreakpointModel::d_func() (this=0x0) at ../kdevplatform/debugger/breakpoint/breakpointmodel.h:198
#7  0x00007f38447f9b3e in KDevelop::BreakpointModel::updateState(int, KDevelop::Breakpoint::BreakpointState) (this=0x0, row=0, state=KDevelop::Breakpoint::NotStartedState) at ../kdevplatform/debugger/breakpoint/breakpointmodel.cpp:326
#8  0x00007f38447e4fcd in KDevelop::IBreakpointController::updateState(int, KDevelop::Breakpoint::BreakpointState) (this=0x55c5920a9f10, row=0, state=KDevelop::Breakpoint::NotStartedState) at ../kdevplatform/debugger/interfaces/ibreakpointcontroller.cpp:61
#9  0x00007f37d2b7800b in KDevMI::MIBreakpointController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState) (this=0x55c5920a9f10, state=KDevelop::IDebugSession::EndedState) at ../plugins/debuggercommon/mibreakpointcontroller.cpp:301
#10 0x00007f38447e7dea in QtPrivate::FunctorCall<QtPrivate::IndexesList<0>, QtPrivate::List<KDevelop::IDebugSession::DebuggerState>, void, void (KDevelop::IBreakpointController::*)(KDevelop::IDebugSession::DebuggerState)>::call(void (KDevelop::IBreakpointController::*)(KDevelop::IDebugSession::DebuggerState), KDevelop::IBreakpointController*, void**) (f=&virtual table offset 120, o=0x55c5920a9f10, arg=0x7ffe806e5750) at /usr/include/qt/QtCore/qobjectdefs_impl.h:152
#11 0x00007f38447e799b in QtPrivate::FunctionPointer<void (KDevelop::IBreakpointController::*)(KDevelop::IDebugSession::DebuggerState)>::call<QtPrivate::List<KDevelop::IDebugSession::DebuggerState>, void>(void (KDevelop::IBreakpointController::*)(KDevelop::IDebugSession::DebuggerState), KDevelop::IBreakpointController*, void**) (f=&virtual table offset 120, o=0x55c5920a9f10, arg=0x7ffe806e5750) at /usr/include/qt/QtCore/qobjectdefs_impl.h:185
#12 0x00007f38447e72bc in QtPrivate::QSlotObject<void (KDevelop::IBreakpointController::*)(KDevelop::IDebugSession::DebuggerState), QtPrivate::List<KDevelop::IDebugSession::DebuggerState>, void>::impl(int, QtPrivate::QSlotObjectBase*, QObject*, void**, bool*) (which=1, this_=0x55c5a684ba10, r=0x55c5920a9f10, a=0x7ffe806e5750, ret=0x0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:418
#13 0x00007f3845036a26 in  () at /usr/lib/libQt5Core.so.5
#14 0x00007f38447e3c27 in KDevelop::IDebugSession::stateChanged(KDevelop::IDebugSession::DebuggerState) (this=0x55c5c0f70b30, _t1=KDevelop::IDebugSession::EndedState) at kdevplatform/debugger/KDevPlatformDebugger_autogen/include/moc_idebugsession.cpp:284
#15 0x00007f37d2b65243 in KDevMI::MIDebugSession::setSessionState(KDevelop::IDebugSession::DebuggerState) (this=0x55c5c0f70b30, state=KDevelop::IDebugSession::EndedState) at ../plugins/debuggercommon/midebugsession.cpp:369
#16 0x00007f37d2b661e7 in KDevMI::MIDebugSession::handleDebuggerStateChange(QFlags<KDevMI::DBGStateFlag>, QFlags<KDevMI::DBGStateFlag>) (this=0x55c5c0f70b30, oldState=..., newState=...) at ../plugins/debuggercommon/midebugsession.cpp:499
#17 0x00007f37d2b65506 in KDevMI::MIDebugSession::setDebuggerState(QFlags<KDevMI::DBGStateFlag>) (this=0x55c5c0f70b30, newState=...) at ../plugins/debuggercommon/midebugsession.cpp:410
#18 0x00007f37d2b665d8 in operator()() const (__closure=0x55c5b06343f0) at ../plugins/debuggercommon/midebugsession.cpp:567
#19 0x00007f37d2b6be54 in QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, KDevMI::MIDebugSession::stopDebugger()::<lambda()> >::call(struct {...} &, void **) (f=..., arg=0x7ffe806e5a00) at /usr/include/qt/QtCore/qobjectdefs_impl.h:146
#20 0x00007f37d2b6be26 in QtPrivate::Functor<KDevMI::MIDebugSession::stopDebugger()::<lambda()>, 0>::call<QtPrivate::List<>, void>(struct {...} &, void *, void **) (f=..., arg=0x7ffe806e5a00) at /usr/include/qt/QtCore/qobjectdefs_impl.h:256
#21 0x00007f37d2b6bd8f in QtPrivate::QFunctorSlotObject<KDevMI::MIDebugSession::stopDebugger()::<lambda()>, 0, QtPrivate::List<>, void>::impl(int, QtPrivate::QSlotObjectBase *, QObject *, void **, bool *) (which=1, this_=0x55c5b06343e0, r=0x55c5c0f70b30, a=0x7ffe806e5a00, ret=0x0) at /usr/include/qt/QtCore/qobjectdefs_impl.h:443
#22 0x00007f384503ab33 in  () at /usr/lib/libQt5Core.so.5
#23 0x00007f384502c10f in QObject::event(QEvent*) () at /usr/lib/libQt5Core.so.5
#24 0x00007f3845e83702 in QApplicationPrivate::notify_helper(QObject*, QEvent*) () at /usr/lib/libQt5Widgets.so.5
#25 0x00007f3844fff7ba in QCoreApplication::notifyInternal2(QObject*, QEvent*) () at /usr/lib/libQt5Core.so.5
#26 0x00007f38450576b5 in QTimerInfoList::activateTimers() () at /usr/lib/libQt5Core.so.5
#27 0x00007f3845057f62 in  () at /usr/lib/libQt5Core.so.5
#28 0x00007f3842eed43c in g_main_context_dispatch () at /usr/lib/libglib-2.0.so.0
#29 0x00007f3842f3b1d9 in  () at /usr/lib/libglib-2.0.so.0
#30 0x00007f3842eec221 in g_main_context_iteration () at /usr/lib/libglib-2.0.so.0
#31 0x00007f3845058331 in QEventDispatcherGlib::processEvents(QFlags<QEventLoop::ProcessEventsFlag>) () at /usr/lib/libQt5Core.so.5
#32 0x00007f3844ffe13c in QEventLoop::exec(QFlags<QEventLoop::ProcessEventsFlag>) () at /usr/lib/libQt5Core.so.5
#33 0x00007f38450065c4 in QCoreApplication::exec() () at /usr/lib/libQt5Core.so.5
#34 0x000055c5701b267c in main(int, char**) (argc=3, argv=0x7ffe806e6218) at ../app/main.cpp:850
[Inferior 1 (process 15477) detached]
Comment 1 Igor Kushnir 2020-10-16 14:38:54 UTC
Git commit 37ff587ebe2a2c355d71cb583fb5f058c5301ff3 by Igor Kushnir.
Committed on 15/10/2020 at 15:26.
Pushed by igorkushnir into branch '5.6'.

Safely finalize and destroy DebugSession objects

DebugController is responsible for destroying debug sessions and it can
not do so past its own destructor. Currently, depending on the relative
timing of KDevelop exit and the 5 second interval of the singleshot
timer in MIDebugSession::stopDebugger(), a DebugSession may be destroyed
safely in time or not destroyed at all and possibly cause a crash by
accessing the already destroyed DebugController or its children.

Let us kill debugger processes and thus finalize the debug sessions'
states in ~DebugController(). We delay the killing of debugger processes
for as long as possible to give them a chance to exit on their own.

Check ICore::documentController() for nullptr in
BreakpointModel::updateMarks() and
DebugController::clearExecutionPoint() to prevent crashes when these
slots are invoked by DebugSession's state transition signals from
inside ~DebugController(), which is called after ~DocumentController().
FIXED-IN: 5.6.1

M  +9    -2    kdevplatform/debugger/breakpoint/breakpointmodel.cpp
M  +2    -0    kdevplatform/debugger/interfaces/idebugsession.h
M  +20   -2    kdevplatform/shell/debugcontroller.cpp
M  +1    -0    kdevplatform/shell/debugcontroller.h
M  +5    -0    kdevplatform/tests/testdebugsession.cpp
M  +1    -0    kdevplatform/tests/testdebugsession.h
M  +17   -3    plugins/debuggercommon/midebugsession.cpp
M  +4    -0    plugins/debuggercommon/midebugsession.h

https://invent.kde.org/kdevelop/kdevelop/commit/37ff587ebe2a2c355d71cb583fb5f058c5301ff3
Comment 2 Igor Kushnir 2020-10-17 19:26:26 UTC
Had to revert the fix in the stable 5.6 branch, because it broke the ABI. The commit is restored in the master branch, so I set Version Fixed In to 5.7.0.