File indexing completed on 2024-03-24 17:01:22

0001 /*
0002     SPDX-FileCopyrightText: 2009-2010 George Kiagiadakis <kiagiadakis.george@gmail.com>
0003     SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 #include "backtraceparser_p.h"
0008 #include "backtraceparsercdb.h"
0009 #include "backtraceparsergdb.h"
0010 #include "backtraceparserkdbgwin.h"
0011 #include "backtraceparserlldb.h"
0012 #include "backtraceparsernull.h"
0013 #include "drkonqi_parser_debug.h"
0014 
0015 #include <QMetaEnum>
0016 #include <QRegularExpression>
0017 
0018 // factory
0019 BacktraceParser *BacktraceParser::newParser(const QString &debuggerName, QObject *parent)
0020 {
0021     if (debuggerName == QLatin1String("gdb")) {
0022         return new BacktraceParserGdb(parent);
0023     } else if (debuggerName == QLatin1String("kdbgwin")) {
0024         return new BacktraceParserKdbgwin(parent);
0025     } else if (debuggerName == QLatin1String("lldb")) {
0026         return new BacktraceParserLldb(parent);
0027     } else if (debuggerName == QLatin1String("cdb")) {
0028         return new BacktraceParserCdb(parent);
0029     } else {
0030         return new BacktraceParserNull(parent);
0031     }
0032 }
0033 
0034 BacktraceParser::BacktraceParser(QObject *parent)
0035     : QObject(parent)
0036     , d_ptr(nullptr)
0037 {
0038 }
0039 BacktraceParser::~BacktraceParser()
0040 {
0041     delete d_ptr;
0042 }
0043 
0044 void BacktraceParser::connectToGenerator(QObject *generator)
0045 {
0046     connect(generator, SIGNAL(starting()), this, SLOT(resetState()));
0047     connect(generator, SIGNAL(newLine(QString)), this, SLOT(newLine(QString)));
0048 }
0049 
0050 QString BacktraceParser::parsedBacktrace() const
0051 {
0052     Q_D(const BacktraceParser);
0053 
0054     QString result;
0055     if (d) {
0056         for (QList<BacktraceLine>::const_iterator i = d->m_linesList.constBegin(), total = d->m_linesList.constEnd(); i != total; ++i) {
0057             result += i->toString();
0058         }
0059     }
0060     return result;
0061 }
0062 
0063 QList<BacktraceLine> BacktraceParser::parsedBacktraceLines() const
0064 {
0065     Q_D(const BacktraceParser);
0066     return d ? d->m_linesList : QList<BacktraceLine>();
0067 }
0068 
0069 QString BacktraceParser::simplifiedBacktrace() const
0070 {
0071     Q_D(const BacktraceParser);
0072 
0073     // if there is no cached usefulness, the data calculation function has not run yet.
0074     if (d && d->m_usefulness == InvalidUsefulness) {
0075         const_cast<BacktraceParser *>(this)->calculateRatingData();
0076     }
0077 
0078     // if there is no d, the debugger has not run yet, so we have no backtrace.
0079     return d ? d->m_simplifiedBacktrace : QString();
0080 }
0081 
0082 BacktraceParser::Usefulness BacktraceParser::backtraceUsefulness() const
0083 {
0084     Q_D(const BacktraceParser);
0085 
0086     // if there is no cached usefulness, the data calculation function has not run yet.
0087     if (d && d->m_usefulness == InvalidUsefulness) {
0088         const_cast<BacktraceParser *>(this)->calculateRatingData();
0089     }
0090 
0091     // if there is no d, the debugger has not run yet,
0092     // so we can say that the (inexistent) backtrace is Useless.
0093     return d ? d->m_usefulness : Useless;
0094 }
0095 
0096 QStringList BacktraceParser::firstValidFunctions() const
0097 {
0098     Q_D(const BacktraceParser);
0099 
0100     // if there is no cached usefulness, the data calculation function has not run yet.
0101     if (d && d->m_usefulness == InvalidUsefulness) {
0102         const_cast<BacktraceParser *>(this)->calculateRatingData();
0103     }
0104 
0105     // if there is no d, the debugger has not run yet, so we have no functions to return.
0106     return d ? d->m_firstUsefulFunctions : QStringList();
0107 }
0108 
0109 QStringList BacktraceParser::librariesWithMissingDebugSymbols() const
0110 {
0111     Q_D(const BacktraceParser);
0112 
0113     // if there is no cached usefulness, the data calculation function has not run yet.
0114     if (d && d->m_usefulness == InvalidUsefulness) {
0115         const_cast<BacktraceParser *>(this)->calculateRatingData();
0116     }
0117 
0118     // if there is no d, the debugger has not run yet, so we have no libraries.
0119     return d ? d->m_librariesWithMissingDebugSymbols : QStringList();
0120 }
0121 
0122 bool BacktraceParser::hasCompositorCrashed() const
0123 {
0124     Q_D(const BacktraceParser);
0125 
0126     // if there is no cached usefulness, the data calculation function has not run yet.
0127     if (d && d->m_usefulness == InvalidUsefulness) {
0128         const_cast<BacktraceParser *>(this)->calculateRatingData();
0129     }
0130 
0131     // if there is no d, the debugger has not run yet, so we don't know anything.
0132     return d ? d->m_compositorCrashed : false;
0133 }
0134 
0135 void BacktraceParser::resetState()
0136 {
0137     // reset the state of the parser by getting a new instance of Private
0138     delete d_ptr;
0139     d_ptr = constructPrivate();
0140 }
0141 
0142 BacktraceParserPrivate *BacktraceParser::constructPrivate() const
0143 {
0144     return new BacktraceParserPrivate;
0145 }
0146 
0147 /* This function returns true if the given stack frame line is the base of the backtrace
0148    and thus the parser should not rate any frames below that one. */
0149 static bool lineIsStackBase(const BacktraceLine &line)
0150 {
0151     // optimization. if there is no function name, do not bother to check it
0152     if (line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction)
0153         return false;
0154 
0155     // "start_thread" is the base frame for all threads except the main thread, FIXME "start_thread"
0156     // probably works only on linux
0157     // main() or kdemain() is the base for the main thread
0158     if (line.functionName() == QLatin1String("start_thread") || line.functionName() == QLatin1String("main")
0159         || line.functionName() == QLatin1String("kdemain")) {
0160         return true;
0161     }
0162 
0163     // HACK for better rating. we ignore all stack frames below any function that matches
0164     // the following regular expression. The functions that match this expression are usually
0165     //"QApplicationPrivate::notify_helper", "QApplication::notify" and similar, which
0166     // are used to send any kind of event to the Qt application. All stack frames below this,
0167     // with or without debug symbols, are useless to KDE developers, so we ignore them.
0168     const QRegularExpression re(QRegularExpression::anchoredPattern(QStringLiteral("(Q|K)(Core)?Application(Private)?::notify.*")));
0169     if (re.match(line.functionName()).hasMatch()) {
0170         return true;
0171     }
0172 
0173     // attempt to recognize crashes that happen after main has returned (bug 200993)
0174     if (line.functionName() == QLatin1String("~KCleanUpGlobalStatic") || line.functionName() == QLatin1String("~QGlobalStatic")
0175         || line.functionName() == QLatin1String("exit") || line.functionName() == QLatin1String("*__GI_exit"))
0176         return true;
0177 
0178     return false;
0179 }
0180 
0181 /* This function returns true if the given stack frame line is the top of the bactrace
0182    and thus the parser should not rate any frames above that one. This is used to avoid
0183    rating the stack frames of abort(), assert(), Q_ASSERT() and qCCritical(DRKONQI_PARSER_LOG) */
0184 static bool lineIsStackTop(const BacktraceLine &line)
0185 {
0186     // optimization. if there is no function name, do not bother to check it
0187     if (line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction)
0188         return false;
0189 
0190     if (line.functionName().startsWith(QLatin1String("qt_assert")) // qt_assert and qt_assert_x
0191         || line.functionName() == QLatin1String("qFatal") || line.functionName() == QLatin1String("abort")
0192         || line.functionName() == QLatin1String("*__GI_abort") || line.functionName() == QLatin1String("*__GI___assert_fail"))
0193         return true;
0194 
0195     return false;
0196 }
0197 
0198 /* This function returns true if the given stack frame line should be ignored from rating
0199    for some reason. Currently it ignores all libc/libstdc++/libpthread functions. */
0200 static bool lineShouldBeIgnored(const BacktraceLine &line)
0201 {
0202     if (line.libraryName().contains(QLatin1String("libc.so")) || line.libraryName().contains(QLatin1String("libstdc++.so"))
0203         || line.functionName().startsWith(QLatin1String("*__GI_")) // glibc2.9 uses *__GI_ as prefix
0204         || line.libraryName().contains(QLatin1String("libpthread.so")) || line.libraryName().contains(QLatin1String("libglib-2.0.so"))
0205         || line.functionName() == QLatin1String("__libc_start_main") // below main on apps without symbols
0206         || line.functionName() == QLatin1String("_start") // below main on apps without symbols
0207 #ifdef Q_OS_MACOS
0208         || (line.libraryName().startsWith(QLatin1String("libsystem_")) && line.libraryName().endsWith(QLatin1String(".dylib")))
0209         || line.libraryName().contains(QLatin1String("Foundation`"))
0210 #endif
0211         || line.libraryName().contains(QLatin1String("ntdll.dll")) || line.libraryName().contains(QLatin1String("kernel32.dll"))
0212         || line.functionName().contains(QLatin1String("_tmain")) || line.functionName() == QLatin1String("WinMain")) {
0213         return true;
0214     }
0215 
0216     return false;
0217 }
0218 
0219 static bool isFunctionUseful(const BacktraceLine &line)
0220 {
0221     // We need the function name
0222     if (line.rating() == BacktraceLine::MissingEverything || line.rating() == BacktraceLine::MissingFunction) {
0223         return false;
0224     }
0225 
0226     // Misc ignores
0227     if (line.functionName() == QLatin1String("__kernel_vsyscall") || line.functionName() == QLatin1String("raise")
0228         || line.functionName() == QLatin1String("abort") || line.functionName() == QLatin1String("__libc_message")
0229         || line.functionName() == QLatin1String("thr_kill") /* *BSD */) {
0230         return false;
0231     }
0232 
0233     // Ignore core Qt functions
0234     //(QObject can be useful in some cases)
0235     if (line.functionName().startsWith(QLatin1String("QBasicAtomicInt::")) || line.functionName().startsWith(QLatin1String("QBasicAtomicPointer::"))
0236         || line.functionName().startsWith(QLatin1String("QAtomicInt::")) || line.functionName().startsWith(QLatin1String("QAtomicPointer::"))
0237         || line.functionName().startsWith(QLatin1String("QMetaObject::")) || line.functionName().startsWith(QLatin1String("QPointer::"))
0238         || line.functionName().startsWith(QLatin1String("QWeakPointer::")) || line.functionName().startsWith(QLatin1String("QSharedPointer::"))
0239         || line.functionName().startsWith(QLatin1String("QScopedPointer::")) || line.functionName().startsWith(QLatin1String("QMetaCallEvent::"))) {
0240         return false;
0241     }
0242 
0243     // Ignore core Qt containers misc functions
0244     if (line.functionName().endsWith(QLatin1String("detach")) || line.functionName().endsWith(QLatin1String("detach_helper"))
0245         || line.functionName().endsWith(QLatin1String("node_create")) || line.functionName().endsWith(QLatin1String("deref"))
0246         || line.functionName().endsWith(QLatin1String("ref")) || line.functionName().endsWith(QLatin1String("node_copy"))
0247         || line.functionName().endsWith(QLatin1String("d_func"))) {
0248         return false;
0249     }
0250 
0251     // Misc Qt stuff
0252     if (line.functionName() == QLatin1String("qt_message_output") || line.functionName() == QLatin1String("qt_message")
0253         || line.functionName() == QLatin1String("qFatal") || line.functionName().startsWith(QLatin1String("qGetPtrHelper"))
0254         || line.functionName().startsWith(QLatin1String("qt_meta_"))) {
0255         return false;
0256     }
0257 
0258     return true;
0259 }
0260 
0261 static bool isFunctionUsefulForSearch(const BacktraceLine &line)
0262 {
0263     // Ignore Qt containers (and iterators Q*Iterator)
0264     if (line.functionName().startsWith(QLatin1String("QList")) || line.functionName().startsWith(QLatin1String("QLinkedList"))
0265         || line.functionName().startsWith(QLatin1String("QVector")) || line.functionName().startsWith(QLatin1String("QStack"))
0266         || line.functionName().startsWith(QLatin1String("QQueue")) || line.functionName().startsWith(QLatin1String("QSet"))
0267         || line.functionName().startsWith(QLatin1String("QMap")) || line.functionName().startsWith(QLatin1String("QMultiMap"))
0268         || line.functionName().startsWith(QLatin1String("QMapData")) || line.functionName().startsWith(QLatin1String("QHash"))
0269         || line.functionName().startsWith(QLatin1String("QMultiHash")) || line.functionName().startsWith(QLatin1String("QHashData"))) {
0270         return false;
0271     }
0272 
0273     return true;
0274 }
0275 
0276 void BacktraceParser::calculateRatingData()
0277 {
0278     Q_D(BacktraceParser);
0279 
0280     uint rating = 0, bestPossibleRating = 0, counter = 0;
0281     bool haveSeenStackBase = false;
0282 
0283     QListIterator<BacktraceLine> i(d->m_linesToRate);
0284     i.toBack(); // start from the end of the list
0285 
0286     while (i.hasPrevious()) {
0287         const BacktraceLine &line = i.previous();
0288 
0289         if (!d->m_compositorCrashed && line.toString().contains(QLatin1String("The Wayland connection broke. Did the Wayland compositor die"))) {
0290             d->m_compositorCrashed = true;
0291         }
0292 
0293         if (!i.hasPrevious() && line.rating() == BacktraceLine::MissingEverything) {
0294             // Under some circumstances, the very first stack frame is invalid (ex, calling a function
0295             // at an invalid address could result in a stack frame like "0x00000000 in ?? ()"),
0296             // which however does not necessarily mean that the backtrace has a missing symbol on
0297             // the first line. Here we make sure to ignore this line from rating. (bug 190882)
0298             break; // there are no more items anyway, just break the loop
0299         }
0300 
0301         if (lineIsStackBase(line)) {
0302             rating = bestPossibleRating = counter = 0; // restart rating ignoring any previous frames
0303             haveSeenStackBase = true;
0304         } else if (lineIsStackTop(line)) {
0305             break; // we have reached the top, no need to inspect any more frames
0306         }
0307 
0308         if (lineShouldBeIgnored(line)) {
0309             continue;
0310         }
0311 
0312         if (line.rating() == BacktraceLine::MissingFunction || line.rating() == BacktraceLine::MissingSourceFile) {
0313             d->m_librariesWithMissingDebugSymbols.append(line.libraryName().trimmed());
0314         }
0315 
0316         uint multiplier = ++counter; // give weight to the first lines
0317         rating += static_cast<uint>(line.rating()) * multiplier;
0318         bestPossibleRating += static_cast<uint>(BacktraceLine::BestRating) * multiplier;
0319 
0320         qCDebug(DRKONQI_PARSER_LOG) << line.rating() << line.toString();
0321     }
0322 
0323     // Generate a simplified backtrace
0324     //- Starts from the first useful function
0325     //- Max of 5 lines
0326     //- Replaces garbage with [...]
0327     // At the same time, grab the first three useful functions for search queries
0328 
0329     i.toFront(); // Reuse the list iterator
0330     int functionIndex = 0;
0331     int usefulFunctionsCount = 0;
0332     bool firstUsefulFound = false;
0333     while (i.hasNext() && functionIndex < 5) {
0334         const BacktraceLine &line = i.next();
0335         if (!lineShouldBeIgnored(line) && isFunctionUseful(line)) { // Line is not garbage to use
0336             if (!firstUsefulFound) {
0337                 firstUsefulFound = true;
0338             }
0339             // Save simplified backtrace line
0340             d->m_simplifiedBacktrace += line.toString();
0341 
0342             // Fetch three useful functions (only functionName) for search queries
0343             if (usefulFunctionsCount < 3 && isFunctionUsefulForSearch(line) && !d->m_firstUsefulFunctions.contains(line.functionName())) {
0344                 d->m_firstUsefulFunctions.append(line.functionName());
0345                 usefulFunctionsCount++;
0346             }
0347 
0348             functionIndex++;
0349         } else if (firstUsefulFound) {
0350             // Add "[...]" if there are invalid functions in the middle
0351             if (!d->m_simplifiedBacktrace.endsWith(QLatin1String("[...]\n"))) {
0352                 d->m_simplifiedBacktrace += QLatin1String("[...]\n");
0353             }
0354         }
0355     }
0356 
0357     // calculate rating
0358     d->m_usefulness = Useless;
0359     if (rating >= (bestPossibleRating * 0.90)) {
0360         d->m_usefulness = ReallyUseful;
0361     } else if (rating >= (bestPossibleRating * 0.70)) {
0362         d->m_usefulness = MayBeUseful;
0363     } else if (rating >= (bestPossibleRating * 0.40)) {
0364         d->m_usefulness = ProbablyUseless;
0365     }
0366 
0367     // if there is no stack base, the executable is probably stripped,
0368     // so we need to be more strict with rating
0369     if (!haveSeenStackBase) {
0370         // less than 1 stack frame is useless
0371         if (counter < 1) {
0372             d->m_usefulness = Useless;
0373             // more than 1 stack frames might have some value, so let's not be so strict, just lower the rating
0374         } else if (d->m_usefulness > Useless) {
0375             d->m_usefulness = (Usefulness)(d->m_usefulness - 1);
0376         }
0377     }
0378 
0379     qCDebug(DRKONQI_PARSER_LOG) << "Rating:" << rating << "out of" << bestPossibleRating
0380                                 << "Usefulness:" << staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Usefulness")).valueToKey(d->m_usefulness);
0381     qCDebug(DRKONQI_PARSER_LOG) << "90%:" << (bestPossibleRating * 0.90) << "70%:" << (bestPossibleRating * 0.70) << "40%:" << (bestPossibleRating * 0.40);
0382     qCDebug(DRKONQI_PARSER_LOG) << "Have seen stack base:" << haveSeenStackBase << "Lines counted:" << counter;
0383 }
0384 
0385 QString BacktraceParser::informationLines() const
0386 {
0387     Q_D(const BacktraceParser);
0388     QString ret = d->m_infoLines.join(QLatin1Char('\n'));
0389     if (!ret.endsWith(QLatin1Char('\n')))
0390         ret += QLatin1Char('\n');
0391     return ret;
0392 }