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 &params = 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