1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "gdbdebugger.h"
6
7#include "debugmanager.h"
8
9#include <QTextStream>
10#include <QProcess>
11
12class GDBDebuggerPrivate {
13 friend class GDBDebugger;
14
15 QMap<int, gdbmi::Breakpoint> breakpoints;
16 QList<gdbmi::Frame> stackFrames;
17 QList<gdbmi::Thread> threadList;
18 QList<gdbmi::Variable> variableList;
19 std::atomic_bool inferiorRunning{false};
20 std::atomic_bool firstPromt{true};
21 QStringList assemblers;
22};
23
24GDBDebugger::GDBDebugger(QObject *parent)
25 : Debugger(parent)
26 , d(new GDBDebuggerPrivate())
27{
28 connect(this, &GDBDebugger::streamConsole, DebugManager::instance(), &DebugManager::streamConsole);
29 connect(this, &GDBDebugger::streamDebugInternal, DebugManager::instance(), &DebugManager::streamDebugInternal);
30 connect(this, &GDBDebugger::asyncStopped, DebugManager::instance(), &DebugManager::asyncStopped);
31 connect(this, &GDBDebugger::asyncExited, DebugManager::instance(), &DebugManager::asyncExited);
32 connect(this, &GDBDebugger::asyncRunning, DebugManager::instance(), &DebugManager::asyncRunning);
33 connect(this, &GDBDebugger::libraryLoaded, DebugManager::instance(), &DebugManager::libraryLoaded);
34 connect(this, &GDBDebugger::libraryUnloaded, DebugManager::instance(), &DebugManager::libraryUnloaded);
35 connect(this, &GDBDebugger::fireLocker, DebugManager::instance(), &DebugManager::fireLocker);
36 connect(this, &GDBDebugger::fireStackLocker, DebugManager::instance(), &DebugManager::fireStackLocker);
37 connect(this, &GDBDebugger::updateExceptResponse, DebugManager::instance(), &DebugManager::updateExceptResponse);
38 connect(this, &GDBDebugger::terminated, DebugManager::instance(), &DebugManager::terminated);
39 connect(this, &GDBDebugger::assemblerData, DebugManager::instance(), &DebugManager::assemblerData);
40}
41
42GDBDebugger::~GDBDebugger()
43{
44
45}
46
47QString GDBDebugger::program()
48{
49 return ("gdb");
50}
51
52QStringList GDBDebugger::preArguments()
53{
54 return QStringList{"-q", "-interpreter=mi"};
55}
56
57bool GDBDebugger::isInferiorRunning()
58{
59 return d->inferiorRunning;
60}
61
62QString GDBDebugger::quit()
63{
64 return ("-gdb-exit");
65}
66
67QString GDBDebugger::kill()
68{
69 return ("kill");
70}
71
72QString GDBDebugger::breakRemoveAll()
73{
74 return ("-break-delete");
75}
76
77QString GDBDebugger::breakInsert(const QString &path)
78{
79 return QString{"-break-insert %1"}.arg(path);
80}
81
82QString GDBDebugger::breakRemove(int bpid)
83{
84 return QString{"-break-delete %1"}.arg(bpid);
85}
86
87QString GDBDebugger::launchLocal()
88{
89 return ("-exec-run");
90}
91
92QString GDBDebugger::stackListFrames()
93{
94 return ("-stack-list-frames");
95}
96
97QString GDBDebugger::stackListVariables()
98{
99 return ("-stack-list-variables 1");
100}
101
102QString GDBDebugger::commandPause()
103{
104 return ("-exec-until");
105}
106
107QString GDBDebugger::commandContinue()
108{
109 return ("-exec-continue");
110}
111
112QString GDBDebugger::commandNext()
113{
114 return ("-exec-next");
115}
116
117QString GDBDebugger::commandStep()
118{
119 return ("-exec-step");
120}
121
122QString GDBDebugger::commandFinish()
123{
124 return ("-exec-finish");
125}
126
127QString GDBDebugger::threadInfo()
128{
129 return ("-thread-info");
130}
131
132QString GDBDebugger::threadSelect(const int threadId)
133{
134 return QString{"-thread-select %1"}.arg(threadId);
135}
136
137QString GDBDebugger::listSourceFiles()
138{
139 return ("-file-list-exec-source-files");
140}
141
142void GDBDebugger::parseBreakPoint(const QVariant& var)
143{
144 auto data = var.toMap();
145 auto bp = gdbmi::Breakpoint::parseMap(data.value("bkpt").toMap());
146 d->breakpoints.insert(bp.number, bp);
147 emit breakpointInserted(bp);
148}
149
150void GDBDebugger::removeBreakPoint(const int bpid)
151{
152 auto bp = d->breakpoints.value(bpid);
153 d->breakpoints.remove(bpid);
154 emit breakpointRemoved(bp);
155}
156
157void GDBDebugger::clearBreakPoint()
158{
159 d->breakpoints.clear();
160}
161
162QList<int> GDBDebugger::breakpointsForFile(const QString &filePath)
163{
164 QList<int> list;
165 for (auto it = d->breakpoints.cbegin(); it != d->breakpoints.cend(); ++it) {
166 auto& bp = it.value();
167 if (bp.fullname == filePath)
168 list.append(bp.number);
169 }
170 return list;
171}
172
173dap::array<dap::StackFrame> GDBDebugger::allStackframes()
174{
175 dap::array<dap::StackFrame> stackFrames;
176 for (const auto &frame : d->stackFrames) {
177 dap::Source source;
178 dap::StackFrame stackframe;
179 stackframe.id = frame.level;
180 stackframe.line = frame.line;
181 stackframe.column = 1;
182 stackframe.name = frame.func.toStdString();
183 auto address = "0x" + QString::number(frame.addr, 16);
184 stackframe.instructionPointerReference = address.toStdString();
185 source.name = frame.func.toStdString();
186 source.path = frame.fullpath.toStdString();
187 stackframe.source = source;
188 stackFrames.push_back(stackframe);
189 }
190 return stackFrames;
191}
192
193dap::array<dap::Thread> GDBDebugger::allThreadList()
194{
195 std::vector<dap::Thread> retThreads;
196 for (auto it : d->threadList) {
197 dap::Thread thread;
198 thread.id = it.id;
199 thread.name = it.name.toStdString();
200 retThreads.push_back(thread);
201 }
202 return retThreads;
203}
204
205dap::array<dap::Variable> GDBDebugger::allVariableList()
206{
207 dap::array<dap::Variable> variables;
208 for (const auto &var : d->variableList) {
209 dap::Variable variable;
210 variable.name = var.name.toStdString();
211 variable.type = var.type.toStdString();
212 variable.value = var.value.toStdString();
213 variables.push_back(variable);
214 }
215
216 return variables;
217}
218
219void GDBDebugger::handleOutputStreamText(const QString &streamText)
220{
221 QStringList textList;
222 QString temp;
223 do {
224 if (!streamText.contains("Command")) {
225 temp += streamText.split(":").last();
226 }
227
228 if (temp.contains("=thread-group-added") && temp.contains("\\(gdb\\)")) {
229 textList.append(temp);
230 temp.clear();
231 }
232 } while (streamText.contains("~\"done.\n"));
233
234 if (textList.size() > 0) {
235 emit streamDebugInternal(textList);
236 }
237}
238
239void GDBDebugger::handleOutputRecord(const QString &outputRecord)
240{
241 auto record = gdbmi::Record::parseRecord(outputRecord);
242
243 QString sOut;
244 QTextStream(&sOut) << "Response:" << outputRecord << "\n";
245 handleOutputStreamText(sOut);
246
247 switch (record.type) {
248 case gdbmi::Record::RecordType::notify:
249 {
250 parseNotifyData(record);
251 break;
252 }
253 case gdbmi::Record::RecordType::result:
254 {
255 parseResultData(record);
256 break;
257 }
258 case gdbmi::Record::RecordType::console:
259 break;
260 case gdbmi::Record::RecordType::target:
261 {
262 emit streamConsole(gdbmi::escapedText(record.message));
263 break;
264 }
265 case gdbmi::Record::RecordType::program:
266 {
267 emit streamConsole(record.payload.toString());
268 break;
269 }
270 case gdbmi::Record::RecordType::disassemble:
271 {
272 parseDisassembleData(record);
273 break;
274 }
275 default:
276 break;
277 }
278}
279
280void GDBDebugger::parseNotifyData(gdbmi::Record &record)
281{
282 // code is from https://github.com/martinribelotta/gdbfrontend
283 if (record.message == "stopped") {
284 // *stopped, reason="reason",thread-id="id",stopped-threads="stopped",core="core"
285 auto data = record.payload.toMap();
286 gdbmi::AsyncContext ctx;
287 ctx.reason = gdbmi::AsyncContext::textToReason(data.value("reason").toString());
288 ctx.threadId = data.value("thread-id").toString();
289 ctx.core = data.value("core").toInt();
290 ctx.frame = gdbmi::Frame::parseMap(data.value("frame").toMap());
291 d->inferiorRunning.store(false);
292 sendStoppedNotify(ctx);
293 } else if (record.message == "running") {
294 // *running,thread-id="thread"
295 auto data = record.payload.toMap();
296 auto thid = data.value("thread-id").toString();
297 d->inferiorRunning.store(true);
298 emit asyncRunning("gdb", thid);
299 } else if (record.message == "breakpoint-modified") {
300 // =breakpoint-modified,bkpt={...}
301 auto data = record.payload.toMap();
302 auto bp = gdbmi::Breakpoint::parseMap(data.value("bkpt").toMap());
303 d->breakpoints.insert(bp.number, bp);
304 emit breakpointModified(bp);
305 } else if (record.message == "breakpoint-created") {
306 // =breakpoint-created,bkpt={...}
307 auto data = record.payload.toMap();
308 auto bp = gdbmi::Breakpoint::parseMap(data.value("bkpt").toMap());
309 d->breakpoints.insert(bp.number, bp);
310 emit breakpointModified(bp);
311 } else if (record.message == "breakpoint-deleted") {
312 // =breakpoint-deleted,id=number
313 auto data = record.payload.toMap();
314 auto id = data.value("id").toInt();
315 auto bp = d->breakpoints.value(id);
316 d->breakpoints.remove(id);
317 emit breakpointRemoved(bp);
318 } else if (record.message == "thread-group-added") {
319 // =thread-group-added,id="id"
320 auto data = record.payload.toMap();
321 auto id = data.value("id").toInt();
322 gdbmi::Thread thid;
323 thid.id = id;
324 emit threadGroupAdded(thid);
325 } else if (record.message == "thread-group-removed") {
326 // =thread-group-removed,id="id"
327 auto data = record.payload.toMap();
328 auto id = data.value("id").toInt();
329 gdbmi::Thread thid;
330 thid.id = id;
331 emit threadGroupRemoved(thid);
332 } else if (record.message == "thread-group-started") {
333 // =thread-group-started,id="id",pid="pid"
334 auto data = record.payload.toMap();
335 auto id = data.value("id").toInt();
336 auto pid = data.value("pid").toInt();
337 gdbmi::Thread threadId;
338 gdbmi::Thread processId;
339 threadId.id = id;
340 processId.id = pid;
341 emit threadGroupStarted(threadId, processId);
342 } else if (record.message == "thread-group-exited") {
343 // =thread-gorup-exited,id="id"[,exit-code="code"]
344 auto data = record.payload.toMap();
345 gdbmi::Thread threadId;
346 threadId.id = data.value("id").toInt();
347 auto exitCode = data.value("exit-code").toString();
348 emit threadGroupExited(threadId, exitCode);
349 } else if (record.message == "library-loaded") {
350 // =library-loaded,...
351 auto data = record.payload.toMap();
352 auto ranges = data.value("ranges").toMap();
353 gdbmi::Library library;
354 library.id = data.value("id").toString();
355 library.targetName = data.value("target-name").toString();
356 library.hostName = data.value("host-name").toString();
357 library.symbolsLoaded = data.value("symbols-loaded").toString();
358 library.ranges.fromRange = ranges.value("fromRange").toString();
359 library.ranges.toRange = ranges.value("toRange").toString();
360 sendLibraryUnloadedNotify(library, false);
361 } else if (record.message == "library-unloaded") {
362 // =library-unloaded,...
363 auto data = record.payload.toMap();
364 gdbmi::Library library;
365 library.id = data.value("id").toString();
366 library.targetName = data.value("target-name").toString();
367 library.hostName = data.value("host-name").toString();
368 sendLibraryUnloadedNotify(library, false);
369 }
370}
371
372void GDBDebugger::sendStoppedNotify(const gdbmi::AsyncContext &ctx)
373{
374 if (gdbmi::AsyncContext::Reason::exitedNormally == ctx.reason
375 || gdbmi::AsyncContext::Reason::exitedSignalled == ctx.reason
376 || gdbmi::AsyncContext::Reason::Unknown == ctx.reason
377 || gdbmi::AsyncContext::Reason::exited == ctx.reason) {
378 dap::ExitedEvent exitedEvent;
379 emit asyncExited(exitedEvent);
380 }
381
382 dap::StoppedEvent stoppedEvent;
383 stoppedEvent.reason = ctx.reasonToText(ctx.reason).toStdString();
384 stoppedEvent.threadId = ctx.threadId.toInt();
385 stoppedEvent.allThreadsStopped = true;
386 stoppedEvent.line = ctx.frame.line;
387 dap::Source source;
388 source.name = ctx.frame.file.toStdString();
389 source.path = ctx.frame.fullpath.toStdString();
390 stoppedEvent.source = source;
391
392 emit asyncStopped(stoppedEvent);
393}
394
395void GDBDebugger::sendLibraryLoadedNotify(const gdbmi::Library &library, bool print)
396{
397 if (!print)
398 return;
399 dap::ModuleEvent moduleEvent;
400 moduleEvent.reason = "new";
401 dap::Module module;
402 module.id = library.id.toStdString();
403 module.name = library.targetName.toStdString();
404 module.path = library.hostName.toStdString();
405 module.symbolFilePath = library.hostName.toStdString();
406 moduleEvent.module = module;
407
408 emit libraryLoaded(moduleEvent);
409}
410
411void GDBDebugger::sendLibraryUnloadedNotify(const gdbmi::Library &library, bool print)
412{
413 if (!print)
414 return;
415 dap::ModuleEvent moduleEvent;
416 moduleEvent.reason = "remove";
417 dap::Module module;
418 module.id = library.id.toStdString();
419 module.name = library.targetName.toStdString();
420 module.path = library.hostName.toStdString();
421 module.symbolFilePath = library.hostName.toStdString();
422 moduleEvent.module = module;
423
424 emit libraryUnloaded(moduleEvent);
425}
426
427void GDBDebugger::parseResultData(gdbmi::Record &record)
428{
429 // code is from https://github.com/martinribelotta/gdbfrontend
430 if (record.message == "done" || record.message == "") {
431 foreach (QString key, record.payload.toMap().keys()) {
432 if (key == "frame") {
433 auto f = gdbmi::Frame::parseMap(record.payload.toMap().value("frame").toMap());
434 emit updateCurrentFrame(f);
435 } else if(key == "variables") {
436 // -stack-list-varablbes => Scopes Request
437 QList<gdbmi::Variable> variableList;
438 auto locals = record.payload.toMap().value("variables").toList();
439 for (const auto& e: locals)
440 variableList.append(gdbmi::Variable::parseMap(e.toMap()));
441 d->variableList = variableList;
442 emit updateLocalVariables(variableList);
443 emit fireLocker();
444 } else if(key == "threads") {
445 // -thread-info => Thread Request
446 QList<gdbmi::Thread> threadList;
447 auto data = record.payload.toMap();
448 auto threads = data.value("threads").toList();
449 auto currId = data.value("current-thread-id").toInt();
450 for (const auto& e: threads)
451 threadList.append(gdbmi::Thread::parseMap(e.toMap()));
452 d->threadList = threadList;
453 emit updateThreads(currId, threadList);
454 emit fireLocker();
455 } else if(key == "stack") {
456 // -stack-list-frames => StackTrace Reqeust
457 if (!d->inferiorRunning) {
458 QList<gdbmi::Frame> stackFrames;
459 auto stackTrace = record.payload.toMap().value("stack").toList().first().toMap().values("frame");
460 for (const auto& e: stackTrace) {
461 auto frame = gdbmi::Frame::parseMap(e.toMap());
462 stackFrames.prepend(frame);
463 }
464 d->stackFrames = stackFrames;
465 emit updateStackFrame(stackFrames);
466 }
467 emit fireStackLocker();
468 } else if(key == "bkpt") {
469 // -break-insert location
470 auto data = record.payload.toMap();
471 auto bp = gdbmi::Breakpoint::parseMap(data.value("bkpt").toMap());
472 d->breakpoints.insert(bp.number, bp);
473 emit breakpointInserted(bp);
474 }
475
476 emit updateExceptResponse(record.token, record.payload);
477 }
478 } else if (record.message == "connected") {
479 emit targetRemoteConnected();
480 } else if (record.message == "error") {
481 emit gdbError(gdbmi::escapedText(record.payload.toMap().value("msg").toString()));
482 } else if (record.message == "exit") {
483 d->firstPromt.store(false);
484 emit terminated();
485 }
486
487 emit result(record.token, record.message, record.payload);
488}
489
490QString GDBDebugger::disassemble(const QString &address)
491{
492 return QString{"disassemble /m %1"}.arg(address);
493}
494
495void GDBDebugger::parseDisassembleData(const gdbmi::Record &record)
496{
497 if (record.message == "data") {
498 d->assemblers.push_back(gdbmi::escapedText(record.payload.toString()));
499 } else if (record.message == "end") {
500 emit assemblerData(d->assemblers);
501 d->assemblers.clear();
502 }
503}
504
505