diff --git a/src/data/debuggers/external/cdbrc b/src/data/debuggers/external/cdbrc new file mode 100644 --- /dev/null +++ b/src/data/debuggers/external/cdbrc @@ -0,0 +1,28 @@ +[General] +Name=cdb +Name[ca]=cdb +Name[ca@valencia]=cdb +Name[de]=cdb +Name[es]=cdb +Name[fi]=cdb +Name[fr]=cdb +Name[gl]=cdb +Name[id]=cdb +Name[nl]=cdb +Name[nn]=cdb +Name[pl]=cdb +Name[pt]=cdb +Name[pt_BR]=cdb +Name[ru]=cdb +Name[sk]=cdb +Name[sv]=cdb +Name[uk]=cdb +Name[x-test]=xxcdbxx +Name[zh_CN]=cdb +Name[zh_TW]=cdb +TryExec=cdb +Backends=KCrash + +[KCrash] +Exec=konsole --nofork -e cdb.exe -p %pid -lines -c "~*kv; q" +Terminal=true diff --git a/src/data/debuggers/internal/cdbrc b/src/data/debuggers/internal/cdbrc new file mode 100644 --- /dev/null +++ b/src/data/debuggers/internal/cdbrc @@ -0,0 +1,29 @@ +[General] +Name=cdb +Name[ca]=cdb +Name[ca@valencia]=cdb +Name[de]=cdb +Name[es]=cdb +Name[fi]=cdb +Name[fr]=cdb +Name[gl]=cdb +Name[id]=cdb +Name[nl]=cdb +Name[nn]=cdb +Name[pl]=cdb +Name[pt]=cdb +Name[pt_BR]=cdb +Name[ru]=cdb +Name[sk]=cdb +Name[sv]=cdb +Name[uk]=cdb +Name[x-test]=xxcdbxx +Name[zh_CN]=cdb +Name[zh_TW]=cdb +TryExec=cdb +Backends=KCrash + +[KCrash] +Exec=cdb.exe -p %pid -lines -c "~*kv; q" +ExecInputFile=%tempfile +#BatchCommands=set term-width 200\nthread info\nbt all\ndetach diff --git a/src/drkonqibackends.cpp b/src/drkonqibackends.cpp --- a/src/drkonqibackends.cpp +++ b/src/drkonqibackends.cpp @@ -167,7 +167,7 @@ #elif !defined(Q_OS_WIN) QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("gdb")); #else - QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("kdbgwin")); + QString defaultDebuggerName = config.readEntry("Debugger", QStringLiteral("cdb")); #endif Debugger firstKnownGoodDebugger, preferredDebugger; diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt --- a/src/parser/CMakeLists.txt +++ b/src/parser/CMakeLists.txt @@ -1,5 +1,6 @@ set(BACKTRACEPARSER_SRCS backtraceparser.cpp + backtraceparsercdb.cpp backtraceparsergdb.cpp backtraceparserkdbgwin.cpp backtraceparsernull.cpp diff --git a/src/parser/backtraceparser.cpp b/src/parser/backtraceparser.cpp --- a/src/parser/backtraceparser.cpp +++ b/src/parser/backtraceparser.cpp @@ -19,6 +19,7 @@ #include "backtraceparsergdb.h" #include "backtraceparserkdbgwin.h" #include "backtraceparserlldb.h" +#include "backtraceparsercdb.h" #include "backtraceparsernull.h" #include "drkonqi_parser_debug.h" #include @@ -34,6 +35,8 @@ return new BacktraceParserKdbgwin(parent); } else if (debuggerName == QLatin1String("lldb")) { return new BacktraceParserLldb(parent); + } else if (debuggerName == QLatin1String("cdb")) { + return new BacktraceParserCdb(parent); } else { return new BacktraceParserNull(parent); } diff --git a/src/parser/backtraceparsercdb.h b/src/parser/backtraceparsercdb.h new file mode 100644 --- /dev/null +++ b/src/parser/backtraceparsercdb.h @@ -0,0 +1,41 @@ +/* + Copyright (C) 2019 Patrick Pereira + + 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. +*/ +#pragma once + +#include "backtraceparser.h" +class BacktraceParserCdbPrivate; + +class BacktraceParserCdb : public BacktraceParser +{ + Q_OBJECT + Q_DECLARE_PRIVATE(BacktraceParserCdb) +public: + explicit BacktraceParserCdb(QObject *parent = nullptr); + + QString parsedBacktrace() const override; + QList parsedBacktraceLines() const override; + +protected: + BacktraceParserPrivate *constructPrivate() const override; + +protected Q_SLOTS: + void newLine(const QString & lineStr) override; + +private: + void parseLine(const QString & lineStr); +}; diff --git a/src/parser/backtraceparsercdb.cpp b/src/parser/backtraceparsercdb.cpp new file mode 100644 --- /dev/null +++ b/src/parser/backtraceparsercdb.cpp @@ -0,0 +1,295 @@ +/* + Copyright (C) 2019 Patrick Pereira + + 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 "backtraceparsercdb.h" +#include "backtraceparser_p.h" +#include "drkonqi_parser_debug.h" +#include +#include + +class BacktraceLineCdb : public BacktraceLine +{ +public: + BacktraceLineCdb(const QString & line); + +private: + void parse(); + void rate(); +}; + +BacktraceLineCdb::BacktraceLineCdb(const QString & lineStr) + : BacktraceLine() +{ + d->m_line = lineStr; + d->m_functionName = QLatin1String("??"); + parse(); + if (d->m_type == StackFrame) { + rate(); + } +} + +void BacktraceLineCdb::parse() +{ + QRegExp regExp; + + if (d->m_line == QLatin1String("\n")) { + d->m_type = EmptyLine; + return; + } else if (d->m_line == QLatin1String("[KCrash Handler]\n")) { + d->m_type = KCrash; + return; + } else if (d->m_line.contains(QStringLiteral(""))) { + d->m_type = SignalHandlerStart; + return; + } + + regExp.setPattern(QStringLiteral( + "^(?P[0-9,a-f]{8}`[0-9,a-f]{8})" + "\ " + "(?P[0-9,a-f]{8}`[0-9,a-f]{8})" + "\ :\ " + "(?P[0-9,a-f]{8}`[0-9,a-f]{8}\ [0-9,a-f]{8}`[0-9,a-f]{8}\ [0-9,a-f]{8}`[0-9,a-f]{8}\ [0-9,a-f]{8}`[0-9,a-f]{8})" \\Args to Child + "\ :\ " + "(?P(.*))" + "!" + "(?P(.*))" + "\+0x[0-9,a-f]*\ \[" + "(?P(.*))" + "\ @\ " + "(?P[0-9]*)]" + "$" + )); + + qDebug() << "LINE" << d->m_line; + + if (regExp.exactMatch(d->m_line)) { + d->m_type = StackFrame; + d->m_stackFrameNumber = regExp.cap(1).toInt(); + d->m_functionName = regExp.cap(3).trimmed(); + + if (!regExp.cap(7).isEmpty()) { //we have file information (stuff after from|at) + if (regExp.cap(8) == QLatin1String("at")) { //'at' means we have a source file + d->m_file = regExp.cap(9); + } else { //'from' means we have a library + d->m_library = regExp.cap(9); + } + } + + qCDebug(DRKONQI_PARSER_LOG) << d->m_stackFrameNumber << d->m_functionName << d->m_file << d->m_library; + return; + } + + regExp.setPattern(QStringLiteral(".*\\(no debugging symbols found\\).*|" + ".*\\[Thread debugging using libthread_db enabled\\].*|" + ".*\\[New .*|" + "0x[0-9a-f]+.*|" + "Current language:.*")); + if (regExp.exactMatch(d->m_line)) { + qCDebug(DRKONQI_PARSER_LOG) << "garbage detected:" << d->m_line; + d->m_type = Crap; + return; + } + + regExp.setPattern(QStringLiteral("Thread [0-9]+\\s+\\(Thread [0-9a-fx]+\\s+\\(.*\\)\\):\n")); + if (regExp.exactMatch(d->m_line)) { + qCDebug(DRKONQI_PARSER_LOG) << "thread start detected:" << d->m_line; + d->m_type = ThreadStart; + return; + } + + regExp.setPattern(QStringLiteral("\\[Current thread is [0-9]+ \\(.*\\)\\]\n")); + if (regExp.exactMatch(d->m_line)) { + qCDebug(DRKONQI_PARSER_LOG) << "thread indicator detected:" << d->m_line; + d->m_type = ThreadIndicator; + return; + } + + qCDebug(DRKONQI_PARSER_LOG) << "line" << d->m_line << "did not match"; +} + +void BacktraceLineCdb::rate() +{ + LineRating r; + + //for explanations, see the LineRating enum definition + if (!fileName().isEmpty()) { + r = Good; + } else if (!libraryName().isEmpty()) { + if (functionName() == QLatin1String("??")) { + r = MissingFunction; + } else { + r = MissingSourceFile; + } + } else { + if (functionName() == QLatin1String("??")) { + r = MissingEverything; + } else { + r = MissingLibrary; + } + } + + d->m_rating = r; +} + +//END BacktraceLineCdb + +//BEGIN BacktraceParserCdb + +class BacktraceParserCdbPrivate : public BacktraceParserPrivate +{ +public: + BacktraceParserCdbPrivate() + : BacktraceParserPrivate(), + m_possibleKCrashStart(0), m_threadsCount(0), + m_isBelowSignalHandler(false), m_frameZeroAppeared(false) {} + + QString m_lineInputBuffer; + int m_possibleKCrashStart; + int m_threadsCount; + bool m_isBelowSignalHandler; + bool m_frameZeroAppeared; +}; + +BacktraceParserCdb::BacktraceParserCdb(QObject *parent) + : BacktraceParser(parent) +{ +} + +BacktraceParserPrivate* BacktraceParserCdb::constructPrivate() const +{ + return new BacktraceParserCdbPrivate; +} + +void BacktraceParserCdb::newLine(const QString & lineStr) +{ + Q_D(BacktraceParserCdb); + + //when the line is too long, gdb splits it into two lines. + //This breaks parsing and results in two Unknown lines instead of a StackFrame one. + //Here we workaround this by joining the two lines when such a scenario is detected. + if (d->m_lineInputBuffer.isEmpty()) { + d->m_lineInputBuffer = lineStr; + } else if (lineStr.startsWith(QLatin1Char(' ')) || lineStr.startsWith(QLatin1Char('\t'))) { + //gdb always adds some whitespace at the beginning of the second line + d->m_lineInputBuffer.append(lineStr); + } else { + parseLine(d->m_lineInputBuffer); + d->m_lineInputBuffer = lineStr; + } +} + +void BacktraceParserCdb::parseLine(const QString & lineStr) +{ + Q_D(BacktraceParserCdb); + + BacktraceLineCdb line(lineStr); + switch (line.type()) { + case BacktraceLine::Crap: + break; //we don't want crap in the backtrace ;) + case BacktraceLine::ThreadStart: + d->m_linesList.append(line); + d->m_possibleKCrashStart = d->m_linesList.size(); + d->m_threadsCount++; + //reset the state of the flags that need to be per-thread + d->m_isBelowSignalHandler = false; + d->m_frameZeroAppeared = false; // gdb bug workaround flag, see below + break; + case BacktraceLine::SignalHandlerStart: + if (!d->m_isBelowSignalHandler) { + //replace the stack frames of KCrash with a nice message + d->m_linesList.erase(d->m_linesList.begin() + d->m_possibleKCrashStart, d->m_linesList.end()); + d->m_linesList.insert(d->m_possibleKCrashStart, BacktraceLineCdb(QStringLiteral("[KCrash Handler]\n"))); + d->m_isBelowSignalHandler = true; //next line is the first below the signal handler + } else { + //this is not the first time we see a crash handler frame on the same thread, + //so we just add it to the list + d->m_linesList.append(line); + } + break; + case BacktraceLine::StackFrame: + // gdb workaround - (v6.8 at least) - 'thread apply all bt' writes + // the #0 stack frame again at the end. + // Here we ignore this frame by using a flag that tells us whether + // this is the first or the second time that the #0 frame appears in this thread. + // The flag is cleared on each thread start. + if (line.frameNumber() == 0) { + if (d->m_frameZeroAppeared) { + break; //break from the switch so that the frame is not added to the list. + } else { + d->m_frameZeroAppeared = true; + } + } + + //rate the stack frame if we are below the signal handler + if (d->m_isBelowSignalHandler) { + d->m_linesToRate.append(line); + } + Q_FALLTHROUGH(); + //fall through and append the line to the list + default: + d->m_linesList.append(line); + break; + } +} + +QString BacktraceParserCdb::parsedBacktrace() const +{ + Q_D(const BacktraceParserCdb); + + QString result; + if (d) { + QList::const_iterator i; + for (i = d->m_linesList.constBegin(); i != d->m_linesList.constEnd(); ++i) { + //if there is only one thread, we can omit the thread indicator, + //the thread header and all the empty lines. + if (d->m_threadsCount == 1 && ((*i).type() == BacktraceLine::ThreadIndicator + || (*i).type() == BacktraceLine::ThreadStart + || (*i).type() == BacktraceLine::EmptyLine)) + { + continue; + } + result += i->toString(); + } + } + return result; +} + +QList BacktraceParserCdb::parsedBacktraceLines() const +{ + Q_D(const BacktraceParserCdb); + + QList result; + if (d) { + QList::const_iterator i; + for (i = d->m_linesList.constBegin(); i != d->m_linesList.constEnd(); ++i) { + //if there is only one thread, we can omit the thread indicator, + //the thread header and all the empty lines. + if (d->m_threadsCount == 1 && ((*i).type() == BacktraceLine::ThreadIndicator + || (*i).type() == BacktraceLine::ThreadStart + || (*i).type() == BacktraceLine::EmptyLine)) + { + continue; + } + result.append(*i); + } + } + return result; +} + +//END BacktraceParserCdb + +