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
9namespace 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
14const 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
19const auto GDB_MI_CONSOLE = QRegularExpression(R"re(~"(.*)")re",
20 QRegularExpression::MultilineOption |
21 QRegularExpression::DotMatchesEverythingOption);
22const auto GDB_MI_TARGET = QRegularExpression(R"re(@"(.*)")re",
23 QRegularExpression::MultilineOption |
24 QRegularExpression::DotMatchesEverythingOption);
25const 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).
30const auto GDB_MI_NOTIFY = QRegularExpression(R"(^(\d*)[*=](\S+?),(.*)$)",
31 QRegularExpression::MultilineOption |
32 QRegularExpression::NoPatternOption);
33// GDB/MI Finished
34const auto GDB_MI_FINISHED = QRegularExpression(R"(^\(gdb\)\s*$)",
35 QRegularExpression::MultilineOption |
36 QRegularExpression::NoPatternOption);
37
38const QString CLOSE_CHARS{"}]\""};
39
40const auto DISASSEMBLE_DATA_REG = QRegularExpression(R"(~\".+0x.+<\+.+>:.+\")",
41 QRegularExpression::MultilineOption |
42 QRegularExpression::NoPatternOption);
43const auto DISASSEMBLE_END_REG = QRegularExpression(R"(~\"End of assembler dump.)",
44 QRegularExpression::NoPatternOption);
45
46QString escapedText(const QString& s)
47{
48 return QString{s}.replace(QRegularExpression{R"(\\(.))"}, "\1");
49}
50
51template<typename T>
52static T strmap(const QMap<QString, T>& map, const QString& key, T defaultValue)
53{
54 return map.value(key, defaultValue);
55}
56
57namespace priv {
58
59QString::const_iterator& skipspaces(QString::const_iterator& it)
60{
61 while (it->isSpace())
62 ++it;
63 return it;
64}
65
66QString 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
81QString 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
94QString 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
107QVariantList parseArray(const QString& str, QString::const_iterator& it);
108QVariantMap parseKeyVal(const QString& str, QString::const_iterator& it, QChar terminator='\0');
109QVariantMap parseDict(const QString& str, QString::const_iterator& it);
110QVariant parseValue(const QString& str, QString::const_iterator& it, QChar terminator);
111
112QVariantList 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
126QVariantMap parseDict(const QString& str, QString::const_iterator& it)
127{
128 QVariantMap m = parseKeyVal(str, it, '}');
129 ++it;
130 return m;
131}
132
133QVariantMap 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
153QVariant 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
169QVariantMap parseElements(const QString& str)
170{
171 auto it = str.cbegin();
172 return priv::parseKeyVal(str, it);
173}
174
175int strToInt(const QString& s, int def)
176{
177 bool ok = false;
178 int v = s.toInt(&ok);
179 return ok? v:def;
180}
181
182gdbmi::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
196gdbmi::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
211gdbmi::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
229gdbmi::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
242gdbmi::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
267QString 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
292gdbmi::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
304gdbmi::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