diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 46b86d72..2ffca948 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,131 +1,132 @@ include (CheckFunctionExists) check_function_exists("strsignal" HAVE_STRSIGNAL) check_function_exists("uname" HAVE_UNAME) if (NOT DEBUG_PACKAGE_INSTALLER_NAME) set (DEBUG_PACKAGE_INSTALLER_NAME "installdbgsymbols.sh") endif () configure_file (config-drkonqi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-drkonqi.h ) add_subdirectory( data ) add_subdirectory( parser ) if ( WIN32 ) find_package(KDEWin REQUIRED) # for finding drkonqi_debug.h include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory( kdbgwin ) endif () set(drkonqi_SRCS main.cpp drkonqidialog.cpp statuswidget.cpp aboutbugreportingdialog.cpp backtraceratingwidget.cpp backtracewidget.cpp backtracegenerator.cpp drkonqi.cpp drkonqibackends.cpp detachedprocessmonitor.cpp debugpackageinstaller.cpp systeminformation.cpp crashedapplication.cpp debugger.cpp debuggerlaunchers.cpp + ptracer.cpp debuggermanager.cpp applicationdetailsexamples.cpp gdbhighlighter.cpp statusnotifier.cpp ) ki18n_wrap_ui(drkonqi_SRCS ui/maindialog.ui ui/backtracewidget.ui ) # if BACKTRACE_PARSER_DEBUG is enabled, it will show both the # parsed and the unparsed backtrace in the backtrace widget. # Comment this out for release. #add_definitions(-DBACKTRACE_PARSER_DEBUG) set(drkonqi_SRCS ${drkonqi_SRCS} bugzillaintegration/bugzillalib.cpp bugzillaintegration/reportassistantdialog.cpp bugzillaintegration/reportassistantpage.cpp bugzillaintegration/reportassistantpages_base.cpp bugzillaintegration/reportassistantpages_bugzilla.cpp bugzillaintegration/reportassistantpages_bugzilla_duplicates.cpp bugzillaintegration/reportinterface.cpp bugzillaintegration/productmapping.cpp bugzillaintegration/parsebugbacktraces.cpp # Requires kxmlrpcclient bugzillaintegration/duplicatefinderjob.cpp ) ki18n_wrap_ui(drkonqi_SRCS bugzillaintegration/ui/assistantpage_introduction.ui bugzillaintegration/ui/assistantpage_bugawareness.ui bugzillaintegration/ui/assistantpage_conclusions.ui bugzillaintegration/ui/assistantpage_conclusions_dialog.ui bugzillaintegration/ui/assistantpage_bugzilla_login.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates_dialog.ui bugzillaintegration/ui/assistantpage_bugzilla_duplicates_dialog_confirmation.ui bugzillaintegration/ui/assistantpage_bugzilla_information.ui bugzillaintegration/ui/assistantpage_bugzilla_preview.ui bugzillaintegration/ui/assistantpage_bugzilla_send.ui ) ecm_qt_declare_logging_category(drkonqi_SRCS HEADER drkonqi_debug.h IDENTIFIER DRKONQI_LOG CATEGORY_NAME org.kde.drkonqi) add_executable(drkonqi ${drkonqi_SRCS}) ecm_mark_nongui_executable(drkonqi) target_compile_definitions(drkonqi PRIVATE -DPROJECT_VERSION="${PROJECT_VERSION}") target_link_libraries(drkonqi KF5::I18n KF5::CoreAddons KF5::Service KF5::ConfigWidgets KF5::JobWidgets KF5::KIOCore KF5::Crash KF5::Completion Qt5::DBus KF5::XmlRpcClient KF5::WidgetsAddons KF5::Wallet KF5::Notifications # for status notifier KF5::IdleTime # hide status notifier only if user saw it drkonqi_backtrace_parser ) if (${Qt5X11Extras_FOUND}) target_link_libraries(drkonqi Qt5::X11Extras ) endif() if (APPLE) target_link_libraries(drkonqi KF5::WindowSystem ) endif() if (WIN32) target_link_libraries(drkonqi kdewin) endif() install(TARGETS drkonqi DESTINATION ${KDE_INSTALL_LIBEXECDIR}) configure_file(org.kde.drkonqi.desktop.cmake ${CMAKE_BINARY_DIR}/src/org.kde.drkonqi.desktop) install(PROGRAMS ${CMAKE_BINARY_DIR}/src/org.kde.drkonqi.desktop DESTINATION ${KDE_INSTALL_APPDIR}) # Only go into tests once we have a drkonqi target so the tests can reference # it. add_subdirectory( tests ) diff --git a/src/debuggerlaunchers.cpp b/src/debuggerlaunchers.cpp index 673be87a..9e8d4c86 100644 --- a/src/debuggerlaunchers.cpp +++ b/src/debuggerlaunchers.cpp @@ -1,135 +1,144 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "debuggerlaunchers.h" +#include #include #include #include #include "drkonqi_debug.h" #include "detachedprocessmonitor.h" #include "drkonqi.h" #include "crashedapplication.h" +#include "ptracer.h" + DefaultDebuggerLauncher::DefaultDebuggerLauncher(const Debugger & debugger, DebuggerManager *parent) : AbstractDebuggerLauncher(parent), m_debugger(debugger) { m_monitor = new DetachedProcessMonitor(this); connect(m_monitor, &DetachedProcessMonitor::processFinished, this, &DefaultDebuggerLauncher::onProcessFinished); } QString DefaultDebuggerLauncher::name() const { return m_debugger.name(); } void DefaultDebuggerLauncher::start() { if ( static_cast(parent())->debuggerIsRunning() ) { qCWarning(DRKONQI_LOG) << "Another debugger is already running"; return; } QString str = m_debugger.command(); Debugger::expandString(str, Debugger::ExpansionUsageShell); emit starting(); int pid = KProcess::startDetached(KShell::splitArgs(str)); if ( pid > 0 ) { + setPtracer(pid, DrKonqi::pid()); m_monitor->startMonitoring(pid); } else { qCWarning(DRKONQI_LOG) << "Could not start debugger:" << name(); emit finished(); } } void DefaultDebuggerLauncher::onProcessFinished() { + setPtracer(QCoreApplication::applicationPid(), DrKonqi::pid()); emit finished(); } #if 0 TerminalDebuggerLauncher::TerminalDebuggerLauncher(const Debugger & debugger, DebuggerManager *parent) : DefaultDebuggerLauncher(debugger, parent) { } void TerminalDebuggerLauncher::start() { DefaultDebuggerLauncher::start(); //FIXME } #endif -DBusInterfaceLauncher::DBusInterfaceLauncher(const QString &name, DBusInterfaceAdaptor *parent) - : AbstractDebuggerLauncher(parent), m_name(name) +DBusInterfaceLauncher::DBusInterfaceLauncher(const QString &name, qint64 pid, DBusInterfaceAdaptor *parent) + : AbstractDebuggerLauncher(parent), m_name(name), m_pid(pid) { } QString DBusInterfaceLauncher::name() const { return m_name; } void DBusInterfaceLauncher::start() { emit starting(); + + setPtracer(m_pid, DrKonqi::pid()); + emit static_cast(parent())->acceptDebuggingApplication(m_name); } DBusInterfaceAdaptor::DBusInterfaceAdaptor(DebuggerManager *parent) : QDBusAbstractAdaptor(parent) { Q_ASSERT(parent); if (QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.drkonqi-%1").arg(pid()))) { QDBusConnection::sessionBus().registerObject(QStringLiteral("/debugger"), parent); } } int DBusInterfaceAdaptor::pid() { return DrKonqi::crashedApplication()->pid(); } -void DBusInterfaceAdaptor::registerDebuggingApplication(const QString &name) +void DBusInterfaceAdaptor::registerDebuggingApplication(const QString &name, qint64 pid) { if (!name.isEmpty() && !m_launchers.contains(name)) { - auto launcher = new DBusInterfaceLauncher(name, this); + auto launcher = new DBusInterfaceLauncher(name, pid, this); m_launchers.insert(name, launcher); static_cast(parent())->addDebugger(launcher, true); } } void DBusInterfaceAdaptor::debuggingFinished(const QString &name) { auto it = m_launchers.find(name); if (it != m_launchers.end()) { + setPtracer(QCoreApplication::applicationPid(), DrKonqi::pid()); emit it.value()->finished(); } } void DBusInterfaceAdaptor::debuggerClosed(const QString &name) { auto it = m_launchers.find(name); if (it != m_launchers.end()) { emit it.value()->invalidated(); m_launchers.erase(it); } } diff --git a/src/debuggerlaunchers.h b/src/debuggerlaunchers.h index d4862b35..1e9fb9c9 100644 --- a/src/debuggerlaunchers.h +++ b/src/debuggerlaunchers.h @@ -1,111 +1,112 @@ /* Copyright (C) 2009 George Kiagiadakis This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef DEBUGGERLAUNCHERS_H #define DEBUGGERLAUNCHERS_H #include #include "debugger.h" #include "debuggermanager.h" class DetachedProcessMonitor; class AbstractDebuggerLauncher : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name) public: explicit AbstractDebuggerLauncher(QObject *parent = nullptr) : QObject(parent) {} virtual QString name() const = 0; public Q_SLOTS: virtual void start() = 0; Q_SIGNALS: void starting(); void finished(); void invalidated(); }; class DefaultDebuggerLauncher : public AbstractDebuggerLauncher { Q_OBJECT public: explicit DefaultDebuggerLauncher(const Debugger & debugger, DebuggerManager *parent = nullptr); QString name() const override; public Q_SLOTS: void start() override; private Q_SLOTS: void onProcessFinished(); private: const Debugger m_debugger; DetachedProcessMonitor *m_monitor = nullptr; }; #if 0 class TerminalDebuggerLauncher : public DefaultDebuggerLauncher { Q_OBJECT public: explicit TerminalDebuggerLauncher(const Debugger & debugger, DebuggerManager *parent = 0); public Q_SLOTS: virtual void start(); }; #endif class DBusInterfaceAdaptor; /** This class handles the old drkonqi dbus interface used by kdevelop */ class DBusInterfaceLauncher : public AbstractDebuggerLauncher { Q_OBJECT public: - explicit DBusInterfaceLauncher(const QString &name, DBusInterfaceAdaptor *parent = nullptr); + explicit DBusInterfaceLauncher(const QString &name, qint64 pid, DBusInterfaceAdaptor *parent = nullptr); QString name() const override; public Q_SLOTS: void start() override; private: QString m_name; + qint64 m_pid; }; class DBusInterfaceAdaptor : public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.drkonqi") public: explicit DBusInterfaceAdaptor(DebuggerManager *parent); public Q_SLOTS: int pid(); - Q_NOREPLY void registerDebuggingApplication(const QString &name); + Q_NOREPLY void registerDebuggingApplication(const QString &name, qint64 pid = 0); Q_NOREPLY void debuggingFinished(const QString &name); Q_NOREPLY void debuggerClosed(const QString &name); Q_SIGNALS: void acceptDebuggingApplication(const QString &name); private: QHash m_launchers; }; #endif // DEBUGGERLAUNCHERS_H diff --git a/src/ptracer.cpp b/src/ptracer.cpp new file mode 100644 index 00000000..015925ba --- /dev/null +++ b/src/ptracer.cpp @@ -0,0 +1,103 @@ +/* + Copyright (C) 2019 Christoph Roick + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#include "ptracer.h" + +#ifdef Q_OS_LINUX + +#include "drkonqi_debug.h" + +#include +#include + +#include +#include +#include + +#include + +void setPtracer(qint64 debuggerpid, qint64 debuggeepid) +{ + + int sfd = socket(PF_UNIX, SOCK_STREAM, 0); + if (sfd < 0) { + qCWarning(DRKONQI_LOG) << "socket to set ptracer not accessible"; + return; + } + + static struct sockaddr_un server; + static socklen_t sl = sizeof(server); + server.sun_family = AF_UNIX; + const QString socketPath = + QStringLiteral("%1/kcrash_%2").arg(QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation)) + .arg(debuggeepid); + + if (socketPath.size() >= static_cast(sizeof(server.sun_path))) { + qCWarning(DRKONQI_LOG) << "socket path is too long"; + close(sfd); + return; + } + strcpy(server.sun_path, QFile::encodeName(socketPath).constData()); + + if (::connect(sfd, (struct sockaddr *)&server, sl) == 0) { + static const int msize = 21; // most digits in a 64bit int (+sign +'\0') + char msg[msize]; + sprintf(msg, "%lld", debuggerpid); + + int r, bytes = 0; + while (bytes < msize) { + r = write(sfd, msg + bytes, msize - bytes); + if (r > 0) + bytes += r; + else if (r == -1 && errno != EINTR) + break; + } + if (bytes == msize) { + struct pollfd fd; + fd.fd = sfd; + fd.events = POLLIN; + while ((r = poll(&fd, 1, 1000)) == -1 && errno == EINTR) {} + if (r > 0 && (fd.revents & POLLIN)) { + char rmsg[msize]; + bytes = 0; + while (bytes < msize) { + r = read(sfd, rmsg + bytes, msize - bytes); + if (r > 0) + bytes += r; + else if (r == -1 && errno != EINTR) + break; + } + if (bytes == msize && memcmp(msg, rmsg, msize) == 0) + qCInfo(DRKONQI_LOG) << "ptracer set to" << debuggerpid << "by debugged process"; + else + qCWarning(DRKONQI_LOG) << "debugged process did not acknowledge setting ptracer to" << debuggerpid; + close(sfd); + return; + } + } + } + + qCWarning(DRKONQI_LOG) << "unable to set ptracer to" << debuggerpid; + close(sfd); +} + +#else + +void setPtracer(qint64, qint64) +{ +} + +#endif diff --git a/src/ptracer.h b/src/ptracer.h new file mode 100644 index 00000000..2f338e4e --- /dev/null +++ b/src/ptracer.h @@ -0,0 +1,25 @@ +/* + Copyright (C) 2019 Christoph Roick + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifndef PTRACER_H +#define PTRACER_H + +#include + +/** On Linux, tell the process to allow the debugger to attach to it */ +void setPtracer(qint64 debuggerpid, qint64 debuggeepid); + +#endif diff --git a/src/tests/crashtest/crashtest.cpp b/src/tests/crashtest/crashtest.cpp index 1bcc75cc..972d1858 100644 --- a/src/tests/crashtest/crashtest.cpp +++ b/src/tests/crashtest/crashtest.cpp @@ -1,158 +1,162 @@ /***************************************************************** * drkonqi - The KDE Crash Handler * * Copyright (C) 2000-2002 David Faure * Copyright (C) 2000-2002 Waldo Bastian * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************/ // Let's crash. #include #include #include #include #include #include #include #include enum CrashType { Crash, Malloc, Div0, Assert, QAssert, Threads }; struct SomeStruct { int foo() { return ret; } int ret; }; void do_crash() { SomeStruct *obj = nullptr; int ret = obj->foo(); printf("result = %d\n", ret); } void do_malloc() { delete (char*)0xdead; } void do_div0() { volatile int a = 99; volatile int b = 10; volatile int c = a / ( b - 10 ); printf("result = %d\n", c); } void do_assert() { assert(false); } void do_qassert() { Q_ASSERT(false); } void map_function(const QString & s) { while ( s != QLatin1String("thread 4") ) {} do_crash(); } void do_threads() { QStringList foo; foo << QStringLiteral("thread 1") << QStringLiteral("thread 2") << QStringLiteral("thread 3") << QStringLiteral("thread 4") << QStringLiteral("thread 5"); QThreadPool::globalInstance()->setMaxThreadCount(5); QtConcurrent::blockingMap(foo, map_function); } void level4(int t) { if (t == Malloc) do_malloc(); else if (t == Div0) do_div0(); else if (t == Assert) do_assert(); else if (t == QAssert) do_qassert(); else if (t == Threads) do_threads(); else do_crash(); } void level3(int t) { level4(t); } void level2(int t) { level3(t); } void level1(int t) { level2(t); } int main(int argc, char *argv[]) { QApplication app(argc, argv); KAboutData aboutData(QStringLiteral("crashtext"), i18n("Crash Test for DrKonqi"), QStringLiteral("1.1"), i18n("Crash Test for DrKonqi"), KAboutLicense::GPL, i18n("(c) 2000-2002 David Faure, Waldo Bastian")); QCommandLineParser parser; parser.addOption(QCommandLineOption(QStringLiteral("autorestart"), i18n("Automatically restart"))); + parser.addOption(QCommandLineOption(QStringLiteral("kdeinit"), i18n("Start DrKonqi using kdeinit"))); parser.addPositionalArgument(QStringLiteral("type"), i18n("Type of crash."), QStringLiteral("crash|malloc|div0|assert|threads")); aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); - //start drkonqi directly so that drkonqi's output goes to the console + // Start drkonqi directly by default so that drkonqi's output goes to the console. KCrash::CrashFlags flags = KCrash::AlwaysDirectly; + // This can be disabled to be able to test kcrash's real default behavior. + if (parser.isSet(QStringLiteral("kdeinit"))) + flags &= ~KCrash::AlwaysDirectly; if (parser.isSet(QStringLiteral("autorestart"))) flags |= KCrash::AutoRestart; KCrash::setFlags(flags); QByteArray type = parser.positionalArguments().isEmpty() ? QByteArray() : parser.positionalArguments().first().toUtf8(); int crashtype = Crash; if (type == "malloc") crashtype = Malloc; else if (type == "div0") crashtype = Div0; else if (type == "assert") crashtype = Assert; else if (type == "qassert") crashtype = QAssert; else if (type == "threads") crashtype = Threads; level1(crashtype); return app.exec(); }