/*
* 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();
}