diff --git a/src/parser/backtraceparsergdb.cpp b/src/parser/backtraceparsergdb.cpp index e665c160..b3a0354c 100644 --- a/src/parser/backtraceparsergdb.cpp +++ b/src/parser/backtraceparsergdb.cpp @@ -1,295 +1,295 @@ /* 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 "backtraceparsergdb.h" #include "backtraceparser_p.h" #include "drkonqi_parser_debug.h" #include #include //BEGIN BacktraceLineGdb BacktraceLineGdb::BacktraceLineGdb(const QString & lineStr) : BacktraceLine() { d->m_line = lineStr; d->m_functionName = QLatin1String("??"); parse(); if (d->m_type == StackFrame) { rate(); } } void BacktraceLineGdb::parse() { QRegExp regExp; if (d->m_line == QLatin1Char('\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(QLatin1String(""))) { d->m_type = SignalHandlerStart; return; } regExp.setPattern(QStringLiteral("^#([0-9]+)" //matches the stack frame number, ex. "#0" "[\\s]+(0x[0-9a-f]+[\\s]+in[\\s]+)?" // matches " 0x0000dead in " (optionally) "((\\(anonymous namespace\\)::)?[^\\(]+)?" //matches the function name //(anything except left parenthesis, which is the start of the arguments section) //and optionally the prefix "(anonymous namespace)::" "(\\(.*\\))?" //matches the function arguments //(when the app doesn't have debugging symbols) "[\\s]+(const[\\s]+)?" //matches a traling const, if it exists "\\(.*\\)" //matches the arguments of the function with their values //(when the app has debugging symbols) "([\\s]+" //beginning of optional file information "(from|at)[\\s]+" //matches "from " or "at " "(.+)" //matches the filename (source file or shared library file) ")?\n$")); //matches trailing newline. //the )? at the end closes the parenthesis before [\\s]+(from|at) and //notes that the whole expression from there is optional. 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) bool file = regExp.cap(8) == QLatin1String("at"); //'at' means we have a source file (likely) // Gdb isn't entirely consistent here, when it uses 'from' it always refers to a library, but // sometimes the stack can resolve to a library even when it uses the 'at' key word. // This specifically seems to happen when a frame has no function name. const QString path = regExp.cap(9); const auto completeSuffix = QFileInfo(path).completeSuffix(); file = file && completeSuffix != QLatin1String("so") /* libf.so (so) */ && !completeSuffix.startsWith(QLatin1String("so.")) /* libf.so.1 (so.1) */ && !completeSuffix.contains(QLatin1String(".so") /* libf-1.0.so.1 (0.so.1)*/); if (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 BacktraceLineGdb::rate() { LineRating r; //for explanations, see the LineRating enum definition if (!fileName().isEmpty()) { r = Good; } else if (!libraryName().isEmpty()) { - if (functionName() == QLatin1String("??")) { + if (functionName() == QLatin1String("??") || functionName().isEmpty()) { r = MissingFunction; } else { r = MissingSourceFile; } } else { - if (functionName() == QLatin1String("??")) { + if (functionName() == QLatin1String("??") || functionName().isEmpty()) { r = MissingEverything; } else { r = MissingLibrary; } } d->m_rating = r; } //END BacktraceLineGdb //BEGIN BacktraceParserGdb class BacktraceParserGdbPrivate : public BacktraceParserPrivate { public: BacktraceParserGdbPrivate() : 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; }; BacktraceParserGdb::BacktraceParserGdb(QObject *parent) : BacktraceParser(parent) { } BacktraceParserPrivate* BacktraceParserGdb::constructPrivate() const { return new BacktraceParserGdbPrivate; } void BacktraceParserGdb::newLine(const QString & lineStr) { Q_D(BacktraceParserGdb); //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 BacktraceParserGdb::parseLine(const QString & lineStr) { Q_D(BacktraceParserGdb); BacktraceLineGdb 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, BacktraceLineGdb(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 BacktraceParserGdb::parsedBacktrace() const { Q_D(const BacktraceParserGdb); 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 BacktraceParserGdb::parsedBacktraceLines() const { Q_D(const BacktraceParserGdb); 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 BacktraceParserGdb diff --git a/src/tests/gdbbacktracelinetest.cpp b/src/tests/gdbbacktracelinetest.cpp index 85f296ed..4814f616 100644 --- a/src/tests/gdbbacktracelinetest.cpp +++ b/src/tests/gdbbacktracelinetest.cpp @@ -1,128 +1,128 @@ /* Copyright 2020 Harald Sitter This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . */ #include #include "../parser/backtraceparsergdb.h" class GdbBacktraceLineTest : public QObject { Q_OBJECT private Q_SLOTS: // rating() is often times somewhat misleading because it is an exclusive state // but in practise a frame may have multiple issues. for purposes of rating all // issues are considered equal. it's not ideal though, a frame that is missing // multiple elements is objectively worse than a frame that is just missing one. void testComplete() { BacktraceLineGdb line( "#7 0x00007f468b177bfa in KMime::DateFormatterPrivate::localized (t=t@entry=1579263464, shortFormat=shortFormat@entry=true, lang=...) at /usr/src/debug/kmime-19.12.1-lp151.150.1.x86_64/src/kmime_dateformatter.cpp:310\n" ); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 7); QCOMPARE(line.functionName(), "KMime::DateFormatterPrivate::localized"); QCOMPARE(line.fileName(), "/usr/src/debug/kmime-19.12.1-lp151.150.1.x86_64/src/kmime_dateformatter.cpp:310"); QCOMPARE(line.libraryName(), ""); QCOMPARE(line.rating(), BacktraceLine::Good); } void testPoorFile() { BacktraceLineGdb line("#41 0x00007f4684ae4e87 in g_main_context_dispatch () from /usr/lib64/libglib-2.0.so.0\n"); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 41); QCOMPARE(line.functionName(), "g_main_context_dispatch"); QCOMPARE(line.fileName(), ""); QCOMPARE(line.libraryName(), "/usr/lib64/libglib-2.0.so.0"); QCOMPARE(line.rating(), BacktraceLine::MissingSourceFile); } void testNoFunctionPoorFile() { // Also uses 'at' keyword while referring to a library, this used to trip up // the parser and make it think there's a source file, when in reality there // is not. // Lacking a function name further tripped up the parsing because originally // couldn't deal with the function name being missing entirely. // As a result this line used to rate as 'Good' -.- // https://bugs.kde.org/show_bug.cgi?id=416923 // https://bugs.kde.org/show_bug.cgi?id=418538 { // glib BacktraceLineGdb line("#13 0x00007fe6059971b1 in () at /usr/lib/libglib-2.0.so.0\n"); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 13); QCOMPARE(line.functionName(), ""); QCOMPARE(line.fileName(), ""); QCOMPARE(line.libraryName(), "/usr/lib/libglib-2.0.so.0"); - QCOMPARE(line.rating(), BacktraceLine::MissingSourceFile); + QCOMPARE(line.rating(), BacktraceLine::MissingFunction); } { // library without -2.0 (trips up suffix detection) BacktraceLineGdb line("#13 0x00007fe6059971b1 in () at /usr/lib/libpackagekit-qt.so.12\n"); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 13); QCOMPARE(line.functionName(), ""); QCOMPARE(line.fileName(), ""); QCOMPARE(line.libraryName(), "/usr/lib/libpackagekit-qt.so.12"); - QCOMPARE(line.rating(), BacktraceLine::MissingSourceFile); + QCOMPARE(line.rating(), BacktraceLine::MissingFunction); } { // library without any soversion BacktraceLineGdb line("#13 0x00007fe6059971b1 in () at /usr/lib/libpackagekit-qt.so\n"); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 13); QCOMPARE(line.functionName(), ""); QCOMPARE(line.fileName(), ""); QCOMPARE(line.libraryName(), "/usr/lib/libpackagekit-qt.so"); - QCOMPARE(line.rating(), BacktraceLine::MissingSourceFile); + QCOMPARE(line.rating(), BacktraceLine::MissingFunction); } { // library without any soversion but name suffix BacktraceLineGdb line("#13 0x00007fe6059971b1 in () at /usr/lib/libpackagekit-1.0.so\n"); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 13); QCOMPARE(line.functionName(), ""); QCOMPARE(line.fileName(), ""); QCOMPARE(line.libraryName(), "/usr/lib/libpackagekit-1.0.so"); - QCOMPARE(line.rating(), BacktraceLine::MissingSourceFile); + QCOMPARE(line.rating(), BacktraceLine::MissingFunction); } } void testOnlyFunctionNofile() { BacktraceLineGdb line("#20 0x0000557e978c1b7e in _start ()\n"); QCOMPARE(line.type(), BacktraceLine::StackFrame); QCOMPARE(line.frameNumber(), 20); QCOMPARE(line.functionName(), "_start"); QCOMPARE(line.fileName(), ""); QCOMPARE(line.libraryName(), ""); QCOMPARE(line.rating(), BacktraceLine::MissingLibrary); } void testInferiorMarker() { BacktraceLineGdb line("[Inferior 1 (process 72692) detached]\n"); QCOMPARE(line.type(), BacktraceLine::Unknown); QCOMPARE(line.rating(), BacktraceLine::InvalidRating); } }; QTEST_GUILESS_MAIN(GdbBacktraceLineTest) #include "gdbbacktracelinetest.moc"