// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "lldbengine.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace Utils; namespace Debugger::Internal { static int ¤tToken() { static int token = 0; return token; } /////////////////////////////////////////////////////////////////////// // // LldbEngine // /////////////////////////////////////////////////////////////////////// LldbEngine::LldbEngine() { m_lldbProc.setUseCtrlCStub(true); m_lldbProc.setProcessMode(ProcessMode::Writer); setObjectName("LldbEngine"); setDebuggerName("LLDB"); DebuggerSettings &ds = *debuggerSettings(); connect(&ds.autoDerefPointers, &BaseAspect::changed, this, &LldbEngine::updateLocals); connect(ds.createFullBacktrace.action(), &QAction::triggered, this, &LldbEngine::fetchFullBacktrace); connect(&ds.useDebuggingHelpers, &BaseAspect::changed, this, &LldbEngine::updateLocals); connect(&ds.useDynamicType, &BaseAspect::changed, this, &LldbEngine::updateLocals); connect(&ds.intelFlavor, &BaseAspect::changed, this, &LldbEngine::updateAll); connect(&m_lldbProc, &Process::started, this, &LldbEngine::handleLldbStarted); connect(&m_lldbProc, &Process::done, this, &LldbEngine::handleLldbDone); connect(&m_lldbProc, &Process::readyReadStandardOutput, this, &LldbEngine::readLldbStandardOutput); connect(&m_lldbProc, &Process::readyReadStandardError, this, &LldbEngine::readLldbStandardError); connect(this, &LldbEngine::outputReady, this, &LldbEngine::handleResponse, Qt::QueuedConnection); } void LldbEngine::executeDebuggerCommand(const QString &command) { DebuggerCommand cmd("executeDebuggerCommand"); cmd.arg("command", command); runCommand(cmd); } void LldbEngine::runCommand(const DebuggerCommand &command) { if (!m_lldbProc.isRunning()) { // This can legally happen e.g. through a reloadModule() // triggered by changes in view visibility. showMessage(QString("NO LLDB PROCESS RUNNING, CMD IGNORED: %1 %2") .arg(command.function).arg(state())); return; } const int tok = ++currentToken(); DebuggerCommand cmd = command; cmd.arg("token", tok); QString token = QString::number(tok); QString function = cmd.function + "(" + cmd.argsToPython() + ")"; QString msg = token + function + '\n'; if (cmd.flags == Silent) msg.replace(QRegularExpression("\"environment\":.[^]]*."), ""); if (cmd.flags == NeedsFullStop) { cmd.flags &= ~NeedsFullStop; if (state() == InferiorRunOk) { showStatusMessage(Tr::tr("Stopping temporarily"), 1000); m_onStop.append(cmd, false); interruptInferior(); return; } } showMessage(msg, LogInput); m_commandForToken[currentToken()] = cmd; executeCommand("script theDumper." + function); } void LldbEngine::debugLastCommand() { runCommand(m_lastDebuggableCommand); } void LldbEngine::handleAttachedToCore() { QTC_ASSERT(state() == InferiorUnrunnable, qDebug() << state();return); showMessage("Attached to core."); reloadFullStack(); reloadModules(); updateLocals(); } void LldbEngine::executeCommand(const QString &command) { // For some reason, sometimes LLDB misses the first character of the next command on Windows // if passing only 1 LF. m_lldbProc.write(command + "\n\n"); } void LldbEngine::shutdownInferior() { QTC_ASSERT(state() == InferiorShutdownRequested, qDebug() << state()); runCommand({"shutdownInferior"}); } void LldbEngine::shutdownEngine() { QTC_ASSERT(state() == EngineShutdownRequested, qDebug() << state()); abortDebuggerProcess(); } void LldbEngine::abortDebuggerProcess() { if (m_lldbProc.isRunning()) m_lldbProc.stop(); else notifyEngineShutdownFinished(); } static QString adapterStartFailed() { return Tr::tr("Adapter start failed."); } void LldbEngine::setupEngine() { QTC_ASSERT(state() == EngineSetupRequested, qDebug() << state()); const FilePath lldbCmd = runParameters().debugger.command.executable(); showMessage("STARTING LLDB: " + lldbCmd.toUserOutput()); Environment environment = runParameters().debugger.environment; environment.appendOrSet("PYTHONUNBUFFERED", "1"); // avoid flushing problem on macOS DebuggerItem::addAndroidLldbPythonEnv(lldbCmd, environment); if (lldbCmd.osType() == OsTypeLinux) { // LLDB 14 installation on Ubuntu 22.04 is broken: // https://bugs.launchpad.net/ubuntu/+source/llvm-defaults/+bug/1972855 // Brush over it: Process lldbPythonPathFinder; lldbPythonPathFinder.setCommand({lldbCmd, {"-P"}}); lldbPythonPathFinder.start(); lldbPythonPathFinder.waitForFinished(); QString lldbPythonPath = lldbPythonPathFinder.cleanedStdOut(); if (lldbPythonPath.endsWith('\n')) lldbPythonPath.chop(1); if (lldbPythonPath == "/usr/lib/local/lib/python3.10/dist-packages") environment.appendOrSet("PYTHONPATH", "/usr/lib/llvm-14/lib/python3.10/dist-packages"); } m_lldbProc.setEnvironment(environment); if (runParameters().debugger.workingDirectory.isDir()) m_lldbProc.setWorkingDirectory(runParameters().debugger.workingDirectory); if (HostOsInfo::isRunningUnderRosetta()) m_lldbProc.setCommand(CommandLine("/usr/bin/arch", {"-arm64", lldbCmd.toString()})); else m_lldbProc.setCommand(CommandLine(lldbCmd)); m_lldbProc.start(); } void LldbEngine::handleLldbStarted() { m_lldbProc.waitForReadyRead(1000); showStatusMessage(Tr::tr("Setting up inferior...")); const DebuggerRunParameters &rp = runParameters(); QString dumperPath = ICore::resourcePath("debugger").path(); executeCommand("script sys.path.insert(1, '" + dumperPath + "')"); // This triggers reportState("enginesetupok") or "enginesetupfailed": executeCommand("script from lldbbridge import *"); QString commands = nativeStartupCommands(); if (!commands.isEmpty()) executeCommand(commands); const QString path = debuggerSettings()->extraDumperFile.value(); if (!path.isEmpty() && QFileInfo(path).isReadable()) { DebuggerCommand cmd("addDumperModule"); cmd.arg("path", path); runCommand(cmd); } commands = debuggerSettings()->extraDumperCommands.value(); if (!commands.isEmpty()) { DebuggerCommand cmd("executeDebuggerCommand"); cmd.arg("command", commands); runCommand(cmd); } DebuggerCommand cmd1("loadDumpers"); cmd1.callback = [this](const DebuggerResponse &response) { watchHandler()->addDumpers(response.data["dumpers"]); }; runCommand(cmd1); const SourcePathMap sourcePathMap = mergePlatformQtPath(rp, debuggerSettings()->sourcePathMap.value()); for (auto it = sourcePathMap.constBegin(), cend = sourcePathMap.constEnd(); it != cend; ++it) { executeDebuggerCommand( "settings append target.source-map " + it.key() + ' ' + expand(it.value())); } for (const FilePath &path : rp.solibSearchPath) executeDebuggerCommand("settings append target.exec-search-paths " + path.toString()); DebuggerCommand cmd2("setupInferior"); cmd2.arg("executable", rp.inferior.command.executable().path()); cmd2.arg("breakonmain", rp.breakOnMain); cmd2.arg("useterminal", bool(terminal())); cmd2.arg("startmode", rp.startMode); cmd2.arg("nativemixed", isNativeMixedActive()); cmd2.arg("workingdirectory", rp.inferior.workingDirectory.path()); cmd2.arg("environment", rp.inferior.environment.toStringList()); cmd2.arg("processargs", toHex(ProcessArgs::splitArgs(rp.inferior.command.arguments(), HostOsInfo::hostOs()).join(QChar(0)))); cmd2.arg("platform", rp.platform); cmd2.arg("symbolfile", rp.symbolFile.path()); if (terminal()) { const qint64 attachedPID = terminal()->applicationPid(); const qint64 attachedMainThreadID = terminal()->applicationMainThreadId(); const QString msg = (attachedMainThreadID != -1) ? QString::fromLatin1("Attaching to %1 (%2)").arg(attachedPID).arg(attachedMainThreadID) : QString::fromLatin1("Attaching to %1").arg(attachedPID); showMessage(msg, LogMisc); cmd2.arg("startmode", DebuggerStartMode::AttachToLocalProcess); cmd2.arg("attachpid", attachedPID); } else { cmd2.arg("startmode", rp.startMode); // it is better not to check the start mode on the python sid (as we would have to duplicate the // enum values), and thus we assume that if the rp.attachPID is valid we really have to attach QTC_CHECK(rp.attachPID.isValid() && (rp.startMode == AttachToRemoteProcess || rp.startMode == AttachToLocalProcess || rp.startMode == AttachToRemoteServer)); cmd2.arg("attachpid", rp.attachPID.pid()); cmd2.arg("sysroot", rp.deviceSymbolsRoot.isEmpty() ? rp.sysRoot.toString() : rp.deviceSymbolsRoot); cmd2.arg("remotechannel", ((rp.startMode == AttachToRemoteProcess || rp.startMode == AttachToRemoteServer) ? rp.remoteChannel : QString())); cmd2.arg("platform", rp.platform); QTC_CHECK(!rp.continueAfterAttach || (rp.startMode == AttachToRemoteProcess || rp.startMode == AttachToLocalProcess || rp.startMode == AttachToRemoteServer)); m_continueAtNextSpontaneousStop = false; } cmd2.callback = [this](const DebuggerResponse &response) { const bool success = response.data["success"].toInt(); if (success) { BreakpointManager::claimBreakpointsForEngine(this); // Some extra roundtrip to make sure we end up behind all commands triggered // from claimBreakpointsForEngine(). DebuggerCommand cmd3("executeRoundtrip"); cmd3.callback = [this](const DebuggerResponse &) { notifyEngineSetupOk(); runEngine(); }; runCommand(cmd3); } else { notifyEngineSetupFailed(); } }; cmd2.flags = Silent; runCommand(cmd2); DebuggerCommand cmd0("setFallbackQtVersion"); cmd0.arg("version", rp.fallbackQtVersion); runCommand(cmd0); } void LldbEngine::runEngine() { const DebuggerRunParameters &rp = runParameters(); QTC_ASSERT(state() == EngineRunRequested, qDebug() << state(); return); showStatusMessage(Tr::tr("Running requested..."), 5000); DebuggerCommand cmd("runEngine"); if (rp.startMode == AttachToCore) cmd.arg("coreFile", rp.coreFile.path()); runCommand(cmd); } void LldbEngine::interruptInferior() { showStatusMessage(Tr::tr("Interrupt requested..."), 5000); runCommand({"interruptInferior"}); } void LldbEngine::executeStepIn(bool byInstruction) { notifyInferiorRunRequested(); runCommand({QLatin1String(byInstruction ? "executeStepI" : "executeStep")}); } void LldbEngine::executeStepOut() { notifyInferiorRunRequested(); runCommand({"executeStepOut"}); } void LldbEngine::executeStepOver(bool byInstruction) { notifyInferiorRunRequested(); runCommand({QLatin1String(byInstruction ? "executeNextI" : "executeNext")}); } void LldbEngine::continueInferior() { notifyInferiorRunRequested(); DebuggerCommand cmd("continueInferior"); cmd.callback = [this](const DebuggerResponse &response) { if (response.resultClass == ResultError) notifyEngineIll(); }; runCommand(cmd); } void LldbEngine::handleResponse(const QString &response) { GdbMi all; all.fromStringMultiple(response); for (const GdbMi &item : all) { const QString name = item.name(); if (name == "result") { QString msg = item["status"].data(); if (!msg.isEmpty()) msg[0] = msg.at(0).toUpper(); showStatusMessage(msg); int token = item["token"].toInt(); showMessage(QString("%1^").arg(token), LogOutput); if (m_commandForToken.contains(token)) { DebuggerCommand cmd = m_commandForToken.take(token); DebuggerResponse response; response.token = token; response.data = item; if (cmd.callback) cmd.callback(response); } } else if (name == "state") handleStateNotification(all); else if (name == "location") handleLocationNotification(item); else if (name == "output") handleOutputNotification(item); else if (name == "pid") notifyInferiorPid(item.toProcessHandle()); else if (name == "breakpointmodified") handleInterpreterBreakpointModified(item); else if (name == "bridgemessage") showMessage(item["msg"].data(), item["channel"].toInt()); } } void LldbEngine::executeRunToLine(const ContextData &data) { notifyInferiorRunRequested(); DebuggerCommand cmd("executeRunToLocation"); cmd.arg("file", data.fileName.path()); cmd.arg("line", data.lineNumber); cmd.arg("address", data.address); runCommand(cmd); } void LldbEngine::executeRunToFunction(const QString &functionName) { notifyInferiorRunRequested(); DebuggerCommand cmd("executeRunToFunction"); cmd.arg("function", functionName); runCommand(cmd); } void LldbEngine::executeJumpToLine(const ContextData &data) { DebuggerCommand cmd("executeJumpToLocation"); cmd.arg("file", data.fileName.path()); cmd.arg("line", data.lineNumber); cmd.arg("address", data.address); runCommand(cmd); } void LldbEngine::activateFrame(int frameIndex) { if (state() != InferiorStopOk && state() != InferiorUnrunnable) return; StackHandler *handler = stackHandler(); if (handler->isSpecialFrame(frameIndex)) { fetchStack(handler->stackSize() * 10 + 3); return; } QTC_ASSERT(frameIndex < handler->stackSize(), return); handler->setCurrentIndex(frameIndex); gotoCurrentLocation(); DebuggerCommand cmd("activateFrame"); cmd.arg("index", frameIndex); if (Thread thread = threadsHandler()->currentThread()) cmd.arg("thread", thread->id()); runCommand(cmd); updateLocals(); reloadRegisters(); } void LldbEngine::selectThread(const Thread &thread) { QTC_ASSERT(thread, return); DebuggerCommand cmd("selectThread"); cmd.arg("id", thread->id()); cmd.callback = [this](const DebuggerResponse &) { fetchStack(debuggerSettings()->maximalStackDepth()); }; runCommand(cmd); } bool LldbEngine::acceptsBreakpoint(const BreakpointParameters &bp) const { if (runParameters().startMode == AttachToCore) return false; if (bp.isCppBreakpoint()) return true; return isNativeMixedEnabled(); } void LldbEngine::insertBreakpoint(const Breakpoint &bp) { QTC_ASSERT(bp, return); DebuggerCommand cmd("insertBreakpoint"); cmd.callback = [this, bp](const DebuggerResponse &response) { QTC_CHECK(bp && bp->state() == BreakpointInsertionProceeding); updateBreakpointData(bp, response.data, true); }; bp->addToCommand(&cmd); notifyBreakpointInsertProceeding(bp); runCommand(cmd); } void LldbEngine::updateBreakpoint(const Breakpoint &bp) { QTC_ASSERT(bp, return); DebuggerCommand cmd("changeBreakpoint"); cmd.arg("lldbid", bp->responseId()); cmd.callback = [this, bp](const DebuggerResponse &response) { QTC_CHECK(bp && bp->state() == BreakpointUpdateProceeding); updateBreakpointData(bp, response.data, false); }; bp->addToCommand(&cmd); notifyBreakpointChangeProceeding(bp); runCommand(cmd); } void LldbEngine::enableSubBreakpoint(const SubBreakpoint &sbp, bool on) { QTC_ASSERT(sbp, return); Breakpoint bp = sbp->breakpoint(); QTC_ASSERT(bp, return); DebuggerCommand cmd("enableSubbreakpoint"); cmd.arg("lldbid", bp->responseId()); cmd.arg("locid", sbp->responseId); cmd.arg("enabled", on); cmd.callback = [bp, sbp](const DebuggerResponse &response) { QTC_ASSERT(sbp, return); QTC_ASSERT(bp, return); if (response.resultClass == ResultDone) { sbp->params.enabled = response.data["enabled"].toInt(); bp->adjustMarker(); } }; runCommand(cmd); } void LldbEngine::removeBreakpoint(const Breakpoint &bp) { QTC_ASSERT(bp, return); if (!bp->responseId().isEmpty()) { DebuggerCommand cmd("removeBreakpoint"); cmd.arg("lldbid", bp->responseId()); notifyBreakpointRemoveProceeding(bp); runCommand(cmd); // Pretend it succeeds without waiting for response. Feels better. // Otherwise, clicking in the gutter leaves the breakpoint visible // for quite some time, so the user assumes a mis-click and clicks // again, effectivly re-introducing the breakpoint. notifyBreakpointRemoveOk(bp); } } void LldbEngine::updateBreakpointData(const Breakpoint &bp, const GdbMi &bkpt, bool added) { QTC_ASSERT(bp, return); QString rid = bkpt["lldbid"].data(); QTC_ASSERT(bp, return); if (added) bp->setResponseId(rid); QTC_CHECK(bp->responseId() == rid); bp->setAddress(0); bp->setEnabled(bkpt["enabled"].toInt()); bp->setIgnoreCount(bkpt["ignorecount"].toInt()); bp->setCondition(fromHex(bkpt["condition"].data())); bp->setHitCount(bkpt["hitcount"].toInt()); bp->setFileName(FilePath::fromUserInput(bkpt["file"].data())); bp->setLineNumber(bkpt["line"].toInt()); GdbMi locations = bkpt["locations"]; const int numChild = locations.childCount(); if (numChild > 1) { for (const GdbMi &location : locations) { const QString locid = location["locid"].data(); SubBreakpoint loc = bp->findOrCreateSubBreakpoint(locid); QTC_ASSERT(loc, continue); loc->params.type = bp->type(); loc->params.address = location["addr"].toAddress(); loc->params.functionName = location["function"].data(); loc->params.fileName = FilePath::fromUserInput(location["file"].data()); loc->params.lineNumber = location["line"].toInt(); loc->displayName = QString("%1.%2").arg(bp->responseId()).arg(locid); } bp->setPending(false); } else if (numChild == 1) { const GdbMi location = locations.childAt(0); bp->setAddress(location["addr"].toAddress()); bp->setFunctionName(location["function"].data()); bp->setPending(false); } else { // This can happen for pending breakpoints. showMessage(QString("NO LOCATIONS (YET) FOR BP %1").arg(bp->parameters().toString())); } bp->adjustMarker(); if (added) notifyBreakpointInsertOk(bp); else notifyBreakpointChangeOk(bp); } void LldbEngine::handleOutputNotification(const GdbMi &output) { QString channel = output["channel"].data(); QString data = fromHex(output["data"].data()); LogChannel ch = AppStuff; if (channel == "stdout") ch = AppOutput; else if (channel == "stderr") ch = AppError; showMessage(data, ch); } void LldbEngine::handleInterpreterBreakpointModified(const GdbMi &bpItem) { QTC_ASSERT(bpItem.childCount(), return); QString id = bpItem.childAt(0).m_data; Breakpoint bp = breakHandler()->findBreakpointByResponseId(id); if (!bp) // FIXME adapt whole bp handling and turn into soft assert return; // this function got triggered by a lldb internal breakpoint event // avoid asserts regarding unexpected state transitions switch (bp->state()) { case BreakpointInsertionRequested: // was a pending bp bp->gotoState(BreakpointInsertionProceeding, BreakpointInsertionRequested); break; case BreakpointInserted: // was an inserted, gets updated now bp->gotoState(BreakpointUpdateRequested, BreakpointInserted); notifyBreakpointChangeProceeding(bp); break; default: break; } updateBreakpointData(bp, bpItem, false); } void LldbEngine::loadSymbols(const FilePath &moduleName) { Q_UNUSED(moduleName) } void LldbEngine::loadAllSymbols() { } void LldbEngine::reloadModules() { DebuggerCommand cmd("fetchModules"); cmd.callback = [this](const DebuggerResponse &response) { const FilePath inferior = runParameters().inferior.command.executable(); const GdbMi &modules = response.data["modules"]; ModulesHandler *handler = modulesHandler(); handler->beginUpdateAll(); for (const GdbMi &item : modules) { Module module; module.modulePath = inferior.withNewPath(item["file"].data()); module.moduleName = item["name"].data(); module.symbolsRead = Module::UnknownReadState; module.startAddress = item["loaded_addr"].toAddress(); module.endAddress = 0; // FIXME: End address not easily available. handler->updateModule(module); } handler->endUpdateAll(); }; runCommand(cmd); } void LldbEngine::requestModuleSymbols(const FilePath &moduleName) { DebuggerCommand cmd("fetchSymbols"); cmd.arg("module", moduleName.path()); cmd.callback = [moduleName](const DebuggerResponse &response) { const GdbMi &symbols = response.data["symbols"]; const QString module = response.data["module"].data(); Symbols syms; for (const GdbMi &item : symbols) { Symbol symbol; symbol.address = item["address"].data(); symbol.name = item["name"].data(); symbol.state = item["state"].data(); symbol.section = item["section"].data(); symbol.demangled = item["demangled"].data(); syms.append(symbol); } showModuleSymbols(moduleName.withNewPath(module), syms); }; runCommand(cmd); } ////////////////////////////////////////////////////////////////////// // // Tooltip specific stuff // ////////////////////////////////////////////////////////////////////// bool LldbEngine::canHandleToolTip(const DebuggerToolTipContext &context) const { return state() == InferiorStopOk && context.isCppEditor; } void LldbEngine::updateAll() { DebuggerCommand cmd("fetchThreads"); cmd.callback = [this](const DebuggerResponse &response) { threadsHandler()->setThreads(response.data); fetchStack(debuggerSettings()->maximalStackDepth()); reloadRegisters(); }; runCommand(cmd); } ////////////////////////////////////////////////////////////////////// // // Watch specific stuff // ////////////////////////////////////////////////////////////////////// void LldbEngine::assignValueInDebugger(WatchItem *item, const QString &expression, const QVariant &value) { DebuggerCommand cmd("assignValue"); cmd.arg("expr", toHex(expression)); cmd.arg("value", toHex(value.toString())); cmd.arg("type", toHex(item->type)); cmd.arg("simpleType", isIntOrFloatType(item->type)); cmd.callback = [this](const DebuggerResponse &) { updateLocals(); }; runCommand(cmd); } void LldbEngine::doUpdateLocals(const UpdateParameters ¶ms) { watchHandler()->notifyUpdateStarted(params); DebuggerCommand cmd("fetchVariables"); watchHandler()->appendFormatRequests(&cmd); watchHandler()->appendWatchersAndTooltipRequests(&cmd); const bool alwaysVerbose = qtcEnvironmentVariableIsSet("QTC_DEBUGGER_PYTHON_VERBOSE"); const DebuggerSettings &s = *debuggerSettings(); cmd.arg("passexceptions", alwaysVerbose); cmd.arg("fancy", s.useDebuggingHelpers.value()); cmd.arg("autoderef", s.autoDerefPointers.value()); cmd.arg("dyntype", s.useDynamicType.value()); cmd.arg("partialvar", params.partialVariable); cmd.arg("qobjectnames", s.showQObjectNames.value()); cmd.arg("timestamps", s.logTimeStamps.value()); StackFrame frame = stackHandler()->currentFrame(); cmd.arg("context", frame.context); cmd.arg("nativemixed", isNativeMixedActive()); cmd.arg("stringcutoff", s.maximalStringLength.value()); cmd.arg("displaystringlimit", s.displayStringLimit.value()); //cmd.arg("resultvarname", m_resultVarName); cmd.arg("partialvar", params.partialVariable); m_lastDebuggableCommand = cmd; m_lastDebuggableCommand.arg("passexceptions", "1"); cmd.callback = [this](const DebuggerResponse &response) { updateLocalsView(response.data); watchHandler()->notifyUpdateFinished(); updateToolTips(); }; runCommand(cmd); } void LldbEngine::handleLldbDone() { if (m_lldbProc.result() == ProcessResult::StartFailed) { notifyEngineSetupFailed(); showMessage("ADAPTER START FAILED"); ICore::showWarningWithOptions(adapterStartFailed(), Tr::tr("Unable to start LLDB \"%1\": %2") .arg(runParameters().debugger.command.executable().toUserOutput(), m_lldbProc.errorString())); return; } if (m_lldbProc.error() == QProcess::UnknownError) { notifyDebuggerProcessFinished(m_lldbProc.resultData(), "LLDB"); return; } const QProcess::ProcessError error = m_lldbProc.error(); showMessage(QString("LLDB PROCESS ERROR: %1").arg(error)); switch (error) { case QProcess::Crashed: notifyEngineShutdownFinished(); break; // will get a processExited() as well // impossible case QProcess::FailedToStart: case QProcess::ReadError: case QProcess::WriteError: case QProcess::Timedout: default: //setState(EngineShutdownRequested, true); AsynchronousMessageBox::critical(Tr::tr("LLDB I/O Error"), errorMessage(error)); break; } } QString LldbEngine::errorMessage(QProcess::ProcessError error) const { switch (error) { case QProcess::FailedToStart: return Tr::tr("The LLDB process failed to start. Either the " "invoked program \"%1\" is missing, or you may have insufficient " "permissions to invoke the program.") .arg(runParameters().debugger.command.executable().toUserOutput()); case QProcess::Crashed: return Tr::tr("The LLDB process crashed some time after starting " "successfully."); case QProcess::Timedout: return Tr::tr("The last waitFor...() function timed out. " "The state of QProcess is unchanged, and you can try calling " "waitFor...() again."); case QProcess::WriteError: return Tr::tr("An error occurred when attempting to write " "to the LLDB process. For example, the process may not be running, " "or it may have closed its input channel."); case QProcess::ReadError: return Tr::tr("An error occurred when attempting to read from " "the Lldb process. For example, the process may not be running."); default: return Tr::tr("An unknown error in the LLDB process occurred.") + ' '; } } void LldbEngine::readLldbStandardError() { QString err = QString::fromUtf8(m_lldbProc.readAllRawStandardError()); qDebug() << "\nLLDB STDERR UNEXPECTED: " << err; showMessage("Lldb stderr: " + err, LogError); } void LldbEngine::readLldbStandardOutput() { QByteArray outba = m_lldbProc.readAllRawStandardOutput(); outba.replace("\r\n", "\n"); QString out = QString::fromUtf8(outba); showMessage(out, LogOutput); m_inbuffer.append(out); while (true) { int pos = m_inbuffer.indexOf("@\n"); if (pos == -1) break; QString response = m_inbuffer.left(pos).trimmed(); m_inbuffer = m_inbuffer.mid(pos + 2); emit outputReady(response); } } void LldbEngine::handleStateNotification(const GdbMi &item) { const QString newState = item["state"].data(); if (newState == "running") notifyInferiorRunOk(); else if (newState == "inferiorrunfailed") notifyInferiorRunFailed(); else if (newState == "continueafternextstop") m_continueAtNextSpontaneousStop = true; else if (newState == "stopped") { notifyInferiorSpontaneousStop(); if (m_onStop.isEmpty()) { if (m_continueAtNextSpontaneousStop) { m_continueAtNextSpontaneousStop = false; continueInferior(); } else { updateAll(); } } else { showMessage("HANDLING QUEUED COMMANDS AFTER TEMPORARY STOP", LogMisc); DebuggerCommandSequence seq = m_onStop; m_onStop = DebuggerCommandSequence(); for (const DebuggerCommand &cmd : seq.commands()) runCommand(cmd); if (seq.wantContinue()) continueInferior(); } } else if (newState == "inferiorstopok") { notifyInferiorStopOk(); if (!isDying()) updateAll(); } else if (newState == "inferiorstopfailed") notifyInferiorStopFailed(); else if (newState == "inferiorill") notifyInferiorIll(); else if (newState == "enginesetupfailed") { Core::AsynchronousMessageBox::critical(adapterStartFailed(), item["error"].data()); notifyEngineSetupFailed(); } else if (newState == "enginerunfailed") notifyEngineRunFailed(); else if (newState == "enginerunandinferiorrunok") { if (runParameters().continueAfterAttach) m_continueAtNextSpontaneousStop = true; notifyEngineRunAndInferiorRunOk(); } else if (newState == "enginerunandinferiorstopok") { notifyEngineRunAndInferiorStopOk(); continueInferior(); } else if (newState == "enginerunokandinferiorunrunnable") { notifyEngineRunOkAndInferiorUnrunnable(); if (runParameters().startMode == AttachToCore) handleAttachedToCore(); } else if (newState == "inferiorshutdownfinished") notifyInferiorShutdownFinished(); else if (newState == "engineshutdownfinished") notifyEngineShutdownFinished(); else if (newState == "inferiorexited") notifyInferiorExited(); } void LldbEngine::handleLocationNotification(const GdbMi &reportedLocation) { qulonglong address = reportedLocation["address"].toAddress(); FilePath fileName = FilePath::fromUserInput(reportedLocation["file"].data()); QString function = reportedLocation["function"].data(); int lineNumber = reportedLocation["line"].toInt(); Location loc = Location(fileName, lineNumber); if (operatesByInstruction() || !fileName.exists() || lineNumber <= 0) { loc = Location(address); loc.setNeedsMarker(true); loc.setUseAssembler(true); } // Quickly set the location marker. if (lineNumber > 0 && fileName.exists() && function != "::qt_qmlDebugMessageAvailable()") gotoLocation(Location(fileName, lineNumber)); } void LldbEngine::reloadRegisters() { if (!isRegistersWindowVisible()) return; if (state() != InferiorStopOk && state() != InferiorUnrunnable) return; DebuggerCommand cmd("fetchRegisters"); cmd.callback = [this](const DebuggerResponse &response) { RegisterHandler *handler = registerHandler(); for (const GdbMi &item : response.data["registers"]) { Register reg; reg.name = item["name"].data(); reg.value.fromString(item["value"].data(), HexadecimalFormat); reg.size = item["size"].data().toInt(); reg.reportedType = item["type"].data(); if (reg.reportedType.startsWith("unsigned")) reg.kind = IntegerRegister; handler->updateRegister(reg); } handler->commitUpdates(); }; runCommand(cmd); } void LldbEngine::reloadDebuggingHelpers() { runCommand({"reloadDumpers"}); updateAll(); } void LldbEngine::loadAdditionalQmlStack() { fetchStack(-1, true); } void LldbEngine::reloadFullStack() { fetchStack(-1, false); } void LldbEngine::fetchStack(int limit, bool extraQml) { DebuggerCommand cmd("fetchStack"); cmd.arg("nativemixed", isNativeMixedActive()); cmd.arg("stacklimit", limit); cmd.arg("context", stackHandler()->currentFrame().context); cmd.arg("extraqml", int(extraQml)); cmd.callback = [this](const DebuggerResponse &response) { const GdbMi &stack = response.data["stack"]; const bool isFull = !stack["hasmore"].toInt(); stackHandler()->setFramesAndCurrentIndex(stack["frames"], isFull); activateFrame(stackHandler()->currentIndex()); }; runCommand(cmd); } void LldbEngine::fetchDisassembler(DisassemblerAgent *agent) { QPointer p(agent); int id = m_disassemblerAgents.value(p, -1); if (id == -1) { id = ++m_lastAgentId; m_disassemblerAgents.insert(p, id); } const Location &loc = agent->location(); DebuggerCommand cmd("fetchDisassembler"); cmd.arg("address", loc.address()); cmd.arg("function", loc.functionName()); cmd.arg("flavor", debuggerSettings()->intelFlavor.value() ? "intel" : "att"); cmd.callback = [this, id](const DebuggerResponse &response) { DisassemblerLines result; QPointer agent = m_disassemblerAgents.key(id); if (!agent.isNull()) { for (const GdbMi &line : response.data["lines"]) { DisassemblerLine dl; dl.address = line["address"].toAddress(); //dl.data = line["data"].data(); //dl.rawData = line["rawdata"].data(); dl.data = line["rawdata"].data(); if (!dl.data.isEmpty()) dl.data += QString(30 - dl.data.size(), ' '); dl.data += fromHex(line["hexdata"].data()); dl.data += line["data"].data(); dl.offset = line["offset"].toInt(); dl.lineNumber = line["line"].toInt(); dl.fileName = line["file"].data(); dl.function = line["function"].data(); dl.hunk = line["hunk"].toInt(); QString comment = fromHex(line["comment"].data()); if (!comment.isEmpty()) dl.data += " # " + comment; result.appendLine(dl); } agent->setContents(result); } }; runCommand(cmd); } void LldbEngine::fetchFullBacktrace() { DebuggerCommand cmd("fetchFullBacktrace"); cmd.callback = [](const DebuggerResponse &response) { Internal::openTextEditor("Backtrace $", fromHex(response.data.data())); }; runCommand(cmd); } void LldbEngine::fetchMemory(MemoryAgent *agent, quint64 addr, quint64 length) { DebuggerCommand cmd("fetchMemory"); cmd.arg("address", addr); cmd.arg("length", length); cmd.callback = [agent](const DebuggerResponse &response) { qulonglong addr = response.data["address"].toAddress(); QByteArray ba = QByteArray::fromHex(response.data["contents"].data().toUtf8()); agent->addData(addr, ba); }; runCommand(cmd); } void LldbEngine::changeMemory(MemoryAgent *agent, quint64 addr, const QByteArray &data) { Q_UNUSED(agent) DebuggerCommand cmd("writeMemory"); cmd.arg("address", addr); cmd.arg("data", QString::fromUtf8(data.toHex())); cmd.callback = [](const DebuggerResponse &response) { Q_UNUSED(response) }; runCommand(cmd); } void LldbEngine::setRegisterValue(const QString &name, const QString &value) { DebuggerCommand cmd("setRegister"); cmd.arg("name", name); cmd.arg("value", value); runCommand(cmd); } bool LldbEngine::hasCapability(unsigned cap) const { if (cap & (0 //| ReverseSteppingCapability | AutoDerefPointersCapability | DisassemblerCapability | RegisterCapability | ShowMemoryCapability | JumpToLineCapability | ReloadModuleCapability | ReloadModuleSymbolsCapability | BreakOnThrowAndCatchCapability | BreakConditionCapability | BreakIndividualLocationsCapability | TracePointCapability | ReturnFromFunctionCapability | CreateFullBacktraceCapability | WatchpointByAddressCapability | WatchpointByExpressionCapability | WatchWidgetsCapability | AddWatcherCapability | ShowModuleSymbolsCapability | ShowModuleSectionsCapability | CatchCapability | OperateByInstructionCapability | RunToLineCapability | WatchComplexExpressionsCapability | MemoryAddressCapability | AdditionalQmlStackCapability)) return true; if (runParameters().startMode == AttachToCore) return false; //return cap == SnapshotCapability; return false; } DebuggerEngine *createLldbEngine() { return new LldbEngine; } } // Debugger::Internal