diff options
author | con <qtc-commiter@nokia.com> | 2008-12-02 12:01:29 +0100 |
---|---|---|
committer | con <qtc-commiter@nokia.com> | 2008-12-02 12:01:29 +0100 |
commit | 05c35356abc31549c5db6eba31fb608c0365c2a0 (patch) | |
tree | be044530104267afaff13f8943889cb97f8c8bad /src/plugins/debugger | |
download | qt-creator-05c35356abc31549c5db6eba31fb608c0365c2a0.tar.gz |
Initial import
Diffstat (limited to 'src/plugins/debugger')
88 files changed, 17859 insertions, 0 deletions
diff --git a/src/plugins/debugger/Debugger.pluginspec b/src/plugins/debugger/Debugger.pluginspec new file mode 100644 index 0000000000..d68c17517e --- /dev/null +++ b/src/plugins/debugger/Debugger.pluginspec @@ -0,0 +1,13 @@ +<plugin name="Debugger" version="0.9.1" compatVersion="0.9.1"> + <vendor>Nokia Corporation</vendor> + <copyright>(C) 2008 Nokia Corporation</copyright> + <license>Nokia Technology Preview License Agreement</license> + <description>Debugger integration.</description> + <url>http://www.trolltech.com/</url> + <dependencyList> + <dependency name="CppEditor" version="0.9.1"/><!-- Debugger plugin adds items to the editor's context menu --> + <dependency name="ProjectExplorer" version="0.9.1"/> + <dependency name="Core" version="0.9.1"/> + <dependency name="Find" version="0.9.1"/> + </dependencyList> +</plugin> diff --git a/src/plugins/debugger/assert.h b/src/plugins/debugger/assert.h new file mode 100644 index 0000000000..a4310040fe --- /dev/null +++ b/src/plugins/debugger/assert.h @@ -0,0 +1,45 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_QWB_ASSERT_H +#define DEBUGGER_QWB_ASSERT_H + +#ifdef Q_OS_UNIX +#define QWB_ASSERT(cond, action) \ + if(cond){}else{qDebug()<<"ASSERTION"<<#cond<<"FAILED"<<__FILE__<<__LINE__;action;} +#else +#define QWB_ASSERT(cond, action) \ + if(cond){}else{qDebug()<<"ASSERTION"<<#cond<<"FAILED";action;} +#endif + +#endif + diff --git a/src/plugins/debugger/attachexternaldialog.cpp b/src/plugins/debugger/attachexternaldialog.cpp new file mode 100644 index 0000000000..5aeec2ec62 --- /dev/null +++ b/src/plugins/debugger/attachexternaldialog.cpp @@ -0,0 +1,344 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "attachexternaldialog.h" + +#include <QDebug> +#include <QDir> +#include <QFile> +#include <QPushButton> +#include <QStandardItemModel> +#include <QHeaderView> + +using namespace Debugger::Internal; + +AttachExternalDialog::AttachExternalDialog(QWidget *parent, const QString &pid) + : QDialog(parent) +{ + setupUi(this); + buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + m_defaultPID = pid; + m_model = new QStandardItemModel(this); + + procView->setSortingEnabled(true); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + connect(procView, SIGNAL(activated(const QModelIndex &)), + this, SLOT(procSelected(const QModelIndex &))); + + + pidLineEdit->setText(m_defaultPID); + rebuildProcessList(); +} + +static bool isProcessName(const QString &procname) +{ + for (int i = 0; i != procname.size(); ++i) + if (!procname.at(i).isDigit()) + return false; + return true; +} + +struct ProcData { + QString ppid; + QString name; + QString state; +}; + +static void insertItem(QStandardItem *root, const QString &pid, + const QMap<QString, ProcData> &procs, QMap<QString, QStandardItem *> &known) +{ + //qDebug() << "HANDLING " << pid; + QStandardItem *parent = 0; + const ProcData &proc = procs[pid]; + if (1 || pid == "0") { + parent = root; + } else { + if (!known.contains(proc.ppid)) + insertItem(root, proc.ppid, procs, known); + parent = known[proc.ppid]; + } + QList<QStandardItem *> row; + row.append(new QStandardItem(pid)); + row.append(new QStandardItem(proc.name)); + //row.append(new QStandardItem(proc.ppid)); + row.append(new QStandardItem(proc.state)); + parent->appendRow(row); + known[pid] = row[0]; +} + +void AttachExternalDialog::rebuildProcessList() +{ + QStringList procnames = QDir("/proc/").entryList(); + if (procnames.isEmpty()) { + procView->hide(); + return; + } + + typedef QMap<QString, ProcData> Procs; + Procs procs; + + foreach (const QString &procname, procnames) { + if (!isProcessName(procname)) + continue; + QString filename = "/proc/" + procname + "/stat"; + QFile file(filename); + file.open(QIODevice::ReadOnly); + QStringList data = QString::fromLocal8Bit(file.readAll()).split(' '); + //qDebug() << filename << data; + ProcData proc; + proc.name = data.at(1); + if (proc.name.startsWith('(') && proc.name.endsWith(')')) + proc.name = proc.name.mid(1, proc.name.size() - 2); + proc.state = data.at(2); + proc.ppid = data.at(3); + procs[procname] = proc; + } + + m_model->clear(); + QMap<QString, QStandardItem *> known; + for (Procs::const_iterator it = procs.begin(); it != procs.end(); ++it) + insertItem(m_model->invisibleRootItem(), it.key(), procs, known); + m_model->setHeaderData(0, Qt::Horizontal, "Process ID", Qt::DisplayRole); + m_model->setHeaderData(1, Qt::Horizontal, "Name", Qt::DisplayRole); + //model->setHeaderData(2, Qt::Horizontal, "Parent", Qt::DisplayRole); + m_model->setHeaderData(2, Qt::Horizontal, "State", Qt::DisplayRole); + + procView->setModel(m_model); + procView->expandAll(); + procView->resizeColumnToContents(0); + procView->resizeColumnToContents(1); +} + +#ifdef Q_OS_WINDOWS + +#include <windows.h> +#include <tlhelp32.h> +#include <tchar.h> +#include <stdio.h> + +// Forward declarations: +BOOL GetProcessList( ); +BOOL ListProcessModules( DWORD dwPID ); +BOOL ListProcessThreads( DWORD dwOwnerPID ); +void printError( TCHAR* msg ); + +BOOL GetProcessList( ) +{ + HANDLE hProcessSnap; + HANDLE hProcess; + PROCESSENTRY32 pe32; + DWORD dwPriorityClass; + + // Take a snapshot of all processes in the system. + hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); + if( hProcessSnap == INVALID_HANDLE_VALUE ) + { + printError( TEXT("CreateToolhelp32Snapshot (of processes)") ); + return( FALSE ); + } + + // Set the size of the structure before using it. + pe32.dwSize = sizeof( PROCESSENTRY32 ); + + // Retrieve information about the first process, + // and exit if unsuccessful + if( !Process32First( hProcessSnap, &pe32 ) ) + { + printError( TEXT("Process32First") ); // show cause of failure + CloseHandle( hProcessSnap ); // clean the snapshot object + return( FALSE ); + } + + // Now walk the snapshot of processes, and + // display information about each process in turn + do + { + printf( "\n\n=====================================================" ); + _tprintf( TEXT("\nPROCESS NAME: %s"), pe32.szExeFile ); + printf( "\n-----------------------------------------------------" ); + + // Retrieve the priority class. + dwPriorityClass = 0; + hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID ); + if( hProcess == NULL ) + printError( TEXT("OpenProcess") ); + else + { + dwPriorityClass = GetPriorityClass( hProcess ); + if( !dwPriorityClass ) + printError( TEXT("GetPriorityClass") ); + CloseHandle( hProcess ); + } + + printf( "\n Process ID = 0x%08X", pe32.th32ProcessID ); + printf( "\n Thread count = %d", pe32.cntThreads ); + printf( "\n Parent process ID = 0x%08X", pe32.th32ParentProcessID ); + printf( "\n Priority base = %d", pe32.pcPriClassBase ); + if( dwPriorityClass ) + printf( "\n Priority class = %d", dwPriorityClass ); + + // List the modules and threads associated with this process + ListProcessModules( pe32.th32ProcessID ); + ListProcessThreads( pe32.th32ProcessID ); + + } while( Process32Next( hProcessSnap, &pe32 ) ); + + CloseHandle( hProcessSnap ); + return( TRUE ); +} + + +BOOL ListProcessModules( DWORD dwPID ) +{ + HANDLE hModuleSnap = INVALID_HANDLE_VALUE; + MODULEENTRY32 me32; + + // Take a snapshot of all modules in the specified process. + hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dwPID ); + if( hModuleSnap == INVALID_HANDLE_VALUE ) + { + printError( TEXT("CreateToolhelp32Snapshot (of modules)") ); + return( FALSE ); + } + + // Set the size of the structure before using it. + me32.dwSize = sizeof( MODULEENTRY32 ); + + // Retrieve information about the first module, + // and exit if unsuccessful + if( !Module32First( hModuleSnap, &me32 ) ) + { + printError( TEXT("Module32First") ); // show cause of failure + CloseHandle( hModuleSnap ); // clean the snapshot object + return( FALSE ); + } + + // Now walk the module list of the process, + // and display information about each module + do + { + _tprintf( TEXT("\n\n MODULE NAME: %s"), me32.szModule ); + _tprintf( TEXT("\n Executable = %s"), me32.szExePath ); + printf( "\n Process ID = 0x%08X", me32.th32ProcessID ); + printf( "\n Ref count (g) = 0x%04X", me32.GlblcntUsage ); + printf( "\n Ref count (p) = 0x%04X", me32.ProccntUsage ); + printf( "\n Base address = 0x%08X", (DWORD) me32.modBaseAddr ); + printf( "\n Base size = %d", me32.modBaseSize ); + + } while( Module32Next( hModuleSnap, &me32 ) ); + + CloseHandle( hModuleSnap ); + return( TRUE ); +} + +BOOL ListProcessThreads( DWORD dwOwnerPID ) +{ + HANDLE hThreadSnap = INVALID_HANDLE_VALUE; + THREADENTRY32 te32; + + // Take a snapshot of all running threads + hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( hThreadSnap == INVALID_HANDLE_VALUE ) + return( FALSE ); + + // Fill in the size of the structure before using it. + te32.dwSize = sizeof(THREADENTRY32 ); + + // Retrieve information about the first thread, + // and exit if unsuccessful + if( !Thread32First( hThreadSnap, &te32 ) ) + { + printError( TEXT("Thread32First") ); // show cause of failure + CloseHandle( hThreadSnap ); // clean the snapshot object + return( FALSE ); + } + + // Now walk the thread list of the system, + // and display information about each thread + // associated with the specified process + do + { + if( te32.th32OwnerProcessID == dwOwnerPID ) + { + printf( "\n\n THREAD ID = 0x%08X", te32.th32ThreadID ); + printf( "\n Base priority = %d", te32.tpBasePri ); + printf( "\n Delta priority = %d", te32.tpDeltaPri ); + } + } while( Thread32Next(hThreadSnap, &te32 ) ); + + CloseHandle( hThreadSnap ); + return( TRUE ); +} + +void printError( TCHAR* msg ) +{ + DWORD eNum; + TCHAR sysMsg[256]; + TCHAR* p; + + eNum = GetLastError( ); + FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, eNum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + sysMsg, 256, NULL ); + + // Trim the end of the line and terminate it with a null + p = sysMsg; + while( ( *p > 31 ) || ( *p == 9 ) ) + ++p; + do { *p-- = 0; } while( ( p >= sysMsg ) && + ( ( *p == '.' ) || ( *p < 33 ) ) ); + + // Display the message + _tprintf( TEXT("\n WARNING: %s failed with error %d (%s)"), msg, eNum, sysMsg ); +} + +#endif + + +void AttachExternalDialog::procSelected(const QModelIndex &index0) +{ + QModelIndex index = index0.sibling(index0.row(), 0); + QStandardItem *item = m_model->itemFromIndex(index); + if (!item) + return; + pidLineEdit->setText(item->text()); + accept(); +} + +int AttachExternalDialog::attachPID() const +{ + return pidLineEdit->text().toInt(); +} diff --git a/src/plugins/debugger/attachexternaldialog.h b/src/plugins/debugger/attachexternaldialog.h new file mode 100644 index 0000000000..6c77d206b6 --- /dev/null +++ b/src/plugins/debugger/attachexternaldialog.h @@ -0,0 +1,65 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef ATTACHEXTERNALDIALOG_H +#define ATTACHEXTERNALDIALOG_H + +#include "ui_attachexternaldialog.h" + +QT_BEGIN_NAMESPACE +class QStandardItemModel; +QT_END_NAMESPACE + +namespace Debugger { +namespace Internal { + +class AttachExternalDialog : public QDialog, Ui::AttachExternalDialog +{ + Q_OBJECT + +public: + explicit AttachExternalDialog(QWidget *parent, const QString &pid); + int attachPID() const; + +private slots: + void rebuildProcessList(); + void procSelected(const QModelIndex &); + +private: + QString m_defaultPID; + QStandardItemModel *m_model; +}; + +} // namespace Debugger +} // namespace Internal + +#endif // ATTACHEEXTERNALDIALOG_H diff --git a/src/plugins/debugger/attachexternaldialog.ui b/src/plugins/debugger/attachexternaldialog.ui new file mode 100644 index 0000000000..63f214c51e --- /dev/null +++ b/src/plugins/debugger/attachexternaldialog.ui @@ -0,0 +1,64 @@ +<ui version="4.0" > + <class>AttachExternalDialog</class> + <widget class="QDialog" name="AttachExternalDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>561</width> + <height>866</height> + </rect> + </property> + <property name="windowTitle" > + <string>Start Debugger</string> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout" > + <item> + <widget class="QLabel" name="pidLabel" > + <property name="text" > + <string>Attach to Process ID:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="pidLineEdit" /> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="procView" > + <property name="editTriggers" > + <set>QAbstractItemView::NoEditTriggers</set> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons" > + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/debugger/attachremotedialog.cpp b/src/plugins/debugger/attachremotedialog.cpp new file mode 100644 index 0000000000..ffd09e7b78 --- /dev/null +++ b/src/plugins/debugger/attachremotedialog.cpp @@ -0,0 +1,344 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "attachremotedialog.h" + +#include <QDebug> +#include <QDir> +#include <QFile> +#include <QPushButton> +#include <QStandardItemModel> +#include <QHeaderView> + +using namespace Debugger::Internal; + +AttachRemoteDialog::AttachRemoteDialog(QWidget *parent, const QString &pid) + : QDialog(parent) +{ + setupUi(this); + buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + m_defaultPID = pid; + m_model = new QStandardItemModel(this); + + procView->setSortingEnabled(true); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + connect(procView, SIGNAL(activated(const QModelIndex &)), + this, SLOT(procSelected(const QModelIndex &))); + + + pidLineEdit->setText(m_defaultPID); + rebuildProcessList(); +} + +static bool isProcessName(const QString &procname) +{ + for (int i = 0; i != procname.size(); ++i) + if (!procname.at(i).isDigit()) + return false; + return true; +} + +struct ProcData { + QString ppid; + QString name; + QString state; +}; + +static void insertItem(QStandardItem *root, const QString &pid, + const QMap<QString, ProcData> &procs, QMap<QString, QStandardItem *> &known) +{ + //qDebug() << "HANDLING " << pid; + QStandardItem *parent = 0; + const ProcData &proc = procs[pid]; + if (1 || pid == "0") { + parent = root; + } else { + if (!known.contains(proc.ppid)) + insertItem(root, proc.ppid, procs, known); + parent = known[proc.ppid]; + } + QList<QStandardItem *> row; + row.append(new QStandardItem(pid)); + row.append(new QStandardItem(proc.name)); + //row.append(new QStandardItem(proc.ppid)); + row.append(new QStandardItem(proc.state)); + parent->appendRow(row); + known[pid] = row[0]; +} + +void AttachRemoteDialog::rebuildProcessList() +{ + QStringList procnames = QDir("/proc/").entryList(); + if (procnames.isEmpty()) { + procView->hide(); + return; + } + + typedef QMap<QString, ProcData> Procs; + Procs procs; + + foreach (const QString &procname, procnames) { + if (!isProcessName(procname)) + continue; + QString filename = "/proc/" + procname + "/stat"; + QFile file(filename); + file.open(QIODevice::ReadOnly); + QStringList data = QString::fromLocal8Bit(file.readAll()).split(' '); + //qDebug() << filename << data; + ProcData proc; + proc.name = data.at(1); + if (proc.name.startsWith('(') && proc.name.endsWith(')')) + proc.name = proc.name.mid(1, proc.name.size() - 2); + proc.state = data.at(2); + proc.ppid = data.at(3); + procs[procname] = proc; + } + + m_model->clear(); + QMap<QString, QStandardItem *> known; + for (Procs::const_iterator it = procs.begin(); it != procs.end(); ++it) + insertItem(m_model->invisibleRootItem(), it.key(), procs, known); + m_model->setHeaderData(0, Qt::Horizontal, "Process ID", Qt::DisplayRole); + m_model->setHeaderData(1, Qt::Horizontal, "Name", Qt::DisplayRole); + //model->setHeaderData(2, Qt::Horizontal, "Parent", Qt::DisplayRole); + m_model->setHeaderData(2, Qt::Horizontal, "State", Qt::DisplayRole); + + procView->setModel(m_model); + procView->expandAll(); + procView->resizeColumnToContents(0); + procView->resizeColumnToContents(1); +} + +#ifdef Q_OS_WINDOWS + +#include <windows.h> +#include <tlhelp32.h> +#include <tchar.h> +#include <stdio.h> + +// Forward declarations: +BOOL GetProcessList( ); +BOOL ListProcessModules( DWORD dwPID ); +BOOL ListProcessThreads( DWORD dwOwnerPID ); +void printError( TCHAR* msg ); + +BOOL GetProcessList( ) +{ + HANDLE hProcessSnap; + HANDLE hProcess; + PROCESSENTRY32 pe32; + DWORD dwPriorityClass; + + // Take a snapshot of all processes in the system. + hProcessSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); + if( hProcessSnap == INVALID_HANDLE_VALUE ) + { + printError( TEXT("CreateToolhelp32Snapshot (of processes)") ); + return( FALSE ); + } + + // Set the size of the structure before using it. + pe32.dwSize = sizeof( PROCESSENTRY32 ); + + // Retrieve information about the first process, + // and exit if unsuccessful + if( !Process32First( hProcessSnap, &pe32 ) ) + { + printError( TEXT("Process32First") ); // show cause of failure + CloseHandle( hProcessSnap ); // clean the snapshot object + return( FALSE ); + } + + // Now walk the snapshot of processes, and + // display information about each process in turn + do + { + printf( "\n\n=====================================================" ); + _tprintf( TEXT("\nPROCESS NAME: %s"), pe32.szExeFile ); + printf( "\n-----------------------------------------------------" ); + + // Retrieve the priority class. + dwPriorityClass = 0; + hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID ); + if( hProcess == NULL ) + printError( TEXT("OpenProcess") ); + else + { + dwPriorityClass = GetPriorityClass( hProcess ); + if( !dwPriorityClass ) + printError( TEXT("GetPriorityClass") ); + CloseHandle( hProcess ); + } + + printf( "\n Process ID = 0x%08X", pe32.th32ProcessID ); + printf( "\n Thread count = %d", pe32.cntThreads ); + printf( "\n Parent process ID = 0x%08X", pe32.th32ParentProcessID ); + printf( "\n Priority base = %d", pe32.pcPriClassBase ); + if( dwPriorityClass ) + printf( "\n Priority class = %d", dwPriorityClass ); + + // List the modules and threads associated with this process + ListProcessModules( pe32.th32ProcessID ); + ListProcessThreads( pe32.th32ProcessID ); + + } while( Process32Next( hProcessSnap, &pe32 ) ); + + CloseHandle( hProcessSnap ); + return( TRUE ); +} + + +BOOL ListProcessModules( DWORD dwPID ) +{ + HANDLE hModuleSnap = INVALID_HANDLE_VALUE; + MODULEENTRY32 me32; + + // Take a snapshot of all modules in the specified process. + hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dwPID ); + if( hModuleSnap == INVALID_HANDLE_VALUE ) + { + printError( TEXT("CreateToolhelp32Snapshot (of modules)") ); + return( FALSE ); + } + + // Set the size of the structure before using it. + me32.dwSize = sizeof( MODULEENTRY32 ); + + // Retrieve information about the first module, + // and exit if unsuccessful + if( !Module32First( hModuleSnap, &me32 ) ) + { + printError( TEXT("Module32First") ); // show cause of failure + CloseHandle( hModuleSnap ); // clean the snapshot object + return( FALSE ); + } + + // Now walk the module list of the process, + // and display information about each module + do + { + _tprintf( TEXT("\n\n MODULE NAME: %s"), me32.szModule ); + _tprintf( TEXT("\n Executable = %s"), me32.szExePath ); + printf( "\n Process ID = 0x%08X", me32.th32ProcessID ); + printf( "\n Ref count (g) = 0x%04X", me32.GlblcntUsage ); + printf( "\n Ref count (p) = 0x%04X", me32.ProccntUsage ); + printf( "\n Base address = 0x%08X", (DWORD) me32.modBaseAddr ); + printf( "\n Base size = %d", me32.modBaseSize ); + + } while( Module32Next( hModuleSnap, &me32 ) ); + + CloseHandle( hModuleSnap ); + return( TRUE ); +} + +BOOL ListProcessThreads( DWORD dwOwnerPID ) +{ + HANDLE hThreadSnap = INVALID_HANDLE_VALUE; + THREADENTRY32 te32; + + // Take a snapshot of all running threads + hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( hThreadSnap == INVALID_HANDLE_VALUE ) + return( FALSE ); + + // Fill in the size of the structure before using it. + te32.dwSize = sizeof(THREADENTRY32 ); + + // Retrieve information about the first thread, + // and exit if unsuccessful + if( !Thread32First( hThreadSnap, &te32 ) ) + { + printError( TEXT("Thread32First") ); // show cause of failure + CloseHandle( hThreadSnap ); // clean the snapshot object + return( FALSE ); + } + + // Now walk the thread list of the system, + // and display information about each thread + // associated with the specified process + do + { + if( te32.th32OwnerProcessID == dwOwnerPID ) + { + printf( "\n\n THREAD ID = 0x%08X", te32.th32ThreadID ); + printf( "\n Base priority = %d", te32.tpBasePri ); + printf( "\n Delta priority = %d", te32.tpDeltaPri ); + } + } while( Thread32Next(hThreadSnap, &te32 ) ); + + CloseHandle( hThreadSnap ); + return( TRUE ); +} + +void printError( TCHAR* msg ) +{ + DWORD eNum; + TCHAR sysMsg[256]; + TCHAR* p; + + eNum = GetLastError( ); + FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, eNum, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language + sysMsg, 256, NULL ); + + // Trim the end of the line and terminate it with a null + p = sysMsg; + while( ( *p > 31 ) || ( *p == 9 ) ) + ++p; + do { *p-- = 0; } while( ( p >= sysMsg ) && + ( ( *p == '.' ) || ( *p < 33 ) ) ); + + // Display the message + _tprintf( TEXT("\n WARNING: %s failed with error %d (%s)"), msg, eNum, sysMsg ); +} + +#endif + + +void AttachRemoteDialog::procSelected(const QModelIndex &index0) +{ + QModelIndex index = index0.sibling(index0.row(), 0); + QStandardItem *item = m_model->itemFromIndex(index); + if (!item) + return; + pidLineEdit->setText(item->text()); + accept(); +} + +int AttachRemoteDialog::attachPID() const +{ + return pidLineEdit->text().toInt(); +} diff --git a/src/plugins/debugger/attachremotedialog.h b/src/plugins/debugger/attachremotedialog.h new file mode 100644 index 0000000000..ec732cd515 --- /dev/null +++ b/src/plugins/debugger/attachremotedialog.h @@ -0,0 +1,65 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef ATTACHREMOTE_DIALOG_H +#define ATTACHREMOTE_DIALOG_H + +#include "ui_attachremotedialog.h" + +QT_BEGIN_NAMESPACE +class QStandardItemModel; +QT_END_NAMESPACE + +namespace Debugger { +namespace Internal { + +class AttachRemoteDialog : public QDialog, Ui::AttachRemoteDialog +{ + Q_OBJECT + +public: + explicit AttachRemoteDialog(QWidget *parent, const QString &pid); + int attachPID() const; + +private slots: + void rebuildProcessList(); + void procSelected(const QModelIndex &); + +private: + QString m_defaultPID; + QStandardItemModel *m_model; +}; + +} // namespace Debugger +} // namespace Internal + +#endif // ATTACHREMOTEDIALOG_H diff --git a/src/plugins/debugger/attachremotedialog.ui b/src/plugins/debugger/attachremotedialog.ui new file mode 100644 index 0000000000..4d478e3b0f --- /dev/null +++ b/src/plugins/debugger/attachremotedialog.ui @@ -0,0 +1,64 @@ +<ui version="4.0" > + <class>AttachRemoteDialog</class> + <widget class="QDialog" name="AttachRemoteDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>561</width> + <height>866</height> + </rect> + </property> + <property name="windowTitle" > + <string>Start Debugger</string> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout" > + <item> + <widget class="QLabel" name="pidLabel" > + <property name="text" > + <string>Attach to Process ID:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="pidLineEdit" /> + </item> + </layout> + </item> + <item> + <widget class="QTreeView" name="procView" > + <property name="editTriggers" > + <set>QAbstractItemView::NoEditTriggers</set> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons" > + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/debugger/breakbyfunction.ui b/src/plugins/debugger/breakbyfunction.ui new file mode 100644 index 0000000000..06cedb2e46 --- /dev/null +++ b/src/plugins/debugger/breakbyfunction.ui @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BreakByFunctionDialog</class> + <widget class="QDialog" name="BreakByFunctionDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>337</width> + <height>101</height> + </rect> + </property> + <property name="windowTitle"> + <string>Start Debugger</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLabel" name="functionLabel"> + <property name="text"> + <string>Function to break on:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="functionLineEdit"/> + </item> + </layout> + </item> + <item> + <widget class="Line" name="line"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/debugger/breakcondition.ui b/src/plugins/debugger/breakcondition.ui new file mode 100644 index 0000000000..4de45fd29d --- /dev/null +++ b/src/plugins/debugger/breakcondition.ui @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>BreakCondition</class> + <widget class="QDialog" name="BreakCondition"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>435</width> + <height>142</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="labelCondition"> + <property name="text"> + <string>Condition:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="lineEditCondition"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="labelIgnoreCount"> + <property name="text"> + <string>Ignore count:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="spinBoxIgnoreCount"> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>BreakCondition</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>BreakCondition</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp new file mode 100644 index 0000000000..3d83b5e323 --- /dev/null +++ b/src/plugins/debugger/breakhandler.cpp @@ -0,0 +1,559 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "breakhandler.h" + +#include "assert.h" +#include "imports.h" // TextEditor::BaseTextMark + +#include <QtCore/QDebug> +#include <QtCore/QFileInfo> + +using namespace Debugger; +using namespace Debugger::Internal; + + +////////////////////////////////////////////////////////////////// +// +// BreakpointMarker +// +////////////////////////////////////////////////////////////////// + +namespace Debugger { +namespace Internal { + + +// The red blob on the left side in the cpp editor. +class BreakpointMarker : public TextEditor::BaseTextMark +{ + Q_OBJECT +public: + BreakpointMarker(BreakpointData *data, const QString &fileName, int lineNumber) + : BaseTextMark(fileName, lineNumber), m_data(data), m_pending(true) + { + //qDebug() << "CREATE MARKER " << fileName << lineNumber; + } + + ~BreakpointMarker() + { + //qDebug() << "REMOVE MARKER "; + m_data = 0; + } + + QIcon icon() const + { + static const QIcon icon(":/gdbdebugger/images/breakpoint.svg"); + static const QIcon icon2(":/gdbdebugger/images/breakpoint_pending.svg"); + return m_pending ? icon2 : icon; + } + + void setPending(bool pending) + { + if (pending == m_pending) + return; + m_pending = pending; + updateMarker(); + } + + void updateBlock(const QTextBlock &) + { + //qDebug() << "BREAKPOINT MARKER UPDATE BLOCK"; + } + + void removedFromEditor() + { + if (!m_data) + return; + + BreakHandler *handler = m_data->handler(); + handler->removeBreakpoint(handler->indexOf(m_data)); + handler->saveBreakpoints(); + handler->updateMarkers(); + } + + void updateLineNumber(int lineNumber) + { + if (!m_data) + return; + //if (m_data->markerLineNumber == lineNumber) + // return; + if (m_data->markerLineNumber != lineNumber) { + m_data->markerLineNumber = lineNumber; + // FIXME: should we tell gdb about the change? + // Ignore it for now, as we would require re-compilation + // and debugger re-start anyway. + if (0 && !m_data->bpLineNumber.isEmpty()) { + if (!m_data->bpNumber.trimmed().isEmpty()) { + m_data->pending = true; + } + } + } + m_data->lineNumber = QString::number(lineNumber); + m_data->handler()->updateMarkers(); + } + +private: + BreakpointData *m_data; + bool m_pending; +}; + +} // namespace Internal +} // namespace Debugger + + + +////////////////////////////////////////////////////////////////// +// +// BreakpointData +// +////////////////////////////////////////////////////////////////// + +BreakpointData::BreakpointData(BreakHandler *handler) +{ + //qDebug() << "CREATE BREAKPOINTDATA" << this; + m_handler = handler; + pending = true; + marker = 0; + markerLineNumber = 0; + bpMultiple = false; +} + +BreakpointData::~BreakpointData() +{ + removeMarker(); + //qDebug() << "DESTROY BREAKPOINTDATA" << this; +} + +void BreakpointData::removeMarker() +{ + BreakpointMarker *m = marker; + marker = 0; + delete m; +} + +void BreakpointData::updateMarker() +{ + if (marker && (markerFileName != marker->fileName() + || markerLineNumber != marker->lineNumber())) + removeMarker(); + + if (!marker && !markerFileName.isEmpty() && markerLineNumber > 0) + marker = new BreakpointMarker(this, markerFileName, markerLineNumber); + + if (marker) + marker->setPending(pending); +} + +QString BreakpointData::toToolTip() const +{ + QString str; + str += "<table>"; + str += "<tr><td>Marker File:</td><td>" + markerFileName + "</td></tr>"; + str += "<tr><td>Marker Line:</td><td>" + QString::number(markerLineNumber) + "</td></tr>"; + str += "<tr><td>BP Number:</td><td>" + bpNumber + "</td></tr>"; + str += "<tr><td>BP Address:</td><td>" + bpAddress + "</td></tr>"; + str += "<tr><td>----------</td><td></td><td></td></tr>"; + str += "<tr><td>Property:</td><td>Wanted:</td><td>Actual:</td></tr>"; + str += "<tr><td></td><td></td><td></td></tr>"; + str += "<tr><td>Internal Number:</td><td>-</td><td>" + bpNumber + "</td></tr>"; + str += "<tr><td>File Name:</td><td>" + fileName + "</td><td>" + bpFileName + "</td></tr>"; + str += "<tr><td>Function Name:</td><td>" + funcName + "</td><td>" + bpFuncName + "</td></tr>"; + str += "<tr><td>Line Number:</td><td>" + lineNumber + "</td><td>" + bpLineNumber + "</td></tr>"; + str += "<tr><td>Condition:</td><td>" + condition + "</td><td>" + bpCondition + "</td></tr>"; + str += "<tr><td>Ignore count:</td><td>" + ignoreCount + "</td><td>" + bpIgnoreCount + "</td></tr>"; + str += "</table>"; + return str; +} + +bool BreakpointData::isLocatedAt(const QString &fileName_, int lineNumber_) const +{ + /* + if (lineNumber != QString::number(lineNumber_)) + return false; + if (fileName == fileName_) + return true; + if (fileName_.endsWith(fileName)) + return true; + return false; + */ + return lineNumber_ == markerLineNumber && fileName_ == markerFileName; +} + +bool BreakpointData::conditionsMatch() const +{ + // same versions of gdb "beautify" the passed condition + QString s1 = condition; + s1.remove(QChar(' ')); + QString s2 = bpCondition; + s2.remove(QChar(' ')); + return s1 == s2; +} + + +////////////////////////////////////////////////////////////////// +// +// BreakHandler +// +////////////////////////////////////////////////////////////////// + +BreakHandler::BreakHandler(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +int BreakHandler::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 6; +} + +int BreakHandler::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : size(); +} + +void BreakHandler::removeAt(int index) +{ + BreakpointData *data = at(index); + m_bp.removeAt(index); + delete data; +} + +void BreakHandler::clear() +{ + for (int index = size(); --index >= 0; ) + removeAt(index); +} + +int BreakHandler::findBreakpoint(const BreakpointData &needle) +{ + // looks for a breakpoint we might refer to + for (int index = 0; index != size(); ++index) { + const BreakpointData *data = at(index); + // clear hit. + if (data->bpNumber == needle.bpNumber) + return index; + // at least at a position we were looking for + // FIXME: breaks multiple breakpoints at the same location + if (data->fileName == needle.bpFileName + && data->lineNumber == needle.bpLineNumber) + return index; + } + return -1; +} + +int BreakHandler::findBreakpoint(int bpNumber) +{ + for (int index = 0; index != size(); ++index) + if (at(index)->bpNumber == QString::number(bpNumber)) + return index; + return -1; +} + +void BreakHandler::saveBreakpoints() +{ + QList<QVariant> list; + for (int index = 0; index != size(); ++index) { + const BreakpointData *data = at(index); + QMap<QString, QVariant> map; + if (!data->fileName.isEmpty()) + map["filename"] = data->fileName; + if (!data->lineNumber.isEmpty()) + map["linenumber"] = data->lineNumber; + if (!data->funcName.isEmpty()) + map["funcname"] = data->funcName; + if (!data->condition.isEmpty()) + map["condition"] = data->condition; + if (!data->ignoreCount.isEmpty()) + map["ignorecount"] = data->ignoreCount; + list.append(map); + } + setSessionValueRequested("Breakpoints", list); +} + +void BreakHandler::loadBreakpoints() +{ + QVariant value; + sessionValueRequested("Breakpoints", &value); + QList<QVariant> list = value.toList(); + + clear(); + foreach (const QVariant &var, list) { + const QMap<QString, QVariant> map = var.toMap(); + BreakpointData *data = new BreakpointData(this); + data->fileName = map["filename"].toString(); + data->lineNumber = map["linenumber"].toString(); + data->condition = map["condition"].toString(); + data->ignoreCount = map["ignorecount"].toString(); + data->funcName = map["funcname"].toString(); + data->markerFileName = data->fileName; + data->markerLineNumber = data->lineNumber.toInt(); + append(data); + } +} + +void BreakHandler::resetBreakpoints() +{ + for (int index = size(); --index >= 0;) { + BreakpointData *data = at(index); + data->pending = true; + data->bpNumber.clear(); + data->bpFuncName.clear(); + data->bpFileName.clear(); + data->bpLineNumber.clear(); + data->bpCondition.clear(); + data->bpIgnoreCount.clear(); + // keep marker data if it was primary + if (data->markerFileName != data->fileName) + data->markerFileName.clear(); + if (data->markerLineNumber != data->lineNumber.toInt()) + data->markerLineNumber = 0; + } +} + +void BreakHandler::updateMarkers() +{ + for (int index = 0; index != size(); ++index) + at(index)->updateMarker(); + emit layoutChanged(); +} + +QVariant BreakHandler::headerData(int section, + Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + static QString headers[] = { + tr("Number"), tr("Function"), tr("File"), tr("Line"), + tr("Condition"), tr("Ignore") + }; + return headers[section]; + } + return QVariant(); +} + +QVariant BreakHandler::data(const QModelIndex &mi, int role) const +{ + static const QIcon icon(":/gdbdebugger/images/breakpoint.svg"); + static const QIcon icon2(":/gdbdebugger/images/breakpoint_pending.svg"); + static const QString empty = QString(QLatin1Char('-')); + + QWB_ASSERT(mi.isValid(), return QVariant()); + + if (mi.row() >= size()) + return QVariant(); + + const BreakpointData *data = at(mi.row()); + switch (mi.column()) { + case 0: + if (role == Qt::DisplayRole) { + QString str = data->bpNumber; + return str.isEmpty() ? empty : str; + } + if (role == Qt::DecorationRole) + return data->pending ? icon2 : icon; + break; + case 1: + if (role == Qt::DisplayRole) { + QString str = data->pending ? data->funcName : data->bpFuncName; + return str.isEmpty() ? empty : str; + } + break; + case 2: + if (role == Qt::DisplayRole) { + QString str = data->pending ? data->fileName : data->bpFileName; + str = QFileInfo(str).fileName(); + //if (data->bpMultiple && str.isEmpty() && !data->markerFileName.isEmpty()) + // str = data->markerFileName; + return str.isEmpty() ? empty : str; + } + break; + case 3: + if (role == Qt::DisplayRole) { + QString str = data->pending ? data->lineNumber : data->bpLineNumber; + //if (data->bpMultiple && str.isEmpty() && !data->markerFileName.isEmpty()) + // str = data->markerLineNumber; + return str.isEmpty() ? empty : str; + } + break; + case 4: + if (role == Qt::DisplayRole) + return data->pending ? data->condition : data->bpCondition; + if (role == Qt::ToolTipRole) + return tr("Breakpoint will only be hit if this condition is met."); + break; + case 5: + if (role == Qt::DisplayRole) + return data->pending ? data->ignoreCount : data->bpIgnoreCount; + if (role == Qt::ToolTipRole) + return tr("Breakpoint will only be hit after being ignored so many times."); + break; + } + if (role == Qt::ToolTipRole) + return data->toToolTip(); + return QVariant(); +} + +bool BreakHandler::setData(const QModelIndex &mi, const QVariant &value, int role) +{ + if (role != Qt::EditRole) + return false; + + BreakpointData *data = at(mi.row()); + switch (mi.column()) { + case 4: { + QString val = value.toString(); + if (val != data->condition) { + data->condition = val; + dataChanged(mi, mi); + } + return true; + } + case 5: { + QString val = value.toString(); + if (val != data->ignoreCount) { + data->ignoreCount = val; + dataChanged(mi, mi); + } + return true; + } + default: { + return false; + } + } +} + +QList<BreakpointData *> BreakHandler::takeRemovedBreakpoints() +{ + QList<BreakpointData *> result = m_removed; + m_removed.clear(); + return result; +} + +void BreakHandler::removeBreakpointHelper(int index) +{ + BreakpointData *data = m_bp.at(index); + m_bp.removeAt(index); + data->removeMarker(); + m_removed.append(data); +} + + +void BreakHandler::removeBreakpoint(int index) +{ + if (index < 0 || index >= size()) + return; + BreakHandler::removeBreakpointHelper(index); + emit layoutChanged(); + saveBreakpoints(); +} + + +int BreakHandler::indexOf(const QString &fileName, int lineNumber) +{ + for (int index = 0; index != size(); ++index) + if (at(index)->isLocatedAt(fileName, lineNumber)) + return index; + return -1; +} + +void BreakHandler::setBreakpoint(const QString &fileName, int lineNumber) +{ + QFileInfo fi(fileName); + + BreakpointData *data = new BreakpointData(this); + data->fileName = fileName; + data->lineNumber = QString::number(lineNumber); + data->pending = true; + data->markerFileName = fileName; + data->markerLineNumber = lineNumber; + append(data); + emit layoutChanged(); + saveBreakpoints(); + updateMarkers(); +} + +void BreakHandler::removeAllBreakpoints() +{ + for (int index = size(); --index >= 0;) + removeBreakpointHelper(index); + emit layoutChanged(); + saveBreakpoints(); + updateMarkers(); +} + +void BreakHandler::setAllPending() +{ + loadBreakpoints(); + for (int index = size(); --index >= 0;) + at(index)->pending = true; + saveBreakpoints(); + updateMarkers(); +} + +void BreakHandler::saveSessionData() +{ + saveBreakpoints(); + updateMarkers(); +} + +void BreakHandler::loadSessionData() +{ + //resetBreakpoints(); + loadBreakpoints(); + updateMarkers(); +} + +void BreakHandler::activateBreakPoint(int index) +{ + const BreakpointData *data = at(index); + //qDebug() << "BREAKPOINT ACTIVATED: " << data->fileName; + if (!data->markerFileName.isEmpty()) + emit gotoLocation(data->markerFileName, data->markerLineNumber, false); +} + +void BreakHandler::breakByFunction(const QString &functionName) +{ + // One per function is enough for now + for (int index = size(); --index >= 0;) { + const BreakpointData *data = at(index); + QWB_ASSERT(data, break); + if (data->funcName == functionName && data->condition.isEmpty() + && data->ignoreCount.isEmpty()) + return; + } + BreakpointData *data = new BreakpointData(this); + data->funcName = functionName; + append(data); + saveBreakpoints(); + updateMarkers(); +} + +#include "breakhandler.moc" diff --git a/src/plugins/debugger/breakhandler.h b/src/plugins/debugger/breakhandler.h new file mode 100644 index 0000000000..8ce63272b7 --- /dev/null +++ b/src/plugins/debugger/breakhandler.h @@ -0,0 +1,174 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_BREAKHANDLER_H +#define DEBUGGER_BREAKHANDLER_H + +#include <QtCore/QObject> +#include <QtCore/QAbstractItemModel> + +namespace Debugger { +namespace Internal { + +class BreakpointMarker; +class BreakHandler; + +////////////////////////////////////////////////////////////////// +// +// BreakpointData +// +////////////////////////////////////////////////////////////////// + +class BreakpointData +{ +public: + explicit BreakpointData(BreakHandler *handler); + ~BreakpointData(); + + void removeMarker(); + void updateMarker(); + QString toToolTip() const; + BreakHandler *handler() { return m_handler; } + + bool isLocatedAt(const QString &fileName, int lineNumber) const; + bool conditionsMatch() const; + +private: + // Intentionally unimplemented. + // Making it copiable is tricky because of the markers. + void operator=(const BreakpointData &); + BreakpointData(const BreakpointData &); + + // Our owner + BreakHandler *m_handler; // not owned. + +public: + bool pending; // does the debugger engine know about us already? + + // this "user requested information". will get stored in the session + QString fileName; // short name of source file + QString condition; // condition associated with breakpoint + QString ignoreCount; // ignore count associated with breakpoint + QString lineNumber; // line in source file + QString funcName; // name of containing function + + // this is what gdb produced in response + QString bpNumber; // breakpoint number assigned by the debugger engine + QString bpCondition; // condition acknowledged by the debugger engine + QString bpIgnoreCount; // ignore count acknowledged by the debugger engine + QString bpFileName; // file name acknowledged by the debugger engine + QString bpLineNumber; // line number acknowledged by the debugger engine + QString bpFuncName; // function name acknowledged by the debugger engine + QString bpAddress; // address acknowledged by the debugger engine + bool bpMultiple; // happens in constructors/gdb + + // taken from either user input or gdb responses + QString markerFileName; // used to locate the marker + int markerLineNumber; + + // our red blob in the editor + BreakpointMarker *marker; +}; + + +////////////////////////////////////////////////////////////////// +// +// BreakHandler +// +////////////////////////////////////////////////////////////////// + +class BreakHandler : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit BreakHandler(QObject *parent = 0); + + void removeAllBreakpoints(); + void setAllPending(); + void loadSessionData(); + void saveSessionData(); + + QAbstractItemModel *model() { return this; } + + BreakpointData *at(int index) const { return index < size() ? m_bp.at(index) : 0; } + int size() const { return m_bp.size(); } + void append(BreakpointData *data) { m_bp.append(data); } + void removeAt(int index); // also deletes the marker + void clear(); // also deletes all the marker + int indexOf(BreakpointData *data) { return m_bp.indexOf(data); } + int indexOf(const QString &fileName, int lineNumber); + int findBreakpoint(const BreakpointData &data); // returns index + int findBreakpoint(int bpNumber); // returns index + void updateMarkers(); + + QList<BreakpointData *> takeRemovedBreakpoints(); + +public slots: + void setBreakpoint(const QString &fileName, int lineNumber); + void breakByFunction(const QString &functionName); + void activateBreakPoint(int index); + void removeBreakpoint(int index); + +signals: + void gotoLocation(const QString &fileName, int lineNumber, bool setMarker); + + void sessionValueRequested(const QString &name, QVariant *value); + void setSessionValueRequested(const QString &name, const QVariant &value); + +private: + friend class BreakpointMarker; + + // QAbstractItemModel + int columnCount(const QModelIndex &parent) const; + int rowCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &, int role); + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + QModelIndex index(int row, int column, const QModelIndex &) const + { return createIndex(row, column); } + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + void markerUpdated(BreakpointMarker *, int lineNumber); + void loadBreakpoints(); + void saveBreakpoints(); + void resetBreakpoints(); + void removeBreakpointHelper(int index); + + QList<BreakpointData *> m_bp; + QList<BreakpointData *> m_removed; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_BREAKHANDLER_H diff --git a/src/plugins/debugger/breakwindow.cpp b/src/plugins/debugger/breakwindow.cpp new file mode 100644 index 0000000000..11248f9a58 --- /dev/null +++ b/src/plugins/debugger/breakwindow.cpp @@ -0,0 +1,164 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "breakwindow.h" + +#include "ui_breakcondition.h" + +#include <QAction> +#include <QDir> +#include <QFileInfo> +#include <QFileInfoList> +#include <QHeaderView> +#include <QKeyEvent> +#include <QMenu> +#include <QResizeEvent> +#include <QToolButton> +#include <QTreeView> + +using Debugger::Internal::BreakWindow; + + +BreakWindow::BreakWindow(QWidget *parent) + : QTreeView(parent), m_alwaysResizeColumnsToContents(false) +{ + setWindowTitle(tr("Breakpoints")); + setWindowIcon(QIcon(":/gdbdebugger/images/debugger_breakpoints.png")); + setAlternatingRowColors(true); + setRootIsDecorated(false); + setIconSize(QSize(10, 10)); + + connect(this, SIGNAL(activated(QModelIndex)), + this, SLOT(rowActivated(QModelIndex))); +} + +void BreakWindow::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Delete) + deleteBreakpoint(currentIndex()); + QTreeView::keyPressEvent(event); +} + +void BreakWindow::resizeEvent(QResizeEvent *event) +{ + QHeaderView *hv = header(); + int totalSize = event->size().width() - 180; + hv->resizeSection(0, 60); + hv->resizeSection(1, (totalSize * 30) / 100); + hv->resizeSection(2, (totalSize * 30) / 100); + hv->resizeSection(3, (totalSize * 30) / 100); + hv->resizeSection(4, 70); + hv->resizeSection(5, 50); + QTreeView::resizeEvent(event); +} + +void BreakWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + QMenu menu; + QModelIndex index = indexAt(ev->pos()); + QAction *act0 = new QAction("Delete breakpoint", &menu); + QAction *act1 = new QAction("Adjust column widths to contents", &menu); + QAction *act2 = new QAction("Always adjust column widths to contents", &menu); + QAction *act3 = new QAction("Edit condition...", &menu); + act2->setCheckable(true); + act2->setChecked(m_alwaysResizeColumnsToContents); + if (index.isValid()) { + menu.addAction(act0); + menu.addAction(act3); + menu.addSeparator(); + } + menu.addAction(act1); + menu.addAction(act2); + + QAction *act = menu.exec(ev->globalPos()); + + if (act == act0) + deleteBreakpoint(index); + else if (act == act1) + resizeColumnsToContents(); + else if (act == act2) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); + else if (act == act3) + editCondition(index); +} + +void BreakWindow::deleteBreakpoint(const QModelIndex &idx) +{ + int row = idx.row(); + if (row == model()->rowCount() - 1) + --row; + setCurrentIndex(idx.sibling(row, 0)); + emit breakPointDeleted(idx.row()); +} + +void BreakWindow::editCondition(const QModelIndex &idx) +{ + QDialog dlg(this); + Ui::BreakCondition ui; + ui.setupUi(&dlg); + + int row = idx.row(); + dlg.setWindowTitle(tr("Conditions on Breakpoint %1").arg(row)); + ui.lineEditCondition->setText(model()->data(idx.sibling(row, 4)).toString()); + ui.spinBoxIgnoreCount->setValue(model()->data(idx.sibling(row, 5)).toInt()); + + if (dlg.exec() == QDialog::Rejected) + return; + + model()->setData(idx.sibling(row, 4), ui.lineEditCondition->text()); + model()->setData(idx.sibling(row, 5), ui.spinBoxIgnoreCount->value()); +} + +void BreakWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + resizeColumnToContents(1); + resizeColumnToContents(2); + resizeColumnToContents(3); +} + +void BreakWindow::setAlwaysResizeColumnsToContents(bool on) +{ + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + header()->setResizeMode(1, mode); + header()->setResizeMode(2, mode); + header()->setResizeMode(3, mode); +} + +void BreakWindow::rowActivated(const QModelIndex &index) +{ + emit breakPointActivated(index.row()); +} + diff --git a/src/plugins/debugger/breakwindow.h b/src/plugins/debugger/breakwindow.h new file mode 100644 index 0000000000..8b17b55145 --- /dev/null +++ b/src/plugins/debugger/breakwindow.h @@ -0,0 +1,76 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_BREAKWINDOW_H +#define DEBUGGER_BREAKWINDOW_H + +#include <QTreeView> + +namespace Debugger { +namespace Internal { + +class BreakWindow : public QTreeView +{ + Q_OBJECT + +public: + BreakWindow(QWidget *parent = 0); + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on); + +signals: + void breakPointDeleted(int index); + void breakPointActivated(int index); + +private slots: + void rowActivated(const QModelIndex &index); + +protected: + void resizeEvent(QResizeEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + void keyPressEvent(QKeyEvent *ev); + +private: + void deleteBreakpoint(const QModelIndex &idx); + void editCondition(const QModelIndex &idx); + + bool m_alwaysResizeColumnsToContents; +}; + + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_BREAKWINDOW + diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro new file mode 100644 index 0000000000..d21eb7cdc0 --- /dev/null +++ b/src/plugins/debugger/debugger.pro @@ -0,0 +1,92 @@ +TEMPLATE = lib +TARGET = Debugger + +# CONFIG += single +include(../../qworkbenchplugin.pri) +include(../../plugins/projectexplorer/projectexplorer.pri) +include(../../plugins/find/find.pri) +include(../../plugins/coreplugin/coreplugin.pri) +include(../../plugins/texteditor/texteditor.pri) +include(../../plugins/cpptools/cpptools.pri) +include(../../libs/cplusplus/cplusplus.pri) + +# DEFINES += QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII +QT += gui network script + +HEADERS += assert.h \ + attachexternaldialog.h \ + attachremotedialog.h \ + breakhandler.h \ + breakwindow.h \ + debuggerconstants.h \ + debuggermanager.h \ + debuggeroutputwindow.h \ + debuggerplugin.h \ + debuggerrunner.h \ + mode.h \ + disassemblerhandler.h \ + disassemblerwindow.h \ + gdbengine.h \ + gdbmi.h \ + gdboptionpage.h \ + idebuggerengine.h \ + imports.h \ + moduleshandler.h \ + moduleswindow.h \ + procinterrupt.h \ + registerhandler.h \ + registerwindow.h \ + scriptengine.h \ + stackhandler.h \ + stackwindow.h \ + startexternaldialog.h \ + threadswindow.h \ + watchhandler.h \ + watchwindow.h + +SOURCES += attachexternaldialog.cpp \ + attachremotedialog.cpp \ + breakhandler.cpp \ + breakwindow.cpp \ + breakwindow.h \ + debuggermanager.cpp \ + debuggeroutputwindow.cpp \ + debuggerplugin.cpp \ + debuggerrunner.cpp \ + mode.cpp \ + disassemblerhandler.cpp \ + disassemblerwindow.cpp \ + gdbengine.cpp \ + gdbmi.cpp \ + gdboptionpage.cpp \ + gdbtypemacros.cpp \ + gdbengine.h \ + moduleshandler.cpp \ + moduleswindow.cpp \ + procinterrupt.cpp \ + registerhandler.cpp \ + registerwindow.cpp \ + scriptengine.cpp \ + stackhandler.cpp \ + stackwindow.cpp \ + startexternaldialog.cpp \ + threadswindow.cpp \ + watchhandler.cpp \ + watchwindow.cpp + +FORMS += attachexternaldialog.ui \ + attachremotedialog.ui \ + breakbyfunction.ui \ + breakcondition.ui \ + mode.ui \ + gdboptionpage.ui \ + gdbtypemacros.ui \ + startexternaldialog.ui \ + +RESOURCES += debugger.qrc + +false { +SOURCES += $$PWD/modeltest.cpp +HEADERS += $$PWD/modeltest.h +DEFINES += USE_MODEL_TEST=1 +} diff --git a/src/plugins/debugger/debugger.qrc b/src/plugins/debugger/debugger.qrc new file mode 100644 index 0000000000..548c27ac9f --- /dev/null +++ b/src/plugins/debugger/debugger.qrc @@ -0,0 +1,24 @@ +<RCC> + <qresource prefix="/gdbdebugger" > + <file>images/breakpoint.svg</file> + <file>images/breakpoint_pending.svg</file> + <file>images/debugger_breakpoints.png</file> + <file>images/debugger_continue_small.png</file> + <file>images/debugger_interrupt_small.png</file> + <file>images/debugger_start.png</file> + <file>images/debugger_start_small.png</file> + <file>images/debugger_stepinto_small.png</file> + <file>images/debugger_stepout_small.png</file> + <file>images/debugger_stepover_small.png</file> + <file>images/debugger_steponeproc_small.png</file> + <file>images/debugger_stepoverproc_small.png</file> + <file>images/debugger_stop_small.png</file> + <file>images/delete.png</file> + <file>images/done.png</file> + <file>images/empty.svg</file> + <file>images/error.png</file> + <file>images/location.svg</file> + <file>images/newitem.png</file> + <file>images/running.png</file> + </qresource> +</RCC> diff --git a/src/plugins/debugger/debuggerconstants.h b/src/plugins/debugger/debuggerconstants.h new file mode 100644 index 0000000000..56d790e074 --- /dev/null +++ b/src/plugins/debugger/debuggerconstants.h @@ -0,0 +1,65 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGERCONSTANTS_H +#define DEBUGGERCONSTANTS_H + +namespace Debugger { +namespace Constants { + +// modes and their priorities +const char * const MODE_DEBUG = "Debugger.Mode.Debug"; +const int P_MODE_DEBUG = 85; + +// common actions +const char * const INTERRUPT = "Debugger.Interrupt"; +const char * const RESET = "Debugger.Reset"; +const char * const STEP = "Debugger.StepLine"; +const char * const STEPOUT = "Debugger.StepOut"; +const char * const NEXT = "Debugger.NextLine"; +const char * const STEPI = "Debugger.StepInstruction"; +const char * const NEXTI = "Debugger.NextInstruction"; + +const char * const M_VIEW_DEBUG = "Debugger.Menu.View.Debug"; +const char * const G_DEBUG = "Debugger.Group.Debug"; +const char * const G_VIEW_DEBUG = "Debugger.Group.View.Debug"; + +const char * const C_GDBDEBUGGER = "Gdb Debugger"; +const char * const GDBRUNNING = "Gdb.Running"; + +const char * const PROPERTY_REGISTER_FORMAT = "Debugger.Property.RegisterFormat"; + +} // namespace Constants +} // namespace Debugger + +#endif // DEBUGGERCONSTANTS_H + diff --git a/src/plugins/debugger/debuggermanager.cpp b/src/plugins/debugger/debuggermanager.cpp new file mode 100644 index 0000000000..bcf314ae41 --- /dev/null +++ b/src/plugins/debugger/debuggermanager.cpp @@ -0,0 +1,1298 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ + +#include "debuggermanager.h" + +#include "assert.h" +#include "debuggerconstants.h" +#include "idebuggerengine.h" + +#include "breakwindow.h" +#include "disassemblerwindow.h" +#include "debuggeroutputwindow.h" +#include "moduleswindow.h" +#include "registerwindow.h" +#include "stackwindow.h" +#include "threadswindow.h" +#include "watchwindow.h" + +#include "ui_breakbyfunction.h" + +#include "disassemblerhandler.h" +#include "breakhandler.h" +#include "moduleshandler.h" +#include "registerhandler.h" +#include "stackhandler.h" +#include "watchhandler.h" + +#include "startexternaldialog.h" +#include "attachexternaldialog.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QTime> + +#include <QtGui/QAction> +#include <QtGui/QComboBox> +#include <QtGui/QDockWidget> +#include <QtGui/QErrorMessage> +#include <QtGui/QFileDialog> +#include <QtGui/QLabel> +#include <QtGui/QMainWindow> +#include <QtGui/QMessageBox> +#include <QtGui/QPlainTextEdit> +#include <QtGui/QStatusBar> +#include <QtGui/QTextBlock> +#include <QtGui/QTextCursor> +#include <QtGui/QToolBar> +#include <QtGui/QToolButton> +#include <QtGui/QToolTip> + +using namespace Debugger; +using namespace Debugger::Internal; +using namespace Debugger::Constants; + +static const QString tooltipIName = "tooltip"; + +/////////////////////////////////////////////////////////////////////// +// +// BreakByFunctionDialog +// +/////////////////////////////////////////////////////////////////////// + +class BreakByFunctionDialog : public QDialog, Ui::BreakByFunctionDialog +{ + Q_OBJECT + +public: + explicit BreakByFunctionDialog(QWidget *parent) + : QDialog(parent) + { + setupUi(this); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + } + QString functionName() const { return functionLineEdit->text(); } +}; + + +/////////////////////////////////////////////////////////////////////// +// +// DebuggerManager +// +/////////////////////////////////////////////////////////////////////// + +static IDebuggerEngine *gdbEngine = 0; +static IDebuggerEngine *winEngine = 0; +static IDebuggerEngine *scriptEngine = 0; + +extern IDebuggerEngine *createGdbEngine(DebuggerManager *parent); +extern IDebuggerEngine *createWinEngine(DebuggerManager *) { return 0; } +extern IDebuggerEngine *createScriptEngine(DebuggerManager *parent); + +DebuggerManager::DebuggerManager() +{ + init(); +} + +DebuggerManager::~DebuggerManager() +{ + delete gdbEngine; + delete winEngine; + delete scriptEngine; +} + +void DebuggerManager::init() +{ + m_status = -1; + m_busy = false; + + m_attachedPID = 0; + m_startMode = startInternal; + + m_disassemblerHandler = 0; + m_modulesHandler = 0; + m_registerHandler = 0; + + m_breakWindow = new BreakWindow; + m_disassemblerWindow = new DisassemblerWindow; + m_modulesWindow = new ModulesWindow; + m_outputWindow = new DebuggerOutputWindow; + m_registerWindow = new RegisterWindow; + m_stackWindow = new StackWindow; + m_threadsWindow = new ThreadsWindow; + m_localsWindow = new WatchWindow(WatchWindow::LocalsType); + m_watchersWindow = new WatchWindow(WatchWindow::WatchersType); + //m_tooltipWindow = new WatchWindow(WatchWindow::TooltipType); + //m_watchersWindow = new QTreeView; + m_tooltipWindow = new QTreeView; + + m_mainWindow = new QMainWindow; + m_mainWindow->setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); + m_mainWindow->setDocumentMode(true); + + // Stack + m_stackHandler = new StackHandler; + QAbstractItemView *stackView = + qobject_cast<QAbstractItemView *>(m_stackWindow); + stackView->setModel(m_stackHandler->stackModel()); + connect(stackView, SIGNAL(frameActivated(int)), + this, SLOT(activateFrame(int))); + + // Threads + m_threadsHandler = new ThreadsHandler; + QAbstractItemView *threadsView = + qobject_cast<QAbstractItemView *>(m_threadsWindow); + threadsView->setModel(m_threadsHandler->threadsModel()); + connect(threadsView, SIGNAL(threadSelected(int)), + this, SLOT(selectThread(int))); + + // Disassembler + m_disassemblerHandler = new DisassemblerHandler; + QAbstractItemView *disassemblerView = + qobject_cast<QAbstractItemView *>(m_disassemblerWindow); + disassemblerView->setModel(m_disassemblerHandler->model()); + + // Breakpoints + m_breakHandler = new BreakHandler; + QAbstractItemView *breakView = + qobject_cast<QAbstractItemView *>(m_breakWindow); + breakView->setModel(m_breakHandler->model()); + connect(breakView, SIGNAL(breakPointActivated(int)), + m_breakHandler, SLOT(activateBreakPoint(int))); + connect(breakView, SIGNAL(breakPointDeleted(int)), + m_breakHandler, SLOT(removeBreakpoint(int))); + connect(m_breakHandler, SIGNAL(gotoLocation(QString,int,bool)), + this, SLOT(gotoLocation(QString,int,bool))); + connect(m_breakHandler, SIGNAL(sessionValueRequested(QString,QVariant*)), + this, SIGNAL(sessionValueRequested(QString,QVariant*))); + connect(m_breakHandler, SIGNAL(setSessionValueRequested(QString,QVariant)), + this, SIGNAL(setSessionValueRequested(QString,QVariant))); + + // Modules + QAbstractItemView *modulesView = + qobject_cast<QAbstractItemView *>(m_modulesWindow); + m_modulesHandler = new ModulesHandler; + modulesView->setModel(m_modulesHandler->model()); + connect(modulesView, SIGNAL(reloadModulesRequested()), + this, SLOT(reloadModules())); + connect(modulesView, SIGNAL(loadSymbolsRequested(QString)), + this, SLOT(loadSymbols(QString))); + connect(modulesView, SIGNAL(loadAllSymbolsRequested()), + this, SLOT(loadAllSymbols())); + + + // Registers + QAbstractItemView *registerView = + qobject_cast<QAbstractItemView *>(m_registerWindow); + m_registerHandler = new RegisterHandler; + registerView->setModel(m_registerHandler->model()); + + + m_watchHandler = new WatchHandler; + + // Locals + QTreeView *localsView = qobject_cast<QTreeView *>(m_localsWindow); + localsView->setModel(m_watchHandler->model()); + connect(localsView, SIGNAL(requestExpandChildren(QModelIndex)), + this, SLOT(expandChildren(QModelIndex))); + connect(localsView, SIGNAL(requestCollapseChildren(QModelIndex)), + this, SLOT(collapseChildren(QModelIndex))); + connect(localsView, SIGNAL(requestAssignValue(QString,QString)), + this, SLOT(assignValueInDebugger(QString,QString))); + connect(localsView, SIGNAL(requestWatchExpression(QString)), + this, SLOT(watchExpression(QString))); + + // Watchers + QTreeView *watchersView = qobject_cast<QTreeView *>(m_watchersWindow); + watchersView->setModel(m_watchHandler->model()); + connect(watchersView, SIGNAL(requestAssignValue(QString,QString)), + this, SLOT(assignValueInDebugger(QString,QString))); + connect(watchersView, SIGNAL(requestExpandChildren(QModelIndex)), + this, SLOT(expandChildren(QModelIndex))); + connect(watchersView, SIGNAL(requestCollapseChildren(QModelIndex)), + this, SLOT(collapseChildren(QModelIndex))); + connect(watchersView, SIGNAL(requestWatchExpression(QString)), + this, SLOT(watchExpression(QString))); + connect(watchersView, SIGNAL(requestRemoveWatchExpression(QString)), + this, SLOT(removeWatchExpression(QString))); + + // Tooltip + QTreeView *tooltipView = qobject_cast<QTreeView *>(m_tooltipWindow); + tooltipView->setModel(m_watchHandler->model()); + + connect(m_watchHandler, SIGNAL(watchModelUpdateRequested()), + this, SLOT(updateWatchModel())); + + m_startExternalAction = new QAction(this); + m_startExternalAction->setText(tr("Start and Debug External Application...")); + + m_attachExternalAction = new QAction(this); + m_attachExternalAction->setText(tr("Attach to Running External Application...")); + + m_continueAction = new QAction(this); + m_continueAction->setText(tr("Continue")); + m_continueAction->setIcon(QIcon(":/gdbdebugger/images/debugger_continue_small.png")); + + m_stopAction = new QAction(this); + m_stopAction->setText(tr("Interrupt")); + m_stopAction->setIcon(QIcon(":/gdbdebugger/images/debugger_interrupt_small.png")); + + m_resetAction = new QAction(this); + m_resetAction->setText(tr("Reset Debugger")); + + m_nextAction = new QAction(this); + m_nextAction->setText(tr("Step Over")); + //m_nextAction->setShortcut(QKeySequence(tr("F6"))); + m_nextAction->setIcon(QIcon(":/gdbdebugger/images/debugger_stepover_small.png")); + + m_stepAction = new QAction(this); + m_stepAction->setText(tr("Step Into")); + //m_stepAction->setShortcut(QKeySequence(tr("F7"))); + m_stepAction->setIcon(QIcon(":/gdbdebugger/images/debugger_stepinto_small.png")); + + m_nextIAction = new QAction(this); + m_nextIAction->setText(tr("Step Over Instruction")); + //m_nextIAction->setShortcut(QKeySequence(tr("Shift+F6"))); + m_nextIAction->setIcon(QIcon(":/gdbdebugger/images/debugger_stepoverproc_small.png")); + + m_stepIAction = new QAction(this); + m_stepIAction->setText(tr("Step One Instruction")); + //m_stepIAction->setShortcut(QKeySequence(tr("Shift+F9"))); + m_stepIAction->setIcon(QIcon(":/gdbdebugger/images/debugger_steponeproc_small.png")); + + m_stepOutAction = new QAction(this); + m_stepOutAction->setText(tr("Step Out")); + //m_stepOutAction->setShortcut(QKeySequence(tr("Shift+F7"))); + m_stepOutAction->setIcon(QIcon(":/gdbdebugger/images/debugger_stepout_small.png")); + + m_runToLineAction = new QAction(this); + m_runToLineAction->setText(tr("Run to Line")); + + m_runToFunctionAction = new QAction(this); + m_runToFunctionAction->setText(tr("Run to Outermost Function")); + + m_jumpToLineAction = new QAction(this); + m_jumpToLineAction->setText(tr("Jump to Line")); + + m_breakAction = new QAction(this); + m_breakAction->setText(tr("Toggle Breakpoint")); + + m_breakByFunctionAction = new QAction(this); + m_breakByFunctionAction->setText(tr("Set Breakpoint at Function...")); + + m_breakAtMainAction = new QAction(this); + m_breakAtMainAction->setText(tr("Set Breakpoint at Function 'main'")); + + m_debugDumpersAction = new QAction(this); + m_debugDumpersAction->setText(tr("Debug Custom Dumpers")); + m_debugDumpersAction->setCheckable(true); + + m_skipKnownFramesAction = new QAction(this); + m_skipKnownFramesAction->setText(tr("Skip Known Frames When Stepping")); + m_skipKnownFramesAction->setCheckable(true); + + m_useCustomDumpersAction = new QAction(this); + m_useCustomDumpersAction->setText(tr("Use Custom Display for Qt Objects")); + m_useCustomDumpersAction->setToolTip(tr("Checking this will make the debugger " + "try to use code to format certain data (QObject, QString, ...) nicely. ")); + m_useCustomDumpersAction->setCheckable(true); + m_useCustomDumpersAction->setChecked(true); + + m_useCustomDumpersAction = new QAction(this); + m_useCustomDumpersAction->setText(tr("Use Custom Display for Qt Objects")); + m_useCustomDumpersAction->setToolTip(tr("Checking this will make the debugger " + "try to use code to format certain data (QObject, QString, ...) nicely. ")); + m_useCustomDumpersAction->setCheckable(true); + m_useCustomDumpersAction->setChecked(true); + + m_useFastStartAction = new QAction(this); + m_useFastStartAction->setText(tr("Fast Debugger Start")); + m_useFastStartAction->setToolTip(tr("Checking this will make the debugger " + "start fast by loading only very few debug symbols on start up. This " + "might lead to situations where breakpoints can not be set properly. " + "So uncheck this option if you experience breakpoint related problems.")); + m_useFastStartAction->setCheckable(true); + m_useFastStartAction->setChecked(true); + + // FIXME + m_useFastStartAction->setChecked(false); + m_useFastStartAction->setEnabled(false); + + m_dumpLogAction = new QAction(this); + m_dumpLogAction->setText(tr("Dump Log File for Debugging Purposes")); + + m_watchAction = new QAction(this); + m_watchAction->setText(tr("Add to Watch Window")); + + // For usuage hints oin focus{In,Out} + //connect(m_outputWindow, SIGNAL(statusMessageRequested(QString,int)), + // this, SLOT(showStatusMessage(QString,int))); + + connect(m_continueAction, SIGNAL(triggered()), + this, SLOT(continueExec())); + + connect(m_startExternalAction, SIGNAL(triggered()), + this, SLOT(startExternalApplication())); + connect(m_attachExternalAction, SIGNAL(triggered()), + this, SLOT(attachExternalApplication())); + + connect(m_stopAction, SIGNAL(triggered()), + this, SLOT(interruptDebuggingRequest())); + connect(m_resetAction, SIGNAL(triggered()), + this, SLOT(exitDebugger())); + connect(m_nextAction, SIGNAL(triggered()), + this, SLOT(nextExec())); + connect(m_stepAction, SIGNAL(triggered()), + this, SLOT(stepExec())); + connect(m_nextIAction, SIGNAL(triggered()), + this, SLOT(nextIExec())); + connect(m_stepIAction, SIGNAL(triggered()), + this, SLOT(stepIExec())); + connect(m_stepOutAction, SIGNAL(triggered()), + this, SLOT(stepOutExec())); + connect(m_runToLineAction, SIGNAL(triggered()), + this, SLOT(runToLineExec())); + connect(m_runToFunctionAction, SIGNAL(triggered()), + this, SLOT(runToFunctionExec())); + connect(m_jumpToLineAction, SIGNAL(triggered()), + this, SLOT(jumpToLineExec())); + connect(m_watchAction, SIGNAL(triggered()), + this, SLOT(addToWatchWindow())); + connect(m_breakAction, SIGNAL(triggered()), + this, SLOT(toggleBreakpoint())); + connect(m_breakByFunctionAction, SIGNAL(triggered()), + this, SLOT(breakByFunction())); + connect(m_breakAtMainAction, SIGNAL(triggered()), + this, SLOT(breakAtMain())); + + connect(m_useFastStartAction, SIGNAL(triggered()), + this, SLOT(saveSessionData())); + connect(m_useCustomDumpersAction, SIGNAL(triggered()), + this, SLOT(saveSessionData())); + connect(m_skipKnownFramesAction, SIGNAL(triggered()), + this, SLOT(saveSessionData())); + connect(m_dumpLogAction, SIGNAL(triggered()), + this, SLOT(dumpLog())); + + connect(m_outputWindow, SIGNAL(commandExecutionRequested(QString)), + this, SLOT(executeDebuggerCommand(QString))); + + + m_breakDock = createDockForWidget(m_breakWindow); + + m_disassemblerDock = createDockForWidget(m_disassemblerWindow); + connect(m_disassemblerDock->toggleViewAction(), SIGNAL(toggled(bool)), + this, SLOT(reloadDisassembler()), Qt::QueuedConnection); + + m_modulesDock = createDockForWidget(m_modulesWindow); + connect(m_modulesDock->toggleViewAction(), SIGNAL(toggled(bool)), + this, SLOT(reloadModules()), Qt::QueuedConnection); + + m_registerDock = createDockForWidget(m_registerWindow); + connect(m_registerDock->toggleViewAction(), SIGNAL(toggled(bool)), + this, SLOT(reloadRegisters()), Qt::QueuedConnection); + + m_outputDock = createDockForWidget(m_outputWindow); + + m_stackDock = createDockForWidget(m_stackWindow); + + m_threadsDock = createDockForWidget(m_threadsWindow); + + setStatus(DebuggerProcessNotReady); + gdbEngine = createGdbEngine(this); + winEngine = createWinEngine(this); + scriptEngine = createScriptEngine(this); + setDebuggerType(GdbDebugger); +} + +void DebuggerManager::setDebuggerType(DebuggerType type) +{ + switch (type) { + case GdbDebugger: + m_engine = gdbEngine; + break; + case ScriptDebugger: + m_engine = scriptEngine; + break; + case WinDebugger: + m_engine = winEngine; + break; + } +} + +IDebuggerEngine *DebuggerManager::engine() +{ + return m_engine; +} + +IDebuggerManagerAccessForEngines *DebuggerManager::engineInterface() +{ + return dynamic_cast<IDebuggerManagerAccessForEngines *>(this); +} + +IDebuggerManagerAccessForDebugMode *DebuggerManager::debugModeInterface() +{ + return dynamic_cast<IDebuggerManagerAccessForDebugMode *>(this); +} + +void DebuggerManager::createDockWidgets() +{ + QSplitter *localsAndWatchers = new QSplitter(Qt::Vertical, 0); + localsAndWatchers->setWindowTitle(m_localsWindow->windowTitle()); + localsAndWatchers->addWidget(m_localsWindow); + localsAndWatchers->addWidget(m_watchersWindow); + localsAndWatchers->setStretchFactor(0, 3); + localsAndWatchers->setStretchFactor(1, 1); + m_watchDock = createDockForWidget(localsAndWatchers); +} + +QDockWidget *DebuggerManager::createDockForWidget(QWidget *widget) +{ + QDockWidget *dockWidget = new QDockWidget(widget->windowTitle(), m_mainWindow); + dockWidget->setObjectName(widget->windowTitle()); + //dockWidget->setAllowedAreas(Qt::BottomDockWidgetArea | Qt::RightDockWidgetArea); + dockWidget->setAllowedAreas(Qt::AllDockWidgetAreas); // that space is needed. + //dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); + dockWidget->setFeatures(QDockWidget::AllDockWidgetFeatures); + dockWidget->setTitleBarWidget(new QWidget(dockWidget)); + dockWidget->setWidget(widget); + connect(dockWidget->toggleViewAction(), SIGNAL(toggled(bool)), + this, SLOT(dockToggled(bool)), Qt::QueuedConnection); + m_dockWidgets.append(dockWidget); + return dockWidget; +} + +void DebuggerManager::setSimpleDockWidgetArrangement() +{ + foreach (QDockWidget *dockWidget, m_dockWidgets) + m_mainWindow->removeDockWidget(dockWidget); + + foreach (QDockWidget *dockWidget, m_dockWidgets) { + m_mainWindow->addDockWidget(Qt::BottomDockWidgetArea, dockWidget); + dockWidget->show(); + } + + m_mainWindow->tabifyDockWidget(m_watchDock, m_breakDock); + m_mainWindow->tabifyDockWidget(m_watchDock, m_disassemblerDock); + m_mainWindow->tabifyDockWidget(m_watchDock, m_modulesDock); + m_mainWindow->tabifyDockWidget(m_watchDock, m_outputDock); + m_mainWindow->tabifyDockWidget(m_watchDock, m_registerDock); + m_mainWindow->tabifyDockWidget(m_watchDock, m_threadsDock); + + // They are rarely used even in ordinary debugging. Hiding them also saves + // cycles since the corresponding information won't be retrieved. + m_registerDock->hide(); + m_disassemblerDock->hide(); + m_modulesDock->hide(); + m_outputDock->hide(); +} + +void DebuggerManager::setLocked(bool locked) +{ + const QDockWidget::DockWidgetFeatures features = + (locked) ? QDockWidget::NoDockWidgetFeatures : + QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable; + + foreach (QDockWidget *dockWidget, m_dockWidgets) { + QWidget *titleBarWidget = dockWidget->titleBarWidget(); + if (locked && !titleBarWidget) + titleBarWidget = new QWidget(dockWidget); + else if (!locked && titleBarWidget) { + delete titleBarWidget; + titleBarWidget = 0; + } + dockWidget->setTitleBarWidget(titleBarWidget); + dockWidget->setFeatures(features); + } +} + +void DebuggerManager::dockToggled(bool on) +{ + QDockWidget *dw = qobject_cast<QDockWidget *>(sender()->parent()); + if (on && dw) + dw->raise(); +} + +QAbstractItemModel *DebuggerManager::threadsModel() +{ + return qobject_cast<ThreadsWindow*>(m_threadsWindow)->model(); +} + +void DebuggerManager::showStatusMessage(const QString &msg, int timeout) +{ + Q_UNUSED(timeout) + //qDebug() << "STATUS: " << msg; + showDebuggerOutput("status:", msg); + mainWindow()->statusBar()->showMessage(msg, timeout); +#if 0 + QString currentTime = QTime::currentTime().toString("hh:mm:ss.zzz"); + + ICore *core = m_pm->getObject<Core::ICore>(); + //qDebug() << qPrintable(currentTime) << "Setting status: " << msg; + if (msg.isEmpty()) + core->messageManager()->displayStatusBarMessage(msg); + else if (timeout == -1) + core->messageManager()->displayStatusBarMessage(tr("Debugger: ") + msg); + else + core->messageManager()->displayStatusBarMessage(tr("Debugger: ") + msg, timeout); +#endif +} + +void DebuggerManager::notifyStartupFinished() +{ + setStatus(DebuggerProcessReady); + showStatusMessage(tr("Startup finished. Debugger ready."), -1); + if (m_startMode == attachExternal) { + // we continue the execution + engine()->continueInferior(); + } else { + engine()->runInferior(); + } +} + +void DebuggerManager::notifyInferiorStopped() +{ + resetLocation(); + setStatus(DebuggerInferiorStopped); + showStatusMessage(tr("Stopped."), 5000); +} + +void DebuggerManager::notifyInferiorUpdateFinished() +{ + setStatus(DebuggerInferiorReady); + showStatusMessage(tr("Stopped."), 5000); +} + +void DebuggerManager::notifyInferiorRunningRequested() +{ + setStatus(DebuggerInferiorRunningRequested); + showStatusMessage(tr("Running..."), 5000); +} + +void DebuggerManager::notifyInferiorRunning() +{ + setStatus(DebuggerInferiorRunning); + showStatusMessage(tr("Running..."), 5000); +} + +void DebuggerManager::notifyInferiorExited() +{ + setStatus(DebuggerProcessReady); + showStatusMessage(tr("Stopped."), 5000); +} + +void DebuggerManager::notifyInferiorPidChanged(int pid) +{ + //QMessageBox::warning(0, "PID", "PID: " + QString::number(pid)); + //qDebug() << "PID: " << pid; + emit inferiorPidChanged(pid); +} + +void DebuggerManager::showApplicationOutput(const QString &prefix, const QString &str) +{ + applicationOutputAvailable(prefix, str); +} + +void DebuggerManager::shutdown() +{ + //qDebug() << "DEBUGGER_MANAGER SHUTDOWN START"; + engine()->shutdown(); + // Delete these manually before deleting the manager + // (who will delete the models for most views) + delete m_breakWindow; + delete m_disassemblerWindow; + delete m_modulesWindow; + delete m_outputWindow; + delete m_registerWindow; + delete m_stackWindow; + delete m_threadsWindow; + delete m_tooltipWindow; + delete m_watchersWindow; + delete m_localsWindow; + // These widgets are all in some layout which will take care of deletion. + m_breakWindow = 0; + m_disassemblerWindow = 0; + m_modulesWindow = 0; + m_outputWindow = 0; + m_registerWindow = 0; + m_stackWindow = 0; + m_threadsWindow = 0; + m_tooltipWindow = 0; + m_watchersWindow = 0; + m_localsWindow = 0; + + delete m_breakHandler; + delete m_disassemblerHandler; + delete m_modulesHandler; + delete m_registerHandler; + delete m_stackHandler; + delete m_watchHandler; + m_breakHandler = 0; + m_disassemblerHandler = 0; + m_modulesHandler = 0; + m_registerHandler = 0; + m_stackHandler = 0; + m_watchHandler = 0; + //qDebug() << "DEBUGGER_MANAGER SHUTDOWN END"; +} + +void DebuggerManager::toggleBreakpoint() +{ + QString fileName; + int lineNumber = -1; + queryCurrentTextEditor(&fileName, &lineNumber, 0); + if (lineNumber == -1) + return; + toggleBreakpoint(fileName, lineNumber); +} + +void DebuggerManager::toggleBreakpoint(const QString &fileName, int lineNumber) +{ + int index = m_breakHandler->indexOf(fileName, lineNumber); + if (index == -1) + breakHandler()->setBreakpoint(fileName, lineNumber); + else + breakHandler()->removeBreakpoint(index); + engine()->attemptBreakpointSynchronization(); +} + +void DebuggerManager::setToolTipExpression(const QPoint &pos, const QString &exp) +{ + engine()->setToolTipExpression(pos, exp); +} + +void DebuggerManager::updateWatchModel() +{ + engine()->updateWatchModel(); +} + +void DebuggerManager::expandChildren(const QModelIndex &idx) +{ + watchHandler()->expandChildren(idx); +} + +void DebuggerManager::collapseChildren(const QModelIndex &idx) +{ + watchHandler()->collapseChildren(idx); +} + +void DebuggerManager::removeWatchExpression(const QString &iname) +{ + watchHandler()->removeWatchExpression(iname); +} + +QVariant DebuggerManager::sessionValue(const QString &name) +{ + QVariant value; + emit sessionValueRequested(name, &value); + return value; +} + +void DebuggerManager::querySessionValue(const QString &name, QVariant *value) +{ + emit sessionValueRequested(name, value); +} + +void DebuggerManager::setSessionValue(const QString &name, const QVariant &value) +{ + emit setSessionValueRequested(name, value); +} + +QVariant DebuggerManager::configValue(const QString &name) +{ + QVariant value; + emit configValueRequested(name, &value); + return value; +} + +void DebuggerManager::queryConfigValue(const QString &name, QVariant *value) +{ + emit configValueRequested(name, value); +} + +void DebuggerManager::setConfigValue(const QString &name, const QVariant &value) +{ + emit setConfigValueRequested(name, value); +} + +void DebuggerManager::startExternalApplication() +{ + if (!startNewDebugger(startExternal)) + emit debuggingFinished(); +} + +void DebuggerManager::attachExternalApplication() +{ + if (!startNewDebugger(attachExternal)) + emit debuggingFinished(); +} + +bool DebuggerManager::startNewDebugger(StartMode mode) +{ + m_startMode = mode; + // FIXME: Clean up + + if (startMode() == startExternal) { + StartExternalDialog dlg(mainWindow()); + dlg.setExecutableFile( + configValue(QLatin1String("LastExternalExecutableFile")).toString()); + dlg.setExecutableArguments( + configValue(QLatin1String("LastExternalExecutableArguments")).toString()); + if (dlg.exec() != QDialog::Accepted) + return false; + setConfigValue(QLatin1String("LastExternalExecutableFile"), + dlg.executableFile()); + setConfigValue(QLatin1String("LastExternalExecutableArguments"), + dlg.executableArguments()); + m_executable = dlg.executableFile(); + m_processArgs = dlg.executableArguments().split(' '); + m_workingDir = QString(); + m_attachedPID = -1; + } else if (startMode() == attachExternal) { + QString pid; + AttachExternalDialog dlg(mainWindow(), pid); + if (dlg.exec() != QDialog::Accepted) + return false; + m_executable = QString(); + m_processArgs = QStringList(); + m_workingDir = QString(); + m_attachedPID = dlg.attachPID(); + } else if (startMode() == startInternal) { + if (m_executable.isEmpty()) { + QString startDirectory = m_executable; + if (m_executable.isEmpty()) { + QString fileName; + emit currentTextEditorRequested(&fileName, 0, 0); + if (!fileName.isEmpty()) { + const QFileInfo editorFile(fileName); + startDirectory = editorFile.dir().absolutePath(); + } + } + StartExternalDialog dlg(mainWindow()); + dlg.setExecutableFile(startDirectory); + if (dlg.exec() != QDialog::Accepted) + return false; + m_executable = dlg.executableFile(); + m_processArgs = dlg.executableArguments().split(' '); + m_workingDir = QString(); + m_attachedPID = 0; + } else { + //m_executable = QDir::convertSeparators(m_executable); + //m_processArgs = sd.processArgs.join(QLatin1String(" ")); + m_attachedPID = 0; + } + } + + emit debugModeRequested(); + + if (m_executable.endsWith(".js")) + setDebuggerType(ScriptDebugger); + else + setDebuggerType(GdbDebugger); + + if (!engine()->startDebugger()) + return false; + + m_busy = false; + setStatus(DebuggerProcessStartingUp); + return true; +} + +void DebuggerManager::cleanupViews() +{ + resetLocation(); + breakHandler()->setAllPending(); + stackHandler()->removeAll(); + threadsHandler()->removeAll(); + disassemblerHandler()->removeAll(); + modulesHandler()->removeAll(); + watchHandler()->cleanup(); +} + +void DebuggerManager::exitDebugger() +{ + engine()->exitDebugger(); + cleanupViews(); + setStatus(DebuggerProcessNotReady); + setBusyCursor(false); + emit debuggingFinished(); +} + +void DebuggerManager::assignValueInDebugger(const QString &expr, const QString &value) +{ + engine()->assignValueInDebugger(expr, value); +} + +void DebuggerManager::activateFrame(int index) +{ + engine()->activateFrame(index); +} + +void DebuggerManager::selectThread(int index) +{ + engine()->selectThread(index); +} + +void DebuggerManager::loadAllSymbols() +{ + engine()->loadAllSymbols(); +} + +void DebuggerManager::loadSymbols(const QString &module) +{ + engine()->loadSymbols(module); +} + +void DebuggerManager::stepExec() +{ + resetLocation(); + engine()->stepExec(); +} + +void DebuggerManager::stepOutExec() +{ + resetLocation(); + engine()->stepOutExec(); +} + +void DebuggerManager::nextExec() +{ + resetLocation(); + engine()->nextExec(); +} + +void DebuggerManager::stepIExec() +{ + resetLocation(); + engine()->stepIExec(); +} + +void DebuggerManager::nextIExec() +{ + resetLocation(); + engine()->nextIExec(); +} + +void DebuggerManager::executeDebuggerCommand(const QString &command) +{ + engine()->executeDebuggerCommand(command); +} + +void DebuggerManager::sessionLoaded() +{ + exitDebugger(); + loadSessionData(); +} + +void DebuggerManager::aboutToSaveSession() +{ + saveSessionData(); +} + +void DebuggerManager::loadSessionData() +{ + m_breakHandler->loadSessionData(); + + QVariant value; + querySessionValue(QLatin1String("UseFastStart"), &value); + m_useFastStartAction->setChecked(value.toBool()); + querySessionValue(QLatin1String("UseCustomDumpers"), &value); + m_useCustomDumpersAction->setChecked(!value.isValid() || value.toBool()); + querySessionValue(QLatin1String("SkipKnownFrames"), &value); + m_skipKnownFramesAction->setChecked(value.toBool()); + engine()->loadSessionData(); +} + +void DebuggerManager::saveSessionData() +{ + m_breakHandler->saveSessionData(); + + setSessionValue(QLatin1String("UseFastStart"), + m_useFastStartAction->isChecked()); + setSessionValue(QLatin1String("UseCustomDumpers"), + m_useCustomDumpersAction->isChecked()); + setSessionValue(QLatin1String("SkipKnownFrames"), + m_skipKnownFramesAction->isChecked()); + engine()->saveSessionData(); +} + +void DebuggerManager::dumpLog() +{ + QString fileName = QFileDialog::getSaveFileName(mainWindow(), + tr("Save Debugger Log"), QDir::tempPath()); + if (fileName.isEmpty()) + return; + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) + return; + QTextStream ts(&file); + ts << m_outputWindow->inputContents(); + ts << "\n\n=======================================\n\n"; + ts << m_outputWindow->combinedContents(); +} + +#if 0 +// call after m_gdbProc exited. +void GdbEngine::procFinished() +{ + //qDebug() << "GDB PROCESS FINISHED"; + setStatus(DebuggerProcessNotReady); + showStatusMessage(tr("Done"), 5000); + q->m_breakHandler->procFinished(); + q->m_watchHandler->cleanup(); + m_stackHandler->m_stackFrames.clear(); + m_stackHandler->resetModel(); + m_threadsHandler->resetModel(); + if (q->m_modulesHandler) + q->m_modulesHandler->procFinished(); + q->resetLocation(); + setStatus(DebuggerProcessNotReady); + emit q->previousModeRequested(); + emit q->debuggingFinished(); + //exitDebugger(); + //showStatusMessage("Gdb killed"); + m_shortToFullName.clear(); + m_fullToShortName.clear(); + m_shared = 0; + q->m_busy = false; +} +#endif + +void DebuggerManager::addToWatchWindow() +{ + // requires a selection, but that's the only case we want... + QObject *ob = 0; + queryCurrentTextEditor(0, 0, &ob); + QPlainTextEdit *editor = qobject_cast<QPlainTextEdit*>(ob); + if (!editor) + return; + QTextCursor tc = editor->textCursor(); + watchExpression(tc.selectedText()); +} + +void DebuggerManager::watchExpression(const QString &expression) +{ + watchHandler()->watchExpression(expression); + //engine()->updateWatchModel(); +} + +void DebuggerManager::setBreakpoint(const QString &fileName, int lineNumber) +{ + breakHandler()->setBreakpoint(fileName, lineNumber); + engine()->attemptBreakpointSynchronization(); +} + +void DebuggerManager::breakByFunction(const QString &functionName) +{ + breakHandler()->breakByFunction(functionName); + engine()->attemptBreakpointSynchronization(); +} + +void DebuggerManager::breakByFunction() +{ + BreakByFunctionDialog dlg(m_mainWindow); + if (dlg.exec()) + breakByFunction(dlg.functionName()); +} + +void DebuggerManager::breakAtMain() +{ +#ifdef Q_OS_WIN + breakByFunction("qMain"); +#else + breakByFunction("main"); +#endif +} + +void DebuggerManager::setStatus(int status) +{ + //qDebug() << "STATUS CHANGE: from" << m_status << "to" << status; + + if (status == m_status) + return; + + m_status = status; + + const bool started = status == DebuggerInferiorRunning + || status == DebuggerInferiorRunningRequested + || status == DebuggerInferiorStopRequested + || status == DebuggerInferiorStopped + || status == DebuggerInferiorUpdating + || status == DebuggerInferiorUpdateFinishing + || status == DebuggerInferiorReady; + + const bool starting = status == DebuggerProcessStartingUp; + const bool running = status == DebuggerInferiorRunning; + const bool ready = status == DebuggerInferiorStopped + || status == DebuggerInferiorReady + || status == DebuggerProcessReady; + + m_startExternalAction->setEnabled(!started && !starting); + m_attachExternalAction->setEnabled(!started && !starting); + m_watchAction->setEnabled(ready); + m_breakAction->setEnabled(true); + + bool interruptIsExit = !running; + if (interruptIsExit) { + m_stopAction->setIcon(QIcon(":/gdbdebugger/images/debugger_stop_small.png")); + m_stopAction->setText(tr("Stop Debugger")); + } else { + m_stopAction->setIcon(QIcon(":/gdbdebugger/images/debugger_interrupt_small.png")); + m_stopAction->setText(tr("Interrupt")); + } + + m_stopAction->setEnabled(started); + m_resetAction->setEnabled(true); + + m_stepAction->setEnabled(ready); + m_stepOutAction->setEnabled(ready); + m_runToLineAction->setEnabled(ready); + m_runToFunctionAction->setEnabled(ready); + m_jumpToLineAction->setEnabled(ready); + m_nextAction->setEnabled(ready); + m_stepIAction->setEnabled(ready); + m_nextIAction->setEnabled(ready); + //showStatusMessage(QString("started: %1, running: %2").arg(started).arg(running)); + emit statusChanged(m_status); + const bool notbusy = ready || status == DebuggerProcessNotReady; + setBusyCursor(!notbusy); +} + +void DebuggerManager::setBusyCursor(bool busy) +{ + if (busy == m_busy) + return; + //qDebug() << "BUSY: " << busy; + m_busy = busy; + + QCursor cursor(busy ? Qt::BusyCursor : Qt::ArrowCursor); + m_breakWindow->setCursor(cursor); + m_disassemblerWindow->setCursor(cursor); + m_localsWindow->setCursor(cursor); + m_modulesWindow->setCursor(cursor); + m_outputWindow->setCursor(cursor); + m_registerWindow->setCursor(cursor); + m_stackWindow->setCursor(cursor); + m_threadsWindow->setCursor(cursor); + m_tooltipWindow->setCursor(cursor); + m_watchersWindow->setCursor(cursor); +} + +bool DebuggerManager::skipKnownFrames() const +{ + return m_skipKnownFramesAction->isChecked(); +} + +bool DebuggerManager::debugDumpers() const +{ + return m_debugDumpersAction->isChecked(); +} + +bool DebuggerManager::useCustomDumpers() const +{ + return m_useCustomDumpersAction->isChecked(); +} + +bool DebuggerManager::useFastStart() const +{ + return 0; // && m_useFastStartAction->isChecked(); +} + +void DebuggerManager::queryCurrentTextEditor(QString *fileName, int *lineNumber, + QObject **object) +{ + emit currentTextEditorRequested(fileName, lineNumber, object); +} + +void DebuggerManager::continueExec() +{ + engine()->continueInferior(); +} + +void DebuggerManager::interruptDebuggingRequest() +{ + //qDebug() << "INTERRUPTING AT" << status(); + bool interruptIsExit = (status() != DebuggerInferiorRunning); + if (interruptIsExit) + exitDebugger(); + else { + setStatus(DebuggerInferiorStopRequested); + engine()->interruptInferior(); + } +} + + +void DebuggerManager::runToLineExec() +{ + QString fileName; + int lineNumber = -1; + emit currentTextEditorRequested(&fileName, &lineNumber, 0); + if (!fileName.isEmpty()) + engine()->runToLineExec(fileName, lineNumber); +} + +void DebuggerManager::runToFunctionExec() +{ + QString fileName; + int lineNumber = -1; + QObject *object = 0; + emit currentTextEditorRequested(&fileName, &lineNumber, &object); + QPlainTextEdit *ed = qobject_cast<QPlainTextEdit*>(object); + if (!ed) + return; + QTextCursor cursor = ed->textCursor(); + QString functionName = cursor.selectedText(); + if (functionName.isEmpty()) { + const QTextBlock block = cursor.block(); + const QString line = block.text(); + foreach (const QString &str, line.trimmed().split('(')) { + QString a; + for (int i = str.size(); --i >= 0; ) { + if (!str.at(i).isLetterOrNumber()) + break; + a = str.at(i) + a; + } + if (!a.isEmpty()) { + functionName = a; + break; + } + } + } + //qDebug() << "RUN TO FUNCTION " << functionName; + if (!functionName.isEmpty()) + engine()->runToFunctionExec(functionName); +} + +void DebuggerManager::jumpToLineExec() +{ + QString fileName; + int lineNumber = -1; + emit currentTextEditorRequested(&fileName, &lineNumber, 0); + if (!fileName.isEmpty()) + engine()->jumpToLineExec(fileName, lineNumber); +} + +void DebuggerManager::resetLocation() +{ + //m_watchHandler->removeMouseMoveCatcher(editor->widget()); + emit resetLocationRequested(); +} + +void DebuggerManager::gotoLocation(const QString &fileName, int line, + bool setMarker) +{ + emit gotoLocationRequested(fileName, line, setMarker); + //m_watchHandler->installMouseMoveCatcher(editor->widget()); +} + + +////////////////////////////////////////////////////////////////////// +// +// Disassembler specific stuff +// +////////////////////////////////////////////////////////////////////// + +void DebuggerManager::reloadDisassembler() +{ + if (!m_disassemblerDock || !m_disassemblerDock->isVisible()) + return; + engine()->reloadDisassembler(); +} + +void DebuggerManager::disassemblerDockToggled(bool on) +{ + if (on) + reloadDisassembler(); +} + + +////////////////////////////////////////////////////////////////////// +// +// Modules specific stuff +// +////////////////////////////////////////////////////////////////////// + +void DebuggerManager::reloadModules() +{ + if (!m_modulesDock || !m_modulesDock->isVisible()) + return; + engine()->reloadModules(); +} + +void DebuggerManager::modulesDockToggled(bool on) +{ + if (on) + reloadModules(); +} + + +////////////////////////////////////////////////////////////////////// +// +// Output specific stuff +// +////////////////////////////////////////////////////////////////////// + +void DebuggerManager::showDebuggerOutput(const QString &prefix, const QString &msg) +{ + m_outputWindow->showOutput(prefix, msg); +} + +void DebuggerManager::showDebuggerInput(const QString &prefix, const QString &msg) +{ + m_outputWindow->showInput(prefix, msg); +} + + +////////////////////////////////////////////////////////////////////// +// +// Register specific stuff +// +////////////////////////////////////////////////////////////////////// + +void DebuggerManager::registerDockToggled(bool on) +{ + if (on) + reloadRegisters(); +} + +void DebuggerManager::reloadRegisters() +{ + if (!m_registerDock || !m_registerDock->isVisible()) + return; + engine()->reloadRegisters(); +} + + +#include "debuggermanager.moc" diff --git a/src/plugins/debugger/debuggermanager.h b/src/plugins/debugger/debuggermanager.h new file mode 100644 index 0000000000..beaf876d4b --- /dev/null +++ b/src/plugins/debugger/debuggermanager.h @@ -0,0 +1,451 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_DEBUGGERMANAGER_H +#define DEBUGGER_DEBUGGERMANAGER_H + +#include <QtCore/QByteArray> +#include <QtCore/QObject> +#include <QtCore/QPoint> +#include <QtCore/QStringList> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +class QAction; +class QAbstractItemModel; +class QDockWidget; +class QMainWindow; +class QModelIndex; +class QSplitter; +class QWidget; +QT_END_NAMESPACE + +namespace Debugger { +namespace Internal { + +class DebuggerOutputWindow; +class DebuggerPlugin; +class DebugMode; + +class BreakHandler; +class DisassemblerHandler; +class ModulesHandler; +class RegisterHandler; +class StackHandler; +class ThreadsHandler; +class WatchHandler; +class WatchData; +class BreakpointData; + + +// Note: the Debugger process itself is referred to as 'Debugger', +// whereas the debugged process is referred to as 'Inferior' or 'Debuggee'. + +// DebuggerProcessNotReady +// | +// DebuggerProcessStartingUp +// | +// DebuggerReady [R] [N] +// | <-------------------------------------. +// DebuggerInferiorRunningRequested | +// | | +// DebuggerInferiorRunning | +// | | +// DebuggerInferiorStopRequested | +// | | +// DebuggerInferiorStopped | +// | | +// DebuggerInferiorUpdating | +// | | +// DebuggerInferiorUpdateFinishing | +// | | +// DebuggerInferiorReady [C] [N] | +// | | +// `---------------------------------------' +// +// Allowed actions: +// [R] : Run +// [C] : Continue +// [N] : Step, Next + + + +enum DebuggerStatus +{ + DebuggerProcessNotReady, // Debugger not started + DebuggerProcessStartingUp, // Debugger starting up + DebuggerProcessReady, // Debugger started, Inferior not yet + // running or already finished + + DebuggerInferiorRunningRequested, // Debuggee requested to run + DebuggerInferiorRunning, // Debuggee running + DebuggerInferiorStopRequested, // Debuggee running, stop requested + DebuggerInferiorStopped, // Debuggee stopped + + DebuggerInferiorUpdating, // Debuggee updating data views + DebuggerInferiorUpdateFinishing, // Debuggee updating data views aborting + DebuggerInferiorReady, +}; + + +class IDebuggerEngine; +class GdbEngine; +class ScriptEngine; +class WinEngine; + +// The construct below is not nice but enforces a bit of order. The +// DebuggerManager interfaces a lots of thing: The DebuggerPlugin, +// the DebuggerEngines, the RunMode, the handlers and views. +// Instead of making the whole interface public, we split in into +// smaller parts and grant friend access only to the classes that +// need it. + + +// +// IDebuggerManagerAccessForEngines +// + +class IDebuggerManagerAccessForEngines +{ +public: + virtual ~IDebuggerManagerAccessForEngines() {} + +private: + // This is the part of the interface that's exclusively seen by the + // debugger engines. + friend class GdbEngine; + friend class ScriptEngine; + friend class WinEngine; + + // called from the engines after successful startup + virtual void notifyStartupFinished() = 0; + virtual void notifyInferiorStopped() = 0; + virtual void notifyInferiorUpdateFinished() = 0; + virtual void notifyInferiorRunningRequested() = 0; + virtual void notifyInferiorRunning() = 0; + virtual void notifyInferiorExited() = 0; + virtual void notifyInferiorPidChanged(int) = 0; + + virtual DisassemblerHandler *disassemblerHandler() = 0; + virtual ModulesHandler *modulesHandler() = 0; + virtual BreakHandler *breakHandler() = 0; + virtual RegisterHandler *registerHandler() = 0; + virtual StackHandler *stackHandler() = 0; + virtual ThreadsHandler *threadsHandler() = 0; + virtual WatchHandler *watchHandler() = 0; + + virtual void showApplicationOutput(const QString &prefix, const QString &data) = 0; + virtual QAction *useCustomDumpersAction() const = 0; + virtual QAction *debugDumpersAction() const = 0; + virtual bool skipKnownFrames() const = 0; + virtual bool debugDumpers() const = 0; + virtual bool useCustomDumpers() const = 0; + virtual bool useFastStart() const = 0; + + virtual void reloadDisassembler() = 0; + virtual void reloadModules() = 0; + virtual void reloadRegisters() = 0; +}; + + +// +// IDebuggerManagerAccessForDebugMode +// + +class IDebuggerManagerAccessForDebugMode +{ +public: + virtual ~IDebuggerManagerAccessForDebugMode() {} + +private: + friend class DebugMode; + + virtual QWidget *threadsWindow() = 0; + virtual QList<QDockWidget*> dockWidgets() const = 0; + virtual void createDockWidgets() = 0; +}; + + +// +// DebuggerManager +// + +class DebuggerManager : public QObject, + public IDebuggerManagerAccessForEngines, + public IDebuggerManagerAccessForDebugMode +{ + Q_OBJECT + +public: + DebuggerManager(); + ~DebuggerManager(); + + IDebuggerManagerAccessForEngines *engineInterface(); + IDebuggerManagerAccessForDebugMode *debugModeInterface(); + QMainWindow *mainWindow() const { return m_mainWindow; } + + enum StartMode { startInternal, startExternal, attachExternal }; + enum DebuggerType { GdbDebugger, ScriptDebugger, WinDebugger }; + +public slots: + bool startNewDebugger(StartMode mode); + void exitDebugger(); + + void setSimpleDockWidgetArrangement(); + void setLocked(bool locked); + void dockToggled(bool on); + + void setBusyCursor(bool on); + void queryCurrentTextEditor(QString *fileName, int *lineNumber, QObject **ed); + void querySessionValue(const QString &name, QVariant *value); + void setSessionValue(const QString &name, const QVariant &value); + QVariant configValue(const QString &name); + void queryConfigValue(const QString &name, QVariant *value); + void setConfigValue(const QString &name, const QVariant &value); + QVariant sessionValue(const QString &name); + + void gotoLocation(const QString &file, int line, bool setLocationMarker); + void resetLocation(); + + void interruptDebuggingRequest(); + void startExternalApplication(); + void attachExternalApplication(); + + void jumpToLineExec(); + void runToLineExec(); + void runToFunctionExec(); + void toggleBreakpoint(); + void breakByFunction(); + void breakByFunction(const QString &functionName); + void setBreakpoint(const QString &fileName, int lineNumber); + void watchExpression(const QString &expression); + void breakAtMain(); + void activateFrame(int index); + void selectThread(int index); + + void stepExec(); + void stepOutExec(); + void nextExec(); + void stepIExec(); + void nextIExec(); + void continueExec(); + + void addToWatchWindow(); + void updateWatchModel(); + void removeWatchExpression(const QString &iname); + void expandChildren(const QModelIndex &idx); + void collapseChildren(const QModelIndex &idx); + + void sessionLoaded(); + void aboutToSaveSession(); + + void assignValueInDebugger(const QString &expr, const QString &value); + void executeDebuggerCommand(const QString &command); + + void showStatusMessage(const QString &msg, int timeout); // -1 forever + +private slots: + void showDebuggerOutput(const QString &prefix, const QString &msg); + void showDebuggerInput(const QString &prefix, const QString &msg); + void showApplicationOutput(const QString &prefix, const QString &msg); + + void reloadDisassembler(); + void disassemblerDockToggled(bool on); + + void reloadModules(); + void modulesDockToggled(bool on); + void loadSymbols(const QString &moduleName); + void loadAllSymbols(); + + void reloadRegisters(); + void registerDockToggled(bool on); + void setStatus(int status); + +private: + // + // Implementation of IDebuggerManagerAccessForEngines + // + DisassemblerHandler *disassemblerHandler() { return m_disassemblerHandler; } + ModulesHandler *modulesHandler() { return m_modulesHandler; } + BreakHandler *breakHandler() { return m_breakHandler; } + RegisterHandler *registerHandler() { return m_registerHandler; } + StackHandler *stackHandler() { return m_stackHandler; } + ThreadsHandler *threadsHandler() { return m_threadsHandler; } + WatchHandler *watchHandler() { return m_watchHandler; } + QAction *useCustomDumpersAction() const { return m_useCustomDumpersAction; } + QAction *debugDumpersAction() const { return m_debugDumpersAction; } + bool skipKnownFrames() const; + bool debugDumpers() const; + bool useCustomDumpers() const; + bool useFastStart() const; + + void notifyStartupFinished(); + void notifyInferiorStopped(); + void notifyInferiorUpdateFinished(); + void notifyInferiorRunningRequested(); + void notifyInferiorRunning(); + void notifyInferiorExited(); + void notifyInferiorPidChanged(int); + + void cleanupViews(); + + // + // Implementation of IDebuggerManagerAccessForDebugMode + // + QWidget *threadsWindow() { return m_threadsWindow; } + QList<QDockWidget*> dockWidgets() const { return m_dockWidgets; } + void createDockWidgets(); + + // + // internal implementation + // + Q_SLOT void loadSessionData(); + Q_SLOT void saveSessionData(); + Q_SLOT void dumpLog(); + +public: + // stuff in this block should be made private by moving it to + // one of the interfaces + QAbstractItemModel *threadsModel(); + int status() const { return m_status; } + StartMode startMode() const { return m_startMode; } + +signals: + void debuggingFinished(); + void inferiorPidChanged(qint64 pid); + void statusChanged(int newstatus); + void debugModeRequested(); + void previousModeRequested(); + void statusMessageRequested(const QString &msg, int timeout); // -1 for 'forever' + void gotoLocationRequested(const QString &file, int line, bool setLocationMarker); + void resetLocationRequested(); + void currentTextEditorRequested(QString *fileName, int *lineNumber, QObject **ob); + void currentMainWindowRequested(QWidget **); + void sessionValueRequested(const QString &name, QVariant *value); + void setSessionValueRequested(const QString &name, const QVariant &value); + void configValueRequested(const QString &name, QVariant *value); + void setConfigValueRequested(const QString &name, const QVariant &value); + void applicationOutputAvailable(const QString &prefix, const QString &msg); + + +public: + // FIXME: make private + QString m_executable; + QStringList m_environment; + QString m_workingDir; + QString m_buildDir; + QStringList m_processArgs; + int m_attachedPID; + +private: + void init(); + void setDebuggerType(DebuggerType type); + QDockWidget *createDockForWidget(QWidget *widget); + + void shutdown(); + + void toggleBreakpoint(const QString &fileName, int lineNumber); + void setToolTipExpression(const QPoint &pos, const QString &exp0); + + StartMode m_startMode; + DebuggerType m_debuggerType; + + /// Views + QMainWindow *m_mainWindow; + QDockWidget *m_breakDock; + QDockWidget *m_disassemblerDock; + QDockWidget *m_modulesDock; + QDockWidget *m_outputDock; + QDockWidget *m_registerDock; + QDockWidget *m_stackDock; + QDockWidget *m_threadsDock; + QDockWidget *m_watchDock; + QList<QDockWidget*> m_dockWidgets; + + BreakHandler *m_breakHandler; + DisassemblerHandler *m_disassemblerHandler; + ModulesHandler *m_modulesHandler; + RegisterHandler *m_registerHandler; + StackHandler *m_stackHandler; + ThreadsHandler *m_threadsHandler; + WatchHandler *m_watchHandler; + + /// Actions + friend class DebuggerPlugin; + QAction *m_startExternalAction; + QAction *m_attachExternalAction; + QAction *m_continueAction; + QAction *m_stopAction; + QAction *m_resetAction; // FIXME: Should not be needed in a stable release + QAction *m_stepAction; + QAction *m_stepOutAction; + QAction *m_runToLineAction; + QAction *m_runToFunctionAction; + QAction *m_jumpToLineAction; + QAction *m_nextAction; + QAction *m_watchAction; + QAction *m_breakAction; + QAction *m_breakByFunctionAction; + QAction *m_breakAtMainAction; + QAction *m_sepAction; + QAction *m_stepIAction; + QAction *m_nextIAction; + QAction *m_skipKnownFramesAction; + + QAction *m_debugDumpersAction; + QAction *m_useCustomDumpersAction; + QAction *m_useFastStartAction; + QAction *m_dumpLogAction; + + QWidget *m_breakWindow; + QWidget *m_disassemblerWindow; + QWidget *m_localsWindow; + QWidget *m_registerWindow; + QWidget *m_modulesWindow; + QWidget *m_tooltipWindow; + QWidget *m_stackWindow; + QWidget *m_threadsWindow; + QWidget *m_watchersWindow; + DebuggerOutputWindow *m_outputWindow; + + int m_status; + bool m_busy; + + IDebuggerEngine *engine(); + IDebuggerEngine *m_engine; +}; + + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_DEBUGGERMANAGER_H diff --git a/src/plugins/debugger/debuggeroutputwindow.cpp b/src/plugins/debugger/debuggeroutputwindow.cpp new file mode 100644 index 0000000000..7f6bef85d7 --- /dev/null +++ b/src/plugins/debugger/debuggeroutputwindow.cpp @@ -0,0 +1,318 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "debuggeroutputwindow.h" + +#include <QtCore/QDebug> + +#include <QtGui/QAction> +#include <QtGui/QHBoxLayout> +#include <QtGui/QVBoxLayout> +#include <QtGui/QKeyEvent> +#include <QtGui/QLabel> +#include <QtGui/QLineEdit> +#include <QtGui/QMenu> +#include <QtGui/QSpacerItem> +#include <QtGui/QSplitter> +#include <QtGui/QTextBlock> + +#ifndef GDBDEBUGGERLEAN + +#include <aggregation/aggregate.h> +#include <find/basetextfind.h> + +using namespace Find; + +#endif // GDBDEBUGGERLEAN + +using Debugger::Internal::DebuggerOutputWindow; + +///////////////////////////////////////////////////////////////////// +// +// InputPane +// +///////////////////////////////////////////////////////////////////// + +class DebuggerPane : public QTextEdit +{ +public: + DebuggerPane(QWidget *parent) + : QTextEdit(parent) + { + m_clearContentsAction = new QAction(this); + m_clearContentsAction->setText("Clear contents"); + m_clearContentsAction->setEnabled(true); + m_clearContentsAction->setShortcut(Qt::ControlModifier + Qt::Key_R); + connect(m_clearContentsAction, SIGNAL(triggered(bool)), + parent, SLOT(clearContents())); + + m_saveContentsAction = new QAction(this); + m_saveContentsAction->setText("Save contents"); + m_saveContentsAction->setEnabled(true); + } + + void contextMenuEvent(QContextMenuEvent *ev) + { + QMenu *menu = createStandardContextMenu(); + menu->addAction(m_clearContentsAction); + //menu->addAction(m_saveContentsAction); + addContextActions(menu); + menu->exec(ev->globalPos()); + delete menu; + } + + virtual void addContextActions(QMenu *) {} + +public: + QAction *m_clearContentsAction; + QAction *m_saveContentsAction; +}; + +class InputPane : public DebuggerPane +{ + Q_OBJECT +public: + InputPane(QWidget *parent) : DebuggerPane(parent) + { + m_commandExecutionAction = new QAction(this); + m_commandExecutionAction->setText("Execute line"); + m_commandExecutionAction->setEnabled(true); + //m_commandExecutionAction->setShortcut + // (Qt::ControlModifier + Qt::Key_Return); + + connect(m_commandExecutionAction, SIGNAL(triggered(bool)), + this, SLOT(executeCommand())); + } + +signals: + void commandExecutionRequested(const QString &); + void clearContentsRequested(); + void statusMessageRequested(const QString &, int); + void commandSelected(int); + +private slots: + void executeCommand() + { + emit commandExecutionRequested(textCursor().block().text()); + } + +private: + void keyPressEvent(QKeyEvent *ev) + { + if (ev->modifiers() == Qt::ControlModifier && ev->key() == Qt::Key_Return) + emit commandExecutionRequested(textCursor().block().text()); + else if (ev->modifiers() == Qt::ControlModifier && ev->key() == Qt::Key_R) + emit clearContentsRequested(); + else + QTextEdit::keyPressEvent(ev); + } + + void mouseDoubleClickEvent(QMouseEvent *ev) + { + QString line = cursorForPosition(ev->pos()).block().text(); + int n = 0; + + // cut time string + if (line.size() > 18 && line.at(0) == '[') + line = line.mid(18); + //qDebug() << line; + + for (int i = 0; i != line.size(); ++i) { + QChar c = line.at(i); + if (!c.isDigit()) + break; + n = 10 * n + c.unicode() - '0'; + } + emit commandSelected(n); + } + + void addContextActions(QMenu *menu) + { + menu->addAction(m_commandExecutionAction); + } + + void focusInEvent(QFocusEvent *ev) + { + emit statusMessageRequested("Type Ctrl-<Return> to execute a line.", -1); + QTextEdit::focusInEvent(ev); + } + + void focusOutEvent(QFocusEvent *ev) + { + emit statusMessageRequested(QString(), -1); + QTextEdit::focusOutEvent(ev); + } + + QAction *m_commandExecutionAction; +}; + + +///////////////////////////////////////////////////////////////////// +// +// CombinedPane +// +///////////////////////////////////////////////////////////////////// + +class CombinedPane : public DebuggerPane +{ + Q_OBJECT +public: + CombinedPane(QWidget *parent) + : DebuggerPane(parent) + {} + +public slots: + void gotoResult(int i) + { + QString needle = QString::number(i) + '^'; + QString needle2 = "stdout:" + needle; + QTextCursor cursor(document()); + do { + const QString line = cursor.block().text(); + if (line.startsWith(needle) || line.startsWith(needle2)) { + setFocus(); + setTextCursor(cursor); + ensureCursorVisible(); + cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); + setTextCursor(cursor); + break; + } + } while (cursor.movePosition(QTextCursor::Down)); + } +}; + + +///////////////////////////////////////////////////////////////////// +// +// DebuggerOutputWindow +// +///////////////////////////////////////////////////////////////////// + +DebuggerOutputWindow::DebuggerOutputWindow(QWidget *parent) + : QWidget(parent) +{ + setWindowTitle(tr("Gdb")); + + QSplitter *m_splitter = new QSplitter(Qt::Horizontal, this); + // mixed input/output + m_combinedText = new CombinedPane(this); + m_combinedText->setReadOnly(true); + m_combinedText->setReadOnly(false); + m_combinedText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + // input only + m_inputText = new InputPane(this); + m_inputText->setReadOnly(false); + m_inputText->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + m_splitter->addWidget(m_inputText); + m_splitter->addWidget(m_combinedText); + + QGridLayout *layout = new QGridLayout(this); + layout->setMargin(0); + layout->addWidget(m_splitter); + setLayout(layout); + +#ifndef GDBDEBUGGERLEAN + Aggregation::Aggregate *aggregate = new Aggregation::Aggregate; + aggregate->add(m_combinedText); + aggregate->add(new BaseTextFind(m_combinedText)); + + aggregate = new Aggregation::Aggregate; + aggregate->add(m_inputText); + aggregate->add(new BaseTextFind(m_inputText)); +#endif + + connect(m_inputText, SIGNAL(commandExecutionRequested(QString)), + this, SIGNAL(commandExecutionRequested(QString))); + connect(m_inputText, SIGNAL(statusMessageRequested(QString,int)), + this, SIGNAL(statusMessageRequested(QString,int))); + connect(m_inputText, SIGNAL(commandSelected(int)), + m_combinedText, SLOT(gotoResult(int))); +}; + +void DebuggerOutputWindow::onReturnPressed() +{ + emit commandExecutionRequested(m_commandEdit->text()); +} + +void DebuggerOutputWindow::showOutput(const QString &prefix, const QString &output) +{ + if (output.isEmpty()) + return; + foreach (QString line, output.split("\n")) { + // FIXME: QTextEdit asserts on really long lines... + const int n = 3000; + if (line.size() > n) + line = line.left(n) + " [...] <cut off>"; + m_combinedText->append(prefix + line); + } + QTextCursor cursor = m_combinedText->textCursor(); + cursor.movePosition(QTextCursor::End); + m_combinedText->setTextCursor(cursor); + m_combinedText->ensureCursorVisible(); +} + +void DebuggerOutputWindow::showInput(const QString &prefix, const QString &input) +{ + m_inputText->append(input); + QTextCursor cursor = m_inputText->textCursor(); + cursor.movePosition(QTextCursor::End); + m_inputText->setTextCursor(cursor); + m_inputText->ensureCursorVisible(); + showOutput("input:", input); +} + +void DebuggerOutputWindow::clearContents() +{ + m_combinedText->clear(); + m_inputText->clear(); +} + +void DebuggerOutputWindow::setCursor(const QCursor &cursor) +{ + m_combinedText->setCursor(cursor); + m_inputText->setCursor(cursor); + QWidget::setCursor(cursor); +} + +QString DebuggerOutputWindow::combinedContents() const +{ + return m_combinedText->toPlainText(); +} + +QString DebuggerOutputWindow::inputContents() const +{ + return m_inputText->toPlainText(); +} + +#include "debuggeroutputwindow.moc" diff --git a/src/plugins/debugger/debuggeroutputwindow.h b/src/plugins/debugger/debuggeroutputwindow.h new file mode 100644 index 0000000000..f844c0fff9 --- /dev/null +++ b/src/plugins/debugger/debuggeroutputwindow.h @@ -0,0 +1,87 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_OUTPUTWINDOW_H +#define DEBUGGER_OUTPUTWINDOW_H + +#include <QtGui/QLineEdit> +#include <QtGui/QSplitter> +#include <QtGui/QTextEdit> +#include <QtGui/QWidget> + +namespace Debugger { +namespace Internal { + +class DebuggerOutputWindow : public QWidget +{ + Q_OBJECT + +public: + DebuggerOutputWindow(QWidget *parent = 0); + + QWidget *outputWidget(QWidget *) { return this; } + QList<QWidget*> toolBarWidgets(void) const { return QList<QWidget *>(); } + + QString name() const { return windowTitle(); } + void visibilityChanged(bool /*visible*/) {} + + void bringPaneToForeground() { emit showPage(); } + void setCursor(const QCursor &cursor); + + QString combinedContents() const; + QString inputContents() const; + +public slots: + void clearContents(); + void showOutput(const QString &prefix, const QString &output); + void showInput(const QString &prefix, const QString &input); + +signals: + void showPage(); + void statusMessageRequested(const QString &msg, int); + void commandExecutionRequested(const QString &cmd); + +private slots: + void onReturnPressed(); + +private: + QTextEdit *m_combinedText; // combined input/output + QTextEdit *m_inputText; // scriptable input alone + QLineEdit *m_commandEdit; +}; + + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_OUTPUTWINDOW_H + diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp new file mode 100644 index 0000000000..8fe6cb0250 --- /dev/null +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -0,0 +1,610 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "debuggerplugin.h" + +#include "assert.h" +#include "debuggerconstants.h" +#include "debuggermanager.h" +#include "debuggerrunner.h" +#include "gdboptionpage.h" +#include "gdbengine.h" +#include "mode.h" + +#include <coreplugin/actionmanager/actionmanagerinterface.h> +#include <coreplugin/coreconstants.h> +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/icore.h> +#include <coreplugin/messagemanager.h> +#include <coreplugin/modemanager.h> +#include <coreplugin/uniqueidmanager.h> +#include <cplusplus/ExpressionUnderCursor.h> +#include <cppeditor/cppeditorconstants.h> +#include <projectexplorer/projectexplorerconstants.h> +#include <projectexplorer/session.h> +#include <texteditor/basetextmark.h> +#include <texteditor/itexteditor.h> +#include <texteditor/texteditorconstants.h> +#include <texteditor/basetexteditor.h> + +#include <QtCore/QDebug> +#include <QtCore/qplugin.h> +#include <QtCore/QObject> +#include <QtCore/QPoint> +#include <QtCore/QSettings> +#include <QtGui/QPlainTextEdit> +#include <QtGui/QTextBlock> +#include <QtGui/QTextCursor> + + +using namespace Debugger::Internal; +using namespace Debugger::Constants; +using namespace TextEditor; +using namespace Core; +using namespace ProjectExplorer; +using namespace CPlusPlus; + + +namespace Debugger { +namespace Constants { + +const char * const STARTEXTERNAL = "Debugger.StartExternal"; +const char * const ATTACHEXTERNAL = "Debugger.AttachExternal"; + +const char * const RUN_TO_LINE = "Debugger.RunToLine"; +const char * const RUN_TO_FUNCTION = "Debugger.RunToFunction"; +const char * const JUMP_TO_LINE = "Debugger.JumpToLine"; +const char * const TOGGLE_BREAK = "Debugger.ToggleBreak"; +const char * const BREAK_BY_FUNCTION = "Debugger.BreakByFunction"; +const char * const BREAK_AT_MAIN = "Debugger.BreakAtMain"; +const char * const DEBUG_DUMPERS = "Debugger.DebugDumpers"; +const char * const ADD_TO_WATCH = "Debugger.AddToWatch"; +const char * const USE_CUSTOM_DUMPERS = "Debugger.UseCustomDumpers"; +const char * const USE_FAST_START = "Debugger.UseFastStart"; +const char * const SKIP_KNOWN_FRAMES = "Debugger.SkipKnownFrames"; +const char * const DUMP_LOG = "Debugger.DumpLog"; + +#ifdef Q_OS_MAC +const char * const INTERRUPT_KEY = "Shift+F5"; +const char * const RESET_KEY = "Ctrl+Shift+F5"; +const char * const STEP_KEY = "F7"; +const char * const STEPOUT_KEY = "Shift+F7"; +const char * const NEXT_KEY = "F6"; +const char * const STEPI_KEY = "Shift+F9"; +const char * const NEXTI_KEY = "Shift+F6"; +const char * const RUN_TO_LINE_KEY = "Shift+F8"; +const char * const RUN_TO_FUNCTION_KEY = "Ctrl+F6"; +const char * const JUMP_TO_LINE_KEY = "Alt+D,Alt+L"; +const char * const TOGGLE_BREAK_KEY = "F8"; +const char * const BREAK_BY_FUNCTION_KEY = "Alt+D,Alt+F"; +const char * const BREAK_AT_MAIN_KEY = "Alt+D,Alt+M"; +const char * const ADD_TO_WATCH_KEY = "Alt+D,Alt+W"; +#else +const char * const INTERRUPT_KEY = "Shift+F5"; +const char * const RESET_KEY = "Ctrl+Shift+F5"; +const char * const STEP_KEY = "F11"; +const char * const STEPOUT_KEY = "Shift+F11"; +const char * const NEXT_KEY = "F10"; +const char * const STEPI_KEY = ""; +const char * const NEXTI_KEY = ""; +const char * const RUN_TO_LINE_KEY = ""; +const char * const RUN_TO_FUNCTION_KEY = ""; +const char * const JUMP_TO_LINE_KEY = ""; +const char * const TOGGLE_BREAK_KEY = "F9"; +const char * const BREAK_BY_FUNCTION_KEY = ""; +const char * const BREAK_AT_MAIN_KEY = ""; +const char * const ADD_TO_WATCH_KEY = "Ctrl+Alt+Q"; +#endif + +} // namespace Constants +} // namespace Debugger + + +/////////////////////////////////////////////////////////////////////// +// +// LocationMark +// +/////////////////////////////////////////////////////////////////////// + +class Debugger::Internal::LocationMark + : public TextEditor::BaseTextMark +{ + Q_OBJECT + +public: + LocationMark(const QString &fileName, int linenumber) + : BaseTextMark(fileName, linenumber) + { + } + ~LocationMark(); + + QIcon icon() const; + void updateLineNumber(int /*lineNumber*/) {} + void updateBlock(const QTextBlock & /*block*/) {} + void removedFromEditor() { deleteLater(); } +private: +}; + +LocationMark::~LocationMark() +{ + //qDebug() << "LOCATIONMARK DESTRUCTOR" << m_editor; +} + +QIcon LocationMark::icon() const +{ + static const QIcon icon(":/gdbdebugger/images/location.svg"); + return icon; +} + +/////////////////////////////////////////////////////////////////////// +// +// DebuggerPlugin +// +/////////////////////////////////////////////////////////////////////// + +DebuggerPlugin::DebuggerPlugin() +{ + m_pm = 0; + m_generalOptionPage = 0; + m_typeMacroPage = 0; + m_locationMark = 0; + m_manager = 0; +} + +DebuggerPlugin::~DebuggerPlugin() +{} + +void DebuggerPlugin::shutdown() +{ + if (m_debugMode) + m_debugMode->shutdown(); // saves state including manager information + QWB_ASSERT(m_manager, /**/); + if (m_manager) + m_manager->shutdown(); + + //qDebug() << "DebuggerPlugin::~DebuggerPlugin"; + removeObject(m_debugMode); + removeObject(m_generalOptionPage); + removeObject(m_typeMacroPage); + + // FIXME: when using the line below, BreakWindow etc gets deleted twice. + // so better leak for now... + delete m_debugMode; + m_debugMode = 0; + + delete m_generalOptionPage; + m_generalOptionPage = 0; + + delete m_typeMacroPage; + m_typeMacroPage = 0; + + delete m_locationMark; + m_locationMark = 0; + + delete m_manager; + m_manager = 0; +} + +bool DebuggerPlugin::initialize(const QStringList &arguments, QString *error_message) +{ + Q_UNUSED(arguments); + Q_UNUSED(error_message); + + m_manager = new DebuggerManager; + + m_pm = ExtensionSystem::PluginManager::instance(); + + ICore *core = m_pm->getObject<Core::ICore>(); + QWB_ASSERT(core, return false); + + Core::ActionManagerInterface *actionManager = core->actionManager(); + QWB_ASSERT(actionManager, return false); + + Core::UniqueIDManager *uidm = core->uniqueIDManager(); + QWB_ASSERT(uidm, return false); + + QList<int> globalcontext; + globalcontext << Core::Constants::C_GLOBAL_ID; + + QList<int> cppcontext; + cppcontext << uidm->uniqueIdentifier(ProjectExplorer::Constants::LANG_CXX); + + QList<int> debuggercontext; + debuggercontext << uidm->uniqueIdentifier(C_GDBDEBUGGER); + + QList<int> cppeditorcontext; + cppeditorcontext << uidm->uniqueIdentifier(CppEditor::Constants::C_CPPEDITOR); + + QList<int> texteditorcontext; + texteditorcontext << uidm->uniqueIdentifier(TextEditor::Constants::C_TEXTEDITOR); + + m_gdbRunningContext = uidm->uniqueIdentifier(Constants::GDBRUNNING); + + //Core::IActionContainer *mcppcontext = + // actionManager->actionContainer(CppEditor::Constants::M_CONTEXT); + + Core::IActionContainer *mdebug = + actionManager->actionContainer(ProjectExplorer::Constants::M_DEBUG); + + Core::ICommand *cmd = 0; + cmd = actionManager->registerAction(m_manager->m_startExternalAction, + Constants::STARTEXTERNAL, globalcontext); + mdebug->addAction(cmd, Core::Constants::G_DEFAULT_ONE); + +#ifndef Q_OS_WIN + cmd = actionManager->registerAction(m_manager->m_attachExternalAction, + Constants::ATTACHEXTERNAL, globalcontext); + mdebug->addAction(cmd, Core::Constants::G_DEFAULT_ONE); +#endif + + cmd = actionManager->registerAction(m_manager->m_continueAction, + ProjectExplorer::Constants::DEBUG, QList<int>()<< m_gdbRunningContext); + + cmd = actionManager->registerAction(m_manager->m_stopAction, + Constants::INTERRUPT, globalcontext); + cmd->setAttribute(Core::ICommand::CA_UpdateText); + cmd->setAttribute(Core::ICommand::CA_UpdateIcon); + cmd->setDefaultKeySequence(QKeySequence(Constants::INTERRUPT_KEY)); + cmd->setDefaultText(tr("Stop Debugger/Interrupt Debugger")); + mdebug->addAction(cmd, Core::Constants::G_DEFAULT_ONE); + + cmd = actionManager->registerAction(m_manager->m_resetAction, + Constants::RESET, globalcontext); + cmd->setAttribute(Core::ICommand::CA_UpdateText); + cmd->setDefaultKeySequence(QKeySequence(Constants::RESET_KEY)); + cmd->setDefaultText(tr("Reset Debugger")); + //disabled mdebug->addAction(cmd, Core::Constants::G_DEFAULT_ONE); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + cmd = actionManager->registerAction(sep, + QLatin1String("GdbDebugger.Sep1"), globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_nextAction, + Constants::NEXT, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::NEXT_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_stepAction, + Constants::STEP, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::STEP_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_stepOutAction, + Constants::STEPOUT, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::STEPOUT_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_nextIAction, + Constants::NEXTI, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::NEXTI_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_stepIAction, + Constants::STEPI, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::STEPI_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_runToLineAction, + Constants::RUN_TO_LINE, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::RUN_TO_LINE_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_runToFunctionAction, + Constants::RUN_TO_FUNCTION, debuggercontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::RUN_TO_FUNCTION_KEY)); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_jumpToLineAction, + Constants::JUMP_TO_LINE, debuggercontext); + mdebug->addAction(cmd); + + sep = new QAction(this); + sep->setSeparator(true); + cmd = actionManager->registerAction(sep, + QLatin1String("GdbDebugger.Sep3"), globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_breakAction, + Constants::TOGGLE_BREAK, cppeditorcontext); + cmd->setDefaultKeySequence(QKeySequence(Constants::TOGGLE_BREAK_KEY)); + mdebug->addAction(cmd); + //mcppcontext->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_breakByFunctionAction, + Constants::BREAK_BY_FUNCTION, globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_breakAtMainAction, + Constants::BREAK_AT_MAIN, globalcontext); + mdebug->addAction(cmd); + + sep = new QAction(this); + sep->setSeparator(true); + cmd = actionManager->registerAction(sep, + QLatin1String("GdbDebugger.Sep2"), globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_skipKnownFramesAction, + Constants::SKIP_KNOWN_FRAMES, globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_useCustomDumpersAction, + Constants::USE_CUSTOM_DUMPERS, globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_useFastStartAction, + Constants::USE_FAST_START, globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_dumpLogAction, + Constants::DUMP_LOG, globalcontext); + //cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+D,Ctrl+L"))); + cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+F11"))); + mdebug->addAction(cmd); + +#ifdef QT_DEBUG + cmd = actionManager->registerAction(m_manager->m_debugDumpersAction, + Constants::DEBUG_DUMPERS, debuggercontext); + mdebug->addAction(cmd); +#endif + + sep = new QAction(this); + sep->setSeparator(true); + cmd = actionManager->registerAction(sep, + QLatin1String("GdbDebugger.Sep4"), globalcontext); + mdebug->addAction(cmd); + + cmd = actionManager->registerAction(m_manager->m_watchAction, + Constants::ADD_TO_WATCH, cppeditorcontext); + //cmd->setDefaultKeySequence(QKeySequence(tr("ALT+D,ALT+W"))); + mdebug->addAction(cmd); + + m_generalOptionPage = 0; + m_typeMacroPage = 0; + + // FIXME: + m_generalOptionPage = new GdbOptionPage(&theGdbSettings()); + addObject(m_generalOptionPage); + m_typeMacroPage = new TypeMacroPage(&theGdbSettings()); + addObject(m_typeMacroPage); + + m_locationMark = 0; + + m_debugMode = new DebugMode(m_manager, this); + //addAutoReleasedObject(m_debugMode); + addObject(m_debugMode); + + addAutoReleasedObject(new DebuggerRunner(m_manager)); + + // ProjectExplorer + connect(projectExplorer()->session(), SIGNAL(sessionLoaded()), + m_manager, SLOT(sessionLoaded())); + connect(projectExplorer()->session(), SIGNAL(aboutToSaveSession()), + m_manager, SLOT(aboutToSaveSession())); + + // EditorManager + QObject *editorManager = core->editorManager(); + connect(editorManager, SIGNAL(editorAboutToClose(Core::IEditor*)), + this, SLOT(editorAboutToClose(Core::IEditor*))); + connect(editorManager, SIGNAL(editorOpened(Core::IEditor*)), + this, SLOT(editorOpened(Core::IEditor*))); + + // Application interaction + connect(m_manager, SIGNAL(currentTextEditorRequested(QString*,int*,QObject**)), + this, SLOT(queryCurrentTextEditor(QString*,int*,QObject**))); + + connect(m_manager, SIGNAL(setSessionValueRequested(QString,QVariant)), + this, SLOT(setSessionValue(QString,QVariant))); + connect(m_manager, SIGNAL(sessionValueRequested(QString,QVariant*)), + this, SLOT(querySessionValue(QString,QVariant*))); + connect(m_manager, SIGNAL(setConfigValueRequested(QString,QVariant)), + this, SLOT(setConfigValue(QString,QVariant))); + connect(m_manager, SIGNAL(configValueRequested(QString,QVariant*)), + this, SLOT(queryConfigValue(QString,QVariant*))); + + connect(m_manager, SIGNAL(resetLocationRequested()), + this, SLOT(resetLocation())); + connect(m_manager, SIGNAL(gotoLocationRequested(QString,int,bool)), + this, SLOT(gotoLocation(QString,int,bool))); + connect(m_manager, SIGNAL(statusChanged(int)), + this, SLOT(changeStatus(int))); + connect(m_manager, SIGNAL(previousModeRequested()), + this, SLOT(activatePreviousMode())); + connect(m_manager, SIGNAL(debugModeRequested()), + this, SLOT(activateDebugMode())); + + return true; +} + +void DebuggerPlugin::extensionsInitialized() +{ +} + +ProjectExplorer::ProjectExplorerPlugin *DebuggerPlugin::projectExplorer() const +{ + return m_pm->getObject<ProjectExplorer::ProjectExplorerPlugin>(); +} + +/*! Activates the previous mode when the current mode is the debug mode. */ +void DebuggerPlugin::activatePreviousMode() +{ + ICore *core = m_pm->getObject<Core::ICore>(); + Core::ModeManager *const modeManager = core->modeManager(); + + if (modeManager->currentMode() == modeManager->mode(Constants::MODE_DEBUG) + && !m_previousMode.isEmpty()) { + modeManager->activateMode(m_previousMode); + m_previousMode.clear(); + } +} + +void DebuggerPlugin::activateDebugMode() +{ + ICore *core = m_pm->getObject<Core::ICore>(); + Core::ModeManager *modeManager = core->modeManager(); + m_previousMode = QLatin1String(modeManager->currentMode()->uniqueModeName()); + modeManager->activateMode(QLatin1String(MODE_DEBUG)); +} + +void DebuggerPlugin::queryCurrentTextEditor(QString *fileName, int *lineNumber, QObject **object) +{ + ICore *core = m_pm->getObject<Core::ICore>(); + if (!core || !core->editorManager()) + return; + Core::IEditor *editor = core->editorManager()->currentEditor(); + ITextEditor *textEditor = qobject_cast<ITextEditor*>(editor); + if (!textEditor) + return; + if (fileName) + *fileName = textEditor->file()->fileName(); + if (lineNumber) + *lineNumber = textEditor->currentLine(); + if (object) + *object = textEditor->widget(); +} + +void DebuggerPlugin::editorOpened(Core::IEditor *editor) +{ + if (ITextEditor *textEditor = qobject_cast<ITextEditor *>(editor)) { + connect(textEditor, SIGNAL(markRequested(TextEditor::ITextEditor*,int)), + this, SLOT(requestMark(TextEditor::ITextEditor*,int))); + connect(editor, SIGNAL(tooltipRequested(TextEditor::ITextEditor*,QPoint,int)), + this, SLOT(showToolTip(TextEditor::ITextEditor*,QPoint,int))); + } +} + +void DebuggerPlugin::editorAboutToClose(Core::IEditor *editor) +{ + if (ITextEditor *textEditor = qobject_cast<ITextEditor *>(editor)) { + disconnect(textEditor, SIGNAL(markRequested(TextEditor::ITextEditor*,int)), + this, SLOT(requestMark(TextEditor::ITextEditor*,int))); + disconnect(editor, SIGNAL(tooltipRequested(TextEditor::ITextEditor*,QPoint,int)), + this, SLOT(showToolTip(TextEditor::ITextEditor*,QPoint,int))); + } +} + +void DebuggerPlugin::requestMark(TextEditor::ITextEditor *editor, int lineNumber) +{ + m_manager->toggleBreakpoint(editor->file()->fileName(), lineNumber); +} + +void DebuggerPlugin::showToolTip(TextEditor::ITextEditor *editor, + const QPoint &point, int pos) +{ + QPlainTextEdit *plaintext = qobject_cast<QPlainTextEdit*>(editor->widget()); + if (!plaintext) + return; + + QString expr = plaintext->textCursor().selectedText(); + if (expr.isEmpty()) { + QTextCursor tc(plaintext->document()); + tc.setPosition(pos); + + const QChar ch = editor->characterAt(pos); + if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) + tc.movePosition(QTextCursor::EndOfWord); + + // Fetch the expression's code. + ExpressionUnderCursor expressionUnderCursor; + expr = expressionUnderCursor(tc); + } + //qDebug() << " TOOLTIP EXPR " << expr; + m_manager->setToolTipExpression(point, expr); +} + +void DebuggerPlugin::setSessionValue(const QString &name, const QVariant &value) +{ + //qDebug() << "SET SESSION VALUE" << name << value; + ProjectExplorerPlugin *pe = projectExplorer(); + if (pe->session()) + pe->session()->setValue(name, value); + else + qDebug() << "FIXME: Session does not exist yet"; +} + +void DebuggerPlugin::querySessionValue(const QString &name, QVariant *value) +{ + ProjectExplorerPlugin *pe = projectExplorer(); + *value = pe->session()->value(name); + //qDebug() << "GET SESSION VALUE: " << name << value; +} + + +void DebuggerPlugin::setConfigValue(const QString &name, const QVariant &value) +{ + QWB_ASSERT(m_debugMode, return); + m_debugMode->settings()->setValue(name, value); +} + +void DebuggerPlugin::queryConfigValue(const QString &name, QVariant *value) +{ + QWB_ASSERT(m_debugMode, return); + *value = m_debugMode->settings()->value(name); +} + +void DebuggerPlugin::resetLocation() +{ + //qDebug() << "RESET_LOCATION: current:" << currentTextEditor(); + //qDebug() << "RESET_LOCATION: locations:" << m_locationMark; + //qDebug() << "RESET_LOCATION: stored:" << m_locationMark->editor(); + delete m_locationMark; + m_locationMark = 0; +} + +void DebuggerPlugin::gotoLocation(const QString &fileName, int lineNumber, + bool setMarker) +{ + TextEditor::BaseTextEditor::openEditorAt(fileName, lineNumber); + if (setMarker) { + resetLocation(); + m_locationMark = new LocationMark(fileName, lineNumber); + } +} + +void DebuggerPlugin::changeStatus(int status) +{ + bool startIsContinue = (status == DebuggerInferiorStopped); + ICore *core = m_pm->getObject<Core::ICore>(); + if (startIsContinue) { + core->addAdditionalContext(m_gdbRunningContext); + core->updateContext(); + } else { + core->removeAdditionalContext(m_gdbRunningContext); + core->updateContext(); + } +} + +#include "debuggerplugin.moc" + +Q_EXPORT_PLUGIN(DebuggerPlugin) diff --git a/src/plugins/debugger/debuggerplugin.h b/src/plugins/debugger/debuggerplugin.h new file mode 100644 index 0000000000..0d7cf83bd3 --- /dev/null +++ b/src/plugins/debugger/debuggerplugin.h @@ -0,0 +1,111 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGERPLUGIN_H +#define DEBUGGERPLUGIN_H + +#include <projectexplorer/projectexplorer.h> +#include <extensionsystem/iplugin.h> + +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE +class QAction; +class QCursor; +class QAbstractItemView; +QT_END_NAMESPACE + +namespace Core { class IEditor; } +namespace TextEditor { class ITextEditor; } + +namespace Debugger { +namespace Internal { + +class DebuggerManager; +class DebugMode; +class GdbOptionPage; +class TypeMacroPage; +class LocationMark; + +class DebuggerPlugin : public ExtensionSystem::IPlugin +{ + Q_OBJECT + +public: + DebuggerPlugin(); + ~DebuggerPlugin(); + +private: + bool initialize(const QStringList &arguments, QString *error_message); + void shutdown(); + void extensionsInitialized(); + +private slots: + void activatePreviousMode(); + void activateDebugMode(); + void queryCurrentTextEditor(QString *fileName, int *line, QObject **object); + void editorOpened(Core::IEditor *); + void editorAboutToClose(Core::IEditor *); + void changeStatus(int status); + void requestMark(TextEditor::ITextEditor *editor, int lineNumber); + void showToolTip(TextEditor::ITextEditor *editor, const QPoint &pnt, int pos); + + void querySessionValue(const QString &name, QVariant *value); + void setSessionValue(const QString &name, const QVariant &value); + void queryConfigValue(const QString &name, QVariant *value); + void setConfigValue(const QString &name, const QVariant &value); + + void resetLocation(); + void gotoLocation(const QString &fileName, int line, bool setMarker); + +private: + friend class DebuggerManager; + friend class DebugMode; // FIXME: Just a hack now so that it can access the views + + ProjectExplorer::ProjectExplorerPlugin *projectExplorer() const; + + DebuggerManager *m_manager; + DebugMode *m_debugMode; + + ExtensionSystem::PluginManager *m_pm; + GdbOptionPage *m_generalOptionPage; + TypeMacroPage *m_typeMacroPage; + + QString m_previousMode; + LocationMark *m_locationMark; + int m_gdbRunningContext; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGERPLUGIN_H diff --git a/src/plugins/debugger/debuggerrunner.cpp b/src/plugins/debugger/debuggerrunner.cpp new file mode 100644 index 0000000000..0254bebb9b --- /dev/null +++ b/src/plugins/debugger/debuggerrunner.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "debuggerrunner.h" + +#include "assert.h" +#include "debuggermanager.h" + +#include <projectexplorer/applicationrunconfiguration.h> +#include <projectexplorer/environment.h> +#include <projectexplorer/project.h> +#include <projectexplorer/projectexplorerconstants.h> + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> + +using namespace Debugger::Internal; + +using ProjectExplorer::RunConfiguration; +using ProjectExplorer::RunControl; +using ProjectExplorer::ApplicationRunConfiguration; + + +//////////////////////////////////////////////////////////////////////// +// +// DebuggerRunner +// +//////////////////////////////////////////////////////////////////////// + +DebuggerRunner::DebuggerRunner(DebuggerManager *manager) + : m_manager(manager) +{} + +bool DebuggerRunner::canRun(RunConfigurationPtr runConfiguration, const QString &mode) +{ + return mode == ProjectExplorer::Constants::DEBUGMODE + && !qSharedPointerCast<ApplicationRunConfiguration>(runConfiguration).isNull(); +} + +QString DebuggerRunner::displayName() const +{ + return QObject::tr("Debug"); +} + +RunControl* DebuggerRunner::run(RunConfigurationPtr runConfiguration, const QString &mode) +{ + Q_UNUSED(mode); + Q_ASSERT(mode == ProjectExplorer::Constants::DEBUGMODE); + ApplicationRunConfigurationPtr rc = + qSharedPointerCast<ApplicationRunConfiguration>(runConfiguration); + Q_ASSERT(rc); + //qDebug() << "***** Debugging" << rc->name() << rc->executable(); + return new DebuggerRunControl(m_manager, rc); +} + +QWidget *DebuggerRunner::configurationWidget(RunConfigurationPtr runConfiguration) +{ + // NBS TODO: Add GDB-specific configuration widget + Q_UNUSED(runConfiguration); + return 0; +} + + + +//////////////////////////////////////////////////////////////////////// +// +// DebuggerRunControl +// +//////////////////////////////////////////////////////////////////////// + + +DebuggerRunControl::DebuggerRunControl(DebuggerManager *manager, + QSharedPointer<ApplicationRunConfiguration> runConfiguration) + : RunControl(runConfiguration), m_manager(manager), m_running(false) +{ + connect(m_manager, SIGNAL(debuggingFinished()), + this, SLOT(debuggingFinished())); + connect(m_manager, SIGNAL(applicationOutputAvailable(QString, QString)), + this, SLOT(slotAddToOutputWindow(QString, QString))); + connect(m_manager, SIGNAL(inferiorPidChanged(qint64)), + this, SLOT(bringApplicationToForeground(qint64))); +} + +void DebuggerRunControl::start() +{ + m_running = true; + ApplicationRunConfigurationPtr rc = + qSharedPointerCast<ApplicationRunConfiguration>(runConfiguration()); + QWB_ASSERT(rc, return); + ProjectExplorer::Project *project = rc->project(); + QWB_ASSERT(project, return); + + m_manager->m_executable = rc->executable(); + m_manager->m_environment = rc->environment().toStringList(); + m_manager->m_workingDir = rc->workingDirectory(); + m_manager->m_processArgs = rc->commandLineArguments(); + m_manager->m_buildDir = + project->buildDirectory(project->activeBuildConfiguration()); + //<daniel> andre: + "\qtc-gdbmacros\" + + //emit addToOutputWindow(this, tr("Debugging %1").arg(m_executable)); + if (m_manager->startNewDebugger(DebuggerManager::startInternal)) + emit started(); + else + debuggingFinished(); +} + +void DebuggerRunControl::slotAddToOutputWindow(const QString &prefix, const QString &line) +{ + Q_UNUSED(prefix); + foreach (const QString &l, line.split('\n')) + emit addToOutputWindow(this, prefix + l); + //emit addToOutputWindow(this, prefix + line); +} + +void DebuggerRunControl::stop() +{ + m_manager->exitDebugger(); +} + +void DebuggerRunControl::debuggingFinished() +{ + m_running = false; + //emit addToOutputWindow(this, tr("Debugging %1 finished").arg(m_executable)); + emit finished(); +} + +bool DebuggerRunControl::isRunning() const +{ + return m_running; +} diff --git a/src/plugins/debugger/debuggerrunner.h b/src/plugins/debugger/debuggerrunner.h new file mode 100644 index 0000000000..cf8a8d2184 --- /dev/null +++ b/src/plugins/debugger/debuggerrunner.h @@ -0,0 +1,96 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGERRUNNER_H +#define DEBUGGERRUNNER_H + +#include <projectexplorer/runconfiguration.h> + +namespace ProjectExplorer { +class ApplicationRunConfiguration; +} + +namespace Debugger { +namespace Internal { + +class DebuggerManager; +class StartData; + +typedef QSharedPointer<ProjectExplorer::RunConfiguration> + RunConfigurationPtr; + +typedef QSharedPointer<ProjectExplorer::ApplicationRunConfiguration> + ApplicationRunConfigurationPtr; + +class DebuggerRunner : public ProjectExplorer::IRunConfigurationRunner +{ + Q_OBJECT + +public: + explicit DebuggerRunner(DebuggerManager *manager); + + virtual bool canRun(RunConfigurationPtr runConfiguration, const QString &mode); + virtual QString displayName() const; + virtual ProjectExplorer::RunControl *run(RunConfigurationPtr runConfiguration, + const QString &mode); + virtual QWidget *configurationWidget(RunConfigurationPtr runConfiguration); + +private: + DebuggerManager *m_manager; +}; + + +class DebuggerRunControl : public ProjectExplorer::RunControl +{ + Q_OBJECT + +public: + DebuggerRunControl(DebuggerManager *manager, + ApplicationRunConfigurationPtr runConfiguration); + + virtual void start(); + virtual void stop(); + virtual bool isRunning() const; + +private slots: + void debuggingFinished(); + void slotAddToOutputWindow(const QString &prefix, const QString &line); + +private: + DebuggerManager *m_manager; + bool m_running; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGERRUNNER_H diff --git a/src/plugins/debugger/disassemblerhandler.cpp b/src/plugins/debugger/disassemblerhandler.cpp new file mode 100644 index 0000000000..02d3172255 --- /dev/null +++ b/src/plugins/debugger/disassemblerhandler.cpp @@ -0,0 +1,187 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "disassemblerhandler.h" + +#include "assert.h" + +#include <QtCore/QDebug> +#include <QtCore/QAbstractTableModel> + +#include <QtGui/QIcon> + +using namespace Debugger; +using namespace Debugger::Internal; + + +////////////////////////////////////////////////////////////////// +// +// DisassemblerModel +// +////////////////////////////////////////////////////////////////// + +/*! A model to represent the stack in a QTreeView. */ +class Debugger::Internal::DisassemblerModel : public QAbstractTableModel +{ +public: + DisassemblerModel(QObject *parent); + + // ItemModel + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + // Properties + void setLines(const QList<DisassemblerLine> &lines); + QList<DisassemblerLine> lines() const; + void setCurrentLine(int line) { m_currentLine = line; } + +private: + friend class DisassemblerHandler; + QList<DisassemblerLine> m_lines; + int m_currentLine; + QIcon m_positionIcon; + QIcon m_emptyIcon; +}; + +DisassemblerModel::DisassemblerModel(QObject *parent) + : QAbstractTableModel(parent), m_currentLine(0) +{ + m_emptyIcon = QIcon(":/gdbdebugger/images/empty.svg"); + m_positionIcon = QIcon(":/gdbdebugger/images/location.svg"); +} + +int DisassemblerModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_lines.size(); +} + +int DisassemblerModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 3; +} + +QVariant DisassemblerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_lines.size()) + return QVariant(); + + const DisassemblerLine &line = m_lines.at(index.row()); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return line.addressDisplay; + case 1: + return line.symbolDisplay; + case 2: + return line.mnemonic; + } + } else if (role == Qt::ToolTipRole) { + return QString(); + } else if (role == Qt::DecorationRole && index.column() == 0) { + // Return icon that indicates whether this is the active stack frame + return (index.row() == m_currentLine) ? m_positionIcon : m_emptyIcon; + } + + return QVariant(); +} + +QVariant DisassemblerModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + static const char * const headers[] = { + QT_TR_NOOP("Address"), + QT_TR_NOOP("Symbol"), + QT_TR_NOOP("Mnemonic"), + }; + if (section < 3) + return tr(headers[section]); + } + return QVariant(); +} + +void DisassemblerModel::setLines(const QList<DisassemblerLine> &lines) +{ + m_lines = lines; + if (m_currentLine >= m_lines.size()) + m_currentLine = m_lines.size() - 1; + reset(); +} + +QList<DisassemblerLine> DisassemblerModel::lines() const +{ + return m_lines; +} + + + +////////////////////////////////////////////////////////////////// +// +// DisassemblerHandler +// +////////////////////////////////////////////////////////////////// + +DisassemblerHandler::DisassemblerHandler() +{ + m_model = new DisassemblerModel(this); +} + +void DisassemblerHandler::removeAll() +{ + m_model->m_lines.clear(); +} + +QAbstractItemModel *DisassemblerHandler::model() const +{ + return m_model; +} + +void DisassemblerHandler::setLines(const QList<DisassemblerLine> &lines) +{ + m_model->setLines(lines); +} + +QList<DisassemblerLine> DisassemblerHandler::lines() const +{ + return m_model->lines(); +} + +void DisassemblerHandler::setCurrentLine(int line) +{ + m_model->setCurrentLine(line); +} diff --git a/src/plugins/debugger/disassemblerhandler.h b/src/plugins/debugger/disassemblerhandler.h new file mode 100644 index 0000000000..9de497a1b4 --- /dev/null +++ b/src/plugins/debugger/disassemblerhandler.h @@ -0,0 +1,78 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DISASSEMBLERHANDLER_H +#define DISASSEMBLERHANDLER_H + +#include <QtCore/QObject> +#include <QtCore/QAbstractItemModel> + +#include <QtGui/QIcon> + +namespace Debugger { +namespace Internal { + +class DisassemblerLine +{ +public: + QString address; + QString symbol; + QString addressDisplay; + QString symbolDisplay; + QString mnemonic; +}; + +class DisassemblerModel; + +class DisassemblerHandler : public QObject +{ + Q_OBJECT + +public: + DisassemblerHandler(); + QAbstractItemModel *model() const; + +public slots: + void removeAll(); + + void setLines(const QList<DisassemblerLine> &lines); + QList<DisassemblerLine> lines() const; + void setCurrentLine(int line); + +private: + DisassemblerModel *m_model; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DISASSEMBLERHANDLER_H diff --git a/src/plugins/debugger/disassemblerwindow.cpp b/src/plugins/debugger/disassemblerwindow.cpp new file mode 100644 index 0000000000..8130031d70 --- /dev/null +++ b/src/plugins/debugger/disassemblerwindow.cpp @@ -0,0 +1,140 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "disassemblerwindow.h" + +#include <QAction> +#include <QDebug> +#include <QHeaderView> +#include <QMenu> +#include <QResizeEvent> + + +using namespace Debugger::Internal; + +DisassemblerWindow::DisassemblerWindow() + : m_alwaysResizeColumnsToContents(true), m_alwaysReloadContents(false) +{ + setWindowTitle(tr("Disassembler")); + setSortingEnabled(false); + setAlternatingRowColors(true); + setRootIsDecorated(false); + header()->hide(); + //setIconSize(QSize(10, 10)); + //setWindowIcon(QIcon(":/gdbdebugger/images/debugger_breakpoints.png")); + //QHeaderView *hv = header(); + //hv->setDefaultAlignment(Qt::AlignLeft); + //hv->setClickable(true); + //hv->setSortIndicatorShown(true); +} + +void DisassemblerWindow::resizeEvent(QResizeEvent *ev) +{ + //QHeaderView *hv = header(); + //int totalSize = ev->size().width() - 110; + //hv->resizeSection(0, 60); + //hv->resizeSection(1, (totalSize * 50) / 100); + //hv->resizeSection(2, (totalSize * 50) / 100); + //hv->resizeSection(3, 50); + QTreeView::resizeEvent(ev); +} + +void DisassemblerWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + QMenu menu; + //QTreeWidgetItem *item = itemAt(ev->pos()); + QAction *act1 = new QAction("Adjust column widths to contents", &menu); + QAction *act2 = new QAction("Always adjust column widths to contents", &menu); + act2->setCheckable(true); + act2->setChecked(m_alwaysResizeColumnsToContents); + QAction *act3 = new QAction("Reload disassembler listing", &menu); + QAction *act4 = new QAction("Always reload disassembler listing", &menu); + act4->setCheckable(true); + act4->setChecked(m_alwaysReloadContents); + //if (item) { + // menu.addAction(act0); + // menu.addSeparator(); + //} + menu.addAction(act3); + //menu.addAction(act4); + menu.addSeparator(); + menu.addAction(act1); + menu.addAction(act2); + + QAction *act = menu.exec(ev->globalPos()); + + if (act == act1) + resizeColumnsToContents(); + else if (act == act2) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); + else if (act == act3) + reloadContents(); + else if (act == act2) + setAlwaysReloadContents(!m_alwaysReloadContents); +} + +void DisassemblerWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + resizeColumnToContents(1); + resizeColumnToContents(2); +} + +void DisassemblerWindow::setAlwaysResizeColumnsToContents(bool on) +{ + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + header()->setResizeMode(1, mode); + header()->setResizeMode(2, mode); +} + +void DisassemblerWindow::setAlwaysReloadContents(bool on) +{ + m_alwaysReloadContents = on; + if (m_alwaysReloadContents) + reloadContents(); +} + +void DisassemblerWindow::reloadContents() +{ + emit reloadDisassemblerRequested(); +} + + +void DisassemblerWindow::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setAlwaysResizeColumnsToContents(true); +} + diff --git a/src/plugins/debugger/disassemblerwindow.h b/src/plugins/debugger/disassemblerwindow.h new file mode 100644 index 0000000000..3715afca21 --- /dev/null +++ b/src/plugins/debugger/disassemblerwindow.h @@ -0,0 +1,71 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_DISASSEMBLERWINDOW_H +#define DEBUGGER_DISASSEMBLERWINDOW_H + +#include <QTreeView> + +namespace Debugger { +namespace Internal { + +class DisassemblerWindow : public QTreeView +{ + Q_OBJECT + +public: + DisassemblerWindow(); + void setModel(QAbstractItemModel *model); + +signals: + void reloadDisassemblerRequested(); + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on); + void reloadContents(); + void setAlwaysReloadContents(bool on); + +protected: + void resizeEvent(QResizeEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + +private: + bool m_alwaysResizeColumnsToContents; + bool m_alwaysReloadContents; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_DISASSEMBLERWINDOW_H + diff --git a/src/plugins/debugger/gdbengine.cpp b/src/plugins/debugger/gdbengine.cpp new file mode 100644 index 0000000000..9a29bc8675 --- /dev/null +++ b/src/plugins/debugger/gdbengine.cpp @@ -0,0 +1,4035 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ + +#include "gdbengine.h" + +#include "assert.h" +#include "debuggerconstants.h" +#include "debuggermanager.h" +#include "gdbmi.h" +#include "procinterrupt.h" + +#include "disassemblerhandler.h" +#include "breakhandler.h" +#include "moduleshandler.h" +#include "registerhandler.h" +#include "stackhandler.h" +#include "watchhandler.h" + +#include "startexternaldialog.h" +#include "attachexternaldialog.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QTime> +#include <QtCore/QTimer> + +#include <QtGui/QAction> +#include <QtGui/QLabel> +#include <QtGui/QMainWindow> +#include <QtGui/QMessageBox> +#include <QtGui/QToolTip> + +#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) +#include <unistd.h> +#include <dlfcn.h> +#endif + +using namespace Debugger; +using namespace Debugger::Internal; +using namespace Debugger::Constants; + +Q_DECLARE_METATYPE(Debugger::Internal::GdbMi); + +//#define DEBUG_PENDING 1 +//#define DEBUG_SUBITEM 1 + +#if DEBUG_PENDING +# define PENDING_DEBUG(s) qDebug() << s +#else +# define PENDING_DEBUG(s) +#endif + +static const QString tooltipIName = "tooltip"; + +/////////////////////////////////////////////////////////////////////// +// +// GdbSettings +// +/////////////////////////////////////////////////////////////////////// + +GdbSettings &Debugger::Internal::theGdbSettings() +{ + static GdbSettings settings; + return settings; +} + + +/////////////////////////////////////////////////////////////////////// +// +// GdbCommandType +// +/////////////////////////////////////////////////////////////////////// + +enum GdbCommandType +{ + GdbInvalidCommand = 0, + + GdbShowVersion = 100, + GdbFileExecAndSymbols, + GdbQueryPwd, + GdbQuerySources, + GdbQuerySources2, + GdbAsyncOutput2, + GdbExecRun, + GdbExecRunToFunction, + GdbExecStep, + GdbExecNext, + GdbExecStepI, + GdbExecNextI, + GdbExecContinue, + GdbExecFinish, + GdbExecJumpToLine, + GdbExecInterrupt, + GdbInfoShared, + GdbInfoProc, + GdbQueryDataDumper1, + GdbQueryDataDumper2, + GdbInitializeSocket1, + + BreakCondition = 200, + BreakEnablePending, + BreakSetAnnotate, + BreakDelete, + BreakList, + BreakIgnore, + BreakInfo, + BreakInsert, + BreakInsert1, + + DisassemblerList = 300, + + ModulesList = 400, + + RegisterListNames = 500, + RegisterListValues, + + StackSelectThread = 600, + StackListThreads, + StackListFrames, + StackListLocals, + StackListArguments, + + WatchVarAssign = 700, // data changed by user + WatchVarListChildren, + WatchVarCreate, + WatchEvaluateExpression, + WatchToolTip, + WatchDumpCustomSetup, + WatchDumpCustomValue1, // waiting for gdb ack + WatchDumpCustomValue2, // waiting for actual data + WatchDumpCustomEditValue, +}; + +QString dotEscape(QString str) +{ + str.replace(' ', '.'); + str.replace('\\', '.'); + str.replace('/', '.'); + return str; +} + +QString currentTime() +{ + return QTime::currentTime().toString("hh:mm:ss.zzz"); +} + +static int ¤tToken() +{ + static int token = 0; + return token; +} + +static bool isSkippableFunction(const QString &funcName, const QString &fileName) +{ + if (fileName.endsWith("kernel/qobject.cpp")) + return true; + if (fileName.endsWith("kernel/moc_qobject.cpp")) + return true; + if (fileName.endsWith("kernel/qmetaobject.cpp")) + return true; + if (fileName.endsWith(".moc")) + return true; + + if (funcName.endsWith("::qt_metacall")) + return true; + + return false; +} + +static bool isLeavableFunction(const QString &funcName, const QString &fileName) +{ + if (funcName.endsWith("QObjectPrivate::setCurrentSender")) + return true; + if (fileName.endsWith("kernel/qmetaobject.cpp") + && funcName.endsWith("QMetaObject::methodOffset")) + return true; + if (fileName.endsWith("kernel/qobject.h")) + return true; + if (fileName.endsWith("kernel/qobject.cpp") + && funcName.endsWith("QObjectConnectionListVector::at")) + return true; + if (fileName.endsWith("kernel/qobject.cpp") + && funcName.endsWith("~QObject")) + return true; + if (fileName.endsWith("thread/qmutex.cpp")) + return true; + if (fileName.endsWith("thread/qthread.cpp")) + return true; + if (fileName.endsWith("thread/qthread_unix.cpp")) + return true; + if (fileName.endsWith("thread/qmutex.h")) + return true; + if (fileName.contains("thread/qbasicatomic")) + return true; + if (fileName.contains("thread/qorderedmutexlocker_p")) + return true; + if (fileName.contains("arch/qatomic")) + return true; + if (fileName.endsWith("tools/qvector.h")) + return true; + if (fileName.endsWith("tools/qlist.h")) + return true; + if (fileName.endsWith("tools/qhash.h")) + return true; + if (fileName.endsWith("tools/qmap.h")) + return true; + if (fileName.endsWith("tools/qstring.h")) + return true; + if (fileName.endsWith("global/qglobal.h")) + return true; + + return false; +} + + +/////////////////////////////////////////////////////////////////////// +// +// GdbEngine +// +/////////////////////////////////////////////////////////////////////// + +GdbEngine::GdbEngine(DebuggerManager *parent) +{ + q = parent; + qq = parent->engineInterface(); + init(); +} + +GdbEngine::~GdbEngine() +{ +} + +void GdbEngine::init() +{ + m_pendingRequests = 0; + m_gdbVersion = 100; + m_shared = 0; + qq->debugDumpersAction()->setChecked(false); + + m_oldestAcceptableToken = -1; + + // Gdb Process interaction + connect(&m_gdbProc, SIGNAL(error(QProcess::ProcessError)), this, + SLOT(gdbProcError(QProcess::ProcessError))); + connect(&m_gdbProc, SIGNAL(readyReadStandardOutput()), this, + SLOT(readGdbStandardOutput())); + connect(&m_gdbProc, SIGNAL(readyReadStandardError()), this, + SLOT(readGdbStandardError())); + connect(&m_gdbProc, SIGNAL(finished(int, QProcess::ExitStatus)), q, + SLOT(exitDebugger())); + + // Custom dumpers + //m_dumperServerConnection = 0; + //m_dumperServer = new DumperServer(this); + //QString name = "gdb-" + + // QDateTime::currentDateTime().toString("yyyy_MM_dd-hh_mm_ss_zzz"); + //m_dumperServer->listen(name); + //connect(m_dumperServer, SIGNAL(newConnection()), + // this, SLOT(acceptConnection())); + + //if (!m_dumperServer->isListening()) { + // QMessageBox::critical(q->mainWindow(), tr("Dumper Server Setup Failed"), + // tr("Unable to create server listening for data: %1.\n" + // "Server name: %2").arg(m_dumperServer->errorString(), name), + // QMessageBox::Retry | QMessageBox::Cancel); + // } + + connect(qq->debugDumpersAction(), SIGNAL(toggled(bool)), + this, SLOT(setDebugDumpers(bool))); + + connect(qq->useCustomDumpersAction(), SIGNAL(toggled(bool)), + this, SLOT(setCustomDumpersWanted(bool))); + + // Output + connect(this, SIGNAL(gdbResponseAvailable()), + this, SLOT(handleResponse()), Qt::QueuedConnection); + + connect(this, SIGNAL(gdbOutputAvailable(QString,QString)), + q, SLOT(showDebuggerOutput(QString,QString)), + Qt::QueuedConnection); + connect(this, SIGNAL(gdbInputAvailable(QString,QString)), + q, SLOT(showDebuggerInput(QString,QString)), + Qt::QueuedConnection); + connect(this, SIGNAL(applicationOutputAvailable(QString,QString)), + q, SLOT(showApplicationOutput(QString,QString)), + Qt::QueuedConnection); +} + +void GdbEngine::gdbProcError(QProcess::ProcessError error) +{ + QString msg; + switch (error) { + case QProcess::FailedToStart: + msg = QString(tr("The Gdb process failed to start. Either the " + "invoked program '%1' is missing, or you may have insufficient " + "permissions to invoke the program.")).arg(theGdbSettings().m_gdbCmd); + break; + case QProcess::Crashed: + msg = tr("The Gdb process crashed some time after starting " + "successfully."); + break; + case QProcess::Timedout: + msg = tr("The last waitFor...() function timed out. " + "The state of QProcess is unchanged, and you can try calling " + "waitFor...() again."); + break; + case QProcess::WriteError: + msg = tr("An error occurred when attempting to write " + "to the Gdb process. For example, the process may not be running, " + "or it may have closed its input channel."); + break; + case QProcess::ReadError: + msg = tr("An error occurred when attempting to read from " + "the Gdb process. For example, the process may not be running."); + break; + default: + msg = tr("An unknown error in the Gdb process occurred. " + "This is the default return value of error()."); + } + + q->showStatusMessage(msg, 5000); + QMessageBox::critical(q->mainWindow(), tr("Error"), msg); + // act as if it was closed by the core + q->exitDebugger(); +} + +static void skipSpaces(const char *&from, const char *to) +{ + while (from != to && QChar(*from).isSpace()) + ++from; +} + +static inline bool isNameChar(char c) +{ + // could be 'stopped' or 'shlibs-added' + return (c >= 'a' && c <= 'z') || c == '-'; +} + +#if 0 +static void dump(const char *first, const char *middle, const QString & to) +{ + QByteArray ba(first, middle - first); + Q_UNUSED(to); + // note that qDebug cuts off output after a certain size... (bug?) + qDebug("\n>>>>> %s\n%s\n====\n%s\n<<<<<\n", + qPrintable(currentTime()), + qPrintable(QString(ba).trimmed()), + qPrintable(to.trimmed())); + //qDebug() << ""; + //qDebug() << qPrintable(currentTime()) + // << " Reading response: " << QString(ba).trimmed() << "\n"; +} +#endif + +static void skipTerminator(const char *&from, const char *to) +{ + skipSpaces(from, to); + // skip '(gdb)' + if (from[0] == '(' && from[1] == 'g' && from[3] == 'b' && from[4] == ')') + from += 5; + skipSpaces(from, to); +} + +// called asyncronously as response to Gdb stdout output in +// gdbResponseAvailable() +void GdbEngine::handleResponse() +{ + static QTime lastTime; + + emit gdbOutputAvailable(" ", currentTime()); + emit gdbOutputAvailable("stdout:", m_inbuffer); + +#if 0 + qDebug() // << "#### start response handling #### " + << currentTime() + << lastTime.msecsTo(QTime::currentTime()) << "ms," + << "buf: " << m_inbuffer.left(1500) << "..." + //<< "buf: " << m_inbuffer + << "size:" << m_inbuffer.size(); +#else + //qDebug() << "buf: " << m_inbuffer; +#endif + + lastTime = QTime::currentTime(); + + while (1) { + if (m_inbuffer.isEmpty()) + break; + + const char *from = m_inbuffer.constData(); + // FIXME: check for line ending in '\n(gdb)\n' + const char *to = from + m_inbuffer.size(); + const char *inner; + + //const char *oldfrom = from; + + //skipSpaces(from, to); + skipTerminator(from, to); + int token = -1; + + // token is a sequence of numbers + for (inner = from; inner != to; ++inner) + if (*inner < '0' || *inner > '9') + break; + if (from != inner) { + token = QString(QByteArray(from, inner - from)).toInt(); + from = inner; + //qDebug() << "found token " << token; + } + + if (from == to) { + //qDebug() << "Returning: " << toString(); + break; + } + + if (token == -1 && *from != '&' && *from != '~') { + // FIXME: On Linux the application's std::out is merged in here. + // High risk of falsely interpreting this as MI output. + // We assume that we _always_ use tokens, so not finding a token + // is a positive indication for the presence of application output. + QString s; + while (from != to && *from != '\n') + s += *from++; + //qDebug() << "UNREQUESTED DATA " << s << " TAKEN AS APPLICATION OUTPUT"; + s += '\n'; + + m_inbuffer = QByteArray(from, to - from); + emit applicationOutputAvailable("app-stdout: ", s); + continue; + } + + // next char decides kind of record + const char c = *from++; + //qDebug() << "CODE:" << c; + + switch (c) { + case '*': + case '+': + case '=': { + QByteArray asyncClass; + for (; from != to; ++from) { + const char c = *from; + if (!isNameChar(c)) + break; + asyncClass += *from; + } + //qDebug() << "ASYNCCLASS" << asyncClass; + + GdbMi record; + while (from != to && *from == ',') { + ++from; // skip ',' + GdbMi data; + data.parseResultOrValue(from, to); + if (data.isValid()) { + //qDebug() << "parsed response: " << data.toString(); + record.m_children += data; + record.m_type = GdbMi::Tuple; + } + } + //dump(oldfrom, from, record.toString()); + skipTerminator(from, to); + m_inbuffer = QByteArray(from, to - from); + if (asyncClass == "stopped") { + handleAsyncOutput(record); + } else { + qDebug() << "INGNORED ASYNC OUTPUT " << record.toString(); + } + break; + } + + case '~': + case '@': + case '&': { + QString data = GdbMi::parseCString(from, to); + handleStreamOutput(data, c); + //dump(oldfrom, from, record.toString()); + m_inbuffer = QByteArray(from, to - from); + break; + } + + case '#': { + //qDebug() << "CUSTOM OUTPUT, TOKEN" << token; + QString str; + for (; from != to && *from >= '0' && *from <= '9'; ++from) + str += QLatin1Char(*from); + ++from; // skip the ' ' + int len = str.toInt(); + QByteArray ba(from, len); + from += len; + m_inbuffer = QByteArray(from, to - from); + m_customOutputForToken[token] += QString(ba); + break; + } + + case '^': { + GdbResultRecord record; + + record.token = token; + + for (inner = from; inner != to; ++inner) + if (*inner < 'a' || *inner > 'z') + break; + + QByteArray resultClass(from, inner - from); + + if (resultClass == "done") + record.resultClass = GdbResultDone; + else if (resultClass == "running") + record.resultClass = GdbResultRunning; + else if (resultClass == "connected") + record.resultClass = GdbResultConnected; + else if (resultClass == "error") + record.resultClass = GdbResultError; + else if (resultClass == "exit") + record.resultClass = GdbResultExit; + else + record.resultClass = GdbResultUnknown; + + from = inner; + skipSpaces(from, to); + if (from != to && *from == ',') { + ++from; + record.data.parseTuple_helper(from, to); + record.data.m_type = GdbMi::Tuple; + record.data.m_name = "data"; + } + skipSpaces(from, to); + skipTerminator(from, to); + + //qDebug() << "\nLOG STREAM:" + m_pendingLogStreamOutput; + //qDebug() << "\nTARGET STREAM:" + m_pendingTargetStreamOutput; + //qDebug() << "\nCONSOLE STREAM:" + m_pendingConsoleStreamOutput; + record.data.setStreamOutput("logstreamoutput", + m_pendingLogStreamOutput); + record.data.setStreamOutput("targetstreamoutput", + m_pendingTargetStreamOutput); + record.data.setStreamOutput("consolestreamoutput", + m_pendingConsoleStreamOutput); + QByteArray custom = m_customOutputForToken[token]; + if (!custom.isEmpty()) + record.data.setStreamOutput("customvaluecontents", + '{' + custom + '}'); + //m_customOutputForToken.remove(token); + m_pendingLogStreamOutput.clear(); + m_pendingTargetStreamOutput.clear(); + m_pendingConsoleStreamOutput.clear(); + + //dump(oldfrom, from, record.toString()); + m_inbuffer = QByteArray(from, to - from); + handleResultRecord(record); + break; + } + default: { + qDebug() << "FIXME: UNKNOWN CODE: " << c << " IN " << m_inbuffer; + m_inbuffer = QByteArray(from, to - from); + break; + } + } + } + + //qDebug() << "##### end response handling ####\n\n\n" + // << currentTime() << lastTime.msecsTo(QTime::currentTime()); + lastTime = QTime::currentTime(); +} + +#ifdef Q_OS_MAC +static void fixMac(QByteArray &out) +{ + // HACK: gdb on Mac mixes MI1 and MI2 syntax. Not nice. + // it returns: 9^done,locals={{name="a"},{name="w"}} + // instead of: 9^done,locals=[{name="a"},{name="w"}] + if (!out.contains("locals={{name")) + return; + + static const QByteArray termArray("(gdb) "); + int pos = out.indexOf(termArray); + if (pos == -1) + return; + + int pos1 = out.indexOf("={{"); + if (pos1 == -1) + return; + + int pos2 = out.indexOf("]]"); + if (pos2 == -1) + return; + + if (pos1 < pos && pos2 < pos) { + out[pos1 + 1] = '['; + out[pos2 + 1] = ']'; + } +} +#endif + +void GdbEngine::readGdbStandardError() +{ + QByteArray err = m_gdbProc.readAllStandardError(); + emit applicationOutputAvailable("app-stderr:", err); +} + +void GdbEngine::readGdbStandardOutput() +{ + // This is the function called whenever the Gdb process created + // output. As a rule of thumb, stdout contains _real_ Gdb output + // as responses to our command (with exception of the data dumpers) + // and "spontaneous" events like messages on loaded shared libraries. + // Otoh, stderr contains application output produced by qDebug etc. + // There is no organized way to pass application stdout output + + // The result of custom data dumpers arrives over the socket _before_ + // the corresponding Gdb "^done" message arrives here over stdout + // and is merged into the response via m_pendingCustomValueContents. + + // Note that this code here runs syncronized to the arriving + // output. The completed response will be signalled by a queued + // connection to the handlers. + + QByteArray out = m_gdbProc.readAllStandardOutput(); + + //qDebug() << "\n\n\nPLUGIN OUT: '" << out.data() << "'\n\n\n"; + + #ifdef Q_OS_MAC + fixMac(out); + #endif + + m_inbuffer.append(out); + //QWB_ASSERT(!m_inbuffer.isEmpty(), return); + + char c = m_inbuffer[m_inbuffer.size() - 1]; + static const QByteArray termArray("(gdb) "); + if (out.indexOf(termArray) == -1 && c != 10 && c != 13) { + //qDebug() << "\n\nBuffer not yet filled, waiting for more data to arrive"; + //qDebug() << m_inbuffer.data() << m_inbuffer.size(); + //qDebug() << "\n\n"; + return; + } + + emit gdbResponseAvailable(); +} + +void GdbEngine::interruptInferior() +{ + if (m_gdbProc.state() == QProcess::NotRunning) + return; + + if (q->m_attachedPID) { + if (interruptProcess(q->m_attachedPID)) + qq->notifyInferiorStopped(); + return; + } + +#ifdef Q_OS_MAC + sendCommand("-exec-interrupt", GdbExecInterrupt); + qq->notifyInferiorStopped(); +#else + if (interruptChildProcess(m_gdbProc.pid())) + qq->notifyInferiorStopped(); +#endif +} + +void GdbEngine::maybeHandleInferiorPidChanged(const QString &pid0) +{ + int pid = pid0.toInt(); + if (pid == 0) { + qDebug() << "Cannot parse PID from " << pid0; + return; + } + if (pid == m_inferiorPid) + return; + m_inferiorPid = pid; + qq->notifyInferiorPidChanged(pid); +} + +void GdbEngine::sendSynchronizedCommand(const QString & command, + int type, const QVariant &cookie, bool needStop) +{ + sendCommand(command, type, cookie, needStop, true); +} + +void GdbEngine::sendCommand(const QString &command, int type, + const QVariant &cookie, bool needStop, bool synchronized) +{ + if (m_gdbProc.state() == QProcess::NotRunning) { + //qDebug() << "NO GDB PROCESS RUNNING, CMD IGNORED:" << command; + return; + } + + bool temporarilyStopped = false; + if (needStop && q->status() == DebuggerInferiorRunning) { + q->showStatusMessage(tr("Temporarily stopped"), -1); + interruptInferior(); + temporarilyStopped = true; + } + + ++currentToken(); + if (synchronized) { + ++m_pendingRequests; + PENDING_DEBUG(" TYPE " << type << " INCREMENTS PENDING TO: " + << m_pendingRequests << command); + } else { + PENDING_DEBUG(" UNKNOWN TYPE " << type << " LEAVES PENDING AT: " + << m_pendingRequests << command); + } + + GdbCookie cmd; + cmd.synchronized = synchronized; + cmd.command = command; + cmd.command = QString::number(currentToken()) + cmd.command; + if (cmd.command.contains("%1")) + cmd.command = cmd.command.arg(currentToken()); + cmd.type = type; + cmd.cookie = cookie; + + m_cookieForToken[currentToken()] = cmd; + + //qDebug() << ""; + if (!command.isEmpty()) { + //qDebug() << qPrintable(currentTime()) << "RUNNING << cmd.command; + m_gdbProc.write(cmd.command.toLatin1() + "\r\n"); + //emit gdbInputAvailable(QString(), " " + currentTime()); + emit gdbInputAvailable(QString(), "[" + currentTime() + "] " + cmd.command); + //emit gdbInputAvailable(QString(), cmd.command); + } + + if (temporarilyStopped) + sendCommand("-exec-continue"); + + // slows down + //qApp->processEvents(); +} + +void GdbEngine::handleResultRecord(const GdbResultRecord &record) +{ + //qDebug() << "TOKEN: " << record.token + // << " ACCEPTABLE: " << m_oldestAcceptableToken; + //qDebug() << ""; + //qDebug() << qPrintable(currentTime()) << "Reading response: " + // << record.toString() << "\n"; + //qDebug() << "\nRESULT" << record.token << record.toString(); + + int token = record.token; + if (token == -1) + return; + + if (!m_cookieForToken.contains(token)) { + qDebug() << "NO SUCH TOKEN (ANYMORE): " << token; + return; + } + + GdbCookie cmd = m_cookieForToken.take(token); + + // FIXME: this falsely rejects results from the custom dumper recognition + // procedure, too... + if (record.token < m_oldestAcceptableToken) { + //qDebug() << "### SKIPPING OLD RESULT " << record.toString(); + //QMessageBox::information(m_mainWindow, tr("Skipped"), "xxx"); + return; + } + + // We get _two_ results for a '-exec-foo' command: First a + // 'running' notification, then a 'stopped' or similar. + // So put it back. + if (record.resultClass == GdbResultRunning) + m_cookieForToken[token] = cmd; + +#if 0 + qDebug() << "# handleOutput, " + << "cmd type: " << cmd.type + << "cmd synchronized: " << cmd.synchronized + << "\n record: " << record.toString(); +#endif + + // << "\n data: " << record.data.toString(true); + + if (cmd.type != GdbInvalidCommand) + handleResult(record, cmd.type, cmd.cookie); + + if (cmd.synchronized) { + --m_pendingRequests; + PENDING_DEBUG(" TYPE " << cmd.type << " DECREMENTS PENDING TO: " + << m_pendingRequests << cmd.command); + if (m_pendingRequests == 0) + updateWatchModel2(); + } else { + PENDING_DEBUG(" UNKNOWN TYPE " << cmd.type << " LEAVES PENDING AT: " + << m_pendingRequests << cmd.command); + } +} + +void GdbEngine::handleResult(const GdbResultRecord & record, int type, + const QVariant & cookie) +{ + switch (type) { + case GdbExecNext: + case GdbExecStep: + case GdbExecNextI: + case GdbExecStepI: + case GdbExecContinue: + case GdbExecFinish: + // evil code sharing + case GdbExecRun: + handleExecRun(record); + break; + case GdbInfoProc: + handleInfoProc(record); + break; + + case GdbShowVersion: + handleShowVersion(record); + break; + case GdbFileExecAndSymbols: + handleFileExecAndSymbols(record); + break; + case GdbExecRunToFunction: + // that should be "^running". We need to handle the resulting + // "Stopped" + //handleExecRunToFunction(record); + break; + case GdbExecInterrupt: + break; + case GdbExecJumpToLine: + handleExecJumpToLine(record); + break; + case GdbQueryPwd: + handleQueryPwd(record); + break; + case GdbQuerySources: + handleQuerySources(record); + break; + case GdbQuerySources2: + handleQuerySources2(record, cookie); + break; + case GdbAsyncOutput2: + handleAsyncOutput2(cookie.value<GdbMi>()); + break; + case GdbInfoShared: + handleInfoShared(record); + break; + case GdbInitializeSocket1: + //qDebug() << " INIT SOCKET" << record.toString(); + break; + case GdbQueryDataDumper1: + handleQueryDataDumper1(record); + break; + case GdbQueryDataDumper2: + handleQueryDataDumper2(record); + break; + + case BreakList: + handleBreakList(record); + break; + case BreakInsert: + handleBreakInsert(record, cookie.toInt()); + break; + case BreakInsert1: + handleBreakInsert1(record, cookie.toInt()); + break; + case BreakInfo: + handleBreakInfo(record, cookie.toInt()); + break; + case BreakEnablePending: + case BreakDelete: + // nothing + break; + case BreakIgnore: + handleBreakIgnore(record, cookie.toInt()); + break; + case BreakCondition: + handleBreakCondition(record, cookie.toInt()); + break; + + case DisassemblerList: + handleDisassemblerList(record, cookie.toString()); + break; + + case ModulesList: + handleModulesList(record); + break; + + case RegisterListNames: + handleRegisterListNames(record); + break; + case RegisterListValues: + handleRegisterListValues(record); + break; + + case StackListFrames: + handleStackListFrames(record); + break; + case StackListThreads: + handleStackListThreads(record, cookie.toInt()); + break; + case StackSelectThread: + handleStackSelectThread(record, cookie.toInt()); + break; + case StackListLocals: + handleStackListLocals(record); + break; + case StackListArguments: + handleStackListArguments(record); + break; + + case WatchVarListChildren: + handleVarListChildren(record, cookie.value<WatchData>()); + break; + case WatchVarCreate: + handleVarCreate(record, cookie.value<WatchData>()); + break; + case WatchVarAssign: + handleVarAssign(); + break; + case WatchEvaluateExpression: + handleEvaluateExpression(record, cookie.value<WatchData>()); + break; + case WatchToolTip: + handleToolTip(record, cookie.toString()); + break; + case WatchDumpCustomValue1: + handleDumpCustomValue1(record, cookie.value<WatchData>()); + break; + case WatchDumpCustomValue2: + handleDumpCustomValue2(record, cookie.value<WatchData>()); + break; + case WatchDumpCustomSetup: + handleDumpCustomSetup(record); + break; + + default: + qDebug() << "FIXME: GdbEngine::handleResult: " + "should not happen" << type; + break; + } +} + +void GdbEngine::executeDebuggerCommand(const QString &command) +{ + //createGdbProcessIfNeeded(); + if (m_gdbProc.state() == QProcess::NotRunning) { + qDebug() << "NO GDB PROCESS RUNNING, PLAIN CMD IGNORED: " << command; + return; + } + + GdbCookie cmd; + cmd.command = command; + cmd.type = -1; + + //m_cookieForToken[currentToken()] = cmd; + //++currentToken(); + + //qDebug() << ""; + //qDebug() << currentTime() << "Running command: " << cmd.command; + emit gdbInputAvailable(QString(), cmd.command); + m_gdbProc.write(cmd.command.toLatin1() + "\r\n"); +} + +void GdbEngine::handleQueryPwd(const GdbResultRecord &record) +{ + // FIXME: remove this special case as soon as 'pwd' + // is supported by MI + //qDebug() << "PWD OUTPUT:" << record.toString(); + // Gdb responses _unless_ we get an error first. + if (record.resultClass == GdbResultDone) { +#ifdef Q_OS_LINUX + // "5^done,{logstreamoutput="pwd ",consolestreamoutput + // ="Working directory /home/apoenitz/dev/work/test1. "} + m_pwd = record.data.findChild("consolestreamoutput").data(); + int pos = m_pwd.indexOf("Working directory"); + m_pwd = m_pwd.mid(pos + 18); + m_pwd = m_pwd.trimmed(); + if (m_pwd.endsWith('.')) + m_pwd.chop(1); +#endif +#ifdef Q_OS_WIN + // ~"Working directory C:\\Users\\Thomas\\Documents\\WBTest3\\debug.\n" + m_pwd = record.data.findChild("consolestreamoutput").data(); + m_pwd = m_pwd.trimmed(); +#endif + //qDebug() << "PWD RESULT:" << m_pwd; + } +} + +void GdbEngine::handleQuerySources(const GdbResultRecord &record) +{ + if (record.resultClass == GdbResultDone) { + m_shortToFullName.clear(); + m_fullToShortName.clear(); + // "^done,files=[{file="../../../../bin/gdbmacros/gdbmacros.cpp", + // fullname="/data5/dev/ide/main/bin/gdbmacros/gdbmacros.cpp"}, + GdbMi files = record.data.findChild("files"); + foreach (const GdbMi &item, files.children()) { + QString fileName = item.findChild("file").data(); + GdbMi fullName = item.findChild("fullname"); + QString full = fullName.data(); + #ifdef Q_OS_WIN + full = QDir::cleanPath(full); + #endif + if (fullName.isValid() && QFileInfo(full).isReadable()) { + //qDebug() << "STORING 2: " << fileName << full; + m_shortToFullName[fileName] = full; + m_fullToShortName[full] = fileName; + } + } + } +} + +void GdbEngine::handleInfoProc(const GdbResultRecord &record) +{ + if (record.resultClass == GdbResultDone) { + #if defined(Q_OS_MAC) + //^done,process-id="85075" + maybeHandleInferiorPidChanged(record.data.findChild("process-id").data()); + #endif + + #if defined(Q_OS_LINUX) || defined(Q_OS_WIN) + // FIXME: use something more robust + QRegExp re(QLatin1String("process (\\d+)")); + QString data = record.data.findChild("consolestreamoutput").data(); + if (re.indexIn(data) != -1) + maybeHandleInferiorPidChanged(re.cap(1)); + #endif + } +} + +void GdbEngine::handleInfoShared(const GdbResultRecord &record) +{ + if (record.resultClass == GdbResultDone) { + // let the modules handler do the parsing + handleModulesList(record); + QList<Module> modules = qq->modulesHandler()->modules(); + bool reloadNeeded = false; + foreach (const Module &module, modules) { + // FIXME: read this from some list + if (!module.symbolsRead && !module.moduleName.contains("Q")) { + reloadNeeded = true; + sendCommand("sharedlibrary " + dotEscape(module.moduleName)); + } + } + if (reloadNeeded) + reloadModules(); + continueInferior(); + } +} + +void GdbEngine::handleQuerySources2(const GdbResultRecord &record, + const QVariant &cookie) +{ + if (record.resultClass == GdbResultDone) + handleAsyncOutput2(cookie.value<GdbMi>()); +} + +void GdbEngine::handleExecJumpToLine(const GdbResultRecord &record) +{ + // FIXME: remove this special case as soon as 'jump' + // is supported by MI + // "&"jump /home/apoenitz/dev/work/test1/test1.cpp:242" + // ~"Continuing at 0x4058f3." + // ~"run1 (argc=1, argv=0x7fffb213a478) at test1.cpp:242" + // ~"242\t x *= 2;" + //109^done" + qq->notifyInferiorStopped(); + q->showStatusMessage(tr("Jumped. Stopped."), -1); + QString output = record.data.findChild("logstreamoutput").data(); + if (!output.isEmpty()) + return; + QString fileAndLine = output.section(' ', 1, 1); + QString file = fileAndLine.section(':', 0, 0); + int line = fileAndLine.section(':', 1, 1).toInt(); + q->gotoLocation(file, line, true); +} + +void GdbEngine::handleExecRunToFunction(const GdbResultRecord &record) +{ + // FIXME: remove this special case as soon as there's a real + // reason given when the temporary breakpoint is hit. + // reight now we get: + // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4", + // func="foo",args=[{name="str",value="@0x7fff0f450460"}], + // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"} + qq->notifyInferiorStopped(); + q->showStatusMessage(tr("Run to Function finished. Stopped."), -1); + GdbMi frame = record.data.findChild("frame"); + QString file = frame.findChild("fullname").data(); + int line = frame.findChild("line").data().toInt(); + qDebug() << "HIT: " << file << line << " IN " << frame.toString() + << " -- " << record.toString(); + q->gotoLocation(file, line, true); +} + +void GdbEngine::handleStreamOutput(const QString &data, char code) +{ + // Linux + if (data.contains("[New Thread")) { + QRegExp re("\\[New Thread 0x([0-9a-f]*) \\(LWP ([0-9]*)\\)\\]"); + if (re.indexIn(data) != -1) + maybeHandleInferiorPidChanged(re.cap(2)); + } + + // Mac + if (data.contains("[Switching to process ")) { + QRegExp re("\\[Switching to process ([0-9]*) local thread 0x([0-9a-f]*)\\]"); + if (re.indexIn(data) != -1) + maybeHandleInferiorPidChanged(re.cap(1)); + } + + // present it twice: now and together with the next 'real' result + switch (code) { + case '~': + m_pendingConsoleStreamOutput += data; + break; + case '@': + m_pendingTargetStreamOutput += data; + break; + case '&': + m_pendingLogStreamOutput += data; + // On Windows, the contents seem to depend on the debugger + // version and/or OS version used. + if (data.startsWith("warning:")) + qq->showApplicationOutput(QString(), data); + break; + } + +#ifdef Q_OS_LINUX + if (data.startsWith("Pending break") && data.contains("\" resolved")) { + qDebug() << "SCHEDULING -break-list"; + //m_breakListOnStopNeeded = true; + } +#endif + +#if 0 + if (m_slurpingPTypeOutput) + qDebug() << "SLURP: " << output.data; + + // "No symbol \"__dlopen\" in current context." + // "No symbol \"dlopen\" in current context." + if (output.data.startsWith("No symbol ") + && output.data.contains("dlopen")) { + m_dlopened = true; + return; + } + + // output of 'ptype <foo>' + if (output.data.startsWith("type = ")) { + if (output.data.endsWith("{") || output.data.endsWith("{\\n")) { + // multi-line output started here... + m_slurpingPTypeOutput = true; + m_slurpedPTypeOutput = output.data; + } else { + // Happens for simple types. Process it immediately + m_watchHandler->handleTypeContents(output.data); + } + return; + } + if (m_slurpingPTypeOutput) { + m_slurpedPTypeOutput += '\n'; + m_slurpedPTypeOutput += output.data; + if (output.data.startsWith("}")) { + // this is the last line... + m_slurpingPTypeOutput = false; + m_watchHandler->handleTypeContents(m_slurpedPTypeOutput); + m_slurpedPTypeOutput.clear(); + } + return; + } +#endif +} + +static bool isExitedReason(const QString &reason) +{ + return reason == QLatin1String("exited-normally") // inferior exited normally + || reason == QLatin1String("exited-signalled") // inferior exited because of a signal + //|| reason == QLatin1String("signal-received") // inferior received signal + || reason == QLatin1String("exited"); // inferior exited +} + +static bool isStoppedReason(const QString &reason) +{ + return reason == QLatin1String("function-finished") // -exec-finish + || reason == QLatin1String("signal-received") // handled as "isExitedReason" + || reason == QLatin1String("breakpoint-hit") // -exec-continue + || reason == QLatin1String("end-stepping-range") // -exec-next, -exec-step + || reason == QLatin1String("location-reached") // -exec-until + || reason == QLatin1String("access-watchpoint-trigger") + || reason == QLatin1String("read-watchpoint-trigger") +#ifdef Q_OS_MAC + || reason.isEmpty() +#endif + ; +} + +void GdbEngine::handleAsyncOutput(const GdbMi &data) +{ + const QString reason = data.findChild("reason").data(); + + QString console = data.findChild("consolestreamoutput").data(); + if (console.contains("Stopped due to shared library event") || reason.isEmpty()) { + ++m_shared; + //if (m_shared == 2) + // tryLoadCustomDumpers(); + //qDebug() << "SHARED LIBRARY EVENT " << data.toString() << m_shared; + if (qq->useFastStart()) { + if (1 || m_shared <= 16) { // libpthread? + sendCommand("info shared", GdbInfoShared); + //sendCommand("sharedlibrary gdbdebugger "); + //continueInferior(); + } else { + // auto-load from now on + sendCommand("info shared"); + sendCommand("set auto-solib-add on"); + sendCommand("-file-list-exec-source-files", GdbQuerySources); + sendCommand("-break-list", BreakList); + //sendCommand("bt"); + //QVariant var = QVariant::fromValue<GdbMi>(data); + //sendCommand("p 1", GdbAsyncOutput2, var); // dummy + continueInferior(); + } + } else { + // slow start requested. + q->showStatusMessage("Loading " + data.toString(), -1); + continueInferior(); + } + return; + } + + if (isExitedReason(reason)) { + qq->notifyInferiorExited(); + QString msg = "Program exited normally"; + if (reason == "exited") { + msg = "Program exited with exit code " + + data.findChild("exit-code").toString(); + } else if (reason == "exited-signalled") { + msg = "Program exited after receiving signal " + + data.findChild("signal-name").toString(); + } else if (reason == "signal-received") { + msg = "Program exited after receiving signal " + + data.findChild("signal-name").toString(); + } + q->showStatusMessage(msg, -1); + q->exitDebugger(); + return; + } + + tryLoadCustomDumpers(); + + // jump over well-known frames + static int stepCounter = 0; + if (qq->skipKnownFrames()) { + if (reason == "end-stepping-range" || reason == "function-finished") { + GdbMi frame = data.findChild("frame"); + //qDebug() << frame.toString(); + m_currentFrame = frame.findChild("addr").data() + '%' + + frame.findChild("func").data() + '%'; + + QString funcName = frame.findChild("func").data(); + QString fileName = frame.findChild("file").data(); + if (isLeavableFunction(funcName, fileName)) { + //qDebug() << "LEAVING" << funcName; + ++stepCounter; + q->stepOutExec(); + //stepExec(); + return; + } + if (isSkippableFunction(funcName, fileName)) { + //qDebug() << "SKIPPING" << funcName; + ++stepCounter; + q->stepExec(); + return; + } + //if (stepCounter) + // qDebug() << "STEPCOUNTER:" << stepCounter; + stepCounter = 0; + } + } + + if (isStoppedReason(reason) || reason.isEmpty()) { + // Need another round trip + if (reason == "breakpoint-hit") { + GdbMi frame = data.findChild("frame"); + //qDebug() << frame.toString(); + m_currentFrame = frame.findChild("addr").data() + '%' + + frame.findChild("func").data() + '%'; + + QApplication::alert(q->mainWindow(), 200); + sendCommand("-file-list-exec-source-files", GdbQuerySources); + sendCommand("-break-list", BreakList); + QVariant var = QVariant::fromValue<GdbMi>(data); + sendCommand("p 0", GdbAsyncOutput2, var); // dummy + } else { + handleAsyncOutput2(data); + } + return; + } + + qDebug() << "STOPPED FOR UNKNOWN REASON" << data.toString(); + // Ignore it. Will be handled with full response later in the + // JumpToLine or RunToFunction handlers +#if 1 + // FIXME: remove this special case as soon as there's a real + // reason given when the temporary breakpoint is hit. + // reight now we get: + // 14*stopped,thread-id="1",frame={addr="0x0000000000403ce4", + // func="foo",args=[{name="str",value="@0x7fff0f450460"}], + // file="main.cpp",fullname="/tmp/g/main.cpp",line="37"} + // + // MAC yields sometimes: + // stdout:3661*stopped,time={wallclock="0.00658",user="0.00142", + // system="0.00136",start="1218810678.805432",end="1218810678.812011"} + q->resetLocation(); + qq->notifyInferiorStopped(); + q->showStatusMessage(tr("Run to Function finished. Stopped."), -1); + GdbMi frame = data.findChild("frame"); + QString file = frame.findChild("fullname").data(); + int line = frame.findChild("line").data().toInt(); + qDebug() << "HIT: " << file << line << " IN " << frame.toString() + << " -- " << data.toString(); + q->gotoLocation(file, line, true); +#endif +} + + +void GdbEngine::handleAsyncOutput2(const GdbMi &data) +{ + qq->notifyInferiorStopped(); + + // + // Breakpoints + // + //qDebug() << "BREAK ASYNC: " << output.toString(); + //sendListBreakpoints(); + //attemptBreakpointSynchronization(); + //if (m_breakListOnStopNeeded) + // sendListBreakpoints(); + + // something reasonably 'invariant' + // Linux: + //"79*stopped,reason="end-stepping-range",reason="breakpoint-hit",bkptno="1", + //thread-id="1",frame={addr="0x0000000000405d8f",func="run1", + //args=[{name="argc",value="1"},{name="argv",value="0x7fffb7c23058"}], + //file="test1.cpp",fullname="/home/apoenitz/dev/work/test1/test1.cpp" + //,line="261"}" + // Mac: (but only sometimes) + // "82*stopped,bkpt={number="0",type="step + // resume",disp="keep",enabled="y",addr="0x43127171",at="<Find:: + // Internal::FindToolWindow::invokeFindIncremental() + // +225>",thread="1",shlib="/Users/epreuss/dev/ide/main/bin/ + // workbench.app/Contents/PlugIns/Trolltech/libFind.1.0.0.dylib", + // frame="0xbfffd800",thread="1",times="1"}, + + // + // Stack + // + qq->stackHandler()->setCurrentIndex(0); + updateLocals(); // Quick shot + + int currentId = data.findChild("thread-id").data().toInt(); + sendSynchronizedCommand("-stack-list-frames", StackListFrames); + if (supportsThreads()) + sendSynchronizedCommand("-thread-list-ids", StackListThreads, currentId); + + // + // Disassembler + // + // Linux: + //"79*stopped,reason="end-stepping-range",reason="breakpoint-hit",bkptno="1", + //thread-id="1",frame={addr="0x0000000000405d8f",func="run1", + //args=[{name="argc",value="1"},{name="argv",value="0x7fffb7c23058"}], + //file="test1.cpp",fullname="/home/apoenitz/dev/work/test1/test1.cpp",line="261"}" + // Mac: (but only sometimes) + m_address = data.findChild("frame").findChild("addr").data(); + qq->reloadDisassembler(); + + // + // Registers + // + qq->reloadRegisters(); +} + +void GdbEngine::handleShowVersion(const GdbResultRecord &response) +{ + if (response.resultClass == GdbResultDone) { + m_gdbVersion = 100; + QString msg = response.data.findChild("consolestreamoutput").data(); + QRegExp supported("GNU gdb 6.[6789]"); + if (msg.indexOf(supported) == -1) { + QStringList list = msg.split("\n"); + while (list.size() > 2) + list.removeLast(); + msg = tr("The debugger you are using identifies itself as:") + + "<p><p>" + list.join("<br>") + "<p><p>" + + tr("This version is not officially supported by Qt Creator.\n" + "Debugging will most likely not work well.\n" + "Using gdb 6.7 or later is strongly recommended."); +#if 0 + // ugly, but 'Show again' check box... + static QErrorMessage *err = new QErrorMessage(m_mainWindow); + err->setMinimumSize(400, 300); + err->showMessage(msg); +#else + //QMessageBox::information(m_mainWindow, tr("Warning"), msg); +#endif + } + int pos = msg.indexOf("GNU gdb 6."); + if (pos != -1) { + m_gdbVersion = 600 + (msg.at(pos + 10).unicode() - '0') * 10; + //qDebug() << "GDB VERSION " << m_gdbVersion << msg; + } + } +} + +void GdbEngine::handleFileExecAndSymbols + (const GdbResultRecord &response) +{ + if (response.resultClass == GdbResultDone) { + //m_breakHandler->clearBreakMarkers(); + } else if (response.resultClass == GdbResultError) { + QString msg = response.data.findChild("msg").data(); + QMessageBox::critical(q->mainWindow(), tr("Error"), + tr("Starting executable failed:\n") + msg); + QWB_ASSERT(q->status() == DebuggerInferiorRunning, /**/); + interruptInferior(); + } +} + +void GdbEngine::handleExecRun(const GdbResultRecord &response) +{ + if (response.resultClass == GdbResultRunning) { + qq->notifyInferiorRunning(); + q->showStatusMessage(tr("Running..."), -1); + //reloadModules(); + } else if (response.resultClass == GdbResultError) { + QString msg = response.data.findChild("msg").data(); + if (msg == "Cannot find bounds of current function") { + qq->notifyInferiorStopped(); + //q->showStatusMessage(tr("No debug information available. " + // "Leaving function..."), -1); + //stepOutExec(); + } else { + QMessageBox::critical(q->mainWindow(), tr("Error"), + tr("Starting executable failed:\n") + msg); + QWB_ASSERT(q->status() == DebuggerInferiorRunning, /**/); + interruptInferior(); + } + } +} + +void GdbEngine::queryFullName(const QString &fileName, QString *full) +{ + *full = fullName(fileName); +} + +QString GdbEngine::shortName(const QString &fullName) +{ + return m_fullToShortName.value(fullName, QString()); +} + +QString GdbEngine::fullName(const QString &fileName) +{ + //QString absName = m_manager->currentWorkingDirectory() + "/" + file; ?? + if (fileName.isEmpty()) + return QString(); + QString full = m_shortToFullName.value(fileName, QString()); + //qDebug() << "RESOLVING: " << fileName << full; + if (!full.isEmpty()) + return full; + QFileInfo fi(fileName); + if (!fi.isReadable()) + return QString(); + full = fi.absoluteFilePath(); + #ifdef Q_OS_WIN + full = QDir::cleanPath(full); + #endif + //qDebug() << "STORING: " << fileName << full; + m_shortToFullName[fileName] = full; + m_fullToShortName[full] = fileName; + return full; +} + +QString GdbEngine::fullName(const QStringList &candidates) +{ + QString full; + foreach (const QString &fileName, candidates) { + full = fullName(fileName); + if (!full.isEmpty()) + return full; + } + foreach (const QString &fileName, candidates) { + if (!fileName.isEmpty()) + return fileName; + } + return full; +} + +void GdbEngine::shutdown() +{ + exitDebugger(); +} + +void GdbEngine::exitDebugger() +{ + //qDebug() << "EXITING: " << m_gdbProc.state(); + if (m_gdbProc.state() == QProcess::Starting) + m_gdbProc.waitForStarted(); + if (m_gdbProc.state() == QProcess::Running) { + interruptInferior(); + sendCommand("kill"); + sendCommand("-gdb-exit"); + // 20s can easily happen when loading webkit debug information + m_gdbProc.waitForFinished(20000); + if (m_gdbProc.state() != QProcess::Running) { + m_gdbProc.terminate(); + m_gdbProc.waitForFinished(20000); + } + } + if (m_gdbProc.state() != QProcess::NotRunning) + qDebug() << "PROBLEM STOPPING DEBUGGER"; + + m_shortToFullName.clear(); + m_fullToShortName.clear(); + m_varToType.clear(); + m_dataDumperState = DataDumperUninitialized; + m_shared = 0; + qq->debugDumpersAction()->setChecked(false); +} + + +int GdbEngine::currentFrame() const +{ + return qq->stackHandler()->currentIndex(); +} + + +bool GdbEngine::startDebugger() +{ + m_inferiorPid = 0; + QStringList gdbArgs; + + QFileInfo fi(q->m_executable); + QString fileName = '"' + fi.absoluteFilePath() + '"'; + + if (m_gdbProc.state() != QProcess::NotRunning) { + qDebug() << "GDB IS ALREADY RUNNING!"; + return false; + } + + //gdbArgs.prepend(QLatin1String("--quiet")); + gdbArgs.prepend(QLatin1String("mi")); + gdbArgs.prepend(QLatin1String("-i")); + + if (!q->m_workingDir.isEmpty()) + m_gdbProc.setWorkingDirectory(q->m_workingDir); + if (!q->m_environment.isEmpty()) + m_gdbProc.setEnvironment(q->m_environment); + + #if 0 + qDebug() << "Command: " << theGdbSettings().m_gdbCmd; + qDebug() << "WorkingDirectory: " << m_gdbProc.workingDirectory(); + qDebug() << "Environment: " << m_gdbProc.environment(); + qDebug() << "Arguments: " << gdbArgs; + qDebug() << "BuildDir: " << q->m_buildDir; + qDebug() << "ExeFile: " << q->m_executable; + #endif + + q->showStatusMessage("Starting Debugger", -1); + emit gdbInputAvailable(QString(), theGdbSettings().m_gdbCmd + ' ' + gdbArgs.join(" ")); + + m_gdbProc.start(theGdbSettings().m_gdbCmd, gdbArgs); + m_gdbProc.waitForStarted(); + + if (m_gdbProc.state() != QProcess::Running) + return false; + + q->showStatusMessage(tr("Gdb Running"), -1); + + sendCommand("show version", GdbShowVersion); + if (qq->useFastStart()) { + sendCommand("set auto-solib-add off"); + sendCommand("set stop-on-solib-events 1"); + } + //sendCommand("-enable-timings"); + //sendCommand("set stop-on-solib-events 1"); + sendCommand("set print static-members off"); // Seemingly doesn't work. + //sendCommand("define hook-stop\n-thread-list-ids\n-stack-list-frames\nend"); + //sendCommand("define hook-stop\nprint 4\nend"); + //sendCommand("define hookpost-stop\nprint 5\nend"); + //sendCommand("define hook-call\nprint 6\nend"); + //sendCommand("define hookpost-call\nprint 7\nend"); + //sendCommand("set print object on"); // works with CLI, but not MI + //sendCommand("set step-mode on"); // we can't work with that yes + //sendCommand("set exec-done-display on"); + //sendCommand("set print pretty on"); + //sendCommand("set confirm off"); + //sendCommand("set pagination off"); + sendCommand("set breakpoint pending on", BreakEnablePending); + + // one of the following is needed to prevent crashes in gdb on code like: + // template <class T> T foo() { return T(0); } + // int main() { return foo<int>(); } + // (gdb) call 'int foo<int>'() + // /build/buildd/gdb-6.8/gdb/valops.c:2069: internal-error: + sendCommand("set overload-resolution off"); + //sendCommand("set demangle-style none"); + + // From the docs: + // Stop means reenter debugger if this signal happens (implies print). + // Print means print a message if this signal happens. + // Pass means let program see this signal; + // otherwise program doesn't know. + // Pass and Stop may be combined. + // We need "print" as otherwise we would get no feedback whatsoever + // Custom Dumper crashs which happen regularily for when accessing + // uninitialized variables. + sendCommand("handle SIGSEGV nopass stop print"); + + // This is useful to kill the inferior whenever gdb dies. + //sendCommand("handle SIGTERM pass nostop print"); + + sendCommand("set unwindonsignal on"); + sendCommand("pwd", GdbQueryPwd); + + #ifdef Q_OS_MAC + sendCommand("-gdb-set inferior-auto-start-cfm off"); + sendCommand("-gdb-set sharedLibrary load-rules " + "dyld \".*libSystem.*\" " + "all dyld \".*libauto.*\" " + "all dyld \".*AppKit.*\" " + "all dyld \".*PBGDBIntrospectionSupport.*\" " + "all dyld \".*Foundation.*\" " + "all dyld \".*CFDataFormatters.*\" " + "all dyld \".*libobjc.*\" " + "all dyld \".*CarbonDataFormatters"); + #endif + + if (q->startMode() == q->attachExternal) { + sendCommand("attach " + QString::number(q->m_attachedPID)); + } + + if (q->startMode() == q->startInternal) { + sendCommand("-file-exec-and-symbols " + fileName, GdbFileExecAndSymbols); + #ifdef Q_OS_MAC + sendCommand("sharedlibrary apply-load-rules all"); + #endif + sendCommand("-file-list-exec-source-files", GdbQuerySources); + //sendCommand("-gdb-set stop-on-solib-events 1"); + } + + sendCommand("-data-list-register-names", RegisterListNames); + + // set all to "pending" + if (q->startMode() == q->attachExternal) + qq->breakHandler()->removeAllBreakpoints(); + else + qq->breakHandler()->setAllPending(); + + QTimer::singleShot(0, this, SLOT(attemptBreakpointSynchronization())); + + return true; +} + +void GdbEngine::continueInferior() +{ + q->resetLocation(); + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + emit gdbInputAvailable(QString(), QString()); + sendCommand("-exec-continue", GdbExecContinue); +} + +void GdbEngine::runInferior() +{ + q->resetLocation(); + // FIXME: this ignores important startup messages + setTokenBarrier(); + if (!q->m_processArgs.isEmpty()) + sendCommand("-exec-arguments " + q->m_processArgs.join(" ")); + qq->notifyInferiorRunningRequested(); + emit gdbInputAvailable(QString(), QString()); + sendCommand("-exec-run", GdbExecRun); +#if defined(Q_OS_WIN) + sendCommand("info proc", GdbInfoProc); +#endif +#if defined(Q_OS_LINUX) + sendCommand("info proc", GdbInfoProc); +#endif +#if defined(Q_OS_MAC) + sendCommand("info pid", GdbInfoProc, QVariant(), true); +#endif +} + +void GdbEngine::stepExec() +{ + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + emit gdbInputAvailable(QString(), QString()); + sendCommand("-exec-step", GdbExecStep); +} + +void GdbEngine::stepIExec() +{ + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + sendCommand("-exec-step-instruction", GdbExecStepI); +} + +void GdbEngine::stepOutExec() +{ + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + sendCommand("-exec-finish", GdbExecFinish); +} + +void GdbEngine::nextExec() +{ + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + emit gdbInputAvailable(QString(), QString()); + sendCommand("-exec-next", GdbExecNext); +} + +void GdbEngine::nextIExec() +{ + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + sendCommand("-exec-next-instruction", GdbExecNextI); +} + +void GdbEngine::runToLineExec(const QString &fileName, int lineNumber) +{ + setTokenBarrier(); + qq->notifyInferiorRunningRequested(); + sendCommand("-exec-until " + fileName + ":" + QString::number(lineNumber)); +} + +void GdbEngine::runToFunctionExec(const QString &functionName) +{ + setTokenBarrier(); + sendCommand("-break-insert -t " + functionName); + qq->notifyInferiorRunningRequested(); + sendCommand("-exec-continue", GdbExecRunToFunction); +} + +void GdbEngine::jumpToLineExec(const QString &fileName, int lineNumber) +{ +#if 1 + // not available everywhere? + //sendCliCommand("tbreak " + fileName + ":" + QString::number(lineNumber)); + sendCommand("-break-insert -t " + fileName + ":" + QString::number(lineNumber)); + sendCommand("jump " + fileName + ":" + QString::number(lineNumber)); + // will produce something like + // &"jump /home/apoenitz/dev/work/test1/test1.cpp:242" + // ~"Continuing at 0x4058f3." + // ~"run1 (argc=1, argv=0x7fffbf1f5538) at test1.cpp:242" + // ~"242\t x *= 2;" + // 23^done" + q->gotoLocation(fileName, lineNumber, true); + //setBreakpoint(); + //sendCommand("jump " + fileName + ":" + QString::number(lineNumber)); +#else + q->gotoLocation(fileName, lineNumber, true); + setBreakpoint(fileName, lineNumber); + sendCommand("jump " + fileName + ":" + QString::number(lineNumber)); +#endif +} + +/*! + \fn void GdbEngine::setTokenBarrier() + \brief Sets up internal structures to handle a new debugger turn. + + This method is called at the beginnign of all step/next/finish etc. + debugger functions. +*/ + +void GdbEngine::setTokenBarrier() +{ + m_oldestAcceptableToken = currentToken(); +} + +void GdbEngine::setDebugDumpers(bool on) +{ + if (on) { + qDebug() << "SWITCHING ON DUMPER DEBUGGING"; + sendCommand("set unwindonsignal off"); + q->breakByFunction("qDumpObjectData440"); + //updateLocals(); + } else { + qDebug() << "SWITCHING OFF DUMPER DEBUGGING"; + sendCommand("set unwindonsignal on"); + } +} + +//QByteArray GdbEngine::dumperChannel() const +//{ +// return m_dumperServer->serverName().toLatin1(); +// //QByteArray ba; +// //ba.setNum(m_dumperServer->serverPort()); +// //return ba; +//} + + +////////////////////////////////////////////////////////////////////// +// +// Breakpoint specific stuff +// +////////////////////////////////////////////////////////////////////// + +void GdbEngine::breakpointDataFromOutput(BreakpointData *data, const GdbMi &bkpt) +{ + if (!bkpt.isValid()) + return; + if (!data) + return; + data->pending = false; + data->bpMultiple = false; + data->bpCondition.clear(); + QStringList files; + foreach (const GdbMi &child, bkpt.children()) { + if (child.hasName("number")) { + data->bpNumber = child.data(); + } else if (child.hasName("func")) { + data->bpFuncName = child.data(); + } else if (child.hasName("addr")) { + // <MULTIPLE> happens in constructors. In this case there are + // _two_ fields named "addr" in the response. On Linux that is... + if (child.data() == "<MULTIPLE>") + data->bpMultiple = true; + else + data->bpAddress = child.data(); + } else if (child.hasName("file")) { + files.append(child.data()); + } else if (child.hasName("fullname")) { + QString fullName = child.data(); + #ifdef Q_OS_WIN + fullName = QDir::cleanPath(fullName); + #endif + files.prepend(fullName); + } else if (child.hasName("line")) { + data->bpLineNumber = child.data(); + if (child.data().toInt()) + data->markerLineNumber = child.data().toInt(); + } else if (child.hasName("cond")) { + data->bpCondition = child.data(); + // gdb 6.3 likes to "rewrite" conditions. Just accept that fact. + if (data->bpCondition != data->condition && data->conditionsMatch()) + data->condition = data->bpCondition; + } + else if (child.hasName("pending")) { + data->pending = true; + int pos = child.data().lastIndexOf(':'); + if (pos > 0) { + data->bpLineNumber = child.data().mid(pos + 1); + data->markerLineNumber = child.data().mid(pos + 1).toInt(); + files.prepend(child.data().left(pos)); + } else { + files.prepend(child.data()); + } + } + } + // This field is not present. Contents needs to be parsed from + // the plain "ignore" response. + //else if (child.hasName("ignore")) + // data->bpIgnoreCount = child.data(); + + QString name = fullName(files); + if (data->bpFileName.isEmpty()) + data->bpFileName = name; + if (data->markerFileName.isEmpty()) + data->markerFileName = name; +} + +void GdbEngine::sendInsertBreakpoint(int index) +{ + const BreakpointData *data = qq->breakHandler()->at(index); + QString where; + if (data->funcName.isEmpty()) { + where = data->fileName; +#ifdef Q_OS_MAC + // full names do not work on Mac/MI + QFileInfo fi(data->fileName); + where = fi.fileName(); + //where = fi.absoluteFilePath(); +#endif +#ifdef Q_OS_WIN + // full names do not work on Mac/MI + QFileInfo fi(data->fileName); + where = fi.fileName(); + //where = m_manager->shortName(data->fileName); + //if (where.isEmpty()) + // where = data->fileName; +#endif + // we need something like "\"file name.cpp\":100" to + // survive the gdb command line parser with file names intact + where = "\"\\\"" + where + "\\\":" + data->lineNumber + "\""; + } else { + where = data->funcName; + } + + // set up fallback in case of pending breakpoints which aren't handled + // by the MI interface +#ifdef Q_OS_LINUX + QString cmd = "-break-insert "; + //if (!data->condition.isEmpty()) + // cmd += "-c " + data->condition + " "; + cmd += where; +#endif +#ifdef Q_OS_MAC + QString cmd = "-break-insert -l -1 "; + //if (!data->condition.isEmpty()) + // cmd += "-c " + data->condition + " "; + cmd += where; +#endif +#ifdef Q_OS_WIN + QString cmd = "-break-insert "; + //if (!data->condition.isEmpty()) + // cmd += "-c " + data->condition + " "; + cmd += where; +#endif + sendCommand(cmd, BreakInsert, index, true); + //processQueueAndContinue(); +} + +void GdbEngine::handleBreakList(const GdbResultRecord &record) +{ + // 45^done,BreakpointTable={nr_rows="2",nr_cols="6",hdr=[ + // {width="3",alignment="-1",col_name="number",colhdr="Num"}, ... + // body=[bkpt={number="1",type="breakpoint",disp="keep",enabled="y", + // addr="0x000000000040109e",func="main",file="app.cpp", + // fullname="/home/apoenitz/dev/work/plugintest/app/app.cpp", + // line="11",times="1"}, + // bkpt={number="2",type="breakpoint",disp="keep",enabled="y", + // addr="<PENDING>",pending="plugin.cpp:7",times="0"}] ... } + + if (record.resultClass == GdbResultDone) { + GdbMi table = record.data.findChild("BreakpointTable"); + if (table.isValid()) + handleBreakList(table); + } +} + +void GdbEngine::handleBreakList(const GdbMi &table) +{ + //qDebug() << "GdbEngine::handleOutput: table: " + // << table.toString(); + GdbMi body = table.findChild("body"); + //qDebug() << "GdbEngine::handleOutput: body: " + // << body.toString(); + QList<GdbMi> bkpts; + if (body.isValid()) { + // Non-Mac + bkpts = body.children(); + } else { + // Mac + bkpts = table.children(); + // remove the 'hdr' and artificial items + //qDebug() << "FOUND " << bkpts.size() << " BREAKPOINTS"; + for (int i = bkpts.size(); --i >= 0; ) { + int num = bkpts.at(i).findChild("number").data().toInt(); + if (num <= 0) { + //qDebug() << "REMOVING " << i << bkpts.at(i).toString(); + bkpts.removeAt(i); + } + } + //qDebug() << "LEFT " << bkpts.size() << " BREAKPOINTS"; + } + + BreakHandler *handler = qq->breakHandler(); + for (int index = 0; index != bkpts.size(); ++index) { + BreakpointData temp(handler); + breakpointDataFromOutput(&temp, bkpts.at(index)); + int found = handler->findBreakpoint(temp); + if (found != -1) + breakpointDataFromOutput(handler->at(found), bkpts.at(index)); + //else + //qDebug() << "CANNOT HANDLE RESPONSE " << bkpts.at(index).toString(); + } + + attemptBreakpointSynchronization(); + handler->updateMarkers(); +} + + +void GdbEngine::handleBreakIgnore(const GdbResultRecord &record, int index) +{ + // gdb 6.8: + // ignore 2 0: + // ~"Will stop next time breakpoint 2 is reached.\n" + // 28^done + // ignore 2 12: + // &"ignore 2 12\n" + // ~"Will ignore next 12 crossings of breakpoint 2.\n" + // 29^done + // + // gdb 6.3 does not produce any console output + BreakHandler *handler = qq->breakHandler(); + if (record.resultClass == GdbResultDone && index < handler->size()) { + QString msg = record.data.findChild("consolestreamoutput").data(); + BreakpointData *data = handler->at(index); + //if (msg.contains("Will stop next time breakpoint")) { + // data->bpIgnoreCount = "0"; + //} else if (msg.contains("Will ignore next")) { + // data->bpIgnoreCount = data->ignoreCount; + //} + // FIXME: this assumes it is doing the right thing... + data->bpIgnoreCount = data->ignoreCount; + attemptBreakpointSynchronization(); + handler->updateMarkers(); + } +} + +void GdbEngine::handleBreakCondition(const GdbResultRecord &record, int index) +{ + BreakHandler *handler = qq->breakHandler(); + if (record.resultClass == GdbResultDone) { + // we just assume it was successful. otherwise we had to parse + // the output stream data + BreakpointData *data = handler->at(index); + //qDebug() << "HANDLE BREAK CONDITION " << index << data->condition; + data->bpCondition = data->condition; + attemptBreakpointSynchronization(); + handler->updateMarkers(); + } else if (record.resultClass == GdbResultError) { + QString msg = record.data.findChild("msg").data(); + // happens on Mac + if (1 || msg.startsWith("Error parsing breakpoint condition. " + " Will try again when we hit the breakpoint.")) { + BreakpointData *data = handler->at(index); + //qDebug() << "ERROR BREAK CONDITION " << index << data->condition; + data->bpCondition = data->condition; + attemptBreakpointSynchronization(); + handler->updateMarkers(); + } + } +} + +void GdbEngine::handleBreakInsert(const GdbResultRecord &record, int index) +{ + BreakHandler *handler = qq->breakHandler(); + if (record.resultClass == GdbResultDone) { + //qDebug() << "HANDLE BREAK INSERT " << index; +//#ifdef Q_OS_MAC + // interesting only on Mac? + BreakpointData *data = handler->at(index); + GdbMi bkpt = record.data.findChild("bkpt"); + //qDebug() << "BKPT: " << bkpt.toString() << " DATA" << data->toToolTip(); + breakpointDataFromOutput(data, bkpt); +//#endif + attemptBreakpointSynchronization(); + handler->updateMarkers(); + } else if (record.resultClass == GdbResultError) { + const BreakpointData *data = handler->at(index); +#ifdef Q_OS_LINUX + //QString where = "\"\\\"" + data->fileName + "\\\":" + // + data->lineNumber + "\""; + QString where = "\"" + data->fileName + "\":" + + data->lineNumber; + sendCommand("break " + where, BreakInsert1, index); +#endif +#ifdef Q_OS_MAC + QFileInfo fi(data->fileName); + QString where = "\"" + fi.fileName() + "\":" + + data->lineNumber; + sendCommand("break " + where, BreakInsert1, index); +#endif +#ifdef Q_OS_WIN + QFileInfo fi(data->fileName); + QString where = "\"" + fi.fileName() + "\":" + + data->lineNumber; + //QString where = m_data->fileName + QLatin1Char(':') + data->lineNumber; + sendCommand("break " + where, BreakInsert1, index); +#endif + } +} + +void GdbEngine::extractDataFromInfoBreak(const QString &output, BreakpointData *data) +{ + data->bpFileName = "<MULTIPLE>"; + + //qDebug() << output; + if (output.isEmpty()) + return; + // "Num Type Disp Enb Address What + // 4 breakpoint keep y <MULTIPLE> 0x00000000004066ad + // 4.1 y 0x00000000004066ad in CTorTester + // at /data5/dev/ide/main/tests/manual/gdbdebugger/simple/app.cpp:124 + // - or - + // everything on a single line on Windows for constructors of classes + // within namespaces. + // Sometimes the path is relative too. + + QRegExp re("MULTIPLE.*(0x[0-9a-f]+) in (.*)\\s+at (.*):([\\d]+)([^\\d]|$)"); + re.setMinimal(true); + + if (re.indexIn(output) != -1) { + data->bpAddress = re.cap(1); + data->bpFuncName = re.cap(2).trimmed(); + data->bpLineNumber = re.cap(4); + QString full = fullName(re.cap(3)); + data->markerLineNumber = data->bpLineNumber.toInt(); + data->markerFileName = full; + data->bpFileName = full; + //qDebug() << "FOUND BREAKPOINT\n" << output + // << re.cap(1) << "\n" << re.cap(2) << "\n" + // << re.cap(3) << "\n" << re.cap(4) << "\n"; + } else { + qDebug() << "COULD NOT MATCH " << re.pattern() << " AND " << output; + data->bpNumber = "<unavailable>"; + } +} + +void GdbEngine::handleBreakInfo(const GdbResultRecord &record, int bpNumber) +{ + BreakHandler *handler = qq->breakHandler(); + if (record.resultClass == GdbResultDone) { + // Old-style output for multiple breakpoints, presumably in a + // constructor + int found = handler->findBreakpoint(bpNumber); + if (found != -1) { + QString str = record.data.findChild("consolestreamoutput").data(); + extractDataFromInfoBreak(str, handler->at(found)); + handler->updateMarkers(); + attemptBreakpointSynchronization(); // trigger "ready" + } + } +} + +void GdbEngine::handleBreakInsert1(const GdbResultRecord &record, int index) +{ + BreakHandler *handler = qq->breakHandler(); + if (record.resultClass == GdbResultDone) { + // Pending breakpoints in dylibs on Mac only? + BreakpointData *data = handler->at(index); + GdbMi bkpt = record.data.findChild("bkpt"); + breakpointDataFromOutput(data, bkpt); + attemptBreakpointSynchronization(); // trigger "ready" + handler->updateMarkers(); + } else if (record.resultClass == GdbResultError) { + qDebug() << "INSERTING BREAKPOINT WITH BASE NAME FAILED. GIVING UP"; + BreakpointData *data = handler->at(index); + data->bpNumber = "<unavailable>"; + attemptBreakpointSynchronization(); // trigger "ready" + handler->updateMarkers(); + } +} + +void GdbEngine::attemptBreakpointSynchronization() +{ + BreakHandler *handler = qq->breakHandler(); + //qDebug() << "BREAKPOINT SYNCHRONIZATION "; + + foreach (BreakpointData *data, handler->takeRemovedBreakpoints()) { + //qDebug() << " SYNCHRONIZATION REMOVING" << data; + QString bpNumber = data->bpNumber; + if (!bpNumber.trimmed().isEmpty()) + sendCommand("-break-delete " + bpNumber, BreakDelete, 0, true); + //else + // qDebug() << "BP HAS NO NUMBER: " << data->markerFileName; + delete data; + } + + bool updateNeeded = false; + + for (int index = 0; index != handler->size(); ++index) { + BreakpointData *data = handler->at(index); + // multiple breakpoints? + if (data->bpMultiple && data->bpFileName.isEmpty()) { + sendCommand(QString("info break %1").arg(data->bpNumber), + BreakInfo, data->bpNumber.toInt()); + updateNeeded = true; + break; + } + } + + for (int index = 0; index != handler->size(); ++index) { + BreakpointData *data = handler->at(index); + // unset breakpoints? + if (data->bpNumber.isEmpty()) { + data->bpNumber = " "; + sendInsertBreakpoint(index); + //qDebug() << "UPDATE NEEDED BECAUSE OF UNKNOWN BREAKPOINT"; + updateNeeded = true; + break; + } + } + + if (!updateNeeded) { + for (int index = 0; index != handler->size(); ++index) { + BreakpointData *data = handler->at(index); + // update conditions if needed + if (data->bpNumber.toInt() && data->condition != data->bpCondition + && !data->conditionsMatch()) { + sendCommand(QString("condition %1 %2").arg(data->bpNumber) + .arg(data->condition), BreakCondition, index); + //qDebug() << "UPDATE NEEDED BECAUSE OF CONDITION" + // << data->condition << data->bpCondition; + updateNeeded = true; + break; + } + // update ignorecount if needed + if (data->bpNumber.toInt() && data->ignoreCount != data->bpIgnoreCount) { + sendCommand(QString("ignore %1 %2").arg(data->bpNumber) + .arg(data->ignoreCount), BreakIgnore, index); + updateNeeded = true; + break; + } + } + } + + for (int index = 0; index != handler->size(); ++index) { + // happens sometimes on Mac. Brush over symptoms + BreakpointData *data = handler->at(index); + if (data->markerFileName.startsWith("../")) { + data->markerFileName = fullName(data->markerFileName); + handler->updateMarkers(); + } + } + + if (updateNeeded) { + //interruptAndContinue(); + //sendListBreakpoints(); + } + + if (!updateNeeded && q->status() == DebuggerProcessStartingUp) + qq->notifyStartupFinished(); +} + + +////////////////////////////////////////////////////////////////////// +// +// Disassembler specific stuff +// +////////////////////////////////////////////////////////////////////// + +void GdbEngine::reloadDisassembler() +{ + emit sendCommand("disassemble", DisassemblerList, m_address); +} + +void GdbEngine::handleDisassemblerList(const GdbResultRecord &record, + const QString &cookie) +{ + QList<DisassemblerLine> lines; + static const QString pad = QLatin1String(" "); + int currentLine = -1; + if (record.resultClass == GdbResultDone) { + QString res = record.data.findChild("consolestreamoutput").data(); + QTextStream ts(&res, QIODevice::ReadOnly); + while (!ts.atEnd()) { + //0x0000000000405fd8 <_ZN11QTextStreamD1Ev@plt+0>: + // jmpq *2151890(%rip) # 0x6135b0 <_GLOBAL_OFFSET_TABLE_+640> + //0x0000000000405fde <_ZN11QTextStreamD1Ev@plt+6>: + // pushq $0x4d + //0x0000000000405fe3 <_ZN11QTextStreamD1Ev@plt+11>: + // jmpq 0x405af8 <_init+24> + //0x0000000000405fe8 <_ZN9QHashData6rehashEi@plt+0>: + // jmpq *2151882(%rip) # 0x6135b8 <_GLOBAL_OFFSET_TABLE_+648> + QString str = ts.readLine(); + if (!str.startsWith(QLatin1String("0x"))) { + //qDebug() << "IGNORING DISASSEMBLER" << str; + continue; + } + DisassemblerLine line; + QTextStream ts(&str, QIODevice::ReadOnly); + ts >> line.address >> line.symbol; + line.mnemonic = ts.readLine().trimmed(); + if (line.symbol.endsWith(QLatin1Char(':'))) + line.symbol.chop(1); + line.addressDisplay = line.address + pad; + if (line.addressDisplay.startsWith(QLatin1String("0x00000000"))) + line.addressDisplay.replace(2, 8, QString()); + line.symbolDisplay = line.symbol + pad; + + if (line.address == cookie) + currentLine = lines.size(); + + lines.append(line); + } + } else { + DisassemblerLine line; + line.addressDisplay = tr("<could not retreive module information>"); + lines.append(line); + } + + qq->disassemblerHandler()->setLines(lines); + if (currentLine != -1) + qq->disassemblerHandler()->setCurrentLine(currentLine); +} + + +////////////////////////////////////////////////////////////////////// +// +// Modules specific stuff +// +////////////////////////////////////////////////////////////////////// + +void GdbEngine::loadSymbols(const QString &moduleName) +{ + // FIXME: gdb does not understand quoted names here (tested with 6.8) + sendCommand("sharedlibrary " + dotEscape(moduleName)); + reloadModules(); +} + +void GdbEngine::loadAllSymbols() +{ + sendCommand("sharedlibrary .*"); + reloadModules(); +} + +void GdbEngine::reloadModules() +{ + sendCommand("info shared", ModulesList, QVariant()); +} + +void GdbEngine::handleModulesList(const GdbResultRecord &record) +{ + QList<Module> modules; + if (record.resultClass == GdbResultDone) { + QString data = record.data.findChild("consolestreamoutput").data(); + QTextStream ts(&data, QIODevice::ReadOnly); + while (!ts.atEnd()) { + QString line = ts.readLine(); + if (!line.startsWith("0x")) + continue; + Module module; + QString symbolsRead; + QTextStream ts(&line, QIODevice::ReadOnly); + ts >> module.startAddress >> module.endAddress >> symbolsRead; + module.moduleName = ts.readLine().trimmed(); + module.symbolsRead = (symbolsRead == "Yes"); + modules.append(module); + } + } + qq->modulesHandler()->setModules(modules); +} + + +////////////////////////////////////////////////////////////////////// +// +// Stack specific stuff +// +////////////////////////////////////////////////////////////////////// + +void GdbEngine::handleStackSelectThread(const GdbResultRecord &record, int) +{ + Q_UNUSED(record); + qDebug("FIXME: StackHandler::handleOutput: SelectThread"); + q->showStatusMessage(tr("Retrieving data for stack view..."), -1); + sendCommand("-stack-list-frames", StackListFrames); +} + + +void GdbEngine::handleStackListFrames(const GdbResultRecord &record) +{ + QList<StackFrame> stackFrames; + + const GdbMi stack = record.data.findChild("stack"); + QString dummy = stack.toString(); + if (!stack.isValid()) { + qDebug() << "FIXME: stack: " << stack.toString(); + return; + } + + int topFrame = -1; + + for (int i = 0; i != stack.childCount(); ++i) { + //qDebug() << "HANDLING FRAME: " << stack.childAt(i).toString(); + const GdbMi frameMi = stack.childAt(i); + StackFrame frame; + frame.level = i; + QStringList files; + files.append(frameMi.findChild("fullname").data()); + files.append(frameMi.findChild("file").data()); + frame.file = fullName(files); + frame.function = frameMi.findChild("func").data(); + frame.from = frameMi.findChild("from").data(); + frame.line = frameMi.findChild("line").data().toInt(); + frame.address = frameMi.findChild("addr").data(); + + stackFrames.append(frame); + +#ifdef Q_OS_WIN + const bool isBogus = + // Assume this is wrong and points to some strange stl_algobase + // implementation. Happens on Karsten's XP system with Gdb 5.50 + (frame.file.endsWith("/bits/stl_algobase.h") && frame.line == 150) + // Also wrong. Happens on Vista with Gdb 5.50 + || (frame.function == "operator new" && frame.line == 151); + + // immediately leave bogus frames + if (topFrame == -1 && isBogus) { + sendCommand("-exec-finish"); + return; + } + +#endif + + // Initialize top frame to the first valid frame + const bool isValid = !frame.file.isEmpty() && !frame.function.isEmpty(); + if (isValid && topFrame == -1) + topFrame = i; + } + + qq->stackHandler()->setFrames(stackFrames); + +#if 0 + if (0 && topFrame != -1) { + // updates of locals already triggered early + const StackFrame &frame = qq->stackHandler()->currentFrame(); + bool usable = !frame.file.isEmpty() && QFileInfo(frame.file).isReadable(); + if (usable) + q->gotoLocation(frame.file, frame.line, true); + else + qDebug() << "FULL NAME NOT USABLE 0: " << frame.file; + } else { + activateFrame(topFrame); + } +#else + if (topFrame != -1) { + // updates of locals already triggered early + const StackFrame &frame = qq->stackHandler()->currentFrame(); + bool usable = !frame.file.isEmpty() && QFileInfo(frame.file).isReadable(); + if (usable) + q->gotoLocation(frame.file, frame.line, true); + else + qDebug() << "FULL NAME NOT USABLE 0: " << frame.file; + } +#endif +} + +void GdbEngine::selectThread(int index) +{ + //reset location arrow + q->resetLocation(); + + ThreadsHandler *threadsHandler = qq->threadsHandler(); + threadsHandler->setCurrentThread(index); + + QList<ThreadData> threads = threadsHandler->threads(); + QWB_ASSERT(index < threads.size(), return); + int id = threads.at(index).id; + q->showStatusMessage(tr("Retrieving data for stack view..."), -1); + sendCommand(QLatin1String("-thread-select ") + QString::number(id), + StackSelectThread); +} + +void GdbEngine::activateFrame(int frameIndex) +{ + if (q->status() != DebuggerInferiorStopped) + return; + + StackHandler *stackHandler = qq->stackHandler(); + int oldIndex = stackHandler->currentIndex(); + //qDebug() << "ACTIVATE FRAME: " << frameIndex << oldIndex + // << stackHandler->currentIndex(); + + QWB_ASSERT(frameIndex < stackHandler->stackSize(), return); + + if (oldIndex != frameIndex) { + // Assuming this always succeeds saves a roundtrip. + // Otherwise the lines below would need to get triggered + // after a response to this -stack-select-frame here. + sendCommand("-stack-select-frame " + QString::number(frameIndex)); + + stackHandler->setCurrentIndex(frameIndex); + updateLocals(); + } + + const StackFrame &frame = stackHandler->currentFrame(); + + bool usable = !frame.file.isEmpty() && QFileInfo(frame.file).isReadable(); + if (usable) + q->gotoLocation(frame.file, frame.line, true); + else + qDebug() << "FULL NAME NOT USABLE: " << frame.file; +} + +void GdbEngine::handleStackListThreads(const GdbResultRecord &record, int id) +{ + // "72^done,{thread-ids={thread-id="2",thread-id="1"},number-of-threads="2"} + const QList<GdbMi> items = record.data.findChild("thread-ids").children(); + QList<ThreadData> threads; + int currentIndex = -1; + for (int index = 0, n = items.size(); index != n; ++index) { + ThreadData thread; + thread.id = items.at(index).data().toInt(); + threads.append(thread); + if (thread.id == id) { + //qDebug() << "SETTING INDEX TO: " << index << " ID: "<< id << "RECOD: "<< record.toString(); + currentIndex = index; + } + } + ThreadsHandler *threadsHandler = qq->threadsHandler(); + threadsHandler->setThreads(threads); + threadsHandler->setCurrentThread(currentIndex); +} + + +////////////////////////////////////////////////////////////////////// +// +// Register specific stuff +// +////////////////////////////////////////////////////////////////////// + +void GdbEngine::reloadRegisters() +{ + QString format = qq->registerHandler()->model()->property(PROPERTY_REGISTER_FORMAT).toString(); + sendCommand("-data-list-register-values " + format, RegisterListValues); +} + +void GdbEngine::handleRegisterListNames(const GdbResultRecord &record) +{ + if (record.resultClass != GdbResultDone) + return; + + QList<Register> registers; + foreach (const GdbMi &item, record.data.findChild("register-names").children()) + registers.append(Register(item.data())); + + qq->registerHandler()->setRegisters(registers); +} + +void GdbEngine::handleRegisterListValues(const GdbResultRecord &record) +{ + if (record.resultClass != GdbResultDone) + return; + + QList<Register> registers = qq->registerHandler()->registers(); + + // 24^done,register-values=[{number="0",value="0xf423f"},...] + foreach (const GdbMi &item, record.data.findChild("register-values").children()) { + int index = item.findChild("number").data().toInt(); + if (index < registers.size()) { + Register ® = registers[index]; + QString value = item.findChild("value").data(); + reg.changed = (value != reg.value); + if (reg.changed) + reg.value = value; + } + } + qq->registerHandler()->setRegisters(registers); +} + + +////////////////////////////////////////////////////////////////////// +// +// Thread specific stuff +// +////////////////////////////////////////////////////////////////////// + +bool GdbEngine::supportsThreads() const +{ + // 6.3 crashes happily on -thread-list-ids. So don't use it. + // The test below is a semi-random pick, 6.8 works fine + return m_gdbVersion > 650; +} + +////////////////////////////////////////////////////////////////////// +// +// Tooltip specific stuff +// +////////////////////////////////////////////////////////////////////// + +static WatchData m_toolTip; +static QString m_toolTipExpression; +static QPoint m_toolTipPos; +static QHash<QString, WatchData> m_toolTipCache; + +static bool hasLetterOrNumber(const QString &exp) +{ + for (int i = exp.size(); --i >= 0; ) + if (exp[i].isLetterOrNumber() || exp[i] == '_') + return true; + return false; +} + +static bool hasSideEffects(const QString &exp) +{ + // FIXME: complete? + return exp.contains("-=") + || exp.contains("+=") + || exp.contains("/=") + || exp.contains("*=") + || exp.contains("&=") + || exp.contains("|=") + || exp.contains("^=") + || exp.contains("--") + || exp.contains("++"); +} + +static bool isKeyWord(const QString &exp) +{ + // FIXME: incomplete + return exp == QLatin1String("class") + || exp == QLatin1String("const") + || exp == QLatin1String("do") + || exp == QLatin1String("if") + || exp == QLatin1String("return") + || exp == QLatin1String("struct") + || exp == QLatin1String("template") + || exp == QLatin1String("void") + || exp == QLatin1String("volatile") + || exp == QLatin1String("while"); +} + +void GdbEngine::setToolTipExpression(const QPoint &pos, const QString &exp0) +{ + //qDebug() << "SET TOOLTIP EXP" << pos << exp0; + if (q->status() != DebuggerInferiorStopped) { + //qDebug() << "SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED"; + return; + } + + if (qq->debugDumpersAction()->isChecked()) { + // minimize interference + return; + } + + m_toolTipPos = pos; + m_toolTipExpression = exp0; + QString exp = exp0; +/* + if (m_toolTip.isTypePending()) { + qDebug() << "suppressing duplicated tooltip creation"; + return; + } +*/ + if (m_toolTipCache.contains(exp)) { + const WatchData & data = m_toolTipCache[exp]; + // FIXME: qq->watchHandler()->collapseChildren(data.iname); + insertData(data); + return; + } + + QToolTip::hideText(); + if (exp.isEmpty() || exp.startsWith("#")) { + QToolTip::hideText(); + return; + } + + if (!hasLetterOrNumber(exp)) { + QToolTip::showText(m_toolTipPos, + "'" + exp + "' contains no identifier"); + return; + } + + if (isKeyWord(exp)) + return; + + if (exp.startsWith('"') && exp.endsWith('"')) { + QToolTip::showText(m_toolTipPos, "String literal " + exp); + return; + } + + if (exp.startsWith("++") || exp.startsWith("--")) + exp = exp.mid(2); + + if (exp.endsWith("++") || exp.endsWith("--")) + exp = exp.mid(2); + + if (exp.startsWith("<") || exp.startsWith("[")) + return; + + if (hasSideEffects(exp)) { + QToolTip::showText(m_toolTipPos, + "Cowardly refusing to evaluate expression '" + exp + + "' with potential side effects"); + return; + } + + // Gdb crashes when creating a variable object with the name + // of the type of 'this' +/* + for (int i = 0; i != m_currentLocals.childCount(); ++i) { + if (m_currentLocals.childAt(i).exp == "this") { + qDebug() << "THIS IN ROW " << i; + if (m_currentLocals.childAt(i).type.startsWith(exp)) { + QToolTip::showText(m_toolTipPos, + exp + ": type of current 'this'"); + qDebug() << " TOOLTIP CRASH SUPPRESSED"; + return; + } + break; + } + } +*/ + + //if (m_manager->status() != DebuggerInferiorStopped) + // return; + + // FIXME: 'exp' can contain illegal characters + m_toolTip = WatchData(); + //m_toolTip.level = 0; + // m_toolTip.row = 0; + // m_toolTip.parentIndex = 2; + m_toolTip.exp = exp; + m_toolTip.name = exp; + m_toolTip.iname = tooltipIName; + insertData(m_toolTip); + updateWatchModel2(); +} + + +////////////////////////////////////////////////////////////////////// +// +// Watch specific stuff +// +////////////////////////////////////////////////////////////////////// + +static const QString strNotInScope = QLatin1String("<not in scope>"); + +static bool isPointerType(const QString &type) +{ + return type.endsWith("*") || type.endsWith("* const"); +} + +static bool isAccessSpecifier(const QString &str) +{ + static const QStringList items = + QStringList() << "private" << "protected" << "public"; + return items.contains(str); +} + +static bool startsWithDigit(const QString &str) +{ + return !str.isEmpty() && str[0] >= '0' && str[0] <= '9'; +} + +QString stripPointerType(QString type) +{ + if (type.endsWith("*")) + type.chop(1); + if (type.endsWith("* const")) + type.chop(7); + if (type.endsWith(' ')) + type.chop(1); + return type; +} + +static QString gdbQuoteTypes(const QString &type) +{ + // gdb does not understand sizeof(Core::IFile*). + // "sizeof('Core::IFile*')" is also not acceptable, + // it needs to be "sizeof('Core::IFile'*)" + // + // We never will have a perfect solution here (even if we had a full blown + // C++ parser as we do not have information on what is a type and what is + // a vriable name. So "a<b>::c" could either be two comparisons of values + // 'a', 'b' and '::c', or a nested type 'c' in a template 'a<b>'. We + // assume here it is the latter. + //return type; + + // (*('myns::QPointer<myns::QObject>*'*)0x684060)" is not acceptable + // (*('myns::QPointer<myns::QObject>'**)0x684060)" is acceptable + if (isPointerType(type)) + return gdbQuoteTypes(stripPointerType(type)) + "*"; + + QString accu; + QString result; + int templateLevel = 0; + for (int i = 0; i != type.size(); ++i) { + QChar c = type.at(i); + if (c.isLetterOrNumber() || c == '_' || c == ':' || c == ' ') { + accu += c; + } else if (c == '<') { + ++templateLevel; + accu += c; + } else if (c == '<') { + --templateLevel; + accu += c; + } else if (templateLevel > 0) { + accu += c; + } else { + if (accu.contains(':') || accu.contains('<')) + result += '\'' + accu + '\''; + else + result += accu; + accu.clear(); + result += c; + } + } + if (accu.contains(':') || accu.contains('<')) + result += '\'' + accu + '\''; + else + result += accu; + //qDebug() << "GDB_QUOTING" << type << " TO " << result; + + return result; +} + +static void setWatchDataValue(WatchData &data, const GdbMi &mi, + int encoding = 0) +{ + if (mi.isValid()) { + QByteArray ba; + switch (encoding) { + case 0: // unencoded 8 bit data + ba = mi.data(); + break; + case 1: // base64 encoded 8 bit data + ba = QByteArray::fromBase64(mi.data()); + break; + case 2: // base64 encoded 16 bit data + ba = QByteArray::fromBase64(mi.data()); + ba = QString::fromUtf16((ushort *)ba.data(), ba.size() / 2).toUtf8(); + break; + case 3: // base64 encoded 32 bit data + ba = QByteArray::fromBase64(mi.data()); + ba = QString::fromUcs4((uint *)ba.data(), ba.size() / 4).toUtf8(); + break; + } + data.setValue(ba); + } else { + data.setValueNeeded(); + } +} + +static void setWatchDataEditValue(WatchData &data, const GdbMi &mi) +{ + if (mi.isValid()) + data.editvalue = mi.data(); +} + +static void setWatchDataValueToolTip(WatchData &data, const GdbMi &mi) +{ + if (mi.isValid()) + data.setValueToolTip(mi.data()); +} + +static void setWatchDataChildCount(WatchData &data, const GdbMi &mi) +{ + if (mi.isValid()) { + data.childCount = mi.data().toInt(); + data.setChildCountUnneeded(); + if (data.childCount == 0) + data.setChildrenUnneeded(); + } else { + data.childCount = -1; + } +} + +static void setWatchDataValueDisabled(WatchData &data, const GdbMi &mi) +{ + if (mi.data() == "true") + data.valuedisabled = true; + else if (mi.data() == "false") + data.valuedisabled = false; +} + +static void setWatchDataExpression(WatchData &data, const GdbMi &mi) +{ + if (mi.isValid()) + data.exp = "(" + mi.data() + ")"; +} + +static void setWatchDataAddress(WatchData &data, const GdbMi &mi) +{ + if (mi.isValid()) { + data.addr = mi.data(); + if (data.exp.isEmpty()) + data.exp = "(*(" + gdbQuoteTypes(data.type) + "*)" + data.addr + ")"; + } +} + +static bool extractTemplate(const QString &type, QString *tmplate, QString *inner) +{ + // Input "Template<Inner1,Inner2,...>::Foo" will return "Template::Foo" in + // 'tmplate' and "Inner1@Inner2@..." etc in 'inner'. Result indicates + // whether parsing was successful + int level = 0; + for (int i = 0; i != type.size(); ++i) { + QChar c = type[i]; + if (c == '<') { + *(level == 0 ? tmplate : inner) += c; + ++level; + } else if (c == '>') { + --level; + *(level == 0 ? tmplate : inner) += c; + } else if (c == ',') { + *inner += (level == 1) ? '@' : ','; + } else { + *(level == 0 ? tmplate : inner) += c; + } + } + *tmplate = tmplate->trimmed(); + *tmplate = tmplate->remove("<>"); + *inner = inner->trimmed(); + //qDebug() << "EXTRACT TEMPLATE: " << *tmplate << *inner; + return !inner->isEmpty(); +} + +static QString extractTypeFromPTypeOutput(const QString &str) +{ + int pos0 = str.indexOf('='); + int pos1 = str.indexOf('{'); + int pos2 = str.lastIndexOf('}'); + QString res = str; + if (pos0 != -1 && pos1 != -1 && pos2 != -1) + res = str.mid(pos0 + 2, pos1 - 1 - pos0) + + " ... " + str.right(str.size() - pos2); + return res.simplified(); +} + +static bool isIntOrFloatType(const QString &type) +{ + static const QStringList types = QStringList() + << "char" << "int" << "short" << "float" << "double" << "long" + << "bool" << "signed char" << "unsigned" << "unsigned char" + << "unsigned int" << "unsigned long" << "long long" + << "unsigned long long"; + return types.contains(type); +} + +static QString sizeofTypeExpression(const QString &type) +{ + if (type.endsWith('*')) + return "sizeof(void*)"; + if (type.endsWith('>')) + return "sizeof(" + type + ")"; + return "sizeof(" + gdbQuoteTypes(type) + ")"; +} + +void GdbEngine::setCustomDumpersWanted(bool on) +{ + Q_UNUSED(on); + // FIXME: a bit too harsh, but otherwise the treeview + // sometimes look funny + //m_expandedINames.clear(); + updateLocals(); +} + +bool GdbEngine::isCustomValueDumperAvailable(const QString &type) const +{ + if (!qq->useCustomDumpers()) + return false; + if (qq->debugDumpersAction()->isChecked() + && qq->stackHandler()->isDebuggingDumpers()) + return false; + if (m_dataDumperState != DataDumperAvailable) + return false; + if (m_availableSimpleDumpers.contains(type)) + return true; + + QString tmplate; + QString inner; + if (extractTemplate(type, &tmplate, &inner)) { + if (type.startsWith(m_namespace)) { + tmplate = tmplate.mid(m_namespace.size()); + if (tmplate == "QList") + return true; + if (tmplate == "QVector") + return true; + if (tmplate == "QHash") + return true; + if (tmplate == "QHashNode") + return true; + if (tmplate == "QMap") + return true; + if (tmplate == "QMapNode") + return true; + if (tmplate == "QSet") + return true; + } + if (tmplate == "std::vector" && inner != "bool") + return true; + if (tmplate == "std::basic_string") { + if (inner.startsWith("char@") || inner.startsWith("wchar_t@")) + return true; + } + } + + return false; +} + +void GdbEngine::runCustomDumper(const WatchData & data0, bool dumpChildren) +{ + WatchData data = data0; + QWB_ASSERT(!data.exp.isEmpty(), return); + QString tmplate; + QString inner; + bool isTemplate = extractTemplate(data.type, &tmplate, &inner); + QStringList inners = inner.split('@'); + if (inners.at(0).isEmpty()) + inners.clear(); + + QString outertype = isTemplate ? tmplate : data.type; + + if (outertype == "QWidget") + outertype = "QObject"; + + QString extraArgs[4]; + extraArgs[0] = "0"; + extraArgs[1] = "0"; + extraArgs[2] = "0"; + extraArgs[3] = "0"; + int extraArgCount = 0; + + // "generic" template dumpers: passing sizeof(argument) + // gives already most information the dumpers need + foreach (const QString &arg, inners) + extraArgs[extraArgCount++] = sizeofTypeExpression(arg); + + // in rare cases we need more or less: + if (outertype == m_namespace + "QObject") { + extraArgs[extraArgCount++] = "(char*)&((('" + + m_namespace + "QObjectPrivate'*)&" + + data.exp + ")->children)-(char*)&" + data.exp; + } else if (outertype == m_namespace + "QVector") { + extraArgs[extraArgCount++] = "(char*)&((" + + data.exp + ").d->array)-(char*)" + data.exp + ".d"; + } else if (outertype == m_namespace + "QObjectSlot" + || outertype == m_namespace + "QObjectSignal") { + // we need the number out of something like + // iname="local.ob.slots.[2]deleteLater()" + int lastOpened = data.iname.lastIndexOf('['); + int lastClosed = data.iname.lastIndexOf(']'); + QString slotNumber = "-1"; + if (lastOpened != -1 && lastClosed != -1) + slotNumber = data.iname.mid(lastOpened + 1, lastClosed - lastOpened - 1); + extraArgs[extraArgCount++] = slotNumber; + } else if (outertype == m_namespace + "QMap") { + QString nodetype = m_namespace + "QMapNode"; + nodetype += data.type.mid(m_namespace.size() + 4); + //qDebug() << "OUTERTYPE: " << outertype << " NODETYPE: " << nodetype; + extraArgs[extraArgCount++] = sizeofTypeExpression(nodetype); + extraArgs[extraArgCount++] = "(size_t)&(('" + nodetype + "'*)0)->value"; + } else if (outertype == m_namespace + "QMapNode") { + extraArgs[extraArgCount++] = sizeofTypeExpression(data.type); + extraArgs[extraArgCount++] = "(size_t)&(('" + data.type + "'*)0)->value"; + } else if (outertype == "std::vector") { + //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners; + if (inners.at(0) == "bool") { + outertype = "std::vector::bool"; + } else { + //extraArgs[extraArgCount++] = sizeofTypeExpression(data.type); + //extraArgs[extraArgCount++] = "(size_t)&(('" + data.type + "'*)0)->value"; + } + } else if (outertype == "std::basic_string") { + //qDebug() << "EXTRACT TEMPLATE: " << outertype << inners; + if (inners.at(0) == "char") { + outertype = "std::string"; + } else if (inners.at(0) == "wchar_t") { + outertype = "std::wstring"; + } + extraArgs[0] = "0"; + extraArgs[1] = "0"; + extraArgs[2] = "0"; + extraArgs[3] = "0"; + } + + //int protocol = (data.iname.startsWith("watch") && data.type == "QImage") ? 3 : 2; + //int protocol = data.iname.startsWith("watch") ? 3 : 2; + int protocol = 2; + //int protocol = isDisplayedIName(data.iname) ? 3 : 2; + + QString addr; + if (data.addr.startsWith("0x")) + addr = "(void*)" + data.addr; + else + addr = "&(" + data.exp + ")"; + + QByteArray params; + params.append(outertype); + params.append('\0'); + params.append(data.iname); + params.append('\0'); + params.append(data.exp); + params.append('\0'); + params.append(inner); + params.append('\0'); + params.append(data.iname); + params.append('\0'); + + sendWatchParameters(params); + + QString cmd ="call " + + QString("qDumpObjectData440(") + + QString::number(protocol) + + ',' + "%1+1" // placeholder for token + + ',' + addr + + ',' + (dumpChildren ? "1" : "0") + + ',' + extraArgs[0] + + ',' + extraArgs[1] + + ',' + extraArgs[2] + + ',' + extraArgs[3] + ')'; + + sendSynchronizedCommand(cmd, WatchDumpCustomValue1, QVariant::fromValue(data)); + + q->showStatusMessage( + tr("Retrieving data for watch view (%1 requests pending)...") + .arg(m_pendingRequests + 1), -1); + // create response slot for socket data + QVariant var; + var.setValue(data); + sendSynchronizedCommand(QString(), WatchDumpCustomValue2, var); + + // this increases the probability that gdb spits out output it + // has collected so far + //sendCommand("p qDumpInBuffer"); +} + +void GdbEngine::createGdbVariable(const WatchData &data) +{ + sendSynchronizedCommand("-var-delete \"" + data.iname + '"'); + QString exp = data.exp; + if (exp.isEmpty() && data.addr.startsWith("0x")) + exp = "*(" + gdbQuoteTypes(data.type) + "*)" + data.addr; + QVariant val = QVariant::fromValue<WatchData>(data); + sendSynchronizedCommand("-var-create \"" + data.iname + '"' + " * " + + '"' + exp + '"', WatchVarCreate, val); +} + +void GdbEngine::updateSubItem(const WatchData &data0) +{ + WatchData data = data0; + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: " << data.toString(); + #endif + QWB_ASSERT(data.isValid(), return); + + // in any case we need the type first + if (data.isTypeNeeded()) { + // This should only happen if we don't have a variable yet. + // Let's play safe, though. + if (!data.variable.isEmpty()) { + // Update: It does so for out-of-scope watchers. + #if 1 + qDebug() << "FIXME: GdbEngine::updateSubItem: " + << data.toString() << "should not happen"; + #else + data.setType("<out of scope>"); + data.setValue("<out of scope>"); + data.setChildCount(0); + insertData(data); + return; + #endif + } + // The WatchVarCreate handler will receive type information + // and re-insert a WatchData item with correct type, so + // we will not re-enter this bit. + // FIXME: Concurrency issues? + createGdbVariable(data); + return; + } + + // we should have a type now. this is relied upon further below + QWB_ASSERT(!data.type.isEmpty(), return); + + // a common case that can be easily solved + if (data.isChildrenNeeded() && isPointerType(data.type) + && !isCustomValueDumperAvailable(data.type)) { + // We sometimes know what kind of children pointers have + #if DEBUG_SUBITEM + qDebug() << "IT'S A POINTER"; + #endif +#if 1 + WatchData data1; + data1.iname = data.iname + ".*"; + data1.name = "*" + data.name; + data1.exp = "(*(" + data.exp + "))"; + data1.type = stripPointerType(data.type); + data1.setValueNeeded(); + insertData(data1); + data.setChildrenUnneeded(); + insertData(data); +#else + // Try automatic dereferentiation + data.exp = "*(" + data.exp + ")"; + data.type = data.type + "."; // FIXME: fragile HACK to avoid recursion + insertData(data); +#endif + return; + } + + if (data.isValueNeeded() && isCustomValueDumperAvailable(data.type)) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: CUSTOMVALUE"; + #endif + runCustomDumper(data, qq->watchHandler()->isExpandedIName(data.iname)); + return; + } + +/* + if (data.isValueNeeded() && data.exp.isEmpty()) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: NO EXPRESSION?"; + #endif + data.setError("<no expression given>"); + insertData(data); + return; + } +*/ + + if (data.isValueNeeded() && data.variable.isEmpty()) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: VARIABLE NEEDED FOR VALUE"; + #endif + createGdbVariable(data); + // the WatchVarCreate handler will re-insert a WatchData + // item, with valueNeeded() set. + return; + } + + if (data.isValueNeeded()) { + QWB_ASSERT(!data.variable.isEmpty(), return); // tested above + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: VALUE"; + #endif + QString cmd = "-var-evaluate-expression \"" + data.iname + "\""; + sendSynchronizedCommand(cmd, WatchEvaluateExpression, + QVariant::fromValue(data)); + return; + } + + if (data.isChildrenNeeded() && isCustomValueDumperAvailable(data.type)) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: CUSTOMVALUE WITH CHILDREN"; + #endif + runCustomDumper(data, true); + return; + } + + if (data.isChildrenNeeded() && data.variable.isEmpty()) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: VARIABLE NEEDED FOR CHILDREN"; + #endif + createGdbVariable(data); + // the WatchVarCreate handler will re-insert a WatchData + // item, with childrenNeeded() set. + return; + } + + if (data.isChildrenNeeded()) { + QWB_ASSERT(!data.variable.isEmpty(), return); // tested above + QString cmd = "-var-list-children --all-values \"" + data.variable + "\""; + sendSynchronizedCommand(cmd, WatchVarListChildren, QVariant::fromValue(data)); + return; + } + + if (data.isChildCountNeeded() && isCustomValueDumperAvailable(data.type)) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: CUSTOMVALUE WITH CHILDREN"; + #endif + runCustomDumper(data, qq->watchHandler()->isExpandedIName(data.iname)); + return; + } + + if (data.isChildCountNeeded() && data.variable.isEmpty()) { + #if DEBUG_SUBITEM + qDebug() << "UPDATE SUBITEM: VARIABLE NEEDED FOR CHILDCOUNT"; + #endif + createGdbVariable(data); + // the WatchVarCreate handler will re-insert a WatchData + // item, with childrenNeeded() set. + return; + } + + if (data.isChildCountNeeded()) { + QWB_ASSERT(!data.variable.isEmpty(), return); // tested above + QString cmd = "-var-list-children --all-values \"" + data.variable + "\""; + sendCommand(cmd, WatchVarListChildren, QVariant::fromValue(data)); + return; + } + + qDebug() << "FIXME: UPDATE SUBITEM: " << data.toString(); + QWB_ASSERT(false, return); +} + +void GdbEngine::updateWatchModel() +{ + m_pendingRequests = 0; + PENDING_DEBUG("EXTERNAL TRIGGERING UPDATE WATCH MODEL"); + updateWatchModel2(); +} + +void GdbEngine::updateWatchModel2() +{ + PENDING_DEBUG("UPDATE WATCH MODEL"); + QList<WatchData> incomplete = qq->watchHandler()->takeCurrentIncompletes(); + //QWB_ASSERT(incomplete.isEmpty(), /**/); + if (!incomplete.isEmpty()) { + #if DEBUG_PENDING + qDebug() << "##############################################"; + qDebug() << "UPDATE MODEL, FOUND INCOMPLETES:"; + foreach (const WatchData &data, incomplete) + qDebug() << data.toString(); + #endif + + // Bump requests to avoid model rebuilding during the nested + // updateWatchModel runs. + ++m_pendingRequests; + foreach (const WatchData &data, incomplete) + updateSubItem(data); + PENDING_DEBUG("INTERNAL TRIGGERING UPDATE WATCH MODEL"); + updateWatchModel2(); + --m_pendingRequests; + + return; + } + + if (m_pendingRequests > 0) { + PENDING_DEBUG("UPDATE MODEL, PENDING: " << m_pendingRequests); + return; + } + + PENDING_DEBUG("REBUILDING MODEL") + emit gdbInputAvailable(QString(), + "[" + currentTime() + "] <Rebuild Watchmodel>"); + q->showStatusMessage(tr("Finished retrieving data."), -1); + qq->watchHandler()->rebuildModel(); + + if (!m_toolTipExpression.isEmpty()) { + WatchData *data = qq->watchHandler()->findData(tooltipIName); + if (data) { + //m_toolTipCache[data->exp] = *data; + QToolTip::showText(m_toolTipPos, + "(" + data->type + ") " + data->exp + " = " + data->value); + } else { + QToolTip::showText(m_toolTipPos, + "Cannot evaluate expression: " + m_toolTipExpression); + } + } + + //qDebug() << "INSERT DATA" << data0.toString(); + //q->showStatusMessage(tr("Stopped."), 5000); +} + +void GdbEngine::handleQueryDataDumper1(const GdbResultRecord &record) +{ + Q_UNUSED(record); +} + +void GdbEngine::handleQueryDataDumper2(const GdbResultRecord &record) +{ + // is this the official gdb response. However, it won't contain + // interesting data other than the information that 'real' data + // either already arrived or is still in the pipe. So we do + // _not_ register this result for counting purposes, this will + // be done by the 'real' result (with resultClass == GdbResultCustomDone) + //qDebug() << "DATA DUMPER TRIAL:" << record.toString(); + GdbMi output = record.data.findChild("customvaluecontents"); + GdbMi contents(output.data()); + GdbMi simple = contents.findChild("simpledumpers"); + m_namespace = contents.findChild("namespace").data(); + //qDebug() << "OUTPUT: " << output.toString(); + //qDebug() << "CONTENTS: " << contents.toString(); + //qDebug() << "SIMPLE DUMPERS: " << simple.toString(); + m_availableSimpleDumpers.clear(); + foreach (const GdbMi &item, simple.children()) + m_availableSimpleDumpers.append(item.data()); + if (m_availableSimpleDumpers.isEmpty()) { + m_dataDumperState = DataDumperUnavailable; + QMessageBox::warning(q->mainWindow(), + tr("Cannot find special data dumpers"), + tr("The debugged binary does not contain information needed for " + "nice display of Qt data types.\n\n" + "Try might want to try include the file\n\n" + ".../ide/main/bin/gdbmacros/gdbmacros.cpp'\n\n" + "into your project directly.") + ); + } else { + m_dataDumperState = DataDumperAvailable; + } + //qDebug() << "DATA DUMPERS AVAILABLE" << m_availableSimpleDumpers; +} + +void GdbEngine::sendWatchParameters(const QByteArray ¶ms0) +{ + QByteArray params = params0; + params.append('\0'); + char buf[50]; + sprintf(buf, "set {char[%d]} qDumpInBuffer = {", params.size()); + QByteArray encoded; + encoded.append(buf); + for (int i = 0; i != params.size(); ++i) { + sprintf(buf, "%d,", int(params[i])); + encoded.append(buf); + } + encoded[encoded.size() - 1] = '}'; + + sendCommand(encoded); +} + +void GdbEngine::handleVarAssign() +{ + // everything might have changed, force re-evaluation + // FIXME: Speed this up by re-using variables and only + // marking values as 'unknown' + updateLocals(); +} + +void GdbEngine::setWatchDataType(WatchData &data, const GdbMi &mi) +{ + if (mi.isValid()) { + if (!data.framekey.isEmpty()) + m_varToType[data.framekey] = mi.data(); + data.setType(mi.data()); + } else if (data.type.isEmpty()) { + data.setTypeNeeded(); + } +} + +void GdbEngine::handleVarCreate(const GdbResultRecord &record, + const WatchData &data0) +{ + WatchData data = data0; + // happens e.g. when we already issued a var-evaluate command + if (!data.isValid()) + return; + //qDebug() << "HANDLE VARIABLE CREATION: " << data.toString(); + if (record.resultClass == GdbResultDone) { + data.variable = data.iname; + setWatchDataType(data, record.data.findChild("type")); + if (isCustomValueDumperAvailable(data.type)) { + // we do not trust gdb if we have a custom dumper + if (record.data.findChild("children").isValid()) + data.setChildrenUnneeded(); + else if (qq->watchHandler()->isExpandedIName(data.iname)) + data.setChildrenNeeded(); + insertData(data); + } else { + if (record.data.findChild("children").isValid()) + data.setChildrenUnneeded(); + else if (qq->watchHandler()->isExpandedIName(data.iname)) + data.setChildrenNeeded(); + setWatchDataChildCount(data, record.data.findChild("numchild")); + //if (data.isValueNeeded() && data.childCount > 0) + // data.setValue(QByteArray()); + insertData(data); + } + } else if (record.resultClass == GdbResultError) { + data.setError(record.data.findChild("msg").data()); + if (data.isWatcher()) { + data.value = strNotInScope; + data.type = " "; + data.setAllUnneeded(); + data.setChildCount(0); + data.valuedisabled = true; + insertData(data); + } + } +} + +void GdbEngine::handleEvaluateExpression(const GdbResultRecord &record, + const WatchData &data0) +{ + WatchData data = data0; + QWB_ASSERT(data.isValid(), qDebug() << "HUH?"); + if (record.resultClass == GdbResultDone) { + //if (col == 0) + // data.name = record.data.findChild("value").data(); + //else + setWatchDataValue(data, record.data.findChild("value")); + } else if (record.resultClass == GdbResultError) { + data.setError(record.data.findChild("msg").data()); + } + //qDebug() << "HANDLE EVALUATE EXPRESSION: " << data.toString(); + insertData(data); + //updateWatchModel2(); +} + +void GdbEngine::handleDumpCustomSetup(const GdbResultRecord &record) +{ + qDebug() << "CUSTOM SETUP RESULT: " << record.toString(); + if (record.resultClass == GdbResultDone) { + } else if (record.resultClass == GdbResultError) { + QString msg = record.data.findChild("msg").data(); + qDebug() << "CUSTOM DUMPER SETUP ERROR MESSAGE: " << msg; + } +} + +void GdbEngine::handleDumpCustomValue1(const GdbResultRecord &record, + const WatchData &data0) +{ + WatchData data = data0; + QWB_ASSERT(data.isValid(), return); + if (record.resultClass == GdbResultDone) { + // ignore this case, data will follow + } else if (record.resultClass == GdbResultError) { + // Record an extra result, as the socket result will be lost + // in transmission + --m_pendingRequests; + QString msg = record.data.findChild("msg").data(); + //qDebug() << "CUSTOM DUMPER ERROR MESSAGE: " << msg; +#ifdef QT_DEBUG + // Make debugging of dumers easier + if (qq->debugDumpersAction()->isChecked() + && msg.startsWith("The program being debugged stopped while") + && msg.contains("qDumpObjectData440")) { + // Fake full stop + sendCommand("-file-list-exec-source-files", GdbQuerySources); + sendCommand("-break-list", BreakList); + sendCommand("p 0", GdbAsyncOutput2); // dummy + return; + } +#endif + if (msg.startsWith("The program being debugged was sig")) + msg = strNotInScope; + if (msg.startsWith("The program being debugged stopped while")) + msg = strNotInScope; + data.setError(msg); + insertData(data); + } +} + +void GdbEngine::handleDumpCustomValue2(const GdbResultRecord &record, + const WatchData &data0) +{ + WatchData data = data0; + QWB_ASSERT(data.isValid(), return); + //qDebug() << "CUSTOM VALUE RESULT: " << record.toString(); + //qDebug() << "FOR DATA: " << data.toString() << record.resultClass; + if (record.resultClass == GdbResultDone) { + GdbMi output = record.data.findChild("customvaluecontents"); + //qDebug() << "HANDLE VALUE CONTENTS: " << output.toString(true); + if (!output.isValid()) { + //qDebug() << "INVALID"; + // custom dumper produced no output + if (data.isValueNeeded()) + data.setValue("<unknown>"); + if (data.isTypeNeeded()) + data.setType("<unknown>"); + if (data.isChildrenNeeded()) + data.setChildCount(0); + if (data.isChildCountNeeded()) + data.setChildCount(0); + data.setValueToolTip("<custom dumper produced no output>"); + insertData(data); + } else { + GdbMi contents; + //qDebug() << "OUTPUT" << output.toString(true); + contents.fromString(output.data()); + //qDebug() << "CONTENTS" << contents.toString(true); + setWatchDataType(data, contents.findChild("type")); + setWatchDataValue(data, contents.findChild("value"), + contents.findChild("valueencoded").data().toInt()); + setWatchDataAddress(data, contents.findChild("addr")); + setWatchDataChildCount(data, contents.findChild("numchild")); + setWatchDataValueToolTip(data, contents.findChild("valuetooltip")); + setWatchDataValueDisabled(data, contents.findChild("valuedisabled")); + setWatchDataEditValue(data, contents.findChild("editvalue")); + if (qq->watchHandler()->isDisplayedIName(data.iname)) { + GdbMi editvalue = contents.findChild("editvalue"); + if (editvalue.isValid()) { + setWatchDataEditValue(data, editvalue); + qq->watchHandler()->showEditValue(data); + } + } + if (!qq->watchHandler()->isExpandedIName(data.iname)) + data.setChildrenUnneeded(); + GdbMi children = contents.findChild("children"); + if (children.isValid() || !qq->watchHandler()->isExpandedIName(data.iname)) + data.setChildrenUnneeded(); + data.setValueUnneeded(); + + // try not to repeat data too often + WatchData childtemplate; + setWatchDataType(childtemplate, contents.findChild("childtype")); + setWatchDataChildCount(childtemplate, contents.findChild("childnumchild")); + //qDebug() << "DATA: " << data.toString(); + insertData(data); + foreach (GdbMi item, children.children()) { + WatchData data1 = childtemplate; + data1.name = item.findChild("name").data(); + data1.iname = data.iname + "." + data1.name; + //qDebug() << "NAMEENCODED: " << item.findChild("nameencoded").data() + // << item.findChild("nameencoded").data()[1]; + if (item.findChild("nameencoded").data()[0] == '1') + data1.name = QByteArray::fromBase64(data1.name.toUtf8()); + if (item.findChild("nameisindex").data()[0] == '1') + data1.name = '[' + data1.name + ']'; + setWatchDataType(data1, item.findChild("type")); + setWatchDataExpression(data1, item.findChild("exp")); + setWatchDataChildCount(data1, item.findChild("numchild")); + setWatchDataValue(data1, item.findChild("value"), + item.findChild("valueencoded").data().toInt()); + setWatchDataAddress(data1, item.findChild("addr")); + setWatchDataValueToolTip(data1, item.findChild("valuetooltip")); + setWatchDataValueDisabled(data1, item.findChild("valuedisabled")); + if (!qq->watchHandler()->isExpandedIName(data1.iname)) + data1.setChildrenUnneeded(); + //qDebug() << "HANDLE CUSTOM SUBCONTENTS:" << data1.toString(); + insertData(data1); + } + } + //qDebug() << "HANDLE CUSTOM VALUE CONTENTS: " << data.toString(); + } else if (record.resultClass == GdbResultError) { + // FIXME: Should not happen here, i.e. could be removed + QString msg = record.data.findChild("msg").data(); + //qDebug() << "CUSTOM DUMPER ERROR MESSAGE: " << msg; + if (msg.startsWith("The program being debugged was sig")) + msg = strNotInScope; + if (msg.startsWith("The program being debugged stopped while")) + msg = strNotInScope; + data.setError(msg); + insertData(data); + } else { + qDebug() << "STRANGE CUSTOM DUMPER RESULT DATA: " << data.toString(); + } +} + +void GdbEngine::updateLocals() +{ + setTokenBarrier(); + + m_pendingRequests = 0; + PENDING_DEBUG("\nRESET PENDING"); + m_toolTipCache.clear(); + m_toolTipExpression.clear(); + qq->watchHandler()->reinitializeWatchers(); + + int level = currentFrame(); + // '2' is 'list with type and value' + QString cmd = QString("-stack-list-arguments 2 %1 %2").arg(level).arg(level); + sendSynchronizedCommand(cmd, StackListArguments); // stage 1/2 + // '2' is 'list with type and value' + sendSynchronizedCommand("-stack-list-locals 2", StackListLocals); // stage 2/2 +} + +void GdbEngine::handleStackListArguments(const GdbResultRecord &record) +{ + // stage 1/2 + + // Linux: + // 12^done,stack-args= + // [frame={level="0",args=[ + // {name="argc",type="int",value="1"}, + // {name="argv",type="char **",value="(char **) 0x7..."}]}] + // Mac: + // 78^done,stack-args= + // {frame={level="0",args={ + // varobj= + // {exp="this",value="0x38a2fab0",name="var21",numchild="3", + // type="CurrentDocumentFind * const",typecode="PTR", + // dynamic_type="",in_scope="true",block_start_addr="0x3938e946", + // block_end_addr="0x3938eb2d"}, + // varobj= + // {exp="before",value="@0xbfffb9f8: {d = 0x3a7f2a70}", + // name="var22",numchild="1",type="const QString ...} }}} + // + // In both cases, iterating over the children of stack-args/frame/args + // is ok. + m_currentFunctionArgs.clear(); + if (record.resultClass == GdbResultDone) { + const GdbMi list = record.data.findChild("stack-args"); + const GdbMi frame = list.findChild("frame"); + const GdbMi args = frame.findChild("args"); + m_currentFunctionArgs = args.children(); + } else if (record.resultClass == GdbResultError) { + qDebug() << "FIXME: GdbEngine::handleStackListArguments: should not happen"; + } +} + +void GdbEngine::handleStackListLocals(const GdbResultRecord &record) +{ + // stage 2/2 + + // There could be shadowed variables + QHash<QString, int> seen; + QList<GdbMi> locals = record.data.findChild("locals").children(); + locals += m_currentFunctionArgs; + + //qDebug() << m_varToType; + + foreach (const GdbMi &item, locals) { + #ifdef Q_OS_MAC + QString name = item.findChild("exp").data(); + #else + QString name = item.findChild("name").data(); + #endif + int n = seen.value(name); + if (n) { + seen[name] = n + 1; + WatchData data; + data.iname = "local." + name + QString::number(n + 1); + data.name = name + QString(" <shadowed %1>").arg(n); + //data.setValue("<shadowed>"); + setWatchDataValue(data, item.findChild("value")); + data.setType("<shadowed>"); + data.setChildCount(0); + insertData(data); + } else { + seen[name] = 1; + WatchData data; + data.iname = "local." + name; + data.name = name; + data.exp = name; + data.framekey = m_currentFrame + data.name; + setWatchDataType(data, item.findChild("type")); + // set value only directly if it is simple enough, otherwise + // pass through the insertData() machinery + if (isIntOrFloatType(data.type) || isPointerType(data.type)) + setWatchDataValue(data, item.findChild("value")); + if (!qq->watchHandler()->isExpandedIName(data.iname)) + data.setChildrenUnneeded(); + if (isPointerType(data.type) || data.name == "this") + data.setChildCount(1); + if (0 && m_varToType.contains(data.framekey)) { + qDebug() << "RE-USING " << m_varToType.value(data.framekey); + data.setType(m_varToType.value(data.framekey)); + } + insertData(data); + } + } +} + +void GdbEngine::insertData(const WatchData &data0) +{ + //qDebug() << "INSERT DATA" << data0.toString(); + WatchData data = data0; + if (data.value.startsWith("mi_cmd_var_create:")) { + qDebug() << "BOGUS VALUE: " << data.toString(); + return; + } + qq->watchHandler()->insertData(data); +} + +void GdbEngine::handleTypeContents(const QString &output) +{ + // output.startsWith("type = ") == true + // "type = int" + // "type = class QString {" + // "type = class QStringList : public QList<QString> {" + QString tip; + QString className; + if (output.startsWith("type = class")) { + int posBrace = output.indexOf('{'); + QString head = output.mid(13, posBrace - 13 - 1); + int posColon = head.indexOf(": public"); + if (posColon == -1) + posColon = head.indexOf(": protected"); + if (posColon == -1) + posColon = head.indexOf(": private"); + if (posColon == -1) { + className = head; + tip = "class " + className + " { ... }"; + } else { + className = head.left(posColon - 1); + tip = "class " + head + " { ... }"; + } + //qDebug() << "posColon: " << posColon; + //qDebug() << "posBrace: " << posBrace; + //qDebug() << "head: " << head; + } else { + className = output.mid(7); + tip = className; + } + //qDebug() << "output: " << output.left(100) + "..."; + //qDebug() << "className: " << className; + //qDebug() << "tip: " << tip; + //m_toolTip.type = className; + m_toolTip.type.clear(); + m_toolTip.value = tip; +} + +void GdbEngine::handleVarListChildrenHelper(const GdbMi &item, + const WatchData &parent) +{ + //qDebug() << "VAR_LIST_CHILDREN: PARENT 2" << parent.toString(); + //qDebug() << "VAR_LIST_CHILDREN: APPENDEE " << data.toString(); + QByteArray exp = item.findChild("exp").data(); + QByteArray name = item.findChild("name").data(); + if (isAccessSpecifier(exp)) { + // suppress 'private'/'protected'/'public' level + WatchData data; + data.variable = name; + data.iname = parent.iname; + //data.iname = data.variable; + data.exp = parent.exp; + data.setTypeUnneeded(); + data.setValueUnneeded(); + data.setChildCountUnneeded(); + data.setChildrenUnneeded(); + //qDebug() << "DATA" << data.toString(); + QString cmd = "-var-list-children --all-values \"" + data.variable + "\""; + //iname += '.' + exp; + sendSynchronizedCommand(cmd, WatchVarListChildren, QVariant::fromValue(data)); + } else if (item.findChild("numchild").data() == "0") { + // happens for structs without data, e.g. interfaces. + WatchData data; + data.iname = parent.iname + '.' + exp; + data.name = exp; + data.variable = name; + setWatchDataType(data, item.findChild("type")); + setWatchDataValue(data, item.findChild("value")); + setWatchDataAddress(data, item.findChild("addr")); + data.setChildCount(0); + insertData(data); + } else if (parent.iname.endsWith('.')) { + // Happens with anonymous unions + WatchData data; + data.iname = name; + QString cmd = "-var-list-children --all-values \"" + data.variable + "\""; + sendSynchronizedCommand(cmd, WatchVarListChildren, QVariant::fromValue(data)); + } else if (exp == "staticMetaObject") { + // && item.findChild("type").data() == "const QMetaObject") + // FIXME: Namespaces? + // { do nothing } FIXME: make coinfigurable? + // special "clever" hack to avoid clutter in the GUI. + // I am not sure this is a good idea... + } else { + WatchData data; + data.iname = parent.iname + '.' + exp; + data.variable = name; + setWatchDataType(data, item.findChild("type")); + setWatchDataValue(data, item.findChild("value")); + setWatchDataAddress(data, item.findChild("addr")); + setWatchDataChildCount(data, item.findChild("numchild")); + if (!qq->watchHandler()->isExpandedIName(data.iname)) + data.setChildrenUnneeded(); + + data.name = exp; + if (isPointerType(parent.type) && data.type == exp) { + data.exp = "*(" + parent.exp + ")"; + data.name = "*" + parent.name; + } else if (data.type == exp) { + // A type we derive from? gdb crashes when creating variables here + data.exp = parent.exp; + } else if (exp.startsWith("*")) { + // A pointer + data.exp = "*(" + parent.exp + ")"; + } else if (startsWithDigit(exp)) { + // An array. No variables needed? + data.name = "[" + data.name + "]"; + data.exp = parent.exp + "[" + exp + "]"; + } else if (0 && parent.name.endsWith('.')) { + // Happens with anonymous unions + data.exp = parent.exp + exp; + //data.name = "<anonymous union>"; + } else if (exp.isEmpty()) { + // Happens with anonymous unions + data.exp = parent.exp; + data.name = "<n/a>"; + data.iname = parent.iname + ".@"; + data.type = "<anonymous union>"; + } else { + // A structure. Hope there's nothing else... + data.exp = parent.exp + '.' + exp; + } + + if (isCustomValueDumperAvailable(data.type)) { + // we do not trust gdb if we have a custom dumper + data.setValueNeeded(); + data.setChildCountNeeded(); + } + + //qDebug() << "VAR_LIST_CHILDREN: PARENT 3" << parent.toString(); + //qDebug() << "VAR_LIST_CHILDREN: APPENDEE " << data.toString(); + insertData(data); + } +} + +void GdbEngine::handleVarListChildren(const GdbResultRecord &record, + const WatchData &data0) +{ + //WatchResultCounter dummy(this, WatchVarListChildren); + WatchData data = data0; + if (!data.isValid()) + return; + if (record.resultClass == GdbResultDone) { + //qDebug() << "VAR_LIST_CHILDREN: PARENT " << data.toString(); + GdbMi children = record.data.findChild("children"); + + foreach (const GdbMi &child, children.children()) + handleVarListChildrenHelper(child, data); + + if (!isAccessSpecifier(data.variable.split('.').takeLast())) { + data.setChildrenUnneeded(); + insertData(data); + } + } else if (record.resultClass == GdbResultError) { + data.setError(record.data.findChild("msg").data()); + } else { + data.setError("Unknown error: " + record.toString()); + } +} + +void GdbEngine::handleToolTip(const GdbResultRecord &record, + const QString &what) +{ + //qDebug() << "HANDLE TOOLTIP: " << what << m_toolTip.toString(); + // << "record: " << record.toString(); + if (record.resultClass == GdbResultError) { + QString msg = record.data.findChild("msg").data(); + if (what == "create") { + sendCommand("ptype " + m_toolTip.exp, WatchToolTip, "ptype"); + return; + } + if (what == "evaluate") { + if (msg.startsWith("Cannot look up value of a typedef")) { + m_toolTip.value = m_toolTip.exp + " is a typedef."; + //return; + } + } + } else if (record.resultClass == GdbResultDone) { + if (what == "create") { + setWatchDataType(m_toolTip, record.data.findChild("type")); + setWatchDataChildCount(m_toolTip, record.data.findChild("numchild")); + if (isCustomValueDumperAvailable(m_toolTip.type)) + runCustomDumper(m_toolTip, false); + else + q->showStatusMessage(tr("Retrieving data for tooltip..."), -1); + sendCommand("-data-evaluate-expression " + m_toolTip.exp, + WatchToolTip, "evaluate"); + //sendToolTipCommand("-var-evaluate-expression tooltip") + return; + } + if (what == "evaluate") { + m_toolTip.value = m_toolTip.type + ' ' + m_toolTip.exp + + " = " + record.data.findChild("value").data(); + //return; + } + if (what == "ptype") { + GdbMi mi = record.data.findChild("consolestreamoutput"); + m_toolTip.value = extractTypeFromPTypeOutput(mi.data()); + //return; + } + } + + m_toolTip.iname = tooltipIName; + m_toolTip.setChildrenUnneeded(); + m_toolTip.setChildCountUnneeded(); + insertData(m_toolTip); + qDebug() << "DATA INSERTED"; + QTimer::singleShot(0, this, SLOT(updateWatchModel2())); + qDebug() << "HANDLE TOOLTIP END"; +} + +#if 0 +void GdbEngine::handleChangedItem(QStandardItem *item) +{ + // HACK: Just store the item for the slot + // handleChangedItem(QWidget *widget) below. + QModelIndex index = item->index().sibling(item->index().row(), 0); + //WatchData data = m_currentSet.takeData(iname); + //m_editedData = inameFromItem(m_model.itemFromIndex(index)).exp; + //qDebug() << "HANDLE CHANGED EXPRESSION: " << m_editedData; +} +#endif + +void GdbEngine::assignValueInDebugger(const QString &expression, const QString &value) +{ + sendCommand("-var-delete assign"); + sendCommand("-var-create assign * " + expression); + sendCommand("-var-assign assign " + value, WatchVarAssign); +} + + +void GdbEngine::tryLoadCustomDumpers() +{ + if (m_dataDumperState != DataDumperUninitialized) + return; + + PENDING_DEBUG("TRY LOAD CUSTOM DUMPERS"); + m_dataDumperState = DataDumperLoadTried; + +#if defined(Q_OS_LINUX) + QString lib = q->m_buildDir + "/qtc-gdbmacros/libgdbmacros.so"; + if (QFileInfo(lib).isExecutable()) { + //sendCommand("p dlopen"); + if (qq->useFastStart()) + sendCommand("set stop-on-solib-events 0"); + QString flag = QString::number(RTLD_NOW); + sendCommand("call dlopen(\"" + lib + "\", " + flag + ")"); + sendCommand("sharedlibrary " + dotEscape(lib)); + if (qq->useFastStart()) + sendCommand("set stop-on-solib-events 1"); + } +#endif +#if defined(Q_OS_MAC) + QString lib = q->m_buildDir + "/qtc-gdbmacros/libgdbmacros.dylib"; + if (QFileInfo(lib).isExecutable()) { + //sendCommand("p dlopen"); // FIXME: remove me + if (qq->useFastStart()) + sendCommand("set stop-on-solib-events 0"); + QString flag = QString::number(RTLD_NOW); + sendCommand("call dlopen(\"" + lib + "\", " + flag + ")"); + sendCommand("sharedlibrary " + dotEscape(lib)); + if (qq->useFastStart()) + sendCommand("set stop-on-solib-events 1"); + } +#endif +#if defined(Q_OS_WIN) + QString lib = q->m_buildDir + "/qtc-gdbmacros/debug/gdbmacros.dll"; + if (QFileInfo(lib).exists()) { + if (qq->useFastStart()) + sendCommand("set stop-on-solib-events 0"); + //sendCommand("handle SIGSEGV pass stop print"); + //sendCommand("set unwindonsignal off"); + sendCommand("call LoadLibraryA(\"" + lib + "\")"); + sendCommand("sharedlibrary " + dotEscape(lib)); + if (qq->useFastStart()) + sendCommand("set stop-on-solib-events 1"); + } +#endif + + // retreive list of dumpable classes + sendCommand("call qDumpObjectData440(1,%1+1,0,0,0,0,0,0)", + GdbQueryDataDumper1); + // create response slot for socket data + sendCommand(QString(), GdbQueryDataDumper2); +} + + +IDebuggerEngine *createGdbEngine(DebuggerManager *parent) +{ + return new GdbEngine(parent); +} + diff --git a/src/plugins/debugger/gdbengine.h b/src/plugins/debugger/gdbengine.h new file mode 100644 index 0000000000..bdd59cbca6 --- /dev/null +++ b/src/plugins/debugger/gdbengine.h @@ -0,0 +1,351 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_GDBENGINE_H +#define DEBUGGER_GDBENGINE_H + +#include <QtCore/QByteArray> +#include <QtCore/QHash> +#include <QtCore/QMap> +#include <QtCore/QObject> +#include <QtCore/QProcess> +#include <QtCore/QPoint> +#include <QtCore/QVariant> + +QT_BEGIN_NAMESPACE +class QAction; +class QAbstractItemModel; +class QWidget; +QT_END_NAMESPACE + +#include "idebuggerengine.h" +#include "gdbmi.h" + +namespace Debugger { +namespace Internal { + +class DebuggerManager; +class IDebuggerManagerAccessForEngines; +class GdbResultRecord; +class GdbMi; + +class WatchData; +class BreakpointData; + +struct GdbCookie +{ + GdbCookie() : type(0), synchronized(false) {} + + QString command; + int type; + bool synchronized; + QVariant cookie; +}; + +enum DataDumperState +{ + DataDumperUninitialized, + DataDumperLoadTried, + DataDumperAvailable, + DataDumperUnavailable, +}; + +// FIXME: Move to extra file? +class GdbSettings +{ +public: + GdbSettings() { m_autoRun = m_autoQuit = false; } + +public: + QString m_gdbCmd; + QString m_gdbEnv; + bool m_autoRun; + bool m_autoQuit; + + QString m_scriptFile; + QMap<QString, QVariant> m_typeMacros; +}; + +GdbSettings &theGdbSettings(); + +class GdbEngine : public IDebuggerEngine +{ + Q_OBJECT + +public: + GdbEngine(DebuggerManager *parent); + ~GdbEngine(); + +signals: + void gdbResponseAvailable(); + void gdbInputAvailable(const QString &prefix, const QString &msg); + void gdbOutputAvailable(const QString &prefix, const QString &msg); + void applicationOutputAvailable(const QString &prefix, const QString &msg); + +private: + // + // IDebuggerEngine implementation + // + void stepExec(); + void stepOutExec(); + void nextExec(); + void stepIExec(); + void nextIExec(); + + void shutdown(); + void setToolTipExpression(const QPoint &pos, const QString &exp); + bool startDebugger(); + void exitDebugger(); + + void continueInferior(); + void runInferior(); + void interruptInferior(); + + void runToLineExec(const QString &fileName, int lineNumber); + void runToFunctionExec(const QString &functionName); + void jumpToLineExec(const QString &fileName, int lineNumber); + + void activateFrame(int index); + void selectThread(int index); + + Q_SLOT void attemptBreakpointSynchronization(); + + void loadSessionData() {} + void saveSessionData() {} + + void assignValueInDebugger(const QString &expr, const QString &value); + void executeDebuggerCommand(const QString & command); + + void loadSymbols(const QString &moduleName); + void loadAllSymbols(); + + // + // Own stuff + // + int currentFrame() const; + QString currentWorkingDirectory() const { return m_pwd; } + + bool supportsThreads() const; + + void init(); // called by destructor + void queryFullName(const QString &fileName, QString *fullName); + QString fullName(const QString &fileName); + QString shortName(const QString &fullName); + // get one usable name out of these, try full names first + QString fullName(const QStringList &candidates); + + void handleResult(const GdbResultRecord &, int type, const QVariant &); + + // type and cookie are sender-internal data, opaque for the "event + // queue". resultNeeded == true increments m_pendingResults on + // send and decrements on receipt, effectively preventing + // watch model updates before everything is finished. + void sendCommand(const QString & command, + int type = 0, const QVariant &cookie = QVariant(), + bool needStop = false, bool synchronized = false); + void sendSynchronizedCommand(const QString & command, + int type = 0, const QVariant &cookie = QVariant(), + bool needStop = false); + + void setTokenBarrier(); + + void updateLocals(); + +private slots: + void setDebugDumpers(bool on); + void setCustomDumpersWanted(bool on); + + void handleResponse(); + + void gdbProcError(QProcess::ProcessError error); + void readGdbStandardOutput(); + void readGdbStandardError(); + +private: + int terminationIndex(const QByteArray &buffer, int &length); + void handleStreamOutput(const QString &output, char code); + void handleAsyncOutput2(const GdbMi &data); + void handleAsyncOutput(const GdbMi &data); + void handleResultRecord(const GdbResultRecord &response); + void handleFileExecAndSymbols(const GdbResultRecord &response); + void handleExecRun(const GdbResultRecord &response); + void handleExecJumpToLine(const GdbResultRecord &response); + void handleExecRunToFunction(const GdbResultRecord &response); + void handleInfoShared(const GdbResultRecord &response); + void handleInfoProc(const GdbResultRecord &response); + void handleShowVersion(const GdbResultRecord &response); + void handleQueryPwd(const GdbResultRecord &response); + void handleQuerySources(const GdbResultRecord &response); + void handleQuerySources2(const GdbResultRecord &response, + const QVariant &); + + QByteArray m_inbuffer; + + QProcess m_gdbProc; + + QHash<int, GdbCookie> m_cookieForToken; + QHash<int, QByteArray> m_customOutputForToken; + + QByteArray m_pendingConsoleStreamOutput; + QByteArray m_pendingTargetStreamOutput; + QByteArray m_pendingLogStreamOutput; + //QByteArray m_pendingCustomValueContents; + QString m_pwd; + + // contains the first token number for the current round + // of evaluation. Responses with older tokens are considers + // out of date and discarded. + int m_oldestAcceptableToken; + + int m_gdbVersion; // 6.8.0 is 680 + int m_shared; + + // awful hack to keep track of used files + QHash<QString, QString> m_shortToFullName; + QHash<QString, QString> m_fullToShortName; + + // + // Breakpoint specific stuff + // + void handleBreakList(const GdbResultRecord &record); + void handleBreakList(const GdbMi &table); + void handleBreakIgnore(const GdbResultRecord &record, int index); + void handleBreakInsert(const GdbResultRecord &record, int index); + void handleBreakInsert1(const GdbResultRecord &record, int index); + void handleBreakCondition(const GdbResultRecord &record, int index); + void handleBreakInfo(const GdbResultRecord &record, int index); + void extractDataFromInfoBreak(const QString &output, BreakpointData *data); + void breakpointDataFromOutput(BreakpointData *data, const GdbMi &bkpt); + void sendInsertBreakpoint(int index); + + + // + // Disassembler specific stuff + // + void handleDisassemblerList(const GdbResultRecord &record, + const QString &cookie); + void reloadDisassembler(); + QString m_address; + + + // + // Modules specific stuff + // + void reloadModules(); + void handleModulesList(const GdbResultRecord &record); + + + // + // Register specific stuff + // + void reloadRegisters(); + void handleRegisterListNames(const GdbResultRecord &record); + void handleRegisterListValues(const GdbResultRecord &record); + + + // + // Stack specific stuff + // + void handleStackListFrames(const GdbResultRecord &record); + void handleStackSelectThread(const GdbResultRecord &record, int cookie); + void handleStackListThreads(const GdbResultRecord &record, int cookie); + + + // + // Tooltip specific stuff + // + void sendToolTipCommand(const QString &command, const QString &cookie); + + + // + // Watch specific stuff + // + // FIXME: BaseClass. called to improve situation for a watch item + void updateSubItem(const WatchData &data); + + void updateWatchModel(); + Q_SLOT void updateWatchModel2(); + + void insertData(const WatchData &data); + void sendWatchParameters(const QByteArray ¶ms0); + void createGdbVariable(const WatchData &data); + + void handleTypeContents(const QString &output); + void maybeHandleInferiorPidChanged(const QString &pid); + + void tryLoadCustomDumpers(); + void runCustomDumper(const WatchData &data, bool dumpChildren); + bool isCustomValueDumperAvailable(const QString &type) const; + + void handleVarListChildren(const GdbResultRecord &record, + const WatchData &cookie); + void handleVarCreate(const GdbResultRecord &record, + const WatchData &cookie); + void handleVarAssign(); + void handleEvaluateExpression(const GdbResultRecord &record, + const WatchData &cookie); + void handleToolTip(const GdbResultRecord &record, + const QString &cookie); + void handleDumpCustomValue1(const GdbResultRecord &record, + const WatchData &cookie); + void handleQueryDataDumper1(const GdbResultRecord &record); + void handleQueryDataDumper2(const GdbResultRecord &record); + void handleDumpCustomValue2(const GdbResultRecord &record, + const WatchData &cookie); + void handleDumpCustomEditValue(const GdbResultRecord &record); + void handleDumpCustomSetup(const GdbResultRecord &record); + void handleStackListLocals(const GdbResultRecord &record); + void handleStackListArguments(const GdbResultRecord &record); + void handleVarListChildrenHelper(const GdbMi &child, + const WatchData &parent); + void setWatchDataType(WatchData &data, const GdbMi &mi); + + QString m_editedData; + int m_pendingRequests; + int m_inferiorPid; + + QStringList m_availableSimpleDumpers; + QString m_namespace; // namespace used in "namespaced Qt"; + + DataDumperState m_dataDumperState; // state of qt creator dumpers + QList<GdbMi> m_currentFunctionArgs; + QString m_currentFrame; + QMap<QString, QString> m_varToType; + + DebuggerManager *q; + IDebuggerManagerAccessForEngines *qq; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_GDBENGINE_H diff --git a/src/plugins/debugger/gdbmi.cpp b/src/plugins/debugger/gdbmi.cpp new file mode 100644 index 0000000000..9091422ad4 --- /dev/null +++ b/src/plugins/debugger/gdbmi.cpp @@ -0,0 +1,473 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "gdbmi.h" +#include "assert.h" + +#include <QtCore/QByteArray> +#include <QtCore/QDebug> +#include <QtCore/QTextStream> + +namespace Debugger { +namespace Internal { + +QTextStream & operator<<(QTextStream & os, const GdbMi & mi) +{ + return os << mi.toString(); +} + +//static void skipSpaces(const GdbMi::Char *&from, const GdbMi::Char *to) +//{ +// while (from != to && QChar(*from).isSpace()) +// ++from; +//} + + +void GdbMi::parseResultOrValue(const Char *&from, const Char *to) +{ + //skipSpaces(from, to); + while (from != to && QChar(*from).isSpace()) + ++from; + + //qDebug() << "parseResultOrValue: " << QByteArray::fromLatin1(from, to - from); + parseValue(from, to); + if (isValid()) { + //qDebug() << "no valid result in " << QByteArray::fromLatin1(from, to - from); + return; + } + if (from == to || *from == '(') + return; + const Char *ptr = from; + while (ptr < to && *ptr != '=') { + //qDebug() << "adding" << QChar(*ptr) << "to name"; + ++ptr; + } + m_name = QByteArray(from, ptr - from); + from = ptr; + if (from < to && *from == '=') { + ++from; + parseValue(from, to); + } +} + +QByteArray GdbMi::parseCString(const Char *&from, const Char *to) +{ + QByteArray result; + //qDebug() << "parseCString: " << QByteArray::fromUtf16(from, to - from); + if (*from != '"') { + qDebug() << "MI Parse Error, double quote expected"; + return QByteArray(); + } + const Char *ptr = from; + ++ptr; + while (ptr < to) { + if (*ptr == '"') { + ++ptr; + result = QByteArray(from + 1, ptr - from - 2); + break; + } + if (*ptr == '\\' && ptr < to - 1) + ++ptr; + ++ptr; + } + + if (result.contains('\\')) { + if (result.contains("\\032\\032")) + result.clear(); + else { + result = result.replace("\\n", "\n"); + result = result.replace("\\t", "\t"); + result = result.replace("\\\"", "\""); + } + } + + from = ptr; + return result; +} + +void GdbMi::parseValue(const Char *&from, const Char *to) +{ + //qDebug() << "parseValue: " << QByteArray::fromUtf16(from, to - from); + switch (*from) { + case '{': + parseTuple(from, to); + break; + case '[': + parseList(from, to); + break; + case '"': + m_type = Const; + m_data = parseCString(from, to); + break; + default: + break; + } +} + + +void GdbMi::parseTuple(const Char *&from, const Char *to) +{ + //qDebug() << "parseTuple: " << QByteArray::fromUtf16(from, to - from); + QWB_ASSERT(*from == '{', /**/); + ++from; + parseTuple_helper(from, to); +} + +void GdbMi::parseTuple_helper(const Char *&from, const Char *to) +{ + //qDebug() << "parseTuple_helper: " << QByteArray::fromUtf16(from, to - from); + m_type = Tuple; + while (from < to) { + if (*from == '}') { + ++from; + break; + } + GdbMi child; + child.parseResultOrValue(from, to); + //qDebug() << "\n=======\n" << qPrintable(child.toString()) << "\n========\n"; + if (!child.isValid()) + return; + m_children += child; + if (*from == ',') + ++from; + } +} + +void GdbMi::parseList(const Char *&from, const Char *to) +{ + //qDebug() << "parseList: " << QByteArray::fromUtf16(from, to - from); + QWB_ASSERT(*from == '[', /**/); + ++from; + m_type = List; + while (from < to) { + if (*from == ']') { + ++from; + break; + } + GdbMi child; + child.parseResultOrValue(from, to); + if (child.isValid()) + m_children += child; + if (*from == ',') + ++from; + } +} + +void GdbMi::setStreamOutput(const QByteArray &name, const QByteArray &content) +{ + if (content.isEmpty()) + return; + GdbMi child; + child.m_type = Const; + child.m_name = name; + child.m_data = content; + m_children += child; + if (m_type == Invalid) + m_type = Tuple; +} + +static QByteArray ind(int indent) +{ + return QByteArray(2 * indent, ' '); +} + +void GdbMi::dumpChildren(QByteArray * str, bool multiline, int indent) const +{ + for (int i = 0; i < m_children.size(); ++i) { + if (i != 0) { + *str += ','; + if (multiline) + *str += '\n'; + } + if (multiline) + *str += ind(indent); + *str += m_children.at(i).toString(multiline, indent); + } +} + +QByteArray GdbMi::toString(bool multiline, int indent) const +{ + QByteArray result; + switch (m_type) { + case Invalid: + if (multiline) { + result += ind(indent) + "Invalid\n"; + } else { + result += "Invalid"; + } + break; + case Const: + if (!m_name.isEmpty()) + result += m_name + "="; + if (multiline) { + result += "\"" + m_data + "\""; + } else { + result += "\"" + m_data + "\""; + } + break; + case Tuple: + if (!m_name.isEmpty()) + result += m_name + "="; + if (multiline) { + result += "{\n"; + dumpChildren(&result, multiline, indent + 1); + result += '\n' + ind(indent) + "}"; + } else { + result += "{"; + dumpChildren(&result, multiline, indent + 1); + result += "}"; + } + break; + case List: + if (!m_name.isEmpty()) + result += m_name + "="; + if (multiline) { + result += "[\n"; + dumpChildren(&result, multiline, indent + 1); + result += "]"; + } else { + result += "["; + dumpChildren(&result, multiline, indent + 1); + result += '\n' + ind(indent) + "]"; + } + break; + } + return result; +} + +void GdbMi::fromString(const QByteArray &ba) +{ + const Char *from = ba.constBegin(); + const Char *to = ba.constEnd(); + parseResultOrValue(from, to); +} + +GdbMi GdbMi::findChild(const QByteArray &name) const +{ + for (int i = 0; i < m_children.size(); ++i) + if (m_children.at(i).m_name == name) + return m_children.at(i); + return GdbMi(); +} + + +GdbMi GdbMi::findChild(const QByteArray &name, const QByteArray &defaultData) const +{ + for (int i = 0; i < m_children.size(); ++i) + if (m_children.at(i).m_name == name) + return m_children.at(i); + GdbMi result; + result.m_data = defaultData; + return result; +} + + +////////////////////////////////////////////////////////////////////////////////// +// +// GdbResultRecord +// +////////////////////////////////////////////////////////////////////////////////// + +QByteArray stringFromResultClass(GdbResultClass resultClass) +{ + switch (resultClass) { + case GdbResultDone: return "done"; + case GdbResultRunning: return "running"; + case GdbResultConnected: return "connected"; + case GdbResultError: return "error"; + case GdbResultExit: return "exit"; + default: return "unknown"; + } +}; + +QByteArray GdbResultRecord::toString() const +{ + QByteArray result; + if (token != -1) + result = QByteArray::number(token); + result += '^'; + result += stringFromResultClass(resultClass); + if (data.isValid()) + result += ',' + data.toString(); + result += '\n'; + return result; +} + + +////////////////////////////////////////////////////////////////////////////////// +// +// GdbStreamOutput +// +////////////////////////////////////////////////////////////////////////////////// + +#if 0 + +static const char test1[] = + "1^done,stack=[frame={level=\"0\",addr=\"0x00000000004061ca\"," + "func=\"main\",file=\"test1.cpp\"," + "fullname=\"/home/apoenitz/work/test1/test1.cpp\",line=\"209\"}]\n" + "(gdb)\n"; + +static const char test2[] = + "2^done,stack=[frame={level=\"0\",addr=\"0x00002ac058675840\"," + "func=\"QApplication\",file=\"/home/apoenitz/dev/qt/src/gui/kernel/qapplication.cpp\"," + "fullname=\"/home/apoenitz/dev/qt/src/gui/kernel/qapplication.cpp\",line=\"592\"}," + "frame={level=\"1\",addr=\"0x00000000004061e0\",func=\"main\",file=\"test1.cpp\"," + "fullname=\"/home/apoenitz/work/test1/test1.cpp\",line=\"209\"}]\n" + "(gdb)\n"; + +static const char test3[] = + "3^done,stack=[frame={level=\"0\",addr=\"0x00000000004061ca\"," + "func=\"main\",file=\"test1.cpp\"," + "fullname=\"/home/apoenitz/work/test1/test1.cpp\",line=\"209\"}]\n" + "(gdb)\n"; + +static const char test4[] = + "&\"source /home/apoenitz/dev/ide/main/bin/gdb/qt4macros\\n\"\n" + "4^done\n" + "(gdb)\n"; + + +static const char test5[] = + "1*stopped,reason=\"breakpoint-hit\",bkptno=\"1\",thread-id=\"1\"," + "frame={addr=\"0x0000000000405738\",func=\"main\"," + "args=[{name=\"argc\",value=\"1\"},{name=\"argv\",value=\"0x7fff1ac78f28\"}]," + "file=\"test1.cpp\",fullname=\"/home/apoenitz/work/test1/test1.cpp\"," + "line=\"209\"}\n" + "(gdb)\n"; + +static const char test6[] = + "{u = {u = 2048, v = 16788279, w = -689265400}, a = 1, b = -689265424, c = 11063, s = {static null = {<No data fields>}, static shared_null = {ref = {value = 2}, alloc = 0, size = 0, data = 0x6098da, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, static shared_empty = {ref = {value = 1}, alloc = 0, size = 0, data = 0x2b37d84f8fba, clean = 0, simpletext = 0, righttoleft = 0, asciiCache = 0, capacity = 0, reserved = 0, array = {0}}, d = 0x6098c0, static codecForCStrings = 0x0}}"; + +static const char test8[] = + "8^done,data={locals={{name=\"a\"},{name=\"w\"}}}\n" + "(gdb)\n"; + +static const char test9[] = + "9^done,data={locals=[name=\"baz\",name=\"urgs\",name=\"purgs\"]}\n" + "(gdb)\n"; + + +static const char test10[] = + "16^done,name=\"urgs\",numchild=\"1\",type=\"Urgs\"\n" + "(gdb)\n" + "17^done,name=\"purgs\",numchild=\"1\",type=\"Urgs *\"\n" + "(gdb)\n" + "18^done,name=\"bar\",numchild=\"0\",type=\"int\"\n" + "(gdb)\n" + "19^done,name=\"z\",numchild=\"0\",type=\"int\"\n" + "(gdb)\n"; + +static const char test11[] = + "[{name=\"size\",value=\"1\",type=\"size_t\",readonly=\"true\"}," + "{name=\"0\",value=\"one\",type=\"QByteArray\"}]"; + +static const char test12[] = + "{iname=\"local.hallo\",value=\"\\\"\\\"\",type=\"QByteArray\",numchild=\"0\"}"; + +static struct Tester { + + Tester() { + //test(test10); + test2(test12); + //test(test4); + //apple(); + exit(0); + } + + void test(const char* input) + { + //qDebug("\n<<<<\n%s\n====\n%s\n>>>>\n", input, + //qPrintable(GdbResponse(input).toString())); + } + + void test2(const char* input) + { + GdbMi mi(input); + qDebug("\n<<<<\n%s\n====\n%s\n>>>>\n", input, + qPrintable(mi.toString())); + } + + void apple() + { + QByteArray input(test9); +/* + qDebug() << "input: " << input; + input = input.replace("{{","["); + input = input.replace("},{",","); + input = input.replace("}}","]"); + qDebug() << "input: " << input; + GdbResponse response(input); + qDebug() << "read: " << response.toString(); + GdbMi list = response.results[0].data.findChild("data").findChild("locals"); + QByteArrayList locals; + foreach (const GdbMi &item, list.children()) + locals.append(item.string()); + qDebug() << "Locals (new): " << locals; +*/ + } + void parse(const QByteArray &str) + { + QByteArray result; + result += "\n "; + int indent = 0; + int from = 0; + int to = str.size(); + if (str.size() && str[0] == '{' /*'}'*/) { + ++from; + --to; + } + for (int i = from; i < to; ++i) { + if (str[i] == '{') + result += "{\n" + QByteArray(2*++indent + 1, QChar(' ')); + else if (str[i] == '}') { + if (!result.isEmpty() && result[result.size() - 1] != '\n') + result += "\n"; + result += QByteArray(2*--indent + 1, QChar(' ')) + "}\n"; + } + else if (str[i] == ',') { + if (true || !result.isEmpty() && result[result.size() - 1] != '\n') + result += "\n"; + result += QByteArray(2*indent, QChar(' ')); + } + else + result += str[i]; + } + qDebug() << "result:\n" << result; + } + +} dummy; + +#endif + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/gdbmi.h b/src/plugins/debugger/gdbmi.h new file mode 100644 index 0000000000..381b5ba86b --- /dev/null +++ b/src/plugins/debugger/gdbmi.h @@ -0,0 +1,183 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ + +#ifndef DEBUGGER_GDBMI_H +#define DEBUGGER_GDBMI_H + +#include <qglobal.h> + +#include <QtCore/QByteArray> +#include <QtCore/QList> + +namespace Debugger { +namespace Internal { + +/* + +output ==> + ( out-of-band-record )* [ result-record ] "(gdb)" nl +result-record ==> + [ token ] "^" result-class ( "," result )* nl +out-of-band-record ==> + async-record | stream-record +async-record ==> + exec-async-output | status-async-output | notify-async-output +exec-async-output ==> + [ token ] "*" async-output +status-async-output ==> + [ token ] "+" async-output +notify-async-output ==> + [ token ] "=" async-output +async-output ==> + async-class ( "," result )* nl +result-class ==> + "done" | "running" | "connected" | "error" | "exit" +async-class ==> + "stopped" | others (where others will be added depending on the needs--this is still in development). +result ==> + variable "=" value +variable ==> + string +value ==> + const | tuple | list +const ==> + c-string +tuple ==> + "{}" | "{" result ( "," result )* "}" +list ==> + "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]" +stream-record ==> + console-stream-output | target-stream-output | log-stream-output +console-stream-output ==> + "~" c-string +target-stream-output ==> + "@" c-string +log-stream-output ==> + "&" c-string +nl ==> + CR | CR-LF +token ==> + any sequence of digits. + + */ + +// FIXME: rename into GdbMiValue +class GdbMi +{ +public: + GdbMi() : m_type(Invalid) {} + explicit GdbMi(const QByteArray &str) { fromString(str); } + + QByteArray m_name; + QByteArray m_data; + QList<GdbMi> m_children; + + enum Type { + Invalid, + Const, + Tuple, + List, + }; + + Type m_type; + + inline Type type() const { return m_type; } + inline QByteArray name() const { return m_name; } + inline bool hasName(const char *name) const { return m_name == name; } + + inline bool isValid() const { return m_type != Invalid; } + inline bool isConst() const { return m_type == Const; } + inline bool isTuple() const { return m_type == Tuple; } + inline bool isList() const { return m_type == List; } + + + inline QByteArray data() const { return m_data; } + inline const QList<GdbMi> &children() const { return m_children; } + inline int childCount() const { return m_children.size(); } + + const GdbMi & childAt(int index) const { return m_children[index]; } + GdbMi & childAt(int index) { return m_children[index]; } + GdbMi findChild(const QByteArray &name) const; + GdbMi findChild(const QByteArray &name, const QByteArray &defaultString) const; + + QByteArray toString(bool multiline = false, int indent = 0) const; + void fromString(const QByteArray &str); + void setStreamOutput(const QByteArray &name, const QByteArray &content); + +private: + friend class GdbResultRecord; + friend class GdbEngine; + + //typedef ushort Char; + typedef char Char; + static QByteArray parseCString(const Char *&from, const Char *to); + void parseResultOrValue(const Char *&from, const Char *to); + void parseValue(const Char *&from, const Char *to); + void parseTuple(const Char *&from, const Char *to); + void parseTuple_helper(const Char *&from, const Char *to); + void parseList(const Char *&from, const Char *to); + + void dumpChildren(QByteArray *str, bool multiline, int indent) const; +}; + +enum GdbResultClass +{ + // "done" | "running" | "connected" | "error" | "exit" + GdbResultUnknown, + GdbResultDone, + GdbResultCustomDone, + GdbResultRunning, + GdbResultConnected, + GdbResultError, + GdbResultExit, +}; + +class GdbResultRecord +{ +public: + GdbResultRecord() : token(-1), resultClass(GdbResultUnknown) {} + QByteArray toString() const; + + int token; + GdbResultClass resultClass; + GdbMi data; +private: + friend class GdbMi; +}; + +} // namespace Internal +} // namespace Debugger + +//Q_DECLARE_METATYPE(GdbDebugger::Internal::GdbMi); + +#endif // DEBUGGER_GDBMI_H diff --git a/src/plugins/debugger/gdboptionpage.cpp b/src/plugins/debugger/gdboptionpage.cpp new file mode 100644 index 0000000000..bee68d1833 --- /dev/null +++ b/src/plugins/debugger/gdboptionpage.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "gdboptionpage.h" + +#include "gdbengine.h" + +#include <extensionsystem/pluginmanager.h> +#include <coreplugin/icore.h> + +#include <QtCore/QSettings> +#include <QtGui/QLineEdit> +#include <QtGui/QFileDialog> + +using namespace Debugger::Internal; + +GdbOptionPage::GdbOptionPage(GdbSettings *settings) +{ + m_pm = ExtensionSystem::PluginManager::instance(); + m_settings = settings; + + Core::ICore *coreIFace = m_pm->getObject<Core::ICore>(); + if (!coreIFace || !coreIFace->settings()) + return; + QSettings *s = coreIFace->settings(); + s->beginGroup("GdbOptions"); + QString defaultCommand("gdb"); +#if defined(Q_OS_WIN32) + defaultCommand.append(".exe"); +#endif + m_settings->m_gdbCmd = s->value("Location", defaultCommand).toString(); + m_settings->m_gdbEnv = s->value("Environment", "").toString(); + m_settings->m_autoRun = s->value("AutoRun", true).toBool(); + m_settings->m_autoQuit = s->value("AutoQuit", true).toBool(); + s->endGroup(); +} + +QString GdbOptionPage::name() const +{ + return tr("Gdb"); +} + +QString GdbOptionPage::category() const +{ + return "Debugger|Gdb"; +} + +QString GdbOptionPage::trCategory() const +{ + return tr("Debugger|Gdb"); +} + +QWidget *GdbOptionPage::createPage(QWidget *parent) +{ + QWidget *w = new QWidget(parent); + m_ui.setupUi(w); + m_ui.gdbEdit->setText(m_settings->m_gdbCmd); + m_ui.envEdit->setText(m_settings->m_gdbEnv); + m_ui.autoStartBox->setChecked(m_settings->m_autoRun); + m_ui.autoQuitBox->setChecked(m_settings->m_autoQuit); + connect(m_ui.pushButtonBrowse, SIGNAL(clicked()), + this, SLOT(browse())); + + return w; +} + +void GdbOptionPage::browse() +{ + QString fileName = QFileDialog::getOpenFileName(m_ui.pushButtonBrowse, + "Browse for gdb executable"); + if (fileName.isEmpty()) + return; + m_settings->m_gdbCmd = fileName; + m_ui.gdbEdit->setText(fileName); +} + +void GdbOptionPage::finished(bool accepted) +{ + if (!accepted) + return; + + m_settings->m_gdbCmd = m_ui.gdbEdit->text(); + m_settings->m_gdbEnv = m_ui.envEdit->text(); + m_settings->m_autoRun = m_ui.autoStartBox->isChecked(); + m_settings->m_autoQuit = m_ui.autoQuitBox->isChecked(); + + Core::ICore *coreIFace = m_pm->getObject<Core::ICore>(); + if (!coreIFace || !coreIFace->settings()) + return; + + QSettings *s = coreIFace->settings(); + + s->beginGroup("GdbOptions"); + s->setValue("Location", m_settings->m_gdbCmd); + s->setValue("Environment", m_settings->m_gdbEnv); + s->setValue("AutoRun", m_settings->m_autoRun); + s->setValue("AutoQuit", m_settings->m_autoQuit); + s->endGroup(); +} diff --git a/src/plugins/debugger/gdboptionpage.h b/src/plugins/debugger/gdboptionpage.h new file mode 100644 index 0000000000..0a83533742 --- /dev/null +++ b/src/plugins/debugger/gdboptionpage.h @@ -0,0 +1,106 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef GDBOPTIONPAGE_H +#define GDBOPTIONPAGE_H + +#include "ui_gdboptionpage.h" +#include "ui_gdbtypemacros.h" + +#include <coreplugin/dialogs/ioptionspage.h> + +#include <QtGui/QWidget> + +namespace ExtensionSystem { class PluginManager; } + +namespace Debugger { +namespace Internal { + +class GdbSettings; + +class GdbOptionPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + GdbOptionPage(GdbSettings *settings); + + QString name() const; + QString category() const; + QString trCategory() const; + + QWidget *createPage(QWidget *parent); + void finished(bool accepted); + +public slots: + void browse(); + +private: + ExtensionSystem::PluginManager *m_pm; + Ui::GdbOptionPage m_ui; + + GdbSettings *m_settings; +}; + +class TypeMacroPage : public Core::IOptionsPage +{ + Q_OBJECT + +public: + TypeMacroPage(GdbSettings *settings); + + QString name() const; + QString category() const; + QString trCategory() const; + + QWidget *createPage(QWidget *parent); + void finished(bool accepted); + +private slots: + void onScriptButton(); + void onAddButton(); + void onDelButton(); + void currentItemChanged(QTreeWidgetItem *item); + void updateButtonState(); + +private: + ExtensionSystem::PluginManager *m_pm; + Ui::TypeMacroPage m_ui; + + GdbSettings *m_settings; + QWidget *m_widget; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // GDBOPTIONPAGE_H diff --git a/src/plugins/debugger/gdboptionpage.ui b/src/plugins/debugger/gdboptionpage.ui new file mode 100644 index 0000000000..4b58d5d714 --- /dev/null +++ b/src/plugins/debugger/gdboptionpage.ui @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>GdbOptionPage</class> + <widget class="QWidget" name="GdbOptionPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>433</width> + <height>216</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout"> + <property name="spacing"> + <number>6</number> + </property> + <property name="margin"> + <number>9</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Gdb Debug Options</string> + </property> + <layout class="QGridLayout"> + <property name="margin"> + <number>9</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <item row="0" column="1"> + <widget class="QLineEdit" name="gdbEdit"/> + </item> + <item row="1" column="1" colspan="2"> + <widget class="QLineEdit" name="envEdit"/> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Gdb Location:</string> + </property> + <property name="buddy"> + <cstring>gdbEdit</cstring> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Environment:</string> + </property> + <property name="buddy"> + <cstring>envEdit</cstring> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QPushButton" name="pushButtonBrowse"> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../coreplugin/core.qrc"> + <normaloff>:/qworkbench/images/fileopen.png</normaloff>:/qworkbench/images/fileopen.png</iconset> + </property> + <property name="checkable"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QCheckBox" name="autoStartBox"> + <property name="text"> + <string>Auto run executable on debugger startup</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="autoQuitBox"> + <property name="text"> + <string>Quit debugger when the executable exits</string> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>415</width> + <height>41</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + <resources> + <include location="../coreplugin/core.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/plugins/debugger/gdbtypemacros.cpp b/src/plugins/debugger/gdbtypemacros.cpp new file mode 100644 index 0000000000..8a35720097 --- /dev/null +++ b/src/plugins/debugger/gdbtypemacros.cpp @@ -0,0 +1,214 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "gdboptionpage.h" +#include "gdbengine.h" +#include "imports.h" + +#include <extensionsystem/pluginmanager.h> +#include <coreplugin/icore.h> + +#include <QtCore/QSettings> +#include <QtCore/QByteArray> +#include <QtGui/QFileDialog> + +using namespace Debugger::Internal; + +TypeMacroPage::TypeMacroPage(GdbSettings *settings) +{ + m_pm = ExtensionSystem::PluginManager::instance(); + m_settings = settings; + + Core::ICore *coreIFace = m_pm->getObject<Core::ICore>(); + if (!coreIFace || !coreIFace->settings()) + return; + + QSettings *s = coreIFace->settings(); + s->beginGroup("GdbOptions"); + if (!s->contains("ScriptFile") && !s->contains("TypeMacros")) { + //insert qt4 defaults + m_settings->m_scriptFile = coreIFace->resourcePath() + + QLatin1String("/gdb/qt4macros"); + for (int i=0; i<3; ++i) { + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + switch(i) { + case 0: + stream << QString("printqstring") << (int)1; + m_settings->m_typeMacros.insert(QLatin1String("QString"), data); + break; + case 1: + stream << QString("printqcolor") << (int)0; + m_settings->m_typeMacros.insert(QLatin1String("QColor"), data); + break; + case 2: + stream << QString("printqfont") << (int)1; + m_settings->m_typeMacros.insert(QLatin1String("QFont"), data); + break; + } + } + + s->setValue("ScriptFile", m_settings->m_scriptFile); + s->setValue("TypeMacros", m_settings->m_typeMacros); + } else { + m_settings->m_scriptFile = s->value("ScriptFile", QString()).toString(); + m_settings->m_typeMacros = s->value("TypeMacros", QMap<QString,QVariant>()).toMap(); + } + s->endGroup(); +} + +QString TypeMacroPage::name() const +{ + return tr("Type Macros"); +} + +QString TypeMacroPage::category() const +{ + return "Debugger|Gdb"; +} + +QString TypeMacroPage::trCategory() const +{ + return tr("Debugger|Gdb"); +} + +QWidget *TypeMacroPage::createPage(QWidget *parent) +{ + QString macro; + int index; + + m_widget = new QWidget(parent); + m_ui.setupUi(m_widget); + + connect(m_ui.addButton, SIGNAL(clicked()), + this, SLOT(onAddButton())); + + connect(m_ui.delButton, SIGNAL(clicked()), + this, SLOT(onDelButton())); + + connect(m_ui.scriptButton, SIGNAL(clicked()), + this, SLOT(onScriptButton())); + + connect(m_ui.treeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), + this, SLOT(currentItemChanged(QTreeWidgetItem *))); + + connect(m_ui.typeEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updateButtonState())); + + connect(m_ui.macroEdit, SIGNAL(textChanged(const QString &)), + this, SLOT(updateButtonState())); + + QMap<QString, QVariant>::const_iterator i = m_settings->m_typeMacros.constBegin(); + while (i != m_settings->m_typeMacros.constEnd()) { + QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget); + QDataStream stream(i.value().toByteArray()); + stream >> macro >> index; + item->setText(0, i.key()); + item->setText(1, macro); + item->setData(0, Qt::UserRole, index); + ++i; + } + + m_ui.scriptEdit->setText(m_settings->m_scriptFile); + + updateButtonState(); + + return m_widget; +} + +void TypeMacroPage::finished(bool accepted) +{ + if (!accepted) + return; + + m_settings->m_typeMacros.clear(); + m_settings->m_scriptFile = m_ui.scriptEdit->text(); + + for (int i=0; i<m_ui.treeWidget->topLevelItemCount(); ++i) { + QTreeWidgetItem *item = m_ui.treeWidget->topLevelItem(i); + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << item->text(1) << item->data(0, Qt::UserRole).toInt(); + m_settings->m_typeMacros.insert(item->text(0), data); + } + + Core::ICore *coreIFace = m_pm->getObject<Core::ICore>(); + if (coreIFace && coreIFace->settings()) { + QSettings *s = coreIFace->settings(); + s->beginGroup("GdbOptions"); + s->setValue("ScriptFile", m_settings->m_scriptFile); + s->setValue("TypeMacros", m_settings->m_typeMacros); + s->endGroup(); + } +} + +void TypeMacroPage::onScriptButton() +{ + QString fileName = QFileDialog::getOpenFileName(m_widget, tr("Select Gdb Script")); + m_ui.scriptEdit->setText(fileName); + updateButtonState(); +} + +void TypeMacroPage::onAddButton() +{ + if (m_ui.typeEdit->text().isEmpty() || m_ui.macroEdit->text().isEmpty()) + return; + + QTreeWidgetItem *item = new QTreeWidgetItem(m_ui.treeWidget); + item->setText(0, m_ui.typeEdit->text()); + item->setText(1, m_ui.macroEdit->text()); + item->setData(0, Qt::UserRole, m_ui.parseAsBox->currentIndex()); + + updateButtonState(); +} + +void TypeMacroPage::onDelButton() +{ + if (QTreeWidgetItem *item = m_ui.treeWidget->currentItem()) + delete item; + updateButtonState(); +} + +void TypeMacroPage::currentItemChanged(QTreeWidgetItem *item) +{ + m_ui.typeEdit->setText(item ? item->text(0) : QString()); + m_ui.macroEdit->setText(item ? item->text(1) : QString()); + m_ui.parseAsBox->setCurrentIndex(item ? item->data(0, Qt::UserRole).toInt() : 0); + updateButtonState(); +} + +void TypeMacroPage::updateButtonState() +{ + m_ui.delButton->setEnabled(m_ui.treeWidget->currentItem() != 0); + m_ui.addButton->setDisabled(m_ui.typeEdit->text().isEmpty() + || m_ui.macroEdit->text().isEmpty()); +} diff --git a/src/plugins/debugger/gdbtypemacros.ui b/src/plugins/debugger/gdbtypemacros.ui new file mode 100644 index 0000000000..aa7215577b --- /dev/null +++ b/src/plugins/debugger/gdbtypemacros.ui @@ -0,0 +1,186 @@ +<ui version="4.0" > + <author></author> + <comment></comment> + <exportmacro></exportmacro> + <class>TypeMacroPage</class> + <widget class="QWidget" name="TypeMacroPage" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>519</width> + <height>238</height> + </rect> + </property> + <property name="windowTitle" > + <string>Form</string> + </property> + <layout class="QVBoxLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QGroupBox" name="groupBox" > + <property name="title" > + <string>Script File</string> + </property> + <layout class="QHBoxLayout" > + <property name="margin" > + <number>9</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item> + <widget class="QLineEdit" name="scriptEdit" /> + </item> + <item> + <widget class="QToolButton" name="scriptButton" > + <property name="minimumSize" > + <size> + <width>21</width> + <height>23</height> + </size> + </property> + <property name="text" > + <string>...</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="0" column="0" colspan="2" > + <widget class="QTreeWidget" name="treeWidget" > + <property name="rootIsDecorated" > + <bool>false</bool> + </property> + <column> + <property name="text" > + <string>Type</string> + </property> + </column> + <column> + <property name="text" > + <string>Macro</string> + </property> + </column> + </widget> + </item> + <item row="1" column="2" > + <widget class="QToolButton" name="addButton" > + <property name="minimumSize" > + <size> + <width>21</width> + <height>23</height> + </size> + </property> + <property name="text" > + <string>+</string> + </property> + <property name="icon" > + <iconset resource="gdbdebugger.qrc" >:/gdbdebugger/images/newitem.png</iconset> + </property> + </widget> + </item> + <item row="2" column="0" > + <widget class="QLabel" name="label_2" > + <property name="text" > + <string>Macro Name:</string> + </property> + </widget> + </item> + <item row="3" column="0" > + <widget class="QLabel" name="label_3" > + <property name="text" > + <string>Parse as:</string> + </property> + </widget> + </item> + <item row="2" column="1" > + <widget class="QLineEdit" name="macroEdit" /> + </item> + <item row="0" column="2" > + <layout class="QVBoxLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="delButton" > + <property name="minimumSize" > + <size> + <width>21</width> + <height>23</height> + </size> + </property> + <property name="text" > + <string>-</string> + </property> + <property name="icon" > + <iconset resource="gdbdebugger.qrc" >:/gdbdebugger/images/delete.png</iconset> + </property> + </widget> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" > + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="1" column="1" > + <widget class="QLineEdit" name="typeEdit" /> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="label" > + <property name="text" > + <string>Type:</string> + </property> + </widget> + </item> + <item row="3" column="1" > + <widget class="QComboBox" name="parseAsBox" > + <item> + <property name="text" > + <string>ASCII (char *)</string> + </property> + </item> + <item> + <property name="text" > + <string>Unicode (short)</string> + </property> + </item> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <pixmapfunction></pixmapfunction> + <resources> + <include location="gdbdebugger.qrc" /> + </resources> + <connections/> +</ui> diff --git a/src/plugins/debugger/idebuggerengine.h b/src/plugins/debugger/idebuggerengine.h new file mode 100644 index 0000000000..84edcb6ca5 --- /dev/null +++ b/src/plugins/debugger/idebuggerengine.h @@ -0,0 +1,88 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_IDEBUGGERENGINE_H +#define DEBUGGER_IDEBUGGERENGINE_H + +#include <QtCore/QObject> + +namespace Debugger { +namespace Internal { + +class IDebuggerEngine : public QObject +{ +public: + IDebuggerEngine(QObject *parent = 0) : QObject(parent) {} + + virtual void shutdown() = 0; + virtual void setToolTipExpression(const QPoint &pos, const QString &exp) = 0; + virtual bool startDebugger() = 0; + virtual void exitDebugger() = 0; + virtual void updateWatchModel() = 0; + + virtual void stepExec() = 0; + virtual void stepOutExec() = 0; + virtual void nextExec() = 0; + virtual void stepIExec() = 0; + virtual void nextIExec() = 0; + + virtual void continueInferior() = 0; + virtual void runInferior() = 0; + virtual void interruptInferior() = 0; + + virtual void runToLineExec(const QString &fileName, int lineNumber) = 0; + virtual void runToFunctionExec(const QString &functionName) = 0; + virtual void jumpToLineExec(const QString &fileName, int lineNumber) = 0; + virtual void assignValueInDebugger(const QString &expr, const QString &value) = 0; + virtual void executeDebuggerCommand(const QString &command) = 0; + + virtual void activateFrame(int index) = 0; + virtual void selectThread(int index) = 0; + + virtual void attemptBreakpointSynchronization() = 0; + + virtual void loadSessionData() = 0; + virtual void saveSessionData() = 0; + + virtual void reloadDisassembler() = 0; + + virtual void reloadModules() = 0; + virtual void loadSymbols(const QString &moduleName) = 0; + virtual void loadAllSymbols() = 0; + + virtual void reloadRegisters() = 0; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_IDEBUGGERENGINE_H diff --git a/src/plugins/debugger/images/breakpoint.svg b/src/plugins/debugger/images/breakpoint.svg new file mode 100644 index 0000000000..e8d63cc903 --- /dev/null +++ b/src/plugins/debugger/images/breakpoint.svg @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="14" + height="14" + id="svg2270" + sodipodi:version="0.32" + inkscape:version="0.45.1" + version="1.0" + sodipodi:docbase="D:\depot\research\main\editor\images" + sodipodi:docname="breakpoint.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2272"> + <linearGradient + id="linearGradient7029"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop7031" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop7033" /> + </linearGradient> + <linearGradient + id="linearGradient17794"> + <stop + style="stop-color:#f18383;stop-opacity:1;" + offset="0" + id="stop17798" /> + <stop + id="stop8006" + offset="0.3807947" + style="stop-color:#ed6767;stop-opacity:1;" /> + <stop + style="stop-color:#e62323;stop-opacity:1;" + offset="1" + id="stop17796" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient17794" + id="linearGradient24732" + gradientUnits="userSpaceOnUse" + x1="472.42236" + y1="436.79602" + x2="461.39169" + y2="424.95065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient17794" + id="linearGradient2438" + gradientUnits="userSpaceOnUse" + x1="472.42236" + y1="436.79602" + x2="461.39169" + y2="424.95065" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient17794" + id="radialGradient6052" + cx="466.73566" + cy="431.19708" + fx="466.73566" + fy="431.19708" + r="9.3095722" + gradientTransform="matrix(1,0,0,1.0057859,0,-2.4948735)" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7029" + id="linearGradient7035" + x1="6.75" + y1="0.5" + x2="6.75" + y2="12.5" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="32" + inkscape:cx="8.6877264" + inkscape:cy="6.3888789" + inkscape:document-units="px" + inkscape:current-layer="g25843" + width="14px" + height="14px" + inkscape:window-width="1280" + inkscape:window-height="998" + inkscape:window-x="0" + inkscape:window-y="0" + showgrid="true" + gridspacingx="0.5px" + gridspacingy="0.5px" + gridempspacing="2" + inkscape:grid-points="true" /> + <metadata + id="metadata2275"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g25843" + transform="matrix(0.7931251,0,0,0.7931251,-372.13374,-408.22195)"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient6052);fill-opacity:1.0;stroke:#c80000;stroke-width:1.43637741;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path22737" + sodipodi:cx="466.73566" + sodipodi:cy="431.19708" + sodipodi:rx="8.5913839" + sodipodi:ry="8.6452484" + d="M 475.32704 431.19708 A 8.5913839 8.6452484 0 1 1 458.14427,431.19708 A 8.5913839 8.6452484 0 1 1 475.32704 431.19708 z" + transform="matrix(0.8805346,0,0,0.8750503,66.41784,145.57686)" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient7035);fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path6058" + sodipodi:cx="6.75" + sodipodi:cy="6.5" + sodipodi:rx="5.75" + sodipodi:ry="6" + d="M 12.5 6.5 A 5.75 6 0 1 1 1,6.5 A 5.75 6 0 1 1 12.5 6.5 z" + transform="matrix(0.9867408,0,0,0.6304178,470.73423,515.01579)" /> + </g> + </g> +</svg> diff --git a/src/plugins/debugger/images/breakpoint_pending.svg b/src/plugins/debugger/images/breakpoint_pending.svg new file mode 100644 index 0000000000..e7094068b5 --- /dev/null +++ b/src/plugins/debugger/images/breakpoint_pending.svg @@ -0,0 +1,534 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="14" + height="14" + id="svg2270" + sodipodi:version="0.32" + inkscape:version="0.45.1" + version="1.0" + sodipodi:docbase="c:\depot\research\main\editor\images" + sodipodi:docname="pendingbreakpoint.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2272"> + <linearGradient + id="linearGradient7029"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop7031" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop7033" /> + </linearGradient> + <linearGradient + id="linearGradient17794"> + <stop + style="stop-color:#f18383;stop-opacity:1;" + offset="0" + id="stop17798" /> + <stop + id="stop8006" + offset="0.3807947" + style="stop-color:#ed6767;stop-opacity:1;" /> + <stop + style="stop-color:#e62323;stop-opacity:1;" + offset="1" + id="stop17796" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient17794" + id="linearGradient24732" + gradientUnits="userSpaceOnUse" + x1="472.42236" + y1="436.79602" + x2="461.39169" + y2="424.95065" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient17794" + id="linearGradient2438" + gradientUnits="userSpaceOnUse" + x1="472.42236" + y1="436.79602" + x2="461.39169" + y2="424.95065" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient17794" + id="radialGradient6052" + cx="466.73566" + cy="431.19708" + fx="466.73566" + fy="431.19708" + r="9.3095722" + gradientTransform="matrix(1,0,0,1.0057859,0,-2.4948735)" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient7029" + id="linearGradient7035" + x1="6.75" + y1="0.5" + x2="6.75" + y2="12.5" + gradientUnits="userSpaceOnUse" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="12.5" + x2="6.75" + y1="0.5" + x1="6.75" + id="linearGradient2228" + xlink:href="#linearGradient7029" + inkscape:collect="always" /> + <radialGradient + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,1.0057859,0,-2.4948735)" + r="9.3095722" + fy="431.19708" + fx="466.73566" + cy="431.19708" + cx="466.73566" + id="radialGradient2226" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2224" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2222" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + id="linearGradient2214"> + <stop + id="stop2216" + offset="0" + style="stop-color:#f18383;stop-opacity:1;" /> + <stop + style="stop-color:#ed6767;stop-opacity:1;" + offset="0.3807947" + id="stop2218" /> + <stop + id="stop2220" + offset="1" + style="stop-color:#e62323;stop-opacity:1;" /> + </linearGradient> + <linearGradient + id="linearGradient2208"> + <stop + id="stop2210" + offset="0" + style="stop-color:#ffffff;stop-opacity:1;" /> + <stop + id="stop2212" + offset="1" + style="stop-color:#ffffff;stop-opacity:0;" /> + </linearGradient> + <linearGradient + gradientTransform="matrix(1.2661544,0,0,1.2608351,469.23729,510.59508)" + y2="10.60876" + x2="10.981011" + y1="9.9135647" + x1="10.946278" + gradientUnits="userSpaceOnUse" + id="linearGradient4173" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1.2608351,0,0,1.2608351,475.7834,516.59183)" + y2="9.578125" + x2="5.859375" + y1="6.609375" + x1="5.953125" + gradientUnits="userSpaceOnUse" + id="linearGradient4159" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="11" + x2="11" + y1="10" + x1="11" + id="linearGradient4156" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + y2="521.00476" + x2="472.35138" + y1="519.11353" + x1="472.35138" + gradientTransform="matrix(1,0,0,1.0000093,10.421461,10.71221)" + gradientUnits="userSpaceOnUse" + id="linearGradient4138" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1,0,0,0.9687523,10.283691,16.9106)" + gradientUnits="userSpaceOnUse" + y2="521.00476" + x2="472.35138" + y1="519.11353" + x1="472.35138" + id="linearGradient4134" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2293" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2291" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + id="linearGradient2285"> + <stop + id="stop2287" + offset="0" + style="stop-color:#c80000;stop-opacity:1;" /> + <stop + id="stop2289" + offset="1" + style="stop-color:#ffa0a0;stop-opacity:1;" /> + </linearGradient> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient25826" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + id="linearGradient4128"> + <stop + id="stop4130" + offset="0" + style="stop-color:#ffffff;stop-opacity:0.78431374;" /> + <stop + id="stop4132" + offset="1" + style="stop-color:#ffffff;stop-opacity:0.23529412;" /> + </linearGradient> + <linearGradient + gradientTransform="matrix(1.2661544,0,0,1.2608351,469.23729,510.59508)" + y2="10.60876" + x2="10.981011" + y1="9.9135647" + x1="10.946278" + gradientUnits="userSpaceOnUse" + id="linearGradient2378" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="11.147234" + x2="11.02502" + y1="9.6892195" + x1="10.968282" + id="linearGradient2376" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1.2608351,0,0,1.2608351,475.7834,516.59183)" + y2="9.578125" + x2="5.859375" + y1="6.609375" + x1="5.953125" + gradientUnits="userSpaceOnUse" + id="linearGradient2374" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientUnits="userSpaceOnUse" + y2="11" + x2="11" + y1="10" + x1="11" + id="linearGradient2372" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + y2="521.00476" + x2="472.35138" + y1="519.11353" + x1="472.35138" + gradientTransform="matrix(1,0,0,0.6666648,10.303108,184.09099)" + gradientUnits="userSpaceOnUse" + id="linearGradient2370" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + y2="521.00476" + x2="472.35138" + y1="519.11353" + x1="472.35138" + gradientTransform="matrix(1,0,0,0.6666648,10.342666,173.98441)" + gradientUnits="userSpaceOnUse" + id="linearGradient2368" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + y2="521.00476" + x2="472.35138" + y1="519.11353" + x1="472.35138" + gradientTransform="matrix(1,0,0,1.0000093,10.421461,10.71221)" + gradientUnits="userSpaceOnUse" + id="linearGradient2366" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + gradientTransform="matrix(1,0,0,0.9687523,10.283691,16.9106)" + gradientUnits="userSpaceOnUse" + y2="521.00476" + x2="472.35138" + y1="519.11353" + x1="472.35138" + id="linearGradient2364" + xlink:href="#linearGradient4128" + inkscape:collect="always" /> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2362" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2360" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + id="linearGradient2354"> + <stop + id="stop2356" + offset="0" + style="stop-color:#c80000;stop-opacity:1;" /> + <stop + id="stop2358" + offset="1" + style="stop-color:#ffa0a0;stop-opacity:1;" /> + </linearGradient> + <linearGradient + y2="424.95065" + x2="461.39169" + y1="436.79602" + x1="472.42236" + gradientUnits="userSpaceOnUse" + id="linearGradient2352" + xlink:href="#linearGradient17794" + inkscape:collect="always" /> + <linearGradient + id="linearGradient2346"> + <stop + id="stop2348" + offset="0" + style="stop-color:#ffffff;stop-opacity:0.78431374;" /> + <stop + id="stop2350" + offset="1" + style="stop-color:#ffffff;stop-opacity:0.23529412;" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4128" + id="linearGradient2392" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,0.6666648,10.342666,173.98441)" + x1="472.35138" + y1="519.11353" + x2="472.35138" + y2="521.00476" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4128" + id="linearGradient2394" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1,0,0,0.6666648,10.303108,184.09099)" + x1="472.35138" + y1="519.11353" + x2="472.35138" + y2="521.00476" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient4128" + id="linearGradient2396" + gradientUnits="userSpaceOnUse" + x1="10.968282" + y1="9.6892195" + x2="11.02502" + y2="11.147234" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="45.254834" + inkscape:cx="13.678175" + inkscape:cy="6.8291478" + inkscape:document-units="px" + inkscape:current-layer="g25843" + width="14px" + height="14px" + inkscape:window-width="1280" + inkscape:window-height="998" + inkscape:window-x="44" + inkscape:window-y="58" + showgrid="true" + gridspacingx="0.5px" + gridspacingy="0.5px" + gridempspacing="2" + inkscape:grid-points="true" /> + <metadata + id="metadata2275"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <g + id="g25843" + transform="matrix(0.7931251,0,0,0.7931251,-372.13374,-408.22195)"> + <path + sodipodi:type="arc" + style="fill:url(#radialGradient6052);fill-opacity:1.0;stroke:#c80000;stroke-width:1.43637741;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path22737" + sodipodi:cx="466.73566" + sodipodi:cy="431.19708" + sodipodi:rx="8.5913839" + sodipodi:ry="8.6452484" + d="M 475.32704 431.19708 A 8.5913839 8.6452484 0 1 1 458.14427,431.19708 A 8.5913839 8.6452484 0 1 1 475.32704 431.19708 z" + transform="matrix(0.8805346,0,0,0.8750503,66.41784,145.57686)" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:url(#linearGradient7035);fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path6058" + sodipodi:cx="6.75" + sodipodi:cy="6.5" + sodipodi:rx="5.75" + sodipodi:ry="6" + d="M 12.5 6.5 A 5.75 6 0 1 1 1,6.5 A 5.75 6 0 1 1 12.5 6.5 z" + transform="matrix(0.9867408,0,0,0.6304178,470.73423,515.01579)" /> + <g + id="g2380" + inkscape:label="Layer 1" + transform="matrix(1.2608365,0,0,1.2633098,469.19929,514.69071)"> + <g + transform="matrix(0.7931251,0,0,0.7931251,-372.13374,-408.22195)" + id="g2382"> + <path + style="fill:#7f7f8c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 481.17723,524.15684 C 481.17723,524.15684 482.11277,524.68766 483.0584,524.68766 C 484.00403,524.68766 484.95974,524.15684 484.95974,524.15684 L 483.4493,526.03249 L 483.4493,527.83201 L 484.8944,528.56151 L 484.95974,531.09144 L 481.17723,531.09144 L 481.16963,528.69627 L 482.71594,527.80266 L 482.72456,526.04216 L 481.17723,524.15684 z " + id="path2442" + sodipodi:nodetypes="czcccccccccc" /> + <path + style="opacity:0.2;fill:#7f7f7f;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 481.17723,521.00476 L 481.17723,523.52643 L 482.43807,526.0481 L 481.17723,528.56977 L 481.17723,531.72186 L 484.95974,531.72186 L 484.95974,528.56977 L 483.6989,526.0481 L 484.95974,523.52643 L 484.95974,521.00476 L 481.17723,521.00476 z " + id="path2444" + sodipodi:nodetypes="ccccccccccc" /> + <path + sodipodi:nodetypes="cccccccccccccccccccccc" + id="path2174" + d="M 480.54682,520.37434 L 480.54681,523.52643 L 481.80765,526.0481 L 480.54681,528.56977 L 480.54682,531.72185 L 485.59016,531.72185 L 485.59015,528.56977 L 484.32932,526.0481 L 485.59015,523.52643 L 485.59016,520.37434 L 480.54682,520.37434 z M 481.80766,521.63517 L 484.32932,521.63517 L 484.32932,523.52643 L 483.46249,526.0481 L 484.32932,528.56977 L 484.32932,530.46102 L 481.80766,530.46102 L 481.80765,528.56977 L 482.72372,526.0481 L 481.80765,523.52643 L 481.80766,521.63517 z " + style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <rect + ry="1.2608351" + y="519.7439" + x="479.28598" + height="2.5216701" + width="7.565001" + id="rect2172" + style="opacity:1;fill:#7f2aff;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="1.2608351" + y="529.83063" + x="479.28598" + height="2.5216701" + width="7.565001" + id="rect2170" + style="opacity:1;fill:#6600ff;fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0.63040882" + y="520.0589" + x="480.17267" + height="1.2608176" + width="5.6737618" + id="rect4140" + style="opacity:1;fill:url(#linearGradient2392);fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + ry="0.63040882" + y="530.16577" + x="480.13324" + height="1.2608176" + width="5.6737618" + id="rect4144" + style="opacity:1;fill:url(#linearGradient2394);fill-opacity:1;stroke:none;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + sodipodi:nodetypes="cssz" + transform="matrix(1.2608351,0,0,1.2608351,469.1993,514.70058)" + id="path4161" + d="M 11.007811,9.9179701 C 10.386665,9.9257826 9.6414144,10.484263 9.5594939,10.992187 C 9.5017174,11.350414 12.494284,11.43273 12.441847,10.914063 C 12.40754,10.574731 11.628908,9.9101582 11.007811,9.9179701 z " + style="fill:url(#linearGradient2396);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + </g> + </g> + </g> +</svg> diff --git a/src/plugins/debugger/images/debugger_breakpoints.png b/src/plugins/debugger/images/debugger_breakpoints.png Binary files differnew file mode 100644 index 0000000000..75bb5f15bf --- /dev/null +++ b/src/plugins/debugger/images/debugger_breakpoints.png diff --git a/src/plugins/debugger/images/debugger_continue_small.png b/src/plugins/debugger/images/debugger_continue_small.png Binary files differnew file mode 100644 index 0000000000..4a3788c149 --- /dev/null +++ b/src/plugins/debugger/images/debugger_continue_small.png diff --git a/src/plugins/debugger/images/debugger_interrupt_small.png b/src/plugins/debugger/images/debugger_interrupt_small.png Binary files differnew file mode 100644 index 0000000000..815400cb58 --- /dev/null +++ b/src/plugins/debugger/images/debugger_interrupt_small.png diff --git a/src/plugins/debugger/images/debugger_start.png b/src/plugins/debugger/images/debugger_start.png Binary files differnew file mode 100644 index 0000000000..8eed81a899 --- /dev/null +++ b/src/plugins/debugger/images/debugger_start.png diff --git a/src/plugins/debugger/images/debugger_start_small.png b/src/plugins/debugger/images/debugger_start_small.png Binary files differnew file mode 100644 index 0000000000..4a3788c149 --- /dev/null +++ b/src/plugins/debugger/images/debugger_start_small.png diff --git a/src/plugins/debugger/images/debugger_stepinto_small.png b/src/plugins/debugger/images/debugger_stepinto_small.png Binary files differnew file mode 100644 index 0000000000..da36a5f670 --- /dev/null +++ b/src/plugins/debugger/images/debugger_stepinto_small.png diff --git a/src/plugins/debugger/images/debugger_steponeproc_small.png b/src/plugins/debugger/images/debugger_steponeproc_small.png Binary files differnew file mode 100644 index 0000000000..cf164c6604 --- /dev/null +++ b/src/plugins/debugger/images/debugger_steponeproc_small.png diff --git a/src/plugins/debugger/images/debugger_stepout_small.png b/src/plugins/debugger/images/debugger_stepout_small.png Binary files differnew file mode 100644 index 0000000000..e5eeeb32ad --- /dev/null +++ b/src/plugins/debugger/images/debugger_stepout_small.png diff --git a/src/plugins/debugger/images/debugger_stepover_small.png b/src/plugins/debugger/images/debugger_stepover_small.png Binary files differnew file mode 100644 index 0000000000..e8a5d08046 --- /dev/null +++ b/src/plugins/debugger/images/debugger_stepover_small.png diff --git a/src/plugins/debugger/images/debugger_stepoverproc_small.png b/src/plugins/debugger/images/debugger_stepoverproc_small.png Binary files differnew file mode 100644 index 0000000000..34e712da06 --- /dev/null +++ b/src/plugins/debugger/images/debugger_stepoverproc_small.png diff --git a/src/plugins/debugger/images/debugger_stop_small.png b/src/plugins/debugger/images/debugger_stop_small.png Binary files differnew file mode 100644 index 0000000000..1063d08998 --- /dev/null +++ b/src/plugins/debugger/images/debugger_stop_small.png diff --git a/src/plugins/debugger/images/delete.png b/src/plugins/debugger/images/delete.png Binary files differnew file mode 100644 index 0000000000..e4139afc55 --- /dev/null +++ b/src/plugins/debugger/images/delete.png diff --git a/src/plugins/debugger/images/done.png b/src/plugins/debugger/images/done.png Binary files differnew file mode 100644 index 0000000000..b5238f7680 --- /dev/null +++ b/src/plugins/debugger/images/done.png diff --git a/src/plugins/debugger/images/empty.svg b/src/plugins/debugger/images/empty.svg new file mode 100644 index 0000000000..46209de391 --- /dev/null +++ b/src/plugins/debugger/images/empty.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="14" + height="14" + id="svg2243" + sodipodi:version="0.32" + inkscape:version="0.45.1" + version="1.0" + sodipodi:docbase="c:\depot\research\main\editor\images" + sodipodi:docname="location.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2245"> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="64" + inkscape:cx="8.3920091" + inkscape:cy="7.4257237" + inkscape:document-units="px" + inkscape:current-layer="layer1" + width="14px" + height="14px" + showborder="true" + inkscape:window-width="1600" + inkscape:window-height="1174" + inkscape:window-x="0" + inkscape:window-y="0" + gridempspacing="2" + showgrid="true" + inkscape:grid-points="true" + gridspacingx="0.5px" + gridspacingy="0.5px" /> + <metadata + id="metadata2248"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + </g> +</svg> diff --git a/src/plugins/debugger/images/error.png b/src/plugins/debugger/images/error.png Binary files differnew file mode 100644 index 0000000000..700530438a --- /dev/null +++ b/src/plugins/debugger/images/error.png diff --git a/src/plugins/debugger/images/location.svg b/src/plugins/debugger/images/location.svg new file mode 100644 index 0000000000..afb70052a1 --- /dev/null +++ b/src/plugins/debugger/images/location.svg @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://web.resource.org/cc/" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="14" + height="14" + id="svg2243" + sodipodi:version="0.32" + inkscape:version="0.45.1" + version="1.0" + sodipodi:docbase="c:\depot\research\main\editor\images" + sodipodi:docname="location.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape"> + <defs + id="defs2245"> + <linearGradient + id="linearGradient3134"> + <stop + style="stop-color:#dcdc23;stop-opacity:1;" + offset="0" + id="stop3136" /> + <stop + id="stop5080" + offset="0.64285713" + style="stop-color:#e5d044;stop-opacity:1;" /> + <stop + style="stop-color:#b89354;stop-opacity:1;" + offset="1" + id="stop3138" /> + </linearGradient> + <linearGradient + id="linearGradient3137"> + <stop + style="stop-color:#ffffff;stop-opacity:0.86274511;" + offset="0" + id="stop3139" /> + <stop + style="stop-color:#ffffff;stop-opacity:0;" + offset="1" + id="stop3141" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3137" + id="linearGradient3143" + x1="6.5" + y1="3" + x2="6.515625" + y2="12.180227" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3134" + id="linearGradient3140" + x1="6.5" + y1="3.015625" + x2="6.484375" + y2="11.984375" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="64" + inkscape:cx="8.3920091" + inkscape:cy="7.4257237" + inkscape:document-units="px" + inkscape:current-layer="layer1" + width="14px" + height="14px" + showborder="true" + inkscape:window-width="1600" + inkscape:window-height="1174" + inkscape:window-x="0" + inkscape:window-y="0" + gridempspacing="2" + showgrid="true" + inkscape:grid-points="true" + gridspacingx="0.5px" + gridspacingy="0.5px" /> + <metadata + id="metadata2248"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + style="fill:url(#linearGradient3140);fill-opacity:1.0;fill-rule:evenodd;stroke:#b18b1b;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 6.5,3 L 6.5,5.5 L 0.5,5.5 L 0.5,9.5 L 6.5,9.5 L 6.5,12 L 13.125,7.5 L 6.5,3 z " + id="path2216" + sodipodi:nodetypes="cccccccc" /> + <path + style="fill:url(#linearGradient3143);fill-opacity:1.0;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;opacity:1" + d="M 6.5,3 L 6.5,5.5 L 0.5,5.5 L 0.5,7.5 C 7,6.5 7.5,9.5 13,7.5 L 6.5,3 z " + id="path5066" + sodipodi:nodetypes="cccccc" /> + </g> +</svg> diff --git a/src/plugins/debugger/images/newitem.png b/src/plugins/debugger/images/newitem.png Binary files differnew file mode 100644 index 0000000000..7e26ea9b15 --- /dev/null +++ b/src/plugins/debugger/images/newitem.png diff --git a/src/plugins/debugger/images/running.png b/src/plugins/debugger/images/running.png Binary files differnew file mode 100644 index 0000000000..1cf8888ecb --- /dev/null +++ b/src/plugins/debugger/images/running.png diff --git a/src/plugins/debugger/imports.h b/src/plugins/debugger/imports.h new file mode 100644 index 0000000000..8a2edd4c39 --- /dev/null +++ b/src/plugins/debugger/imports.h @@ -0,0 +1,49 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_IMPORTS_H +#define DEBUGGER_IMPORTS_H + +// FIXME: Plan is to remove this file. It's needed to get "harmless" +// replacements for GH internals in the standalone version + +#ifndef GDBDEBUGGERLEAN + +#include <texteditor/basetextmark.h> + +#else + +#include "lean.h" + +#endif + +#endif // DEBUGGER_IMPORTS_H diff --git a/src/plugins/debugger/mode.cpp b/src/plugins/debugger/mode.cpp new file mode 100644 index 0000000000..2023cdd7ee --- /dev/null +++ b/src/plugins/debugger/mode.cpp @@ -0,0 +1,233 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "mode.h" + +#include "assert.h" +#include "debuggerconstants.h" +#include "debuggermanager.h" + +#include <coreplugin/coreconstants.h> +#include <coreplugin/icore.h> +#include <coreplugin/modemanager.h> +#include <coreplugin/uniqueidmanager.h> +#include <coreplugin/actionmanager/actionmanagerinterface.h> +#include <coreplugin/editormanager/editormanager.h> +#include <coreplugin/minisplitter.h> +#include <coreplugin/findplaceholder.h> +#include <coreplugin/outputpane.h> +#include <coreplugin/navigationwidget.h> +#include <coreplugin/rightpane.h> +#include <projectexplorer/projectexplorerconstants.h> + +#include <QtCore/QDebug> +#include <QtCore/QSettings> +#include <QtGui/QDockWidget> +#include <QtGui/QLabel> +#include <QtGui/QMainWindow> +#include <QtGui/QVBoxLayout> +#include <QtGui/QWidget> + +using namespace Core; +using namespace ExtensionSystem; +using namespace Debugger; +using namespace Debugger::Internal; +using namespace Debugger::Constants; + + +DebugMode::DebugMode(DebuggerManager *manager, QObject *parent) + : BaseMode(tr("Debug"), Constants::MODE_DEBUG, + QIcon(":/fancyactionbar/images/mode_Debug.png"), + Constants::P_MODE_DEBUG, 0, parent), + m_manager(manager) +{ + IDebuggerManagerAccessForDebugMode *managerAccess = + m_manager->debugModeInterface(); + UniqueIDManager *uidm = + PluginManager::instance()->getObject<ICore>()->uniqueIDManager(); + QList<int> context; + context.append(uidm->uniqueIdentifier(Core::Constants::C_EDITORMANAGER)); + context.append(uidm->uniqueIdentifier(Constants::C_GDBDEBUGGER)); + context.append(uidm->uniqueIdentifier(Core::Constants::C_NAVIGATION_PANE)); + setContext(context); + + QBoxLayout *editorHolderLayout = new QVBoxLayout; + editorHolderLayout->setMargin(0); + editorHolderLayout->setSpacing(0); + editorHolderLayout->addWidget(new EditorManagerPlaceHolder(this)); + editorHolderLayout->addWidget(new FindToolBarPlaceHolder(this)); + + QWidget *editorAndFindWidget = new QWidget; + editorAndFindWidget->setLayout(editorHolderLayout); + + MiniSplitter *rightPaneSplitter = new MiniSplitter; + rightPaneSplitter->addWidget(editorAndFindWidget); + rightPaneSplitter->addWidget(new RightPanePlaceHolder(this)); + rightPaneSplitter->setStretchFactor(0, 1); + rightPaneSplitter->setStretchFactor(1, 0); + + QWidget *centralWidget = new QWidget; + QBoxLayout *toolBarAddingLayout = new QVBoxLayout(centralWidget); + toolBarAddingLayout->setMargin(0); + toolBarAddingLayout->setSpacing(0); + toolBarAddingLayout->addWidget(rightPaneSplitter); + + m_manager->mainWindow()->setCentralWidget(centralWidget); + + MiniSplitter *splitter = new MiniSplitter; + splitter->addWidget(m_manager->mainWindow()); + splitter->addWidget(new OutputPanePlaceHolder(this)); + splitter->setStretchFactor(0, 10); + splitter->setStretchFactor(1, 0); + splitter->setOrientation(Qt::Vertical); + + MiniSplitter *splitter2 = new MiniSplitter; + splitter2 = new MiniSplitter; + splitter2->addWidget(new NavigationWidgetPlaceHolder(this)); + splitter2->addWidget(splitter); + splitter2->setStretchFactor(0, 0); + splitter2->setStretchFactor(1, 1); + + setWidget(splitter2); + + QToolBar *toolBar = createToolBar(); + toolBarAddingLayout->addWidget(toolBar); + + managerAccess->createDockWidgets(); + m_manager->setSimpleDockWidgetArrangement(); + readSettings(); + + connect(ModeManager::instance(), SIGNAL(currentModeChanged(Core::IMode*)), + this, SLOT(focusCurrentEditor(Core::IMode*))); + widget()->setFocusProxy(EditorManager::instance()); +} + +DebugMode::~DebugMode() +{ + // Make sure the editor manager does not get deleted + EditorManager::instance()->setParent(0); +} + +void DebugMode::shutdown() +{ + writeSettings(); +} + +QToolBar *DebugMode::createToolBar() +{ + IDebuggerManagerAccessForDebugMode *managerAccess = + m_manager->debugModeInterface(); + + Core::ActionManagerInterface *am = + ExtensionSystem::PluginManager::instance() + ->getObject<Core::ICore>()->actionManager(); + QToolBar *debugToolBar = new QToolBar; + debugToolBar->addAction(am->command(ProjectExplorer::Constants::DEBUG)->action()); + debugToolBar->addAction(am->command(Constants::INTERRUPT)->action()); + debugToolBar->addAction(am->command(Constants::NEXT)->action()); + debugToolBar->addAction(am->command(Constants::STEP)->action()); + debugToolBar->addAction(am->command(Constants::STEPOUT)->action()); + debugToolBar->addSeparator(); + debugToolBar->addAction(am->command(Constants::STEPI)->action()); + debugToolBar->addAction(am->command(Constants::NEXTI)->action()); + debugToolBar->addSeparator(); + debugToolBar->addWidget(new QLabel(tr("Threads:"))); + + QComboBox *threadBox = new QComboBox; + threadBox->setModel(m_manager->threadsModel()); + connect(threadBox, SIGNAL(activated(int)), + managerAccess->threadsWindow(), SIGNAL(threadSelected(int))); + debugToolBar->addWidget(threadBox); + + QWidget *stretch = new QWidget; + stretch->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + debugToolBar->addWidget(stretch); + + QMenu *viewMenu = new QMenu(debugToolBar); + m_toggleLockedAction = new QAction(tr("Locked"), viewMenu); + m_toggleLockedAction->setCheckable(true); + m_toggleLockedAction->setChecked(true); + connect(m_toggleLockedAction, SIGNAL(toggled(bool)), + m_manager, SLOT(setLocked(bool))); + foreach (QDockWidget *dockWidget, managerAccess->dockWidgets()) + viewMenu->addAction(dockWidget->toggleViewAction()); + viewMenu->addSeparator(); + viewMenu->addAction(m_toggleLockedAction); + viewMenu->addSeparator(); + + QAction *resetToSimpleAction = viewMenu->addAction(tr("Reset to default layout")); + connect(resetToSimpleAction, SIGNAL(triggered()), + m_manager, SLOT(setSimpleDockWidgetArrangement())); + QToolButton *viewMenuButton = new QToolButton(debugToolBar); + viewMenuButton->setText(tr("View ")); + viewMenuButton->setPopupMode(QToolButton::InstantPopup); + viewMenuButton->setMenu(viewMenu); + debugToolBar->addWidget(viewMenuButton); + + return debugToolBar; +} + +void DebugMode::focusCurrentEditor(IMode *mode) +{ + if (mode != this) + return; + + EditorManager *editorManager = EditorManager::instance(); + + if (editorManager->currentEditor()) + editorManager->currentEditor()->widget()->setFocus(); +} + +void DebugMode::writeSettings() const +{ + QSettings *s = settings(); + QWB_ASSERT(m_manager, return); + QWB_ASSERT(m_manager->mainWindow(), return); + s->beginGroup(QLatin1String("DebugMode")); + s->setValue(QLatin1String("State"), m_manager->mainWindow()->saveState()); + s->setValue(QLatin1String("Locked"), m_toggleLockedAction->isChecked()); + s->endGroup(); +} + +void DebugMode::readSettings() +{ + QSettings *s = settings(); + s->beginGroup(QLatin1String("DebugMode")); + m_manager->mainWindow()->restoreState(s->value(QLatin1String("State"), QByteArray()).toByteArray()); + m_toggleLockedAction->setChecked(s->value(QLatin1String("Locked"), true).toBool()); + s->endGroup(); +} + +QSettings *DebugMode::settings() +{ + return PluginManager::instance()->getObject<ICore>()->settings(); +} diff --git a/src/plugins/debugger/mode.h b/src/plugins/debugger/mode.h new file mode 100644 index 0000000000..15506d0cac --- /dev/null +++ b/src/plugins/debugger/mode.h @@ -0,0 +1,84 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_DEBUGMODE_H +#define DEBUGGER_DEBUGMODE_H + +#include <coreplugin/basemode.h> + +#include <QtCore/QList> +#include <QtCore/QPointer> + +QT_BEGIN_NAMESPACE +class QAction; +class QDockWidget; +class QMainWindow; +class QSettings; +class QSplitter; +class QToolBar; +class QWidget; +QT_END_NAMESPACE + +namespace Debugger { +namespace Internal { + +class DebuggerManager; + +class DebugMode : public Core::BaseMode +{ + Q_OBJECT + +public: + DebugMode(DebuggerManager *manager, QObject *parent = 0); + ~DebugMode(); + + // IMode + void activated(); + void shutdown(); + static QSettings *settings(); + +private slots: + void focusCurrentEditor(Core::IMode *mode); + +private: + QToolBar *createToolBar(); + void writeSettings() const; + void readSettings(); + + QPointer<DebuggerManager> m_manager; + QAction *m_toggleLockedAction; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_DEBUGMODE_H diff --git a/src/plugins/debugger/mode.ui b/src/plugins/debugger/mode.ui new file mode 100644 index 0000000000..da44cf38b4 --- /dev/null +++ b/src/plugins/debugger/mode.ui @@ -0,0 +1,76 @@ +<ui version="4.0" > + <class>DebugMode</class> + <widget class="QWidget" name="DebugMode" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>558</width> + <height>353</height> + </rect> + </property> + <property name="windowTitle" > + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3" > + <property name="spacing" > + <number>0</number> + </property> + <property name="margin" > + <number>0</number> + </property> + <item> + <widget class="QSplitter" name="vSplitter" > + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <widget class="QWidget" native="1" name="editorHolder" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Preferred" hsizetype="Preferred" > + <horstretch>10</horstretch> + <verstretch>10</verstretch> + </sizepolicy> + </property> + </widget> + <widget class="QWidget" name="layoutWidget" > + <layout class="QVBoxLayout" name="verticalLayout" > + <property name="spacing" > + <number>0</number> + </property> + <item> + <layout class="QVBoxLayout" name="toolbarLayout" > + <property name="spacing" > + <number>0</number> + </property> + </layout> + </item> + <item> + <widget class="QSplitter" name="hSplitter" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <widget class="QTabWidget" name="bottomTabWidget" > + <property name="sizePolicy" > + <sizepolicy vsizetype="Expanding" hsizetype="Expanding" > + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="tabPosition" > + <enum>QTabWidget::South</enum> + </property> + <property name="tabShape" > + <enum>QTabWidget::Rounded</enum> + </property> + </widget> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/debugger/moduleshandler.cpp b/src/plugins/debugger/moduleshandler.cpp new file mode 100644 index 0000000000..458482b385 --- /dev/null +++ b/src/plugins/debugger/moduleshandler.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "moduleshandler.h" + +#include "assert.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QList> +#include <QtCore/QTextStream> + +#include <QtGui/QAction> +#include <QtGui/QMainWindow> +#include <QtGui/QStandardItemModel> +#include <QtGui/QSortFilterProxyModel> + +using namespace Debugger; +using namespace Debugger::Internal; + + +////////////////////////////////////////////////////////////////// +// +// ModulesModel +// +////////////////////////////////////////////////////////////////// + +class Debugger::Internal::ModulesModel : public QAbstractItemModel +{ +public: + ModulesModel(ModulesHandler *parent) + : QAbstractItemModel(parent) + {} + + // QAbstractItemModel + int columnCount(const QModelIndex &parent) const + { return parent.isValid() ? 0 : 4; } + int rowCount(const QModelIndex &parent) const + { return parent.isValid() ? 0 : m_modules.size(); } + QModelIndex parent(const QModelIndex &) const { return QModelIndex(); } + QModelIndex index(int row, int column, const QModelIndex &) const + { return createIndex(row, column); } + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + + void clearModel() { if (!m_modules.isEmpty()) { m_modules.clear(); update(); } } + void update() { reset(); } + +public: + QList<Module> m_modules; +}; + +QVariant ModulesModel::headerData(int section, + Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + static QString headers[] = { + tr("Module name") + " ", + tr("Symbols read") + " ", + tr("Start address") + " ", + tr("End addAress") + " " + }; + return headers[section]; + } + return QVariant(); +} + +QVariant ModulesModel::data(const QModelIndex &index, int role) const +{ + //static const QIcon icon(":/gdbdebugger/images/breakpoint.svg"); + //static const QIcon icon2(":/gdbdebugger/images/breakpoint_pending.svg"); + + int row = index.row(); + if (row < 0 || row >= m_modules.size()) + return QVariant(); + + const Module &module = m_modules.at(row); + + switch (index.column()) { + case 0: + if (role == Qt::DisplayRole) + return module.moduleName; + // FIXME: add icons + //if (role == Qt::DecorationRole) + // return module.symbolsRead ? icon2 : icon; + break; + case 1: + if (role == Qt::DisplayRole) + return module.symbolsRead ? "yes" : "no"; + break; + case 2: + if (role == Qt::DisplayRole) + return module.startAddress; + break; + case 3: + if (role == Qt::DisplayRole) + return module.endAddress; + break; + } + return QVariant(); +} + +bool ModulesModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + return QAbstractItemModel::setData(index, value, role); +} + + +////////////////////////////////////////////////////////////////// +// +// ModulesHandler +// +////////////////////////////////////////////////////////////////// + +ModulesHandler::ModulesHandler() +{ + m_model = new ModulesModel(this); + m_proxyModel = new QSortFilterProxyModel(this); + m_proxyModel->setSourceModel(m_model); +} + +QAbstractItemModel *ModulesHandler::model() const +{ + return m_proxyModel; +} + +void ModulesHandler::removeAll() +{ + m_model->clearModel(); +} + + +void ModulesHandler::setModules(const QList<Module> &modules) +{ + m_model->m_modules = modules; + m_model->update(); +} + +QList<Module> ModulesHandler::modules() const +{ + return m_model->m_modules; +} diff --git a/src/plugins/debugger/moduleshandler.h b/src/plugins/debugger/moduleshandler.h new file mode 100644 index 0000000000..49efe16a72 --- /dev/null +++ b/src/plugins/debugger/moduleshandler.h @@ -0,0 +1,104 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_MODULESHANDLER_H +#define DEBUGGER_MODULESHANDLER_H + +#include <QtCore/QList> +#include <QtCore/QObject> + +QT_BEGIN_NAMESPACE +class QAbstractItemModel; +class QSortFilterProxyModel; +QT_END_NAMESPACE + + +namespace Debugger { +namespace Internal { + +class ModulesModel; + +enum ModulesModelRoles +{ + DisplaySourceRole = Qt::UserRole, + LoadSymbolsRole, + LoadAllSymbolsRole +}; + + +////////////////////////////////////////////////////////////////// +// +// Module +// +////////////////////////////////////////////////////////////////// + +class Module +{ +public: + Module() : symbolsRead(false) {} + +public: + QString moduleName; + bool symbolsRead; + QString startAddress; + QString endAddress; +}; + + +////////////////////////////////////////////////////////////////// +// +// ModulesHandler +// +////////////////////////////////////////////////////////////////// + +class ModulesHandler : public QObject +{ + Q_OBJECT + +public: + ModulesHandler(); + + QAbstractItemModel *model() const; + + void setModules(const QList<Module> &modules); + QList<Module> modules() const; + void removeAll(); + +private: + ModulesModel *m_model; + QSortFilterProxyModel *m_proxyModel; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_MODULESHANDLER_H diff --git a/src/plugins/debugger/moduleswindow.cpp b/src/plugins/debugger/moduleswindow.cpp new file mode 100644 index 0000000000..f98db1ce1f --- /dev/null +++ b/src/plugins/debugger/moduleswindow.cpp @@ -0,0 +1,136 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "moduleswindow.h" +#include "moduleshandler.h" // for model roles + +#include <QAction> +#include <QDebug> +#include <QHeaderView> +#include <QMenu> +#include <QResizeEvent> +#include <QToolButton> + + +using Debugger::Internal::ModulesWindow; + +ModulesWindow::ModulesWindow(QWidget *parent) + : QTreeView(parent), m_alwaysResizeColumnsToContents(false) +{ + setWindowTitle(tr("Modules")); + setSortingEnabled(true); + setAlternatingRowColors(true); + setRootIsDecorated(false); + setIconSize(QSize(10, 10)); +} + +void ModulesWindow::resizeEvent(QResizeEvent *event) +{ + //QHeaderView *hv = header(); + //int totalSize = event->size().width() - 110; + //hv->resizeSection(0, totalSize / 4); + //hv->resizeSection(1, totalSize / 4); + //hv->resizeSection(2, totalSize / 4); + //hv->resizeSection(3, totalSize / 4); + //hv->resizeSection(0, 60); + //hv->resizeSection(1, (totalSize * 50) / 100); + //hv->resizeSection(2, (totalSize * 50) / 100); + //hv->resizeSection(3, 50); + //setColumnHidden(3, true); + QTreeView::resizeEvent(event); +} + +void ModulesWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + QModelIndex index = indexAt(ev->pos()); + index = index.sibling(index.row(), 0); + QString name = model()->data(index).toString(); + + QMenu menu; + QAction *act0 = new QAction("Update module list", &menu); + QAction *act1 = new QAction("Adjust column widths to contents", &menu); + QAction *act2 = new QAction("Always adjust column widths to contents", &menu); + act2->setCheckable(true); + act2->setChecked(m_alwaysResizeColumnsToContents); + QAction *act3 = new QAction("Show source files for module " + name, &menu); + QAction *act4 = new QAction("Load symbols for all modules", &menu); + QAction *act5 = new QAction("Load symbols for module " + name, &menu); + act5->setDisabled(name.isEmpty()); + + menu.addAction(act0); + menu.addAction(act4); + menu.addAction(act5); + menu.addSeparator(); + menu.addAction(act1); + menu.addAction(act2); + + QAction *act = menu.exec(ev->globalPos()); + + if (act == act0) + emit reloadModulesRequested(); + else if (act == act1) + resizeColumnsToContents(); + else if (act == act2) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); + else if (act == act3) + emit displaySourceRequested(name); + else if (act == act4) + emit loadAllSymbolsRequested(); + else if (act == act5) + emit loadSymbolsRequested(name); +} + +void ModulesWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + resizeColumnToContents(1); + resizeColumnToContents(2); +} + +void ModulesWindow::setAlwaysResizeColumnsToContents(bool on) +{ + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + header()->setResizeMode(1, mode); + header()->setResizeMode(2, mode); + header()->setResizeMode(3, mode); + //setColumnHidden(3, true); +} + +void ModulesWindow::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setAlwaysResizeColumnsToContents(true); +} + diff --git a/src/plugins/debugger/moduleswindow.h b/src/plugins/debugger/moduleswindow.h new file mode 100644 index 0000000000..a014ff8e6c --- /dev/null +++ b/src/plugins/debugger/moduleswindow.h @@ -0,0 +1,71 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_MODULESWINDOW_H +#define DEBUGGER_MODULESWINDOW_H + +#include <QTreeView> + +namespace Debugger { +namespace Internal { + +class ModulesWindow : public QTreeView +{ + Q_OBJECT + +public: + explicit ModulesWindow(QWidget *parent = 0); + +signals: + void reloadModulesRequested(); + void displaySourceRequested(const QString &modulesName); + void loadSymbolsRequested(const QString &modulesName); + void loadAllSymbolsRequested(); + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on); + +protected: + void resizeEvent(QResizeEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + void setModel(QAbstractItemModel *model); + +private: + bool m_alwaysResizeColumnsToContents; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_MODULESWINDOW_H + diff --git a/src/plugins/debugger/procinterrupt.cpp b/src/plugins/debugger/procinterrupt.cpp new file mode 100644 index 0000000000..29d4d5803d --- /dev/null +++ b/src/plugins/debugger/procinterrupt.cpp @@ -0,0 +1,182 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "procinterrupt.h" + +#ifdef Q_OS_WIN +#include <windows.h> +#include <Tlhelp32.h> + +using namespace Debugger::Internal; + +typedef HANDLE (WINAPI *PtrCreateRemoteThread)( + HANDLE hProcess, + LPSECURITY_ATTRIBUTES lpThreadAttributes, + SIZE_T dwStackSize, + LPTHREAD_START_ROUTINE lpStartAddress, + LPVOID lpParameter, + DWORD dwCreationFlags, + LPDWORD lpThreadId); + +PtrCreateRemoteThread resolveCreateRemoteThread() +{ + HINSTANCE hLib = LoadLibraryA("Kernel32"); + return (PtrCreateRemoteThread)GetProcAddress(hLib, "CreateRemoteThread"); +} + +DWORD findProcessId(DWORD parentId) +{ + HANDLE hProcList = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + + PROCESSENTRY32 procEntry; + procEntry.dwSize = sizeof(PROCESSENTRY32); + + DWORD procId = 0; + + BOOL moreProc = Process32First(hProcList, &procEntry); + while (moreProc) { + if (procEntry.th32ParentProcessID == parentId) { + procId = procEntry.th32ProcessID; + break; + } + moreProc = Process32Next(hProcList, &procEntry); + } + + CloseHandle(hProcList); + return procId; +} +#else + +#include <QtCore/QLatin1String> +#include <QtCore/QString> +#include <QtCore/QDir> +#include <QtCore/QFileInfoList> +#include <QtCore/QByteArray> +#include <QtCore/QDebug> + +#include <sys/types.h> +#include <signal.h> + +#include <sys/sysctl.h> + +#define OPProcessValueUnknown UINT_MAX + +/* Mac OS X +int OPParentIDForProcessID(int pid) + // Returns the parent process id for the given process id (pid) +{ + struct kinfo_proc info; + size_t length = sizeof(struct kinfo_proc); + int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; + if (sysctl(mib, 4, &info, &length, NULL, 0) < 0) + return OPProcessValueUnknown; + if (length == 0) + return OPProcessValueUnknown; + return info.kp_eproc.e_ppid; +} +*/ + +int findParentProcess(int procId) +{ + QFile statFile(QLatin1String("/proc/") + QString::number(procId) + + QLatin1String("/stat")); + if (!statFile.open(QIODevice::ReadOnly)) + return -1; + + QByteArray line = statFile.readLine(); + line = line.mid(line.indexOf(')') + 4); + //qDebug() << "1: " << line; + line = line.left(line.indexOf(' ')); + //qDebug() << "2: " << line; + + return QString(line).toInt(); +} + +int findChildProcess(int parentId) +{ + QDir proc(QLatin1String("/proc")); + QFileInfoList procList = proc.entryInfoList(QDir::Dirs); + foreach (const QFileInfo &info, procList) { + int procId = 0; + bool ok = false; + procId = info.baseName().toInt(&ok); + if (!ok || !procId) + continue; + + if (findParentProcess(procId) == parentId) + return procId; + } + + return -1; +} + +#endif + +bool Debugger::Internal::interruptProcess(int pID) +{ +#ifdef Q_OS_WIN + DWORD pid = pID; + if (!pid) + return false; + + PtrCreateRemoteThread libFunc = resolveCreateRemoteThread(); + if (libFunc) { + DWORD dwThreadId = 0; + HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS, false, pid); + HANDLE hthread = libFunc(hproc, NULL, 0, (LPTHREAD_START_ROUTINE)DebugBreak, 0, 0, &dwThreadId); + CloseHandle(hthread); + if (dwThreadId) + return true; + } +#else + int procId = pID; + if (procId != -1) { + if (kill(procId, 2) == 0) + return true; + } + +#endif + + return false; +} + +bool Debugger::Internal::interruptChildProcess(Q_PID parentPID) +{ +#ifdef WIN32 + DWORD pid = findProcessId(parentPID->dwProcessId); + return interruptProcess(pid); +#else + int procId = findChildProcess(parentPID); + //qDebug() << "INTERRUPTING PROCESS" << procId; + return interruptProcess(procId); +#endif +} diff --git a/src/plugins/debugger/procinterrupt.h b/src/plugins/debugger/procinterrupt.h new file mode 100644 index 0000000000..0fcf27b0f1 --- /dev/null +++ b/src/plugins/debugger/procinterrupt.h @@ -0,0 +1,47 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_PROCINTERRUPT_H +#define DEBUGGER_PROCINTERRUPT_H + +#include <QtCore/QProcess> + +namespace Debugger { +namespace Internal { + +bool interruptProcess(int pID); +bool interruptChildProcess(Q_PID parentPID); + +} // Internal +} // GdbDebugger + +#endif // DEBUGGER_PROCINTERRUPT_H diff --git a/src/plugins/debugger/registerhandler.cpp b/src/plugins/debugger/registerhandler.cpp new file mode 100644 index 0000000000..4c70e2339c --- /dev/null +++ b/src/plugins/debugger/registerhandler.cpp @@ -0,0 +1,128 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "registerhandler.h" + +#include "assert.h" +#include "debuggerconstants.h" + +#include <QtCore/QAbstractTableModel> +#include <QtCore/QDebug> + +#include <QtGui/QColor> + +using namespace Debugger; +using namespace Debugger::Internal; +using namespace Debugger::Constants; + + + +////////////////////////////////////////////////////////////////// +// +// RegisterHandler +// +////////////////////////////////////////////////////////////////// + +RegisterHandler::RegisterHandler(QObject *parent) + : QAbstractTableModel(parent) +{ + setProperty(PROPERTY_REGISTER_FORMAT, "x"); +} + +int RegisterHandler::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return m_registers.size(); +} + +int RegisterHandler::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 2; +} + +QVariant RegisterHandler::data(const QModelIndex &index, int role) const +{ + static const QVariant red = QColor(200, 0, 0); + if (!index.isValid() || index.row() >= m_registers.size()) + return QVariant(); + + const Register ® = m_registers.at(index.row()); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: + return reg.name; + case 1: + return reg.value; + } + } + if (role == Qt::TextColorRole && reg.changed && index.column() == 1) + return red; + return QVariant(); +} + +QVariant RegisterHandler::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + static const char * const headers[] = { + QT_TR_NOOP("Name"), + QT_TR_NOOP("Value"), + }; + if (section < 2) + return tr(headers[section]); + } + return QVariant(); +} + +void RegisterHandler::removeAll() +{ + m_registers.clear(); + reset(); +} + +bool RegisterHandler::isEmpty() const +{ + return m_registers.isEmpty(); +} + +void RegisterHandler::setRegisters(const QList<Register> ®isters) +{ + m_registers = registers; + reset(); +} + +QList<Register> RegisterHandler::registers() const +{ + return m_registers; +} diff --git a/src/plugins/debugger/registerhandler.h b/src/plugins/debugger/registerhandler.h new file mode 100644 index 0000000000..933e45aad4 --- /dev/null +++ b/src/plugins/debugger/registerhandler.h @@ -0,0 +1,81 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_REGISTERHANDLER_H +#define DEBUGGER_REGISTERHANDLER_H + +#include <QtCore/QAbstractTableModel> + +namespace Debugger { +namespace Internal { + +class Register +{ +public: + Register() : changed(true) {} + Register(QString const &name_) : name(name_), changed(true) {} + +public: + QString name; + QString value; + bool changed; +}; + +class RegisterHandler : public QAbstractTableModel +{ + Q_OBJECT + +public: + RegisterHandler(QObject *parent = 0); + + void sessionClosed(); + QAbstractItemModel *model() { return this; } + + bool isEmpty() const; // nothing known so far? + void setRegisters(const QList<Register> ®isters); + QList<Register> registers() const; + void removeAll(); + +private: + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + QList<Register> m_registers; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_REGISTERHANDLER_H diff --git a/src/plugins/debugger/registerwindow.cpp b/src/plugins/debugger/registerwindow.cpp new file mode 100644 index 0000000000..5e53139285 --- /dev/null +++ b/src/plugins/debugger/registerwindow.cpp @@ -0,0 +1,181 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "registerwindow.h" + +#include "debuggerconstants.h" + +#include <QAction> +#include <QDebug> +#include <QDir> +#include <QFileInfo> +#include <QFileInfoList> +#include <QHeaderView> +#include <QMenu> +#include <QResizeEvent> +#include <QToolButton> + + +using namespace Debugger::Internal; +using namespace Debugger::Constants; + +RegisterWindow::RegisterWindow() + : m_alwaysResizeColumnsToContents(true), m_alwaysReloadContents(false) +{ + setWindowTitle(tr("Registers")); + setSortingEnabled(true); + setAlternatingRowColors(true); + setRootIsDecorated(false); + //header()->hide(); + //setIconSize(QSize(10, 10)); + //setWindowIcon(QIcon(":/gdbdebugger/images/debugger_breakpoints.png")); + //QHeaderView *hv = header(); + //hv->setDefaultAlignment(Qt::AlignLeft); + //hv->setClickable(true); + //hv->setSortIndicatorShown(true); +} + +void RegisterWindow::resizeEvent(QResizeEvent *ev) +{ + //QHeaderView *hv = header(); + //int totalSize = ev->size().width() - 110; + //hv->resizeSection(0, totalSize / 4); + //hv->resizeSection(1, totalSize / 4); + QTreeView::resizeEvent(ev); +} + +void RegisterWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + enum { Hex, Bin, Dec, Raw, Oct, Nat, + Adjust, AlwaysAdjust, Reload, AlwaysReload, Count }; + + QMenu menu; + QAction *actions[Count]; + //QTreeWidgetItem *item = itemAt(ev->pos()); + QString format = model()->property(PROPERTY_REGISTER_FORMAT).toString(); + qDebug() << "FORMAT: " << format; + + actions[Adjust] = menu.addAction("Adjust column widths to contents"); + + actions[AlwaysAdjust] = menu.addAction("Always adjust column widths to contents"); + actions[AlwaysAdjust]->setCheckable(true); + actions[AlwaysAdjust]->setChecked(m_alwaysResizeColumnsToContents); + + actions[Reload] = menu.addAction("Reload register listing"); + + actions[AlwaysReload] = menu.addAction("Always reload register listing"); + actions[AlwaysReload]->setCheckable(true); + actions[AlwaysReload]->setChecked(m_alwaysReloadContents); + + menu.addSeparator(); + + actions[Hex] = menu.addAction("Hexadecimal"); + actions[Hex]->setCheckable(true); + actions[Hex]->setChecked(format == "h"); + + actions[Bin] = menu.addAction("Binary"); + actions[Bin]->setCheckable(true); + actions[Bin]->setChecked(format == "t"); + + actions[Dec] = menu.addAction("Decimal"); + actions[Dec]->setCheckable(true); + actions[Dec]->setChecked(format == "d"); + + actions[Raw] = menu.addAction("Raw"); + actions[Raw]->setCheckable(true); + actions[Raw]->setChecked(format == "r"); + + actions[Nat] = menu.addAction("Natural"); + actions[Nat]->setCheckable(true); + actions[Nat]->setChecked(format == "N"); + + actions[Oct] = menu.addAction("Octal"); + actions[Oct]->setCheckable(true); + actions[Oct]->setChecked(format == "o"); + + QAction *act = menu.exec(ev->globalPos()); + + if (act == actions[Adjust]) + resizeColumnsToContents(); + else if (act == actions[AlwaysAdjust]) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); + else if (act == actions[Reload]) + reloadContents(); + else if (act == actions[AlwaysReload]) + setAlwaysReloadContents(!m_alwaysReloadContents); + else if (act == actions[Hex]) + model()->setProperty(PROPERTY_REGISTER_FORMAT, "h"); + else if (act == actions[Oct]) + model()->setProperty(PROPERTY_REGISTER_FORMAT, "o"); + else if (act == actions[Bin]) + model()->setProperty(PROPERTY_REGISTER_FORMAT, "t"); + else if (act == actions[Dec]) + model()->setProperty(PROPERTY_REGISTER_FORMAT, "d"); + else if (act == actions[Nat]) + model()->setProperty(PROPERTY_REGISTER_FORMAT, "N"); + +} + +void RegisterWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + resizeColumnToContents(1); +} + +void RegisterWindow::setAlwaysResizeColumnsToContents(bool on) +{ + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + header()->setResizeMode(1, mode); +} + +void RegisterWindow::setAlwaysReloadContents(bool on) +{ + m_alwaysReloadContents = on; + if (m_alwaysReloadContents) + reloadContents(); +} + +void RegisterWindow::reloadContents() +{ + emit reloadRegisterRequested(); +} + + +void RegisterWindow::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setAlwaysResizeColumnsToContents(true); +} + diff --git a/src/plugins/debugger/registerwindow.h b/src/plugins/debugger/registerwindow.h new file mode 100644 index 0000000000..4c2cc7b7a6 --- /dev/null +++ b/src/plugins/debugger/registerwindow.h @@ -0,0 +1,71 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_REGISTERWINDOW_H +#define DEBUGGER_REGISTERWINDOW_H + +#include <QTreeView> + +namespace Debugger { +namespace Internal { + +class RegisterWindow : public QTreeView +{ + Q_OBJECT + +public: + RegisterWindow(); + void setModel(QAbstractItemModel *model); + +signals: + void reloadRegisterRequested(); + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on); + void reloadContents(); + void setAlwaysReloadContents(bool on); + +protected: + void resizeEvent(QResizeEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + +private: + bool m_alwaysResizeColumnsToContents; + bool m_alwaysReloadContents; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_REGISTERWINDOW_H + diff --git a/src/plugins/debugger/scriptengine.cpp b/src/plugins/debugger/scriptengine.cpp new file mode 100644 index 0000000000..c6c7dd65a5 --- /dev/null +++ b/src/plugins/debugger/scriptengine.cpp @@ -0,0 +1,677 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ + +#include "scriptengine.h" + +#include "assert.h" +#include "debuggerconstants.h" +#include "debuggermanager.h" + +#include "disassemblerhandler.h" +#include "breakhandler.h" +#include "moduleshandler.h" +#include "registerhandler.h" +#include "stackhandler.h" +#include "watchhandler.h" + +#include "startexternaldialog.h" +#include "attachexternaldialog.h" + +#include <QtCore/QDateTime> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QTimer> + +#include <QtGui/QAction> +#include <QtGui/QToolTip> + +#include <QtScript/QScriptContext> +#include <QtScript/QScriptClassPropertyIterator> +#include <QtScript/QScriptContextInfo> +#include <QtScript/QScriptEngine> +#include <QtScript/QScriptEngineAgent> +#include <QtScript/QScriptValue> +#include <QtScript/QScriptValueIterator> + +using namespace Debugger; +using namespace Debugger::Internal; +using namespace Debugger::Constants; + + +/////////////////////////////////////////////////////////////////////// +// +// ScriptEngine +// +/////////////////////////////////////////////////////////////////////// + +class Debugger::Internal::ScriptAgent : public QScriptEngineAgent +{ +public: + ScriptAgent(ScriptEngine *debugger, QScriptEngine *script); + ~ScriptAgent() {} + + void contextPop(); + void contextPush(); + void exceptionCatch(qint64 scriptId, const QScriptValue &exception); + void exceptionThrow(qint64 scriptId, const QScriptValue & exception, + bool hasHandler); + void functionEntry(qint64 scriptId); + void functionExit(qint64 scriptId, const QScriptValue &returnValue); + void positionChange(qint64 scriptId, int lineNumber, int columnNumber); + void scriptLoad(qint64 id, const QString &program, const QString &fileName, + int baseLineNumber); + void scriptUnload(qint64 id); + +private: + void maybeBreakNow(bool byFunction); + + ScriptEngine *q; +}; + +ScriptAgent::ScriptAgent(ScriptEngine *debugger, QScriptEngine *script) + : QScriptEngineAgent(script), q(debugger) +{} + +void ScriptAgent::contextPop() +{ + qDebug() << "ScriptAgent::contextPop: "; +} + +void ScriptAgent::contextPush() +{ + qDebug() << "ScriptAgent::contextPush: "; +} + +void ScriptAgent::exceptionCatch(qint64 scriptId, const QScriptValue & exception) +{ + qDebug() << "ScriptAgent::exceptionCatch: " << scriptId << &exception; +} + +void ScriptAgent::exceptionThrow(qint64 scriptId, const QScriptValue &exception, + bool hasHandler) +{ + qDebug() << "ScriptAgent::exceptionThrow: " << scriptId << &exception + << hasHandler; +} + +void ScriptAgent::functionEntry(qint64 scriptId) +{ + Q_UNUSED(scriptId); + q->maybeBreakNow(true); +} + +void ScriptAgent::functionExit(qint64 scriptId, const QScriptValue &returnValue) +{ + qDebug() << "ScriptAgent::functionExit: " << scriptId << &returnValue; +} + +void ScriptAgent::positionChange(qint64 scriptId, int lineNumber, int columnNumber) +{ + //qDebug() << "ScriptAgent::position: " << lineNumber; + Q_UNUSED(scriptId); + Q_UNUSED(lineNumber); + Q_UNUSED(columnNumber); + q->maybeBreakNow(false); +} + +void ScriptAgent::scriptLoad(qint64 scriptId, const QString &program, + const QString &fileName, int baseLineNumber) +{ + Q_UNUSED(scriptId); + Q_UNUSED(program); + Q_UNUSED(fileName); + Q_UNUSED(baseLineNumber); + //qDebug() << "ScriptAgent::scriptLoad: " << program << fileName + // << baseLineNumber; +} + +void ScriptAgent::scriptUnload(qint64 scriptId) +{ + Q_UNUSED(scriptId); + //qDebug() << "ScriptAgent::scriptUnload: " << scriptId; +} + + +/////////////////////////////////////////////////////////////////////// +// +// ScriptEngine +// +/////////////////////////////////////////////////////////////////////// + +ScriptEngine::ScriptEngine(DebuggerManager *parent) +{ + q = parent; + qq = parent->engineInterface(); + m_scriptEngine = new QScriptEngine(this); + m_scriptAgent = new ScriptAgent(this, m_scriptEngine); + m_scriptEngine->setAgent(m_scriptAgent); + m_scriptEngine->setProcessEventsInterval(1 /*ms*/); +} + +ScriptEngine::~ScriptEngine() +{ +} + +void ScriptEngine::executeDebuggerCommand(const QString &command) +{ + Q_UNUSED(command); + qDebug() << "FIXME: ScriptEngine::executeDebuggerCommand()"; +} + +void ScriptEngine::shutdown() +{ + exitDebugger(); +} + +void ScriptEngine::exitDebugger() +{ + //qDebug() << " ScriptEngine::exitDebugger()"; + m_stopped = false; + m_stopOnNextLine = false; + m_scriptEngine->abortEvaluation(); + qq->notifyInferiorExited(); +} + +bool ScriptEngine::startDebugger() +{ + m_stopped = false; + m_stopOnNextLine = false; + m_scriptEngine->abortEvaluation(); + QFileInfo fi(q->m_executable); + m_scriptFileName = fi.absoluteFilePath(); + QFile scriptFile(m_scriptFileName); + if (!scriptFile.open(QIODevice::ReadOnly)) + return false; + QTextStream stream(&scriptFile); + m_scriptContents = stream.readAll(); + scriptFile.close(); + attemptBreakpointSynchronization(); + QTimer::singleShot(0, q, SLOT(notifyStartupFinished())); + return true; +} + +void ScriptEngine::continueInferior() +{ + //qDebug() << "ScriptEngine::continueInferior()"; + m_stopped = false; + m_stopOnNextLine = false; +} + +void ScriptEngine::runInferior() +{ + //qDebug() << "ScriptEngine::runInferior()"; + QScriptValue result = m_scriptEngine->evaluate(m_scriptContents, m_scriptFileName); +} + +void ScriptEngine::interruptInferior() +{ + m_stopped = false; + m_stopOnNextLine = true; + qDebug() << "FIXME: ScriptEngine::interruptInferior()"; +} + +void ScriptEngine::stepExec() +{ + //qDebug() << "FIXME: ScriptEngine::stepExec()"; + m_stopped = false; + m_stopOnNextLine = true; +} + +void ScriptEngine::stepIExec() +{ + //qDebug() << "FIXME: ScriptEngine::stepIExec()"; + m_stopped = false; + m_stopOnNextLine = true; +} + +void ScriptEngine::stepOutExec() +{ + //qDebug() << "FIXME: ScriptEngine::stepOutExec()"; + m_stopped = false; + m_stopOnNextLine = true; +} + +void ScriptEngine::nextExec() +{ + //qDebug() << "FIXME: ScriptEngine::nextExec()"; + m_stopped = false; + m_stopOnNextLine = true; +} + +void ScriptEngine::nextIExec() +{ + //qDebug() << "FIXME: ScriptEngine::nextIExec()"; + m_stopped = false; + m_stopOnNextLine = true; +} + +void ScriptEngine::runToLineExec(const QString &fileName, int lineNumber) +{ + Q_UNUSED(fileName); + Q_UNUSED(lineNumber); + qDebug() << "FIXME: ScriptEngine::runToLineExec()"; +} + +void ScriptEngine::runToFunctionExec(const QString &functionName) +{ + Q_UNUSED(functionName); + qDebug() << "FIXME: ScriptEngine::runToFunctionExec()"; +} + +void ScriptEngine::jumpToLineExec(const QString &fileName, int lineNumber) +{ + Q_UNUSED(fileName); + Q_UNUSED(lineNumber); + qDebug() << "FIXME: ScriptEngine::jumpToLineExec()"; +} + +void ScriptEngine::activateFrame(int index) +{ + Q_UNUSED(index); +} + +void ScriptEngine::selectThread(int index) +{ + Q_UNUSED(index); +} + +void ScriptEngine::attemptBreakpointSynchronization() +{ + BreakHandler *handler = qq->breakHandler(); + bool updateNeeded = false; + for (int index = 0; index != handler->size(); ++index) { + BreakpointData *data = handler->at(index); + if (data->pending) { + data->pending = false; // FIXME + updateNeeded = true; + } + if (data->bpNumber.isEmpty()) { + data->bpNumber = QString::number(index + 1); + updateNeeded = true; + } + if (!data->fileName.isEmpty() && data->markerFileName.isEmpty()) { + data->markerFileName = data->fileName; + data->markerLineNumber = data->lineNumber.toInt(); + updateNeeded = true; + } + } + if (updateNeeded) + handler->updateMarkers(); +} + +void ScriptEngine::reloadDisassembler() +{ +} + +void ScriptEngine::loadSymbols(const QString &moduleName) +{ + Q_UNUSED(moduleName); +} + +void ScriptEngine::loadAllSymbols() +{ +} + +void ScriptEngine::reloadModules() +{ +} + + + +////////////////////////////////////////////////////////////////////// +// +// Tooltip specific stuff +// +////////////////////////////////////////////////////////////////////// + +static WatchData m_toolTip; +static QPoint m_toolTipPos; +static QHash<QString, WatchData> m_toolTipCache; + +static bool hasLetterOrNumber(const QString &exp) +{ + for (int i = exp.size(); --i >= 0; ) + if (exp[i].isLetterOrNumber()) + return true; + return false; +} + +static bool hasSideEffects(const QString &exp) +{ + // FIXME: complete? + return exp.contains("-=") + || exp.contains("+=") + || exp.contains("/=") + || exp.contains("*=") + || exp.contains("&=") + || exp.contains("|=") + || exp.contains("^=") + || exp.contains("--") + || exp.contains("++"); +} + +void ScriptEngine::setToolTipExpression(const QPoint &pos, const QString &exp0) +{ + Q_UNUSED(pos); + Q_UNUSED(exp0); + + if (q->status() != DebuggerInferiorStopped) { + //qDebug() << "SUPPRESSING DEBUGGER TOOLTIP, INFERIOR NOT STOPPED"; + return; + } + + //m_toolTipPos = pos; + QString exp = exp0; + +/* + if (m_toolTipCache.contains(exp)) { + const WatchData & data = m_toolTipCache[exp]; + q->watchHandler()->removeChildren(data.iname); + insertData(data); + return; + } +*/ + + QToolTip::hideText(); + if (exp.isEmpty() || exp.startsWith("#")) { + QToolTip::hideText(); + return; + } + + if (!hasLetterOrNumber(exp)) { + QToolTip::showText(m_toolTipPos, + "'" + exp + "' contains no identifier"); + return; + } + + if (exp.startsWith('"') && exp.endsWith('"')) { + QToolTip::showText(m_toolTipPos, "String literal " + exp); + return; + } + + if (exp.startsWith("++") || exp.startsWith("--")) + exp = exp.mid(2); + + if (exp.endsWith("++") || exp.endsWith("--")) + exp = exp.mid(2); + + if (exp.startsWith("<") || exp.startsWith("[")) + return; + + if (hasSideEffects(exp)) { + QToolTip::showText(m_toolTipPos, + "Cowardly refusing to evaluate expression '" + exp + + "' with potential side effects"); + return; + } + +#if 0 + //if (m_manager->status() != DebuggerInferiorStopped) + // return; + + // FIXME: 'exp' can contain illegal characters + m_toolTip = WatchData(); + m_toolTip.exp = exp; + m_toolTip.name = exp; + m_toolTip.iname = tooltipIName; + insertData(m_toolTip); +#endif +} + + +////////////////////////////////////////////////////////////////////// +// +// Watch specific stuff +// +////////////////////////////////////////////////////////////////////// + +void ScriptEngine::assignValueInDebugger(const QString &expression, + const QString &value) +{ + Q_UNUSED(expression); + Q_UNUSED(value); +} + +void ScriptEngine::maybeBreakNow(bool byFunction) +{ + QScriptContext *context = m_scriptEngine->currentContext(); + QScriptContextInfo info(context); + + // + // Update breakpoints + // + QString functionName = info.functionName(); + QString fileName = info.fileName(); + int lineNumber = info.lineNumber(); + if (byFunction) + lineNumber = info.functionStartLineNumber(); + + BreakHandler *handler = qq->breakHandler(); + + if (m_stopOnNextLine) { + m_stopOnNextLine = false; + } else { + int index = 0; + for (; index != handler->size(); ++index) { + BreakpointData *data = handler->at(index); + if (byFunction) { + if (!functionName.isEmpty() && data->funcName == functionName) + break; + } else { + if (info.lineNumber() == data->lineNumber.toInt() + && fileName == data->fileName) + break; + } + } + + if (index == handler->size()) + return; + + // we just run into a breakpoint + //qDebug() << "RESOLVING BREAKPOINT AT " << fileName << lineNumber; + BreakpointData *data = handler->at(index); + data->bpLineNumber = QString::number(lineNumber); + data->bpFileName = fileName; + data->bpFuncName = functionName; + data->markerLineNumber = lineNumber; + data->markerFileName = fileName; + data->pending = false; + data->updateMarker(); + } + + qq->notifyInferiorStopped(); + q->gotoLocation(fileName, lineNumber, true); + + qq->watchHandler()->reinitializeWatchers(); + //qDebug() << "UPDATE LOCALS"; + + // + // Build stack + // + QList<StackFrame> stackFrames; + int i = 0; + for (QScriptContext *c = context; c; c = c->parentContext(), ++i) { + QScriptContextInfo info(c); + StackFrame frame; + frame.level = i; + frame.file = info.fileName(); + frame.function = info.functionName(); + frame.from = QString::number(info.functionStartLineNumber()); + frame.to = QString::number(info.functionEndLineNumber()); + frame.line = info.lineNumber(); + + if (frame.function.isEmpty()) + frame.function = "<global scope>"; + //frame.address = ...; + stackFrames.append(frame); + } + qq->stackHandler()->setFrames(stackFrames); + + // + // Build locals + // + WatchData data; + data.iname = "local"; + data.name = "local"; + data.scriptValue = context->activationObject(); + qq->watchHandler()->insertData(data); + updateWatchModel(); + + // FIXME: Use an extra thread. This here is evil + m_stopped = true; + while (m_stopped) { + //qDebug() << "LOOPING"; + QApplication::processEvents(); + } + //qDebug() << "RUNNING AGAIN"; +} + +void ScriptEngine::updateWatchModel() +{ + while (true) { + QList<WatchData> list = qq->watchHandler()->takeCurrentIncompletes(); + if (list.isEmpty()) + break; + foreach (const WatchData &data, list) + updateSubItem(data); + } + qq->watchHandler()->rebuildModel(); + q->showStatusMessage(tr("Stopped."), 5000); +} + +void ScriptEngine::updateSubItem(const WatchData &data0) +{ + WatchData data = data0; + //qDebug() << "\nUPDATE SUBITEM: " << data.toString(); + QWB_ASSERT(data.isValid(), return); + + if (data.isTypeNeeded() || data.isValueNeeded()) { + QScriptValue ob = data.scriptValue; + if (ob.isArray()) { + data.setType("Array"); + data.setValue(" "); + } else if (ob.isBool()) { + data.setType("Bool"); + data.setValue(ob.toBool() ? "true" : "false"); + data.setChildCount(0); + } else if (ob.isDate()) { + data.setType("Date"); + data.setValue(ob.toDateTime().toString().toUtf8()); + data.setChildCount(0); + } else if (ob.isError()) { + data.setType("Error"); + data.setValue(" "); + } else if (ob.isFunction()) { + data.setType("Function"); + data.setValue(" "); + } else if (ob.isNull()) { + data.setType("<null>"); + data.setValue("<null>"); + } else if (ob.isNumber()) { + data.setType("Number"); + data.setValue(QString::number(ob.toNumber()).toUtf8()); + data.setChildCount(0); + } else if (ob.isObject()) { + data.setType("Object"); + data.setValue(" "); + } else if (ob.isQMetaObject()) { + data.setType("QMetaObject"); + data.setValue(" "); + } else if (ob.isQObject()) { + data.setType("QObject"); + data.setValue(" "); + } else if (ob.isRegExp()) { + data.setType("RegExp"); + data.setValue(ob.toRegExp().pattern().toUtf8()); + } else if (ob.isString()) { + data.setType("String"); + data.setValue(ob.toString().toUtf8()); + } else if (ob.isVariant()) { + data.setType("Variant"); + data.setValue(" "); + } else if (ob.isUndefined()) { + data.setType("<undefined>"); + data.setValue("<unknown>"); + } else { + data.setType("<unknown>"); + data.setValue("<unknown>"); + } + qq->watchHandler()->insertData(data); + return; + } + + if (data.isChildrenNeeded()) { + int numChild = 0; + QScriptValueIterator it(data.scriptValue); + while (it.hasNext()) { + it.next(); + WatchData data1; + data1.iname = data.iname + "." + it.name(); + data1.name = it.name(); + data1.scriptValue = it.value(); + if (qq->watchHandler()->isExpandedIName(data1.iname)) + data1.setChildrenNeeded(); + else + data1.setChildrenUnneeded(); + qq->watchHandler()->insertData(data1); + ++numChild; + } + //qDebug() << " ... CHILDREN: " << numChild; + data.setChildCount(numChild); + data.setChildrenUnneeded(); + qq->watchHandler()->insertData(data); + return; + } + + if (data.isChildCountNeeded()) { + int numChild = 0; + QScriptValueIterator it(data.scriptValue); + while (it.hasNext()) { + it.next(); + ++numChild; + } + data.setChildCount(numChild); + //qDebug() << " ... CHILDCOUNT: " << numChild; + qq->watchHandler()->insertData(data); + return; + } + + QWB_ASSERT(false, return); +} + +IDebuggerEngine *createScriptEngine(DebuggerManager *parent) +{ + return new ScriptEngine(parent); +} + diff --git a/src/plugins/debugger/scriptengine.h b/src/plugins/debugger/scriptengine.h new file mode 100644 index 0000000000..69cb440570 --- /dev/null +++ b/src/plugins/debugger/scriptengine.h @@ -0,0 +1,134 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_SCRIPTENGINE_H +#define DEBUGGER_SCRIPTENGINE_H + +#include <QtCore/QByteArray> +#include <QtCore/QHash> +#include <QtCore/QMap> +#include <QtCore/QObject> +#include <QtCore/QProcess> +#include <QtCore/QPoint> +#include <QtCore/QSet> +#include <QtCore/QVariant> + +#include <QtNetwork/QLocalSocket> + +QT_BEGIN_NAMESPACE +class QAction; +class QAbstractItemModel; +class QSplitter; +class QToolBar; +class QScriptEngine; +class QScriptValue; +QT_END_NAMESPACE + +#include "idebuggerengine.h" + +namespace Debugger { +namespace Internal { + +class DebuggerManager; +class IDebuggerManagerAccessForEngines; +class ScriptAgent; +class WatchData; + +class ScriptEngine : public IDebuggerEngine +{ + Q_OBJECT + +public: + ScriptEngine(DebuggerManager *parent); + ~ScriptEngine(); + +private: + // IDebuggerEngine implementation + void stepExec(); + void stepOutExec(); + void nextExec(); + void stepIExec(); + void nextIExec(); + + void shutdown(); + void setToolTipExpression(const QPoint &pos, const QString &exp); + bool startDebugger(); + void exitDebugger(); + + void continueInferior(); + void runInferior(); + void interruptInferior(); + + void runToLineExec(const QString &fileName, int lineNumber); + void runToFunctionExec(const QString &functionName); + void jumpToLineExec(const QString &fileName, int lineNumber); + + void activateFrame(int index); + void selectThread(int index); + + void attemptBreakpointSynchronization(); + + void loadSessionData() {} + void saveSessionData() {} + + void assignValueInDebugger(const QString &expr, const QString &value); + void executeDebuggerCommand(const QString & command); + + void loadSymbols(const QString &moduleName); + void loadAllSymbols(); + void reloadDisassembler(); + void reloadModules(); + void reloadRegisters() {} + + bool supportsThreads() const { return true; } + void maybeBreakNow(bool byFunction); + void updateWatchModel(); + void updateSubItem(const WatchData &data0); + +private: + friend class ScriptAgent; + DebuggerManager *q; + IDebuggerManagerAccessForEngines *qq; + + QScriptEngine *m_scriptEngine; + QString m_scriptContents; + QString m_scriptFileName; + ScriptAgent *m_scriptAgent; + + bool m_stopped; + bool m_stopOnNextLine; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_SCRIPTENGINE_H diff --git a/src/plugins/debugger/stackhandler.cpp b/src/plugins/debugger/stackhandler.cpp new file mode 100644 index 0000000000..d05e259c09 --- /dev/null +++ b/src/plugins/debugger/stackhandler.cpp @@ -0,0 +1,271 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "stackhandler.h" + +#include "assert.h" + +#include <QtCore/QAbstractTableModel> +#include <QtCore/QDebug> +#include <QtCore/QFileInfo> + +using namespace Debugger::Internal; + + +//////////////////////////////////////////////////////////////////////// +// +// StackHandler +// +//////////////////////////////////////////////////////////////////////// + +StackHandler::StackHandler(QObject *parent) + : QAbstractTableModel(parent), m_currentIndex(0) +{ + m_emptyIcon = QIcon(":/gdbdebugger/images/empty.svg"); + m_positionIcon = QIcon(":/gdbdebugger/images/location.svg"); +} + +int StackHandler::rowCount(const QModelIndex &parent) const +{ + // Since the stack is not a tree, row count is 0 for any valid parent + return parent.isValid() ? 0 : m_stackFrames.size(); +} + +int StackHandler::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 4; +} + +QVariant StackHandler::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_stackFrames.size()) + return QVariant(); + + const StackFrame &frame = m_stackFrames.at(index.row()); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: // Stack frame level + return QString::number(frame.level); + case 1: // Function name + return frame.function; + case 2: // File name + return frame.file.isEmpty() ? frame.from : QFileInfo(frame.file).fileName(); + case 3: // Line number + return frame.line; + case 4: // Address + return frame.address; + } + } else if (role == Qt::ToolTipRole) { + return "<table><tr><td>Address:</td><td>" + frame.address + "</td></tr>" + + "<tr><td>Function: </td><td>" + frame.function + "</td></tr>" + + "<tr><td>File: </td><td>" + frame.file + "</td></tr>" + + "<tr><td>Line: </td><td>" + QString::number(frame.line) + "</td></tr>" + + "<tr><td>From: </td><td>" + frame.from + "</td></tr></table>" + + "<tr><td>To: </td><td>" + frame.to + "</td></tr></table>"; + } else if (role == Qt::DecorationRole && index.column() == 0) { + // Return icon that indicates whether this is the active stack frame + return (index.row() == m_currentIndex) ? m_positionIcon : m_emptyIcon; + } + + return QVariant(); +} + +QVariant StackHandler::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + static const char * const headers[] = { + QT_TR_NOOP("Level"), + QT_TR_NOOP("Function"), + QT_TR_NOOP("File"), + QT_TR_NOOP("Line"), + QT_TR_NOOP("Address") + }; + if (section < 5) + return tr(headers[section]); + } + return QVariant(); +} + +Qt::ItemFlags StackHandler::flags(const QModelIndex &index) const +{ + if (index.row() >= m_stackFrames.size()) + return 0; + const StackFrame &frame = m_stackFrames.at(index.row()); + const bool isValid = !frame.file.isEmpty() && !frame.function.isEmpty(); + return isValid ? QAbstractTableModel::flags(index) : Qt::ItemFlags(0); +} + +StackFrame StackHandler::currentFrame() const +{ + QWB_ASSERT(m_currentIndex >= 0, return StackFrame()); + QWB_ASSERT(m_currentIndex < m_stackFrames.size(), return StackFrame()); + return m_stackFrames.at(m_currentIndex); +} + +void StackHandler::setCurrentIndex(int level) +{ + if (level == m_currentIndex) + return; + + // Emit changed for previous frame + QModelIndex i = index(m_currentIndex, 0); + emit dataChanged(i, i); + + m_currentIndex = level; + + // Emit changed for new frame + i = index(m_currentIndex, 0); + emit dataChanged(i, i); +} + +void StackHandler::removeAll() +{ + m_stackFrames.clear(); + m_currentIndex = 0; + reset(); +} + +void StackHandler::setFrames(const QList<StackFrame> &frames) +{ + m_stackFrames = frames; + if (m_currentIndex >= m_stackFrames.size()) + m_currentIndex = m_stackFrames.size() - 1; + reset(); +} + +QList<StackFrame> StackHandler::frames() const +{ + return m_stackFrames; +} + +bool StackHandler::isDebuggingDumpers() const +{ + for (int i = m_stackFrames.size(); --i >= 0; ) + if (m_stackFrames.at(i).function.startsWith("qDumpObjectData")) + return true; + return false; +} + +//////////////////////////////////////////////////////////////////////// +// +// ThreadsHandler +// +//////////////////////////////////////////////////////////////////////// + +ThreadsHandler::ThreadsHandler(QObject *parent) + : QAbstractTableModel(parent), m_currentIndex(0) +{ + m_emptyIcon = QIcon(":/gdbdebugger/images/empty.svg"); + m_positionIcon = QIcon(":/gdbdebugger/images/location.svg"); +} + +int ThreadsHandler::rowCount(const QModelIndex &parent) const +{ + // Since the stack is not a tree, row count is 0 for any valid parent + return parent.isValid() ? 0 : m_threads.size(); +} + +int ThreadsHandler::columnCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : 1; +} + +QVariant ThreadsHandler::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_threads.size()) + return QVariant(); + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: // Thread ID + return m_threads.at(index.row()).id; + case 1: // Function name + return "???"; + } + } else if (role == Qt::ToolTipRole) { + return "Thread: " + QString::number(m_threads.at(index.row()).id); + } else if (role == Qt::DecorationRole && index.column() == 0) { + // Return icon that indicates whether this is the active stack frame + return (index.row() == m_currentIndex) ? m_positionIcon : m_emptyIcon; + } + + return QVariant(); +} + +QVariant ThreadsHandler::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + static const char * const headers[] = { + QT_TR_NOOP("Thread ID"), + }; + if (section < 1) + return tr(headers[section]); + } + return QVariant(); +} + +void ThreadsHandler::setCurrentThread(int index) +{ + if (index == m_currentIndex) + return; + + // Emit changed for previous frame + QModelIndex i = ThreadsHandler::index(m_currentIndex, 0); + emit dataChanged(i, i); + + m_currentIndex = index; + + // Emit changed for new frame + i = ThreadsHandler::index(m_currentIndex, 0); + emit dataChanged(i, i); +} + +void ThreadsHandler::setThreads(const QList<ThreadData> &threads) +{ + m_threads = threads; + if (m_currentIndex >= m_threads.size()) + m_currentIndex = m_threads.size() - 1; + reset(); +} + +QList<ThreadData> ThreadsHandler::threads() const +{ + return m_threads; +} + +void ThreadsHandler::removeAll() +{ + m_threads.clear(); + m_currentIndex = 0; + reset(); +} diff --git a/src/plugins/debugger/stackhandler.h b/src/plugins/debugger/stackhandler.h new file mode 100644 index 0000000000..81a4686f68 --- /dev/null +++ b/src/plugins/debugger/stackhandler.h @@ -0,0 +1,136 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_STACKHANDLER_H +#define DEBUGGER_STACKHANDLER_H + +#include <QtCore/QAbstractTableModel> +#include <QtCore/QObject> + +#include <QtGui/QIcon> + +namespace Debugger { +namespace Internal { + +//////////////////////////////////////////////////////////////////////// +// +// StackModel +// +//////////////////////////////////////////////////////////////////////// + +struct StackFrame +{ + int level; + QString function; + QString file; // we try to put an absolute file name in there + QString from; + QString to; + int line; + QString address; +}; + +/*! A model to represent the stack in a QTreeView. */ +class StackHandler : public QAbstractTableModel +{ +public: + StackHandler(QObject *parent = 0); + + void setFrames(const QList<StackFrame> &frames); + QList<StackFrame> frames() const; + void setCurrentIndex(int index); + int currentIndex() const { return m_currentIndex; } + StackFrame currentFrame() const; + int stackSize() const { return m_stackFrames.size(); } + + // Called from StackHandler after a new stack list has been received + void removeAll(); + QAbstractItemModel *stackModel() { return this; } + bool isDebuggingDumpers() const; + +private: + // QAbstractTableModel + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + + QList<StackFrame> m_stackFrames; + int m_currentIndex; + QIcon m_positionIcon; + QIcon m_emptyIcon; +}; + + +//////////////////////////////////////////////////////////////////////// +// +// ThreadsHandler +// +//////////////////////////////////////////////////////////////////////// + +struct ThreadData +{ + int id; +}; + +/*! A model to represent the running threads in a QTreeView or ComboBox */ +class ThreadsHandler : public QAbstractTableModel +{ +public: + ThreadsHandler(QObject *parent = 0); + + void setCurrentThread(int index); + void selectThread(int index); + void setThreads(const QList<ThreadData> &threads); + void removeAll(); + QList<ThreadData> threads() const; + QAbstractItemModel *threadsModel() { return this; } + +private: + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + +private: + friend class StackHandler; + QList<ThreadData> m_threads; + int m_currentIndex; + QIcon m_positionIcon; + QIcon m_emptyIcon; +}; + + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_STACKHANDLER_H diff --git a/src/plugins/debugger/stackwindow.cpp b/src/plugins/debugger/stackwindow.cpp new file mode 100644 index 0000000000..3dc1209a00 --- /dev/null +++ b/src/plugins/debugger/stackwindow.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "stackwindow.h" + +#include "assert.h" +#include "stackhandler.h" + +#include <QAction> +#include <QComboBox> +#include <QDebug> +#include <QHeaderView> +#include <QMenu> +#include <QResizeEvent> +#include <QTreeView> +#include <QVBoxLayout> + +using Debugger::Internal::StackWindow; + +StackWindow::StackWindow(QWidget *parent) + : QTreeView(parent), m_alwaysResizeColumnsToContents(false) +{ + setWindowTitle(tr("Stack")); + + setAlternatingRowColors(true); + setRootIsDecorated(false); + setIconSize(QSize(10, 10)); + + header()->setDefaultAlignment(Qt::AlignLeft); + + connect(this, SIGNAL(activated(QModelIndex)), + this, SLOT(rowActivated(QModelIndex))); +} + +void StackWindow::resizeEvent(QResizeEvent *event) +{ + QHeaderView *hv = header(); + int totalSize = event->size().width() - 120; + if (totalSize > 10) { + hv->resizeSection(0, 45); + hv->resizeSection(1, totalSize / 2); + hv->resizeSection(2, totalSize / 2); + hv->resizeSection(3, 55); + } + QTreeView::resizeEvent(event); +} + +void StackWindow::rowActivated(const QModelIndex &index) +{ + //qDebug() << "ACTIVATED: " << index.row() << index.column(); + emit frameActivated(index.row()); +} + +void StackWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + QMenu menu; + QAction *act1 = new QAction(tr("Adjust column widths to contents"), &menu); + QAction *act2 = new QAction(tr("Always adjust column widths to contents"), &menu); + act2->setCheckable(true); + act2->setChecked(m_alwaysResizeColumnsToContents); + + menu.addAction(act1); + menu.addAction(act2); + + QAction *act = menu.exec(ev->globalPos()); + + if (act == act1) + resizeColumnsToContents(); + else if (act == act2) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); +} + +void StackWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + resizeColumnToContents(1); + resizeColumnToContents(2); +} + +void StackWindow::setAlwaysResizeColumnsToContents(bool on) +{ + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + header()->setResizeMode(1, mode); + header()->setResizeMode(2, mode); +} diff --git a/src/plugins/debugger/stackwindow.h b/src/plugins/debugger/stackwindow.h new file mode 100644 index 0000000000..5edfeb3fb6 --- /dev/null +++ b/src/plugins/debugger/stackwindow.h @@ -0,0 +1,75 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_STACKWINDOW_H +#define DEBUGGER_STACKWINDOW_H + +#include <QtGui/QTreeView> +#include <QtGui/QWidget> + +QT_BEGIN_NAMESPACE +class QComboBox; +class QModelIndex; +QT_END_NAMESPACE + +namespace Debugger { +namespace Internal { + +class StackWindow : public QTreeView +{ + Q_OBJECT + +public: + StackWindow(QWidget *parent = 0); + +signals: + void frameActivated(int); + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on); + +private slots: + void rowActivated(const QModelIndex &index); + +private: + void resizeEvent(QResizeEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + + bool m_alwaysResizeColumnsToContents; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_STACKWINDOW_H + diff --git a/src/plugins/debugger/startexternaldialog.cpp b/src/plugins/debugger/startexternaldialog.cpp new file mode 100644 index 0000000000..c87982ab1d --- /dev/null +++ b/src/plugins/debugger/startexternaldialog.cpp @@ -0,0 +1,124 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "startexternaldialog.h" + +#include <QtGui/QFileDialog> +#include <QtGui/QPushButton> + +using namespace Debugger::Internal; + +StartExternalDialog::StartExternalDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + + //execLabel->setHidden(false); + //execEdit->setHidden(false); + //browseButton->setHidden(false); + + execLabel->setText(tr("Executable:")); + argLabel->setText(tr("Arguments:")); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); + + connect(browseButton, SIGNAL(clicked()), + this, SLOT(onBrowseButton())); +} + +void StartExternalDialog::setExecutableFile(const QString &str) +{ + execEdit->setText(str); +} + +void StartExternalDialog::setExecutableArguments(const QString &str) +{ + argsEdit->setText(str); +} + +QString StartExternalDialog::executableFile() const +{ + return execEdit->text(); +} + +QString StartExternalDialog::executableArguments() const +{ + return argsEdit->text(); + /* + bool inQuotes = false; + QString args = argsEdit->text(); + QChar current; + QChar last; + QString arg; + + QStringList result; + if (!args.isEmpty()) + result << QLatin1String("--args"); + result << execEdit->text(); + + for(int i=0; i<args.length(); ++i) { + current = args.at(i); + + if (current == QLatin1Char('\"') && last != QLatin1Char('\\')) { + if (inQuotes && !arg.isEmpty()) { + result << arg; + arg.clear(); + } + inQuotes = !inQuotes; + } else if (!inQuotes && current == QLatin1Char(' ')) { + arg = arg.trimmed(); + if (!arg.isEmpty()) { + result << arg; + arg.clear(); + } + } else { + arg += current; + } + + last = current; + } + + if (!arg.isEmpty()) + result << arg; + + return result; + */ +} + +void StartExternalDialog::onBrowseButton() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Select Executable"), + execEdit->text()); + execEdit->setText(fileName); +} diff --git a/src/plugins/debugger/startexternaldialog.h b/src/plugins/debugger/startexternaldialog.h new file mode 100644 index 0000000000..10a43670db --- /dev/null +++ b/src/plugins/debugger/startexternaldialog.h @@ -0,0 +1,63 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_STARTEXTERNALDIALOG_H +#define DEBUGGER_STARTEXTERNALDIALOG_H + +#include <QtGui/QDialog> + +#include "ui_startexternaldialog.h" + +namespace Debugger { +namespace Internal { + +class StartExternalDialog : public QDialog, Ui::StartExternalDialog +{ + Q_OBJECT + +public: + StartExternalDialog(QWidget *parent); + + void setExecutableFile(const QString &executable); + void setExecutableArguments(const QString &args); + + QString executableFile() const; + QString executableArguments() const; + +private slots: + void onBrowseButton(); +}; + +} // namespace Debugger +} // namespace Internal + +#endif // DEBUGGER_STARTEXTERNALDIALOG_H diff --git a/src/plugins/debugger/startexternaldialog.ui b/src/plugins/debugger/startexternaldialog.ui new file mode 100644 index 0000000000..7888db2a3e --- /dev/null +++ b/src/plugins/debugger/startexternaldialog.ui @@ -0,0 +1,93 @@ +<ui version="4.0" > + <class>StartExternalDialog</class> + <widget class="QDialog" name="StartExternalDialog" > + <property name="geometry" > + <rect> + <x>0</x> + <y>0</y> + <width>425</width> + <height>127</height> + </rect> + </property> + <property name="windowTitle" > + <string>Start Debugger</string> + </property> + <layout class="QVBoxLayout" > + <property name="spacing" > + <number>6</number> + </property> + <property name="margin" > + <number>9</number> + </property> + <item> + <layout class="QGridLayout" > + <property name="margin" > + <number>0</number> + </property> + <property name="spacing" > + <number>6</number> + </property> + <item row="1" column="1" > + <widget class="QLineEdit" name="argsEdit" /> + </item> + <item row="0" column="1" > + <widget class="QLineEdit" name="execEdit" /> + </item> + <item row="0" column="0" > + <widget class="QLabel" name="execLabel" > + <property name="text" > + <string>Executable:</string> + </property> + </widget> + </item> + <item row="0" column="2" > + <widget class="QToolButton" name="browseButton" > + <property name="text" > + <string>...</string> + </property> + </widget> + </item> + <item row="1" column="0" > + <widget class="QLabel" name="argLabel" > + <property name="text" > + <string>Arguments:</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer> + <property name="orientation" > + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0" > + <size> + <width>407</width> + <height>16</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="Line" name="line" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox" > + <property name="orientation" > + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons" > + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/debugger/threadswindow.cpp b/src/plugins/debugger/threadswindow.cpp new file mode 100644 index 0000000000..beffda84cb --- /dev/null +++ b/src/plugins/debugger/threadswindow.cpp @@ -0,0 +1,112 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "threadswindow.h" + +#include "assert.h" +#include "stackhandler.h" + +#include <QAction> +#include <QComboBox> +#include <QDebug> +#include <QHeaderView> +#include <QMenu> +#include <QResizeEvent> +#include <QTreeView> +#include <QVBoxLayout> + +using Debugger::Internal::ThreadsWindow; + +ThreadsWindow::ThreadsWindow(QWidget *parent) + : QTreeView(parent), m_alwaysResizeColumnsToContents(false) +{ + setWindowTitle(tr("Thread")); + + setAlternatingRowColors(true); + setRootIsDecorated(false); + setIconSize(QSize(10, 10)); + + header()->setDefaultAlignment(Qt::AlignLeft); + + connect(this, SIGNAL(activated(const QModelIndex &)), + this, SLOT(rowActivated(const QModelIndex &))); +} + +void ThreadsWindow::resizeEvent(QResizeEvent *event) +{ + //QHeaderView *hv = header(); + //int totalSize = event->size().width() - 120; + //hv->resizeSection(0, 45); + //hv->resizeSection(1, totalSize); + //hv->resizeSection(2, 55); + QTreeView::resizeEvent(event); +} + +void ThreadsWindow::rowActivated(const QModelIndex &index) +{ + //qDebug() << "ACTIVATED: " << index.row() << index.column(); + emit threadSelected(index.row()); +} + +void ThreadsWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + QMenu menu; + QAction *act1 = new QAction(tr("Adjust column widths to contents"), &menu); + QAction *act2 = new QAction(tr("Always adjust column widths to contents"), &menu); + act2->setCheckable(true); + act2->setChecked(m_alwaysResizeColumnsToContents); + menu.addAction(act1); + menu.addAction(act2); + + QAction *act = menu.exec(ev->globalPos()); + + if (act == act1) + resizeColumnsToContents(); + else if (act == act2) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); +} + +void ThreadsWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + //resizeColumnToContents(1); +} + +void ThreadsWindow::setAlwaysResizeColumnsToContents(bool on) +{ + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + //header()->setResizeMode(1, mode); +} + diff --git a/src/plugins/debugger/threadswindow.h b/src/plugins/debugger/threadswindow.h new file mode 100644 index 0000000000..05b94dc531 --- /dev/null +++ b/src/plugins/debugger/threadswindow.h @@ -0,0 +1,68 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_THREADWINDOW_H +#define DEBUGGER_THREADWINDOW_H + +#include <QtGui/QTreeView> + +namespace Debugger { +namespace Internal { + +class ThreadsWindow : public QTreeView +{ + Q_OBJECT + +public: + ThreadsWindow(QWidget *parent = 0); + +signals: + void threadSelected(int); + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on); + +private slots: + void rowActivated(const QModelIndex &index); + +private: + void resizeEvent(QResizeEvent *ev); + void contextMenuEvent(QContextMenuEvent *ev); + + bool m_alwaysResizeColumnsToContents; +}; + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_THREADWINDOW_H diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp new file mode 100644 index 0000000000..9147aa308e --- /dev/null +++ b/src/plugins/debugger/watchhandler.cpp @@ -0,0 +1,1137 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "watchhandler.h" + +#if USE_MODEL_TEST +#include "modeltest.h" +#endif + +#include "assert.h" + +#include <QtCore/QDebug> +#include <QtCore/QEvent> + +#include <QtGui/QApplication> +#include <QtGui/QLabel> +#include <QtGui/QToolTip> +#include <QtGui/QTextEdit> + +#include <ctype.h> + +// creates debug output regarding pending watch data results +//#define DEBUG_PENDING 1 +// creates debug output for accesses to the itemmodel +//#define DEBUG_MODEL 1 + +#if DEBUG_MODEL +# define MODEL_DEBUG(s) qDebug() << s +#else +# define MODEL_DEBUG(s) +#endif +#define MODEL_DEBUGX(s) qDebug() << s + +using namespace Debugger::Internal; + +static const QString strNotInScope = QLatin1String("<not in scope>"); + +static bool isIntOrFloatType(const QString &type) +{ + static const QStringList types = QStringList() + << "char" << "int" << "short" << "float" << "double" << "long" + << "bool" << "signed char" << "unsigned" << "unsigned char" + << "unsigned int" << "unsigned long" << "long long"; + return types.contains(type); +} + +static bool isPointerType(const QString &type) +{ + return type.endsWith("*") || type.endsWith("* const"); +} + +static QString htmlQuote(const QString &str0) +{ + QString str = str0; + str.replace('&', "&"); + str.replace('<', "<"); + str.replace('>', ">"); + return str; +} + +//////////////////////////////////////////////////////////////////// +// +// WatchData +// +//////////////////////////////////////////////////////////////////// + +WatchData::WatchData() +{ + valuedisabled = false; + state = InitialState; + childCount = -1; + parentIndex = -1; + row = -1; + level = -1; + changed = false; +} + +void WatchData::setError(const QString &msg) +{ + setAllUnneeded(); + value = msg; + setChildCount(0); + valuedisabled = true; +} + +static QByteArray quoteUnprintable(const QByteArray &ba) +{ + QByteArray res; + char buf[10]; + for (int i = 0, n = ba.size(); i != n; ++i) { + char c = ba.at(i); + if (isprint(c)) { + res += c; + } else { + qsnprintf(buf, sizeof(buf) - 1, "\\%x", int(c)); + res += buf; + } + } + return res; +} + +void WatchData::setValue(const QByteArray &value0) +{ + value = quoteUnprintable(value0); + if (value == "{...}") { + value.clear(); + childCount = 1; // at least one... + } + + // avoid duplicated information + if (value.startsWith("(") && value.contains(") 0x")) + value = value.mid(value.lastIndexOf(") 0x") + 2); + + // doubles are sometimes displayed as "@0x6141378: 1.2". + // I don't want that. + if (/*isIntOrFloatType(type) && */ value.startsWith("@0x") + && value.contains(':')) { + value = value.mid(value.indexOf(':') + 2); + setChildCount(0); + } + + // "numchild" is sometimes lying + //MODEL_DEBUG("\n\n\nPOINTER: " << type << value); + if (isPointerType(type)) + setChildCount(value != "0x0" && value != "<null>"); + + // pointer type information is available in the 'type' + // column. No need to duplicate it here. + if (value.startsWith("(" + type + ") 0x")) + value = value.section(" ", -1, -1); + + setValueUnneeded(); +} + +void WatchData::setValueToolTip(const QString &tooltip) +{ + valuetooltip = tooltip; +} + +void WatchData::setType(const QString &str) +{ + type = str.trimmed(); + bool changed = true; + while (changed) { + if (type.endsWith("const")) + type.chop(5); + else if (type.endsWith(" ")) + type.chop(1); + else if (type.endsWith("&")) + type.chop(1); + else if (type.startsWith("const ")) + type = type.mid(6); + else if (type.startsWith("volatile ")) + type = type.mid(9); + else if (type.startsWith("class ")) + type = type.mid(6); + else if (type.startsWith("struct ")) + type = type.mid(6); + else if (type.startsWith(" ")) + type = type.mid(1); + else + changed = false; + } + setTypeUnneeded(); + if (isIntOrFloatType(type)) + setChildCount(0); +} + +void WatchData::setAddress(const QString & str) +{ + addr = str; +} + +QString WatchData::toString() const +{ + QString res = "{"; + + res += "level=\"" + QString::number(level) + "\","; + res += "parent=\"" + QString::number(parentIndex) + "\","; + res += "row=\"" + QString::number(row) + "\","; + res += "child=\""; + foreach (int index, childIndex) + res += QString::number(index) + ","; + if (res.endsWith(',')) + res[res.size() - 1] = '"'; + else + res += '"'; + res += ","; + + + if (!iname.isEmpty()) + res += "iname=\"" + iname + "\","; + if (!exp.isEmpty()) + res += "exp=\"" + exp + "\","; + + if (!variable.isEmpty()) + res += "variable=\"" + variable + "\","; + + if (isValueNeeded()) + res += "value=<needed>,"; + if (isValueKnown() && !value.isEmpty()) + res += "value=\"" + value + "\","; + + if (!editvalue.isEmpty()) + res += "editvalue=\"" + editvalue + "\","; + + if (isTypeNeeded()) + res += "type=<needed>,"; + if (isTypeKnown() && !type.isEmpty()) + res += "type=\"" + type + "\","; + + if (isChildCountNeeded()) + res += "numchild=<needed>,"; + if (isChildCountKnown() && childCount == -1) + res += "numchild=\"" + QString::number(childCount) + "\","; + + if (isChildrenNeeded()) + res += "children=<needed>,"; + + if (res.endsWith(',')) + res[res.size() - 1] = '}'; + else + res += '}'; + + return res; +} + + +static bool iNameSorter(const WatchData &d1, const WatchData &d2) +{ + if (d1.level != d2.level) + return d1.level < d2.level; + + for (int level = 0; level != d1.level; ++level) { + QString name1 = d1.iname.section('.', level, level); + QString name2 = d2.iname.section('.', level, level); + //MODEL_DEBUG(" SORT: " << name1 << name2 << (name1 < name2)); + + if (name1 != name2) { + // This formerly used inames. in this case 'lastIndexOf' probably + // makes more sense. + if (name1.startsWith('[') && name2.startsWith('[')) { + return name1.mid(1, name1.indexOf(']') - 1).toInt() + < name2.mid(1, name2.indexOf(']') - 1).toInt(); + // numbers should be sorted according to their numerical value + //int pos = d1.name.lastIndexOf('.'); + //if (pos != -1 && pos + 1 != d1.name.size() && d1.name.at(pos + 1).isDigit()) + // return d1.name.size() < d2.name.size(); + // fall through + } + return name1 < name2; + } + } + return false; +} + +static QString parentName(const QString &iname) +{ + int pos = iname.lastIndexOf("."); + if (pos == -1) + return QString(); + return iname.left(pos); +} + + +static void insertDataHelper(QList<WatchData> &list, const WatchData &data) +{ + // FIXME: Quadratic algorithm + for (int i = list.size(); --i >= 0; ) { + if (list.at(i).iname == data.iname) { + list[i] = data; + return; + } + } + list.append(data); +} + +static WatchData take(const QString &iname, QList<WatchData> *list) +{ + for (int i = list->size(); --i >= 0;) { + if (list->at(i).iname == iname) { + WatchData res = list->at(i); + (*list)[i] = list->back(); + (void) list->takeLast(); + return res; + //return list->takeAt(i); + } + } + return WatchData(); +} + + +/////////////////////////////////////////////////////////////////////// +// +// WatchHandler +// +/////////////////////////////////////////////////////////////////////// + +WatchHandler::WatchHandler() +{ + m_expandPointers = true; + m_inFetchMore = false; + m_inChange = false; + + cleanModel(); + m_displaySet = m_completeSet; +} + +bool WatchHandler::setData(const QModelIndex &idx, + const QVariant &value, int role) +{ +/* + Q_UNUSED(idx); + Q_UNUSED(value); + Q_UNUSED(role); + if (role == VisualRole) { + QString iname = inameFromIndex(index); + setDisplayedIName(iname, value.toBool()); + return true; + } + return true; +*/ + return QAbstractItemModel::setData(idx, value, role); +} + +static QString niceType(QString type) +{ + if (type.contains("std::")) { + static QRegExp re("std::vector<(.*)\\s*,std::allocator<(.*)>\\s*>"); + re.setMinimal(true); + + type.replace("std::basic_string<char, std::char_traits<char>, " + "std::allocator<char> >", "std::string"); + type.replace("std::basic_string<wchar_t, std::char_traits<wchar_t>, " + "std::allocator<wchar_t> >", "std::wstring"); + + for (int i = 0; i != 10; ++i) { + if (re.indexIn(type) == -1 || re.cap(1) != re.cap(2)) + break; + type.replace(re.cap(0), "std::vector<" + re.cap(1) + ">"); + } + + type.replace(" >", ">"); + } + return type; +} + +QVariant WatchHandler::data(const QModelIndex &idx, int role) const +{ + int node = idx.internalId(); + if (node < 0) + return QVariant(); + + const WatchData &data = m_displaySet.at(node); + + switch (role) { + case Qt::DisplayRole: { + switch (idx.column()) { + case 0: return data.name; + case 1: return data.value; + case 2: return niceType(data.type); + default: break; + } + break; + } + + case Qt::ToolTipRole: { + QString val = data.value; + if (val.size() > 1000) + val = val.left(1000) + " ... <cut off>"; + + QString tt = "<table>"; + //tt += "<tr><td>internal name</td><td> : </td><td>"; + //tt += htmlQuote(iname) + "</td></tr>"; + tt += "<tr><td>expression</td><td> : </td><td>"; + tt += htmlQuote(data.exp) + "</td></tr>"; + tt += "<tr><td>type</td><td> : </td><td>"; + tt += htmlQuote(data.type) + "</td></tr>"; + //if (!valuetooltip.isEmpty()) + // tt += valuetooltip; + //else + tt += "<tr><td>value</td><td> : </td><td>"; + tt += htmlQuote(data.value) + "</td></tr>"; + tt += "<tr><td>addr</td><td> : </td><td>"; + tt += htmlQuote(data.addr) + "</td></tr>"; + tt += "<tr><td>iname</td><td> : </td><td>"; + tt += htmlQuote(data.iname) + "</td></tr>"; + tt += "</table>"; + tt.replace("@value@", htmlQuote(data.value)); + + if (tt.size() > 10000) + tt = tt.left(10000) + " ... <cut off>"; + return tt; + } + + case Qt::ForegroundRole: { + static const QVariant red(QColor(200, 0, 0)); + static const QVariant black(QColor(0, 0, 0)); + static const QVariant gray(QColor(140, 140, 140)); + switch (idx.column()) { + case 0: return black; + case 1: return data.valuedisabled ? gray : data.changed ? red : black; + case 2: return black; + } + break; + } + + case INameRole: + return data.iname; + + case VisualRole: + return m_displayedINames.contains(data.iname); + + default: + break; + } + return QVariant(); +} + +Qt::ItemFlags WatchHandler::flags(const QModelIndex &idx) const +{ + using namespace Qt; + + if (!idx.isValid()) + return ItemFlags(); + + int node = idx.internalId(); + if (node < 0) + return ItemFlags(); + + // enabled, editable, selectable, checkable, and can be used both as the + // source of a drag and drop operation and as a drop target. + + static const ItemFlags DefaultNotEditable = + ItemIsSelectable + | ItemIsDragEnabled + | ItemIsDropEnabled + // | ItemIsUserCheckable + // | ItemIsTristate + | ItemIsEnabled; + + static const ItemFlags DefaultEditable = + DefaultNotEditable | ItemIsEditable; + + const WatchData &data = m_displaySet.at(node); + return idx.column() == 1 && + data.isWatcher() ? DefaultEditable : DefaultNotEditable; +} + +QVariant WatchHandler::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Vertical) + return QVariant(); + if (role == Qt::DisplayRole) { + switch (section) { + case 0: return tr("Name") + " "; + case 1: return tr("Value") + " "; + case 2: return tr("Type") + " "; + } + } + return QVariant(); +} + +QString WatchHandler::toString() const +{ + QString res; + res += "\nIncomplete:\n"; + for (int i = 0, n = m_incompleteSet.size(); i != n; ++i) { + res += QString("%1: ").arg(i); + res += m_incompleteSet.at(i).toString(); + res += '\n'; + } + res += "\nComplete:\n"; + for (int i = 0, n = m_completeSet.size(); i != n; ++i) { + res += QString("%1: ").arg(i); + res += m_completeSet.at(i).toString(); + res += '\n'; + } + res += "\nDisplay:\n"; + for (int i = 0, n = m_displaySet.size(); i != n; ++i) { + res += QString("%1: ").arg(i); + res += m_displaySet.at(i).toString(); + res += '\n'; + } +#if 0 + res += "\nOld:\n"; + for (int i = 0, n = m_oldSet.size(); i != n; ++i) { + res += m_oldSet.at(i).toString(); + res += '\n'; + } +#endif + return res; +} + +WatchData *WatchHandler::findData(const QString &iname) +{ + for (int i = m_completeSet.size(); --i >= 0; ) + if (m_completeSet.at(i).iname == iname) + return &m_completeSet[i]; + return 0; +} + +WatchData WatchHandler::takeData(const QString &iname) +{ + WatchData data = take(iname, &m_incompleteSet); + if (data.isValid()) + return data; + return take(iname, &m_completeSet); +} + +QList<WatchData> WatchHandler::takeCurrentIncompletes() +{ + QList<WatchData> res = m_incompleteSet; + //MODEL_DEBUG("TAKING INCOMPLETES" << toString()); + m_incompleteSet.clear(); + return res; +} + +void WatchHandler::rebuildModel() +{ + if (m_inChange) { + MODEL_DEBUG("RECREATE MODEL IGNORED, CURRENT SET:\n" << toString()); + return; + } + + #ifdef DEBUG_PENDING + MODEL_DEBUG("RECREATE MODEL, CURRENT SET:\n" << toString()); + #endif + + QHash<QString, QString> oldValues; + for (int i = 0, n = m_oldSet.size(); i != n; ++i) { + WatchData &data = m_oldSet[i]; + oldValues[data.iname] = data.value; + } + #ifdef DEBUG_PENDING + MODEL_DEBUG("OLD VALUES: " << oldValues); + #endif + + for (int i = m_completeSet.size(); --i >= 0; ) { + WatchData &data = m_completeSet[i]; + data.level = data.iname.isEmpty() ? 0 : data.iname.count('.') + 1; + data.childIndex.clear(); + } + + qSort(m_completeSet.begin(), m_completeSet.end(), &iNameSorter); + + QHash<QString, int> iname2idx; + + for (int i = m_completeSet.size(); --i > 0; ) { + WatchData &data = m_completeSet[i]; + data.parentIndex = 0; + data.childIndex.clear(); + iname2idx[data.iname] = i; + } + + for (int i = 1; i < m_completeSet.size(); ++i) { + WatchData &data = m_completeSet[i]; + QString parentIName = parentName(data.iname); + data.parentIndex = iname2idx.value(parentIName, 0); + WatchData &parent = m_completeSet[data.parentIndex]; + data.row = parent.childIndex.size(); + parent.childIndex.append(i); + } + + m_oldSet = m_completeSet; + m_oldSet += m_incompleteSet; + + for (int i = 0, n = m_completeSet.size(); i != n; ++i) { + WatchData &data = m_completeSet[i]; + data.changed = !data.value.isEmpty() + && data.value != oldValues[data.iname] + && data.value != strNotInScope; + } + + //emit layoutAboutToBeChanged(); + + m_displaySet = m_completeSet; + + #ifdef DEBUG_PENDING + MODEL_DEBUG("SET " << toString()); + #endif + +#if 1 + // Append dummy item to get the [+] effect + for (int i = 0, n = m_displaySet.size(); i != n; ++i) { + WatchData &data = m_displaySet[i]; + if (data.childCount > 0 && data.childIndex.size() == 0) { + WatchData dummy; + dummy.state = 0; + dummy.row = 0; + dummy.iname = data.iname + ".dummy"; + //dummy.name = data.iname + ".dummy"; + //dummy.name = "<loading>"; + dummy.level = data.level + 1; + dummy.parentIndex = i; + dummy.childCount = 0; + data.childIndex.append(m_displaySet.size()); + m_displaySet.append(dummy); + } + } +#endif + + // Possibly append dummy items to prevent empty views + bool ok = true; + QWB_ASSERT(m_displaySet.size() >= 2, ok = false); + QWB_ASSERT(m_displaySet.at(1).iname == "local", ok = false); + QWB_ASSERT(m_displaySet.at(2).iname == "tooltip", ok = false); + QWB_ASSERT(m_displaySet.at(3).iname == "watch", ok = false); + if (ok) { + for (int i = 1; i <= 3; ++i) { + WatchData &data = m_displaySet[i]; + if (data.childIndex.size() == 0) { + WatchData dummy; + dummy.state = 0; + dummy.row = 0; + if (i == 1) { + dummy.iname = "local.dummy"; + dummy.name = "<No Locals>"; + } else if (i == 2) { + dummy.iname = "tooltip.dummy"; + dummy.name = "<No Tooltip>"; + } else { + dummy.iname = "watch.dummy"; + dummy.name = "<No Watchers>"; + } + dummy.level = 2; + dummy.parentIndex = i; + dummy.childCount = 0; + data.childIndex.append(m_displaySet.size()); + m_displaySet.append(dummy); + } + } + } + + m_inChange = true; + //qDebug() << "WATCHHANDLER: RESET ABOUT TO EMIT"; + emit reset(); + //qDebug() << "WATCHHANDLER: RESET EMITTED"; + m_inChange = false; + //emit layoutChanged(); + //QSet<QString> einames = m_expandedINames; + //einames.insert("local"); + //einames.insert("watch"); + //emit expandedItems(einames); + + #if DEBUG_MODEL + #if USE_MODEL_TEST + //(void) new ModelTest(this, this); + #endif + #endif + + #ifdef DEBUG_PENDING + MODEL_DEBUG("SORTED: " << toString()); + MODEL_DEBUG("EXPANDED INAMES: " << m_expandedINames); + #endif +} + +void WatchHandler::cleanup() +{ + m_oldSet.clear(); + m_expandedINames.clear(); + m_displayedINames.clear(); + cleanModel(); + m_displaySet = m_completeSet; +#if 0 + for (EditWindows::ConstIterator it = m_editWindows.begin(); + it != m_editWindows.end(); ++it) { + if (!it.value().isNull()) + delete it.value(); + } + m_editWindows.clear(); +#endif + emit reset(); +} + +void WatchHandler::collapseChildren(const QModelIndex &idx) +{ + if (m_inChange || m_completeSet.isEmpty()) { + //qDebug() << "WATCHHANDLER: COLLAPSE IGNORED" << idx; + return; + } + QWB_ASSERT(checkIndex(idx.internalId()), return); +#if 0 + QString iname0 = m_displaySet.at(idx.internalId()).iname; + MODEL_DEBUG("COLLAPSE NODE" << iname0); + QString iname1 = iname0 + '.'; + for (int i = m_completeSet.size(); --i >= 0; ) { + QString iname = m_completeSet.at(i).iname; + if (iname.startsWith(iname1)) { + // Better leave it in in case the user re-enters the branch? + (void) m_completeSet.takeAt(i); + MODEL_DEBUG(" REMOVING " << iname); + m_expandedINames.remove(iname); + } + } + m_expandedINames.remove(iname0); + //MODEL_DEBUG(toString()); + //rebuildModel(); +#endif +} + +void WatchHandler::expandChildren(const QModelIndex &idx) +{ + if (m_inChange || m_completeSet.isEmpty()) { + //qDebug() << "WATCHHANDLER: EXPAND IGNORED" << idx; + return; + } + int index = idx.internalId(); + if (index == 0) + return; + QWB_ASSERT(index >= 0, qDebug() << toString() << index; return); + QWB_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return); + const WatchData &display = m_displaySet.at(index); + QWB_ASSERT(index >= 0, qDebug() << toString() << index; return); + QWB_ASSERT(index < m_completeSet.size(), qDebug() << toString() << index; return); + const WatchData &complete = m_completeSet.at(index); + MODEL_DEBUG("\n\nEXPAND" << display.iname); + if (display.iname.isEmpty()) { + // This should not happen but the view seems to send spurious + // "expand()" signals folr the root item from time to time. + // Try to handle that gracfully. + //MODEL_DEBUG(toString()); + qDebug() << "FIXME: expandChildren, no data " << display.iname << "found" + << idx; + //rebuildModel(); + return; + } + + //qDebug() << " ... NODE: " << display.toString() + // << complete.childIndex.size() << complete.childCount; + + if (m_expandedINames.contains(display.iname)) + return; + + // This is a performance hack and not strictly necessary. + // Remove it if there are troubles when expanding nodes. + if (0 && complete.childCount > 0 && complete.childIndex.size() > 0) { + MODEL_DEBUG("SKIP FETCHING CHILDREN"); + return; + } + + WatchData data = takeData(display.iname); // remove previous data + m_expandedINames.insert(data.iname); + if (data.iname.contains('.')) // not for top-level items + data.setChildrenNeeded(); + insertData(data); + emit watchModelUpdateRequested(); +} + +void WatchHandler::insertData(const WatchData &data) +{ + //MODEL_DEBUG("INSERTDATA: " << data.toString()); + QWB_ASSERT(data.isValid(), return); + if (data.isSomethingNeeded()) + insertDataHelper(m_incompleteSet, data); + else + insertDataHelper(m_completeSet, data); + //MODEL_DEBUG("INSERT RESULT" << toString()); +} + +void WatchHandler::watchExpression(const QString &exp) +{ + // FIXME: 'exp' can contain illegal characters + //MODEL_DEBUG("WATCH: " << exp); + WatchData data; + data.exp = exp; + data.name = exp; + data.iname = "watch." + exp; + insertData(data); +} + + +void WatchHandler::setDisplayedIName(const QString &iname, bool on) +{ + WatchData *d = findData(iname); + if (!on || !d) { + delete m_editWindows.take(iname); + m_displayedINames.remove(iname); + return; + } + if (d->exp.isEmpty()) { + //emit statusMessageRequested(tr("Sorry. Cannot visualize objects without known address."), 5000); + return; + } + d->setValueNeeded(); + m_displayedINames.insert(iname); + insertData(*d); +} + +void WatchHandler::showEditValue(const WatchData &data) +{ + // editvalue is always base64 encoded + QByteArray ba = QByteArray::fromBase64(data.editvalue); + //QByteArray ba = data.editvalue; + QWidget *w = m_editWindows.value(data.iname); + qDebug() << "SHOW_EDIT_VALUE " << data.toString() << data.type + << data.iname << w; + if (data.type == "QImage") { + if (!w) { + w = new QLabel; + m_editWindows[data.iname] = w; + } + QDataStream ds(&ba, QIODevice::ReadOnly); + QVariant v; + ds >> v; + QString type = QString::fromAscii(v.typeName()); + QImage im = v.value<QImage>(); + if (QLabel *l = qobject_cast<QLabel *>(w)) + l->setPixmap(QPixmap::fromImage(im)); + } else if (data.type == "QPixmap") { + if (!w) { + w = new QLabel; + m_editWindows[data.iname] = w; + } + QDataStream ds(&ba, QIODevice::ReadOnly); + QVariant v; + ds >> v; + QString type = QString::fromAscii(v.typeName()); + QPixmap im = v.value<QPixmap>(); + if (QLabel *l = qobject_cast<QLabel *>(w)) + l->setPixmap(im); + } else if (data.type == "QString") { + if (!w) { + w = new QTextEdit; + m_editWindows[data.iname] = w; + } +#if 0 + QDataStream ds(&ba, QIODevice::ReadOnly); + QVariant v; + ds >> v; + QString type = QString::fromAscii(v.typeName()); + QString str = v.value<QString>(); +#else + MODEL_DEBUG("DATA: " << ba); + QString str = QString::fromUtf16((ushort *)ba.constData(), ba.size()/2); +#endif + if (QTextEdit *t = qobject_cast<QTextEdit *>(w)) + t->setText(str); + } + if (w) + w->show(); +} + +void WatchHandler::removeWatchExpression(const QString &iname) +{ + MODEL_DEBUG("REMOVE WATCH: " << iname); + (void) takeData(iname); + emit watchModelUpdateRequested(); +} + +void WatchHandler::cleanModel() +{ + // This uses data stored in m_oldSet to re-create a new set + // one-by-one + m_completeSet.clear(); + m_incompleteSet.clear(); + + WatchData root; + root.state = 0; + root.level = 0; + root.row = 0; + root.name = "Root"; + root.parentIndex = -1; + root.childIndex.append(1); + root.childIndex.append(2); + root.childIndex.append(3); + m_completeSet.append(root); + + WatchData local; + local.iname = "local"; + local.name = "Locals"; + local.state = 0; + local.level = 1; + local.row = 0; + local.parentIndex = 0; + m_completeSet.append(local); + + WatchData tooltip; + tooltip.iname = "tooltip"; + tooltip.name = "Tooltip"; + tooltip.state = 0; + tooltip.level = 1; + tooltip.row = 1; + tooltip.parentIndex = 0; + m_completeSet.append(tooltip); + + WatchData watch; + watch.iname = "watch"; + watch.name = "Watchers"; + watch.state = 0; + watch.level = 1; + watch.row = 2; + watch.parentIndex = 0; + m_completeSet.append(watch); +} + + +void WatchHandler::reinitializeWatchers() +{ + cleanModel(); + + // copy over all watchers and mark all watchers as incomplete + for (int i = 0, n = m_oldSet.size(); i < n; ++i) { + WatchData data = m_oldSet.at(i); + if (data.isWatcher()) { + data.level = -1; + data.row = -1; + data.parentIndex = -1; + data.variable.clear(); + data.setAllNeeded(); + data.valuedisabled = false; + insertData(data); // properly handles "neededChildren" + } + } +} + +bool WatchHandler::canFetchMore(const QModelIndex &parent) const +{ + MODEL_DEBUG("CAN FETCH MORE: " << parent << "false"); +#if 1 + Q_UNUSED(parent); + return false; +#else + // FIXME: not robust enough. Problem is that fetchMore + // needs to be made synchronous to be useful. Busy loop is no good. + if (!parent.isValid()) + return false; + QWB_ASSERT(checkIndex(parent.internalId()), return false); + const WatchData &data = m_displaySet.at(parent.internalId()); + MODEL_DEBUG("CAN FETCH MORE: " << parent << " children: " << data.childCount + << data.iname); + return data.childCount > 0; +#endif +} + +void WatchHandler::fetchMore(const QModelIndex &parent) +{ + MODEL_DEBUG("FETCH MORE: " << parent); + return; + + QWB_ASSERT(checkIndex(parent.internalId()), return); + QString iname = m_displaySet.at(parent.internalId()).iname; + + if (m_inFetchMore) { + MODEL_DEBUG("LOOP IN FETCH MORE" << iname); + return; + } + m_inFetchMore = true; + + WatchData data = takeData(iname); + MODEL_DEBUG("FETCH MORE: " << parent << ":" << iname << data.name); + + if (!data.isValid()) { + MODEL_DEBUG("FIXME: FETCH MORE, no data " << iname << "found"); + return; + } + + m_expandedINames.insert(data.iname); + if (data.iname.contains('.')) // not for top-level items + data.setChildrenNeeded(); + + MODEL_DEBUG("FETCH MORE: data:" << data.toString()); + insertData(data); + //emit watchUpdateRequested(); + + while (m_inFetchMore) { + QApplication::processEvents(); + } + m_inFetchMore = false; + MODEL_DEBUG("BUSY LOOP FINISHED, data:" << data.toString()); +} + +QModelIndex WatchHandler::index(int row, int col, const QModelIndex &parent) const +{ + #ifdef DEBUG_MODEL + MODEL_DEBUG("INDEX " << row << col << parent); + #endif + //if (col != 0) { + // MODEL_DEBUG(" -> " << QModelIndex() << " (3) "); + // return QModelIndex(); + //} + if (row < 0) { + MODEL_DEBUG(" -> " << QModelIndex() << " (4) "); + return QModelIndex(); + } + if (!parent.isValid()) { + if (row == 0 && col >= 0 && col < 3 && parent.row() == -1) { + MODEL_DEBUG(" -> " << createIndex(0, 0, 0) << " (B) "); + return createIndex(0, col, 0); + } + MODEL_DEBUG(" -> " << QModelIndex() << " (1) "); + return QModelIndex(); + } + int parentIndex = parent.internalId(); + if (parentIndex < 0) { + //MODEL_DEBUG("INDEX " << row << col << parentIndex << "INVALID"); + MODEL_DEBUG(" -> " << QModelIndex() << " (2) "); + return QModelIndex(); + } + QWB_ASSERT(checkIndex(parentIndex), return QModelIndex()); + const WatchData &data = m_displaySet.at(parentIndex); + QWB_ASSERT(row >= 0, qDebug() << "ROW: " << row << "PARENT: " << parent + << data.toString() << toString(); return QModelIndex()); + QWB_ASSERT(row < data.childIndex.size(), + MODEL_DEBUG("ROW: " << row << data.toString() << toString()); + return QModelIndex()); + QModelIndex idx = createIndex(row, col, data.childIndex.at(row)); + QWB_ASSERT(idx.row() == m_displaySet.at(idx.internalId()).row, + return QModelIndex()); + MODEL_DEBUG(" -> " << idx << " (A) "); + return idx; +} + +QModelIndex WatchHandler::parent(const QModelIndex &idx) const +{ + if (!idx.isValid()) { + MODEL_DEBUG(" -> " << QModelIndex() << " (1) "); + return QModelIndex(); + } + MODEL_DEBUG("PARENT " << idx); + int currentIndex = idx.internalId(); + QWB_ASSERT(checkIndex(currentIndex), return QModelIndex()); + QWB_ASSERT(idx.row() == m_displaySet.at(currentIndex).row, + MODEL_DEBUG("IDX: " << idx << toString(); return QModelIndex())); + int parentIndex = m_displaySet.at(currentIndex).parentIndex; + if (parentIndex < 0) { + MODEL_DEBUG(" -> " << QModelIndex() << " (2) "); + return QModelIndex(); + } + QWB_ASSERT(checkIndex(parentIndex), return QModelIndex()); + QModelIndex parent = + createIndex(m_displaySet.at(parentIndex).row, 0, parentIndex); + MODEL_DEBUG(" -> " << parent); + return parent; +} + +int WatchHandler::rowCount(const QModelIndex &idx) const +{ + MODEL_DEBUG("ROW COUNT " << idx); + if (idx.column() > 0) { + MODEL_DEBUG(" -> " << 0 << " (A) "); + return 0; + } + int thisIndex = idx.internalId(); + QWB_ASSERT(checkIndex(thisIndex), return 0); + if (idx.row() == -1 && idx.column() == -1) { + MODEL_DEBUG(" -> " << 3 << " (B) "); + return 1; + } + if (thisIndex < 0) { + MODEL_DEBUG(" -> " << 0 << " (C) "); + return 0; + } + if (thisIndex == 0) { + MODEL_DEBUG(" -> " << 3 << " (D) "); + return 3; + } + const WatchData &data = m_displaySet.at(thisIndex); + int rows = data.childIndex.size(); + MODEL_DEBUG(" -> " << rows << " (E) "); + return rows; + // Try lazy evaluation + //if (rows > 0) + // return rows; + //return data.childCount; +} + +int WatchHandler::columnCount(const QModelIndex &idx) const +{ + MODEL_DEBUG("COLUMN COUNT " << idx); + if (idx == QModelIndex()) { + MODEL_DEBUG(" -> " << 3 << " (C) "); + return 3; + } + if (idx.column() != 0) { + MODEL_DEBUG(" -> " << 0 << " (A) "); + return 0; + } + MODEL_DEBUG(" -> " << 3 << " (B) "); + QWB_ASSERT(checkIndex(idx.internalId()), return 3); + return 3; +} + +bool WatchHandler::hasChildren(const QModelIndex &idx) const +{ + // that's the base implementation: + bool base = rowCount(idx) > 0 && columnCount(idx) > 0; + MODEL_DEBUG("HAS CHILDREN: " << idx << base); + return base; + QWB_ASSERT(checkIndex(idx.internalId()), return false); + const WatchData &data = m_displaySet.at(idx.internalId()); + MODEL_DEBUG("HAS CHILDREN: " << idx << data.toString()); + return data.childCount > 0; // || data.childIndex.size() > 0; +} + +bool WatchHandler::checkIndex(int id) const +{ + if (id < 0) { + MODEL_DEBUG("CHECK INDEX FAILED" << id); + return false; + } + if (id >= m_displaySet.size()) { + MODEL_DEBUG("CHECK INDEX FAILED" << id << toString()); + return false; + } + return true; +} diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h new file mode 100644 index 0000000000..b3db114be8 --- /dev/null +++ b/src/plugins/debugger/watchhandler.h @@ -0,0 +1,219 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_WATCHHANDLER_H +#define DEBUGGER_WATCHHANDLER_H + +#include <QtCore/QPointer> +#include <QtCore/QObject> +#include <QtCore/QHash> +#include <QtCore/QSet> +#include <QtGui/QStandardItem> +#include <QtGui/QStandardItemModel> +#include <QtGui/QTreeView> +#include <QtScript/QScriptValue> + +namespace Debugger { +namespace Internal { + +class WatchData +{ +public: + WatchData(); + + enum State + { + Complete = 0, + ChildCountNeeded = 1, + ValueNeeded = 2, + TypeNeeded = 4, + ChildrenNeeded = 8, + + NeededMask = ValueNeeded + | TypeNeeded + | ChildrenNeeded + | ChildCountNeeded, + + InitialState = ValueNeeded + | TypeNeeded + | ChildrenNeeded + | ChildCountNeeded + }; + + void setValue(const QByteArray &); + void setType(const QString &); + void setValueToolTip(const QString &); + void setError(const QString &); + void setAddress(const QString &address); + + bool isSomethingNeeded() const { return state & NeededMask; } + void setAllNeeded() { state = NeededMask; } + void setAllUnneeded() { state = State(0); } + + bool isTypeNeeded() const { return state & TypeNeeded; } + bool isTypeKnown() const { return !(state & TypeNeeded); } + void setTypeNeeded() { state = State(state | TypeNeeded); } + void setTypeUnneeded() { state = State(state & ~TypeNeeded); } + + bool isValueNeeded() const { return state & ValueNeeded; } + bool isValueKnown() const { return !(state & ValueNeeded); } + void setValueNeeded() { state = State(state | ValueNeeded); } + void setValueUnneeded() { state = State(state & ~ValueNeeded); } + + bool isChildrenNeeded() const { return state & ChildrenNeeded; } + bool isChildrenKnown() const { return !(state & ChildrenNeeded); } + void setChildrenNeeded() { state = State(state | ChildrenNeeded); } + void setChildrenUnneeded() { state = State(state & ~ChildrenNeeded); } + + bool isChildCountNeeded() const { return state & ChildCountNeeded; } + bool isChildCountKnown() const { return !(state & ChildCountNeeded); } + void setChildCountNeeded() { state = State(state | ChildCountNeeded); } + void setChildCountUnneeded() { state = State(state & ~ChildCountNeeded); } + void setChildCount(int n) { childCount = n; setChildCountUnneeded(); + if (n == 0) setChildrenUnneeded(); } + + QString toString() const; + bool isLocal() const { return iname.startsWith(QLatin1String("local.")); } + bool isWatcher() const { return iname.startsWith(QLatin1String("watch.")); }; + bool isValid() const { return !iname.isEmpty(); } + +public: + QString iname; // internal name sth like 'local.baz.public.a' + QString exp; // the expression + QString name; // displayed name + QString value; // displayed value + QByteArray editvalue; // displayed value + QString valuetooltip; // tooltip in value column + QString type; // displayed type + QString variable; // name of internal Gdb variable if created + QString addr; // displayed adress + QString framekey; // key for type cache + QScriptValue scriptValue; // if needed... + int childCount; + bool valuedisabled; // value will be greyed out + +private: + +public: + int state; + + // Model + int parentIndex; + int row; + int level; + QList<int> childIndex; + bool changed; +}; + +enum { INameRole = Qt::UserRole, VisualRole }; + + +class WatchHandler : public QAbstractItemModel +{ + Q_OBJECT + +public: + WatchHandler(); + QAbstractItemModel *model() { return this; } + + // + // QAbstractItemModel + // + bool setData(const QModelIndex &index, const QVariant &value, int role); + QVariant data(const QModelIndex &index, int role) const; + QModelIndex index(int, int, const QModelIndex &idx) const; + QModelIndex parent(const QModelIndex &idx) const; + int rowCount(const QModelIndex &idx) const; + int columnCount(const QModelIndex &idx) const; + bool hasChildren(const QModelIndex &idx) const; + Qt::ItemFlags flags(const QModelIndex &idx) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + bool checkIndex(int id) const; + +//public slots: + void cleanup(); + void watchExpression(const QString &exp); + void removeWatchExpression(const QString &exp); + void reinitializeWatchers(); + + void collapseChildren(const QModelIndex &idx); + void expandChildren(const QModelIndex &idx); + + void rebuildModel(); // unconditionally version of above + void showEditValue(const WatchData &data); + + bool isDisplayedIName(const QString &iname) const + { return m_displayedINames.contains(iname); } + bool isExpandedIName(const QString &iname) const + { return m_expandedINames.contains(iname); } + + void insertData(const WatchData &data); + QList<WatchData> takeCurrentIncompletes(); + + bool canFetchMore(const QModelIndex &parent) const; + void fetchMore(const QModelIndex &parent); + + WatchData *findData(const QString &iname); + +signals: + void watchModelUpdateRequested(); + +private: + WatchData takeData(const QString &iname); + QString toString() const; + void cleanModel(); + + bool m_expandPointers; + bool m_inChange; + + typedef QMap<QString, QPointer<QWidget> > EditWindows; + EditWindows m_editWindows; + + QList<WatchData> m_incompleteSet; + QList<WatchData> m_completeSet; + QList<WatchData> m_oldSet; + QList<WatchData> m_displaySet; + + void setDisplayedIName(const QString &iname, bool on); + QSet<QString> m_expandedINames; // those expanded in the treeview + QSet<QString> m_displayedINames; // those with "external" viewers + + bool m_inFetchMore; +}; + +} // namespace Internal +} // namespace Debugger + +Q_DECLARE_METATYPE(Debugger::Internal::WatchData); + +#endif // DEBUGGER_WATCHHANDLER_H diff --git a/src/plugins/debugger/watchwindow.cpp b/src/plugins/debugger/watchwindow.cpp new file mode 100644 index 0000000000..6f153d2de5 --- /dev/null +++ b/src/plugins/debugger/watchwindow.cpp @@ -0,0 +1,253 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#include "watchwindow.h" + +#include <QtCore/QDebug> +#include <QtCore/QTimer> + +#include <QtGui/QAction> +#include <QtGui/QContextMenuEvent> +#include <QtGui/QHeaderView> +#include <QtGui/QLineEdit> +#include <QtGui/QMenu> +#include <QtGui/QResizeEvent> +#include <QtGui/QSplitter> + +using namespace Debugger::Internal; + +enum { INameRole = Qt::UserRole, VisualRole }; + +///////////////////////////////////////////////////////////////////// +// +// WatchWindow +// +///////////////////////////////////////////////////////////////////// + +WatchWindow::WatchWindow(Type type, QWidget *parent) + : QTreeView(parent), m_type(type) +{ + m_blocked = false; + setWindowTitle(tr("Locals and Watchers")); + setAlternatingRowColors(true); + setIndentation(indentation() * 9/10); + setUniformRowHeights(true); + + connect(itemDelegate(), SIGNAL(commitData(QWidget *)), + this, SLOT(handleChangedItem(QWidget *))); + connect(this, SIGNAL(expanded(QModelIndex)), + this, SLOT(expandNode(QModelIndex))); + connect(this, SIGNAL(collapsed(QModelIndex)), + this, SLOT(collapseNode(QModelIndex))); +} + +void WatchWindow::expandNode(const QModelIndex &idx) +{ + //QModelIndex mi0 = idx.sibling(idx.row(), 0); + //QString iname = model()->data(mi0, INameRole).toString(); + //QString name = model()->data(mi0, Qt::DisplayRole).toString(); + //qDebug() << "\n\nEXPAND NODE " // << iname << name + // << idx << (m_blocked ? "blocked" : "passed"); + //if (isExpanded(idx)) + // return; + //if (m_blocked) + // return; + emit requestExpandChildren(idx); +} + +void WatchWindow::collapseNode(const QModelIndex &idx) +{ + //QModelIndex mi0 = idx.sibling(idx.row(), 0); + //QString iname = model()->data(mi0, INameRole).toString(); + //QString name = model()->data(mi0, Qt::DisplayRole).toString(); + //qDebug() << "COLLAPSE NODE " << idx; + if (m_blocked) + return; + emit requestCollapseChildren(idx); +} + +void WatchWindow::contextMenuEvent(QContextMenuEvent *ev) +{ + QMenu menu; + QAction *act1 = new QAction("Adjust column widths to contents", &menu); + QAction *act2 = new QAction("Always adjust column widths to contents", &menu); + act2->setCheckable(true); + act2->setChecked(m_alwaysResizeColumnsToContents); + + menu.addAction(act1); + menu.addAction(act2); + + QAction *act3 = 0; + QAction *act4 = 0; + QModelIndex idx = indexAt(ev->pos()); + QModelIndex mi0 = idx.sibling(idx.row(), 0); + QString exp = model()->data(mi0).toString(); + QString iname = model()->data(mi0, INameRole).toString(); + QModelIndex mi1 = idx.sibling(idx.row(), 0); + QString value = model()->data(mi1).toString(); + bool visual = false; + if (idx.isValid()) { + menu.addSeparator(); + if (m_type == LocalsType) + act3 = new QAction("Watch expression '" + exp + "'", &menu); + else + act3 = new QAction("Remove expression '" + exp + "'", &menu); + menu.addAction(act3); + + visual = model()->data(mi0, VisualRole).toBool(); + act4 = new QAction("Watch expression '" + exp + "' in separate widget", &menu); + act4->setCheckable(true); + act4->setChecked(visual); + // FIXME: menu.addAction(act4); + } + + QAction *act = menu.exec(ev->globalPos()); + + if (!act) + ; + else if (act == act1) + resizeColumnsToContents(); + else if (act == act2) + setAlwaysResizeColumnsToContents(!m_alwaysResizeColumnsToContents); + else if (act == act3) + if (m_type == LocalsType) + emit requestWatchExpression(exp); + else + emit requestRemoveWatchExpression(iname); + else if (act == act4) + model()->setData(mi0, !visual, VisualRole); +} + +void WatchWindow::resizeColumnsToContents() +{ + resizeColumnToContents(0); + resizeColumnToContents(1); +} + +void WatchWindow::setAlwaysResizeColumnsToContents(bool on) +{ + if (!header()) + return; + m_alwaysResizeColumnsToContents = on; + QHeaderView::ResizeMode mode = on + ? QHeaderView::ResizeToContents : QHeaderView::Interactive; + header()->setResizeMode(0, mode); + header()->setResizeMode(1, mode); +} + +void WatchWindow::editItem(const QModelIndex &idx) +{ + Q_UNUSED(idx); // FIXME +} + +void WatchWindow::reset() +{ + int row = 0; + if (m_type == TooltipType) + row = 1; + else if (m_type == WatchersType) + row = 2; + //qDebug() << "WATCHWINDOW::RESET" << row; + QTreeView::reset(); + setRootIndex(model()->index(row, 0, model()->index(0, 0))); + //setRootIndex(model()->index(0, 0)); +} + +void WatchWindow::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + + setRootIsDecorated(true); + header()->setDefaultAlignment(Qt::AlignLeft); + header()->setResizeMode(QHeaderView::ResizeToContents); + if (m_type != LocalsType) + header()->hide(); + + connect(model, SIGNAL(modelAboutToBeReset()), + this, SLOT(modelAboutToBeReset())); + connect(model, SIGNAL(modelReset()), + this, SLOT(modelReset())); +} + +void WatchWindow::modelAboutToBeReset() +{ + m_blocked = true; + //qDebug() << "Model about to be reset"; + m_expandedItems.clear(); + m_expandedItems.insert("local"); + m_expandedItems.insert("watch"); + modelAboutToBeResetHelper(model()->index(0, 0)); + //qDebug() << " expanded: " << m_expandedItems; +} + +void WatchWindow::modelAboutToBeResetHelper(const QModelIndex &idx) +{ + QString iname = model()->data(idx, INameRole).toString(); + //qDebug() << "Model about to be reset helper" << iname << idx + // << isExpanded(idx); + if (isExpanded(idx)) + m_expandedItems.insert(iname); + for (int i = 0, n = model()->rowCount(idx); i != n; ++i) { + QModelIndex idx1 = model()->index(i, 0, idx); + modelAboutToBeResetHelper(idx1); + } +} + +void WatchWindow::modelReset() +{ + //qDebug() << "Model reset"; + expand(model()->index(0, 0)); + modelResetHelper(model()->index(0, 0)); + m_blocked = false; +} + +void WatchWindow::modelResetHelper(const QModelIndex &idx) +{ + QString name = model()->data(idx, Qt::DisplayRole).toString(); + QString iname = model()->data(idx, INameRole).toString(); + //qDebug() << "Model reset helper" << iname << name; + if (m_expandedItems.contains(iname)) { + expand(idx); + for (int i = 0, n = model()->rowCount(idx); i != n; ++i) { + QModelIndex idx1 = model()->index(i, 0, idx); + modelResetHelper(idx1); + } + } +} + +void WatchWindow::handleChangedItem(QWidget *widget) +{ + QLineEdit *lineEdit = qobject_cast<QLineEdit *>(widget); + if (lineEdit) + requestAssignValue("foo", lineEdit->text()); +} + diff --git a/src/plugins/debugger/watchwindow.h b/src/plugins/debugger/watchwindow.h new file mode 100644 index 0000000000..da0ee9ee8f --- /dev/null +++ b/src/plugins/debugger/watchwindow.h @@ -0,0 +1,95 @@ +/*************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Qt Software Information (qt-info@nokia.com) +** +** +** Non-Open Source Usage +** +** Licensees may use this file in accordance with the Qt Beta Version +** License Agreement, Agreement version 2.2 provided with the Software or, +** alternatively, in accordance with the terms contained in a written +** agreement between you and Nokia. +** +** GNU General Public License Usage +** +** Alternatively, this file may be used under the terms of the GNU General +** Public License versions 2.0 or 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the packaging +** of this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** +** http://www.fsf.org/licensing/licenses/info/GPLv2.html and +** http://www.gnu.org/copyleft/gpl.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt GPL Exception version +** 1.2, included in the file GPL_EXCEPTION.txt in this package. +** +***************************************************************************/ +#ifndef DEBUGGER_WATCHWINDOW_H +#define DEBUGGER_WATCHWINDOW_H + +#include <QtGui/QTreeView> + +namespace Debugger { +namespace Internal { + +///////////////////////////////////////////////////////////////////// +// +// WatchWindow +// +///////////////////////////////////////////////////////////////////// + +class WatchWindow : public QTreeView +{ + Q_OBJECT + +public: + enum Type { LocalsType, TooltipType, WatchersType }; + + WatchWindow(Type type, QWidget *parent = 0); + void setType(Type type) { m_type = type; } + Type type() const { return m_type; } + +public slots: + void resizeColumnsToContents(); + void setAlwaysResizeColumnsToContents(bool on = true); + void setModel(QAbstractItemModel *model); + +signals: + void requestWatchExpression(const QString &exp); + void requestRemoveWatchExpression(const QString &iname); + void requestAssignValue(const QString &exp, const QString &value); + void requestExpandChildren(const QModelIndex &idx); + void requestCollapseChildren(const QModelIndex &idx); + +private slots: + void handleChangedItem(QWidget *); + void expandNode(const QModelIndex &index); + void collapseNode(const QModelIndex &index); + void modelAboutToBeReset(); + void modelReset(); + +private: + void contextMenuEvent(QContextMenuEvent *ev); + void editItem(const QModelIndex &idx); + void reset(); /* reimpl */ + + void modelAboutToBeResetHelper(const QModelIndex &idx); + void modelResetHelper(const QModelIndex &idx); + + bool m_alwaysResizeColumnsToContents; + Type m_type; + bool m_blocked; + QSet<QString> m_expandedItems; +}; + + +} // namespace Internal +} // namespace Debugger + +#endif // DEBUGGER_WATCHWINDOW_H |