File indexing completed on 2024-04-21 05:26:38

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     d->m_librariesWithMissingDebugSymbols.removeDuplicates();
0291 
0292     // Generate a simplified backtrace
0293     //- Starts from the first useful function
0294     //- Max of 5 lines
0295     //- Replaces garbage with [...]
0296     // At the same time, grab the first three useful functions for search queries
0297 
0298     i.toFront(); // Reuse the list iterator
0299     int functionIndex = 0;
0300     bool firstUsefulFound = false;
0301     while (i.hasNext() && functionIndex < 5) {
0302         const BacktraceLine &line = i.next();
0303         if (!lineShouldBeIgnored(line) && isFunctionUseful(line)) { // Line is not garbage to use
0304             if (!firstUsefulFound) {
0305                 firstUsefulFound = true;
0306             }
0307             // Save simplified backtrace line
0308             d->m_simplifiedBacktrace += line.toString();
0309 
0310             functionIndex++;
0311         } else if (firstUsefulFound) {
0312             // Add "[...]" if there are invalid functions in the middle
0313             if (!d->m_simplifiedBacktrace.endsWith(QLatin1String("[...]\n"))) {
0314                 d->m_simplifiedBacktrace += QLatin1String("[...]\n");
0315             }
0316         }
0317     }
0318 
0319     // calculate rating
0320     d->m_usefulness = Useless;
0321     if (rating >= (bestPossibleRating * 0.90)) {
0322         d->m_usefulness = ReallyUseful;
0323     } else if (rating >= (bestPossibleRating * 0.70)) {
0324         d->m_usefulness = MayBeUseful;
0325     } else if (rating >= (bestPossibleRating * 0.40)) {
0326         d->m_usefulness = ProbablyUseless;
0327     }
0328 
0329     // if there is no stack base, the executable is probably stripped,
0330     // so we need to be more strict with rating
0331     if (!haveSeenStackBase) {
0332         // less than 1 stack frame is useless
0333         if (counter < 1) {
0334             d->m_usefulness = Useless;
0335             // more than 1 stack frames might have some value, so let's not be so strict, just lower the rating
0336         } else if (d->m_usefulness > Useless) {
0337             d->m_usefulness = (Usefulness)(d->m_usefulness - 1);
0338         }
0339     }
0340 
0341     qCDebug(DRKONQI_PARSER_LOG) << "Rating:" << rating << "out of" << bestPossibleRating
0342                                 << "Usefulness:" << staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("Usefulness")).valueToKey(d->m_usefulness);
0343     qCDebug(DRKONQI_PARSER_LOG) << "90%:" << (bestPossibleRating * 0.90) << "70%:" << (bestPossibleRating * 0.70) << "40%:" << (bestPossibleRating * 0.40);
0344     qCDebug(DRKONQI_PARSER_LOG) << "Have seen stack base:" << haveSeenStackBase << "Lines counted:" << counter;
0345 }
0346 
0347 QString BacktraceParser::informationLines() const
0348 {
0349     Q_D(const BacktraceParser);
0350     QString ret = d->m_infoLines.join(QLatin1Char('\n'));
0351     if (!ret.endsWith(QLatin1Char('\n')))
0352         ret += QLatin1Char('\n');
0353     return ret;
0354 }
0355 
0356 void BacktraceParser::newLineInternal(const QString &)
0357 {
0358     Q_D(BacktraceParser);
0359     d->m_usefulness = InvalidUsefulness;
0360 }
0361 
0362 #include "moc_backtraceparser.cpp"