/* * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "debugger.h" #include "utils.h" #include "mainwindow.h" #include "editor.h" #include "settings.h" #include "widgets/cpudialog.h" #include "systemconsts.h" #include #include #include #include #include #include #include #include #include #include "widgets/signalmessagedialog.h" Debugger::Debugger(QObject *parent) : QObject(parent), mForceUTF8(false), mLastLoadtime(0), mProjectLastLoadtime(0) { //models deleted in the destructor mBreakpointModel= std::make_shared(this); mBacktraceModel = std::make_shared(this); mWatchModel = std::make_shared(this); mRegisterModel = std::make_shared(this); mMemoryModel = std::make_shared(8,this); connect(mMemoryModel.get(),&MemoryModel::setMemoryData, this, &Debugger::setMemoryData); connect(mWatchModel.get(), &WatchModel::setWatchVarValue, this, &Debugger::setWatchVarValue); mExecuting = false; mReader = nullptr; mTarget = nullptr; mCommandChanged = false; mLeftPageIndexBackup = -1; connect(mWatchModel.get(), &WatchModel::fetchChildren, this, &Debugger::fetchVarChildren); setIsForProject(false); } Debugger::~Debugger() { // delete mBreakpointModel; // delete mBacktraceModel; // delete mWatchModel; // delete mRegisterModel; // delete mMemoryModel; } bool Debugger::start(int compilerSetIndex, const QString& inferior, const QStringList& binDirs) { Settings::PCompilerSet compilerSet = pSettings->compilerSets().getSet(compilerSetIndex); if (!compilerSet) { compilerSet = pSettings->compilerSets().defaultSet(); } if (!compilerSet) { QMessageBox::critical(pMainWindow, tr("No compiler set"), tr("No compiler set is configured.")+tr("Can't start debugging.")); return false; } setForceUTF8(CompilerInfoManager::forceUTF8InDebugger(compilerSet->compilerType())); mExecuting = true; QString debuggerPath = compilerSet->debugger(); //QFile debuggerProgram(debuggerPath); // if (!isTextAllAscii(debuggerPath)) { // mExecuting = false; // QMessageBox::critical(pMainWindow, // tr("Debugger path error"), // tr("Debugger's path \"%1\" contains non-ascii characters.") // .arg(debuggerPath) // + "
" // + tr("This prevents it from executing.")); // return false; // } if (!fileExists(debuggerPath)) { mExecuting = false; QMessageBox::critical(pMainWindow, tr("Debugger not exists"), tr("Can''t find debugger (gdb) in : \"%1\"").arg(debuggerPath) +"
" +tr("Please check the \"program\" page of compiler settings.")); return false; } if (pSettings->debugger().useGDBServer()) { if (!isTextAllAscii(compilerSet->debugServer())) { mExecuting = false; QMessageBox::critical(pMainWindow, tr("GDB Server path error"), tr("GDB Server's path \"%1\" contains non-ascii characters.") .arg(compilerSet->debugServer()) + "
" + tr("This prevents it from executing.")); return false; } if (!fileExists(compilerSet->debugServer())) { mExecuting = false; QMessageBox::critical(pMainWindow, tr("GDB Server not exists"), tr("Can''t find gdb server in : \"%1\"").arg(compilerSet->debugServer())); return false; } } mMemoryModel->reset(); mWatchModel->resetAllVarInfos(); if (pSettings->debugger().useGDBServer()) { //deleted when thread finished mTarget = new DebugTarget(inferior,compilerSet->debugServer(),pSettings->debugger().GDBServerPort()); if (pSettings->executor().redirectInput()) mTarget->setInputFile(pSettings->executor().inputFilename()); connect(mTarget, &QThread::finished,[this](){ if (mExecuting) { stop(); } mTarget->deleteLater(); mTarget = nullptr; }); mTarget->addBinDirs(binDirs); mTarget->addBinDir(pSettings->dirs().appDir()); mTarget->start(); mTarget->waitStart(); } //delete when thread finished mReader = new DebugReader(this); mReader->addBinDirs(binDirs); mReader->addBinDir(pSettings->dirs().appDir()); mReader->setDebuggerPath(debuggerPath); connect(mReader, &QThread::finished,this,&Debugger::cleanUpReader); connect(mReader, &QThread::finished,mMemoryModel.get(),&MemoryModel::reset); connect(mReader, &DebugReader::parseFinished,this,&Debugger::syncFinishedParsing,Qt::BlockingQueuedConnection); connect(mReader, &DebugReader::changeDebugConsoleLastLine,this,&Debugger::onChangeDebugConsoleLastline); connect(mReader, &DebugReader::cmdStarted,pMainWindow, &MainWindow::disableDebugActions); connect(mReader, &DebugReader::cmdFinished,pMainWindow, &MainWindow::enableDebugActions); connect(mReader, &DebugReader::inferiorStopped, pMainWindow, &MainWindow::enableDebugActions); connect(mReader, &DebugReader::breakpointInfoGetted, mBreakpointModel.get(), &BreakpointModel::updateBreakpointNumber); connect(mReader, &DebugReader::localsUpdated, pMainWindow, &MainWindow::onLocalsReady); connect(mReader, &DebugReader::memoryUpdated,this, &Debugger::updateMemory); connect(mReader, &DebugReader::evalUpdated,this, &Debugger::updateEval); connect(mReader, &DebugReader::disassemblyUpdate,this, &Debugger::updateDisassembly); connect(mReader, &DebugReader::registerNamesUpdated, this, &Debugger::updateRegisterNames); connect(mReader, &DebugReader::registerValuesUpdated, this, &Debugger::updateRegisterValues); connect(mReader, &DebugReader::varCreated,mWatchModel.get(), &WatchModel::updateVarInfo); connect(mReader, &DebugReader::prepareVarChildren,mWatchModel.get(), &WatchModel::prepareVarChildren); connect(mReader, &DebugReader::addVarChild,mWatchModel.get(), &WatchModel::addVarChild); connect(mReader, &DebugReader::varValueUpdated,mWatchModel.get(), &WatchModel::updateVarValue); connect(mReader, &DebugReader::varsValueUpdated,mWatchModel.get(), &WatchModel::updateAllHasMoreVars); connect(mReader, &DebugReader::inferiorContinued,pMainWindow, &MainWindow::removeActiveBreakpoints); connect(mReader, &DebugReader::inferiorStopped,pMainWindow, &MainWindow::setActiveBreakpoint); connect(mReader, &DebugReader::inferiorStopped,this, &Debugger::refreshAll); mReader->registerInferiorStoppedCommand("-stack-list-frames",""); mReader->start(); mReader->waitStart(); pMainWindow->updateAppTitle(); //Application.HintHidePause := 5000; return true; } void Debugger::stop() { if (mExecuting) { if (mTarget) { mTarget->stopDebug(); mTarget = nullptr; } mReader->stopDebug(); } } void Debugger::cleanUpReader() { if (mExecuting) { mExecuting = false; //stop debugger mReader->deleteLater(); mReader=nullptr; if (pMainWindow->cpuDialog()!=nullptr) { pMainWindow->cpuDialog()->close(); } // Free resources pMainWindow->removeActiveBreakpoints(); pMainWindow->txtLocals()->clear(); pMainWindow->updateAppTitle(); pMainWindow->updateDebugEval(""); mBacktraceModel->clear(); mWatchModel->clearAllVarInfos(); mBreakpointModel->invalidateAllBreakpointNumbers(); pMainWindow->updateEditorActions(); } } void Debugger::updateRegisterNames(const QStringList ®isterNames) { mRegisterModel->updateNames(registerNames); } void Debugger::updateRegisterValues(const QHash &values) { mRegisterModel->updateValues(values); } void Debugger::refreshAll() { refreshWatchVars(); sendCommand("-stack-list-variables", "--all-values"); if (memoryModel()->startAddress()>0) sendCommand("-data-read-memory",QString("%1 x 1 %2 %3 ") .arg(memoryModel()->startAddress()) .arg(pSettings->debugger().memoryViewRows()) .arg(pSettings->debugger().memoryViewColumns()) ); } std::shared_ptr Debugger::registerModel() const { return mRegisterModel; } std::shared_ptr Debugger::watchModel() const { return mWatchModel; } void Debugger::sendCommand(const QString &command, const QString ¶ms, DebugCommandSource source) { if (mExecuting && mReader) { mReader->postCommand(command,params,source); } } bool Debugger::commandRunning() { if (mExecuting && mReader) { return mReader->commandRunning(); } return false; } bool Debugger::inferiorRunning() { if (mExecuting && mReader) { return mReader->inferiorRunning(); } return false; } void Debugger::interrupt() { sendCommand("-exec-interrupt", ""); } bool Debugger::isForProject() const { return mBreakpointModel->isForProject(); } void Debugger::setIsForProject(bool newIsForProject) { if (!executing()) { mBreakpointModel->setIsForProject(newIsForProject); mWatchModel->setIsForProject(newIsForProject); } } void Debugger::clearForProject() { mBreakpointModel->clear(true); mWatchModel->clear(true); } void Debugger::addBreakpoint(int line, const Editor* editor) { addBreakpoint(line,editor->filename(), editor->inProject()); } void Debugger::addBreakpoint(int line, const QString &filename, bool forProject) { PBreakpoint bp=std::make_shared(); bp->number = -1; bp->line = line; bp->filename = filename; bp->condition = ""; bp->enabled = true; bp->breakpointType = BreakpointType::Breakpoint; bp->timestamp = QDateTime::currentMSecsSinceEpoch(); mBreakpointModel->addBreakpoint(bp,forProject); if (mExecuting) { sendBreakpointCommand(bp); } } void Debugger::deleteBreakpoints(const QString &filename, bool forProject) { const QList& list=mBreakpointModel->breakpoints(forProject); for (int i=list.size()-1;i>=0;i--) { PBreakpoint bp = list[i]; if (bp->filename == filename) { mBreakpointModel->removeBreakpoint(i,forProject); } } } void Debugger::deleteBreakpoints(const Editor *editor) { deleteBreakpoints(editor->filename(),editor->inProject()); } void Debugger::deleteBreakpoints(bool forProject) { mBreakpointModel->clear(forProject); // for (int i=mBreakpointModel->breakpoints().size()-1;i>=0;i--) { // removeBreakpoint(i); // } } void Debugger::removeBreakpoint(int line, const Editor *editor) { removeBreakpoint(line,editor->filename(),editor->inProject()); } void Debugger::removeBreakpoint(int line, const QString &filename, bool forProject) { const QList& breakpoints=mBreakpointModel->breakpoints(forProject); for (int i=breakpoints.size()-1;i>=0;i--) { PBreakpoint bp = breakpoints[i]; if (bp->filename == filename && bp->line == line) { removeBreakpoint(i, forProject); } } } void Debugger::removeBreakpoint(int index, bool forProject) { sendClearBreakpointCommand(index, forProject); mBreakpointModel->removeBreakpoint(index, forProject); } PBreakpoint Debugger::breakpointAt(int line, const QString& filename, int *index , bool forProject) { const QList& breakpoints=mBreakpointModel->breakpoints(forProject); for (*index=0;*indexline == line && breakpoint->filename == filename) return breakpoint; } *index=-1; return PBreakpoint(); } PBreakpoint Debugger::breakpointAt(int line, const Editor *editor, int *index) { return breakpointAt(line,editor->filename(),index, editor->inProject()); } void Debugger::setBreakPointCondition(int index, const QString &condition, bool forProject) { PBreakpoint breakpoint=mBreakpointModel->setBreakPointCondition(index,condition, forProject); if (condition.isEmpty()) { sendCommand("-break-condition", QString("%1").arg(breakpoint->number)); } else { sendCommand("-break-condition", QString("%1 %2").arg(breakpoint->number).arg(condition)); } } void Debugger::sendAllBreakpointsToDebugger() { for (PBreakpoint breakpoint:mBreakpointModel->breakpoints(mBreakpointModel->isForProject())) { sendBreakpointCommand(breakpoint); } } void Debugger::saveForNonproject(const QString &filename) { save(filename,QString()); } void Debugger::saveForProject(const QString &filename, const QString &projectFolder) { save(filename,projectFolder); } void Debugger::loadForNonproject(const QString &filename) { bool forProject = false; mLastLoadtime = 0; PDebugConfig pConfig = load(filename, forProject); if (pConfig->timestamp>0) { mBreakpointModel->setBreakpoints(pConfig->breakpoints,forProject); mWatchModel->setWatchVars(pConfig->watchVars,forProject); } } void Debugger::loadForProject(const QString &filename, const QString &projectFolder) { bool forProject = true; mProjectLastLoadtime = 0; PDebugConfig pConfig = load(filename, forProject); if (pConfig->timestamp>0) { foreach (const PBreakpoint& breakpoint, pConfig->breakpoints) { breakpoint->filename = generateAbsolutePath(projectFolder,breakpoint->filename); } mBreakpointModel->setBreakpoints(pConfig->breakpoints,forProject); mWatchModel->setWatchVars(pConfig->watchVars,forProject); } } void Debugger::addWatchVar(const QString &expression) { // Don't allow duplicates... PWatchVar oldVar = mWatchModel->findWatchVar(expression); if (oldVar) return; PWatchVar var = std::make_shared(); var->parent= PWatchVar(); var->expression = expression; var->value = tr("Execute to evaluate"); var->numChild = 0; var->hasMore = false; var->timestamp = QDateTime::currentMSecsSinceEpoch(); addWatchVar(var,isForProject()); } void Debugger::modifyWatchVarExpression(const QString &oldExpr, const QString &newExpr) { // check if name already exists; PWatchVar var = mWatchModel->findWatchVar(newExpr); if (var) return; var = mWatchModel->findWatchVar(oldExpr); if (var) { if (mExecuting && !var->expression.isEmpty()) sendRemoveWatchCommand(var); var->expression = newExpr; var->type.clear(); var->value.clear(); var->hasMore = false; var->numChild=0; var->name.clear(); var->children.clear(); if (mExecuting) { sendWatchCommand(var); } } } void Debugger::refreshWatchVars() { if (mExecuting) { sendAllWatchVarsToDebugger(); sendCommand("-var-update"," --all-values *"); } } void Debugger::fetchVarChildren(const QString &varName) { if (mExecuting) { sendCommand("-var-list-children",varName); } } bool Debugger::forceUTF8() const { return mForceUTF8; } void Debugger::setForceUTF8(bool newForceUTF8) { mForceUTF8 = newForceUTF8; } std::shared_ptr Debugger::memoryModel() const { return mMemoryModel; } void Debugger::removeWatchVars(bool deleteparent) { if (deleteparent) { mWatchModel->clear(); } else { for(const PWatchVar& var:mWatchModel->watchVars()) { sendRemoveWatchCommand(var); } mWatchModel->clearAllVarInfos(); } } void Debugger::removeWatchVar(const QModelIndex &index) { PWatchVar var = mWatchModel->findWatchVar(index); if (!var) return; sendRemoveWatchCommand(var); mWatchModel->removeWatchVar(index); } void Debugger::sendAllWatchVarsToDebugger() { for (PWatchVar var:mWatchModel->watchVars()) { if (var->name.isEmpty()) sendWatchCommand(var); } } PWatchVar Debugger::findWatchVar(const QString &expression) { return mWatchModel->findWatchVar(expression); } PWatchVar Debugger::watchVarAt(const QModelIndex &index) { return mWatchModel->findWatchVar(index); } //void Debugger::notifyWatchVarUpdated(PWatchVar var) //{ // mWatchModel->notifyUpdated(var); //} std::shared_ptr Debugger::backtraceModel() { return mBacktraceModel; } std::shared_ptr Debugger::breakpointModel() { return mBreakpointModel; } void Debugger::sendWatchCommand(PWatchVar var) { sendCommand("-var-create", var->expression); } void Debugger::sendRemoveWatchCommand(PWatchVar var) { sendCommand("-var-delete",QString("%1").arg(var->name)); } void Debugger::sendBreakpointCommand(PBreakpoint breakpoint) { if (breakpoint && mExecuting) { // break "filename":linenum QString condition; if (!breakpoint->condition.isEmpty()) { condition = " -c " + breakpoint->condition; } QString filename = breakpoint->filename; filename.replace('\\','/'); sendCommand("-break-insert", QString("%1 --source \"%2\" --line %3") .arg(condition,filename) .arg(breakpoint->line)); } } void Debugger::sendClearBreakpointCommand(int index, bool forProject) { sendClearBreakpointCommand(mBreakpointModel->breakpoints(forProject)[index]); } void Debugger::sendClearBreakpointCommand(PBreakpoint breakpoint) { // Debugger already running? Remove it from GDB if (breakpoint && breakpoint->number>=0 && mExecuting) { //clear "filename":linenum QString filename = breakpoint->filename; filename.replace('\\','/'); sendCommand("-break-delete", QString("%1").arg(breakpoint->number)); } } QJsonArray BreakpointModel::toJson(const QString& projectFolder) { bool forProject = !projectFolder.isEmpty(); QJsonArray array; foreach (const PBreakpoint& breakpoint, breakpoints(forProject)) { QJsonObject obj; if (forProject) obj["filename"]=extractRelativePath(projectFolder, breakpoint->filename); else obj["filename"]=breakpoint->filename; obj["line"]=breakpoint->line; obj["condition"]=breakpoint->condition; obj["enabled"]=breakpoint->enabled; obj["breakpoint_type"] = static_cast(breakpoint->breakpointType); obj["timestamp"]=QString("%1").arg(breakpoint->timestamp); array.append(obj); } return array; } void BreakpointModel::setBreakpoints(const QList &list, bool forProject) { if (mIsForProject == forProject) beginResetModel(); if (forProject) { mProjectBreakpoints = list; } else { mBreakpoints = list; } if (mIsForProject == forProject) endResetModel(); } void Debugger::save(const QString &filename, const QString& projectFolder) { bool forProject=!projectFolder.isEmpty(); QList breakpoints; QList watchVars=mWatchModel->watchVars(forProject); QSet breakpointCompareSet; QSet watchVarCompareSet; if (forProject) { //convert project file's absolute path to relative path foreach (const PBreakpoint& breakpoint, mBreakpointModel->breakpoints(forProject)) { QString filename = extractRelativePath(projectFolder, breakpoint->filename); QString key = QString("%1-%2").arg(filename).arg(breakpoint->line); breakpointCompareSet.insert(key); } } else { foreach (const PBreakpoint& breakpoint, mBreakpointModel->breakpoints(forProject)) { QString key = QString("%1-%2").arg(breakpoint->filename).arg(breakpoint->line); breakpointCompareSet.insert(key); } } foreach (const PWatchVar& watchVar, watchVars) { watchVarCompareSet.insert(watchVar->expression); } std::shared_ptr pConfig = load(filename, forProject); QFile file(filename); if (file.open(QFile::WriteOnly | QFile::Truncate)) { foreach (const PBreakpoint& breakpoint, pConfig->breakpoints) { QString key = QString("%1-%2").arg(breakpoint->filename).arg(breakpoint->line); if (!breakpointCompareSet.contains(key)) { breakpointCompareSet.insert(key); if (forProject) breakpoint->filename=generateAbsolutePath(projectFolder,breakpoint->filename); mBreakpointModel->addBreakpoint(breakpoint,forProject); } } foreach (const PWatchVar& watchVar, pConfig->watchVars) { QString key = watchVar->expression; if (!watchVarCompareSet.contains(key)) { watchVarCompareSet.insert(key); addWatchVar(watchVar,forProject); } } qint64 saveTimestamp = QDateTime::currentMSecsSinceEpoch();; if (forProject) { mProjectLastLoadtime = saveTimestamp; } else { mLastLoadtime = saveTimestamp; } QJsonObject rootObj; rootObj["timestamp"] = QString("%1").arg(saveTimestamp); rootObj["breakpoints"] = mBreakpointModel->toJson(projectFolder); rootObj["watchvars"] = mWatchModel->toJson(forProject); QJsonDocument doc; doc.setObject(rootObj); if (file.write(doc.toJson())<0) { throw FileError(tr("Save file '%1' failed.") .arg(filename)); } } else { throw FileError(tr("Can't open file '%1' for write.") .arg(filename)); } } PDebugConfig Debugger::load(const QString &filename, bool forProject) { qint64 criteriaTimestamp; if (forProject) { criteriaTimestamp = mProjectLastLoadtime; } else { criteriaTimestamp = mLastLoadtime; } std::shared_ptr pConfig=std::make_shared(); pConfig->timestamp=0; QFile file(filename); if (!file.exists()) return pConfig; if (file.open(QFile::ReadOnly)) { QByteArray content = file.readAll(); QJsonParseError error; QJsonDocument doc(QJsonDocument::fromJson(content,&error)); if (error.error != QJsonParseError::NoError) { throw FileError(tr("Error in json file '%1':%2 : %3") .arg(filename) .arg(error.offset) .arg(error.errorString())); } QJsonObject rootObject = doc.object(); qint64 timestamp = rootObject["timestamp"].toString().toLongLong(); if (timestamp <= criteriaTimestamp) return pConfig; pConfig->timestamp = timestamp; pConfig->breakpoints = mBreakpointModel->loadJson(rootObject["breakpoints"].toArray(),criteriaTimestamp); pConfig->watchVars = mWatchModel->loadJson(rootObject["watchvars"].toArray(), criteriaTimestamp); if (forProject) { mProjectLastLoadtime = QDateTime::currentMSecsSinceEpoch(); } else { mLastLoadtime = QDateTime::currentMSecsSinceEpoch(); } } else { throw FileError(tr("Can't open file '%1' for read.") .arg(filename)); } return pConfig; } void Debugger::addWatchVar(const PWatchVar &watchVar, bool forProject) { mWatchModel->addWatchVar(watchVar,forProject); if (forProject == isForProject()) sendWatchCommand(watchVar); } void Debugger::syncFinishedParsing() { bool spawnedcpuform = false; // GDB determined that the source code is more recent than the executable. Ask the user if he wants to rebuild. if (mReader->receivedSFWarning()) { if (QMessageBox::question(pMainWindow, tr("Compile"), tr("Source file is more recent than executable.")+"

" + tr("Recompile?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes ) == QMessageBox::Yes) { stop(); pMainWindow->compile(); return; } } // show command output if (pSettings->debugger().enableDebugConsole() ) { if (pSettings->debugger().showDetailLog()) { for (const QString& line:mReader->fullOutput()) { pMainWindow->addDebugOutput(line); } } else { if (mReader->currentCmd() && mReader->currentCmd()->command == "disas") { } else { for (const QString& line:mReader->consoleOutput()) { pMainWindow->addDebugOutput(line); } if ( (mReader->currentCmd() && mReader->currentCmd()->source== DebugCommandSource::Console) || !mReader->consoleOutput().isEmpty() ) { pMainWindow->addDebugOutput("(gdb)"); } } } } // The program to debug has stopped. Stop the debugger if (mReader->processExited()) { stop(); return; } if (mReader->signalReceived() && mReader->signalName()!="SIGINT" && mReader->signalName()!="SIGTRAP") { SignalMessageDialog dialog(pMainWindow); dialog.setOpenCPUInfo(pSettings->debugger().openCPUInfoWhenSignaled()); dialog.setMessage( tr("Signal \"%1\" Received: ").arg(mReader->signalName()) + "
" + mReader->signalMeaning()); int result = dialog.exec(); if (result == QDialog::Accepted && dialog.openCPUInfo()) { pMainWindow->showCPUInfoDialog(); } } // CPU form updates itself when spawned, don't update twice! if ((mReader->updateCPUInfo() && !spawnedcpuform) && (pMainWindow->cpuDialog()!=nullptr)) { pMainWindow->cpuDialog()->updateInfo(); } } void Debugger::setMemoryData(qulonglong address, unsigned char data) { sendCommand("-data-write-memory-bytes", QString("%1 \"%2\"").arg(address).arg(data,2,16,QChar('0'))); refreshAll(); } void Debugger::setWatchVarValue(const QString &name, const QString &value) { sendCommand("-var-assign",QString("%1 %2").arg(name,value)); refreshAll(); } void Debugger::updateMemory(const QStringList &value) { mMemoryModel->updateMemory(value); emit memoryExamineReady(value); } void Debugger::updateEval(const QString &value) { emit evalValueReady(value); } void Debugger::updateDisassembly(const QString& file, const QString& func, const QStringList &value) { if (pMainWindow->cpuDialog()) { pMainWindow->cpuDialog()->setDisassembly(file,func,value); } } void Debugger::onChangeDebugConsoleLastline(const QString& text) { //pMainWindow->changeDebugOutputLastline(text); pMainWindow->addDebugOutput(text); } int Debugger::leftPageIndexBackup() const { return mLeftPageIndexBackup; } void Debugger::setLeftPageIndexBackup(int leftPageIndexBackup) { mLeftPageIndexBackup = leftPageIndexBackup; } bool Debugger::executing() const { return mExecuting; } DebugReader::DebugReader(Debugger* debugger, QObject *parent) : QThread(parent), #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) mCmdQueueMutex(), #else mCmdQueueMutex(QMutex::Recursive), #endif mStartSemaphore(0) { mDebugger = debugger; mProcess = std::make_shared(); mCmdRunning = false; mAsyncUpdated = false; } void DebugReader::postCommand(const QString &Command, const QString &Params, DebugCommandSource Source) { QMutexLocker locker(&mCmdQueueMutex); PDebugCommand pCmd = std::make_shared(); pCmd->command = Command; pCmd->params = Params; pCmd->source = Source; mCmdQueue.enqueue(pCmd); // if (!mCmdRunning) // runNextCmd(); } void DebugReader::registerInferiorStoppedCommand(const QString &Command, const QString &Params) { QMutexLocker locker(&mCmdQueueMutex); PDebugCommand pCmd = std::make_shared(); pCmd->command = Command; pCmd->params = Params; pCmd->source = DebugCommandSource::Other; mInferiorStoppedHookCommands.append(pCmd); } void DebugReader::clearCmdQueue() { QMutexLocker locker(&mCmdQueueMutex); mCmdQueue.clear(); } void DebugReader::processConsoleOutput(const QByteArray& line) { if (line.length()>3 && line.startsWith("~\"") && line.endsWith("\"")) { QByteArray s=line.mid(2,line.length()-3); QByteArray stringValue; const char *p=s.data(); while (*p!=0) { if (*p=='\\' && *(p+1)!=0) { p++; switch (*p) { case '\'': stringValue+=0x27; p++; break; case '"': stringValue+=0x22; p++; break; case '?': stringValue+=0x3f; p++; break; case '\\': stringValue+=0x5c; p++; break; case 'a': stringValue+=0x07; p++; break; case 'b': stringValue+=0x08; p++; break; case 'f': stringValue+=0x0c; p++; break; case 'n': stringValue+=0x0a; p++; break; case 'r': stringValue+=0x0d; p++; break; case 't': stringValue+=0x09; p++; break; case 'v': stringValue+=0x0b; p++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int i=0; for (i=0;i<3;i++) { if (*(p+i)<'0' || *(p+i)>'7') break; } QByteArray numStr(p,i); bool ok; unsigned char ch = numStr.toInt(&ok,8); stringValue+=ch; p+=i; break; } } } else { stringValue+=*p; p++; } } mConsoleOutput.append(QString::fromLocal8Bit(stringValue)); } } void DebugReader::processResult(const QByteArray &result) { GDBMIResultParser parser; GDBMIResultType resultType; GDBMIResultParser::ParseObject multiValues; if (!mCurrentCmd) return; bool parseOk = parser.parse(result, mCurrentCmd->command, resultType,multiValues); if (!parseOk) return; switch(resultType) { case GDBMIResultType::BreakpointTable: case GDBMIResultType::Frame: case GDBMIResultType::Locals: break; case GDBMIResultType::Breakpoint: handleBreakpoint(multiValues["bkpt"].object()); return; case GDBMIResultType::FrameStack: handleStack(multiValues["stack"].array()); return; case GDBMIResultType::LocalVariables: handleLocalVariables(multiValues["variables"].array()); return; case GDBMIResultType::Evaluation: handleEvaluation(multiValues["value"].value()); return; case GDBMIResultType::Memory: handleMemory(multiValues["memory"].array()); return; case GDBMIResultType::RegisterNames: handleRegisterNames(multiValues["register-names"].array()); return; case GDBMIResultType::RegisterValues: handleRegisterValue(multiValues["register-values"].array()); return; case GDBMIResultType::CreateVar: handleCreateVar(multiValues); return; case GDBMIResultType::ListVarChildren: handleListVarChildren(multiValues); return; case GDBMIResultType::UpdateVarValue: handleUpdateVarValue(multiValues["changelist"].array()); return; default: return; } } void DebugReader::processExecAsyncRecord(const QByteArray &line) { QByteArray result; GDBMIResultParser::ParseObject multiValues; GDBMIResultParser parser; if (!parser.parseAsyncResult(line,result,multiValues)) return; if (result == "running") { mInferiorRunning = true; mCurrentAddress=0; mCurrentFile.clear(); mCurrentLine=-1; mCurrentFunc.clear(); emit inferiorContinued(); return; } if (result == "stopped") { mInferiorRunning = false; QByteArray reason = multiValues["reason"].value(); if (reason == "exited") { //inferior exited, gdb should terminate too mProcessExited = true; return; } if (reason == "exited-normally") { //inferior exited, gdb should terminate too mProcessExited = true; return; } if (reason == "exited-signalled") { //inferior exited, gdb should terminate too mProcessExited = true; mSignalReceived = true; return; } mUpdateCPUInfo = true; GDBMIResultParser::ParseValue frame(multiValues["frame"]); if (frame.isValid()) { GDBMIResultParser::ParseObject frameObj = frame.object(); mCurrentAddress = frameObj["addr"].hexValue(); mCurrentLine = frameObj["line"].intValue(); if (mDebugger->forceUTF8()) mCurrentFile = frameObj["fullname"].utf8PathValue(); else mCurrentFile = frameObj["fullname"].pathValue(); mCurrentFunc = frameObj["func"].value(); } if (reason == "signal-received") { mSignalReceived = true; mSignalName = multiValues["signal-name"].value(); mSignalMeaning = multiValues["signal-meaning"].value(); } runInferiorStoppedHook(); if (mCurrentCmd && mCurrentCmd->source == DebugCommandSource::Console) emit inferiorStopped(mCurrentFile, mCurrentLine,false); else emit inferiorStopped(mCurrentFile, mCurrentLine,true); } } void DebugReader::processError(const QByteArray &errorLine) { mConsoleOutput.append(QString::fromLocal8Bit(errorLine)); } void DebugReader::processResultRecord(const QByteArray &line) { if (line.startsWith("^exit")) { mProcessExited = true; return; } if (line.startsWith("^error")) { processError(line); return; } if (line.startsWith("^done") || line.startsWith("^running")) { int pos = line.indexOf(','); if (pos>=0) { QByteArray result = line.mid(pos+1); processResult(result); } else if (mCurrentCmd && !(mCurrentCmd->command.startsWith('-'))) { if (mCurrentCmd->command == "disas") { QStringList disOutput = mConsoleOutput; if (disOutput.length()>=3) { disOutput.pop_back(); disOutput.pop_front(); disOutput.pop_front(); } emit disassemblyUpdate(mCurrentFile,mCurrentFunc, disOutput); } } return ; } if (line.startsWith("^connected")) { //TODO: connected to remote target return; } } void DebugReader::processDebugOutput(const QByteArray& debugOutput) { // Only update once per update at most //WatchView.Items.BeginUpdate; emit parseStarted(); mConsoleOutput.clear(); mFullOutput.clear(); mSignalReceived = false; mUpdateCPUInfo = false; mReceivedSFWarning = false; QList lines = splitByteArrayToLines(debugOutput); for (int i=0;idebugger().showDetailLog()) mFullOutput.append(line); line = removeToken(line); if (line.isEmpty()) { continue; } switch (line[0]) { case '~': // console stream output processConsoleOutput(line); break; case '@': // target stream output case '&': // log stream output break; case '^': // result record processResultRecord(line); break; case '*': // exec async output processExecAsyncRecord(line); break; case '+': // status async output case '=': // notify async output break; } } emit parseFinished(); mConsoleOutput.clear(); mFullOutput.clear(); } void DebugReader::runInferiorStoppedHook() { foreach (const PDebugCommand& cmd, mInferiorStoppedHookCommands) { mCmdQueue.push_front(cmd); } } void DebugReader::runNextCmd() { QMutexLocker locker(&mCmdQueueMutex); if (mCurrentCmd) { DebugCommandSource commandSource = mCurrentCmd->source; mCurrentCmd=nullptr; if (commandSource!=DebugCommandSource::HeartBeat) emit cmdFinished(); } if (mCmdQueue.isEmpty()) { if (pSettings->debugger().useGDBServer() && mInferiorRunning && !mAsyncUpdated) { mAsyncUpdated = true; QTimer::singleShot(50,this,&DebugReader::asyncUpdate); } return; } PDebugCommand pCmd = mCmdQueue.dequeue(); mCmdRunning = true; mCurrentCmd = pCmd; if (pCmd->source!=DebugCommandSource::HeartBeat) emit cmdStarted(); QByteArray s; QByteArray params; s=pCmd->command.toLocal8Bit(); if (!pCmd->params.isEmpty()) { params = pCmd->params.toLocal8Bit(); } //clang compatibility if (pCmd->command == "-break-insert" && mDebugger->forceUTF8()) { params = pCmd->params.toUtf8(); } if (pCmd->command == "-var-create") { //hack for variable creation,to easy remember var expression params = " - @ "+params; } else if (pCmd->command == "-var-list-children") { //hack for list variable children,to easy remember var expression params = " --all-values \"" + params+'\"'; } s+=" "+params; s+= "\n"; if (mProcess->write(s)<0) { emit writeToDebugFailed(); } // if devDebugger.ShowCommandLog or pCmd^.ShowInConsole then begin if (pSettings->debugger().enableDebugConsole() ) { //update debug console if (pSettings->debugger().showDetailLog() && pCmd->source != DebugCommandSource::Console) { emit changeDebugConsoleLastLine(pCmd->command + ' ' + params); } } } QStringList DebugReader::tokenize(const QString &s) { QStringList result; int tStart,tEnd; int i=0; while (i') { i++; break; } i++; } tEnd = std::min(i,s.length()); result.append(s.mid(tStart,tEnd-tStart)); } else if (ch == '(') { tStart = i; i++; while (iforceUTF8()) filename = breakpoint["fullname"].utf8PathValue(); else filename = breakpoint["fullname"].pathValue(); int line = breakpoint["line"].intValue(); int number = breakpoint["number"].intValue(); emit breakpointInfoGetted(filename, line , number); } void DebugReader::handleStack(const QList & stack) { mDebugger->backtraceModel()->clear(); foreach (const GDBMIResultParser::ParseValue& frameValue, stack) { GDBMIResultParser::ParseObject frameObject = frameValue.object(); PTrace trace = std::make_shared(); trace->funcname = frameObject["func"].value(); if (mDebugger->forceUTF8()) trace->filename = frameObject["fullname"].utf8PathValue(); else trace->filename = frameObject["fullname"].pathValue(); trace->line = frameObject["line"].intValue(); trace->level = frameObject["level"].intValue(0); trace->address = frameObject["addr"].value(); mDebugger->backtraceModel()->addTrace(trace); } } void DebugReader::handleLocalVariables(const QList &variables) { QStringList locals; foreach (const GDBMIResultParser::ParseValue& varValue, variables) { GDBMIResultParser::ParseObject varObject = varValue.object(); locals.append( QString("%1 = %2") .arg( QString(varObject["name"].value()), QString(varObject["value"].value()) )); } emit localsUpdated(locals); } void DebugReader::handleEvaluation(const QString &value) { emit evalUpdated(value); } void DebugReader::handleMemory(const QList &rows) { QStringList memory; foreach (const GDBMIResultParser::ParseValue& row, rows) { GDBMIResultParser::ParseObject rowObject = row.object(); QList data = rowObject["data"].array(); QStringList values; foreach (const GDBMIResultParser::ParseValue& val, data) { values.append(val.value()); } memory.append(QString("%1 %2") .arg(rowObject["addr"].value(),values.join(" "))); } emit memoryUpdated(memory); } void DebugReader::handleRegisterNames(const QList &names) { QStringList nameList; foreach (const GDBMIResultParser::ParseValue& nameValue, names) { // QString text = nameValue.value().trimmed(); // if (!text.isEmpty()) nameList.append(nameValue.value()); } emit registerNamesUpdated(nameList); } void DebugReader::handleRegisterValue(const QList &values) { QHash result; foreach (const GDBMIResultParser::ParseValue& val, values) { GDBMIResultParser::ParseObject obj = val.object(); int number = obj["number"].intValue(); QString value = obj["value"].value(); bool ok; long long intVal; intVal = value.toLongLong(&ok,10); if (ok) { value = QString("0x%1").arg(intVal,0,16); } result.insert(number,value); } emit registerValuesUpdated(result); } void DebugReader::handleCreateVar(const GDBMIResultParser::ParseObject &multiVars) { if (!mCurrentCmd) return; QString expression = mCurrentCmd->params; QString name = multiVars["name"].value(); int numChild = multiVars["numchild"].intValue(0); QString value = multiVars["value"].value(); QString type = multiVars["type"].value(); bool hasMore = multiVars["has_more"].value() != "0"; emit varCreated(expression,name,numChild,value,type,hasMore); } void DebugReader::handleListVarChildren(const GDBMIResultParser::ParseObject &multiVars) { if (!mCurrentCmd) return; QString parentName = mCurrentCmd->params; int parentNumChild = multiVars["numchild"].intValue(0); QList children = multiVars["children"].array(); bool hasMore = multiVars["has_more"].value()!="0"; emit prepareVarChildren(parentName,parentNumChild,hasMore); foreach(const GDBMIResultParser::ParseValue& child, children) { GDBMIResultParser::ParseObject childObj = child.object(); QString name = childObj["name"].value(); QString exp = childObj["exp"].value(); int numChild = childObj["numchild"].intValue(0); QString value = childObj["value"].value(); QString type = childObj["type"].value(); bool hasMore = childObj["has_more"].value() != "0"; emit addVarChild(parentName, name, exp, numChild, value, type, hasMore); } } void DebugReader::handleUpdateVarValue(const QList &changes) { foreach (const GDBMIResultParser::ParseValue& value, changes) { GDBMIResultParser::ParseObject obj = value.object(); QString name = obj["name"].value(); QString val = obj["value"].value(); QString inScope = obj["in_scope"].value(); bool typeChanged = (obj["type_changed"].value()=="true"); QString newType = obj["new_type"].value(); int newNumChildren = obj["new_num_children"].intValue(-1); bool hasMore = (obj["has_more"].value() == "1"); emit varValueUpdated(name,val,inScope,typeChanged,newType,newNumChildren, hasMore); } //todo: -var-list-children will freeze if the var is not correctly initialized //emit varsValueUpdated(); } QByteArray DebugReader::removeToken(const QByteArray &line) { int p=0; while (p'9') { break; } p++; } if (p(); auto action = finally([&]{ mProcess.reset(); }); mProcess->setProgram(cmd); mProcess->setArguments(splitProcessCommand(arguments)); mProcess->setProcessChannelMode(QProcess::MergedChannels); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH"); QStringList pathAdded = mBinDirs; if (!path.isEmpty()) { path = pathAdded.join(PATH_SEPARATOR) + PATH_SEPARATOR + path; } else { path = pathAdded.join(PATH_SEPARATOR); } QString cmdDir = extractFileDir(cmd); if (!cmdDir.isEmpty()) { path = cmdDir + PATH_SEPARATOR + path; } env.insert("PATH",path); mProcess->setProcessEnvironment(env); mProcess->setWorkingDirectory(workingDir); connect(mProcess.get(), &QProcess::errorOccurred, [&](){ mErrorOccured= true; }); QByteArray buffer; QByteArray readed; mProcess->start(); mProcess->waitForStarted(5000); mStartSemaphore.release(1); while (true) { mProcess->waitForFinished(1); if (mProcess->state()!=QProcess::Running) { break; } if (mStop) { mProcess->terminate(); mProcess->kill(); break; } if (mErrorOccured) break; readed = mProcess->readAll(); buffer += readed; if (readed.endsWith("\n")&& outputTerminated(buffer)) { processDebugOutput(buffer); buffer.clear(); mCmdRunning = false; runNextCmd(); } else if (!mCmdRunning && readed.isEmpty()){ runNextCmd(); } else if (readed.isEmpty()){ msleep(1); } } if (mErrorOccured) { emit processError(mProcess->error()); } } BreakpointModel::BreakpointModel(QObject *parent):QAbstractTableModel(parent), mIsForProject(false) { } int BreakpointModel::rowCount(const QModelIndex &) const { return breakpoints(mIsForProject).size(); } int BreakpointModel::columnCount(const QModelIndex &) const { return 3; } QVariant BreakpointModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); const QList &list=breakpoints(mIsForProject); if (index.row()<0 || index.row() >= static_cast(list.size())) return QVariant(); PBreakpoint breakpoint = list[index.row()]; if (!breakpoint) return QVariant(); switch (role) { case Qt::DisplayRole: switch (index.column()) { case 0: { return extractFileName(breakpoint->filename); } case 1: if (breakpoint->line>0) return breakpoint->line; else return ""; case 2: return breakpoint->condition; default: return QVariant(); } case Qt::ToolTipRole: switch (index.column()) { case 0: return breakpoint->filename; case 1: if (breakpoint->line>0) return breakpoint->line; else return ""; case 2: return breakpoint->condition; default: return QVariant(); } default: return QVariant(); } } QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(section) { case 0: return tr("Filename"); case 1: return tr("Line"); case 2: return tr("Condition"); } } return QVariant(); } void BreakpointModel::addBreakpoint(PBreakpoint p, bool forProject) { if (forProject) { if (forProject==mIsForProject) beginInsertRows(QModelIndex(),mProjectBreakpoints.count(),mProjectBreakpoints.count()); mProjectBreakpoints.push_back(p); } else { if (forProject==mIsForProject) beginInsertRows(QModelIndex(),mBreakpoints.count(),mBreakpoints.count()); mBreakpoints.push_back(p); } if (forProject==mIsForProject) endInsertRows(); } void BreakpointModel::clear(bool forProject) { if (forProject == mIsForProject) beginResetModel(); if (forProject) mProjectBreakpoints.clear(); else mBreakpoints.clear(); if (forProject == mIsForProject) endResetModel(); } void BreakpointModel::removeBreakpoint(int row, bool forProject) { if (forProject==mIsForProject) beginRemoveRows(QModelIndex(),row,row); if (forProject) mProjectBreakpoints.removeAt(row); else mBreakpoints.removeAt(row); if (forProject==mIsForProject) endRemoveRows(); } void BreakpointModel::removeBreakpointsInFile(const QString &fileName, bool forProject) { QList & lst=forProject?mProjectBreakpoints:mBreakpoints; for (int i=lst.count()-1;i>=0;i--) { if (lst[i]->filename==fileName) removeBreakpoint(i,forProject); } } void BreakpointModel::renameBreakpointFilenames(const QString &oldFileName, const QString &newFileName, bool forProject) { QList & lst=forProject?mProjectBreakpoints:mBreakpoints; for (int i=lst.count()-1;i>=0;i--) { if (lst[i]->filename==oldFileName) { lst[i]->filename=newFileName; if (forProject == mIsForProject) { QModelIndex index=createIndex(i,0); emit dataChanged(index,index); } } } } void BreakpointModel::invalidateAllBreakpointNumbers() { foreach (PBreakpoint bp,mBreakpoints) { bp->number = -1; } foreach (PBreakpoint bp,mProjectBreakpoints) { bp->number = -1; } //emit dateChanged(createIndex(0,0),) } PBreakpoint BreakpointModel::setBreakPointCondition(int index, const QString &condition,bool forProject) { PBreakpoint breakpoint = breakpoints(forProject)[index]; breakpoint->condition = condition; if (forProject==mIsForProject) emit dataChanged(createIndex(index,0),createIndex(index,2)); return breakpoint; } PBreakpoint BreakpointModel::breakpoint(int index, bool forProject) const { const QList list=breakpoints(forProject); if (index<0 && index>=list.count()) return PBreakpoint(); return list[index]; } void BreakpointModel::updateBreakpointNumber(const QString& filename, int line, int number) { foreach (PBreakpoint bp, breakpoints(mIsForProject)) { if (bp->filename == filename && bp->line == line) { bp->number = number; return; } } } void BreakpointModel::onFileDeleteLines(const QString& filename, int startLine, int count, bool forProject) { const QList &list=breakpoints(forProject); for (int i = list.count()-1;i>=0;i--){ PBreakpoint breakpoint = list[i]; if (breakpoint->filename == filename && breakpoint->line>=startLine) { if (breakpoint->line >= startLine+count) { breakpoint->line -= count; if (forProject==mIsForProject) emit dataChanged(createIndex(i,0),createIndex(i,2)); } else { removeBreakpoint(i,forProject); } } } } void BreakpointModel::onFileInsertLines(const QString& filename, int startLine, int count, bool forProject) { const QList &list=breakpoints(forProject); for (int i = list.count()-1;i>=0;i--){ PBreakpoint breakpoint = list[i]; if (breakpoint->filename == filename && breakpoint->line>=startLine) { breakpoint->line+=count; if (forProject == mIsForProject) emit dataChanged(createIndex(i,0),createIndex(i,2)); } } } bool BreakpointModel::isForProject() const { return mIsForProject; } void BreakpointModel::setIsForProject(bool newIsForProject) { if (mIsForProject!=newIsForProject) { beginResetModel(); mIsForProject = newIsForProject; endResetModel(); } } QList BreakpointModel::loadJson(const QJsonArray& jsonArray, qint64 criteriaTime) { QList result; for (int i=0;i criteriaTime) { PBreakpoint breakpoint = std::make_shared(); breakpoint->filename = obj["filename"].toString(); breakpoint->line = obj["line"].toInt(); breakpoint->condition = obj["condition"].toString(); breakpoint->enabled = obj["enabled"].toBool(); breakpoint->breakpointType = static_cast(obj["breakpoint_type"].toInt()); breakpoint->timestamp = timestamp; result.append(breakpoint); } } return result; } BacktraceModel::BacktraceModel(QObject *parent):QAbstractTableModel(parent) { } int BacktraceModel::rowCount(const QModelIndex &) const { return mList.size(); } int BacktraceModel::columnCount(const QModelIndex &) const { return 3; } QVariant BacktraceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row()<0 || index.row() >= static_cast(mList.size())) return QVariant(); PTrace trace = mList[index.row()]; if (!trace) return QVariant(); switch (role) { case Qt::DisplayRole: switch (index.column()) { case 0: return trace->funcname; case 1: return trace->filename; case 2: if (trace->line>0) return trace->line; else return ""; default: return QVariant(); } default: return QVariant(); } } QVariant BacktraceModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(section) { case 0: return tr("Function"); case 1: return tr("Filename"); case 2: return tr("Line"); } } return QVariant(); } void BacktraceModel::addTrace(PTrace p) { beginInsertRows(QModelIndex(),mList.size(),mList.size()); mList.push_back(p); endInsertRows(); } void BacktraceModel::clear() { beginResetModel(); mList.clear(); endResetModel(); } void BacktraceModel::removeTrace(int row) { beginRemoveRows(QModelIndex(),row,row); mList.removeAt(row); endRemoveRows(); } const QList &BacktraceModel::backtraces() const { return mList; } PTrace BacktraceModel::backtrace(int index) const { if (index>=0 && index < mList.count()){ return mList[index]; } return PTrace(); } WatchModel::WatchModel(QObject *parent):QAbstractItemModel(parent) { mUpdateCount = 0; mIsForProject = false; } QVariant WatchModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } WatchVar* item = static_cast(index.internalPointer()); switch (role) { case Qt::DisplayRole: switch(index.column()) { case 0: return item->expression; case 1: return item->type; case 2: return item->value; } } return QVariant(); } QModelIndex WatchModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row,column,parent)) return QModelIndex(); WatchVar* parentItem; PWatchVar pChild; if (!parent.isValid()) { parentItem = nullptr; pChild = watchVars(mIsForProject)[row]; } else { parentItem = static_cast(parent.internalPointer()); pChild = parentItem->children[row]; } if (pChild) { return createIndex(row,column,pChild.get()); } return QModelIndex(); } static int getWatchIndex(WatchVar* var, const QList &list) { for (int i=0;i(index.internalPointer()); PWatchVar parentItem = childItem->parent.lock(); //parent is root if (parentItem == nullptr) { return QModelIndex(); } int row; PWatchVar grandItem = parentItem->parent.lock(); if (grandItem == nullptr) { row = getWatchIndex(parentItem.get(), watchVars(mIsForProject)); } else { row = getWatchIndex(parentItem.get(), grandItem->children); } return createIndex(row,0,parentItem.get()); } int WatchModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid()) { return watchVars(mIsForProject).count(); } else { WatchVar* parentItem = static_cast(parent.internalPointer()); return parentItem->children.count(); } } int WatchModel::columnCount(const QModelIndex&) const { return 3; } void WatchModel::addWatchVar(PWatchVar watchVar, bool forProject) { QList &vars=(forProject?mProjectWatchVars:mWatchVars); for (PWatchVar var:vars) { if (watchVar->expression == var->expression) { return; } } if (forProject==mIsForProject) beginInsertRows(QModelIndex(),vars.count(),vars.count()); vars.append(watchVar); if (forProject==mIsForProject) endInsertRows(); } void WatchModel::setWatchVars(const QList list, bool forProject) { if (mIsForProject == forProject) beginResetModel(); if (forProject) { mProjectWatchVars = list; } else { mWatchVars = list; } if (mIsForProject == forProject) endResetModel(); } void WatchModel::removeWatchVar(const QString &express) { QList &vars=(mIsForProject?mProjectWatchVars:mWatchVars); for (int i=vars.size()-1;i>=0;i--) { PWatchVar var = vars[i]; if (express == var->expression) { QModelIndex parentIndex = index(var->parent.lock()); beginRemoveRows(parentIndex,i,i); if (mVarIndex.contains(var->name)) mVarIndex.remove(var->name); vars.removeAt(i); endRemoveRows(); } } } void WatchModel::removeWatchVar(const QModelIndex &index) { int r=index.row(); beginRemoveRows(QModelIndex(),r,r); QList &vars=(mIsForProject?mProjectWatchVars:mWatchVars); PWatchVar var = vars[r]; if (mVarIndex.contains(var->name)) mVarIndex.remove(var->name); vars.removeAt(r); endRemoveRows(); } void WatchModel::clear() { beginResetModel(); QList &vars=(mIsForProject?mProjectWatchVars:mWatchVars); vars.clear(); endResetModel(); } void WatchModel::clear(bool forProject) { if (mIsForProject == forProject) beginResetModel(); QList &vars=(forProject?mProjectWatchVars:mWatchVars); vars.clear(); if (mIsForProject == forProject) endResetModel(); } const QList &WatchModel::watchVars() const { return watchVars(mIsForProject); } PWatchVar WatchModel::findWatchVar(const QModelIndex &index) { if (!index.isValid()) return PWatchVar(); int r=index.row(); return watchVars(mIsForProject)[r]; } PWatchVar WatchModel::findWatchVar(const QString &expr) { foreach (const PWatchVar &var, watchVars(mIsForProject)) { if (expr == var->expression) { return var; } } return PWatchVar(); } void WatchModel::resetAllVarInfos() { beginResetModel(); foreach (PWatchVar var, watchVars(mIsForProject)) { var->name.clear(); var->value = tr("Not Valid"); var->numChild = 0; var->hasMore = false; var->type.clear(); var->children.clear(); } mVarIndex.clear(); endResetModel(); } void WatchModel::updateVarInfo(const QString &expression, const QString &name, int numChild, const QString &value, const QString &type, bool hasMore) { PWatchVar var = findWatchVar(expression); if (!var) return; var->name = name; var->value = value; var->numChild = numChild; var->hasMore = hasMore; var->type = type; mVarIndex.insert(name,var); QModelIndex idx = index(var); if (!idx.isValid()) return; emit dataChanged(idx,createIndex(idx.row(),2,var.get())); } void WatchModel::prepareVarChildren(const QString &parentName, int numChild, bool hasMore) { PWatchVar var = mVarIndex.value(parentName,PWatchVar()); if (var) { var->numChild = numChild; var->hasMore = hasMore; if (var->children.count()>0) { beginRemoveRows(index(var),0,var->children.count()-1); var->children.clear(); endRemoveRows(); } } } void WatchModel::addVarChild(const QString &parentName, const QString &name, const QString &exp, int numChild, const QString &value, const QString &type, bool hasMore) { PWatchVar var = mVarIndex.value(parentName,PWatchVar()); if (!var) return; beginInsertRows(index(var),var->children.count(),var->children.count()); PWatchVar child = std::make_shared(); child->name = name; child->expression = exp; child->numChild = numChild; child->value = value; child->type = type; child->hasMore = hasMore; child->parent = var; child->timestamp = QDateTime::currentMSecsSinceEpoch(); var->children.append(child); endInsertRows(); mVarIndex.insert(name,child); } void WatchModel::updateVarValue(const QString &name, const QString &val, const QString &inScope, bool typeChanged, const QString &newType, int newNumChildren, bool hasMore) { PWatchVar var = mVarIndex.value(name,PWatchVar()); if (!var) return; if (inScope == "true") { var->value = val; } else{ var->value = tr("Not Valid"); } if (typeChanged) { var->type = newType; } QModelIndex idx = index(var); bool oldHasMore = var->hasMore; var->hasMore = hasMore; if (newNumChildren>=0 && var->numChild!=newNumChildren) { var->numChild = newNumChildren; fetchMore(idx); } else if (!oldHasMore && hasMore) { fetchMore(idx); } emit dataChanged(idx,createIndex(idx.row(),2,var.get())); } void WatchModel::updateAllHasMoreVars() { foreach (const PWatchVar& var, mVarIndex.values()) { if (var->hasMore) { QModelIndex idx = index(var); fetchMore(idx); } } } bool WatchModel::isForProject() const { return mIsForProject; } void WatchModel::setIsForProject(bool newIsForProject) { if (newIsForProject!=mIsForProject) { beginResetModel(); mVarIndex.clear(); mIsForProject=newIsForProject; endResetModel(); } } const QList &WatchModel::watchVars(bool forProject) const { return forProject?mProjectWatchVars:mWatchVars; } void WatchModel::clearAllVarInfos() { beginResetModel(); foreach (PWatchVar var, watchVars(mIsForProject)) { var->name.clear(); var->value = tr("Execute to evaluate"); var->numChild = 0; var->hasMore = false; var->type.clear(); var->children.clear(); } mVarIndex.clear(); endResetModel(); } void WatchModel::beginUpdate() { if (mUpdateCount == 0) { beginResetModel(); } mUpdateCount++; } void WatchModel::endUpdate() { mUpdateCount--; if (mUpdateCount == 0) { endResetModel(); } } void WatchModel::notifyUpdated(PWatchVar var) { if (!var) return; int row; PWatchVar parent = var->parent.lock(); if (parent==nullptr) { row = watchVars(mIsForProject).indexOf(var); } else { row = parent->children.indexOf(var); } if (row<0) return; //qDebug()<<"dataChanged"<text; emit dataChanged(createIndex(row,0,var.get()),createIndex(row,0,var.get())); } QJsonArray WatchModel::toJson(bool forProject) { QJsonArray array; foreach (const PWatchVar& watchVar, watchVars(forProject)) { QJsonObject obj; obj["expression"]=watchVar->expression; obj["timestamp"]=QString("%1").arg(watchVar->timestamp); array.append(obj); } return array; } QModelIndex WatchModel::index(PWatchVar var) const { if (!var) return QModelIndex(); return index(var.get()); } QModelIndex WatchModel::index(WatchVar* pVar) const { if (pVar==nullptr) return QModelIndex(); PWatchVar parent=pVar->parent.lock(); if (parent) { int row=-1; for (int i=0;ichildren.count();i++) { if (parent->children[i].get() == pVar) { row = i; break; } } if (row<0) return QModelIndex(); return createIndex(row,0,pVar); } else { const QList &vars=watchVars(mIsForProject); int row=-1; for (int i=0;i WatchModel::loadJson(const QJsonArray &jsonArray, qint64 criteriaTimestamp) { QList result; QJsonArray array = jsonArray; for (int i=0;icriteriaTimestamp) { PWatchVar var = std::make_shared(); var->parent= PWatchVar(); var->expression = obj["expression"].toString(); var->value = tr("Execute to evaluate"); var->numChild = 0; var->hasMore=false; var->timestamp = timestamp; result.append(var); } } return result; } bool WatchModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) { return false; } if (index.column()==2 && role == Qt::EditRole) { WatchVar* item = static_cast(index.internalPointer()); emit setWatchVarValue(item->name,value.toString()); } return false; } Qt::ItemFlags WatchModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if (!index.isValid()) { return Qt::ItemIsEnabled; } if (index.column() == 2) { WatchVar* item = static_cast(index.internalPointer()); if (item->numChild==0 && !item->type.isEmpty()) flags |= Qt::ItemIsEditable; } return flags; } QVariant WatchModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(section) { case 0: return tr("Expression"); case 1: return tr("Type"); case 2: return tr("Value"); } } return QVariant(); } void WatchModel::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { return; } WatchVar* item = static_cast(parent.internalPointer()); item->hasMore = false; item->numChild = item->children.count(); emit fetchChildren(item->name); } bool WatchModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } WatchVar* item = static_cast(parent.internalPointer()); return item->numChild>item->children.count() || item->hasMore; } bool WatchModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return true; } WatchVar* item = static_cast(parent.internalPointer()); return item->numChild>0 || item->hasMore; } RegisterModel::RegisterModel(QObject *parent):QAbstractTableModel(parent) { } int RegisterModel::rowCount(const QModelIndex &) const { return mRegisterNames.count(); } int RegisterModel::columnCount(const QModelIndex &) const { return 2; } QVariant RegisterModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row()<0 || index.row() >= static_cast(mRegisterNames.size())) return QVariant(); switch (role) { case Qt::DisplayRole: switch (index.column()) { case 0: return mRegisterNames[index.row()]; case 1: return mRegisterValues.value( mRegisterNameIndex.value(index.row(),-1) ,""); default: return QVariant(); } default: return QVariant(); } } QVariant RegisterModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch(section) { case 0: return tr("Register"); case 1: return tr("Value"); } } return QVariant(); } void RegisterModel::updateNames(const QStringList ®Names) { beginResetModel(); mRegisterNameIndex.clear(); mRegisterNames.clear(); for (int i=0;i registerValues) { mRegisterValues= registerValues; emit dataChanged(createIndex(0,1), createIndex(mRegisterNames.count()-1,1)); } void RegisterModel::clear() { beginResetModel(); mRegisterNames.clear(); mRegisterValues.clear(); endResetModel(); } DebugTarget::DebugTarget( const QString &inferior, const QString &GDBServer, int port, QObject *parent): QThread(parent), mInferior(inferior), mGDBServer(GDBServer), mPort(port), mStop(false), mStartSemaphore(0), mErrorOccured(false) { mProcess = nullptr; } void DebugTarget::setInputFile(const QString &inputFile) { mInputFile = inputFile; } void DebugTarget::stopDebug() { mStop = true; } void DebugTarget::waitStart() { mStartSemaphore.acquire(1); } const QStringList &DebugTarget::binDirs() const { return mBinDirs; } void DebugTarget::addBinDirs(const QStringList &binDirs) { mBinDirs.append(binDirs); } void DebugTarget::addBinDir(const QString &binDir) { mBinDirs.append(binDir); } void DebugTarget::run() { mStop = false; mErrorOccured = false; //find first available port QString cmd; QString arguments; #ifdef Q_OS_WIN cmd= mGDBServer; arguments = QString(" localhost:%1 \"%2\"").arg(mPort).arg(mInferior); #else cmd= pSettings->environment().terminalPath(); arguments = QString(" -e \"%1\" localhost:%2 \"%3\"").arg(mGDBServer).arg(mPort).arg(mInferior); #endif QString workingDir = QFileInfo(mInferior).path(); mProcess = std::make_shared(); auto action = finally([&]{ mProcess.reset(); }); mProcess->setProgram(cmd); mProcess->setArguments(splitProcessCommand(arguments)); mProcess->setProcessChannelMode(QProcess::MergedChannels); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); QString path = env.value("PATH"); QStringList pathAdded = mBinDirs; if (!path.isEmpty()) { path = pathAdded.join(PATH_SEPARATOR) + PATH_SEPARATOR + path; } else { path = pathAdded.join(PATH_SEPARATOR); } QString cmdDir = extractFileDir(cmd); if (!cmdDir.isEmpty()) { path = cmdDir + PATH_SEPARATOR + path; } env.insert("PATH",path); mProcess->setProcessEnvironment(env); mProcess->setWorkingDirectory(workingDir); #ifdef Q_OS_WIN mProcess->setCreateProcessArgumentsModifier([this](QProcess::CreateProcessArguments * args){ if (programHasConsole(mInferior)) { args->flags |= CREATE_NEW_CONSOLE; args->flags &= ~CREATE_NO_WINDOW; } if (mInputFile.isEmpty()) { args->startupInfo -> dwFlags &= ~STARTF_USESTDHANDLES; } else { args->startupInfo->hStdOutput = NULL; args->startupInfo->hStdError = NULL; } }); #endif connect(mProcess.get(), &QProcess::errorOccurred, [&](){ mErrorOccured= true; }); mProcess->start(); mProcess->waitForStarted(5000); mStartSemaphore.release(1); if (mProcess->state()==QProcess::Running && !mInputFile.isEmpty()) { mProcess->write(readFileToByteArray(mInputFile)); mProcess->waitForFinished(0); } bool writeChannelClosed = false; while (true) { if (mProcess->bytesToWrite()==0 && !writeChannelClosed) { writeChannelClosed = true; mProcess->closeWriteChannel(); } mProcess->waitForFinished(1); if (mProcess->state()!=QProcess::Running) { break; } if (mStop) { mProcess->terminate(); mProcess->kill(); break; } if (mErrorOccured) break; msleep(1); } if (mErrorOccured) { emit processError(mProcess->error()); } } MemoryModel::MemoryModel(int dataPerLine, QObject *parent): QAbstractTableModel(parent), mDataPerLine(dataPerLine), mStartAddress(0) { } void MemoryModel::updateMemory(const QStringList &value) { int maxDataPerLine=-1; QRegExp delimiter("(\\s+)"); QList newModel; for (int i=0;i= QT_VERSION_CHECK(5, 15, 0) QStringList dataLst = line.split(delimiter,Qt::SkipEmptyParts); #else QStringList dataLst = line.split(delimiter,QString::SkipEmptyParts); #endif PMemoryLine memoryLine = std::make_shared(); memoryLine->startAddress = -1; if (dataLst.length()>0) { bool isOk; memoryLine->startAddress = stringToHex(dataLst[0],isOk); if (isOk) { if (dataLst.length()-1>maxDataPerLine) maxDataPerLine = dataLst.length()-1; for (int j=1;jdatas.append((unsigned char)data); else memoryLine->datas.append(0); } } else { memoryLine->startAddress=0; } } newModel.append(memoryLine); } if (newModel.count()>0 && newModel.count()== mLines.count() && newModel[0]->startAddress == mLines[0]->startAddress && maxDataPerLine==mDataPerLine) { for (int i=0;idatas.count();j++) { if (j>=oldLine->datas.count()) break; if (newLine->datas[j]!=oldLine->datas[j]) newLine->changedDatas.insert(j); } } mLines = newModel; emit dataChanged(createIndex(0,0), createIndex(mLines.count()-1,mDataPerLine-1)); } else { beginResetModel(); if (maxDataPerLine>0) mDataPerLine=maxDataPerLine; mLines = newModel; endResetModel(); } if (mLines.count()>0) { mStartAddress = mLines[0]->startAddress; } else { mStartAddress = 0; } } int MemoryModel::rowCount(const QModelIndex &/*parent*/) const { return mLines.count(); } int MemoryModel::columnCount(const QModelIndex &/*parent*/) const { return mDataPerLine; } QVariant MemoryModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); if (index.row()<0 || index.row()>=mLines.count()) return QVariant(); PMemoryLine line = mLines[index.row()]; int col = index.column(); if (col<0 || col>=line->datas.count()) return QVariant(); if (role == Qt::DisplayRole) return QString("%1").arg(line->datas[col],2,16,QChar('0')); return QVariant(); } QVariant MemoryModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical && role == Qt::DisplayRole) { if (section<0 || section>=mLines.count()) return QVariant(); PMemoryLine line = mLines[section]; return QString("0x%1").arg(line->startAddress,0,16,QChar('0')); } return QVariant(); } bool MemoryModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!index.isValid()) return false; if (index.row()<0 || index.row()>=mLines.count()) return false; PMemoryLine line = mLines[index.row()]; int col = index.column(); if (col<0 || col>=line->datas.count()) return false; if (role == Qt::EditRole && mStartAddress>0) { bool ok; unsigned char val = ("0x"+value.toString()).toUInt(&ok,16); if (!ok) return false; emit setMemoryData(mStartAddress+mDataPerLine*index.row()+col,val); return true; } return false; } Qt::ItemFlags MemoryModel::flags(const QModelIndex &/*index*/) const { Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; if (mStartAddress!=0) flags |= Qt::ItemIsEditable; return flags; } qulonglong MemoryModel::startAddress() const { return mStartAddress; } void MemoryModel::reset() { mStartAddress=0; mLines.clear(); }