Commit 13121f4a authored by René J.V. Bertin's avatar René J.V. Bertin

lldb and Mac support

Adds support for generating backtraces with and attaching LLVM's lldb
debugger. On Mac the application brings itself to the foreground.

Differential Revision: https://phabricator.kde.org/D4929
parent 42109536
......@@ -23,7 +23,9 @@ 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.")
......
......@@ -111,6 +111,11 @@ if (${Qt5X11Extras_FOUND})
Qt5::X11Extras
)
endif()
if (APPLE)
target_link_libraries(drkonqi
KF5::WindowSystem
)
endif()
if (WIN32)
target_link_libraries(drkonqi kdewin)
......
......@@ -56,9 +56,12 @@ BacktraceGenerator::~BacktraceGenerator()
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 @@ bool BacktraceGenerator::start()
*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<void (KProcess::*)(int, QProcess::ExitStatus)>(&KProcess::finished), this, &BacktraceGenerator::slotProcessExited);
......@@ -117,6 +127,11 @@ bool BacktraceGenerator::start()
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 @@ void BacktraceGenerator::slotReadInput()
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;
}
}
}
......
......@@ -205,6 +205,9 @@ void BacktraceWidget::loadData()
// 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());
......
[General]
Name=lldb
TryExec=lldb
Backends=KCrash
[KCrash]
Exec=AppleTerminal lldb -p %pid
Terminal=true
[General]
Name=lldb
TryExec=lldb
Backends=KCrash
[KCrash]
Exec=konsole --nofork -e lldb -p %pid
Terminal=true
[General]
Name=lldb
TryExec=lldb
Backends=KCrash
[KCrash]
Exec=lldb -p %pid
ExecInputFile=%tempfile
BatchCommands=set term-width 200\nthread info\nbt all\ndetach
......@@ -105,6 +105,15 @@ bool Debugger::runInTerminal() const
}
}
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)
{
......
......@@ -70,6 +70,8 @@ public:
/** 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,
......
......@@ -36,6 +36,10 @@
#include "backtracegenerator.h"
#include "drkonqi.h"
#ifdef Q_OS_MACOS
#include <AvailabilityMacros.h>
#endif
AbstractDrKonqiBackend::~AbstractDrKonqiBackend()
{
}
......@@ -158,7 +162,9 @@ DebuggerManager *KCrashBackend::constructDebuggerManager()
{
QList<Debugger> 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"));
......
......@@ -38,6 +38,9 @@
#if HAVE_X11
#include <QX11Info>
#endif
#ifdef Q_OS_MACOS
#include <KWindowSystem>
#endif
#include "drkonqi.h"
#include "drkonqidialog.h"
......@@ -81,7 +84,7 @@ int main(int argc, char* argv[])
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 @@ int main(int argc, char* argv[])
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);
......
......@@ -3,6 +3,7 @@ set(BACKTRACEPARSER_SRCS
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)
......
......@@ -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 <QRegExp>
......@@ -31,6 +32,8 @@ BacktraceParser *BacktraceParser::newParser(const QString & debuggerName, QObjec
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 @@ static bool lineShouldBeIgnored(const BacktraceLine & line)
|| 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"))
......
/*
Copyright (C) 2014 René J.V. Bertin <rjvbertin@gmail.com>
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
/*
Copyright (C) 2014-2016 René J.V. Bertin <rjvbertin@gmail.com>
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
......@@ -4,45 +4,47 @@ 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.")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment