diff --git a/CMakeLists.txt b/CMakeLists.txt index 364f90ac..27612812 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,83 +1,85 @@ cmake_minimum_required(VERSION 3.0) project(drkonqi) set(PROJECT_VERSION "5.14.80") set(PROJECT_VERSION_MAJOR 5) set(QT_MIN_VERSION "5.11.0") set(KF5_MIN_VERSION "5.50.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(ECMQtDeclareLoggingCategory) include(ECMAddTests) include(ECMMarkAsTest) include(CheckFunctionExists) include(FeatureSummary) 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.") if (MINGW) find_package(ZLIB REQUIRED) find_library(INTL_LIBRARY NAMES intl) find_library(IBERTY_LIBRARY NAMES iberty) find_library(BFD_LIBRARY NAMES bfd) if (IBERTY_LIBRARY) set(iberty_FOUND 1) else() set(msg "iberty") endif() if (BFD_LIBRARY) set(bfd_FOUND 1) else() set(msg "${msg} bfd") endif() if (INTL_LIBRARY) set(intl_FOUND 1) else() set(msg "${msg} intl") endif() if (msg) message(FATAL_ERROR "could not find ${msg}") endif() add_library(z STATIC IMPORTED) set_target_properties(z PROPERTIES IMPORTED_LOCATION ${ZLIB_LIBRARIES} IMPORTED_INCLUDE_DIRECTORIES ${ZLIB_INCLUDE_DIRS} ) add_library(intl SHARED IMPORTED) set_target_properties(intl PROPERTIES IMPORTED_IMPLIB ${INTL_LIBRARY} ) add_library(iberty STATIC IMPORTED) set_target_properties(iberty PROPERTIES IMPORTED_LOCATION ${IBERTY_LIBRARY} ) add_library(bfd STATIC IMPORTED) set_target_properties(bfd PROPERTIES IMPORTED_LOCATION ${BFD_LIBRARY} # bfd header requires this to be defined INTERFACE_COMPILE_DEFINITIONS "PACKAGE;PACKAGE_VERSION" ) endif() include_directories("${CMAKE_CURRENT_BINARY_DIR}") configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) add_definitions(-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT) add_subdirectory(src) install( FILES drkonqi.categories DESTINATION ${KDE_INSTALL_CONFDIR} ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0f4cc51a..46b86d72 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,126 +1,131 @@ 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 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/backtracegenerator.cpp b/src/backtracegenerator.cpp index 91986c45..11296bb6 100644 --- a/src/backtracegenerator.cpp +++ b/src/backtracegenerator.cpp @@ -1,165 +1,197 @@ /***************************************************************** * drkonqi - The KDE Crash Handler * * Copyright (C) 2000-2003 Hans Petter Bieker * Copyright (C) 2009 George Kiagiadakis * * 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. *****************************************************************/ #include "backtracegenerator.h" #include #include "drkonqi_debug.h" #include #include #include "parser/backtraceparser.h" BacktraceGenerator::BacktraceGenerator(const Debugger & debugger, QObject *parent) : QObject(parent), m_debugger(debugger), m_proc(nullptr), m_temp(nullptr), m_state(NotLoaded) { m_parser = BacktraceParser::newParser(m_debugger.codeName(), this); m_parser->connectToGenerator(this); #ifdef BACKTRACE_PARSER_DEBUG m_debugParser = BacktraceParser::newParser(QString(), this); //uses the null parser m_debugParser->connectToGenerator(this); #endif } BacktraceGenerator::~BacktraceGenerator() { if (m_proc && m_proc->state() == QProcess::Running) { qCWarning(DRKONQI_LOG) << "Killing running debugger instance"; m_proc->disconnect(this); 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; } } bool BacktraceGenerator::start() { //they should always be null before entering this function. Q_ASSERT(!m_proc); Q_ASSERT(!m_temp); m_parsedBacktrace.clear(); m_state = Loading; emit starting(); if (!m_debugger.isValid() || !m_debugger.isInstalled()) { m_state = FailedToStart; emit failedToStart(); return false; } m_proc = new KProcess; m_proc->setEnv(QStringLiteral("LC_ALL"), QStringLiteral("C")); // force C locale m_temp = new QTemporaryFile; m_temp->open(); m_temp->write(m_debugger.backtraceBatchCommands().toLatin1()); m_temp->write("\n", 1); m_temp->flush(); // start the debugger QString str = m_debugger.command(); Debugger::expandString(str, Debugger::ExpansionUsageShell, m_temp->fileName()); *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); m_proc->start(); if (!m_proc->waitForStarted()) { //we mustn't keep these around... m_proc->deleteLater(); m_temp->deleteLater(); m_proc = nullptr; m_temp = nullptr; m_state = FailedToStart; emit failedToStart(); return false; } return true; } 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(); int pos; while ((pos = m_output.indexOf('\n')) != -1) { QString line = QString::fromLocal8Bit(m_output.constData(), pos + 1); 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; + } } } void BacktraceGenerator::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) { //these are useless now m_proc->deleteLater(); m_temp->deleteLater(); m_proc = nullptr; m_temp = nullptr; //mark the end of the backtrace for the parser emit newLine(QString()); if (exitStatus != QProcess::NormalExit || exitCode != 0) { m_state = Failed; emit someError(); return; } //no translation, string appears in the report QString tmp(QStringLiteral("Application: %progname (%execname), signal: %signame\n")); Debugger::expandString(tmp); m_parsedBacktrace = tmp + m_parser->parsedBacktrace(); m_state = Loaded; #ifdef BACKTRACE_PARSER_DEBUG //append the raw unparsed backtrace m_parsedBacktrace += "\n------------ Unparsed Backtrace ------------\n"; m_parsedBacktrace += m_debugParser->parsedBacktrace(); //it's not really parsed, it's from the null parser. #endif emit done(); } diff --git a/src/backtracewidget.cpp b/src/backtracewidget.cpp index 6c4d7357..add70677 100644 --- a/src/backtracewidget.cpp +++ b/src/backtracewidget.cpp @@ -1,402 +1,405 @@ /******************************************************************* * backtracewidget.cpp * Copyright 2009,2010 Dario Andres Rodriguez * * 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 "backtracewidget.h" #include #include #include #include #include #include #include "drkonqi.h" #include "backtraceratingwidget.h" #include "crashedapplication.h" #include "backtracegenerator.h" #include "parser/backtraceparser.h" #include "drkonqi_globals.h" #include "debuggermanager.h" #include "gdbhighlighter.h" static const char extraDetailsLabelMargin[] = " margin: 5px; "; BacktraceWidget::BacktraceWidget(BacktraceGenerator *generator, QWidget *parent, bool showToggleBacktrace) : QWidget(parent), m_btGenerator(generator), m_highlighter(nullptr) { ui.setupUi(this); //Debug package installer m_debugPackageInstaller = new DebugPackageInstaller(this); connect(m_debugPackageInstaller, &DebugPackageInstaller::error, this, &BacktraceWidget::debugPackageError); connect(m_debugPackageInstaller, &DebugPackageInstaller::packagesInstalled, this, &BacktraceWidget::regenerateBacktrace); connect(m_debugPackageInstaller, &DebugPackageInstaller::canceled, this, &BacktraceWidget::debugPackageCanceled); connect(m_btGenerator, &BacktraceGenerator::done, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::someError, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::failedToStart, this, &BacktraceWidget::loadData); connect(m_btGenerator, &BacktraceGenerator::newLine, this, &BacktraceWidget::backtraceNewLine); connect(ui.m_extraDetailsLabel, &QLabel::linkActivated, this, &BacktraceWidget::extraDetailsLinkActivated); ui.m_extraDetailsLabel->setVisible(false); ui.m_extraDetailsLabel->setStyleSheet(QLatin1String(extraDetailsLabelMargin)); //Setup the buttons KGuiItem::assign(ui.m_reloadBacktraceButton, KGuiItem2(i18nc("@action:button", "&Reload"), QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@info:tooltip", "Use this button to " "reload the crash information (backtrace). This is useful when you have " "installed the proper debug symbol packages and you want to obtain " "a better backtrace."))); connect(ui.m_reloadBacktraceButton, &QPushButton::clicked, this, &BacktraceWidget::regenerateBacktrace); KGuiItem::assign(ui.m_installDebugButton, KGuiItem2(i18nc("@action:button", "&Install Debug Symbols"), QIcon::fromTheme(QStringLiteral("system-software-update")), i18nc("@info:tooltip", "Use this button to " "install the missing debug symbols packages."))); ui.m_installDebugButton->setVisible(false); connect(ui.m_installDebugButton, &QPushButton::clicked, this, &BacktraceWidget::installDebugPackages); KGuiItem::assign(ui.m_copyButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@info:tooltip", "Use this button to copy the " "crash information (backtrace) to the clipboard."))); connect(ui.m_copyButton, &QPushButton::clicked, this, &BacktraceWidget::copyClicked); ui.m_copyButton->setEnabled(false); KGuiItem::assign(ui.m_saveButton, KGuiItem2(QString(), QIcon::fromTheme(QStringLiteral("document-save")), i18nc("@info:tooltip", "Use this button to save the " "crash information (backtrace) to a file. This is useful " "if you want to take a look at it or to report the bug " "later."))); connect(ui.m_saveButton, &QPushButton::clicked, this, &BacktraceWidget::saveClicked); ui.m_saveButton->setEnabled(false); //Create the rating widget m_backtraceRatingWidget = new BacktraceRatingWidget(ui.m_statusWidget); ui.m_statusWidget->addCustomStatusWidget(m_backtraceRatingWidget); ui.m_statusWidget->setIdle(QString()); //Do we need the "Show backtrace" toggle action ? if (!showToggleBacktrace) { ui.mainLayout->removeWidget(ui.m_toggleBacktraceCheckBox); ui.m_toggleBacktraceCheckBox->setVisible(false); toggleBacktrace(true); } else { //Generate help widget ui.m_backtraceHelpLabel->setText( i18n("

What is a \"backtrace\" ?

A backtrace basically describes what was " "happening inside the application when it crashed, so the developers may track " "down where the mess started. They may look meaningless to you, but they might " "actually contain a wealth of useful information.
Backtraces are commonly " "used during interactive and post-mortem debugging.

")); ui.m_backtraceHelpIcon->setPixmap(QIcon::fromTheme(QStringLiteral("help-hint")).pixmap(48,48)); connect(ui.m_toggleBacktraceCheckBox, &QCheckBox::toggled, this, &BacktraceWidget::toggleBacktrace); toggleBacktrace(false); } ui.m_backtraceEdit->setFont( QFontDatabase::systemFont(QFontDatabase::FixedFont) ); } void BacktraceWidget::setAsLoading() { //remove the syntax highlighter delete m_highlighter; m_highlighter = nullptr; //Set the widget as loading and disable all the action buttons ui.m_backtraceEdit->setText(i18nc("@info:status", "Loading...")); ui.m_backtraceEdit->setEnabled(false); ui.m_statusWidget->setBusy(i18nc("@info:status", "Generating backtrace... (this may take some time)")); m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); m_backtraceRatingWidget->setState(BacktraceGenerator::Loading); ui.m_extraDetailsLabel->setVisible(false); ui.m_extraDetailsLabel->clear(); ui.m_installDebugButton->setVisible(false); ui.m_reloadBacktraceButton->setEnabled(false); ui.m_copyButton->setEnabled(false); ui.m_saveButton->setEnabled(false); } //Force backtrace generation void BacktraceWidget::regenerateBacktrace() { setAsLoading(); if (!DrKonqi::debuggerManager()->debuggerIsRunning()) { m_btGenerator->start(); } else { anotherDebuggerRunning(); } emit stateChanged(); } void BacktraceWidget::generateBacktrace() { if (m_btGenerator->state() == BacktraceGenerator::NotLoaded) { //First backtrace generation regenerateBacktrace(); } else if (m_btGenerator->state() == BacktraceGenerator::Loading) { //Set in loading state, the widget will catch the backtrace events anyway setAsLoading(); emit stateChanged(); } else { //*Finished* states setAsLoading(); emit stateChanged(); //Load already generated information loadData(); } } void BacktraceWidget::anotherDebuggerRunning() { //As another debugger is running, we should disable the actions and notify the user ui.m_backtraceEdit->setEnabled(false); ui.m_backtraceEdit->setText(i18nc("@info", "Another debugger is currently debugging the " "same application. The crash information could not be fetched.")); m_backtraceRatingWidget->setState(BacktraceGenerator::Failed); m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The crash information could not be fetched.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "Another debugging process is attached to " "the crashed application. Therefore, the DrKonqi debugger cannot " "fetch the backtrace. Please close the other debugger and " "click Reload.")); ui.m_installDebugButton->setVisible(false); ui.m_reloadBacktraceButton->setEnabled(true); } void BacktraceWidget::loadData() { //Load the backtrace data from the generator m_backtraceRatingWidget->setState(m_btGenerator->state()); if (m_btGenerator->state() == BacktraceGenerator::Loaded) { ui.m_backtraceEdit->setEnabled(true); ui.m_backtraceEdit->setPlainText(m_btGenerator->backtrace()); // 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()); } // highlight if possible if (m_btGenerator->debugger().codeName() == QLatin1String("gdb")) { m_highlighter = new GdbHighlighter(ui.m_backtraceEdit->document(), m_btGenerator->parser()->parsedBacktraceLines()); } BacktraceParser * btParser = m_btGenerator->parser(); m_backtraceRatingWidget->setUsefulness(btParser->backtraceUsefulness()); //Generate the text to put in the status widget (backtrace usefulness) QString usefulnessText; switch (btParser->backtraceUsefulness()) { case BacktraceParser::ReallyUseful: usefulnessText = i18nc("@info", "The generated crash information is useful"); break; case BacktraceParser::MayBeUseful: usefulnessText = i18nc("@info", "The generated crash information may be useful"); break; case BacktraceParser::ProbablyUseless: usefulnessText = i18nc("@info", "The generated crash information is probably not useful"); break; case BacktraceParser::Useless: usefulnessText = i18nc("@info", "The generated crash information is not useful"); break; default: //let's hope nobody will ever see this... ;) usefulnessText = i18nc("@info", "The rating of this crash information is invalid. " "This is a bug in DrKonqi itself."); break; } ui.m_statusWidget->setIdle(usefulnessText); if (btParser->backtraceUsefulness() != BacktraceParser::ReallyUseful) { //Not a perfect bactrace, ask the user to try to improve it ui.m_extraDetailsLabel->setVisible(true); if (canInstallDebugPackages()) { //The script to install the debug packages is available ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You can click the " "Install Debug Symbols button in order to automatically " "install the missing debugging information packages. If this method " "does not work: please read How to " "create useful crash reports to learn how to get a useful " "backtrace; install the needed packages (" "list of files) and click the " "Reload button.", QLatin1String(TECHBASE_HOWTO_DOC), QLatin1String("#missingDebugPackages"))); ui.m_installDebugButton->setVisible(true); //Retrieve the libraries with missing debug info QStringList missingLibraries = btParser->librariesWithMissingDebugSymbols().toList(); m_debugPackageInstaller->setMissingLibraries(missingLibraries); } else { //No automated method to install the missing debug info //Tell the user to read the howto and reload ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "Please read How to " "create useful crash reports to learn how to get a useful " "backtrace; install the needed packages (" "list of files) and click the " "Reload button.", QLatin1String(TECHBASE_HOWTO_DOC), QLatin1String("#missingDebugPackages"))); } } ui.m_copyButton->setEnabled(true); ui.m_saveButton->setEnabled(true); } else if (m_btGenerator->state() == BacktraceGenerator::Failed) { //The backtrace could not be generated because the debugger had an error m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger has quit unexpectedly.")); ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You could try to regenerate the " "backtrace by clicking the Reload" " button.")); } else if (m_btGenerator->state() == BacktraceGenerator::FailedToStart) { //The backtrace could not be generated because the debugger could not start (missing) //Tell the user to install it. m_backtraceRatingWidget->setUsefulness(BacktraceParser::Useless); ui.m_statusWidget->setIdle(i18nc("@info:status", "The debugger application is missing or " "could not be launched.")); ui.m_backtraceEdit->setPlainText(i18nc("@info:status", "The crash information could not be generated.")); ui.m_extraDetailsLabel->setVisible(true); ui.m_extraDetailsLabel->setText(xi18nc("@info/rich", "You need to first install the debugger " "application (%1) then click the Reload" " button.", m_btGenerator->debugger().name())); } ui.m_reloadBacktraceButton->setEnabled(true); emit stateChanged(); } void BacktraceWidget::backtraceNewLine(const QString & line) { //While loading the backtrace (unparsed) a new line was sent from the debugger, append it ui.m_backtraceEdit->append(line.trimmed()); } void BacktraceWidget::copyClicked() { ui.m_backtraceEdit->selectAll(); ui.m_backtraceEdit->copy(); } void BacktraceWidget::saveClicked() { DrKonqi::saveReport(m_btGenerator->backtrace(), this); } void BacktraceWidget::hilightExtraDetailsLabel(bool hilight) { QString stylesheet; if (hilight) { stylesheet = QLatin1String("border-width: 2px; " "border-style: solid; " "border-color: red;"); } else { stylesheet = QLatin1String("border-width: 0px;"); } stylesheet += QLatin1String(extraDetailsLabelMargin); ui.m_extraDetailsLabel->setStyleSheet(stylesheet); } void BacktraceWidget::focusImproveBacktraceButton() { ui.m_installDebugButton->setFocus(); } void BacktraceWidget::installDebugPackages() { ui.m_installDebugButton->setVisible(false); m_debugPackageInstaller->installDebugPackages(); } void BacktraceWidget::debugPackageError(const QString & errorMessage) { ui.m_installDebugButton->setVisible(true); KMessageBox::error(this, errorMessage, i18nc("@title:window", "Error during the installation of" " debug symbols")); } void BacktraceWidget::debugPackageCanceled() { ui.m_installDebugButton->setVisible(true); } bool BacktraceWidget::canInstallDebugPackages() const { return m_debugPackageInstaller->canInstallDebugPackages(); } void BacktraceWidget::toggleBacktrace(bool show) { ui.m_backtraceStack->setCurrentWidget(show ? ui.backtracePage : ui.backtraceHelpPage); } void BacktraceWidget::extraDetailsLinkActivated(QString link) { if (link.startsWith(QLatin1String("http"))) { //Open externally QDesktopServices::openUrl(QUrl(link)); } else if (link == QLatin1String("#missingDebugPackages")) { BacktraceParser * btParser = m_btGenerator->parser(); QStringList missingDbgForFiles = btParser->librariesWithMissingDebugSymbols().toList(); missingDbgForFiles.insert(0, DrKonqi::crashedApplication()->executable().absoluteFilePath()); //HTML message QString message = QStringLiteral(""); message += i18n("The packages containing debug information for the following application and libraries are missing:"); message += QStringLiteral("
    "); Q_FOREACH(const QString & string, missingDbgForFiles) { message += QStringLiteral("
  • ") + string + QStringLiteral("
  • "); } message += QStringLiteral("
"); KMessageBox::information(this, message, i18nc("messagebox title","Missing debug information packages")); } } diff --git a/src/data/debuggers/external.mac/lldbrc b/src/data/debuggers/external.mac/lldbrc new file mode 100644 index 00000000..65d7945b --- /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 index 00000000..c8ef63b9 --- /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 index 00000000..0606f8f5 --- /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.cpp b/src/debugger.cpp index 7e833f0f..9f86dfc9 100644 --- a/src/debugger.cpp +++ b/src/debugger.cpp @@ -1,152 +1,161 @@ /* 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 "debugger.h" #include #include #include #include #include #include "crashedapplication.h" #include "drkonqi.h" //static QList Debugger::availableInternalDebuggers(const QString & backend) { return availableDebuggers(QStringLiteral("debuggers/internal"), backend); } //static QList Debugger::availableExternalDebuggers(const QString & backend) { return availableDebuggers(QStringLiteral("debuggers/external"), backend); } bool Debugger::isValid() const { return m_config; } bool Debugger::isInstalled() const { QString tryexec = tryExec(); return !tryexec.isEmpty() && !QStandardPaths::findExecutable(tryexec).isEmpty(); } QString Debugger::name() const { return isValid() ? m_config->group("General").readEntry("Name") : QString(); } QString Debugger::codeName() const { //fall back to the "TryExec" string if "CodeName" is not specified. //for most debuggers those strings should be the same return isValid() ? m_config->group("General").readEntry("CodeName", tryExec()) : QString(); } QString Debugger::tryExec() const { return isValid() ? m_config->group("General").readEntry("TryExec") : QString(); } QStringList Debugger::supportedBackends() const { return isValid() ? m_config->group("General").readEntry("Backends") .split(QLatin1Char('|'), QString::SkipEmptyParts) : QStringList(); } void Debugger::setUsedBackend(const QString & backendName) { if (supportedBackends().contains(backendName)) { m_backend = backendName; } } QString Debugger::command() const { if (!isValid() || !m_config->hasGroup(m_backend)) { return QString(); } else { return m_config->group(m_backend).readPathEntry("Exec", QString()); } } QString Debugger::backtraceBatchCommands() const { if (!isValid() || !m_config->hasGroup(m_backend)) { return QString(); } else { return m_config->group(m_backend).readEntry("BatchCommands"); } } bool Debugger::runInTerminal() const { if (!isValid() || !m_config->hasGroup(m_backend)) { return false; } else { return m_config->group(m_backend).readEntry("Terminal", false); } } +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) { const CrashedApplication *appInfo = DrKonqi::crashedApplication(); QHash map; map[QLatin1String("progname")] = appInfo->name(); map[QLatin1String("execname")] = appInfo->fakeExecutableBaseName(); map[QLatin1String("execpath")] = appInfo->executable().absoluteFilePath(); map[QLatin1String("signum")] = QString::number(appInfo->signalNumber()); map[QLatin1String("signame")] = appInfo->signalName(); map[QLatin1String("pid")] = QString::number(appInfo->pid()); map[QLatin1String("tempfile")] = tempFile; map[QLatin1String("thread")] = QString::number(appInfo->thread()); if (usage == ExpansionUsageShell) { str = KMacroExpander::expandMacrosShellQuote(str, map); } else { str = KMacroExpander::expandMacros(str, map); } } //static QList Debugger::availableDebuggers(const QString & path, const QString & backend) { QStringList debuggerDirs { // Search in default path QStandardPaths::locate(QStandardPaths::AppDataLocation, path, QStandardPaths::LocateDirectory), // Search from application path, this helps when deploying an application QString(QStringLiteral("%1/%2")).arg(QCoreApplication::applicationDirPath(), path) }; QList result; for (const auto & debuggerDir: debuggerDirs) { QStringList debuggers = QDir(debuggerDir).entryList(QDir::Files); for (const auto & debuggerFile : debuggers) { Debugger debugger; debugger.m_config = KSharedConfig::openConfig(QString(QStringLiteral("%1/%2")).arg(debuggerDir, debuggerFile)); if (debugger.supportedBackends().contains(backend)) { debugger.setUsedBackend(backend); result.append(debugger); } } } return result; } diff --git a/src/debugger.h b/src/debugger.h index 878911b5..485d89cc 100644 --- a/src/debugger.h +++ b/src/debugger.h @@ -1,88 +1,90 @@ /* 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 DEBUGGER_H #define DEBUGGER_H #include #include class Debugger { public: static QList availableInternalDebuggers(const QString & backend); static QList availableExternalDebuggers(const QString & backend); /** Returns true if this Debugger instance is valid, or false otherwise. * Debugger instances are valid only if they have been constructed from * availableInternalDebuggers() or availableExternalDebuggers(). If they * have been constructed directly using the Debugger constructor, they are invalid. */ bool isValid() const; /** Returns true if this debugger is installed. This is determined by * looking for the executable that tryExec() returns. If it is in $PATH, * this method returns true. */ bool isInstalled() const; /** Returns the translatable name of the debugger (eg. "GDB") */ QString name() const; /** Returns the code name of the debugger (eg. "gdb"). */ QString codeName() const; /** Returns the executable name that drkonqi should check if it exists * to determine whether the debugger is installed */ QString tryExec() const; /** Returns a list with the drkonqi backends that this debugger supports */ QStringList supportedBackends() const; /** Sets the backend to be used. This function must be called before using * command(), backtraceBatchCommands() or runInTerminal(). */ void setUsedBackend(const QString & backendName); /** Returns the command that should be run to use the debugger */ QString command() const; /** Returns the commands that should be given to the debugger when * run in batch mode in order to generate a backtrace */ QString backtraceBatchCommands() const; /** 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, ExpansionUsageShell }; static void expandString(QString & str, ExpandStringUsage usage = ExpansionUsagePlainText, const QString & tempFile = QString()); private: static QList availableDebuggers(const QString &path, const QString & backend); KSharedConfig::Ptr m_config; QString m_backend; }; #endif diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp index c9746a99..7a30ca21 100644 --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -1,233 +1,239 @@ /* 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 "drkonqibackends.h" #include #include #include #include #include #include #include #include #include #include #include "drkonqi_debug.h" #include "crashedapplication.h" #include "debugger.h" #include "debuggermanager.h" #include "backtracegenerator.h" #include "drkonqi.h" +#ifdef Q_OS_MACOS +#include +#endif + AbstractDrKonqiBackend::~AbstractDrKonqiBackend() { } bool AbstractDrKonqiBackend::init() { m_crashedApplication = constructCrashedApplication(); m_debuggerManager = constructDebuggerManager(); return true; } KCrashBackend::KCrashBackend() : QObject(), AbstractDrKonqiBackend(), m_state(ProcessRunning) { } KCrashBackend::~KCrashBackend() { continueAttachedProcess(); } bool KCrashBackend::init() { AbstractDrKonqiBackend::init(); //check whether the attached process exists and whether we have permissions to inspect it if (crashedApplication()->pid() <= 0) { qCWarning(DRKONQI_LOG) << "Invalid pid specified"; return false; } #if !defined(Q_OS_WIN32) if (::kill(crashedApplication()->pid(), 0) < 0) { switch (errno) { case EPERM: qCWarning(DRKONQI_LOG) << "DrKonqi doesn't have permissions to inspect the specified process"; break; case ESRCH: qCWarning(DRKONQI_LOG) << "The specified process does not exist."; break; default: break; } return false; } //--keeprunning means: generate backtrace instantly and let the process continue execution if(DrKonqi::isKeepRunning()) { stopAttachedProcess(); debuggerManager()->backtraceGenerator()->start(); connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::continueAttachedProcess); } else { connect(debuggerManager(), &DebuggerManager::debuggerStarting, this, &KCrashBackend::onDebuggerStarting); connect(debuggerManager(), &DebuggerManager::debuggerFinished, this, &KCrashBackend::onDebuggerFinished); //stop the process to avoid high cpu usage by other threads (bug 175362). //if the process was started by kdeinit, we need to wait a bit for KCrash //to reach the alarm(0); call in kdeui/util/kcrash.cpp line 406 or else //if we stop it before this call, pending alarm signals will kill the //process when we try to continue it. QTimer::singleShot(2000, this, &KCrashBackend::stopAttachedProcess); } #endif //Handle drkonqi crashes s_pid = crashedApplication()->pid(); //copy pid for use by the crash handler, so that it is safer KCrash::setEmergencySaveFunction(emergencySaveFunction); return true; } CrashedApplication *KCrashBackend::constructCrashedApplication() { CrashedApplication *a = new CrashedApplication(this); a->m_datetime = QDateTime::currentDateTime(); a->m_name = DrKonqi::programName(); a->m_version = DrKonqi::appVersion(); a->m_reportAddress = BugReportAddress(DrKonqi::bugAddress()); a->m_pid = DrKonqi::pid(); a->m_signalNumber = DrKonqi::signal(); a->m_restarted = DrKonqi::isRestarted(); a->m_thread = DrKonqi::thread(); //try to determine the executable that crashed if ( QFileInfo(QStringLiteral("/proc/%1/exe").arg(a->m_pid)).exists() ) { //on linux, the fastest and most reliable way is to get the path from /proc qCDebug(DRKONQI_LOG) << "Using /proc to determine executable path"; a->m_executable.setFile(QFile::symLinkTarget(QStringLiteral("/proc/%1/exe").arg(a->m_pid))); if (DrKonqi::isKdeinit() || a->m_executable.fileName().startsWith(QLatin1String("python")) ) { a->m_fakeBaseName = DrKonqi::appName(); } } else { if ( DrKonqi::isKdeinit() ) { a->m_executable = QFileInfo(QStandardPaths::findExecutable(QStringLiteral("kdeinit5"))); a->m_fakeBaseName = DrKonqi::appName(); } else { QFileInfo execPath(DrKonqi::appName()); if ( execPath.isAbsolute() ) { a->m_executable = execPath; } else if ( !DrKonqi::appPath().isEmpty() ) { QDir execDir(DrKonqi::appPath()); a->m_executable = execDir.absoluteFilePath(execPath.fileName()); } else { a->m_executable = QFileInfo(QStandardPaths::findExecutable(execPath.fileName())); } } } qCDebug(DRKONQI_LOG) << "Executable is:" << a->m_executable.absoluteFilePath(); qCDebug(DRKONQI_LOG) << "Executable exists:" << a->m_executable.exists(); return a; } DebuggerManager *KCrashBackend::constructDebuggerManager() { 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")); #endif Debugger firstKnownGoodDebugger, preferredDebugger; foreach (const Debugger & debugger, internalDebuggers) { if (!firstKnownGoodDebugger.isValid() && debugger.isInstalled()) { firstKnownGoodDebugger = debugger; } if (debugger.codeName() == defaultDebuggerName) { preferredDebugger = debugger; } if (firstKnownGoodDebugger.isValid() && preferredDebugger.isValid()) { break; } } if (!preferredDebugger.isInstalled()) { if (firstKnownGoodDebugger.isValid()) { preferredDebugger = firstKnownGoodDebugger; } else { qCWarning(DRKONQI_LOG) << "Unable to find an internal debugger that can work with the KCrash backend"; } } return new DebuggerManager(preferredDebugger, Debugger::availableExternalDebuggers(QStringLiteral("KCrash")), this); } void KCrashBackend::stopAttachedProcess() { if (m_state == ProcessRunning) { qCDebug(DRKONQI_LOG) << "Sending SIGSTOP to process"; ::kill(crashedApplication()->pid(), SIGSTOP); m_state = ProcessStopped; } } void KCrashBackend::continueAttachedProcess() { if (m_state == ProcessStopped) { qCDebug(DRKONQI_LOG) << "Sending SIGCONT to process"; ::kill(crashedApplication()->pid(), SIGCONT); m_state = ProcessRunning; } } void KCrashBackend::onDebuggerStarting() { continueAttachedProcess(); m_state = DebuggerRunning; } void KCrashBackend::onDebuggerFinished() { m_state = ProcessRunning; stopAttachedProcess(); } //static qint64 KCrashBackend::s_pid = 0; //static void KCrashBackend::emergencySaveFunction(int signal) { // In case drkonqi itself crashes, we need to get rid of the process being debugged, // so we kill it, no matter what its state was. Q_UNUSED(signal); ::kill(s_pid, SIGKILL); } diff --git a/src/main.cpp b/src/main.cpp index 77197d41..7c87ec5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,177 +1,183 @@ /***************************************************************** * drkonqi - The KDE Crash Handler * * Copyright (C) 2000-2003 Hans Petter Bieker * * 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. *****************************************************************/ #include #include #include #include #include #include #include #if HAVE_X11 #include #endif +#ifdef Q_OS_MACOS +#include +#endif #include "drkonqi.h" #include "drkonqidialog.h" #include "statusnotifier.h" static const char version[] = PROJECT_VERSION; static const char description[] = I18N_NOOP("The KDE Crash Handler gives the user feedback " "if a program has crashed."); int main(int argc, char* argv[]) { #ifndef Q_OS_WIN //krazy:exclude=cpp // Drop privs. setgid(getgid()); if (setuid(getuid()) < 0 && geteuid() != getuid()) { exit(255); } #endif QApplication qa(argc, argv); qa.setAttribute(Qt::AA_UseHighDpiPixmaps, true); KLocalizedString::setApplicationDomain("drkonqi5"); QCoreApplication::setApplicationName(QStringLiteral("drkonqi")); QCoreApplication::setApplicationVersion(QString::fromLatin1(version)); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); // Prevent KApplication from setting the crash handler. We will set it later... setenv("KDE_DEBUG", "true", 1); // Session management is not needed, do not even connect in order to survive longer than ksmserver. unsetenv("SESSION_MANAGER"); KAboutData aboutData(QStringLiteral("drkonqi"), i18n("The KDE Crash Handler"), QString::fromLatin1(version), i18n(description), KAboutLicense::GPL, i18n("(C) 2000-2018, The DrKonqi Authors")); aboutData.addAuthor(i18nc("@info:credit","Hans Petter Bieker"), QString(), QStringLiteral("bieker@kde.org")); aboutData.addAuthor(i18nc("@info:credit","Dario Andres Rodriguez"), QString(), QStringLiteral("andresbajotierra@gmail.com")); aboutData.addAuthor(i18nc("@info:credit","George Kiagiadakis"), QString(), 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; aboutData.setupCommandLine(&parser); QCommandLineOption signalOption(QStringLiteral("signal"), i18nc("@info:shell","The signal that was caught"), QStringLiteral("number")); QCommandLineOption appNameOption(QStringLiteral("appname"), i18nc("@info:shell"," of the program"), QStringLiteral("name")); QCommandLineOption appPathOption(QStringLiteral("apppath"), i18nc("@info:shell"," to the executable"), QStringLiteral("path")); QCommandLineOption appVersionOption(QStringLiteral("appversion"), i18nc("@info:shell","The of the program"), QStringLiteral("version")); QCommandLineOption bugAddressOption(QStringLiteral("bugaddress"), i18nc("@info:shell","The bug
to use"), QStringLiteral("address")); QCommandLineOption programNameOption(QStringLiteral("programname"), i18nc("@info:shell","Translated of the program"), QStringLiteral("name")); QCommandLineOption pidOption(QStringLiteral("pid"), i18nc("@info:shell","The of the program"), QStringLiteral("pid")); QCommandLineOption startupIdOption(QStringLiteral("startupid"), i18nc("@info:shell","Startup of the program"), QStringLiteral("id")); QCommandLineOption kdeinitOption(QStringLiteral("kdeinit"), i18nc("@info:shell","The program was started by kdeinit")); QCommandLineOption saferOption(QStringLiteral("safer"), i18nc("@info:shell","Disable arbitrary disk access")); QCommandLineOption restartedOption(QStringLiteral("restarted"), i18nc("@info:shell","The program has already been restarted")); QCommandLineOption keepRunningOption(QStringLiteral("keeprunning"), i18nc("@info:shell","Keep the program running and generate " "the backtrace at startup")); QCommandLineOption threadOption(QStringLiteral("thread"), i18nc("@info:shell","The of the failing thread"), QStringLiteral("threadid")); QCommandLineOption dialogOption(QStringLiteral("dialog"), i18nc("@info:shell","Do not show a notification but launch the debug dialog directly")); parser.addOption(signalOption); parser.addOption(appNameOption); parser.addOption(appPathOption); parser.addOption(appVersionOption); parser.addOption(bugAddressOption); parser.addOption(programNameOption); parser.addOption(pidOption); parser.addOption(startupIdOption); parser.addOption(kdeinitOption); parser.addOption(saferOption); parser.addOption(restartedOption); parser.addOption(keepRunningOption); parser.addOption(threadOption); parser.addOption(dialogOption); parser.process(qa); aboutData.processCommandLine(&parser); DrKonqi::setSignal(parser.value(signalOption).toInt()); DrKonqi::setAppName(parser.value(appNameOption)); DrKonqi::setAppPath(parser.value(appPathOption)); DrKonqi::setAppVersion(parser.value(appVersionOption)); DrKonqi::setBugAddress(parser.value(bugAddressOption)); DrKonqi::setProgramName(parser.value(programNameOption)); DrKonqi::setPid(parser.value(pidOption).toInt()); DrKonqi::setKdeinit(parser.isSet(kdeinitOption)); DrKonqi::setSafer(parser.isSet(saferOption)); DrKonqi::setRestarted(parser.isSet(restartedOption)); DrKonqi::setKeepRunning(parser.isSet(keepRunningOption)); DrKonqi::setThread(parser.value(threadOption).toInt()); auto forceDialog = parser.isSet(dialogOption); #if HAVE_X11 const QString startupId = parser.value(startupIdOption); if (!startupId.isEmpty()) { QX11Info::setNextStartupId(startupId.toUtf8()); } #endif if (!DrKonqi::init()) { return 1; } qa.setQuitOnLastWindowClosed(false); auto openDrKonqiDialog = [&qa]{ 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); // if no notification service is running (eg. shell crashed, or other desktop environment) // and we didn't auto-restart the app, open DrKonqi dialog instead of showing an SNI // and emitting a desktop notification if (forceDialog || (!restarted && !StatusNotifier::notificationServiceRegistered())) { openDrKonqiDialog(); } else { StatusNotifier *statusNotifier = new StatusNotifier(); if (!restarted) { statusNotifier->notify(); } QObject::connect(statusNotifier, &StatusNotifier::expired, &qa, &QApplication::quit); QObject::connect(statusNotifier, &StatusNotifier::activated, openDrKonqiDialog); } int ret = qa.exec(); DrKonqi::cleanup(); return ret; } diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt index d6c9bace..9910585f 100644 --- a/src/parser/CMakeLists.txt +++ b/src/parser/CMakeLists.txt @@ -1,14 +1,15 @@ set(BACKTRACEPARSER_SRCS backtraceparser.cpp 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) add_library(drkonqi_backtrace_parser STATIC ${BACKTRACEPARSER_SRCS}) target_link_libraries(drkonqi_backtrace_parser PUBLIC Qt5::Core ) diff --git a/src/parser/backtraceparser.cpp b/src/parser/backtraceparser.cpp index 94883ce1..9abd03c6 100644 --- a/src/parser/backtraceparser.cpp +++ b/src/parser/backtraceparser.cpp @@ -1,394 +1,401 @@ /* Copyright (C) 2009-2010 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "backtraceparser_p.h" #include "backtraceparsergdb.h" #include "backtraceparserkdbgwin.h" +#include "backtraceparserlldb.h" #include "backtraceparsernull.h" #include "drkonqi_parser_debug.h" #include #include #include //factory BacktraceParser *BacktraceParser::newParser(const QString & debuggerName, QObject *parent) { if (debuggerName == QLatin1String("gdb")) { 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); } } BacktraceParser::BacktraceParser(QObject *parent) : QObject(parent), d_ptr(nullptr) {} BacktraceParser::~BacktraceParser() { delete d_ptr; } void BacktraceParser::connectToGenerator(QObject *generator) { connect(generator, SIGNAL(starting()), this, SLOT(resetState())); connect(generator, SIGNAL(newLine(QString)), this, SLOT(newLine(QString))); } QString BacktraceParser::parsedBacktrace() const { Q_D(const BacktraceParser); QString result; if (d) { for (QList::const_iterator i = d->m_linesList.constBegin(), total = d->m_linesList.constEnd(); i != total; ++i) { result += i->toString(); } } return result; } QList BacktraceParser::parsedBacktraceLines() const { Q_D(const BacktraceParser); return d ? d->m_linesList : QList(); } QString BacktraceParser::simplifiedBacktrace() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, so we have no backtrace. return d ? d->m_simplifiedBacktrace : QString(); } BacktraceParser::Usefulness BacktraceParser::backtraceUsefulness() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, //so we can say that the (inexistent) backtrace is Useless. return d ? d->m_usefulness : Useless; } QStringList BacktraceParser::firstValidFunctions() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, so we have no functions to return. return d ? d->m_firstUsefulFunctions : QStringList(); } QSet BacktraceParser::librariesWithMissingDebugSymbols() const { Q_D(const BacktraceParser); //if there is no cached usefulness, the data calculation function has not run yet. if (d && d->m_usefulness == InvalidUsefulness) { const_cast(this)->calculateRatingData(); } //if there is no d, the debugger has not run yet, so we have no libraries. return d ? d->m_librariesWithMissingDebugSymbols : QSet(); } void BacktraceParser::resetState() { //reset the state of the parser by getting a new instance of Private delete d_ptr; d_ptr = constructPrivate(); } BacktraceParserPrivate *BacktraceParser::constructPrivate() const { return new BacktraceParserPrivate; } /* This function returns true if the given stack frame line is the base of the backtrace and thus the parser should not rate any frames below that one. */ static bool lineIsStackBase(const BacktraceLine & line) { //optimization. if there is no function name, do not bother to check it if ( line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction ) return false; //this is the base frame for all threads except the main thread //FIXME that probably works only on linux if ( line.functionName() == QLatin1String("start_thread") ) return true; QRegExp regExp; regExp.setPattern(QStringLiteral("(kde)?main")); //main() or kdemain() is the base for the main thread if ( regExp.exactMatch(line.functionName()) ) return true; //HACK for better rating. we ignore all stack frames below any function that matches //the following regular expression. The functions that match this expression are usually //"QApplicationPrivate::notify_helper", "QApplication::notify" and similar, which //are used to send any kind of event to the Qt application. All stack frames below this, //with or without debug symbols, are useless to KDE developers, so we ignore them. regExp.setPattern(QStringLiteral("(Q|K)(Core)?Application(Private)?::notify.*")); if ( regExp.exactMatch(line.functionName()) ) return true; //attempt to recognize crashes that happen after main has returned (bug 200993) if ( line.functionName() == QLatin1String("~KCleanUpGlobalStatic") || line.functionName() == QLatin1String("~QGlobalStatic") || line.functionName() == QLatin1String("exit") || line.functionName() == QLatin1String("*__GI_exit") ) return true; return false; } /* This function returns true if the given stack frame line is the top of the bactrace and thus the parser should not rate any frames above that one. This is used to avoid rating the stack frames of abort(), assert(), Q_ASSERT() and qCCritical(DRKONQI_PARSER_LOG) */ static bool lineIsStackTop(const BacktraceLine & line) { //optimization. if there is no function name, do not bother to check it if ( line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction ) return false; if ( line.functionName().startsWith(QLatin1String("qt_assert")) //qt_assert and qt_assert_x || line.functionName() == QLatin1String("qFatal") || line.functionName() == QLatin1String("abort") || line.functionName() == QLatin1String("*__GI_abort") || line.functionName() == QLatin1String("*__GI___assert_fail") ) return true; return false; } /* This function returns true if the given stack frame line should be ignored from rating for some reason. Currently it ignores all libc/libstdc++/libpthread functions. */ static bool lineShouldBeIgnored(const BacktraceLine & line) { if ( line.libraryName().contains(QStringLiteral("libc.so")) || line.libraryName().contains(QStringLiteral("libstdc++.so")) || 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")) || line.functionName() == QLatin1String("WinMain") ) return true; return false; } static bool isFunctionUseful(const BacktraceLine & line) { //We need the function name if ( line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction ) { return false; } //Misc ignores if ( line.functionName() == QLatin1String("__kernel_vsyscall") || line.functionName() == QLatin1String("raise") || line.functionName() == QLatin1String("abort") || line.functionName() == QLatin1String("__libc_message") || line.functionName() == QLatin1String("thr_kill") /* *BSD */) { return false; } //Ignore core Qt functions //(QObject can be useful in some cases) if ( line.functionName().startsWith(QLatin1String("QBasicAtomicInt::")) || line.functionName().startsWith(QLatin1String("QBasicAtomicPointer::")) || line.functionName().startsWith(QLatin1String("QAtomicInt::")) || line.functionName().startsWith(QLatin1String("QAtomicPointer::")) || line.functionName().startsWith(QLatin1String("QMetaObject::")) || line.functionName().startsWith(QLatin1String("QPointer::")) || line.functionName().startsWith(QLatin1String("QWeakPointer::")) || line.functionName().startsWith(QLatin1String("QSharedPointer::")) || line.functionName().startsWith(QLatin1String("QScopedPointer::")) || line.functionName().startsWith(QLatin1String("QMetaCallEvent::")) ) { return false; } //Ignore core Qt containers misc functions if ( line.functionName().endsWith(QLatin1String("detach")) || line.functionName().endsWith(QLatin1String("detach_helper")) || line.functionName().endsWith(QLatin1String("node_create")) || line.functionName().endsWith(QLatin1String("deref")) || line.functionName().endsWith(QLatin1String("ref")) || line.functionName().endsWith(QLatin1String("node_copy")) || line.functionName().endsWith(QLatin1String("d_func")) ) { return false; } //Misc Qt stuff if ( line.functionName() == QLatin1String("qt_message_output") || line.functionName() == QLatin1String("qt_message") || line.functionName() == QLatin1String("qFatal") || line.functionName().startsWith(QLatin1String("qGetPtrHelper")) || line.functionName().startsWith(QLatin1String("qt_meta_")) ) { return false; } return true; } static bool isFunctionUsefulForSearch(const BacktraceLine & line) { //Ignore Qt containers (and iterators Q*Iterator) if ( line.functionName().startsWith(QLatin1String("QList")) || line.functionName().startsWith(QLatin1String("QLinkedList")) || line.functionName().startsWith(QLatin1String("QVector")) || line.functionName().startsWith(QLatin1String("QStack")) || line.functionName().startsWith(QLatin1String("QQueue")) || line.functionName().startsWith(QLatin1String("QSet")) || line.functionName().startsWith(QLatin1String("QMap")) || line.functionName().startsWith(QLatin1String("QMultiMap")) || line.functionName().startsWith(QLatin1String("QMapData")) || line.functionName().startsWith(QLatin1String("QHash")) || line.functionName().startsWith(QLatin1String("QMultiHash")) || line.functionName().startsWith(QLatin1String("QHashData")) ) { return false; } return true; } void BacktraceParser::calculateRatingData() { Q_D(BacktraceParser); uint rating = 0, bestPossibleRating = 0, counter = 0; bool haveSeenStackBase = false; QListIterator i(d->m_linesToRate); i.toBack(); //start from the end of the list while( i.hasPrevious() ) { const BacktraceLine & line = i.previous(); if ( !i.hasPrevious() && line.rating() == BacktraceLine::MissingEverything ) { //Under some circumstances, the very first stack frame is invalid (ex, calling a function //at an invalid address could result in a stack frame like "0x00000000 in ?? ()"), //which however does not necessarily mean that the backtrace has a missing symbol on //the first line. Here we make sure to ignore this line from rating. (bug 190882) break; //there are no more items anyway, just break the loop } if ( lineIsStackBase(line) ) { rating = bestPossibleRating = counter = 0; //restart rating ignoring any previous frames haveSeenStackBase = true; } else if ( lineIsStackTop(line) ) { break; //we have reached the top, no need to inspect any more frames } if ( lineShouldBeIgnored(line) ) { continue; } if ( line.rating() == BacktraceLine::MissingFunction || line.rating() == BacktraceLine::MissingSourceFile) { d->m_librariesWithMissingDebugSymbols.insert(line.libraryName().trimmed()); } uint multiplier = ++counter; //give weight to the first lines rating += static_cast(line.rating()) * multiplier; bestPossibleRating += static_cast(BacktraceLine::BestRating) * multiplier; qCDebug(DRKONQI_PARSER_LOG) << line.rating() << line.toString(); } //Generate a simplified backtrace //- Starts from the first useful function //- Max of 5 lines //- Replaces garbage with [...] //At the same time, grab the first three useful functions for search queries i.toFront(); //Reuse the list iterator int functionIndex = 0; int usefulFunctionsCount = 0; bool firstUsefulFound = false; while( i.hasNext() && functionIndex < 5 ) { const BacktraceLine & line = i.next(); if ( !lineShouldBeIgnored(line) && isFunctionUseful(line) ) { //Line is not garbage to use if (!firstUsefulFound) { firstUsefulFound = true; } //Save simplified backtrace line d->m_simplifiedBacktrace += line.toString(); //Fetch three useful functions (only functionName) for search queries if (usefulFunctionsCount < 3 && isFunctionUsefulForSearch(line) && !d->m_firstUsefulFunctions.contains(line.functionName())) { d->m_firstUsefulFunctions.append(line.functionName()); usefulFunctionsCount++; } functionIndex++; } else if (firstUsefulFound) { //Add "[...]" if there are invalid functions in the middle if (!d->m_simplifiedBacktrace.endsWith(QLatin1String("[...]\n"))) { d->m_simplifiedBacktrace += QLatin1String("[...]\n"); } } } //calculate rating d->m_usefulness = Useless; if (rating >= (bestPossibleRating*0.90)) { d->m_usefulness = ReallyUseful; } else if (rating >= (bestPossibleRating*0.70)) { d->m_usefulness = MayBeUseful; } else if (rating >= (bestPossibleRating*0.40)) { d->m_usefulness = ProbablyUseless; } //if there is no stack base, the executable is probably stripped, //so we need to be more strict with rating if ( !haveSeenStackBase ) { //less than 4 stack frames is useless if ( counter < 4 ) { d->m_usefulness = Useless; //more than 4 stack frames might have some value, so let's not be so strict, just lower the rating } else if ( d->m_usefulness > Useless ) { d->m_usefulness = (Usefulness) (d->m_usefulness - 1); } } qCDebug(DRKONQI_PARSER_LOG) << "Rating:" << rating << "out of" << bestPossibleRating << "Usefulness:" << staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Usefulness")).valueToKey(d->m_usefulness); qCDebug(DRKONQI_PARSER_LOG) << "90%:" << (bestPossibleRating*0.90) << "70%:" << (bestPossibleRating*0.70) << "40%:" << (bestPossibleRating*0.40); qCDebug(DRKONQI_PARSER_LOG) << "Have seen stack base:" << haveSeenStackBase << "Lines counted:" << counter; } diff --git a/src/parser/backtraceparserlldb.cpp b/src/parser/backtraceparserlldb.cpp new file mode 100644 index 00000000..2c72ca56 --- /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/parser/backtraceparserlldb.h b/src/parser/backtraceparserlldb.h new file mode 100644 index 00000000..ab07ec6e --- /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/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index cb9d3919..83c0da92 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -1,48 +1,50 @@ add_subdirectory(crashtest) add_subdirectory(backtraceparsertest) if(KF5XmlRpcClient_FOUND) 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.")