diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,9 @@ find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets Test DBus Concurrent) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS I18n CoreAddons Service ConfigWidgets JobWidgets KIO Crash Completion XmlRpcClient WidgetsAddons Wallet Notifications IdleTime) - +if(APPLE) + find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS WindowSystem) +endif() find_package(Qt5X11Extras ${QT_MIN_VERSION} CONFIG) set_package_properties(Qt5X11Extras PROPERTIES TYPE RECOMMENDED PURPOSE "Recommended for better integration on X11.") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -111,6 +111,11 @@ Qt5::X11Extras ) endif() +if (APPLE) + target_link_libraries(drkonqi + KF5::WindowSystem + ) +endif() if (WIN32) target_link_libraries(drkonqi kdewin) diff --git a/src/backtracegenerator.cpp b/src/backtracegenerator.cpp --- a/src/backtracegenerator.cpp +++ b/src/backtracegenerator.cpp @@ -56,9 +56,12 @@ m_proc->terminate(); if (!m_proc->waitForFinished(10000)) { m_proc->kill(); - m_proc->waitForFinished(); + // lldb can become "stuck" on OS X; just mark m_proc as to be deleted later rather + // than waiting a potentially very long time for it to heed the kill() request. + m_proc->deleteLater(); + } else { + delete m_proc; } - delete m_proc; delete m_temp; } } @@ -96,6 +99,13 @@ *m_proc << KShell::splitArgs(str); m_proc->setOutputChannelMode(KProcess::OnlyStdoutChannel); m_proc->setNextOpenMode(QIODevice::ReadWrite | QIODevice::Text); + // check if the debugger should take its input from a file we'll generate, + // and take the appropriate steps if so + QString stdinFile = m_debugger.backendValueOfParameter(QStringLiteral("ExecInputFile")); + Debugger::expandString(stdinFile, Debugger::ExpansionUsageShell, m_temp->fileName()); + if (!stdinFile.isEmpty() && QFile::exists(stdinFile)) { + m_proc->setStandardInputFile(stdinFile); + } connect(m_proc, &KProcess::readyReadStandardOutput, this, &BacktraceGenerator::slotReadInput); connect(m_proc, static_cast(&KProcess::finished), this, &BacktraceGenerator::slotProcessExited); @@ -117,6 +127,11 @@ void BacktraceGenerator::slotReadInput() { + if (!m_proc) { + // this can happen with lldb after we detected that it detached from the debuggee. + return; + } + // we do not know if the output array ends in the middle of an utf-8 sequence m_output += m_proc->readAllStandardOutput(); @@ -126,6 +141,23 @@ m_output.remove(0, pos + 1); emit newLine(line); + line = line.simplified(); + if (line.startsWith(QLatin1String("Process ")) && line.endsWith(QLatin1String(" detached"))) { + // lldb is acting on a detach command (in lldbrc) + // Anything following this line doesn't interest us, and lldb has been known + // to turn into a zombie instead of exitting, thereby blocking us. + // Tell the process to quit if it's still running, and pretend it did. + if (m_proc && m_proc->state() == QProcess::Running) { + m_proc->terminate(); + if (!m_proc->waitForFinished(500)) { + m_proc->kill(); + } + if (m_proc) { + slotProcessExited(0, QProcess::NormalExit); + } + } + return; + } } } diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp --- a/src/backtracewidget.cpp +++ b/src/backtracewidget.cpp @@ -205,6 +205,9 @@ // scroll to crash QTextCursor crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("[KCrash Handler]")); + if (crashCursor.isNull()) { + crashCursor = ui.m_backtraceEdit->document()->find(QStringLiteral("KCrash::defaultCrashHandler")); + } if (!crashCursor.isNull()) { crashCursor.movePosition(QTextCursor::Up, QTextCursor::MoveAnchor); ui.m_backtraceEdit->verticalScrollBar()->setValue(ui.m_backtraceEdit->cursorRect(crashCursor).top()); diff --git a/src/data/debuggers/external.mac/lldbrc b/src/data/debuggers/external.mac/lldbrc new file mode 100644 --- /dev/null +++ b/src/data/debuggers/external.mac/lldbrc @@ -0,0 +1,8 @@ +[General] +Name=lldb +TryExec=lldb +Backends=KCrash + +[KCrash] +Exec=AppleTerminal lldb -p %pid +Terminal=true diff --git a/src/data/debuggers/external/lldbrc b/src/data/debuggers/external/lldbrc new file mode 100644 --- /dev/null +++ b/src/data/debuggers/external/lldbrc @@ -0,0 +1,8 @@ +[General] +Name=lldb +TryExec=lldb +Backends=KCrash + +[KCrash] +Exec=konsole --nofork -e lldb -p %pid +Terminal=true diff --git a/src/data/debuggers/internal/lldbrc b/src/data/debuggers/internal/lldbrc new file mode 100644 --- /dev/null +++ b/src/data/debuggers/internal/lldbrc @@ -0,0 +1,9 @@ +[General] +Name=lldb +TryExec=lldb +Backends=KCrash + +[KCrash] +Exec=lldb -p %pid +ExecInputFile=%tempfile +BatchCommands=set term-width 200\nthread info\nbt all\ndetach diff --git a/src/debugger.h b/src/debugger.h --- a/src/debugger.h +++ b/src/debugger.h @@ -70,6 +70,8 @@ /** If this is an external debugger, it returns whether it should be run in a terminal or not */ bool runInTerminal() const; + /** Returns the value of the arbitrary configuration parameter @param key, or an empty QString if @param key isn't defined */ + QString backendValueOfParameter(const QString &key) const; enum ExpandStringUsage { ExpansionUsagePlainText, diff --git a/src/debugger.cpp b/src/debugger.cpp --- a/src/debugger.cpp +++ b/src/debugger.cpp @@ -105,6 +105,15 @@ } } +QString Debugger::backendValueOfParameter(const QString &key) const +{ + if (!isValid() || !m_config->hasGroup(m_backend)) { + return QString(); + } else { + return m_config->group(m_backend).readEntry(key, QString()); + } +} + //static void Debugger::expandString(QString & str, ExpandStringUsage usage, const QString & tempFile) { diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -36,6 +36,10 @@ #include "backtracegenerator.h" #include "drkonqi.h" +#ifdef Q_OS_MACOS +#include +#endif + AbstractDrKonqiBackend::~AbstractDrKonqiBackend() { } @@ -158,7 +162,9 @@ { QList internalDebuggers = Debugger::availableInternalDebuggers(QStringLiteral("KCrash")); KConfigGroup config(KSharedConfig::openConfig(), "DrKonqi"); -#ifndef Q_OS_WIN +#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED > 1070 + QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("lldb")); +#elif !defined(Q_OS_WIN) QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb")); #else QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("kdbgwin")); diff --git a/src/main.cpp b/src/main.cpp --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,9 @@ #if HAVE_X11 #include #endif +#ifdef Q_OS_MACOS +#include +#endif #include "drkonqi.h" #include "drkonqidialog.h" @@ -81,7 +84,7 @@ QStringLiteral("gkiagia@users.sourceforge.net")); aboutData.addAuthor(i18nc("@info:credit","A. L. Spehr"), QString(), QStringLiteral("spehr@kde.org")); - qa.setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"))); + qa.setWindowIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug"), qa.windowIcon())); qa.setDesktopFileName(QStringLiteral("org.kde.drkonqi")); QCommandLineParser parser; @@ -152,6 +155,9 @@ DrKonqiDialog *w = new DrKonqiDialog(); QObject::connect(w, &DrKonqiDialog::rejected, &qa, &QApplication::quit); w->show(); +#ifdef Q_OS_MACOS + KWindowSystem::forceActiveWindow(w->winId()); +#endif }; bool restarted = parser.isSet(restartedOption); diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt --- a/src/parser/CMakeLists.txt +++ b/src/parser/CMakeLists.txt @@ -3,6 +3,7 @@ backtraceparsergdb.cpp backtraceparserkdbgwin.cpp backtraceparsernull.cpp + backtraceparserlldb.cpp ) ecm_qt_declare_logging_category(BACKTRACEPARSER_SRCS HEADER drkonqi_parser_debug.h IDENTIFIER DRKONQI_PARSER_LOG CATEGORY_NAME org.kde.drkonqi.parser) diff --git a/src/parser/backtraceparser.cpp b/src/parser/backtraceparser.cpp --- a/src/parser/backtraceparser.cpp +++ b/src/parser/backtraceparser.cpp @@ -18,6 +18,7 @@ #include "backtraceparser_p.h" #include "backtraceparsergdb.h" #include "backtraceparserkdbgwin.h" +#include "backtraceparserlldb.h" #include "backtraceparsernull.h" #include "drkonqi_parser_debug.h" #include @@ -31,6 +32,8 @@ return new BacktraceParserGdb(parent); } else if (debuggerName == QLatin1String("kdbgwin")) { return new BacktraceParserKdbgwin(parent); + } else if (debuggerName == QLatin1String("lldb")) { + return new BacktraceParserLldb(parent); } else { return new BacktraceParserNull(parent); } @@ -198,6 +201,10 @@ || line.functionName().startsWith(QLatin1String("*__GI_")) //glibc2.9 uses *__GI_ as prefix || line.libraryName().contains(QStringLiteral("libpthread.so")) || line.libraryName().contains(QStringLiteral("libglib-2.0.so")) +#ifdef Q_OS_MACOS + || (line.libraryName().startsWith(QStringLiteral("libsystem_")) && line.libraryName().endsWith(QStringLiteral(".dylib"))) + || line.libraryName().contains(QStringLiteral("Foundation`")) +#endif || line.libraryName().contains(QStringLiteral("ntdll.dll")) || line.libraryName().contains(QStringLiteral("kernel32.dll")) || line.functionName().contains(QStringLiteral("_tmain")) diff --git a/src/parser/backtraceparserlldb.h b/src/parser/backtraceparserlldb.h new file mode 100644 --- /dev/null +++ b/src/parser/backtraceparserlldb.h @@ -0,0 +1,37 @@ +/* + Copyright (C) 2014-2016 René J.V. Bertin + + 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#ifndef BACKTRACEPARSERLLDB_H +#define BACKTRACEPARSERLLDB_H + +#include "backtraceparser.h" + +class BacktraceParserLldb : public BacktraceParser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(BacktraceParser) +public: + explicit BacktraceParserLldb(QObject *parent = 0); + +protected Q_SLOTS: + virtual void newLine(const QString & lineStr); + +protected: + virtual BacktraceParserPrivate *constructPrivate() const; +}; + +#endif // BACKTRACEPARSERLLDB_H diff --git a/src/parser/backtraceparserlldb.cpp b/src/parser/backtraceparserlldb.cpp new file mode 100644 --- /dev/null +++ b/src/parser/backtraceparserlldb.cpp @@ -0,0 +1,57 @@ +/* + Copyright (C) 2014 René J.V. Bertin + + 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ +#include "backtraceparserlldb.h" +#include "backtraceparser_p.h" + +//BEGIN BacktraceParserLldb + +class BacktraceLineLldb : public BacktraceLine +{ +public: + BacktraceLineLldb(const QString & line); +}; + +BacktraceLineLldb::BacktraceLineLldb(const QString & line) + : BacktraceLine() +{ + d->m_line = line; + // For now we'll have faith that lldb provides useful information, and that it would + // be unwarranted to give it a rating of "MissingEverything". + d->m_rating = Good; +} + +//END BacktraceLineLldb + +//BEGIN BacktraceParserLldb + +BacktraceParserLldb::BacktraceParserLldb(QObject *parent) : BacktraceParser(parent) {} + +BacktraceParserPrivate *BacktraceParserLldb::constructPrivate() const +{ + BacktraceParserPrivate *d = BacktraceParser::constructPrivate(); + d->m_usefulness = MayBeUseful; + return d; +} + +void BacktraceParserLldb::newLine(const QString & lineStr) +{ + d_ptr->m_linesList.append(BacktraceLineLldb(lineStr)); +} + + +//END BacktraceParserLldb diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -4,45 +4,47 @@ add_subdirectory(bugzillalibtest) endif() -if(NOT RUBY_EXECTUABLE) - find_program(RUBY_EXECUTABLE ruby) -endif() -if(RUBY_EXECUTABLE) - execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'atspi'" - RESULT_VARIABLE RUBY_ATSPI) - execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'xmlrpc/server'" - RESULT_VARIABLE RUBY_XMLRPC) -endif() -if(NOT GDB_EXECUTABLE) - # Needed so drkonqi can actually trace something. - find_program(GDB_EXECUTABLE gdb) -endif() -if(NOT XVFB_RUN_EXECTUABLE) - find_program(XVFB_RUN_EXECTUABLE xvfb-run) -endif() +if(NOT APPLE) + if(NOT RUBY_EXECTUABLE) + find_program(RUBY_EXECUTABLE ruby) + endif() + if(RUBY_EXECUTABLE) + execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'atspi'" + RESULT_VARIABLE RUBY_ATSPI) + execute_process(COMMAND ${RUBY_EXECUTABLE} -e "require 'xmlrpc/server'" + RESULT_VARIABLE RUBY_XMLRPC) + endif() + if(NOT GDB_EXECUTABLE) + # Needed so drkonqi can actually trace something. + find_program(GDB_EXECUTABLE gdb) + endif() + if(NOT XVFB_RUN_EXECTUABLE) + find_program(XVFB_RUN_EXECTUABLE xvfb-run) + endif() -set(ATSPI_PATHS - /usr/lib/at-spi2-core/ # debians - /usr/lib/at-spi2/ # suses -) -if(NOT ATSPI_BUS_LAUNCHER_EXECUTABLE) - find_program(ATSPI_BUS_LAUNCHER_EXECUTABLE - NAMES at-spi-bus-launcher - PATHS ${ATSPI_PATHS} - DOC "AT-SPI accessibility dbus launcher") -endif() -if(NOT ATSPI_REGISTRYD_EXECUTABLE) - find_program(ATSPI_REGISTRYD_EXECUTABLE - NAMES at-spi2-registryd - PATHS ${ATSPI_PATHS} - DOC "AT-SPI accessibility registry daemon") -endif() + set(ATSPI_PATHS + /usr/lib/at-spi2-core/ # debians + /usr/lib/at-spi2/ # suses + ) + if(NOT ATSPI_BUS_LAUNCHER_EXECUTABLE) + find_program(ATSPI_BUS_LAUNCHER_EXECUTABLE + NAMES at-spi-bus-launcher + PATHS ${ATSPI_PATHS} + DOC "AT-SPI accessibility dbus launcher") + endif() + if(NOT ATSPI_REGISTRYD_EXECUTABLE) + find_program(ATSPI_REGISTRYD_EXECUTABLE + NAMES at-spi2-registryd + PATHS ${ATSPI_PATHS} + DOC "AT-SPI accessibility registry daemon") + endif() -if(RUBY_EXECUTABLE AND XVFB_RUN_EXECTUABLE AND ATSPI_BUS_LAUNCHER_EXECUTABLE - AND ATSPI_REGISTRYD_EXECUTABLE AND GDB_EXECUTABLE - AND RUBY_ATSPI EQUAL 0 AND RUBY_XMLRPC EQUAL 0) - set(WITH_DRKONI_INTEGRATION_TESTING TRUE) - add_subdirectory(integration) + if(RUBY_EXECUTABLE AND XVFB_RUN_EXECTUABLE AND ATSPI_BUS_LAUNCHER_EXECUTABLE + AND ATSPI_REGISTRYD_EXECUTABLE AND GDB_EXECUTABLE + AND RUBY_ATSPI EQUAL 0 AND RUBY_XMLRPC EQUAL 0) + set(WITH_DRKONI_INTEGRATION_TESTING TRUE) + add_subdirectory(integration) + endif() + add_feature_info(DrKonqiIntegrationTesting WITH_DRKONI_INTEGRATION_TESTING + "Needs Ruby, functional atspi and xmlrpc gems, gdb, as well as xvfb-run.") endif() -add_feature_info(DrKonqiIntegrationTesting WITH_DRKONI_INTEGRATION_TESTING - "Needs Ruby, functional atspi and xmlrpc gems, gdb, as well as xvfb-run.")