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 | |
14 | class 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 | |
27 | DebugManager::DebugManager(QObject *parent) |
28 | : QObject(parent) |
29 | , d(new DebugManagerPrivate()) |
30 | { |
31 | |
32 | } |
33 | |
34 | DebugManager::~DebugManager() |
35 | { |
36 | if (d) { |
37 | delete d; |
38 | } |
39 | } |
40 | |
41 | DebugManager *DebugManager::instance() |
42 | { |
43 | static DebugManager ins; |
44 | return &ins; |
45 | } |
46 | |
47 | void 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 | |
102 | void 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 | |
117 | qint64 DebugManager::getProcessId() |
118 | { |
119 | return d->process->processId(); |
120 | } |
121 | |
122 | void DebugManager::waitLocker() |
123 | { |
124 | d->locker.wait(); |
125 | } |
126 | |
127 | void DebugManager::fireLocker() |
128 | { |
129 | d->locker.fire(); |
130 | } |
131 | |
132 | void DebugManager::fireStackLocker() |
133 | { |
134 | d->stacklocker.fire(); |
135 | } |
136 | |
137 | bool DebugManager::isExecuting() const |
138 | { |
139 | return d->process->state() != QProcess::NotRunning; |
140 | } |
141 | |
142 | void 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 | |
154 | bool 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 | |
171 | void 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 | |
179 | void DebugManager::launchLocal() |
180 | { |
181 | command(d->debugger->launchLocal()); |
182 | } |
183 | |
184 | void DebugManager::quit() |
185 | { |
186 | command(d->debugger->quit()); |
187 | } |
188 | |
189 | void DebugManager::terminate() |
190 | { |
191 | d->process->terminate(); |
192 | emit terminated(); |
193 | } |
194 | |
195 | void DebugManager::kill() |
196 | { |
197 | command(d->debugger->kill()); |
198 | } |
199 | |
200 | void DebugManager::breakRemoveAll() |
201 | { |
202 | commandAndResponse(d->debugger->breakRemoveAll(), [this](const QVariant&) { |
203 | d->debugger->clearBreakPoint(); |
204 | }); |
205 | } |
206 | |
207 | void DebugManager::breakInsert(const QString &path) |
208 | { |
209 | commandAndResponse(d->debugger->breakInsert(path), [this](const QVariant& r) { |
210 | d->debugger->parseBreakPoint(r); |
211 | }); |
212 | } |
213 | |
214 | void 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 | |
224 | void DebugManager::removeBreakpointInFile(const QString &filePath) |
225 | { |
226 | auto breakPointIds = d->debugger->breakpointsForFile(filePath); |
227 | foreach(auto bpid, breakPointIds) { |
228 | breakRemove(bpid); |
229 | } |
230 | } |
231 | |
232 | void DebugManager::breakRemove(int bpid) |
233 | { |
234 | commandAndResponse(d->debugger->breakRemove(bpid), [this, bpid](const QVariant&) { |
235 | d->debugger->removeBreakPoint(bpid); |
236 | }); |
237 | } |
238 | |
239 | void DebugManager::stackListFrames() |
240 | { |
241 | if (command(d->debugger->stackListFrames())) |
242 | d->stacklocker.wait(); |
243 | } |
244 | |
245 | void DebugManager::stackListVariables() |
246 | { |
247 | if (command(d->debugger->stackListVariables())) |
248 | waitLocker(); |
249 | } |
250 | |
251 | void DebugManager::threadInfo() |
252 | { |
253 | if (command(d->debugger->threadInfo())) |
254 | waitLocker(); |
255 | } |
256 | |
257 | void DebugManager::commandPause() |
258 | { |
259 | command(d->debugger->commandPause()); |
260 | } |
261 | |
262 | void DebugManager::commandContinue() |
263 | { |
264 | command(d->debugger->commandContinue()); |
265 | } |
266 | |
267 | void DebugManager::commandNext() |
268 | { |
269 | command(d->debugger->commandNext()); //step over |
270 | } |
271 | |
272 | void DebugManager::commandStep() |
273 | { |
274 | command(d->debugger->commandStep()); //step in |
275 | } |
276 | |
277 | void DebugManager::commandFinish() |
278 | { |
279 | command(d->debugger->commandFinish()); //step out |
280 | } |
281 | |
282 | void DebugManager::threadSelect(const int threadId) |
283 | { |
284 | command(d->debugger->threadSelect(threadId)); |
285 | } |
286 | |
287 | void DebugManager::listSourceFiles() |
288 | { |
289 | command(d->debugger->listSourceFiles()); |
290 | } |
291 | |
292 | dap::array<dap::StackFrame> DebugManager::allStackframes() |
293 | { |
294 | return d->debugger->allStackframes(); |
295 | } |
296 | |
297 | dap::array<dap::Thread> DebugManager::allThreadList() |
298 | { |
299 | return d->debugger->allThreadList(); |
300 | } |
301 | |
302 | dap::array<dap::Variable> DebugManager::allVariableList() |
303 | { |
304 | return d->debugger->allVariableList(); |
305 | } |
306 | |
307 | void DebugManager::disassemble(const QString &address) |
308 | { |
309 | command(d->debugger->disassemble(address)); |
310 | } |
311 | |