File indexing completed on 2024-04-21 09:17:08

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