diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e47a25..b641b35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ "terminate" that switches to a "restart". * Fixed bug when adding variable to tracker. Sometimes would not refresh value. * Raise Logger or Tracker tab when new variable is added. +* Implment gdb's "checkpoint" feature. As simple time-travel feature. ## [2.5] - 2024-12-24 * Console now supports a subset of ANSI color codes. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8d1fd3c..0960a32 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,7 @@ set(HEADER_FILES SeerCatchpointsBrowserWidget.h SeerPrintpointCreateDialog.h SeerPrintpointsBrowserWidget.h + SeerCheckpointsBrowserWidget.h SeerSeerLogWidget.h SeerConsoleWidget.h SeerConfigDialog.h @@ -176,6 +177,7 @@ set(SOURCE_FILES SeerCatchpointsBrowserWidget.cpp SeerPrintpointCreateDialog.cpp SeerPrintpointsBrowserWidget.cpp + SeerCheckpointsBrowserWidget.cpp SeerSeerLogWidget.cpp SeerConsoleWidget.cpp SeerConfigDialog.cpp diff --git a/src/SeerCheckpointsBrowserWidget.cpp b/src/SeerCheckpointsBrowserWidget.cpp new file mode 100644 index 0000000..72ff6fe --- /dev/null +++ b/src/SeerCheckpointsBrowserWidget.cpp @@ -0,0 +1,212 @@ +#include "SeerCheckpointsBrowserWidget.h" +#include "SeerUtl.h" +#include +#include +#include +#include +#include + +SeerCheckpointsBrowserWidget::SeerCheckpointsBrowserWidget (QWidget* parent) : QWidget(parent) { + + // Construct the UI. + setupUi(this); + + // Setup the widgets + checkpointsTreeWidget->clear(); + + checkpointsTreeWidget->setSortingEnabled(false); + checkpointsTreeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + checkpointsTreeWidget->resizeColumnToContents(0); // state + checkpointsTreeWidget->resizeColumnToContents(1); // number + checkpointsTreeWidget->resizeColumnToContents(2); // process + checkpointsTreeWidget->resizeColumnToContents(3); // file + checkpointsTreeWidget->resizeColumnToContents(4); // line + + // Connect things. + QObject::connect(checkpointsTreeWidget, &QTreeWidget::itemDoubleClicked, this, &SeerCheckpointsBrowserWidget::handleItemDoubleClicked); + QObject::connect(refreshCheckpointsToolButton, &QToolButton::clicked, this, &SeerCheckpointsBrowserWidget::handleRefreshToolButton); + QObject::connect(addCheckpointToolButton, &QToolButton::clicked, this, &SeerCheckpointsBrowserWidget::handleAddToolButton); + QObject::connect(deleteCheckpointsToolButton, &QToolButton::clicked, this, &SeerCheckpointsBrowserWidget::handleDeleteToolButton); + QObject::connect(selectCheckpointToolButton, &QToolButton::clicked, this, &SeerCheckpointsBrowserWidget::handleSelectToolButton); +} + +SeerCheckpointsBrowserWidget::~SeerCheckpointsBrowserWidget () { +} + +void SeerCheckpointsBrowserWidget::handleText (const QString& text) { + + // Don't do any work if the widget is hidden. + if (isHidden()) { + return; + } + + if (text.startsWith("^done,checkpoints=[") && text.endsWith("]")) { + + // + // "^done,checkpoints=[ + // {id="0",state="*",process="Thread 0x7ffff7e7f740 (LWP 31803) (main process) at 0x0",file="",line=""}, + // {id="1",state=" ",process="process 31806 at 0x55555555513f",file="hellostruct.cpp",line="49"}, + // {id="2",state=" ",process="process 31807 at 0x5555555552a0",file="hellostruct.cpp",line="62"} + // ] + // + + checkpointsTreeWidget->clear(); + checkpointsTreeWidget->setSortingEnabled(false); + checkpointsTreeWidget->sortByColumn(-1, Qt::AscendingOrder); + + QString checkpoints_text = Seer::parseFirst(text, "checkpoints=", '[', ']', false); + + QStringList checkpoints_list = Seer::parse(checkpoints_text, "", '{', '}', false); + + for (const auto& checkpoint_entry : checkpoints_list) { + + QString id_text = Seer::parseFirst(checkpoint_entry, "id=", '"', '"', false); + QString state_text = Seer::parseFirst(checkpoint_entry, "state=", '"', '"', false); + QString process_text = Seer::parseFirst(checkpoint_entry, "process=", '"', '"', false); + QString file_text = Seer::parseFirst(checkpoint_entry, "file=", '"', '"', false); + QString line_text = Seer::parseFirst(checkpoint_entry, "line=", '"', '"', false); + + // Add the function to the tree. + QTreeWidgetItem* item = new QTreeWidgetItem; + + item->setText(0, state_text); + item->setText(1, id_text); + item->setText(2, process_text); + item->setText(3, file_text); + item->setText(4, line_text); + + checkpointsTreeWidget->addTopLevelItem(item); + } + }else{ + // Ignore others. + } + + checkpointsTreeWidget->resizeColumnToContents(0); + checkpointsTreeWidget->resizeColumnToContents(1); + checkpointsTreeWidget->resizeColumnToContents(2); + checkpointsTreeWidget->resizeColumnToContents(3); + checkpointsTreeWidget->resizeColumnToContents(4); + + QApplication::restoreOverrideCursor(); +} + +void SeerCheckpointsBrowserWidget::handleStoppingPointReached () { + + // Don't do any work if the widget is hidden. + if (isHidden()) { + return; + } + + emit refreshCheckpointsList(); +} + +void SeerCheckpointsBrowserWidget::handleSessionTerminated () { + + // Delete previous contents. + checkpointsTreeWidget->clear(); +} + +void SeerCheckpointsBrowserWidget::handleItemDoubleClicked (QTreeWidgetItem* item, int column) { + + Q_UNUSED(column); + + emit selectCheckpoint(item->text(1)); +} + +void SeerCheckpointsBrowserWidget::handleRefreshToolButton () { + + emit refreshCheckpointsList(); +} + +void SeerCheckpointsBrowserWidget::handleAddToolButton () { + + // Otherwise send the command to create the checkpoint. + emit insertCheckpoint(); +} + +void SeerCheckpointsBrowserWidget::handleSelectToolButton () { + + // Any items in the tree? + if (checkpointsTreeWidget->topLevelItemCount() == 0) { + QMessageBox::warning(this, "Seer", QString("There are no checkpoints to switch to."), QMessageBox::Ok, QMessageBox::Ok); + return; + } + + // Get selected tree items. + QList items = checkpointsTreeWidget->selectedItems(); + + if (items.count() == 0) { + QMessageBox::warning(this, "Seer", QString("Selected a checkpoint to switch to."), QMessageBox::Ok, QMessageBox::Ok); + return; + } + + if (items.count() > 1) { + QMessageBox::warning(this, "Seer", QString("Select only 1 checkpoint to switch to."), QMessageBox::Ok, QMessageBox::Ok); + return; + } + + // Build a string that is a list of checkpoints. + QString checkpoints; + + QList::iterator i; + for (i = items.begin(); i != items.end(); ++i) { + + if (i != items.begin()) { + checkpoints += " "; + } + + checkpoints += (*i)->text(1); + } + + // Don't do anything if the list of checkpoints is empty. + if (checkpoints == "") { + return; + } + + // Send the signal. + emit selectCheckpoint(checkpoints); +} + +void SeerCheckpointsBrowserWidget::handleDeleteToolButton () { + + // Any items in the tree? + if (checkpointsTreeWidget->topLevelItemCount() == 0) { + QMessageBox::warning(this, "Seer", QString("There are no checkpoints to delete."), QMessageBox::Ok, QMessageBox::Ok); + return; + } + + // Get selected tree items. + QList items = checkpointsTreeWidget->selectedItems(); + + if (items.count() == 0) { + QMessageBox::warning(this, "Seer", QString("Select checkpoints to delete."), QMessageBox::Ok, QMessageBox::Ok); + return; + } + + // Build a string that is a list of checkpoints. + QString checkpoints; + + QList::iterator i; + for (i = items.begin(); i != items.end(); ++i) { + if (i != items.begin()) { + checkpoints += " "; + } + checkpoints += (*i)->text(1); + } + + // Don't do anything if the list of checkpoints is empty. + if (checkpoints == "") { + return; + } + + // Send the signal. + emit deleteCheckpoints(checkpoints); +} + +void SeerCheckpointsBrowserWidget::showEvent (QShowEvent* event) { + + QWidget::showEvent(event); + + emit refreshCheckpointsList(); +} + diff --git a/src/SeerCheckpointsBrowserWidget.h b/src/SeerCheckpointsBrowserWidget.h new file mode 100644 index 0000000..719fe56 --- /dev/null +++ b/src/SeerCheckpointsBrowserWidget.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include "ui_SeerCheckpointsBrowserWidget.h" + +class SeerCheckpointsBrowserWidget : public QWidget, protected Ui::SeerCheckpointsBrowserWidgetForm { + + Q_OBJECT + + public: + explicit SeerCheckpointsBrowserWidget (QWidget* parent = 0); + ~SeerCheckpointsBrowserWidget (); + + public slots: + void handleText (const QString& text); + void handleStoppingPointReached (); + void handleSessionTerminated (); + + private slots: + void handleItemDoubleClicked (QTreeWidgetItem* item, int column); + void handleRefreshToolButton (); + void handleAddToolButton (); + void handleDeleteToolButton (); + void handleSelectToolButton (); + + signals: + void refreshCheckpointsList (); + void insertCheckpoint (); + void selectCheckpoint (QString checkpoint); + void deleteCheckpoints (QString checkpoints); + + protected: + void showEvent (QShowEvent* event); + + private: +}; + diff --git a/src/SeerCheckpointsBrowserWidget.ui b/src/SeerCheckpointsBrowserWidget.ui new file mode 100644 index 0000000..2a087ba --- /dev/null +++ b/src/SeerCheckpointsBrowserWidget.ui @@ -0,0 +1,141 @@ + + + SeerCheckpointsBrowserWidgetForm + + + + 0 + 0 + 1162 + 625 + + + + Form + + + + + + 5 + + + + State + + + + + Number + + + + + Process + + + + + File + + + + + Line + + + + + + + + + + Add a new checkpoint at the current spot in the program. + + + ... + + + + :/seer/resources/RelaxLightIcons/document-new.svg:/seer/resources/RelaxLightIcons/document-new.svg + + + + + + + Refresh the list of checkpoints. + + + + + + + :/seer/resources/RelaxLightIcons/view-refresh.svg:/seer/resources/RelaxLightIcons/view-refresh.svg + + + + + + + Switch to the selected breakpoint. + + + ... + + + + :/seer/resources/RelaxLightIcons/list-add.svg:/seer/resources/RelaxLightIcons/list-add.svg + + + + + + + Delete selected checkpoints. + + + ... + + + + :/seer/resources/RelaxLightIcons/edit-delete.svg:/seer/resources/RelaxLightIcons/edit-delete.svg + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/SeerGdbWidget.cpp b/src/SeerGdbWidget.cpp index afb5427..891bf85 100644 --- a/src/SeerGdbWidget.cpp +++ b/src/SeerGdbWidget.cpp @@ -89,6 +89,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { _watchpointsBrowserWidget = new SeerWatchpointsBrowserWidget(this); _catchpointsBrowserWidget = new SeerCatchpointsBrowserWidget(this); _printpointsBrowserWidget = new SeerPrintpointsBrowserWidget(this); + _checkpointsBrowserWidget = new SeerCheckpointsBrowserWidget(this); _gdbOutputLog = new SeerGdbLogWidget(this); _seerOutputLog = new SeerSeerLogWidget(this); @@ -100,6 +101,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { logsTabWidget->addTab(_watchpointsBrowserWidget, "Watchpoints"); logsTabWidget->addTab(_catchpointsBrowserWidget, "Catchpoints"); logsTabWidget->addTab(_printpointsBrowserWidget, "Printpoints"); + logsTabWidget->addTab(_checkpointsBrowserWidget, "Checkpoints"); logsTabWidget->addTab(_gdbOutputLog, "GDB output"); logsTabWidget->addTab(_seerOutputLog, "Seer output"); logsTabWidget->setCurrentIndex(0); @@ -184,6 +186,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, _watchpointsBrowserWidget, &SeerWatchpointsBrowserWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, _catchpointsBrowserWidget, &SeerCatchpointsBrowserWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, _printpointsBrowserWidget, &SeerPrintpointsBrowserWidget::handleText); + QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, _checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, variableManagerWidget->registerValuesBrowserWidget(), &SeerRegisterValuesBrowserWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, variableManagerWidget->variableTrackerBrowserWidget(), &SeerVariableTrackerBrowserWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, variableManagerWidget->variableLoggerBrowserWidget(), &SeerVariableLoggerBrowserWidget::handleText); @@ -231,11 +234,11 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { QObject::connect(sourceLibraryManagerWidget->libraryBrowserWidget(), &SeerLibraryBrowserWidget::refreshLibraryList, this, &SeerGdbWidget::handleGdbExecutableLibraries); QObject::connect(sourceLibraryManagerWidget->adaExceptionsBrowserWidget(), &SeerAdaExceptionsBrowserWidget::refreshAdaExceptions, this, &SeerGdbWidget::handleGdbAdaListExceptions); QObject::connect(sourceLibraryManagerWidget->adaExceptionsBrowserWidget(), &SeerAdaExceptionsBrowserWidget::insertCatchpoint, this, &SeerGdbWidget::handleGdbCatchpointInsert); - QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::refreshSkipList, this, &SeerGdbWidget::handleGdbListSkips); - QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::addSkip, this, &SeerGdbWidget::handleGdbAddSkip); - QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::deleteSkips, this, &SeerGdbWidget::handleGdbDeleteSkips); - QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::enableSkips, this, &SeerGdbWidget::handleGdbEnableSkips); - QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::disableSkips, this, &SeerGdbWidget::handleGdbDisableSkips); + QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::refreshSkipList, this, &SeerGdbWidget::handleGdbSkipList); + QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::addSkip, this, &SeerGdbWidget::handleGdbSkipAdd); + QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::deleteSkips, this, &SeerGdbWidget::handleGdbSkipDelete); + QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::enableSkips, this, &SeerGdbWidget::handleGdbSkipEnable); + QObject::connect(sourceLibraryManagerWidget->skipBrowserWidget(), &SeerSkipBrowserWidget::disableSkips, this, &SeerGdbWidget::handleGdbSkipDisable); QObject::connect(stackManagerWidget->stackFramesBrowserWidget(), &SeerStackFramesBrowserWidget::refreshStackFrames, this, &SeerGdbWidget::handleGdbStackListFrames); QObject::connect(stackManagerWidget->stackFramesBrowserWidget(), &SeerStackFramesBrowserWidget::selectedFrame, this, &SeerGdbWidget::handleGdbStackSelectFrame); @@ -345,6 +348,11 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { QObject::connect(_printpointsBrowserWidget, &SeerPrintpointsBrowserWidget::addBreakpointIgnore, this, &SeerGdbWidget::handleGdbBreakpointIgnore); QObject::connect(_printpointsBrowserWidget, &SeerPrintpointsBrowserWidget::addBreakpointCommand, this, &SeerGdbWidget::handleGdbBreakpointCommand); + QObject::connect(_checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::refreshCheckpointsList, this, &SeerGdbWidget::handleGdbCheckpointList); + QObject::connect(_checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::insertCheckpoint, this, &SeerGdbWidget::handleGdbCheckpointInsert); + QObject::connect(_checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::selectCheckpoint, this, &SeerGdbWidget::handleGdbCheckpointSelect); + QObject::connect(_checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::deleteCheckpoints, this, &SeerGdbWidget::handleGdbCheckpointDelete); + QObject::connect(this, &SeerGdbWidget::assemblyConfigChanged, editorManagerWidget, &SeerEditorManagerWidget::handleAssemblyConfigChanged); QObject::connect(this, &SeerGdbWidget::stoppingPointReached, stackManagerWidget, &SeerStackManagerWidget::handleStoppingPointReached); @@ -361,6 +369,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { QObject::connect(this, &SeerGdbWidget::stoppingPointReached, _watchpointsBrowserWidget, &SeerWatchpointsBrowserWidget::handleStoppingPointReached); QObject::connect(this, &SeerGdbWidget::stoppingPointReached, _catchpointsBrowserWidget, &SeerCatchpointsBrowserWidget::handleStoppingPointReached); QObject::connect(this, &SeerGdbWidget::stoppingPointReached, _printpointsBrowserWidget, &SeerPrintpointsBrowserWidget::handleStoppingPointReached); + QObject::connect(this, &SeerGdbWidget::stoppingPointReached, _checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::handleStoppingPointReached); QObject::connect(this, &SeerGdbWidget::sessionTerminated, sourceLibraryManagerWidget->sourceBrowserWidget(), &SeerSourceBrowserWidget::handleSessionTerminated); QObject::connect(this, &SeerGdbWidget::sessionTerminated, sourceLibraryManagerWidget->functionBrowserWidget(), &SeerFunctionBrowserWidget::handleSessionTerminated); @@ -384,6 +393,7 @@ SeerGdbWidget::SeerGdbWidget (QWidget* parent) : QWidget(parent) { QObject::connect(this, &SeerGdbWidget::sessionTerminated, _watchpointsBrowserWidget, &SeerWatchpointsBrowserWidget::handleSessionTerminated); QObject::connect(this, &SeerGdbWidget::sessionTerminated, _catchpointsBrowserWidget, &SeerCatchpointsBrowserWidget::handleSessionTerminated); QObject::connect(this, &SeerGdbWidget::sessionTerminated, _printpointsBrowserWidget, &SeerPrintpointsBrowserWidget::handleSessionTerminated); + QObject::connect(this, &SeerGdbWidget::sessionTerminated, _checkpointsBrowserWidget, &SeerCheckpointsBrowserWidget::handleSessionTerminated); QObject::connect(leftCenterRightSplitter, &QSplitter::splitterMoved, this, &SeerGdbWidget::handleSplitterMoved); QObject::connect(sourceLibraryVariableManagerSplitter, &QSplitter::splitterMoved, this, &SeerGdbWidget::handleSplitterMoved); @@ -2432,7 +2442,7 @@ void SeerGdbWidget::handleGdbAdaListExceptions () { handleGdbCommand("-info-ada-exceptions"); } -void SeerGdbWidget::handleGdbListSkips () { +void SeerGdbWidget::handleGdbSkipList () { if (executableLaunchMode() == "") { return; @@ -2441,7 +2451,7 @@ void SeerGdbWidget::handleGdbListSkips () { handleGdbCommand("-skip-list"); } -void SeerGdbWidget::handleGdbAddSkip (QString skipmode, QString skipparameters) { +void SeerGdbWidget::handleGdbSkipAdd (QString skipmode, QString skipparameters) { if (executableLaunchMode() == "") { return; @@ -2459,10 +2469,10 @@ void SeerGdbWidget::handleGdbAddSkip (QString skipmode, QString skipparameters) return; } - handleGdbListSkips(); + handleGdbSkipList(); } -void SeerGdbWidget::handleGdbDeleteSkips (QString skipids) { +void SeerGdbWidget::handleGdbSkipDelete (QString skipids) { if (executableLaunchMode() == "") { return; @@ -2470,10 +2480,10 @@ void SeerGdbWidget::handleGdbDeleteSkips (QString skipids) { handleGdbCommand("-skip-delete " + skipids); - handleGdbListSkips(); + handleGdbSkipList(); } -void SeerGdbWidget::handleGdbEnableSkips (QString skipids) { +void SeerGdbWidget::handleGdbSkipEnable (QString skipids) { if (executableLaunchMode() == "") { return; @@ -2481,10 +2491,10 @@ void SeerGdbWidget::handleGdbEnableSkips (QString skipids) { handleGdbCommand("-skip-enable " + skipids); - handleGdbListSkips(); + handleGdbSkipList(); } -void SeerGdbWidget::handleGdbDisableSkips (QString skipids) { +void SeerGdbWidget::handleGdbSkipDisable (QString skipids) { if (executableLaunchMode() == "") { return; @@ -2492,7 +2502,52 @@ void SeerGdbWidget::handleGdbDisableSkips (QString skipids) { handleGdbCommand("-skip-disable " + skipids); - handleGdbListSkips(); + handleGdbSkipList(); +} + +void SeerGdbWidget::handleGdbCheckpointList () { + + if (executableLaunchMode() == "") { + return; + } + + handleGdbCommand("-checkpoint-list"); +} + +void SeerGdbWidget::handleGdbCheckpointInsert () { + + if (executableLaunchMode() == "") { + return; + } + + handleGdbCommand("-checkpoint-create"); + handleGdbCommand("-checkpoint-list"); +} + +void SeerGdbWidget::handleGdbCheckpointSelect (QString id) { + + if (executableLaunchMode() == "") { + return; + } + + handleGdbCommand("-checkpoint-select " + id); + + emit stoppingPointReached(); +} + +void SeerGdbWidget::handleGdbCheckpointDelete (QString ids) { + + if (executableLaunchMode() == "") { + return; + } + + QStringList list = ids.split(" "); + + for (auto id : list) { + handleGdbCommand("-checkpoint-delete " + id); + } + + handleGdbCommand("-checkpoint-list"); } void SeerGdbWidget::handleGdbRegisterListNames () { @@ -3823,19 +3878,32 @@ void SeerGdbWidget::handleGdbLoadMICommands () { // Open the source file from resources. QFile miFile(miInfo.absoluteFilePath()); if (!miFile.exists()) { - qDebug() << "Resource file" << miInfo << "does not exist!"; + qDebug().nospace().noquote() << "Resource file '" << miInfo << "' does not exist!"; continue; } // Destination file path in /tmp. QString destinationPath = "/tmp/" + miInfo.fileName(); - // Copy to temp. Don't check return status. I don't think it works - // if the source is in Resources. - miFile.copy(destinationPath); + // Delete possible old temp version, if it exists. + if (QFile::exists(destinationPath)) { + bool f = QFile::remove(destinationPath); + if (f == false) { + qDebug().nospace().noquote() << "Old temp Resource file '" << destinationPath << "' can not be deleted!"; + continue; + } + } + + // Copy to temp. + bool f = miFile.copy(destinationPath); + if (f == false) { + qDebug().nospace().noquote() << "Resource file '" << miInfo << "' can not be copied to '" << destinationPath << "'!"; + continue; + } // Source it. if (QFile::exists(destinationPath) == false) { + qDebug().nospace().noquote() << "Temp Resource file '" << destinationPath << "' does not exist!"; continue; } diff --git a/src/SeerGdbWidget.h b/src/SeerGdbWidget.h index 3fadba5..46e7052 100644 --- a/src/SeerGdbWidget.h +++ b/src/SeerGdbWidget.h @@ -9,6 +9,7 @@ #include "SeerWatchpointsBrowserWidget.h" #include "SeerCatchpointsBrowserWidget.h" #include "SeerPrintpointsBrowserWidget.h" +#include "SeerCheckpointsBrowserWidget.h" #include "GdbMonitor.h" #include #include @@ -310,11 +311,15 @@ class SeerGdbWidget : public QWidget, protected Ui::SeerGdbWidgetForm { void handleGdbThreadSelectId (int threadid); void handleGdbAdaListTasks (); void handleGdbAdaListExceptions (); - void handleGdbListSkips (); - void handleGdbAddSkip (QString skipmode, QString skipparameters); - void handleGdbDeleteSkips (QString skipids); - void handleGdbEnableSkips (QString skipids); - void handleGdbDisableSkips (QString skipids); + void handleGdbSkipList (); + void handleGdbSkipAdd (QString skipmode, QString skipparameters); + void handleGdbSkipDelete (QString skipids); + void handleGdbSkipEnable (QString skipids); + void handleGdbSkipDisable (QString skipids); + void handleGdbCheckpointList (); + void handleGdbCheckpointInsert (); + void handleGdbCheckpointSelect (QString id); + void handleGdbCheckpointDelete (QString ids); void handleGdbRegisterListNames (); void handleGdbRegisterListValues (QString fmt); void handleGdbRegisterSetValue (QString fmt, QString name, QString value); @@ -447,6 +452,7 @@ class SeerGdbWidget : public QWidget, protected Ui::SeerGdbWidgetForm { SeerWatchpointsBrowserWidget* _watchpointsBrowserWidget; SeerCatchpointsBrowserWidget* _catchpointsBrowserWidget; SeerPrintpointsBrowserWidget* _printpointsBrowserWidget; + SeerCheckpointsBrowserWidget* _checkpointsBrowserWidget; SeerGdbLogWidget* _gdbOutputLog; SeerSeerLogWidget* _seerOutputLog; diff --git a/src/SeerMainWindow.cpp b/src/SeerMainWindow.cpp index 9e91f80..7028eec 100644 --- a/src/SeerMainWindow.cpp +++ b/src/SeerMainWindow.cpp @@ -1143,6 +1143,9 @@ void SeerMainWindow::handleText (const QString& text) { }else if (text.startsWith("^done,skips=[") && text.endsWith("]")) { return; + }else if (text.startsWith("^done,checkpoints=[") && text.endsWith("]")) { + return; + }else if (text.contains(QRegularExpression("^([0-9]+)\\^done"))) { return; diff --git a/src/resource.qrc b/src/resource.qrc index c545427..d13cbc1 100644 --- a/src/resource.qrc +++ b/src/resource.qrc @@ -83,6 +83,7 @@ resources/mi-python/MIEcho.py resources/mi-python/MISkip.py resources/mi-python/MIKill.py + resources/mi-python/MICheckpoint.py diff --git a/src/resources/help/BreakpointGdbSeerManager.md b/src/resources/help/BreakpointGdbSeerManager.md index 5eeadc4..18cc81f 100644 --- a/src/resources/help/BreakpointGdbSeerManager.md +++ b/src/resources/help/BreakpointGdbSeerManager.md @@ -8,6 +8,7 @@ This part of Seer shows Breakpoints, GDB log, and Seer log information. In detai * Watchpoints * Catchpoints * Printpoints +* Checkpoints * GDB output * Seer output * Save and load breakpoints @@ -60,6 +61,19 @@ There are other types of catchpoints but GDB/mi only supports the above list at A printpoint is a type of breakpoint that will print the value of a variable at a certain line of a function. It relies on gdb's ```dprintf``` feature. +### Checkpoints + +A checkpoint is a simple form of time-travel debugging. You can create a checkpoint at any point where the program you're debugging +is stopped. This checkpoint is listed in the Checkpoints tab. You can continue to debug your program. At any time, you can return +back to the checkpoint (go back in time) and continue debugging again from that point. + +You can create as many checkpoints as you want and switch between them. + +A couple things to note: + +* This is not supported on all platforms. +* Switching to a previous checkpoint does not undo any I/O. You can't unwrite data written to a file or unprint text sent to a printer. + ### Modifying existing breakpoints. @@ -112,5 +126,6 @@ Consult these gdb references 1. [Link](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Set-Breaks.html#Set-Breaks) Using Breakpoints. 2. [Link](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Set-Watchpoints.html#Set-Watchpoints) Using Watchpoints. 3. [Link](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Set-Catchpoints.html#Set-Catchpoints) Using Catchpoints. -4. [Link](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Dynamic-Printf.html#Dynamic-Printf) Using DPrintf. +4. [Link](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Dynamic-Printf.html#Dynamic-Printf) Using DPrintf for Printpoints. +5. [Link](https://sourceware.org/gdb/current/onlinedocs/gdb.html/Checkpoint_002fRestart.html#Checkpoint_002fRestart) Using Checkpoints. diff --git a/src/resources/mi-python/MICheckpoint.py b/src/resources/mi-python/MICheckpoint.py index 011f370..57362ee 100644 --- a/src/resources/mi-python/MICheckpoint.py +++ b/src/resources/mi-python/MICheckpoint.py @@ -41,8 +41,8 @@ class MICheckpoint(gdb.MICommand): checkpointmeta["id"] = columns.group(2) checkpointmeta["state"] = columns.group(1) checkpointmeta["process"] = columns.group(3) - checkpointmeta["file"] = columns.group(4) - checkpointmeta["line"] = columns.group(5) + checkpointmeta["file"] = re.sub("^file ", "", columns.group(4)) + checkpointmeta["line"] = re.sub("^line ", "", columns.group(5)) checkpointentries.append(checkpointmeta) @@ -64,7 +64,7 @@ class MICheckpoint(gdb.MICommand): gdb.execute ("checkpoint " + " ".join(argv), to_string=True) return None elif self._mode == "select": - gdb.execute ("select " + " ".join(argv), to_string=True) + gdb.execute ("restart " + " ".join(argv), to_string=True) return None elif self._mode == "delete": gdb.execute ("delete checkpoint " + " ".join(argv), to_string=True)