1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "debugmanager.h"
6#include "locker.h"
7
8#include "debugger/debugger.h"
9#include "debugger/gdbmi/gdbdebugger.h"
10#include "debugger/javascript/jsdebugger.h"
11
12#include <QProcess>
13
14class DebugManagerPrivate {
15 friend class DebugManager;
16
17 QSharedPointer<Debugger> debugger;
18 QSharedPointer<QProcess> process{nullptr};
19 QString tempBuffer;
20 ConditionLockEx locker;
21 ConditionLockEx stacklocker;
22 QHash<int, DebugManager::ResponseEntry> resposeExpected;
23 QStringList arguments;
24 int tokenCounter = 0;
25};
26
27DebugManager::DebugManager(QObject *parent)
28 : QObject(parent)
29 , d(new DebugManagerPrivate())
30{
31
32}
33
34DebugManager::~DebugManager()
35{
36 if (d) {
37 delete d;
38 }
39}
40
41DebugManager *DebugManager::instance()
42{
43 static DebugManager ins;
44 return &ins;
45}
46
47void DebugManager::initProcess()
48{
49 d->process.reset(new QProcess());
50
51 connect(d->process.data(), &QProcess::readyReadStandardOutput, [this]() {
52 QString output = d->process->readAllStandardOutput();
53 for (const auto& c: output)
54 switch (c.toLatin1()) {
55 case '\r':
56 case '\n':
57 {
58 d->tempBuffer.append(c);
59 d->debugger->handleOutputRecord(d->tempBuffer);
60 d->tempBuffer.clear();
61 break;
62 }
63 default:
64 {
65 d->tempBuffer.append(c);
66 break;
67 }
68 }
69 });
70 connect(d->process.data(), &QProcess::readyReadStandardError, [this]() {
71 QString output = d->process->readAllStandardError();
72 for (const auto& c: output)
73 switch (c.toLatin1()) {
74 case '\r':
75 case '\n':
76 {
77 d->tempBuffer.append(c);
78 d->debugger->handleOutputRecord(d->tempBuffer);
79 d->tempBuffer.clear();
80 break;
81 }
82 default:
83 {
84 d->tempBuffer.append(c);
85 break;
86 }
87 }
88 });
89
90 connect(d->process.data(), &QProcess::started, [this]() {
91 d->tokenCounter = 0;
92 d->tempBuffer.clear();
93 d->resposeExpected.clear();
94 });
95
96 connect(d->process.data(), &QProcess::started,
97 this, &DebugManager::dbgProcessStarted);
98 connect(d->process.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
99 this, &DebugManager::dbgProcessFinished);
100}
101
102void DebugManager::initDebugger(const QString &program, const QStringList &arguments)
103{
104 if (d->process) {
105 d->process->terminate();
106 }
107
108 d->arguments = arguments;
109
110 if (program.contains("gdb")) {
111 d->debugger.reset(new GDBDebugger());
112 } else if (program.contains("jsdbg")) {
113 d->debugger.reset(new JSDebugger());
114 }
115}
116
117qint64 DebugManager::getProcessId()
118{
119 return d->process->processId();
120}
121
122void DebugManager::waitLocker()
123{
124 d->locker.wait();
125}
126
127void DebugManager::fireLocker()
128{
129 d->locker.fire();
130}
131
132void DebugManager::fireStackLocker()
133{
134 d->stacklocker.fire();
135}
136
137bool DebugManager::isExecuting() const
138{
139 return d->process->state() != QProcess::NotRunning;
140}
141
142void DebugManager::execute()
143{
144 initProcess();
145
146 foreach (auto arg, d->debugger->preArguments()) {
147 d->arguments.prepend(arg);
148 }
149 d->process->setArguments(d->arguments);
150 d->process->setProgram(d->debugger->program());
151 d->process->start();
152}
153
154bool DebugManager::command(const QString &cmd)
155{
156 if (d->debugger->isInferiorRunning())
157 return false;
158
159 auto tokStr = QString{"%1"}.arg(d->tokenCounter, 6, 10, QChar{'0'});
160 auto line = QString{"%1%2%3"}.arg(tokStr, cmd, "\n");
161 d->tokenCounter = (d->tokenCounter + 1) % 999999;
162 d->process->write(line.toLocal8Bit());
163 d->process->waitForBytesWritten();
164 QString sOut;
165 QTextStream(&sOut) << "Command:" << line << "\n";
166 d->debugger->handleOutputStreamText(sOut);
167
168 return true;
169}
170
171void DebugManager::commandAndResponse(const QString& cmd,
172 const ResponseEntry::ResponseHandler_t& handler,
173 ResponseEntry::ResponseAction_t action)
174{
175 d->resposeExpected.insert(d->tokenCounter, { action, handler });
176 command(cmd);
177}
178
179void DebugManager::launchLocal()
180{
181 command(d->debugger->launchLocal());
182}
183
184void DebugManager::quit()
185{
186 command(d->debugger->quit());
187}
188
189void DebugManager::terminate()
190{
191 d->process->terminate();
192 emit terminated();
193}
194
195void DebugManager::kill()
196{
197 command(d->debugger->kill());
198}
199
200void DebugManager::breakRemoveAll()
201{
202 commandAndResponse(d->debugger->breakRemoveAll(), [this](const QVariant&) {
203 d->debugger->clearBreakPoint();
204 });
205}
206
207void DebugManager::breakInsert(const QString &path)
208{
209 commandAndResponse(d->debugger->breakInsert(path), [this](const QVariant& r) {
210 d->debugger->parseBreakPoint(r);
211 });
212}
213
214void DebugManager::updateExceptResponse(const int token, const QVariant& payload)
215{
216 if (d->resposeExpected.contains(token)) {
217 auto& expect = d->resposeExpected.value(token);
218 expect.handler(payload);
219 if (expect.action == ResponseEntry::ResponseEntry::ResponseAction_t::Temporal)
220 d->resposeExpected.remove(token);
221 }
222}
223
224void DebugManager::removeBreakpointInFile(const QString &filePath)
225{
226 auto breakPointIds = d->debugger->breakpointsForFile(filePath);
227 foreach(auto bpid, breakPointIds) {
228 breakRemove(bpid);
229 }
230}
231
232void DebugManager::breakRemove(int bpid)
233{
234 commandAndResponse(d->debugger->breakRemove(bpid), [this, bpid](const QVariant&) {
235 d->debugger->removeBreakPoint(bpid);
236 });
237}
238
239void DebugManager::stackListFrames()
240{
241 if (command(d->debugger->stackListFrames()))
242 d->stacklocker.wait();
243}
244
245void DebugManager::stackListVariables()
246{
247 if (command(d->debugger->stackListVariables()))
248 waitLocker();
249}
250
251void DebugManager::threadInfo()
252{
253 if (command(d->debugger->threadInfo()))
254 waitLocker();
255}
256
257void DebugManager::commandPause()
258{
259 command(d->debugger->commandPause());
260}
261
262void DebugManager::commandContinue()
263{
264 command(d->debugger->commandContinue());
265}
266
267void DebugManager::commandNext()
268{
269 command(d->debugger->commandNext()); //step over
270}
271
272void DebugManager::commandStep()
273{
274 command(d->debugger->commandStep()); //step in
275}
276
277void DebugManager::commandFinish()
278{
279 command(d->debugger->commandFinish()); //step out
280}
281
282void DebugManager::threadSelect(const int threadId)
283{
284 command(d->debugger->threadSelect(threadId));
285}
286
287void DebugManager::listSourceFiles()
288{
289 command(d->debugger->listSourceFiles());
290}
291
292dap::array<dap::StackFrame> DebugManager::allStackframes()
293{
294 return d->debugger->allStackframes();
295}
296
297dap::array<dap::Thread> DebugManager::allThreadList()
298{
299 return d->debugger->allThreadList();
300}
301
302dap::array<dap::Variable> DebugManager::allVariableList()
303{
304 return d->debugger->allVariableList();
305}
306
307void DebugManager::disassemble(const QString &address)
308{
309 command(d->debugger->disassemble(address));
310}
311