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 }