| 1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
| 2 | // |
| 3 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | |
| 5 | #include "gdbmi.h" |
| 6 | |
| 7 | #include <QRegularExpression> |
| 8 | |
| 9 | namespace gdbmi { |
| 10 | // code is from https://github.com/martinribelotta/gdbfrontend |
| 11 | // https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html#GDB_002fMI |
| 12 | |
| 13 | // GDB/MI Result Records: ^done, ^running, ^connected, ^error, exit |
| 14 | const auto GDB_MI_RESULT = QRegularExpression(R"(^(\d*)\^(\S+?)(?:,(.*))?$)" , |
| 15 | QRegularExpression::MultilineOption | |
| 16 | QRegularExpression::NoPatternOption); |
| 17 | |
| 18 | // GDB/MI Stream Records: "~" string-output "@" string-output "&" string-output |
| 19 | const auto GDB_MI_CONSOLE = QRegularExpression(R"re(~"(.*)")re" , |
| 20 | QRegularExpression::MultilineOption | |
| 21 | QRegularExpression::DotMatchesEverythingOption); |
| 22 | const auto GDB_MI_TARGET = QRegularExpression(R"re(@"(.*)")re" , |
| 23 | QRegularExpression::MultilineOption | |
| 24 | QRegularExpression::DotMatchesEverythingOption); |
| 25 | const auto GDB_MI_LOG = QRegularExpression(R"re(&"(.*)")re" , |
| 26 | QRegularExpression::MultilineOption | |
| 27 | QRegularExpression::DotMatchesEverythingOption); |
| 28 | |
| 29 | // GDB/MI Async Records:a consequence of commands (e.g., a breakpoint modified) or a result of target activity (e.g., target stopped). |
| 30 | const auto GDB_MI_NOTIFY = QRegularExpression(R"(^(\d*)[*=](\S+?),(.*)$)" , |
| 31 | QRegularExpression::MultilineOption | |
| 32 | QRegularExpression::NoPatternOption); |
| 33 | // GDB/MI Finished |
| 34 | const auto GDB_MI_FINISHED = QRegularExpression(R"(^\(gdb\)\s*$)" , |
| 35 | QRegularExpression::MultilineOption | |
| 36 | QRegularExpression::NoPatternOption); |
| 37 | |
| 38 | const QString CLOSE_CHARS{"}]\"" }; |
| 39 | |
| 40 | const auto DISASSEMBLE_DATA_REG = QRegularExpression(R"(~\".+0x.+<\+.+>:.+\")" , |
| 41 | QRegularExpression::MultilineOption | |
| 42 | QRegularExpression::NoPatternOption); |
| 43 | const auto DISASSEMBLE_END_REG = QRegularExpression(R"(~\"End of assembler dump.)" , |
| 44 | QRegularExpression::NoPatternOption); |
| 45 | |
| 46 | QString escapedText(const QString& s) |
| 47 | { |
| 48 | return QString{s}.replace(QRegularExpression{R"(\\(.))" }, "\1" ); |
| 49 | } |
| 50 | |
| 51 | template<typename T> |
| 52 | static T strmap(const QMap<QString, T>& map, const QString& key, T defaultValue) |
| 53 | { |
| 54 | return map.value(key, defaultValue); |
| 55 | } |
| 56 | |
| 57 | namespace priv { |
| 58 | |
| 59 | QString::const_iterator& skipspaces(QString::const_iterator& it) |
| 60 | { |
| 61 | while (it->isSpace()) |
| 62 | ++it; |
| 63 | return it; |
| 64 | } |
| 65 | |
| 66 | QString parseString(const QString& s, QString::const_iterator& it) |
| 67 | { |
| 68 | QString v; |
| 69 | while (it != s.cend()) { |
| 70 | if (*it == '"') |
| 71 | break; |
| 72 | if (*it == '\\') |
| 73 | if (++it == s.cend()) |
| 74 | break; |
| 75 | v += *it++; |
| 76 | } |
| 77 | ++it; |
| 78 | return v; |
| 79 | } |
| 80 | |
| 81 | QString parseKey(const QString& str, QString::const_iterator& it) |
| 82 | { |
| 83 | QString key; |
| 84 | while (it != str.cend()) { |
| 85 | if (*it == '=') |
| 86 | break; |
| 87 | if (!it->isSpace()) |
| 88 | key += *it; |
| 89 | ++it; |
| 90 | } |
| 91 | return key; |
| 92 | } |
| 93 | |
| 94 | QString consumeTo(QChar c, const QString& str, QString::const_iterator& it) |
| 95 | { |
| 96 | QString consumed; |
| 97 | while (it != str.cend()) { |
| 98 | if (*it == c) { |
| 99 | ++it; |
| 100 | break; |
| 101 | } |
| 102 | consumed += *it++; |
| 103 | } |
| 104 | return consumed; |
| 105 | } |
| 106 | |
| 107 | QVariantList parseArray(const QString& str, QString::const_iterator& it); |
| 108 | QVariantMap parseKeyVal(const QString& str, QString::const_iterator& it, QChar terminator='\0'); |
| 109 | QVariantMap parseDict(const QString& str, QString::const_iterator& it); |
| 110 | QVariant parseValue(const QString& str, QString::const_iterator& it, QChar terminator); |
| 111 | |
| 112 | QVariantList parseArray(const QString& str, QString::const_iterator& it) |
| 113 | { |
| 114 | QVariantList l; |
| 115 | while (it != str.cend() && *it != ']') { |
| 116 | l.append(parseValue(str, it, ']')); |
| 117 | if (*it == ']') { |
| 118 | ++it; |
| 119 | break; |
| 120 | } |
| 121 | consumeTo(',', str, it); |
| 122 | } |
| 123 | return l; |
| 124 | } |
| 125 | |
| 126 | QVariantMap parseDict(const QString& str, QString::const_iterator& it) |
| 127 | { |
| 128 | QVariantMap m = parseKeyVal(str, it, '}'); |
| 129 | ++it; |
| 130 | return m; |
| 131 | } |
| 132 | |
| 133 | QVariantMap parseKeyVal(const QString& str, QString::const_iterator& it, QChar terminator) |
| 134 | { |
| 135 | QVariantMap m; |
| 136 | while (it != str.cend() && *it != terminator) { |
| 137 | auto k = parseKey(str, skipspaces(it)); |
| 138 | auto v = parseValue(str, skipspaces(++it), terminator); |
| 139 | // qInfo() << "Key => " << k; |
| 140 | // qInfo() << "Value => " << k; |
| 141 | m.insertMulti(k,v); |
| 142 | //m.insert(k, v); //insertMulti?? |
| 143 | if (it >= str.cend()) |
| 144 | break; |
| 145 | if (*it == terminator) { |
| 146 | break; |
| 147 | } |
| 148 | consumeTo(',', str, it); |
| 149 | } |
| 150 | return m; |
| 151 | } |
| 152 | |
| 153 | QVariant parseValue(const QString& str, QString::const_iterator& it, QChar terminator) |
| 154 | { |
| 155 | if (*it == '"') { |
| 156 | return parseString(str, ++it); |
| 157 | } else if (*it == '[') { |
| 158 | return parseArray(str, ++it); |
| 159 | } else if (*it == '{') { |
| 160 | return parseDict(str, ++it); |
| 161 | } |
| 162 | if (CLOSE_CHARS.contains(*it)) |
| 163 | return {}; |
| 164 | return parseKeyVal(str, it, terminator); |
| 165 | } |
| 166 | |
| 167 | } //namespace prvi |
| 168 | |
| 169 | QVariantMap parseElements(const QString& str) |
| 170 | { |
| 171 | auto it = str.cbegin(); |
| 172 | return priv::parseKeyVal(str, it); |
| 173 | } |
| 174 | |
| 175 | int strToInt(const QString& s, int def) |
| 176 | { |
| 177 | bool ok = false; |
| 178 | int v = s.toInt(&ok); |
| 179 | return ok? v:def; |
| 180 | } |
| 181 | |
| 182 | gdbmi::Variable gdbmi::Variable::parseMap(const QVariantMap &data) |
| 183 | { |
| 184 | gdbmi::Variable v; |
| 185 | v.name = data.value("name" ).toString(); |
| 186 | v.numChild = data.value("numchild" , 0).toInt(); |
| 187 | v.value = data.value("value" ).toString(); |
| 188 | v.type = data.value("type" ).toString(); |
| 189 | v.threadId = data.value("thread-id" ).toString(); |
| 190 | v.hasMore = data.value("has_more" , false).toBool(); |
| 191 | v.dynamic = data.value("dynamic" , false).toBool(); |
| 192 | v.displayhint = data.value("displayhint" ).toString(); |
| 193 | return v; |
| 194 | } |
| 195 | |
| 196 | gdbmi::Frame gdbmi::Frame::parseMap(const QVariantMap &data) |
| 197 | { |
| 198 | gdbmi::Frame f; |
| 199 | f.level = data.value("level" ).toInt(); |
| 200 | f.addr = data.value("addr" ).toString().toULongLong(nullptr, 16); |
| 201 | f.func = data.value("func" ).toString(); |
| 202 | f.file = data.value("file" ).toString(); |
| 203 | auto args = data.value("args" ).toMap(); |
| 204 | for (auto it = args.cbegin(); it != args.cend(); ++it) |
| 205 | f.params.insert(it.key(), it.value().toString()); |
| 206 | f.fullpath = data.value("fullname" ).toString(); |
| 207 | f.line = data.value("line" ).toInt(); |
| 208 | return f; |
| 209 | } |
| 210 | |
| 211 | gdbmi::Breakpoint gdbmi::Breakpoint::parseMap(const QVariantMap &data) |
| 212 | { |
| 213 | gdbmi::Breakpoint bp; |
| 214 | bp.number = data.value("number" ).toInt(); |
| 215 | bp.type = data.value("type" ).toString(); |
| 216 | bp.disp = strmap({{ "keep" , keep }, { "del" , del }}, data.value("disp" ).toString(), keep); |
| 217 | bp.enable = strmap({{ "y" , true }, { "n" , false }}, data.value("enable" ).toString(), true); |
| 218 | bp.addr = data.value("addr" ).toString().toULongLong(nullptr, 16); |
| 219 | bp.func = data.value("func" ).toString(); |
| 220 | bp.file = data.value("file" ).toString(); |
| 221 | bp.fullname = data.value("fullname" ).toString(); |
| 222 | bp.line = data.value("line" ).toInt(); |
| 223 | bp.threadGroups = data.value("thread-groups" ).toStringList(); |
| 224 | bp.times = data.value("times" ).toInt(); |
| 225 | bp.originalLocation = data.value("original-location" ).toString(); |
| 226 | return bp; |
| 227 | } |
| 228 | |
| 229 | gdbmi::Thread gdbmi::Thread::parseMap(const QVariantMap &data) |
| 230 | { |
| 231 | gdbmi::Thread t; |
| 232 | t.id = data.value("id" ).toInt(); |
| 233 | t.targetId = data.value("target-id" ).toString(); |
| 234 | t.details = data.value("details" ).toString(); |
| 235 | t.name = data.value("name" ).toString(); |
| 236 | t.state = strmap({{ "stopped" , Stopped }, { "running" , Running }}, data.value("state" ).toString(), Unknown); |
| 237 | t.frame = Frame::parseMap(data.value("frame" ).toMap()); |
| 238 | t.core = data.value("core" ).toInt(); |
| 239 | return t; |
| 240 | } |
| 241 | |
| 242 | gdbmi::AsyncContext::Reason gdbmi::AsyncContext::textToReason(const QString &s) |
| 243 | { |
| 244 | static const QMap<QString, Reason> map{ |
| 245 | { "breakpoint-hit" ,Reason::breakpointHhit }, // A breakpoint was reached. |
| 246 | { "watchpoint-trigger" ,Reason::watchpointTrigger }, // A watchpoint was triggered. |
| 247 | { "read-watchpoint-trigger" ,Reason::readWatchpointTrigger }, // A read watchpoint was triggered. |
| 248 | { "access-watchpoint-trigger" ,Reason::accessWatchpointTrigger }, // An access watchpoint was triggered. |
| 249 | { "function-finished" ,Reason::functionFinished }, // An -exec-finish or similar CLI command was accomplished. |
| 250 | { "location-reached" ,Reason::locationReached }, // An -exec-until or similar CLI command was accomplished. |
| 251 | { "watchpoint-scope" ,Reason::watchpointScope }, // A watchpoint has gone out of scope. |
| 252 | { "end-stepping-range" ,Reason::endSteppingRange }, // An -exec-next, -exec-next-instruction, -exec-step, -exec-step-instruction or similar CLI command was accomplished. |
| 253 | { "exited-signalled" ,Reason::exitedSignalled }, // The inferior exited because of a signal. |
| 254 | { "exited" ,Reason::exited }, // The inferior exited. |
| 255 | { "exited-normally" ,Reason::exitedNormally }, // The inferior exited normally. |
| 256 | { "signal-received" ,Reason::signalReceived }, // A signal was received by the inferior. |
| 257 | { "solib-event" ,Reason::solibEvent }, // The inferior has stopped due to a library being loaded or unloaded. This can happen when stop-on-solib-events (see Files) is set or when a catch load or catch unload catchpoint is in use (see Set Catchpoints). |
| 258 | { "fork" ,Reason::fork }, // The inferior has forked. This is reported when catch fork (see Set Catchpoints) has been used. |
| 259 | { "vfork" ,Reason::vfork }, // The inferior has vforked. This is reported in when catch vfork (see Set Catchpoints) has been used. |
| 260 | { "syscall-entry" ,Reason::syscallEntry }, // The inferior entered a system call. This is reported when catch syscall (see Set Catchpoints) has been used. |
| 261 | { "syscall-return" ,Reason::syscallReturn }, // The inferior returned from a system call. This is reported when catch syscall (see Set Catchpoints) has been used. |
| 262 | { "exec" ,Reason::exec }, // The inferior called exec. This is reported when catch exec (see Set Catchpoints) has been used. |
| 263 | }; |
| 264 | return map.value(s, Reason::Unknown); |
| 265 | } |
| 266 | |
| 267 | QString gdbmi::AsyncContext::reasonToText(gdbmi::AsyncContext::Reason r) |
| 268 | { |
| 269 | switch (r) { |
| 270 | case Reason::breakpointHhit : return "breakpoint-hit" ; |
| 271 | case Reason::watchpointTrigger : return "watchpoint-trigger" ; |
| 272 | case Reason::readWatchpointTrigger : return "read-watchpoint-trigger" ; |
| 273 | case Reason::accessWatchpointTrigger : return "access-watchpoint-trigger" ; |
| 274 | case Reason::functionFinished : return "function-finished" ; |
| 275 | case Reason::locationReached : return "location-reached" ; |
| 276 | case Reason::watchpointScope : return "watchpoint-scope" ; |
| 277 | case Reason::endSteppingRange : return "end-stepping-range" ; |
| 278 | case Reason::exitedSignalled : return "exited-signalled" ; |
| 279 | case Reason::exited : return "exited" ; |
| 280 | case Reason::exitedNormally : return "exited-normally" ; |
| 281 | case Reason::signalReceived : return "signal-received" ; |
| 282 | case Reason::solibEvent : return "solib-event" ; |
| 283 | case Reason::fork : return "fork" ; |
| 284 | case Reason::vfork : return "vfork" ; |
| 285 | case Reason::syscallEntry : return "syscall-entry" ; |
| 286 | case Reason::syscallReturn : return "syscall-return" ; |
| 287 | case Reason::exec : return "exec" ; |
| 288 | default: return "unknown" ; |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | gdbmi::Library gdbmi::Library::parseMap(const QVariantMap& data) { |
| 293 | gdbmi::Library l; |
| 294 | l.id = data.value("id" ).toString(); |
| 295 | l.targetName = data.value("target-name" ).toString(); |
| 296 | l.hostName = data.value("host-name" ).toString(); |
| 297 | l.symbolsLoaded = data.value("symbols-loaded" ).toString(); |
| 298 | auto ranges = data.value("ranges" ).toMap(); |
| 299 | l.ranges.fromRange = ranges.value("from" ).toString(); |
| 300 | l.ranges.toRange = ranges.value("to" ).toString(); |
| 301 | return l; |
| 302 | } |
| 303 | |
| 304 | gdbmi::Record gdbmi::Record::parseRecord(const QString& outputText) |
| 305 | { |
| 306 | QRegularExpressionMatch regMatch; |
| 307 | if ((regMatch = DISASSEMBLE_DATA_REG.match(outputText)).hasMatch()) { |
| 308 | return Record(RecordType::disassemble, |
| 309 | "data" , |
| 310 | outputText, |
| 311 | -1); |
| 312 | } else if ((regMatch = DISASSEMBLE_END_REG.match(outputText)).hasMatch()) { |
| 313 | return Record(RecordType::disassemble, |
| 314 | "end" , |
| 315 | QVariant(), |
| 316 | -1); |
| 317 | } else if ((regMatch = GDB_MI_NOTIFY.match(outputText)).hasMatch()) { |
| 318 | return Record(RecordType::notify, |
| 319 | regMatch.captured(2), |
| 320 | parseElements(regMatch.captured(3)), |
| 321 | strToInt(regMatch.captured(1), -1)); |
| 322 | } else if ((regMatch = GDB_MI_RESULT.match(outputText)).hasMatch()) { |
| 323 | return Record(RecordType::result, |
| 324 | regMatch.captured(2), |
| 325 | parseElements(regMatch.captured(3)), |
| 326 | strToInt(regMatch.captured(1), -1)); |
| 327 | } else if ((regMatch = GDB_MI_CONSOLE.match(outputText)).hasMatch()) { |
| 328 | return Record(RecordType::console, |
| 329 | outputText, |
| 330 | outputText, |
| 331 | -1); |
| 332 | } else if ((regMatch = GDB_MI_LOG.match(outputText)).hasMatch()) { |
| 333 | return Record(RecordType::log, |
| 334 | regMatch.captured(1), |
| 335 | regMatch.captured(0), |
| 336 | -1); |
| 337 | } else if ((regMatch = GDB_MI_TARGET.match(outputText)).hasMatch()) { |
| 338 | return Record(RecordType::target, |
| 339 | regMatch.captured(1), |
| 340 | regMatch.captured(0), |
| 341 | -1); |
| 342 | } else if ((regMatch = GDB_MI_FINISHED.match(outputText)).hasMatch()) { |
| 343 | return Record(RecordType::promt, {}, {}, -1); |
| 344 | } else { |
| 345 | return Record(RecordType::program, {}, outputText, -1); |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | } //namespace gdbmi |
| 350 | |
| 351 | |