File indexing completed on 2024-04-21 16:12:18
0001 /* 0002 SPDX-FileCopyrightText: 2009-2010 George Kiagiadakis <gkiagia@users.sourceforge.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 #include "backtraceparsergdb.h" 0007 #include "backtraceparser_p.h" 0008 #include "drkonqi_parser_debug.h" 0009 0010 #include <QFileInfo> 0011 #include <QRegularExpression> 0012 0013 // BEGIN BacktraceLineGdb 0014 0015 const QLatin1String BacktraceParserGdb::KCRASH_INFO_MESSAGE("KCRASH_INFO_MESSAGE: "); 0016 0017 BacktraceLineGdb::BacktraceLineGdb(const QString &lineStr) 0018 : BacktraceLine() 0019 { 0020 d->m_line = lineStr; 0021 d->m_functionName = QLatin1String("??"); 0022 parse(); 0023 if (d->m_type == StackFrame) { 0024 rate(); 0025 } 0026 } 0027 0028 void BacktraceLineGdb::parse() 0029 { 0030 if (d->m_line == QLatin1Char('\n')) { 0031 d->m_type = EmptyLine; 0032 return; 0033 } else if (d->m_line == QLatin1String("[KCrash Handler]\n")) { 0034 d->m_type = KCrash; 0035 return; 0036 } else if (d->m_line.contains(QLatin1String("<signal handler called>"))) { 0037 d->m_type = SignalHandlerStart; 0038 return; 0039 } 0040 0041 static QRegularExpression regExp; 0042 // make dot "." char match new lines, to match e.g.: 0043 // "#5 0x00007f50e99f776f in QWidget::testAttribute_helper (this=0x6e6440,\n attribute=Qt::WA_WState_Created) at kernel/qwidget.cpp:9081\n" 0044 // gdb breaks long stack frame lines into multiple ones for readability 0045 regExp.setPatternOptions(QRegularExpression::DotMatchesEverythingOption); 0046 regExp.setPattern( 0047 QRegularExpression::anchoredPattern(QStringLiteral("#([0-9]+)" // matches the stack frame number, ex. "#0" 0048 "[\\s]+(?:0x[0-9a-f]+[\\s]+in[\\s]+)?" // matches " 0x0000dead in " (optionally) 0049 "((?:\\(anonymous namespace\\)::)?[^\\(]+)?" // matches the function name 0050 //(anything except left parenthesis, which is the start of the arguments section) 0051 // and optionally the prefix "(anonymous namespace)::" 0052 "(?:\\(.*\\))?" // matches the function arguments 0053 //(when the app doesn't have debugging symbols) 0054 "[\\s]+(?:const[\\s]+)?" // matches a trailing const, if it exists 0055 "\\(.*\\)" // matches the arguments of the function with their values 0056 //(when the app has debugging symbols) 0057 "([\\s]+" // beginning of optional file information 0058 "(from|at)[\\s]+" // matches "from " or "at " 0059 "(.+)" // matches the filename (source file or shared library file) 0060 ")?\n"))); // matches trailing newline. 0061 // the )? at the end closes the parenthesis before [\\s]+(from|at) and 0062 // notes that the whole expression from there is optional. 0063 0064 QRegularExpressionMatch match = regExp.match(d->m_line); 0065 if (match.hasMatch()) { 0066 d->m_type = StackFrame; 0067 d->m_stackFrameNumber = match.captured(1).toInt(); 0068 d->m_functionName = match.captured(2).trimmed(); 0069 0070 if (!match.captured(3).isEmpty()) { // we have file information (stuff after from|at) 0071 bool file = match.captured(4) == QLatin1String("at"); //'at' means we have a source file (likely) 0072 // Gdb isn't entirely consistent here, when it uses 'from' it always refers to a library, but 0073 // sometimes the stack can resolve to a library even when it uses the 'at' key word. 0074 // This specifically seems to happen when a frame has no function name. 0075 const QString path = match.captured(5); 0076 const auto completeSuffix = QFileInfo(path).completeSuffix(); 0077 file = file && completeSuffix != QLatin1String("so") /* libf.so (so) */ 0078 && !completeSuffix.startsWith(QLatin1String("so.")) /* libf.so.1 (so.1) */ 0079 && !completeSuffix.contains(QLatin1String(".so") /* libf-1.0.so.1 (0.so.1)*/); 0080 if (file) { 0081 d->m_file = match.captured(5); 0082 } else { //'from' means we have a library 0083 d->m_library = match.captured(5); 0084 } 0085 } 0086 0087 qCDebug(DRKONQI_PARSER_LOG) << d->m_stackFrameNumber << d->m_functionName << d->m_file << d->m_library; 0088 return; 0089 } 0090 0091 if (d->m_line.contains(BacktraceParserGdb::KCRASH_INFO_MESSAGE)) { 0092 qCDebug(DRKONQI_PARSER_LOG) << "info:" << d->m_line; 0093 d->m_type = Info; 0094 return; 0095 } 0096 0097 regExp.setPatternOptions(regExp.patternOptions() & ~QRegularExpression::DotMatchesEverythingOption); 0098 0099 regExp.setPattern( 0100 QRegularExpression::anchoredPattern(QStringLiteral(".*\\(no debugging symbols found\\).*|" 0101 ".*\\[Thread debugging using libthread_db enabled\\].*|" 0102 ".*\\[New .*|" 0103 "0x[0-9a-f]+.*|" 0104 "Current language:.*"))); 0105 if (regExp.match(d->m_line).hasMatch()) { 0106 qCDebug(DRKONQI_PARSER_LOG) << "garbage detected:" << d->m_line; 0107 d->m_type = Crap; 0108 return; 0109 } 0110 0111 regExp.setPattern(QRegularExpression::anchoredPattern(QStringLiteral("Thread [0-9]+\\s+\\(Thread [0-9a-fx]+\\s+\\(.*\\)\\):\n"))); 0112 if (regExp.match(d->m_line).hasMatch()) { 0113 qCDebug(DRKONQI_PARSER_LOG) << "thread start detected:" << d->m_line; 0114 d->m_type = ThreadStart; 0115 return; 0116 } 0117 0118 regExp.setPattern(QRegularExpression::anchoredPattern(QStringLiteral("\\[Current thread is [0-9]+ \\(.*\\)\\]\n"))); 0119 if (regExp.match(d->m_line).hasMatch()) { 0120 qCDebug(DRKONQI_PARSER_LOG) << "thread indicator detected:" << d->m_line; 0121 d->m_type = ThreadIndicator; 0122 return; 0123 } 0124 0125 qCDebug(DRKONQI_PARSER_LOG) << "line" << d->m_line << "did not match"; 0126 } 0127 0128 void BacktraceLineGdb::rate() 0129 { 0130 LineRating r; 0131 0132 // for explanations, see the LineRating enum definition 0133 if (!fileName().isEmpty()) { 0134 r = Good; 0135 } else if (!libraryName().isEmpty()) { 0136 if (functionName() == QLatin1String("??") || functionName().isEmpty()) { 0137 r = MissingFunction; 0138 } else { 0139 r = MissingSourceFile; 0140 } 0141 } else { 0142 if (functionName() == QLatin1String("??") || functionName().isEmpty()) { 0143 r = MissingEverything; 0144 } else { 0145 r = MissingLibrary; 0146 } 0147 } 0148 0149 d->m_rating = r; 0150 } 0151 0152 // END BacktraceLineGdb 0153 0154 // BEGIN BacktraceParserGdb 0155 0156 class BacktraceParserGdbPrivate : public BacktraceParserPrivate 0157 { 0158 public: 0159 using BacktraceParserPrivate::BacktraceParserPrivate; 0160 0161 QString m_lineInputBuffer; 0162 int m_possibleKCrashStart = 0; 0163 int m_threadsCount = 0; 0164 bool m_isBelowSignalHandler = false; 0165 bool m_frameZeroAppeared = false; 0166 }; 0167 0168 BacktraceParserGdb::BacktraceParserGdb(QObject *parent) 0169 : BacktraceParser(parent) 0170 { 0171 } 0172 0173 BacktraceParserPrivate *BacktraceParserGdb::constructPrivate() const 0174 { 0175 return new BacktraceParserGdbPrivate; 0176 } 0177 0178 void BacktraceParserGdb::newLine(const QString &lineStr) 0179 { 0180 Q_D(BacktraceParserGdb); 0181 0182 // when the line is too long, gdb splits it into two lines. 0183 // This breaks parsing and results in two Unknown lines instead of a StackFrame one. 0184 // Here we workaround this by joining the two lines when such a scenario is detected. 0185 if (d->m_lineInputBuffer.isEmpty()) { 0186 d->m_lineInputBuffer = lineStr; 0187 } else if (lineStr.startsWith(QLatin1Char(' ')) || lineStr.startsWith(QLatin1Char('\t'))) { 0188 // gdb always adds some whitespace at the beginning of the second line 0189 d->m_lineInputBuffer.append(lineStr); 0190 } else { 0191 parseLine(d->m_lineInputBuffer); 0192 d->m_lineInputBuffer = lineStr; 0193 } 0194 } 0195 0196 void BacktraceParserGdb::parseLine(const QString &lineStr) 0197 { 0198 Q_D(BacktraceParserGdb); 0199 0200 BacktraceLineGdb line(lineStr); 0201 switch (line.type()) { 0202 case BacktraceLine::Crap: 0203 break; // we don't want crap in the backtrace ;) 0204 case BacktraceLine::Info: 0205 d->m_infoLines << line.toString().mid(KCRASH_INFO_MESSAGE.size()); 0206 break; 0207 case BacktraceLine::ThreadStart: 0208 d->m_linesList.append(line); 0209 d->m_possibleKCrashStart = d->m_linesList.size(); 0210 d->m_threadsCount++; 0211 // reset the state of the flags that need to be per-thread 0212 d->m_isBelowSignalHandler = false; 0213 d->m_frameZeroAppeared = false; // gdb bug workaround flag, see below 0214 break; 0215 case BacktraceLine::SignalHandlerStart: 0216 if (!d->m_isBelowSignalHandler) { 0217 // replace the stack frames of KCrash with a nice message 0218 d->m_linesList.erase(d->m_linesList.begin() + d->m_possibleKCrashStart, d->m_linesList.end()); 0219 d->m_linesList.insert(d->m_possibleKCrashStart, BacktraceLineGdb(QStringLiteral("[KCrash Handler]\n"))); 0220 d->m_isBelowSignalHandler = true; // next line is the first below the signal handler 0221 } else { 0222 // this is not the first time we see a crash handler frame on the same thread, 0223 // so we just add it to the list 0224 d->m_linesList.append(line); 0225 } 0226 break; 0227 case BacktraceLine::StackFrame: 0228 // gdb workaround - (v6.8 at least) - 'thread apply all bt' writes 0229 // the #0 stack frame again at the end. 0230 // Here we ignore this frame by using a flag that tells us whether 0231 // this is the first or the second time that the #0 frame appears in this thread. 0232 // The flag is cleared on each thread start. 0233 if (line.frameNumber() == 0) { 0234 if (d->m_frameZeroAppeared) { 0235 break; // break from the switch so that the frame is not added to the list. 0236 } else { 0237 d->m_frameZeroAppeared = true; 0238 } 0239 } 0240 0241 // rate the stack frame if we are below the signal handler 0242 if (d->m_isBelowSignalHandler) { 0243 d->m_linesToRate.append(line); 0244 } 0245 Q_FALLTHROUGH(); 0246 // fall through and append the line to the list 0247 default: 0248 d->m_linesList.append(line); 0249 break; 0250 } 0251 } 0252 0253 QString BacktraceParserGdb::parsedBacktrace() const 0254 { 0255 Q_D(const BacktraceParserGdb); 0256 0257 QString result; 0258 if (d) { 0259 QList<BacktraceLine>::const_iterator i; 0260 for (i = d->m_linesList.constBegin(); i != d->m_linesList.constEnd(); ++i) { 0261 // if there is only one thread, we can omit the thread indicator, 0262 // the thread header and all the empty lines. 0263 if (d->m_threadsCount == 1 0264 && ((*i).type() == BacktraceLine::ThreadIndicator || (*i).type() == BacktraceLine::ThreadStart || (*i).type() == BacktraceLine::EmptyLine)) { 0265 continue; 0266 } 0267 result += i->toString(); 0268 } 0269 } 0270 return result; 0271 } 0272 0273 QList<BacktraceLine> BacktraceParserGdb::parsedBacktraceLines() const 0274 { 0275 Q_D(const BacktraceParserGdb); 0276 0277 QList<BacktraceLine> result; 0278 if (d) { 0279 QList<BacktraceLine>::const_iterator i; 0280 for (i = d->m_linesList.constBegin(); i != d->m_linesList.constEnd(); ++i) { 0281 // if there is only one thread, we can omit the thread indicator, 0282 // the thread header and all the empty lines. 0283 if (d->m_threadsCount == 1 0284 && ((*i).type() == BacktraceLine::ThreadIndicator || (*i).type() == BacktraceLine::ThreadStart || (*i).type() == BacktraceLine::EmptyLine)) { 0285 continue; 0286 } 0287 result.append(*i); 0288 } 0289 } 0290 return result; 0291 } 0292 0293 // END BacktraceParserGdb