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"