diff --git a/plugins/debuggercommon/CMakeLists.txt b/plugins/debuggercommon/CMakeLists.txt index e0e47f68c4..e56fa77d0a 100644 --- a/plugins/debuggercommon/CMakeLists.txt +++ b/plugins/debuggercommon/CMakeLists.txt @@ -1,94 +1,93 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevdebuggercommon\") set(debuggercommon_SRCS mi/mi.cpp mi/milexer.cpp mi/miparser.cpp mi/micommand.cpp mi/micommandqueue.cpp dbgglobal.cpp dialogs/selectcoredialog.cpp # debug session & debugger midebugger.cpp midebugsession.cpp midebuggerplugin.cpp midebugjobs.cpp # controllers mibreakpointcontroller.cpp miframestackmodel.cpp mivariablecontroller.cpp mivariable.cpp stringhelpers.cpp stty.cpp # tool views widgets/debuggerconsoleview.cpp widgets/disassemblewidget.cpp # register registers/registersview.cpp registers/registercontroller.cpp registers/registersmanager.cpp registers/registercontroller_x86.cpp registers/registercontroller_arm.cpp registers/modelsmanager.cpp registers/converters.cpp ) if(KF5SysGuard_FOUND) list(APPEND debuggercommon_SRCS dialogs/processselection.cpp ) endif() declare_qt_logging_category(debuggercommon_SRCS TYPE PLUGIN HEADER debuglog.h IDENTIFIER DEBUGGERCOMMON - CATEGORY_BASENAME "common" - DESCRIPTION "debuggers common" + CATEGORY_BASENAME "debuggercommon" ) ki18n_wrap_ui(debuggercommon_SRCS dialogs/selectcoredialog.ui widgets/debuggerconsoleview.ui widgets/selectaddressdialog.ui registers/registersview.ui ) # Use old behavior (ignore the visibility properties for static libraries, object # libraries, and executables without exports) on target kdevdebuggercommon (so the # default public visibility is used). # kdevdebuggercommon is used by target test_gdb which is added by ecm_add_test, # which doesn't set CMP0063 so old behavior is used. # If kdevdebuggercommon honors visibility properties (set to hidden), it will cause # linker warnings about direct access to global weak symbol when link against test_gdb if(NOT CMAKE_VERSION VERSION_LESS "3.3") cmake_policy(SET CMP0063 OLD) endif() add_library(kdevdebuggercommon STATIC ${debuggercommon_SRCS}) target_link_libraries(kdevdebuggercommon PUBLIC KDev::Debugger KDev::OutputView KDev::Sublime PRIVATE Qt5::Core Qt5::Gui Qt5::Widgets KDev::Util KDev::Language KDev::IExecute ) target_include_directories(kdevdebuggercommon PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ) if(KF5SysGuard_FOUND) target_link_libraries(kdevdebuggercommon PUBLIC KF5::ProcessUi ) endif() kde_target_enable_exceptions(kdevdebuggercommon PUBLIC) if(BUILD_TESTING) add_subdirectory(tests) endif() diff --git a/plugins/debuggercommon/midebugger.cpp b/plugins/debuggercommon/midebugger.cpp index c43737f716..1009fb8527 100644 --- a/plugins/debuggercommon/midebugger.cpp +++ b/plugins/debuggercommon/midebugger.cpp @@ -1,370 +1,370 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * 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 "midebugger.h" #include "debuglog.h" #include "mi/micommand.h" #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif // #define DEBUG_NO_TRY //to get a backtrace to where the exception was thrown using namespace KDevMI; using namespace KDevMI::MI; MIDebugger::MIDebugger(QObject* parent) : QObject(parent) { m_process = new KProcess(this); m_process->setOutputChannelMode(KProcess::SeparateChannels); connect(m_process, &KProcess::readyReadStandardOutput, this, &MIDebugger::readyReadStandardOutput); connect(m_process, &KProcess::readyReadStandardError, this, &MIDebugger::readyReadStandardError); connect(m_process, QOverload::of(&QProcess::finished), this, &MIDebugger::processFinished); connect(m_process, &QProcess::errorOccurred, this, &MIDebugger::processErrored); } MIDebugger::~MIDebugger() { // prevent Qt warning: QProcess: Destroyed while process is still running. if (m_process && m_process->state() == QProcess::Running) { disconnect(m_process, &QProcess::errorOccurred, this, &MIDebugger::processErrored); m_process->kill(); m_process->waitForFinished(10); } } void MIDebugger::execute(MICommand* command) { m_currentCmd = command; QString commandText = m_currentCmd->cmdToSend(); qCDebug(DEBUGGERCOMMON) << "SEND:" << commandText.trimmed(); QByteArray commandUtf8 = commandText.toUtf8(); m_process->write(commandUtf8); command->markAsSubmitted(); QString prettyCmd = m_currentCmd->cmdToSend(); prettyCmd.remove(QRegExp(QStringLiteral("set prompt \032.\n"))); prettyCmd = QLatin1String("(gdb) ") + prettyCmd; if (m_currentCmd->isUserCommand()) emit userCommandOutput(prettyCmd); else emit internalCommandOutput(prettyCmd); } bool MIDebugger::isReady() const { return m_currentCmd == nullptr; } void MIDebugger::interrupt() { #ifndef Q_OS_WIN int pid = m_process->pid(); if (pid != 0) { ::kill(pid, SIGINT); } #else SetConsoleCtrlHandler(nullptr, true); GenerateConsoleCtrlEvent(0, 0); SetConsoleCtrlHandler(nullptr, false); #endif } MICommand* MIDebugger::currentCommand() const { return m_currentCmd; } void MIDebugger::kill() { m_process->kill(); } void MIDebugger::readyReadStandardOutput() { m_process->setReadChannel(QProcess::StandardOutput); m_buffer += m_process->readAll(); for (;;) { /* In MI mode, all messages are exactly one line. See if we have any complete lines in the buffer. */ int i = m_buffer.indexOf('\n'); if (i == -1) break; QByteArray reply(m_buffer.left(i)); m_buffer.remove(0, i+1); processLine(reply); } } void MIDebugger::readyReadStandardError() { m_process->setReadChannel(QProcess::StandardError); emit debuggerInternalOutput(QString::fromUtf8(m_process->readAll())); } void MIDebugger::processLine(const QByteArray& line) { if (line != "(gdb) ") { qCDebug(DEBUGGERCOMMON) << "Debugger output (pid =" << m_process->pid() << "): " << line; } FileSymbol file; file.contents = line; std::unique_ptr r(m_parser.parse(&file)); if (!r) { // simply ignore the invalid MI message because both gdb and lldb // sometimes produces invalid messages that can be safely ignored. qCDebug(DEBUGGERCOMMON) << "Invalid MI message:" << line; // We don't consider the current command done. // So, if a command results in unparseable reply, // we'll just wait for the "right" reply, which might // never come. However, marking the command as // done in this case is even more risky. // It's probably possible to get here if we're debugging // natively without PTY, though this is uncommon case. return; } #ifndef DEBUG_NO_TRY try { #endif switch(r->kind) { case MI::Record::Result: { auto& result = static_cast(*r); // it's still possible for the user to issue a MI command, // emit correct signal if (m_currentCmd && m_currentCmd->isUserCommand()) { emit userCommandOutput(QString::fromUtf8(line) + QLatin1Char('\n')); } else { emit internalCommandOutput(QString::fromUtf8(line) + QLatin1Char('\n')); } // protect against wild replies that sometimes returned from gdb without a pending command if (!m_currentCmd) { qCWarning(DEBUGGERCOMMON) << "Received a result without a pending command"; throw std::runtime_error("Received a result without a pending command"); } else if (m_currentCmd->token() != result.token) { std::stringstream ss; ss << "Received a result with token not matching pending command. " << "Pending: " << m_currentCmd->token() << "Received: " << result.token; qCWarning(DEBUGGERCOMMON) << ss.str().c_str(); throw std::runtime_error(ss.str()); } // GDB doc: "running" and "exit" are status codes equivalent to "done" if (result.reason == QLatin1String("done") || result.reason == QLatin1String("running") || result.reason == QLatin1String("exit")) { qCDebug(DEBUGGERCOMMON) << "Result token is" << result.token; m_currentCmd->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command successful, times " << m_currentCmd->totalProcessingTime() << m_currentCmd->queueTime() << m_currentCmd->gdbProcessingTime(); m_currentCmd->invokeHandler(result); } else if (result.reason == QLatin1String("error")) { qCDebug(DEBUGGERCOMMON) << "Handling error"; m_currentCmd->markAsCompleted(); qCDebug(DEBUGGERCOMMON) << "Command error, times" << m_currentCmd->totalProcessingTime() << m_currentCmd->queueTime() << m_currentCmd->gdbProcessingTime(); // Some commands want to handle errors themself. if (m_currentCmd->handlesError() && m_currentCmd->invokeHandler(result)) { qCDebug(DEBUGGERCOMMON) << "Invoked custom handler\n"; // Done, nothing more needed } else emit error(result); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled result code: " << result.reason; } delete m_currentCmd; m_currentCmd = nullptr; emit ready(); break; } case MI::Record::Async: { auto& async = static_cast(*r); switch (async.subkind) { case MI::AsyncRecord::Exec: { // Prefix '*'; asynchronous state changes of the target if (async.reason == QLatin1String("stopped")) { emit programStopped(async); } else if (async.reason == QLatin1String("running")) { emit programRunning(); } else { qCDebug(DEBUGGERCOMMON) << "Unhandled exec notification: " << async.reason; } break; } case MI::AsyncRecord::Notify: { // Prefix '='; supplementary information that we should handle (new breakpoint etc.) emit notification(async); break; } case MI::AsyncRecord::Status: { // Prefix '+'; GDB documentation: // On-going status information about progress of a slow operation; may be ignored break; } } break; } case MI::Record::Stream: { auto& s = static_cast(*r); if (s.subkind == MI::StreamRecord::Target) { emit applicationOutput(s.message); } else if (s.subkind == MI::StreamRecord::Console) { if (m_currentCmd && m_currentCmd->isUserCommand()) emit userCommandOutput(s.message); else emit internalCommandOutput(s.message); if (m_currentCmd) m_currentCmd->newOutput(s.message); } else { emit debuggerInternalOutput(s.message); } emit streamRecord(s); break; } case MI::Record::Prompt: break; } #ifndef DEBUG_NO_TRY } catch(const std::exception& e) { KMessageBox::detailedSorry( qApp->activeWindow(), i18nc("Internal debugger error", "

The debugger component encountered an internal error while " "processing the reply from the debugger. Please submit a bug report. " "The debug session will now end to prevent potential crash"), i18n("The exception is: %1\n" "The MI response is: %2", QString::fromUtf8(e.what()), QString::fromLatin1(line)), i18n("Internal debugger error")); emit exited(true, QString::fromUtf8(e.what())); } #endif } void MIDebugger::processFinished(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(DEBUGGERCOMMON) << "Debugger FINISHED\n"; bool abnormal = exitCode != 0 || exitStatus != QProcess::NormalExit; emit userCommandOutput(QStringLiteral("Process exited\n")); emit exited(abnormal, i18n("Process exited")); } void MIDebugger::processErrored(QProcess::ProcessError error) { - qCDebug(DEBUGGERCOMMON) << "Debugger ERRORED" << error; + qCWarning(DEBUGGERCOMMON) << "Debugger ERRORED" << error << m_process->errorString(); if(error == QProcess::FailedToStart) { KMessageBox::information( qApp->activeWindow(), i18n("Could not start debugger." "

Could not run '%1'. " "Make sure that the path name is specified correctly.", m_debuggerExecutable), i18n("Could not start debugger")); emit userCommandOutput(QStringLiteral("Process failed to start\n")); emit exited(true, i18n("Process failed to start")); } else if (error == QProcess::Crashed) { KMessageBox::error( qApp->activeWindow(), i18n("Debugger crashed." "

The debugger process '%1' crashed.
" "Because of that the debug session has to be ended.
" "Try to reproduce the crash without KDevelop and report a bug.
", m_debuggerExecutable), i18n("Debugger crashed")); emit userCommandOutput(QStringLiteral("Process crashed\n")); emit exited(true, i18n("Process crashed")); } } diff --git a/plugins/gdb/gdb.cpp b/plugins/gdb/gdb.cpp index 2c5e15c4af..f838f6eaaa 100644 --- a/plugins/gdb/gdb.cpp +++ b/plugins/gdb/gdb.cpp @@ -1,98 +1,100 @@ /* * Low level GDB interface. * * Copyright 1999 John Birch * Copyright 2007 Vladimir Prus * Copyright 2016 Aetf * * 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 "gdb.h" #include "dbgglobal.h" #include "debuglog.h" #include #include #include #include #include #include #include using namespace KDevMI::GDB; using namespace KDevMI::MI; GdbDebugger::GdbDebugger(QObject* parent) : MIDebugger(parent) { } GdbDebugger::~GdbDebugger() { } bool GdbDebugger::start(KConfigGroup& config, const QStringList& extraArguments) { // FIXME: verify that default value leads to something sensible QUrl gdbUrl = config.readEntry(Config::GdbPathEntry, QUrl()); if (gdbUrl.isEmpty()) { m_debuggerExecutable = QStringLiteral("gdb"); } else { // FIXME: verify its' a local path. m_debuggerExecutable = gdbUrl.url(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); } QStringList arguments = extraArguments; arguments << QStringLiteral("--interpreter=mi2") << QStringLiteral("-quiet"); + QString fullCommand; + QUrl shell = config.readEntry(Config::DebuggerShellEntry, QUrl()); if(!shell.isEmpty()) { qCDebug(DEBUGGERGDB) << "have shell" << shell; QString shell_without_args = shell.toLocalFile().split(QLatin1Char(' ')).first(); QFileInfo info(shell_without_args); /*if( info.isRelative() ) { shell_without_args = build_dir + "/" + shell_without_args; info.setFile( shell_without_args ); }*/ if(!info.exists()) { KMessageBox::information( qApp->activeWindow(), i18n("Could not locate the debugging shell '%1'.", shell_without_args ), i18n("Debugging Shell Not Found") ); return false; } arguments.insert(0, m_debuggerExecutable); arguments.insert(0, shell.toLocalFile()); m_process->setShellCommand(KShell::joinArgs(arguments)); } else { m_process->setProgram(m_debuggerExecutable, arguments); + fullCommand = m_debuggerExecutable + QLatin1Char(' '); } + fullCommand += arguments.join(QLatin1Char(' ')); m_process->start(); - qCDebug(DEBUGGERGDB) << "Starting GDB with command" << shell.toLocalFile() + QLatin1Char(' ') + m_debuggerExecutable - + QLatin1Char(' ') + arguments.join(QLatin1Char(' ')); + qCDebug(DEBUGGERGDB) << "Starting GDB with command" << fullCommand; qCDebug(DEBUGGERGDB) << "GDB process pid:" << m_process->pid(); - emit userCommandOutput(shell.toLocalFile() + QLatin1Char(' ') + m_debuggerExecutable - + QLatin1Char(' ') + arguments.join(QLatin1Char(' ')) + QLatin1Char('\n')); + emit userCommandOutput(fullCommand + QLatin1Char('\n')); return true; }