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 | |