Commit 1b9b962b authored by Friedemann Kleint's avatar Friedemann Kleint

Debugger[new CDB]: Polish options, add remote debugging.

parent 8b59a52b
......@@ -221,9 +221,11 @@ STDMETHODIMP EventCallback::ExitProcess(
{
ExtensionContext::instance().report('E', 0, eventContextC, "Process exited (%lu)",
ExitCode);
dprintf("%s ExitProcess %u\n", creatorOutputPrefixC, ExitCode);
return m_wrapped ? m_wrapped->ExitProcess(ExitCode) : S_OK;
const HRESULT hr = m_wrapped ? m_wrapped->ExitProcess(ExitCode) : S_OK;
// Remotely debugged process exited, there is no session-inactive notification.
// Note: We get deleted here, so, order is important.
ExtensionContext::instance().unhookCallbacks();
return hr;
}
STDMETHODIMP EventCallback::LoadModule(
......
......@@ -61,6 +61,8 @@ public:
// Call this from the first extension command that gets a client.
// Does not work when called from initialization.
void hookCallbacks(CIDebugClient *client);
// Undo hooking.
void unhookCallbacks();
// Report output in standardized format understood by Qt Creator.
// '<qtcreatorcdbext>|R|<token>|<serviceName>|<one-line-output>'.
......@@ -81,7 +83,6 @@ public:
void setStopReason(const StopReasonMap &, const std::string &reason = std::string());
private:
void unhookCallbacks();
bool isInitialized() const;
void discardSymbolGroup();
......
......@@ -12,4 +12,5 @@ modules
idle
help
memory
shutdownex
KnownStructOutput
......@@ -351,6 +351,15 @@ extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn)
return S_OK;
}
// Extension command 'shutdownex' (shutdown is reserved):
// Unhook the output callbacks. This is normally done by the session
// inaccessible notification, however, this does not work for remote-controlled sessions.
extern "C" HRESULT CALLBACK shutdownex(CIDebugClient *, PCSTR)
{
ExtensionContext::instance().unhookCallbacks();
return S_OK;
}
// Hook for dumping Known Structs. Not currently used.
// Shows up in 'dv' as well as IDebugSymbolGroup::GetValueText.
......
......@@ -48,6 +48,7 @@
#include <coreplugin/icore.h>
#include <utils/synchronousprocess.h>
#include <utils/winutils.h>
#include <utils/qtcassert.h>
#include <utils/savedaction.h>
......@@ -228,7 +229,6 @@ static inline bool validMode(DebuggerStartMode sm)
case NoStartMode:
case AttachTcf:
case AttachCore:
case AttachToRemote:
case StartRemoteGdb:
return false;
default:
......@@ -334,7 +334,7 @@ void CdbEngine::setupEngine()
}
// Determine full path to the CDB extension library.
static inline QString extensionLibraryName(bool is64Bit)
QString CdbEngine::extensionLibraryName(bool is64Bit)
{
// Determine extension lib name and path to use
QString rc;
......@@ -387,18 +387,24 @@ bool CdbEngine::doSetupEngine(QString *errorMessage)
// Determine extension lib name and path to use
// The extension is passed as relative name with the path variable set
//(does not work with absolute path names)
const QFileInfo extensionFi(extensionLibraryName(m_options->is64bit));
const QFileInfo extensionFi(CdbEngine::extensionLibraryName(m_options->is64bit));
if (!extensionFi.isFile()) {
*errorMessage = QString::fromLatin1("Internal error: The extension %1 cannot be found.").
arg(QDir::toNativeSeparators(extensionFi.absoluteFilePath()));
return false;
}
const QString extensionFileName = extensionFi.fileName();
// Prepare arguments
const DebuggerStartParameters &sp = startParameters();
QStringList arguments;
const bool isRemote = sp.startMode == AttachToRemote;
if (isRemote) { // Must be first
arguments << QLatin1String("-remote") << sp.remoteChannel;
} else {
arguments << (QLatin1String("-a") + extensionFileName);
}
// Source line info/No terminal breakpoint / Pull extension
arguments << QLatin1String("-lines") << QLatin1String("-G")
<< (QLatin1String("-a") + extensionFi.fileName())
// register idle (debuggee stop) notification
<< QLatin1String("-c")
<< QString::fromAscii(".idle_cmd " + m_extensionCommandPrefixBA + "idle");
......@@ -413,8 +419,10 @@ bool CdbEngine::doSetupEngine(QString *errorMessage)
case StartExternal:
arguments << QDir::toNativeSeparators(sp.executable);
break;
case AttachToRemote:
break;
case AttachExternal:
case AttachCrashedExternal: // @TODO: event handle for crashed?
case AttachCrashedExternal:
arguments << QLatin1String("-p") << QString::number(sp.attachPID);
if (sp.startMode == AttachCrashedExternal)
arguments << QLatin1String("-e") << sp.crashParameter << QLatin1String("-g");
......@@ -451,17 +459,28 @@ bool CdbEngine::doSetupEngine(QString *errorMessage)
showMessage(QString::fromLatin1("%1 running as %2").
arg(QDir::toNativeSeparators(executable)).arg(pid), LogMisc);
m_hasDebuggee = true;
if (isRemote) { // We do not get an 'idle' in a remote session, but are accessible
m_accessible = true;
const QByteArray loadCommand = QByteArray(".load ")
+ extensionFileName.toLocal8Bit();
postCommand(loadCommand, 0);
notifyEngineSetupOk();
}
return true;
}
void CdbEngine::setupInferior()
{
if (debug)
qDebug("setupInferior");
attemptBreakpointSynchronization();
postExtensionCommand("pid", QByteArray(), 0, &CdbEngine::handlePid);
}
void CdbEngine::runEngine()
{
if (debug)
qDebug("runEngine");
postCommand("g", 0);
}
......@@ -477,6 +496,11 @@ void CdbEngine::shutdownInferior()
notifyInferiorShutdownOk();
return;
}
if (!canInterruptInferior()) {
notifyInferiorShutdownFailed();
return;
}
if (m_accessible) {
if (startParameters().startMode == AttachExternal || startParameters().startMode == AttachCrashedExternal)
detachDebugger();
......@@ -513,8 +537,19 @@ void CdbEngine::shutdownEngine()
if (m_accessible) {
if (startParameters().startMode == AttachExternal)
detachDebugger();
postCommand("q", 0);
// Remote requires a bit more force to quit.
if (startParameters().startMode == AttachToRemote) {
postCommand(m_extensionCommandPrefixBA + "shutdownex", 0);
postCommand("qq", 0);
} else {
postCommand("q", 0);
}
m_notifyEngineShutdownOnTermination = true;
return;
} else {
// Remote process. No can do, currently
m_notifyEngineShutdownOnTermination = true;
Utils::SynchronousProcess::stopProcess(m_process);
return;
}
// Lost debuggee, debugger should quit anytime now
......@@ -638,9 +673,21 @@ void CdbEngine::doContinueInferior()
postCommand(QByteArray("g"), 0);
}
bool CdbEngine::canInterruptInferior() const
{
return startParameters().startMode != AttachToRemote;
}
void CdbEngine::interruptInferior()
{
doInterruptInferior(NoSpecialStop);
if (canInterruptInferior()) {
doInterruptInferior(NoSpecialStop);
} else {
showMessage(tr("Interrupting is not possible in remote sessions."), LogError);
notifyInferiorStopOk();
notifyInferiorRunRequested();
notifyInferiorRunOk();
}
}
void CdbEngine::doInterruptInferior(SpecialStopMode sm)
......
......@@ -115,7 +115,7 @@ public:
virtual void reloadSourceFiles();
virtual void reloadFullStack();
//virtual bool isSynchronous() const { return true; }
static QString extensionLibraryName(bool is64Bit);
private slots:
void readyReadStandardOut();
......@@ -151,6 +151,7 @@ private:
void doContinueInferior();
inline void parseOutputLine(QByteArray line);
inline bool isCdbProcessRunning() const { return m_process.state() != QProcess::NotRunning; }
bool canInterruptInferior() const;
// Builtin commands
void dummyHandler(const CdbBuiltinCommandPtr &);
......
......@@ -30,15 +30,23 @@
#include "cdboptionspage2.h"
#include "cdboptions2.h"
#include "debuggerconstants.h"
#include "cdbengine2.h"
#ifdef Q_OS_WIN
# include <utils/winutils.h>
#endif
#include <utils/synchronousprocess.h>
#include <coreplugin/icore.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QUrl>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QDateTime>
#include <QtCore/QTextStream>
#include <QtCore/QTimer>
#include <QtCore/QProcess>
#include <QtGui/QMessageBox>
#include <QtGui/QDesktopServices>
......@@ -66,7 +74,7 @@ static inline QString msgPathConfigNote()
}
CdbOptionsPageWidget::CdbOptionsPageWidget(QWidget *parent) :
QWidget(parent)
QWidget(parent), m_reportTimer(0)
{
m_ui.setupUi(this);
m_ui.noteLabel->setText(msgPathConfigNote());
......@@ -75,10 +83,9 @@ CdbOptionsPageWidget::CdbOptionsPageWidget(QWidget *parent) :
m_ui.pathChooser->setExpectedKind(Utils::PathChooser::ExistingCommand);
m_ui.pathChooser->addButton(tr("Autodetect"), this, SLOT(autoDetect()));
m_ui.failureLabel->setVisible(false);
m_ui.cdbPathGroupBox->installEventFilter(this);
}
void CdbOptionsPageWidget::setOptions(CdbOptions &o)
{
m_ui.pathChooser->setPath(o.executable);
......@@ -88,17 +95,33 @@ void CdbOptionsPageWidget::setOptions(CdbOptions &o)
m_ui.sourcePathListEditor->setPathList(o.sourcePaths);
}
bool CdbOptionsPageWidget::is64Bit() const
{
return m_ui.is64BitCheckBox->isChecked();
}
QString CdbOptionsPageWidget::path() const
{
return m_ui.pathChooser->path();
}
CdbOptions CdbOptionsPageWidget::options() const
{
CdbOptions rc;
rc.executable = m_ui.pathChooser->path();
rc.executable = path();
rc.enabled = m_ui.cdbPathGroupBox->isChecked();
rc.is64bit = m_ui.is64BitCheckBox->isChecked();
rc.is64bit = is64Bit();
rc.symbolPaths = m_ui.symbolPathListEditor->pathList();
rc.sourcePaths = m_ui.sourcePathListEditor->pathList();
return rc;
}
void CdbOptionsPageWidget::hideReportLabel()
{
m_ui.reportLabel->clear();
m_ui.reportLabel->setVisible(false);
}
void CdbOptionsPageWidget::autoDetect()
{
QString executable;
......@@ -109,6 +132,10 @@ void CdbOptionsPageWidget::autoDetect()
if (ok) {
m_ui.is64BitCheckBox->setChecked(is64bit);
m_ui.pathChooser->setPath(executable);
QString report;
// Now check for the extension library as well.
const bool allOk = checkInstallation(executable, is64Bit(), &report);
setReport(report, allOk);
} else {
const QString msg = tr("\"Debugging Tools for Windows\" could not be found.");
const QString details = tr("Checked:\n%1").arg(checkedDirectories.join(QString(QLatin1Char('\n'))));
......@@ -118,10 +145,23 @@ void CdbOptionsPageWidget::autoDetect()
}
}
void CdbOptionsPageWidget::setFailureMessage(const QString &msg)
void CdbOptionsPageWidget::setReport(const QString &msg, bool success)
{
m_ui.failureLabel->setText(msg);
m_ui.failureLabel->setVisible(!msg.isEmpty());
// Hide label after some interval
if (!m_reportTimer) {
m_reportTimer = new QTimer(this);
m_reportTimer->setSingleShot(true);
connect(m_reportTimer, SIGNAL(timeout()), this, SLOT(hideReportLabel()));
} else {
if (m_reportTimer->isActive())
m_reportTimer->stop();
}
m_reportTimer->setInterval(success ? 10000 : 20000);
m_reportTimer->start();
m_ui.reportLabel->setText(msg);
m_ui.reportLabel->setStyleSheet(success ? QString() : QString::fromAscii("background-color : 'red'"));
m_ui.reportLabel->setVisible(true);
}
void CdbOptionsPageWidget::downLoadLinkActivated(const QString &link)
......@@ -138,6 +178,62 @@ QString CdbOptionsPageWidget::searchKeywords() const
return rc;
}
static QString cdbVersion(const QString &executable)
{
QProcess cdb;
cdb.start(executable, QStringList(QLatin1String("-version")));
cdb.closeWriteChannel();
if (!cdb.waitForStarted())
return QString();
if (!cdb.waitForFinished()) {
Utils::SynchronousProcess::stopProcess(cdb);
return QString();
}
return QString::fromLocal8Bit(cdb.readAllStandardOutput());
}
bool CdbOptionsPageWidget::checkInstallation(const QString &executable,
bool is64Bit, QString *message)
{
// 1) Check on executable
unsigned checkedItems = 0;
QString rc;
if (executable.isEmpty()) {
message->append(tr("No cdb executable specified.\n"));
} else {
const QString version = cdbVersion(executable);
if (version.isEmpty()) {
message->append(tr("Unable to determine version of %1.\n").
arg(executable));
} else {
message->append(tr("Version: %1").arg(version));
checkedItems++;
}
}
// 2) Check on extension library
const QFileInfo extensionFi(CdbEngine::extensionLibraryName(is64Bit));
if (extensionFi.isFile()) {
message->append(tr("Extension library: %1, built: %3.\n").
arg(QDir::toNativeSeparators(extensionFi.absoluteFilePath())).
arg(extensionFi.lastModified().toString(Qt::SystemLocaleShortDate)));
checkedItems++;
} else {
message->append("Extension library not found.\n");
}
return checkedItems == 2u;
}
bool CdbOptionsPageWidget::eventFilter(QObject *o, QEvent *e)
{
if (o != m_ui.cdbPathGroupBox || e->type() != QEvent::ToolTip)
return QWidget::eventFilter(o, e);
QString message;
checkInstallation(path(), is64Bit(), &message);
m_ui.cdbPathGroupBox->setToolTip(message);
return false;
}
// ---------- CdbOptionsPage
CdbOptionsPage *CdbOptionsPage::m_instance = 0;
......@@ -183,7 +279,6 @@ QWidget *CdbOptionsPage::createPage(QWidget *parent)
{
m_widget = new CdbOptionsPageWidget(parent);
m_widget->setOptions(*m_options);
m_widget->setFailureMessage(m_failureMessage);
if (m_searchKeywords.isEmpty())
m_searchKeywords = m_widget->searchKeywords();
return m_widget;
......
......@@ -39,6 +39,8 @@
#include <QtCore/QPointer>
#include <QtCore/QSharedPointer>
QT_FORWARD_DECLARE_CLASS(QTimer)
namespace Debugger {
namespace Cdb {
......@@ -51,16 +53,25 @@ public:
void setOptions(CdbOptions &o);
CdbOptions options() const;
void setFailureMessage(const QString &);
QString searchKeywords() const;
virtual bool eventFilter(QObject *, QEvent *);
private slots:
void autoDetect();
void downLoadLinkActivated(const QString &);
void hideReportLabel();
private:
void setReport(const QString &, bool success);
inline bool is64Bit() const;
inline QString path() const;
static bool checkInstallation(const QString &executable, bool is64Bit,
QString *message);
Ui::CdbOptionsPageWidget2 m_ui;
QTimer *m_reportTimer;
};
class CdbOptionsPage : public Core::IOptionsPage
......@@ -87,15 +98,12 @@ public:
static QString settingsId();
// Load failure messages can be displayed here
void setFailureMessage(const QString &msg) { m_failureMessage = msg; }
QSharedPointer<CdbOptions> options() const { return m_options; }
private:
static CdbOptionsPage *m_instance;
const QSharedPointer<CdbOptions> m_options;
QPointer<CdbOptionsPageWidget> m_widget;
QString m_failureMessage;
QString m_searchKeywords;
};
......
......@@ -2,14 +2,19 @@
<ui version="4.0">
<class>Debugger::Cdb::CdbOptionsPageWidget2</class>
<widget class="QWidget" name="Debugger::Cdb::CdbOptionsPageWidget2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>135</width>
<height>246</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="cdbPathGroupBox">
<property name="toolTip">
<string>These options take effect at the next start of Qt Creator.</string>
</property>
<property name="title">
<string extracomment="Placeholder">CDB</string>
</property>
......@@ -92,13 +97,16 @@
</spacer>
</item>
<item>
<widget class="QLabel" name="failureLabel">
<widget class="QLabel" name="reportLabel">
<property name="styleSheet">
<string notr="true">background-color: 'red';</string>
<string notr="true"/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
......
......@@ -154,10 +154,14 @@ QVariant cdbIntegerValue(const QByteArray &t)
return converted;
}
/* Parse:
/* Parse: 64bit:
\code
Child-SP RetAddr Call Site
00000000`0012a290 00000000`70deb844 QtCored4!QString::QString+0x18 [c:\qt\src\corelib\tools\qstring.h @ 729]
\endcode 32bit:
\code
ChildEBP RetAddr
0012cc68 6714d114 QtCored4!QString::QString+0xf [d:\dev\qt4.7-vs8\qt\src\corelib\tools\qstring.h @ 729]
\endcode */
static inline bool isHexDigit(char c)
......@@ -168,7 +172,7 @@ static inline bool isHexDigit(char c)
static inline bool parseStackFrame(QByteArray line, Debugger::Internal::StackFrame *frame)
{
frame->clear();
if (line.isEmpty() || line.startsWith("Child-SP") || !isHexDigit(line.at(0)))
if (line.isEmpty() || line.startsWith("Child") || !isHexDigit(line.at(0)))
return false;
if (line.endsWith(']')) {
const int sourceFilePos = line.lastIndexOf('[');
......
......@@ -29,6 +29,7 @@
#include "debuggerdialogs.h"
#include "debuggerconstants.h"
#include "cdb2/cdbengine2.h"
#include "ui_attachcoredialog.h"
#include "ui_attachexternaldialog.h"
......@@ -43,9 +44,11 @@
#include <coreplugin/icore.h>
#include <utils/synchronousprocess.h>
#include <utils/historycompleter.h>
#include <utils/qtcassert.h>
#include <QtCore/QDebug>
#include <QtCore/QProcess>
#include <QtCore/QRegExp>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <QtCore/QCoreApplication>
......@@ -56,6 +59,7 @@
#include <QtGui/QProxyModel>
#include <QtGui/QSortFilterProxyModel>
#include <QtGui/QMessageBox>
#include <QtGui/QGroupBox>
using namespace Utils;
......@@ -744,6 +748,97 @@ void StartRemoteDialog::updateState()
m_ui->serverStartScript->setEnabled(enabled);
}
// --------- StartRemoteCdbDialog
static inline QString cdbRemoteHelp()
{
const char *cdbConnectionSyntax =
"Server:Port<br>"
"tcp:server=Server,port=Port[,password=Password][,ipversion=6]\n"
"tcp:clicon=Server,port=Port[,password=Password][,ipversion=6]\n"
"npipe:server=Server,pipe=PipeName[,password=Password]\n"
"com:port=COMPort,baud=BaudRate,channel=COMChannel[,password=Password]\n"
"spipe:proto=Protocol,{certuser=Cert|machuser=Cert},server=Server,pipe=PipeName[,password=Password]\n"
"ssl:proto=Protocol,{certuser=Cert|machuser=Cert},server=Server,port=Socket[,password=Password]\n"
"ssl:proto=Protocol,{certuser=Cert|machuser=Cert},clicon=Server,port=Socket[,password=Password]";
const QString ext32 = QDir::toNativeSeparators(Debugger::Cdb::CdbEngine::extensionLibraryName(false));
const QString ext64 = QDir::toNativeSeparators(Debugger::Cdb::CdbEngine::extensionLibraryName(true));
return StartRemoteCdbDialog::tr(
"<html><body><p>The remote CDB needs to load the matching Qt Creator CDB extension "
"(<code>%1</code> or <code>%2</code>, respectively).</p><p>Copy it onto the remote machine and set the "
"environment variable <code>%3</code> to point to its folder.</p><p>"
"Launch the remote CDB as <code>%4 &lt;executable&gt;</code> "
" to use TCP/IP as communication protocol.</p><p>Enter the connection parameters as:</p>"
"<pre>%5</pre></body></html>").
arg(ext32, ext64, QLatin1String("_NT_DEBUGGER_EXTENSION_PATH"),
QLatin1String("cdb.exe -server tcp:port=1234"),
QLatin1String(cdbConnectionSyntax));
}
StartRemoteCdbDialog::StartRemoteCdbDialog(QWidget *parent) :
QDialog(parent), m_okButton(0), m_lineEdit(new QLineEdit)
{
setWindowTitle(tr("Start a CDB Remote Session"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QGroupBox *groupBox = new QGroupBox;
QFormLayout *formLayout = new QFormLayout;
QLabel *helpLabel = new QLabel(cdbRemoteHelp());
helpLabel->setWordWrap(true);
helpLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
formLayout->addRow(helpLabel);
QLabel *label = new QLabel(tr("&Connection:"));
label->setBuddy(m_lineEdit);
m_lineEdit->setMinimumWidth(400);
connect(m_lineEdit, SIGNAL(textChanged(QString)), this, SLOT(textChanged(QString)));
formLayout->addRow(label, m_lineEdit);
groupBox->setLayout(formLayout);
QVBoxLayout *vLayout = new QVBoxLayout;
vLayout->addWidget(groupBox);
QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
vLayout->addWidget(box);
m_okButton = box->button(QDialogButtonBox::Ok);
connect(m_lineEdit, SIGNAL(returnPressed()), m_okButton, SLOT(animateClick()));
m_okButton->setEnabled(false);
connect(box, SIGNAL(accepted()), this, SLOT(accept()));
connect(box, SIGNAL(rejected()), this, SLOT(reject()));
setLayout(vLayout);
}
void StartRemoteCdbDialog::accept()
{
if (!m_lineEdit->text().isEmpty())
QDialog::accept();
}
StartRemoteCdbDialog::~StartRemoteCdbDialog()
{
}
void StartRemoteCdbDialog::textChanged(const QString &t)
{
m_okButton->setEnabled(!t.isEmpty());
}
QString StartRemoteCdbDialog::connection() const
{
const QString rc = m_lineEdit->text();