1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "pdbengine.h"
27
28 #include <debugger/debuggeractions.h>
29 #include <debugger/debuggercore.h>
30 #include <debugger/debuggerdialogs.h>
31 #include <debugger/debuggerplugin.h>
32 #include <debugger/debuggerprotocol.h>
33 #include <debugger/debuggertooltipmanager.h>
34 #include <debugger/threaddata.h>
35
36 #include <debugger/breakhandler.h>
37 #include <debugger/moduleshandler.h>
38 #include <debugger/procinterrupt.h>
39 #include <debugger/registerhandler.h>
40 #include <debugger/stackhandler.h>
41 #include <debugger/sourceutils.h>
42 #include <debugger/watchhandler.h>
43 #include <debugger/watchutils.h>
44
45 #include <utils/qtcassert.h>
46 #include <utils/qtcprocess.h>
47
48 #include <coreplugin/idocument.h>
49 #include <coreplugin/icore.h>
50 #include <coreplugin/messagebox.h>
51
52 #include <QDateTime>
53 #include <QDebug>
54 #include <QDir>
55 #include <QFileInfo>
56 #include <QTimer>
57 #include <QVariant>
58 #include <QJsonArray>
59
60 using namespace Core;
61
62 namespace Debugger {
63 namespace Internal {
64
PdbEngine()65 PdbEngine::PdbEngine()
66 {
67 setObjectName("PdbEngine");
68 setDebuggerName("PDB");
69 }
70
executeDebuggerCommand(const QString & command)71 void PdbEngine::executeDebuggerCommand(const QString &command)
72 {
73 QTC_ASSERT(state() == InferiorStopOk, qDebug() << state());
74 if (state() == DebuggerNotReady) {
75 showMessage("PDB PROCESS NOT RUNNING, PLAIN CMD IGNORED: " + command);
76 return;
77 }
78 QTC_ASSERT(m_proc.state() == QProcess::Running, notifyEngineIll());
79 postDirectCommand(command);
80 }
81
postDirectCommand(const QString & command)82 void PdbEngine::postDirectCommand(const QString &command)
83 {
84 QTC_ASSERT(m_proc.state() == QProcess::Running, notifyEngineIll());
85 showMessage(command, LogInput);
86 m_proc.write(command.toUtf8() + '\n');
87 }
88
runCommand(const DebuggerCommand & cmd)89 void PdbEngine::runCommand(const DebuggerCommand &cmd)
90 {
91 if (state() == EngineSetupRequested) { // cmd has been triggered too early
92 showMessage("IGNORED COMMAND: " + cmd.function);
93 return;
94 }
95 QTC_ASSERT(m_proc.state() == QProcess::Running, notifyEngineIll());
96 QString command = "qdebug('" + cmd.function + "'," + cmd.argsToPython() + ")";
97 showMessage(command, LogInput);
98 m_proc.write(command.toUtf8() + '\n');
99 }
100
shutdownInferior()101 void PdbEngine::shutdownInferior()
102 {
103 QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state());
104 notifyInferiorShutdownFinished();
105 }
106
shutdownEngine()107 void PdbEngine::shutdownEngine()
108 {
109 QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state());
110 m_proc.kill();
111 }
112
setupEngine()113 void PdbEngine::setupEngine()
114 {
115 QTC_ASSERT(state() == EngineSetupRequested, qDebug() << state());
116
117 m_interpreter = runParameters().interpreter;
118 QString bridge = ICore::resourcePath("debugger/pdbbridge.py").toString();
119
120 connect(&m_proc, &QProcess::errorOccurred, this, &PdbEngine::handlePdbError);
121 connect(&m_proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
122 this, &PdbEngine::handlePdbFinished);
123 connect(&m_proc, &QProcess::readyReadStandardOutput,
124 this, &PdbEngine::readPdbStandardOutput);
125 connect(&m_proc, &QProcess::readyReadStandardError,
126 this, &PdbEngine::readPdbStandardError);
127
128 QFile scriptFile(runParameters().mainScript);
129 if (!scriptFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
130 AsynchronousMessageBox::critical(tr("Python Error"),
131 QString("Cannot open script file %1:\n%2").
132 arg(scriptFile.fileName(), scriptFile.errorString()));
133 notifyEngineSetupFailed();
134 }
135
136 QStringList args = {bridge, scriptFile.fileName()};
137 args.append(Utils::ProcessArgs::splitArgs(runParameters().inferior.workingDirectory));
138 showMessage("STARTING " + m_interpreter + ' ' + args.join(' '));
139 m_proc.setEnvironment(runParameters().debugger.environment.toStringList());
140 m_proc.start(m_interpreter, args);
141
142 if (!m_proc.waitForStarted()) {
143 const QString msg = tr("Unable to start pdb \"%1\": %2")
144 .arg(m_interpreter, m_proc.errorString());
145 notifyEngineSetupFailed();
146 showMessage("ADAPTER START FAILED");
147 if (!msg.isEmpty())
148 ICore::showWarningWithOptions(tr("Adapter start failed"), msg);
149 notifyEngineSetupFailed();
150 return;
151 }
152
153 notifyEngineSetupOk();
154 QTC_ASSERT(state() == EngineRunRequested, qDebug() << state());
155
156 showStatusMessage(tr("Running requested..."), 5000);
157 BreakpointManager::claimBreakpointsForEngine(this);
158 notifyEngineRunAndInferiorStopOk();
159 updateAll();
160 }
161
interruptInferior()162 void PdbEngine::interruptInferior()
163 {
164 QString error;
165 interruptProcess(m_proc.processId(), GdbEngineType, &error);
166 }
167
executeStepIn(bool)168 void PdbEngine::executeStepIn(bool)
169 {
170 notifyInferiorRunRequested();
171 notifyInferiorRunOk();
172 postDirectCommand("step");
173 }
174
executeStepOut()175 void PdbEngine::executeStepOut()
176 {
177 notifyInferiorRunRequested();
178 notifyInferiorRunOk();
179 postDirectCommand("return");
180 }
181
executeStepOver(bool)182 void PdbEngine::executeStepOver(bool)
183 {
184 notifyInferiorRunRequested();
185 notifyInferiorRunOk();
186 postDirectCommand("next");
187 }
188
continueInferior()189 void PdbEngine::continueInferior()
190 {
191 notifyInferiorRunRequested();
192 notifyInferiorRunOk();
193 // Callback will be triggered e.g. when breakpoint is hit.
194 postDirectCommand("continue");
195 }
196
executeRunToLine(const ContextData & data)197 void PdbEngine::executeRunToLine(const ContextData &data)
198 {
199 Q_UNUSED(data)
200 QTC_CHECK("FIXME: PdbEngine::runToLineExec()" && false);
201 }
202
executeRunToFunction(const QString & functionName)203 void PdbEngine::executeRunToFunction(const QString &functionName)
204 {
205 Q_UNUSED(functionName)
206 QTC_CHECK("FIXME: PdbEngine::runToFunctionExec()" && false);
207 }
208
executeJumpToLine(const ContextData & data)209 void PdbEngine::executeJumpToLine(const ContextData &data)
210 {
211 Q_UNUSED(data)
212 QTC_CHECK("FIXME: PdbEngine::jumpToLineExec()" && false);
213 }
214
activateFrame(int frameIndex)215 void PdbEngine::activateFrame(int frameIndex)
216 {
217 if (state() != InferiorStopOk && state() != InferiorUnrunnable)
218 return;
219
220 StackHandler *handler = stackHandler();
221 QTC_ASSERT(frameIndex < handler->stackSize(), return);
222 handler->setCurrentIndex(frameIndex);
223 gotoLocation(handler->currentFrame());
224 updateLocals();
225 }
226
selectThread(const Thread & thread)227 void PdbEngine::selectThread(const Thread &thread)
228 {
229 Q_UNUSED(thread)
230 }
231
acceptsBreakpoint(const BreakpointParameters & bp) const232 bool PdbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const
233 {
234 return bp.fileName.endsWith(".py");
235 }
236
insertBreakpoint(const Breakpoint & bp)237 void PdbEngine::insertBreakpoint(const Breakpoint &bp)
238 {
239 QTC_ASSERT(bp, return);
240 QTC_CHECK(bp->state() == BreakpointInsertionRequested);
241 notifyBreakpointInsertProceeding(bp);
242
243 QString loc;
244 const BreakpointParameters ¶ms = bp->requestedParameters();
245 if (params.type == BreakpointByFunction)
246 loc = params.functionName;
247 else
248 loc = params.fileName.toString() + ':' + QString::number(params.lineNumber);
249
250 postDirectCommand("break " + loc);
251 }
252
updateBreakpoint(const Breakpoint & bp)253 void PdbEngine::updateBreakpoint(const Breakpoint &bp)
254 {
255 QTC_ASSERT(bp, return);
256 const BreakpointState state = bp->state();
257 if (QTC_GUARD(state == BreakpointUpdateRequested))
258 notifyBreakpointChangeProceeding(bp);
259 if (bp->responseId().isEmpty()) // FIXME postpone update somehow (QTimer::singleShot?)
260 return;
261
262 // FIXME figure out what needs to be changed (there might be more than enabled state)
263 const BreakpointParameters &requested = bp->requestedParameters();
264 if (requested.enabled != bp->isEnabled()) {
265 if (bp->isEnabled())
266 postDirectCommand("disable " + bp->responseId());
267 else
268 postDirectCommand("enable " + bp->responseId());
269 bp->setEnabled(!bp->isEnabled());
270 }
271 // Pretend it succeeds without waiting for response.
272 notifyBreakpointChangeOk(bp);
273 }
274
removeBreakpoint(const Breakpoint & bp)275 void PdbEngine::removeBreakpoint(const Breakpoint &bp)
276 {
277 QTC_ASSERT(bp, return);
278 QTC_CHECK(bp->state() == BreakpointRemoveRequested);
279 notifyBreakpointRemoveProceeding(bp);
280 if (bp->responseId().isEmpty()) {
281 notifyBreakpointRemoveFailed(bp);
282 return;
283 }
284 showMessage(QString("DELETING BP %1 IN %2")
285 .arg(bp->responseId()).arg(bp->fileName().toUserOutput()));
286 postDirectCommand("clear " + bp->responseId());
287 // Pretend it succeeds without waiting for response.
288 notifyBreakpointRemoveOk(bp);
289 }
290
loadSymbols(const QString & moduleName)291 void PdbEngine::loadSymbols(const QString &moduleName)
292 {
293 Q_UNUSED(moduleName)
294 }
295
loadAllSymbols()296 void PdbEngine::loadAllSymbols()
297 {
298 }
299
reloadModules()300 void PdbEngine::reloadModules()
301 {
302 runCommand({"listModules"});
303 }
304
refreshModules(const GdbMi & modules)305 void PdbEngine::refreshModules(const GdbMi &modules)
306 {
307 ModulesHandler *handler = modulesHandler();
308 handler->beginUpdateAll();
309 for (const GdbMi &item : modules) {
310 Module module;
311 module.moduleName = item["name"].data();
312 QString path = item["value"].data();
313 int pos = path.indexOf("' from '");
314 if (pos != -1) {
315 path = path.mid(pos + 8);
316 if (path.size() >= 2)
317 path.chop(2);
318 } else if (path.startsWith("<module '")
319 && path.endsWith("' (built-in)>")) {
320 path = "(builtin)";
321 }
322 module.modulePath = path;
323 handler->updateModule(module);
324 }
325 handler->endUpdateAll();
326 }
327
requestModuleSymbols(const QString & moduleName)328 void PdbEngine::requestModuleSymbols(const QString &moduleName)
329 {
330 DebuggerCommand cmd("listSymbols");
331 cmd.arg("module", moduleName);
332 runCommand(cmd);
333 }
334
refreshState(const GdbMi & reportedState)335 void PdbEngine::refreshState(const GdbMi &reportedState)
336 {
337 QString newState = reportedState.data();
338 if (newState == "stopped") {
339 notifyInferiorSpontaneousStop();
340 updateAll();
341 } else if (newState == "inferiorexited") {
342 notifyInferiorExited();
343 }
344 }
345
refreshLocation(const GdbMi & reportedLocation)346 void PdbEngine::refreshLocation(const GdbMi &reportedLocation)
347 {
348 StackFrame frame;
349 frame.file = reportedLocation["file"].data();
350 frame.line = reportedLocation["line"].toInt();
351 frame.usable = QFileInfo(frame.file).isReadable();
352 if (state() == InferiorRunOk) {
353 showMessage(QString("STOPPED AT: %1:%2").arg(frame.file).arg(frame.line));
354 gotoLocation(frame);
355 notifyInferiorSpontaneousStop();
356 updateAll();
357 }
358 }
359
refreshSymbols(const GdbMi & symbols)360 void PdbEngine::refreshSymbols(const GdbMi &symbols)
361 {
362 QString moduleName = symbols["module"].data();
363 Symbols syms;
364 for (const GdbMi &item : symbols["symbols"]) {
365 Symbol symbol;
366 symbol.name = item["name"].data();
367 syms.append(symbol);
368 }
369 showModuleSymbols(moduleName, syms);
370 }
371
canHandleToolTip(const DebuggerToolTipContext &) const372 bool PdbEngine::canHandleToolTip(const DebuggerToolTipContext &) const
373 {
374 return state() == InferiorStopOk;
375 }
376
assignValueInDebugger(WatchItem *,const QString & expression,const QVariant & value)377 void PdbEngine::assignValueInDebugger(WatchItem *, const QString &expression, const QVariant &value)
378 {
379 //DebuggerCommand cmd("assignValue");
380 //cmd.arg("expression", expression);
381 //cmd.arg("value", value.toString());
382 //runCommand(cmd);
383 postDirectCommand("global " + expression + ';' + expression + "=" + value.toString());
384 updateLocals();
385 }
386
updateItem(const QString & iname)387 void PdbEngine::updateItem(const QString &iname)
388 {
389 Q_UNUSED(iname)
390 updateAll();
391 }
392
handlePdbError(QProcess::ProcessError error)393 void PdbEngine::handlePdbError(QProcess::ProcessError error)
394 {
395 showMessage("HANDLE PDB ERROR");
396 switch (error) {
397 case QProcess::Crashed:
398 break; // will get a processExited() as well
399 // impossible case QProcess::FailedToStart:
400 case QProcess::ReadError:
401 case QProcess::WriteError:
402 case QProcess::Timedout:
403 default:
404 //setState(EngineShutdownRequested, true);
405 m_proc.kill();
406 AsynchronousMessageBox::critical(tr("Pdb I/O Error"), errorMessage(error));
407 break;
408 }
409 }
410
errorMessage(QProcess::ProcessError error) const411 QString PdbEngine::errorMessage(QProcess::ProcessError error) const
412 {
413 switch (error) {
414 case QProcess::FailedToStart:
415 return tr("The Pdb process failed to start. Either the "
416 "invoked program \"%1\" is missing, or you may have insufficient "
417 "permissions to invoke the program.")
418 .arg(m_interpreter);
419 case QProcess::Crashed:
420 return tr("The Pdb process crashed some time after starting "
421 "successfully.");
422 case QProcess::Timedout:
423 return tr("The last waitFor...() function timed out. "
424 "The state of QProcess is unchanged, and you can try calling "
425 "waitFor...() again.");
426 case QProcess::WriteError:
427 return tr("An error occurred when attempting to write "
428 "to the Pdb process. For example, the process may not be running, "
429 "or it may have closed its input channel.");
430 case QProcess::ReadError:
431 return tr("An error occurred when attempting to read from "
432 "the Pdb process. For example, the process may not be running.");
433 default:
434 return tr("An unknown error in the Pdb process occurred.") + ' ';
435 }
436 }
437
handlePdbFinished(int code,QProcess::ExitStatus type)438 void PdbEngine::handlePdbFinished(int code, QProcess::ExitStatus type)
439 {
440 showMessage(QString("PDB PROCESS FINISHED, status %1, code %2").arg(type).arg(code));
441 notifyEngineSpontaneousShutdown();
442 }
443
readPdbStandardError()444 void PdbEngine::readPdbStandardError()
445 {
446 QString err = QString::fromUtf8(m_proc.readAllStandardError());
447 //qWarning() << "Unexpected pdb stderr:" << err;
448 showMessage("Unexpected pdb stderr: " + err);
449 //handleOutput(err);
450 }
451
readPdbStandardOutput()452 void PdbEngine::readPdbStandardOutput()
453 {
454 QString out = QString::fromUtf8(m_proc.readAllStandardOutput());
455 handleOutput(out);
456 }
457
handleOutput(const QString & data)458 void PdbEngine::handleOutput(const QString &data)
459 {
460 m_inbuffer.append(data);
461 while (true) {
462 int pos = m_inbuffer.indexOf('\n');
463 if (pos == -1)
464 break;
465 QString response = m_inbuffer.left(pos).trimmed();
466 m_inbuffer = m_inbuffer.mid(pos + 1);
467 handleOutput2(response);
468 }
469 }
470
handleOutput2(const QString & data)471 void PdbEngine::handleOutput2(const QString &data)
472 {
473 const QStringList lines = data.split('\n');
474 for (const QString &line : lines) {
475 GdbMi item;
476 item.fromString(line);
477
478 showMessage(line, LogOutput);
479
480 if (line.startsWith("stack={")) {
481 refreshStack(item);
482 } else if (line.startsWith("data={")) {
483 refreshLocals(item);
484 } else if (line.startsWith("modules=[")) {
485 refreshModules(item);
486 } else if (line.startsWith("symbols={")) {
487 refreshSymbols(item);
488 } else if (line.startsWith("location={")) {
489 refreshLocation(item);
490 } else if (line.startsWith("state=")) {
491 refreshState(item);
492 } else if (line.startsWith("Breakpoint")) {
493 const int pos1 = line.indexOf(" at ");
494 QTC_ASSERT(pos1 != -1, continue);
495 const QString bpnr = line.mid(11, pos1 - 11);
496 const int pos2 = line.lastIndexOf(':');
497 QTC_ASSERT(pos2 != -1, continue);
498 const Utils::FilePath fileName = Utils::FilePath::fromString(
499 line.mid(pos1 + 4, pos2 - pos1 - 4));
500 const int lineNumber = line.mid(pos2 + 1).toInt();
501 const Breakpoint bp = Utils::findOrDefault(breakHandler()->breakpoints(), [&](const Breakpoint &bp) {
502 return bp->parameters().isLocatedAt(fileName, lineNumber, bp->markerFileName())
503 || bp->requestedParameters().isLocatedAt(fileName, lineNumber, bp->markerFileName());
504 });
505 QTC_ASSERT(bp, continue);
506 bp->setResponseId(bpnr);
507 bp->setFileName(fileName);
508 bp->setLineNumber(lineNumber);
509 bp->adjustMarker();
510 bp->setPending(false);
511 notifyBreakpointInsertOk(bp);
512 if (bp->needsChange()) {
513 bp->gotoState(BreakpointUpdateRequested, BreakpointInserted);
514 updateBreakpoint(bp);
515 // QTC_CHECK(!bp->needsChange());
516 }
517 }
518 }
519 }
520
refreshLocals(const GdbMi & vars)521 void PdbEngine::refreshLocals(const GdbMi &vars)
522 {
523 WatchHandler *handler = watchHandler();
524 handler->resetValueCache();
525 handler->insertItems(vars);
526 handler->notifyUpdateFinished();
527
528 updateToolTips();
529 }
530
refreshStack(const GdbMi & stack)531 void PdbEngine::refreshStack(const GdbMi &stack)
532 {
533 StackHandler *handler = stackHandler();
534 StackFrames frames;
535 for (const GdbMi &item : stack["frames"]) {
536 StackFrame frame;
537 frame.level = item["level"].data();
538 frame.file = item["file"].data();
539 frame.function = item["function"].data();
540 frame.module = item["function"].data();
541 frame.line = item["line"].toInt();
542 frame.address = item["address"].toAddress();
543 GdbMi usable = item["usable"];
544 if (usable.isValid())
545 frame.usable = usable.data().toInt();
546 else
547 frame.usable = QFileInfo(frame.file).isReadable();
548 frames.append(frame);
549 }
550 bool canExpand = stack["hasmore"].toInt();
551 //action(ExpandStack)->setEnabled(canExpand);
552 handler->setFrames(frames, canExpand);
553
554 int index = stackHandler()->firstUsableIndex();
555 handler->setCurrentIndex(index);
556 if (index >= 0 && index < handler->stackSize())
557 gotoLocation(handler->frameAt(index));
558 }
559
updateAll()560 void PdbEngine::updateAll()
561 {
562 runCommand({"stackListFrames"});
563 updateLocals();
564 }
565
updateLocals()566 void PdbEngine::updateLocals()
567 {
568 DebuggerCommand cmd("updateData");
569 cmd.arg("nativeMixed", isNativeMixedActive());
570 watchHandler()->appendFormatRequests(&cmd);
571 watchHandler()->appendWatchersAndTooltipRequests(&cmd);
572
573 const static bool alwaysVerbose = qEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE");
574 cmd.arg("passexceptions", alwaysVerbose);
575 cmd.arg("fancy", debuggerSettings()->useDebuggingHelpers.value());
576
577 //cmd.arg("resultvarname", m_resultVarName);
578 //m_lastDebuggableCommand = cmd;
579 //m_lastDebuggableCommand.args.replace("\"passexceptions\":0", "\"passexceptions\":1");
580 cmd.arg("frame", stackHandler()->currentIndex());
581
582 watchHandler()->notifyUpdateStarted();
583 runCommand(cmd);
584 }
585
hasCapability(unsigned cap) const586 bool PdbEngine::hasCapability(unsigned cap) const
587 {
588 return cap & (ReloadModuleCapability
589 | BreakConditionCapability
590 | ShowModuleSymbolsCapability);
591 }
592
createPdbEngine()593 DebuggerEngine *createPdbEngine()
594 {
595 return new PdbEngine;
596 }
597
598 } // namespace Internal
599 } // namespace Debugger
600