1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "dapdebugger.h"
6#include "runtimecfgprovider.h"
7#include "debugsession.h"
8#include "debugservice.h"
9#include "debuggersignals.h"
10#include "debuggerglobals.h"
11#include "debugmodel.h"
12#include "stackframe.h"
13#include "interface/appoutputpane.h"
14#include "interface/stackframemodel.h"
15#include "interface/stackframeview.h"
16#include "interface/messagebox.h"
17#include "interface/breakpointmodel.h"
18#include "event/eventreceiver.h"
19#include "common/common.h"
20#include "services/builder/builderservice.h"
21#include "services/option/optionmanager.h"
22#include "services/language/languageservice.h"
23
24#include <QDateTime>
25#include <QTextBlock>
26#include <QtWidgets/QVBoxLayout>
27#include <QtWidgets/QHBoxLayout>
28#include <QComboBox>
29#include <QLabel>
30#include <QDBusConnection>
31#include <QDBusMessage>
32#include <QFileInfo>
33
34#define PER_WAIT_MSEC (1000)
35#define MAX_WAIT_TIMES (10)
36
37/**
38 * @brief Debugger::Debugger
39 * For serial debugging service
40 */
41using namespace dap;
42using namespace DEBUG_NAMESPACE;
43using namespace dpfservice;
44
45class DebuggerPrivate
46{
47 friend class DAPDebugger;
48
49 QString activeProjectKitName;
50 dpfservice::ProjectInfo projectInfo;
51 QString currentOpenedFileName;
52 QString currentBuildUuid;
53 QString requestDAPPortUuid;
54 QString userKitName;
55
56 QSharedPointer<RunTimeCfgProvider> rtCfgProvider;
57 QSharedPointer<DEBUG::DebugSession> session;
58
59 dap::integer threadId = 0;
60
61 /**
62 * @brief interface objects.
63 */
64 OutputPane *outputPane = nullptr;
65
66 QWidget *stackPane = nullptr;
67 StackFrameView *stackView = nullptr;
68 StackFrameModel stackModel;
69 QComboBox *threadSelector = nullptr;
70
71 QTreeView *localsView = nullptr;
72 LocalTreeModel localsModel;
73
74 StackFrameView *breakpointView = nullptr;
75 BreakpointModel breakpointModel;
76
77 QPointer<QWidget> alertBox;
78 AbstractDebugger::RunState runState = AbstractDebugger::kNoRun;
79
80 std::atomic_bool isCustomDap = false;
81
82 QProcess backend;
83
84 QMultiMap<QString, int> bps;
85};
86
87DAPDebugger::DAPDebugger(QObject *parent)
88 : AbstractDebugger(parent)
89 , d(new DebuggerPrivate())
90{
91 qRegisterMetaType<OutputPane::OutputFormat>("OutputPane::OutputFormat");
92 qRegisterMetaType<StackFrameData>("StackFrameData");
93 qRegisterMetaType<StackFrames>("StackFrames");
94
95 qRegisterMetaType<IVariable>("IVariable");
96 qRegisterMetaType<IVariables>("IVariables");
97 qRegisterMetaType<dpf::Event>("dpf::Event");
98 qRegisterMetaType<RunState>("RunState");
99
100 d->session.reset(new DebugSession(debugService->getModel(), this));
101 connect(d->session.get(), &DebugSession::sigRegisterHandlers, this, &DAPDebugger::registerDapHandlers);
102 d->rtCfgProvider.reset(new RunTimeCfgProvider(this));
103
104 connect(debuggerSignals, &DebuggerSignals::receivedEvent, this, &DAPDebugger::handleEvents);
105
106 QDBusConnection sessionBus = QDBusConnection::sessionBus();
107 sessionBus.connect(QString(""),
108 "/path",
109 "com.deepin.unioncode.interface",
110 "dapport",
111 this, SLOT(slotReceivedDAPPort(QString, int, QString, QMap<QString, QVariant>)));
112
113 sessionBus.connect(QString(""),
114 "/path",
115 "com.deepin.unioncode.interface",
116 "output",
117 this, SLOT(slotOutputMsg(const QString&, const QString&)));
118
119 initializeView();
120
121 killBackend();
122 launchBackend();
123}
124
125DAPDebugger::~DAPDebugger()
126{
127 delete d->alertBox;
128 // all widgets in tabWidget will be deleted automatically.
129}
130
131QWidget *DAPDebugger::getOutputPane() const
132{
133 return d->outputPane;
134}
135
136QWidget *DAPDebugger::getStackPane() const
137{
138 return d->stackPane;
139}
140
141QWidget *DAPDebugger::getLocalsPane() const
142{
143 return d->localsView;
144}
145
146QWidget *DAPDebugger::getBreakpointPane() const
147{
148 return d->breakpointView;
149}
150
151void DAPDebugger::startDebug()
152{
153 updateRunState(kPreparing);
154 auto &ctx = dpfInstance.serviceContext();
155 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
156 if (service) {
157 auto generator = service->create<LanguageGenerator>(d->activeProjectKitName);
158 if (generator) {
159 if (generator->isNeedBuild()) {
160 d->currentBuildUuid = requestBuild();
161 } else {
162 prepareDebug();
163 }
164 }
165 }
166}
167
168void DAPDebugger::detachDebug()
169{
170}
171
172void DAPDebugger::interruptDebug()
173{
174 if (d->runState == kRunning) {
175 // Just use temporary parameters now, same for the back
176 d->session->pause(d->threadId);
177 }
178}
179
180void DAPDebugger::continueDebug()
181{
182 if (d->runState == kStopped) {
183 d->session->continueDbg(d->threadId);
184 editor.cleanRunning();
185 }
186}
187
188void DAPDebugger::abortDebug()
189{
190 if (d->runState == kRunning || d->runState == kStopped || d->runState == kCustomRunning) {
191 auto &ctx = dpfInstance.serviceContext();
192 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
193 if (service) {
194 QString kitName = (d->runState == kCustomRunning) ? d->userKitName : d->activeProjectKitName;
195 auto generator = service->create<LanguageGenerator>(kitName);
196 if (generator) {
197 if (generator->isStopDAPManually()) {
198 stopDAP();
199 } else {
200 d->session->terminate();
201 }
202 }
203 }
204 }
205}
206
207void DAPDebugger::restartDebug()
208{
209 if (d->runState == kStopped || d->runState == kRunning) {
210 auto &ctx = dpfInstance.serviceContext();
211 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
212 if (service) {
213 auto generator = service->create<LanguageGenerator>(d->activeProjectKitName);
214 if (generator) {
215 if (generator->isRestartDAPManually()) {
216 stopDAP();
217 prepareDebug();
218 } else {
219 d->session->restart();
220 }
221 }
222 }
223 }
224}
225
226void DAPDebugger::stepOver()
227{
228 if (d->runState == kStopped) {
229 d->session->next(d->threadId, undefined);
230 }
231}
232
233void DAPDebugger::stepIn()
234{
235 if (d->runState == kStopped) {
236 d->session->stepIn(d->threadId, undefined, undefined);
237 }
238}
239
240void DAPDebugger::stepOut()
241{
242 if (d->runState == kStopped) {
243 d->session->stepOut(d->threadId, undefined);
244 }
245}
246
247DAPDebugger::RunState DAPDebugger::getRunState() const
248{
249 return d->runState;
250}
251
252void DAPDebugger::addBreakpoint(const QString &filePath, int lineNumber)
253{
254 // update model here.
255 Internal::Breakpoint bp;
256 bp.filePath = filePath;
257 bp.fileName = QFileInfo(filePath).fileName();
258 bp.lineNumber = lineNumber;
259 d->breakpointModel.insertBreakpoint(bp);
260
261 // send to backend.
262 dap::array<IBreakpointData> rawBreakpoints;
263 IBreakpointData bpData;
264 bpData.id = QUuid::createUuid().toString().toStdString();
265 bpData.lineNumber = lineNumber;
266 bpData.enabled = true; // TODO(mozart):get from editor.
267 rawBreakpoints.push_back(bpData);
268
269 if (d->runState == kStopped || d->runState == kRunning) {
270 debugService->addBreakpoints(filePath, rawBreakpoints, d->session.get());
271 } else {
272 debugService->addBreakpoints(filePath, rawBreakpoints, undefined);
273 }
274}
275
276void DAPDebugger::removeBreakpoint(const QString &filePath, int lineNumber)
277{
278 // update model here.
279 Internal::Breakpoint bp;
280 bp.filePath = filePath;
281 bp.fileName = QFileInfo(filePath).fileName();
282 bp.lineNumber = lineNumber;
283 d->breakpointModel.removeBreakpoint(bp);
284
285 // send to backend.
286 if (d->runState == kStopped || d->runState == kRunning) {
287 debugService->removeBreakpoints(filePath, lineNumber, d->session.get());
288 } else {
289 debugService->removeBreakpoints(filePath, lineNumber, undefined);
290 }
291}
292
293bool DAPDebugger::getLocals(dap::integer frameId, IVariables *out)
294{
295 return d->session->getLocals(frameId, out);
296}
297
298void DAPDebugger::registerDapHandlers()
299{
300 dap::Session *dapSession = d->session.get()->getDapSession();
301 /*
302 * Process the only one reverse request.
303 */
304 dapSession->registerHandler([&](const RunInTerminalRequest &request) {
305 Q_UNUSED(request)
306 qInfo() << "\n--> recv : "
307 << "RunInTerminalRequest";
308 return RunInTerminalResponse();
309 });
310
311 /*
312 * Register events.
313 */
314 // This event indicates that the debug adapter is ready to accept configuration requests.
315 dapSession->registerHandler([&](const InitializedEvent &event) {
316 Q_UNUSED(event)
317 qInfo() << "\n--> recv : "
318 << "InitializedEvent";
319
320 if (d->isCustomDap) {
321 auto threads = d->session->fetchThreads(nullptr);
322 updateThreadList(-1, threads);
323 updateRunState(DAPDebugger::RunState::kCustomRunning);
324 } else {
325 d->session.get()->getRawSession()->setReadyForBreakpoints(true);
326 debugService->sendAllBreakpoints(d->session.get());
327
328 d->session.get()->getRawSession()->configurationDone().wait();
329
330 d->session->fetchThreads(nullptr);
331 updateRunState(DAPDebugger::RunState::kRunning);
332 }
333 });
334
335 // The event indicates that the execution of the debuggee has stopped due to some condition.
336 dapSession->registerHandler([&](const StoppedEvent &event) {
337 qInfo() << "\n--> recv : "
338 << "StoppedEvent";
339 qInfo() << "\n THREAD STOPPED. Reason : " << event.reason.c_str();
340
341 IRawStoppedDetails *details = new IRawStoppedDetails();
342 details->reason = event.reason;
343 details->description = event.description;
344 details->threadId = event.threadId;
345 details->text = event.text;
346 // details.totalFrames = event.;
347 details->allThreadsStopped = event.allThreadsStopped.value();
348 // details.framesErrorMessage = even;
349 details->hitBreakpointIds = event.hitBreakpointIds;
350 d->session->getStoppedDetails().push_back(details);
351
352 auto threads = d->session->fetchThreads(details);
353
354 int curThreadID = static_cast<int>(event.threadId.value(0));
355 updateThreadList(curThreadID, threads);
356
357 // ui focus on the active frame.
358 if (event.reason == "function breakpoint"
359 || event.reason == "breakpoint"
360 || event.reason == "step"
361 || event.reason == "breakpoint-hit"
362 || event.reason == "function-finished"
363 || event.reason == "end-stepping-range"
364 || event.reason == "signal-received") {
365 if (event.threadId) {
366 d->threadId = event.threadId.value(0);
367 switchCurrentThread(static_cast<int>(d->threadId));
368 }
369 updateRunState(DAPDebugger::RunState::kStopped);
370 } else if (event.reason == "exception") {
371 QString name;
372 if (event.description) {
373 name = event.description.value().c_str();
374 } else {
375 name = event.reason.c_str();
376 }
377 QString meaning;
378 if (event.text) {
379 meaning = event.text.value().c_str();
380 }
381
382 QMetaObject::invokeMethod(this, "showStoppedBySignalMessageBox",
383 Q_ARG(QString, meaning), Q_ARG(QString, name));
384
385 printOutput(tr("\nThe debugee has Terminated.\n"), OutputPane::OutputFormat::NormalMessage);
386
387 updateRunState(kNoRun);
388 }
389 });
390
391 // The event indicates that the execution of the debuggee has continued.
392 // session->registerHandler([&](const ContinuedEvent &event){
393 // allThreadsContinued = event.allThreadsContinued;
394 // Q_UNUSED(event)
395 // qInfo() << "\n--> recv : " << "ContinuedEvent";
396 // });
397
398 // The event indicates that the debuggee has exited and returns its exit code.
399 dapSession->registerHandler([&](const ExitedEvent &event) {
400 Q_UNUSED(event)
401 qInfo() << "\n--> recv : "
402 << "ExitedEvent";
403 printOutput(tr("The debugee has Exited.\n"), OutputPane::OutputFormat::NormalMessage);
404 updateRunState(kNoRun);
405 });
406
407 // The event indicates that debugging of the debuggee has terminated.
408 // This does not mean that the debuggee itself has exited.
409 dapSession->registerHandler([&](const TerminatedEvent &event) {
410 Q_UNUSED(event)
411 qInfo() << "\n--> recv : "
412 << "TerminatedEvent";
413 printOutput(tr("\nThe debugee has Terminated.\n"), OutputPane::OutputFormat::NormalMessage);
414 updateRunState(kNoRun);
415 });
416
417 // The event indicates that a thread has started or exited.
418 dapSession->registerHandler([&](const ThreadEvent &event) {
419 Q_UNUSED(event)
420 qInfo() << "\n--> recv : "
421 << "ThreadEvent";
422 });
423
424 // The event indicates that the target has produced some output.
425 dapSession->registerHandler([&](const OutputEvent &event) {
426 Q_UNUSED(event)
427 qInfo() << "\n--> recv : "
428 << "OutputEvent\n"
429 << "content : " << event.output.c_str();
430
431 if (event.category) {
432 if (event.category.value() == "assembler") {
433 QString output = event.output.c_str();
434 handleAssemble(output);
435 return;
436 }
437 }
438
439 OutputPane::OutputFormat format = OutputPane::OutputFormat::NormalMessage;
440 if (event.category) {
441 dap::string category = event.category.value();
442 if (category == "stdout") {
443 format = OutputPane::OutputFormat::StdOut;
444 } else if (category == "stderr") {
445 format = OutputPane::OutputFormat::StdErr;
446 } else {
447 format = OutputPane::OutputFormat::LogMessage;
448 }
449 }
450
451 QString output = event.output.c_str();
452 if (output.contains("received signal")
453 || output.contains("Program")) {
454 format = OutputPane::OutputFormat::StdErr;
455 }
456 printOutput(output, format);
457 });
458
459 // The event indicates that some information about a breakpoint has changed.
460 dapSession->registerHandler([&](const BreakpointEvent &event) {
461 Q_UNUSED(event)
462 qInfo() << "\n--> recv : "
463 << "BreakpointEvent";
464 });
465
466 // The event indicates that some information about a module has changed.
467 dapSession->registerHandler([&](const ModuleEvent &event) {
468 Q_UNUSED(event)
469 qInfo() << "\n--> recv : "
470 << "ModuleEvent";
471 });
472
473 // The event indicates that some source has been added, changed,
474 // or removed from the set of all loaded sources.
475 dapSession->registerHandler([&](const LoadedSourceEvent &event) {
476 Q_UNUSED(event)
477 qInfo() << "\n--> recv : "
478 << "LoadedSourceEvent";
479 });
480
481 // The event indicates that the debugger has begun debugging a new process.
482 dapSession->registerHandler([&](const ProcessEvent &event) {
483 Q_UNUSED(event)
484 qInfo() << "\n--> recv : "
485 << "ProcessEvent";
486 });
487
488 // // The event indicates that one or more capabilities have changed.
489 // session->registerHandler([&](const CapabilitiesEvent &event){
490 // Q_UNUSED(event)
491 // qInfo() << "\n--> recv : " << "CapabilitiesEvent";
492 // });
493
494 dapSession->registerHandler([&](const ProgressStartEvent &event) {
495 Q_UNUSED(event)
496 qInfo() << "\n--> recv : "
497 << "ProgressStartEvent";
498 });
499
500 dapSession->registerHandler([&](const ProgressUpdateEvent &event) {
501 Q_UNUSED(event)
502 qInfo() << "\n--> recv : "
503 << "ProgressUpdateEvent";
504 });
505
506 dapSession->registerHandler([&](const ProgressEndEvent &event) {
507 Q_UNUSED(event)
508 qInfo() << "\n--> recv : "
509 << "ProgressEndEvent";
510 });
511
512 // This event signals that some state in the debug adapter has changed
513 // and requires that the client needs to re-render the data snapshot previously requested.
514 dapSession->registerHandler([&](const InvalidatedEvent &event) {
515 Q_UNUSED(event)
516 qInfo() << "\n--> recv : "
517 << "InvalidatedEvent";
518 });
519}
520
521void DAPDebugger::handleEvents(const dpf::Event &event)
522{
523 QString topic = event.topic();
524 QString data = event.data().toString();
525 if (topic == T_BUILDER) {
526 if (data == D_BUILD_STATE) {
527 int state = event.property(P_STATE).toInt();
528 BuildCommandInfo commandInfo = qvariant_cast<BuildCommandInfo>(event.property(P_ORIGINCMD));
529 if (commandInfo.uuid == d->currentBuildUuid) {
530 int buildSuccess = 0;
531 if (state == buildSuccess && d->runState == kPreparing)
532 start();
533 }
534 }
535 }
536
537 if (event.data() == debugger.prepareDebugDone.name) {
538 bool succeed = event.property(debugger.prepareDebugDone.pKeys[0]).toBool();
539 QString errorMsg = event.property(debugger.prepareDebugDone.pKeys[1]).toString();
540 if (!succeed) {
541 printOutput(errorMsg, OutputPane::ErrorMessage);
542 updateRunState(kPreparing);
543 } else {
544 auto &ctx = dpfInstance.serviceContext();
545 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
546 if (service) {
547 auto generator = service->create<LanguageGenerator>(d->activeProjectKitName);
548 if (generator) {
549 QMap<QString, QVariant> param = generator->getDebugArguments(getActiveProjectInfo(), d->currentOpenedFileName);
550 requestDebugPort(param, d->activeProjectKitName, false);
551 }
552 }
553 }
554 } else if (event.data() == debugger.prepareDebugProgress.name) {
555 printOutput(event.property(debugger.prepareDebugProgress.pKeys[0]).toString());
556 } else if (event.data() == project.activedProject.name) {
557 getActiveProjectInfo() = qvariant_cast<ProjectInfo>(event.property(project.activedProject.pKeys[0]));
558 d->activeProjectKitName = getActiveProjectInfo().kitName();
559 updateRunState(kNoRun);
560 } else if (event.data() == project.createdProject.name) {
561 getActiveProjectInfo() = qvariant_cast<ProjectInfo>(event.property(project.createdProject.pKeys[0]));
562 d->activeProjectKitName = getActiveProjectInfo().kitName();
563 updateRunState(kNoRun);
564 } else if (event.data() == project.deletedProject.name) {
565 d->activeProjectKitName.clear();
566 updateRunState(kNoRun);
567 } else if (event.data() == editor.switchedFile.name) {
568 QString filePath = event.property(editor.switchedFile.pKeys[0]).toString();
569 if (d->currentOpenedFileName != filePath) {
570 d->currentOpenedFileName = filePath;
571 }
572 } else if (event.data() == editor.openedFile.name) {
573 QString filePath = event.property(editor.switchedFile.pKeys[0]).toString();
574 d->currentOpenedFileName = filePath;
575 if (d->bps.count(filePath)) {
576 QList<int> lines = d->bps.values(filePath);
577 for (int line: lines) {
578 editor.addDebugPoint(filePath, line);
579 }
580 }
581 } else if (event.data() == editor.closedFile.name) {
582 QString filePath = event.property(editor.switchedFile.pKeys[0]).toString();
583 if (d->currentOpenedFileName == filePath) {
584 d->currentOpenedFileName.clear();
585 }
586 } else if (event.data() == editor.addadDebugPoint.name) {
587 QString filePath = event.property(editor.addadDebugPoint.pKeys[0]).toString();
588 int line = event.property(editor.addadDebugPoint.pKeys[1]).toInt();
589 d->bps.insert(filePath, line);
590 addBreakpoint(filePath, line);
591 } else if (event.data() == editor.removedDebugPoint.name) {
592 QString filePath = event.property(editor.removeDebugPoint.pKeys[0]).toString();
593 int line = event.property(editor.removeDebugPoint.pKeys[1]).toInt();
594 d->bps.remove(filePath, line);
595 removeBreakpoint(filePath, line);
596 }
597}
598
599void DAPDebugger::printOutput(const QString &content, OutputPane::OutputFormat format)
600{
601 QMetaObject::invokeMethod(this, "synPrintOutput", Q_ARG(QString, content), Q_ARG(OutputPane::OutputFormat, format));
602}
603
604void DAPDebugger::synPrintOutput(const QString &content, OutputPane::OutputFormat format)
605{
606 QString outputContent = content;
607 if (format == OutputPane::OutputFormat::NormalMessage) {
608 QTextDocument *doc = d->outputPane->document();
609 QTextBlock tb = doc->lastBlock();
610 QString lastLineText = tb.text();
611 QString prefix = "\n";
612 if (lastLineText.isEmpty()) {
613 prefix = "";
614 }
615 QDateTime curDatetime = QDateTime::currentDateTime();
616 QString time = curDatetime.toString("hh:mm:ss");
617 outputContent = prefix + time + ":" + content + "\n";
618 }
619 OutputPane::AppendMode mode = OutputPane::AppendMode::Normal;
620 d->outputPane->appendText(outputContent, format, mode);
621}
622
623void DAPDebugger::handleFrames(const StackFrames &stackFrames)
624{
625 d->stackModel.setFrames(stackFrames);
626
627 auto curFrame = d->stackModel.currentFrame();
628 if (curFrame.line == -1) {
629 // none of frame in model.
630 return;
631 }
632
633 if (QFileInfo(curFrame.file).exists()) {
634 editor.runningToLine(curFrame.file, curFrame.line);
635 } else {
636 if (curFrame.address.isEmpty()) {
637 disassemble(curFrame.address);
638 }
639 }
640
641 // update local variables.
642 IVariables locals;
643 getLocals(curFrame.frameId, &locals);
644 d->localsModel.setDatas(locals);
645}
646
647void DAPDebugger::updateThreadList(int curr, const dap::array<dap::Thread> &threads)
648{
649 d->threadSelector->clear();
650 int currIdx = -1;
651 for (const auto& e: threads) {
652 QString itemText = "#" + QString::number(e.id) + " " + e.name.c_str();
653 d->threadSelector->addItem(itemText);
654 if (curr == e.id)
655 currIdx = d->threadSelector->count() - 1;
656 }
657 if (currIdx != -1)
658 d->threadSelector->setCurrentIndex(currIdx);
659 else if (!threads.empty()) {
660 d->threadSelector->setCurrentIndex(0);
661 int threadId = static_cast<int>(threads.at(0).id);
662 switchCurrentThread(threadId);
663 }
664}
665
666void DAPDebugger::switchCurrentThread(int threadId)
667{
668 auto thread = d->session->getThread(threadId);
669 if (thread) {
670 thread.value()->fetchCallStack();
671 auto stacks = thread.value()->getCallStack();
672 StackFrames frames;
673 int level = 0;
674 for (auto it : stacks) {
675 // TODO(mozart):send to ui.
676 StackFrameData sf;
677 sf.level = std::to_string(level++).c_str();
678 sf.function = it.name.c_str();
679 if (it.source) {
680 sf.file = it.source.value().path ? it.source.value().path->c_str() : "";
681 } else {
682 sf.file = "No file found.";
683 }
684
685 if (it.moduleId) {
686 auto v = it.moduleId.value();
687 if (v.is<dap::integer>()) {
688 // TODO(mozart)
689 }
690 }
691
692 sf.line = static_cast<qint32>(it.line);
693 sf.address = it.instructionPointerReference ? it.instructionPointerReference.value().c_str() : "";
694 sf.frameId = it.id;
695 frames.push_back(sf);
696 }
697 // emit debuggerSignals->processStackFrames(frames);
698 handleFrames(frames);
699 }
700}
701
702bool DAPDebugger::showStoppedBySignalMessageBox(QString meaning, QString name)
703{
704 if (d->alertBox)
705 return false;
706
707 if (name.isEmpty())
708 name = ' ' + tr("<Unknown>", "name") + ' ';
709 if (meaning.isEmpty())
710 meaning = ' ' + tr("<Unknown>", "meaning") + ' ';
711 const QString msg = tr("<p>The inferior stopped because it received a "
712 "signal from the operating system.<p>"
713 "<table><tr><td>Signal name : </td><td>%1</td></tr>"
714 "<tr><td>Signal meaning : </td><td>%2</td></tr></table>")
715 .arg(name, meaning);
716
717 d->alertBox = Internal::information(tr("Signal Received"), msg);
718 return true;
719}
720
721void DAPDebugger::slotFrameSelected(const QModelIndex &index)
722{
723 Q_UNUSED(index)
724 auto curFrame = d->stackModel.currentFrame();
725
726 if (QFileInfo(curFrame.file).exists()) {
727 editor.jumpToLine(curFrame.file, curFrame.line);
728 } else {
729 if (!curFrame.address.isEmpty()) {
730 disassemble(curFrame.address);
731 }
732 }
733
734 // update local variables.
735 IVariables locals;
736 getLocals(curFrame.frameId, &locals);
737 d->localsModel.setDatas(locals);
738}
739
740void DAPDebugger::slotBreakpointSelected(const QModelIndex &index)
741{
742 Q_UNUSED(index);
743 auto curBP = d->breakpointModel.currentBreakpoint();
744 editor.jumpToLine(curBP.filePath, curBP.lineNumber);
745}
746
747void DAPDebugger::initializeView()
748{
749 // initialize output pane.
750 d->outputPane = OutputPane::instance();
751
752 // initialize stack monitor pane.
753 d->stackPane = new QWidget;
754 QVBoxLayout *vLayout = new QVBoxLayout(d->stackPane);
755 d->stackPane->setLayout(vLayout);
756
757 d->stackView = new StackFrameView();
758 d->stackView->setModel(d->stackModel.model());
759
760 d->threadSelector = new QComboBox(d->stackPane);
761 d->threadSelector->setMinimumWidth(200);
762 connect(d->threadSelector, QOverload<const QString &>::of(&QComboBox::activated), this, &DAPDebugger::currentThreadChanged);
763
764 QHBoxLayout *hLayout = new QHBoxLayout(d->stackPane);
765 hLayout->setAlignment(Qt::AlignLeft);
766 QLabel *label = new QLabel(tr("Threads:"), d->stackPane);
767 hLayout->addWidget(label);
768 hLayout->addWidget(d->threadSelector);
769
770 vLayout->addLayout(hLayout);
771 vLayout->addWidget(d->stackView);
772
773 // intialize breakpint pane.
774 d->breakpointView = new StackFrameView();
775 d->breakpointView->setModel(d->breakpointModel.model());
776
777 d->localsView = new QTreeView();
778 d->localsView->setModel(&d->localsModel);
779 QStringList headers { "name", "value"/*, "reference" */};
780 d->localsModel.setHeaders(headers);
781
782 connect(d->stackView, &QTreeView::doubleClicked, this, &DAPDebugger::slotFrameSelected);
783 connect(d->breakpointView, &QTreeView::doubleClicked, this, &DAPDebugger::slotBreakpointSelected);
784}
785
786void DAPDebugger::exitDebug()
787{
788 // Change UI.
789 editor.cleanRunning({});
790 d->localsView->hide();
791
792 d->localsModel.clear();
793 d->stackModel.removeAll();
794
795 d->threadId = 0;
796
797 d->threadSelector->clear();
798}
799
800void DAPDebugger::updateRunState(DAPDebugger::RunState state)
801{
802 if (d->runState != state) {
803 d->runState = state;
804 switch (state) {
805 case kNoRun:
806 exitDebug();
807 break;
808 case kRunning:
809 case kCustomRunning:
810 QMetaObject::invokeMethod(d->localsView, "show");
811 break;
812 case kStopped:
813 break;
814 default:
815 // do nothing.
816 break;
817 }
818 emit runStateChanged(d->runState);
819 }
820}
821
822QString DAPDebugger::requestBuild()
823{
824 QString buildUuid;
825 auto &ctx = dpfInstance.serviceContext();
826 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
827 if (service) {
828 auto generator = service->create<LanguageGenerator>(d->activeProjectKitName);
829 if (generator) {
830 buildUuid = generator->build(getActiveProjectInfo().workspaceFolder());
831 }
832 }
833
834 return buildUuid;
835}
836
837void DAPDebugger::start()
838{
839 auto &ctx = dpfInstance.serviceContext();
840 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
841 if (service) {
842 auto generator = service->create<LanguageGenerator>(d->activeProjectKitName);
843 if (generator) {
844 if (!generator->isTargetReady()) {
845 printOutput(tr("Please build first.\n Build : Ctrl + B"), OutputPane::ErrorMessage);
846 return;
847 }
848 prepareDebug();
849 }
850 }
851}
852
853void DAPDebugger::prepareDebug()
854{
855 auto runState = getRunState();
856 if (runState == kRunning) {
857 printOutput(tr("Is preparing dependence, please waiting for a moment"));
858 return;
859 }
860
861 auto &ctx = dpfInstance.serviceContext();
862 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
863 if (service) {
864 auto generator = service->create<LanguageGenerator>(d->activeProjectKitName);
865 if (generator) {
866 updateRunState(kStart);
867 QString retMsg;
868 QMap<QString, QVariant> param = generator->getDebugArguments(getActiveProjectInfo(), d->currentOpenedFileName);
869 bool ret = generator->prepareDebug(param, retMsg);
870 if (!ret) {
871 printOutput(retMsg, OutputPane::ErrorMessage);
872 updateRunState(kPreparing);
873 } else if (!generator->isAnsyPrepareDebug()) {
874 requestDebugPort(param, d->activeProjectKitName, false);
875 }
876 }
877 }
878}
879
880bool DAPDebugger::requestDebugPort(const QMap<QString, QVariant> &param, const QString &kitName, bool customDap)
881{
882 if (d->runState == kRunning) {
883 printOutput(tr("Is getting the dap port, please waiting for a moment"));
884 return false;
885 }
886
887 auto &ctx = dpfInstance.serviceContext();
888 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
889 if (service) {
890 auto generator = service->create<LanguageGenerator>(kitName);
891 if (generator) {
892 d->isCustomDap = customDap;
893 QString retMsg;
894 d->requestDAPPortUuid = QUuid::createUuid().toString();
895 printOutput(tr("Requesting debug port..."));
896 if (!generator->requestDAPPort(d->requestDAPPortUuid, param, retMsg)) {
897 printOutput(retMsg, OutputPane::ErrorMessage);
898 stopWaitingDebugPort();
899 return false;
900 }
901 }
902 }
903
904 return true;
905}
906
907void DAPDebugger::stopDAP()
908{
909 updateRunState(kNoRun);
910 d->session->closeSession();
911}
912
913void DAPDebugger::slotReceivedDAPPort(const QString &uuid, int port, const QString &kitName, const QMap<QString, QVariant> &param)
914{
915 if (d->requestDAPPortUuid == uuid)
916 launchSession(port, param, kitName);
917}
918
919void DAPDebugger::slotOutputMsg(const QString &title, const QString &msg)
920{
921 OutputPane::OutputFormat format = OutputPane::OutputFormat::Debug;
922 if (title == "stdErr") {
923 format = OutputPane::OutputFormat::StdErr;
924 } else if (title == "stdOut") {
925 format = OutputPane::OutputFormat::StdOut;
926 } else if (title == "normal") {
927 format = OutputPane::OutputFormat::NormalMessage;
928 }
929 bool isDetail = dpfGetService(ProjectService)->getActiveProjectInfo().detailInformation();
930 if (isDetail || title == "stdErr") {
931 printOutput(msg, format);
932 }
933}
934
935void DAPDebugger::launchBackend()
936{
937 // launch backend by client.
938 if (d->backend.isOpen())
939 return;
940
941 QString toolPath = CustomPaths::global(CustomPaths::Tools);
942 QString backendPath = toolPath + QDir::separator() + "debugadapter";
943
944 d->backend.setProgram(backendPath);
945 d->backend.start();
946 d->backend.waitForStarted();
947}
948
949void DAPDebugger::killBackend()
950{
951 // TODO(logan):backend not support re-connect yet,
952 // so kill it when client launched.
953 // those code will be removed when backend got modified.
954 QProcess::execute("killall -9 debugadapter");
955}
956
957void DAPDebugger::stopWaitingDebugPort()
958{
959 updateRunState(kPreparing);
960}
961
962void DAPDebugger::launchSession(int port, const QMap<QString, QVariant> &param, const QString &kitName)
963{
964 if (!port) {
965 printOutput(tr("\nThe dap port is not ready, please retry.\n"), OutputPane::OutputFormat::ErrorMessage);
966 return;
967 }
968
969 printOutput(tr("Debugging starts"));
970 QString launchTip = QString("Launch dap session with port %1 ...")
971 .arg(port);
972 printOutput(launchTip);
973
974 auto iniRequet = d->rtCfgProvider->initalizeRequest();
975 bool bSuccess = d->session->initialize(d->rtCfgProvider->ip(),
976 port,
977 iniRequet);
978
979 // Launch debuggee.
980 if (bSuccess) {
981 auto &ctx = dpfInstance.serviceContext();
982 LanguageService *service = ctx.service<LanguageService>(LanguageService::name());
983 if (service) {
984 auto generator = service->create<LanguageGenerator>(kitName);
985 if (generator) {
986 if (generator->isLaunchNotAttach()) {
987 dap::LaunchRequest request = generator->launchDAP(param);
988 bSuccess &= d->session->launch(request);
989 } else {
990 dap::AttachRequest request = generator->attachDAP(port, param);
991 bSuccess &= d->session->attach(request);
992 }
993 }
994 } else
995 bSuccess &= false;
996 }
997
998 if (!bSuccess) {
999 qCritical() << "startDebug failed!";
1000 } else {
1001 debugService->getModel()->clear();
1002 debugService->getModel()->addSession(d->session.get());
1003 }
1004}
1005
1006void DAPDebugger::currentThreadChanged(const QString &text)
1007{
1008 QtConcurrent::run([&]() { // run in thread to avoid blocked when get variables.
1009 QStringList l = text.split("#");
1010 QString threadNumber = l.last().split(" ").first();
1011 switchCurrentThread(threadNumber.toInt());
1012 });
1013}
1014
1015bool DAPDebugger::runCoredump(const QString &target, const QString &core, const QString &kit)
1016{
1017 launchBackend();
1018
1019 updateRunState(kStart);
1020 updateRunState(kNoRun);
1021 printOutput(tr("Start debugging coredump file: ") + core + " with " + target);
1022
1023 if (target.isEmpty() || !QFileInfo(target).isFile()) {
1024 QString tipMsg = tr("The coredump target file is error: ") + target;
1025 printOutput(tipMsg, OutputPane::ErrorMessage);
1026 return false;
1027 }
1028
1029 if (core.isEmpty() || !QFileInfo(core).isFile()) {
1030 QString tipMsg = tr("The coredump file is error: ") + core;
1031 printOutput(tipMsg, OutputPane::ErrorMessage);
1032 return false;
1033 }
1034
1035 QMap<QString, QVariant> param;
1036 param.insert("targetPath", target);
1037 param.insert("arguments", QStringList{core});
1038 d->userKitName = kit;
1039
1040 return requestDebugPort(param, d->userKitName, true);
1041}
1042
1043void DAPDebugger::disassemble(const QString &address)
1044{
1045 if (d->runState == kCustomRunning) {
1046 d->session->disassemble(address.toStdString());
1047 }
1048}
1049
1050void DAPDebugger::handleAssemble(const QString &content)
1051{
1052 if (content.isEmpty())
1053 return;
1054
1055 QString assemblerPath = CustomPaths::user(CustomPaths::Flags::Configures) +
1056 QDir::separator() + QString("Disassembler");
1057
1058 QFile file(assemblerPath);
1059 if (file.open(QIODevice::WriteOnly)) {
1060 QTextStream stream(&file);
1061 stream << content;
1062 file.close();
1063 // avoid load manually.
1064 editor.setModifiedAutoReload(assemblerPath, true);
1065
1066 editor.jumpToLine(assemblerPath, 0);
1067 }
1068}
1069
1070ProjectInfo DAPDebugger::getActiveProjectInfo() const
1071{
1072 return dpfGetService(ProjectService)->getActiveProjectInfo();
1073}
1074