RedPanda-CPP/RedPandaIDE/debugger.cpp

3375 lines
106 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "debugger.h"
#include "utils.h"
#include "utils/parsearg.h"
#include "mainwindow.h"
#include "editor.h"
#include "settings.h"
#include "widgets/cpudialog.h"
#include "systemconsts.h"
#include "editorlist.h"
#include <QFile>
#include <QFileInfo>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QDebug>
#include <QDir>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include "widgets/signalmessagedialog.h"
Debugger::Debugger(QObject *parent) : QObject(parent),
mForceUTF8(false),
mDebuggerType(DebuggerType::GDB),
mLastLoadtime(0),
mProjectLastLoadtime(0)
{
//models deleted in the destructor
mBreakpointModel= std::make_shared<BreakpointModel>(this);
mBacktraceModel = std::make_shared<BacktraceModel>(this);
mWatchModel = std::make_shared<WatchModel>(this);
mRegisterModel = std::make_shared<RegisterModel>(this);
mMemoryModel = std::make_shared<MemoryModel>(16,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, const QString& sourceFile)
{
mCurrentSourceFile = sourceFile;
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(compilerSet->forceUTF8());
setDebugInfosUsingUTF8(compilerSet->isDebugInfoUsingUTF8());
if (compilerSet->debugger().endsWith(LLDB_MI_PROGRAM))
setDebuggerType(DebuggerType::LLDB_MI);
else
setDebuggerType(DebuggerType::GDB);
// force to lldb-server if using lldb-mi, which creates new console but does not bind inferiors stdio to the new console on Windows.
setUseDebugServer(pSettings->debugger().useGDBServer() || debuggerType() == DebuggerType::LLDB_MI);
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)
// + "<br />"
// + 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)
+"<br />"
+tr("Please check the \"program\" page of compiler settings."));
return false;
}
if (useDebugServer()) {
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())
+ "<br />"
+ 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 (useDebugServer()) {
//deleted when thread finished
QStringList params;
if (pSettings->executor().useParams())
params = parseArgumentsWithoutVariables(pSettings->executor().params());
mTarget = new DebugTarget(inferior,compilerSet->debugServer(),pSettings->debugger().GDBServerPort(),params);
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::watchpointHitted,pMainWindow,
&MainWindow::onWatchpointHitted);
connect(mReader, &DebugReader::errorNoSymbolTable,pMainWindow,
&MainWindow::stopDebugForNoSymbolTable);
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();
}
mCurrentSourceFile="";
}
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 &registerNames)
{
mRegisterModel->updateNames(registerNames);
}
void Debugger::updateRegisterValues(const QHash<int, QString> &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<RegisterModel> Debugger::registerModel() const
{
return mRegisterModel;
}
std::shared_ptr<WatchModel> Debugger::watchModel() const
{
return mWatchModel;
}
void Debugger::sendCommand(const QString &command, const QString &params, 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<Breakpoint>();
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) {
if (forProject && mBreakpointModel->isForProject()) {
sendBreakpointCommand(bp);
} else if (filename == mCurrentSourceFile) {
sendBreakpointCommand(bp);
}
}
}
void Debugger::deleteBreakpoints(const QString &filename, bool forProject)
{
const QList<PBreakpoint>& 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::deleteInvalidProjectBreakpoints(const QSet<QString> unitFiles)
{
for(int i=mBreakpointModel->breakpoints(true).count()-1;i>=0;i--) {
const PBreakpoint& bp=mBreakpointModel->breakpoint(i,true);
if (!unitFiles.contains(bp->filename))
mBreakpointModel->removeBreakpoint(i, true);
}
}
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<PBreakpoint>& 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<PBreakpoint>& breakpoints=mBreakpointModel->breakpoints(forProject);
for (*index=0;*index<breakpoints.count();(*index)++){
PBreakpoint breakpoint = breakpoints[*index];
if (breakpoint->line == 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())) {
if (mBreakpointModel->isForProject()) {
sendBreakpointCommand(breakpoint);
} else if (breakpoint->filename == mCurrentSourceFile) {
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::addWatchpoint(const QString &expression)
{
QString s=expression.trimmed();
if (!s.isEmpty()) {
sendCommand("-break-watch",s,DebugCommandSource::Other);
}
}
void Debugger::addWatchVar(const QString &expression)
{
// Don't allow duplicates...
PWatchVar oldVar = mWatchModel->findWatchVar(expression);
if (oldVar)
return;
PWatchVar var = std::make_shared<WatchVar>();
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();
if (mDebuggerType==DebuggerType::LLDB_MI) {
for (PWatchVar var:mWatchModel->watchVars()) {
if (!var->name.isEmpty())
sendCommand("-var-update",QString(" --all-values %1").arg(var->name));
}
} else {
sendCommand("-var-update"," --all-values *");
}
}
}
void Debugger::fetchVarChildren(const QString &varName)
{
if (mExecuting) {
sendCommand("-var-list-children",varName);
}
}
bool Debugger::useDebugServer() const
{
return mUseDebugServer;
}
void Debugger::setUseDebugServer(bool newUseDebugServer)
{
mUseDebugServer = newUseDebugServer;
}
bool Debugger::debugInfosUsingUTF8() const
{
return mDebugInfosUsingUTF8;
}
void Debugger::setDebugInfosUsingUTF8(bool newDebugInfosUsingUTF8)
{
mDebugInfosUsingUTF8 = newDebugInfosUsingUTF8;
}
DebuggerType Debugger::debuggerType() const
{
return mDebuggerType;
}
void Debugger::setDebuggerType(DebuggerType newDebuggerType)
{
mDebuggerType = newDebuggerType;
}
bool Debugger::forceUTF8() const
{
return mForceUTF8;
}
void Debugger::setForceUTF8(bool newForceUTF8)
{
mForceUTF8 = newForceUTF8;
}
std::shared_ptr<MemoryModel> 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<BacktraceModel> Debugger::backtraceModel()
{
return mBacktraceModel;
}
std::shared_ptr<BreakpointModel> 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('\\','/');
if (debuggerType()==DebuggerType::LLDB_MI) {
sendCommand("-break-insert",
QString("%1 \"%2:%3\"")
.arg(condition, filename)
.arg(breakpoint->line));
} else {
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<int>(breakpoint->breakpointType);
obj["timestamp"]=QString("%1").arg(breakpoint->timestamp);
array.append(obj);
}
return array;
}
void BreakpointModel::setBreakpoints(const QList<PBreakpoint> &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<PBreakpoint> breakpoints;
QList<PWatchVar> watchVars=mWatchModel->watchVars(forProject);
QSet<QString> breakpointCompareSet;
QSet<QString> 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<DebugConfig> pConfig;
try {
pConfig = load(filename, forProject);
} catch (FileError& e) {
}
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);
if (forProject) {
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<DebugConfig> pConfig=std::make_shared<DebugConfig>();
pConfig->timestamp=0;
QFile file(filename);
if (!file.exists())
return pConfig;
if (file.open(QFile::ReadOnly)) {
QByteArray content = file.readAll().trimmed();
if (content.isEmpty())
return pConfig;
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.")+"<BR /><BR />" + 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())
+ "<br />"
+ 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,mBacktraceModel->backtraces());
}
}
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<QProcess>();
mCmdRunning = false;
mAsyncUpdated = false;
}
void DebugReader::postCommand(const QString &Command, const QString &Params,
DebugCommandSource Source)
{
QMutexLocker locker(&mCmdQueueMutex);
PDebugCommand pCmd = std::make_shared<DebugCommand>();
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<DebugCommand>();
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));
mConsoleOutput.append(QString::fromUtf8(stringValue));
}
}
void DebugReader::processLogOutput(const QByteArray &line)
{
if (mDebugger->debugInfosUsingUTF8() && line.endsWith(": No such file or directory.\n\"")) {
QByteArray newLine = line;
newLine[0]='~';
int p=newLine.lastIndexOf(':');
if (p>0) {
newLine=newLine.left(p);
//qDebug()<<newLine;
processConsoleOutput(newLine);
}
}
}
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::Locals:
break;
case GDBMIResultType::Breakpoint:
handleBreakpoint(multiValues["bkpt"].object());
return;
case GDBMIResultType::Frame:
handleFrame(multiValues["frame"]);
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;
handleFrame(multiValues["frame"]);
if (reason == "signal-received") {
mSignalReceived = true;
mSignalName = multiValues["signal-name"].value();
mSignalMeaning = multiValues["signal-meaning"].value();
} else if (reason == "watchpoint-trigger") {
QString var,oldVal,newVal;
GDBMIResultParser::ParseValue wpt=multiValues["wpt"];
if (wpt.isValid()) {
GDBMIResultParser::ParseObject wptObj = wpt.object();
var=wptObj["exp"].value();
}
GDBMIResultParser::ParseValue varValue=multiValues["value"];
if (varValue.isValid()) {
GDBMIResultParser::ParseObject valueObj = varValue.object();
oldVal=valueObj["old"].value();
newVal=valueObj["new"].value();
}
if (!var.isEmpty()) {
emit watchpointHitted(var,oldVal,newVal);
}
}
runInferiorStoppedHook();
if (reason.isEmpty()) {
QMutexLocker locker(&mCmdQueueMutex);
foreach (const PDebugCommand& cmd, mCmdQueue) {
//gdb-server connected, just ignore it
if (cmd->command=="-exec-continue")
return;
}
}
if (mCurrentCmd && mCurrentCmd->source == DebugCommandSource::Console)
emit inferiorStopped(mCurrentFile, mCurrentLine, false);
else
emit inferiorStopped(mCurrentFile, mCurrentLine, true);
}
}
void DebugReader::processError(const QByteArray &errorLine)
{
QString s = QString::fromLocal8Bit(errorLine);
mConsoleOutput.append(s);
int idx=s.indexOf(",msg=\"No symbol table is loaded");
if (idx>0) {
emit errorNoSymbolTable();
return;
}
}
static QRegularExpression reGdbSourceLine("^(\\d)+\\s+in\\s+(.+)$");
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();
}
if (mDebugger->debugInfosUsingUTF8()) {
QStringList newOutput;
foreach(const QString& s, disOutput) {
QString line = s;
if (!s.isEmpty() && s.front().isDigit()) {
QRegularExpressionMatch match = reGdbSourceLine.match(s);
// qDebug()<<s;
if (match.hasMatch()) {
bool isOk;
int lineno=match.captured(1).toInt(&isOk)-1;;
QString filename = match.captured(2).trimmed();
if (isOk && fileExists(filename)) {
QStringList contents;
if (mFileCache.contains(filename))
contents = mFileCache.value(filename);
else {
if (!pMainWindow->editorList()->getContentFromOpenedEditor(filename,contents))
contents = readFileToLines(filename);
mFileCache[filename]=contents;
}
if (lineno>=0 && lineno<contents.size()) {
line = QString("%1\t%2").arg(lineno+1).arg(contents[lineno]);
}
}
}
}
newOutput.append(line);
}
disOutput=newOutput;
}
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<QByteArray> lines = splitByteArrayToLines(debugOutput);
for (int i=0;i<lines.count();i++) {
QByteArray line = lines[i];
if (pSettings->debugger().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
break;
case '&': // log stream output
processLogOutput(line);
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()
{
QMutexLocker locker(&mCmdQueueMutex);
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 (mDebugger->useDebugServer() && 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 (mDebugger->forceUTF8()) {
params = pCmd->params.toUtf8();
} else if (mDebugger->debugInfosUsingUTF8() &&
(pCmd->command=="-break-insert"
|| pCmd->command=="-var-create"
|| pCmd->command=="-data-read-memory"
|| pCmd->command=="-data-evaluate-expression"
)) {
params = pCmd->params.toUtf8();
}
if (pCmd->command == "-var-create") {
//hack for variable creation,to easy remember var expression
if (mDebugger->debuggerType()==DebuggerType::LLDB_MI)
params = " - * "+params;
else
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<s.length()) {
QChar ch = s[i];
if (ch == ' ' || ch == '\t'
|| ch == '\r'
|| ch == '\n') {
// if (!current.isEmpty()) {
// result.append(current);
// current = "";
// }
i++;
continue;
} else if (ch == '\'') {
tStart = i;
i++; //skip \'
while (i<s.length()) {
if (s[i]=='\'') {
i++;
break;
} else if (s[i] == '\\') {
i+=2;
continue;
}
i++;
}
tEnd = std::min(i,s.length());
result.append(s.mid(tStart,tEnd-tStart));
} else if (ch == '\"') {
tStart = i;
i++; //skip \'
while (i<s.length()) {
if (s[i]=='\"') {
i++;
break;
} else if (s[i] == '\\') {
i+=2;
continue;
}
i++;
}
tEnd = std::min(i,s.length());
result.append(s.mid(tStart,tEnd-tStart));
} else if (ch == '<') {
tStart = i;
i++;
while (i<s.length()) {
if (s[i]=='>') {
i++;
break;
}
i++;
}
tEnd = std::min(i,s.length());
result.append(s.mid(tStart,tEnd-tStart));
} else if (ch == '(') {
tStart = i;
i++;
while (i<s.length()) {
if (s[i]==')') {
i++;
break;
}
i++;
}
tEnd = std::min(i,s.length());
result.append(s.mid(tStart,tEnd-tStart));
} else if (ch == '_' ||
ch == '.' ||
ch == '+' ||
ch == '-' ||
ch.isLetterOrNumber() ) {
tStart = i;
while (i<s.length()) {
ch = s[i];
if (!(ch == '_' ||
ch == '.' ||
ch == '+' ||
ch == '-' ||
ch.isLetterOrNumber() ))
break;
i++;
}
tEnd = std::min(i,s.length());
result.append(s.mid(tStart,tEnd-tStart));
} else {
result.append(s[i]);
i++;
}
}
return result;
}
bool DebugReader::outputTerminated(const QByteArray &text)
{
QStringList lines = textToLines(QString::fromUtf8(text));
foreach (const QString& line,lines) {
if (line.trimmed() == "(gdb)")
return true;
}
return false;
}
void DebugReader::handleBreakpoint(const GDBMIResultParser::ParseObject& breakpoint)
{
QString filename;
// gdb use system encoding for file path
if (mDebugger->forceUTF8() || mDebugger->debugInfosUsingUTF8())
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::handleFrame(const GDBMIResultParser::ParseValue &frame)
{
if (frame.isValid()) {
GDBMIResultParser::ParseObject frameObj = frame.object();
bool ok;
mCurrentAddress = frameObj["addr"].hexValue(ok);
if (!ok)
mCurrentAddress=0;
mCurrentLine = frameObj["line"].intValue();
if (mDebugger->forceUTF8()
|| mDebugger->debugInfosUsingUTF8())
mCurrentFile = frameObj["fullname"].utf8PathValue();
else
mCurrentFile = frameObj["fullname"].pathValue();
mCurrentFunc = frameObj["func"].value();
}
}
void DebugReader::handleStack(const QList<GDBMIResultParser::ParseValue> & stack)
{
mDebugger->backtraceModel()->clear();
foreach (const GDBMIResultParser::ParseValue& frameValue, stack) {
GDBMIResultParser::ParseObject frameObject = frameValue.object();
PTrace trace = std::make_shared<Trace>();
trace->funcname = frameObject["func"].value();
if (mDebugger->forceUTF8() || mDebugger->debugInfosUsingUTF8())
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<GDBMIResultParser::ParseValue> &variables)
{
QStringList locals;
foreach (const GDBMIResultParser::ParseValue& varValue, variables) {
GDBMIResultParser::ParseObject varObject = varValue.object();
QString name = QString(varObject["name"].value());
QString value = QString(varObject["value"].value());
locals.append(
QString("%1 = %2")
.arg(
name,
value
));
}
emit localsUpdated(locals);
}
void DebugReader::handleEvaluation(const QString &value)
{
emit evalUpdated(value);
}
void DebugReader::handleMemory(const QList<GDBMIResultParser::ParseValue> &rows)
{
QStringList memory;
foreach (const GDBMIResultParser::ParseValue& row, rows) {
GDBMIResultParser::ParseObject rowObject = row.object();
QList<GDBMIResultParser::ParseValue> 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<GDBMIResultParser::ParseValue> &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<GDBMIResultParser::ParseValue> &values)
{
QHash<int,QString> 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<GDBMIResultParser::ParseValue> 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<GDBMIResultParser::ParseValue> &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<line.length()) {
QChar ch=line[p];
if (ch<'0' || ch>'9') {
break;
}
p++;
}
if (p<line.length())
return line.mid(p);
return line;
}
void DebugReader::asyncUpdate()
{
QMutexLocker locker(&mCmdQueueMutex);
if (mCmdQueue.isEmpty()) {
postCommand("-var-update"," --all-values *",DebugCommandSource::HeartBeat);
}
mAsyncUpdated = false;
}
const QStringList &DebugReader::binDirs() const
{
return mBinDirs;
}
void DebugReader::addBinDirs(const QStringList &binDirs)
{
mBinDirs.append(binDirs);
}
void DebugReader::addBinDir(const QString &binDir)
{
mBinDirs.append(binDir);
}
const QString &DebugReader::signalMeaning() const
{
return mSignalMeaning;
}
const QString &DebugReader::signalName() const
{
return mSignalName;
}
bool DebugReader::inferiorRunning() const
{
return mInferiorRunning;
}
const QStringList &DebugReader::fullOutput() const
{
return mFullOutput;
}
bool DebugReader::receivedSFWarning() const
{
return mReceivedSFWarning;
}
bool DebugReader::updateCPUInfo() const
{
return mUpdateCPUInfo;
}
const PDebugCommand &DebugReader::currentCmd() const
{
return mCurrentCmd;
}
const QStringList &DebugReader::consoleOutput() const
{
return mConsoleOutput;
}
bool DebugReader::signalReceived() const
{
return mSignalReceived;
}
bool DebugReader::processExited() const
{
return mProcessExited;
}
QString DebugReader::debuggerPath() const
{
return mDebuggerPath;
}
void DebugReader::setDebuggerPath(const QString &debuggerPath)
{
mDebuggerPath = debuggerPath;
}
void DebugReader::stopDebug()
{
mStop = true;
}
bool DebugReader::commandRunning()
{
return !mCmdQueue.isEmpty();
}
void DebugReader::waitStart()
{
mStartSemaphore.acquire(1);
}
void DebugReader::run()
{
mStop = false;
mInferiorRunning = false;
mProcessExited = false;
mErrorOccured = false;
QString cmd = mDebuggerPath;
// QString arguments = "--annotate=2";
QStringList arguments{"--interpret=mi", "--silent"};
QString workingDir = QFileInfo(mDebuggerPath).path();
mProcess = std::make_shared<QProcess>();
auto action = finally([&]{
mProcess.reset();
});
mProcess->setProgram(cmd);
mProcess->setArguments(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<PBreakpoint> &list=breakpoints(mIsForProject);
if (index.row()<0 || index.row() >= static_cast<int>(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<PBreakpoint> & 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<PBreakpoint> & 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<PBreakpoint> 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<PBreakpoint> &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<PBreakpoint> &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<PBreakpoint> BreakpointModel::loadJson(const QJsonArray& jsonArray, qint64 criteriaTime)
{
QList<PBreakpoint> result;
for (int i=0;i<jsonArray.count();i++) {
QJsonValue value = jsonArray[i];
QJsonObject obj=value.toObject();
bool ok;
qint64 timestamp = obj["timestamp"].toString().toLongLong(&ok);
if (ok && timestamp > criteriaTime) {
PBreakpoint breakpoint = std::make_shared<Breakpoint>();
breakpoint->filename = obj["filename"].toString();
breakpoint->line = obj["line"].toInt();
breakpoint->condition = obj["condition"].toString();
breakpoint->enabled = obj["enabled"].toBool();
breakpoint->breakpointType = static_cast<BreakpointType>(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<int>(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<PTrace> &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<WatchVar*>(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<WatchVar*>(parent.internalPointer());
pChild = parentItem->children[row];
}
if (pChild) {
return createIndex(row,column,pChild.get());
}
return QModelIndex();
}
static int getWatchIndex(WatchVar* var, const QList<PWatchVar> &list) {
for (int i=0;i<list.size();i++) {
PWatchVar v = list[i];
if (v.get() == var) {
return i;
}
}
return -1;
}
QModelIndex WatchModel::parent(const QModelIndex &index) const
{
if (!index.isValid()) {
return QModelIndex();
}
WatchVar* childItem = static_cast<WatchVar*>(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<WatchVar*>(parent.internalPointer());
return parentItem->children.count();
}
}
int WatchModel::columnCount(const QModelIndex&) const
{
return 3;
}
void WatchModel::addWatchVar(PWatchVar watchVar, bool forProject)
{
QList<PWatchVar> &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<PWatchVar> 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<PWatchVar> &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<PWatchVar> &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<PWatchVar> &vars=(mIsForProject?mProjectWatchVars:mWatchVars);
vars.clear();
endResetModel();
}
void WatchModel::clear(bool forProject)
{
if (mIsForProject == forProject)
beginResetModel();
QList<PWatchVar> &vars=(forProject?mProjectWatchVars:mWatchVars);
vars.clear();
if (mIsForProject == forProject)
endResetModel();
}
const QList<PWatchVar> &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<WatchVar>();
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<PWatchVar> &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"<<row<<":"<<var->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;i<parent->children.count();i++) {
if (parent->children[i].get() == pVar) {
row = i;
break;
}
}
if (row<0)
return QModelIndex();
return createIndex(row,0,pVar);
} else {
const QList<PWatchVar> &vars=watchVars(mIsForProject);
int row=-1;
for (int i=0;i<vars.count();i++) {
if (vars[i].get() == pVar) {
row = i;
break;
}
}
if (row<0)
return QModelIndex();
return createIndex(row,0,pVar);
}
}
QList<PWatchVar> WatchModel::loadJson(const QJsonArray &jsonArray, qint64 criteriaTimestamp)
{
QList<PWatchVar> result;
QJsonArray array = jsonArray;
for (int i=0;i<jsonArray.count();i++) {
QJsonValue value = array[i];
QJsonObject obj=value.toObject();
bool ok;
qint64 timestamp = obj["timestamp"].toString().toLongLong(&ok);
if (ok && timestamp>criteriaTimestamp) {
PWatchVar var = std::make_shared<WatchVar>();
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<WatchVar*>(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<WatchVar*>(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<WatchVar*>(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<WatchVar*>(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<WatchVar*>(parent.internalPointer());
return item->numChild>0 || item->hasMore;
}
RegisterModel::RegisterModel(QObject *parent):QAbstractTableModel(parent)
{
#if defined(ARCH_X86_64) || defined(ARCH_X86)
//https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
mRegisterDescriptions.insert("rax",tr("64-bit")+" "+tr("Accumulator for operands and results data"));
mRegisterDescriptions.insert("rbx",tr("64-bit")+" "+tr("Pointer to data in the DS segment"));
mRegisterDescriptions.insert("rcx",tr("64-bit")+" "+tr("Counter for string and loop operations"));
mRegisterDescriptions.insert("rdx",tr("64-bit")+" "+tr("I/O pointer"));
mRegisterDescriptions.insert("rsi",tr("64-bit")+" "+tr("Source index for string operations; Pointer to data in the segment pointed to by the DS register"));
mRegisterDescriptions.insert("rdi",tr("64-bit")+" "+tr("Destination index for string operations; Pointer to data (or destination) in the segment pointed to by the ES register"));
mRegisterDescriptions.insert("rsp",tr("64-bit")+" "+tr("Stack pointer (in the SS segment)"));
mRegisterDescriptions.insert("rbp",tr("64-bit")+" "+tr("Pointer to data on the stack (in the SS segment)"));
mRegisterDescriptions.insert("r8",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r9",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r10",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r11",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r12",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r13",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r14",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r15",tr("64-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("rip",tr("64-bit")+" "+tr("Instruction pointer"));
mRegisterDescriptions.insert("rflags",tr("Flags"));
mRegisterDescriptions.insert("eflags",tr("Flags"));
mRegisterDescriptions.insert("eax",tr("32-bit")+" "+tr("Accumulator for operands and results data"));
mRegisterDescriptions.insert("ebx",tr("32-bit")+" "+tr("Pointer to data in the DS segment"));
mRegisterDescriptions.insert("ecx",tr("32-bit")+" "+tr("Counter for string and loop operations"));
mRegisterDescriptions.insert("edx",tr("32-bit")+" "+tr("I/O pointer"));
mRegisterDescriptions.insert("esi",tr("32-bit")+" "+tr("Source index for string operations; Pointer to data in the segment pointed to by the DS register"));
mRegisterDescriptions.insert("edi",tr("32-bit")+" "+tr("Destination index for string operations; Pointer to data (or destination) in the segment pointed to by the ES register"));
mRegisterDescriptions.insert("esp",tr("32-bit")+" "+tr("Stack pointer (in the SS segment)"));
mRegisterDescriptions.insert("ebp",tr("32-bit")+" "+tr("Pointer to data on the stack (in the SS segment)"));
mRegisterDescriptions.insert("r8d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r9d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r10d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r11d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r12d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r13d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r14d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("r15d",tr("32-bit")+" "+tr("General purpose"));
mRegisterDescriptions.insert("eip",tr("32-bit")+" "+tr("Instruction pointer"));
mRegisterDescriptions.insert("ax",tr("lower 16 bits of %1").arg("rax/eax"));
mRegisterDescriptions.insert("bx",tr("lower 16 bits of %1").arg("rbx/rbx"));
mRegisterDescriptions.insert("cx",tr("lower 16 bits of %1").arg("rcx/ecx"));
mRegisterDescriptions.insert("dx",tr("lower 16 bits of %1").arg("rdx/edx"));
mRegisterDescriptions.insert("si",tr("lower 16 bits of %1").arg("rsi/esi"));
mRegisterDescriptions.insert("di",tr("lower 16 bits of %1").arg("rdi/edi"));
mRegisterDescriptions.insert("sp",tr("lower 16 bits of %1").arg("rsp/esp"));
mRegisterDescriptions.insert("bp",tr("lower 16 bits of %1").arg("rbp/esp"));
mRegisterDescriptions.insert("r8w",tr("lower 16 bits of %1").arg("r8"));
mRegisterDescriptions.insert("r9w",tr("lower 16 bits of %1").arg("r9"));
mRegisterDescriptions.insert("r10w",tr("lower 16 bits of %1").arg("r10"));
mRegisterDescriptions.insert("r11w",tr("lower 16 bits of %1").arg("r11"));
mRegisterDescriptions.insert("r12w",tr("lower 16 bits of %1").arg("r12"));
mRegisterDescriptions.insert("r13w",tr("lower 16 bits of %1").arg("r13"));
mRegisterDescriptions.insert("r14w",tr("lower 16 bits of %1").arg("r14"));
mRegisterDescriptions.insert("r15w",tr("lower 16 bits of %1").arg("r15"));
mRegisterDescriptions.insert("ip",tr("lower 16 bits of %1").arg("rip/eip"));
mRegisterDescriptions.insert("al",tr("lower 8 bits of %1").arg("rax/eax"));
mRegisterDescriptions.insert("bl",tr("lower 8 bits of %1").arg("rbx/rbx"));
mRegisterDescriptions.insert("cl",tr("lower 8 bits of %1").arg("rcx/ecx"));
mRegisterDescriptions.insert("dl",tr("lower 8 bits of %1").arg("rdx/edx"));
mRegisterDescriptions.insert("sil",tr("lower 8 bits of %1").arg("rsi/esi"));
mRegisterDescriptions.insert("dil",tr("lower 8 bits of %1").arg("rdi/edi"));
mRegisterDescriptions.insert("spl",tr("lower 8 bits of %1").arg("rsp/esp"));
mRegisterDescriptions.insert("bpl",tr("lower 8 bits of %1").arg("rbp/esp"));
mRegisterDescriptions.insert("r8b",tr("lower 8 bits of %1").arg("r8"));
mRegisterDescriptions.insert("r9b",tr("lower 8 bits of %1").arg("r9"));
mRegisterDescriptions.insert("r10b",tr("lower 8 bits of %1").arg("r10"));
mRegisterDescriptions.insert("r11b",tr("lower 8 bits of %1").arg("r11"));
mRegisterDescriptions.insert("r12b",tr("lower 8 bits of %1").arg("r12"));
mRegisterDescriptions.insert("r13b",tr("lower 8 bits of %1").arg("r13"));
mRegisterDescriptions.insert("r14b",tr("lower 8 bits of %1").arg("r14"));
mRegisterDescriptions.insert("r15b",tr("lower 8 bits of %1").arg("r15"));
mRegisterDescriptions.insert("ah",tr("8 high bits of lower 16 bits of %1").arg("rax/eax"));
mRegisterDescriptions.insert("bh",tr("8 high bits of lower 16 bits of %1").arg("rbx/rbx"));
mRegisterDescriptions.insert("ch",tr("8 high bits of lower 16 bits of %1").arg("rcx/ecx"));
mRegisterDescriptions.insert("dh",tr("8 high bits of lower 16 bits of %1").arg("rdx/edx"));
mRegisterDescriptions.insert("cs",tr("16-bit")+" "+tr("Code segment selector"));
mRegisterDescriptions.insert("ds",tr("16-bit")+" "+tr("Data segment selector"));
mRegisterDescriptions.insert("es",tr("16-bit")+" "+tr("Extra data segment selector"));
mRegisterDescriptions.insert("fs",tr("16-bit")+" "+tr("Extra data segment selector"));
mRegisterDescriptions.insert("gs",tr("16-bit")+" "+tr("Extra data segment selector"));
mRegisterDescriptions.insert("ss",tr("16-bit")+" "+tr("Stack segment selector"));
//x87 fpu
mRegisterDescriptions.insert("st0",tr("Floating-point data"));
mRegisterDescriptions.insert("st1",tr("Floating-point data"));
mRegisterDescriptions.insert("st2",tr("Floating-point data"));
mRegisterDescriptions.insert("st3",tr("Floating-point data"));
mRegisterDescriptions.insert("st4",tr("Floating-point data"));
mRegisterDescriptions.insert("st5",tr("Floating-point data"));
mRegisterDescriptions.insert("st6",tr("Floating-point data"));
mRegisterDescriptions.insert("st7",tr("Floating-point data"));
mRegisterDescriptions.insert("fctrl",tr("Floating-point control"));
mRegisterDescriptions.insert("fstat",tr("Floating-point status"));
mRegisterDescriptions.insert("ftag",tr("Floating-point tag word"));
mRegisterDescriptions.insert("fop",tr("Floating-point operation"));
mRegisterDescriptions.insert("fiseg",tr("Floating-point last instruction segment"));
mRegisterDescriptions.insert("fioff",tr("Floating-point last instruction offset"));
mRegisterDescriptions.insert("foseg",tr("Floating-point last operand segment"));
mRegisterDescriptions.insert("fooff",tr("Floating-point last operand offset"));
mRegisterDescriptions.insert("mm0",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm1",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm2",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm3",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm4",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm5",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm6",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("mm7",tr("64-bit")+" "+"MMX");
mRegisterDescriptions.insert("xmm0",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm1",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm2",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm3",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm4",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm5",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm6",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm7",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm8",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm9",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm11",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm12",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm13",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm14",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("xmm15",tr("128-bit")+" "+"XMM");
mRegisterDescriptions.insert("ymm0",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm1",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm2",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm3",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm4",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm5",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm6",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm7",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm8",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm9",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm11",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm12",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm13",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm14",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("ymm15",tr("256-bit")+" "+"YMM");
mRegisterDescriptions.insert("mxscr",tr("SSE status and control"));
#endif
}
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<int>(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)
,"");
}
break;
case Qt::ToolTipRole:
switch (index.column()) {
case 0:
return mRegisterDescriptions.value(mRegisterNames[index.row()],"");
case 1:
return mRegisterValues.value(
mRegisterNameIndex.value(index.row(),-1)
,"");
}
break;
default:
break;
}
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 &regNames)
{
beginResetModel();
mRegisterNameIndex.clear();
mRegisterNames.clear();
for (int i=0;i<regNames.length();i++) {
QString regName = regNames[i].trimmed();
if (!regName.isEmpty()) {
mRegisterNames.append(regNames[i]);
mRegisterNameIndex.insert(mRegisterNames.count()-1,i);
}
}
endResetModel();
}
void RegisterModel::updateValues(const QHash<int, QString> 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,
const QStringList& arguments,
QObject *parent):
QThread(parent),
mInferior(inferior),
mArguments(arguments),
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
QStringList execArgs;
if (mGDBServer.endsWith(LLDB_SERVER_PROGRAM))
execArgs = QStringList{
mGDBServer,
"gdbserver",
QString("localhost:%1").arg(mPort),
mInferior,
} + mArguments;
else
execArgs = QStringList{
mGDBServer,
QString("localhost:%1").arg(mPort),
mInferior,
} + mArguments;
QString cmd;
QStringList arguments;
std::unique_ptr<QTemporaryFile> fileOwner;
#ifdef Q_OS_WIN
if (pSettings->environment().useCustomTerminal()) {
std::tie(cmd, arguments, fileOwner) = wrapCommandForTerminalEmulator(
pSettings->environment().terminalPath(),
pSettings->environment().terminalArgumentsPattern(),
execArgs
);
} else {
cmd = execArgs[0];
arguments = execArgs.mid(1);
}
#else
std::tie(cmd, arguments, fileOwner) = wrapCommandForTerminalEmulator(
pSettings->environment().terminalPath(),
pSettings->environment().terminalArgumentsPattern(),
execArgs
);
#endif
QString workingDir = QFileInfo(mInferior).path();
mProcess = std::make_shared<QProcess>();
auto action = finally([&]{
mProcess.reset();
});
mProcess->setProgram(cmd);
mProcess->setArguments(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<PMemoryLine> newModel;
for (int i=0;i<value.length();i++) {
QString line = value[i].trimmed();
#if QT_VERSION >= 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>();
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;j<dataLst.length();j++) {
qulonglong data = stringToHex(dataLst[j],isOk);
if (isOk)
memoryLine->datas.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;i<newModel.count();i++) {
PMemoryLine newLine = newModel[i];
PMemoryLine oldLine = mLines[i];
for (int j=0;j<newLine->datas.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+1;
}
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) {
if (col==line->datas.count()) {
QString s;
foreach (unsigned char ch, line->datas) {
if (ch<' ' || ch>=128)
s+='.';
else
s+=ch;
}
return s;
} else
return QString("%1").arg(line->datas[col],2,16,QChar('0'));
} else if (role == Qt::ToolTipRole) {
if (col<line->datas.count()) {
QString s =
tr("addr: %1").arg(line->startAddress+col,0,16)
+"<br/>"
+tr("dec: %1").arg(line->datas[col])
+"<br/>"
+tr("oct: %1").arg(line->datas[col],0,8)
+"<br/>"
+tr("bin: %1").arg(line->datas[col],8,2,QChar('0'))
+"<br/>";
QString chVal;
if (line->datas[col]==0) {
chVal="\\0";
} else if (line->datas[col]=='\n') {
chVal="\\n";
} else if (line->datas[col]=='\t') {
chVal="\\t";
} else if (line->datas[col]=='\r') {
chVal="\\r";
} else if (line->datas[col]>=' ' && line->datas[col]<127) {
chVal=QChar(line->datas[col]);
}
if (!chVal.isEmpty()) {
s+=tr("ascii: \'%1\'").arg(chVal)
+"<br/>";
}
return s;
}
}
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();
}