File indexing completed on 2024-04-21 12:11:07

0001 /*
0002  * XDebug Debugger Support
0003  *
0004  * Copyright 2009 Niko Sams <niko.sams@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU General Public License as
0008  * published by the Free Software Foundation; either version 2 of the
0009  * License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public
0017  * License along with this program; if not, write to the
0018  * Free Software Foundation, Inc.,
0019  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0020  */
0021 
0022 #include "debugsession.h"
0023 
0024 #include <QTime>
0025 #include <QTcpSocket>
0026 #include <QTcpServer>
0027 #include <QFile>
0028 
0029 #include <QDebug>
0030 #include <KLocalizedString>
0031 
0032 #include <interfaces/ilaunchconfiguration.h>
0033 #include <debugger/util/pathmappings.h>
0034 
0035 #include "connection.h"
0036 #include "breakpointcontroller.h"
0037 #include "framestackmodel.h"
0038 #include "variablecontroller.h"
0039 #include "launchconfig.h"
0040 #include "debuggerdebug.h"
0041 
0042 namespace XDebug {
0043 DebugSession::DebugSession()
0044     : KDevelop::IDebugSession()
0045     , m_breakpointController(new BreakpointController(this))
0046     , m_variableController(new VariableController(this))
0047     , m_frameStackModel(new FrameStackModel(this))
0048     , m_server(nullptr)
0049     , m_connection(nullptr)
0050     , m_launchConfiguration(nullptr)
0051     , m_acceptMultipleConnections(false)
0052 {
0053 }
0054 
0055 DebugSession::~DebugSession()
0056 {
0057     emit finished();
0058 }
0059 
0060 void DebugSession::setLaunchConfiguration(KDevelop::ILaunchConfiguration* cfg)
0061 {
0062     m_launchConfiguration = cfg;
0063 }
0064 
0065 void DebugSession::setAcceptMultipleConnections(bool v)
0066 {
0067     m_acceptMultipleConnections = v;
0068 }
0069 
0070 bool DebugSession::listenForConnection(QString& error)
0071 {
0072     Q_ASSERT(!m_server);
0073     m_server = new QTcpServer(this);
0074     qCDebug(KDEV_PHP_DEBUGGER);
0075     int remotePortSetting = m_launchConfiguration->config().readEntry("RemotePort", 9000);
0076     if (m_server->listen(QHostAddress::Any, remotePortSetting)) {
0077         connect(m_server, &QTcpServer::newConnection, this, &DebugSession::incomingConnection);
0078         // avoid 'debug launch' button
0079         stateChanged(ActiveState);
0080     } else {
0081         error = i18n("Opening port %1 failed: %2.", remotePortSetting, m_server->errorString());
0082         qCWarning(KDEV_PHP_DEBUGGER) << "Error" << m_server->errorString();
0083         delete m_server;
0084         m_server = nullptr;
0085         return false;
0086     }
0087     return m_server->isListening();
0088 }
0089 
0090 void DebugSession::closeServer()
0091 {
0092     if (m_server) {
0093         m_server->close();
0094         m_server->deleteLater();
0095         m_server = nullptr;
0096     }
0097 }
0098 
0099 void DebugSession::incomingConnection()
0100 {
0101     qCDebug(KDEV_PHP_DEBUGGER);
0102     QTcpSocket* client = m_server->nextPendingConnection();
0103 
0104     if (m_connection) {
0105         m_connection->disconnect();
0106         m_connection->deleteLater();
0107         m_connection = nullptr;
0108     }
0109 
0110     m_connection = new Connection(client, this);
0111     connect(m_connection, &Connection::output, this, &DebugSession::output);
0112     connect(m_connection, &Connection::outputLine, this, &DebugSession::outputLine);
0113     connect(m_connection, &Connection::stateChanged, this, &DebugSession::stateChanged);
0114     connect(m_connection, &Connection::stateChanged, this, &DebugSession::_stateChanged);
0115     connect(m_connection, &Connection::currentPositionChanged,this, &DebugSession::currentPositionChanged);
0116     connect(m_connection, &Connection::closed, this,  &DebugSession::connectionClosed);
0117 
0118     if (!m_acceptMultipleConnections) {
0119         closeServer();
0120     }
0121 }
0122 
0123 void DebugSession::connectionClosed()
0124 {
0125     Q_ASSERT(sender() == m_connection);
0126 
0127     if (m_acceptMultipleConnections && m_server && m_server->isListening() 
0128        ) {
0129         // clear variable widget
0130         emit stateChanged(NotStartedState);
0131         // avoid 'debug launch' button
0132         emit stateChanged(ActiveState);
0133     } else {
0134         m_connection->setState(DebugSession::EndedState);
0135     }
0136     m_connection->deleteLater();
0137     m_connection = nullptr;
0138 }
0139 
0140 void DebugSession::_stateChanged(KDevelop::IDebugSession::DebuggerState state)
0141 {
0142     qCDebug(KDEV_PHP_DEBUGGER) << state;
0143     if (state == StartingState) {
0144         run();
0145     } else if (state == PausedState) {
0146         raiseEvent(program_state_changed);
0147     } else if (state == StoppingState) {
0148         m_connection->sendCommand("stop");
0149     } else if (state == EndedState) {
0150         raiseEvent(debugger_exited);
0151         emit finished();
0152     }
0153 }
0154 
0155 DebugSession::DebuggerState DebugSession::state() const
0156 {
0157     if (!m_connection) {
0158         return NotStartedState;
0159     }
0160     return m_connection->currentState();
0161 }
0162 
0163 void DebugSession::run()
0164 {
0165     Q_ASSERT(m_connection);
0166     m_connection->sendCommand("run");
0167     m_connection->setState(ActiveState);
0168 }
0169 
0170 void DebugSession::stepOut()
0171 {
0172     Q_ASSERT(m_connection);
0173     m_connection->sendCommand("step_out");
0174     m_connection->setState(ActiveState);
0175 }
0176 
0177 void DebugSession::stepOverInstruction()
0178 {
0179 }
0180 
0181 void DebugSession::stepInto()
0182 {
0183     Q_ASSERT(m_connection);
0184     m_connection->sendCommand("step_into");
0185     m_connection->setState(ActiveState);
0186 }
0187 
0188 void DebugSession::stepIntoInstruction()
0189 {
0190 }
0191 
0192 void DebugSession::stepOver()
0193 {
0194     Q_ASSERT(m_connection);
0195     m_connection->sendCommand("step_over");
0196     m_connection->setState(ActiveState);
0197 }
0198 
0199 void DebugSession::jumpToCursor()
0200 {
0201 }
0202 
0203 void DebugSession::runToCursor()
0204 {
0205 }
0206 
0207 void DebugSession::interruptDebugger()
0208 {
0209 }
0210 
0211 void DebugSession::stopDebugger()
0212 {
0213     closeServer();
0214     // finish debugger when no connection active
0215     if ( state() == DebugSession::NotStartedState && !m_connection ) {
0216         stateChanged(DebugSession::EndedState);
0217         emit finished();
0218     } else {
0219         m_connection->sendCommand("stop");
0220     }
0221 }
0222 
0223 void DebugSession::killDebuggerNow()
0224 {
0225     closeServer();
0226     if (m_connection) {
0227         m_connection->close();
0228     } else {
0229         emit stateChanged(EndedState);
0230         emit finished();
0231     }
0232 }
0233 
0234 void DebugSession::restartDebugger()
0235 {
0236 }
0237 void DebugSession::eval(QByteArray source)
0238 {
0239     Q_ASSERT(m_connection);
0240     m_connection->sendCommand("eval", QStringList(), source);
0241 }
0242 
0243 bool DebugSession::waitForFinished(int msecs)
0244 {
0245     QTime stopWatch;
0246     stopWatch.start();
0247     if (!waitForState(DebugSession::StoppingState, msecs)) {
0248         return false;
0249     }
0250     if (msecs != -1) {
0251         msecs = msecs - stopWatch.elapsed();
0252     }
0253     return true;
0254 }
0255 
0256 bool DebugSession::waitForState(KDevelop::IDebugSession::DebuggerState state, int msecs)
0257 {
0258     if (!m_connection) {
0259         return false;
0260     }
0261     if (m_connection->currentState() == state) {
0262         return true;
0263     }
0264     QTime stopWatch;
0265     stopWatch.start();
0266     if (!waitForConnected(msecs)) {
0267         return false;
0268     }
0269     while (m_connection->currentState() != state) {
0270         if (!m_connection) {
0271             return false;
0272         }
0273         if (!m_connection->socket()) {
0274             return false;
0275         }
0276         if (!m_connection->socket()->isOpen()) {
0277             return false;
0278         }
0279         m_connection->socket()->waitForReadyRead(100);
0280         if (msecs != -1 && stopWatch.elapsed() > msecs) {
0281             return false;
0282         }
0283     }
0284     return true;
0285 }
0286 
0287 bool DebugSession::waitForConnected(int msecs)
0288 {
0289     if (!m_connection) {
0290         Q_ASSERT(m_server);
0291         if (!m_server->waitForNewConnection(msecs)) {
0292             return false;
0293         }
0294     }
0295     Q_ASSERT(m_connection);
0296     Q_ASSERT(m_connection->socket());
0297     return m_connection->socket()->waitForConnected(msecs);
0298 }
0299 
0300 Connection* DebugSession::connection()
0301 {
0302     return m_connection;
0303 }
0304 
0305 bool DebugSession::restartAvaliable() const
0306 {
0307     //not supported at all by xdebug
0308     return false;
0309 }
0310 
0311 KDevelop::IBreakpointController* DebugSession::breakpointController() const
0312 {
0313     return m_breakpointController;
0314 }
0315 
0316 KDevelop::IVariableController* DebugSession::variableController() const
0317 {
0318     return m_variableController;
0319 }
0320 
0321 KDevelop::IFrameStackModel* DebugSession::frameStackModel() const
0322 {
0323     return m_frameStackModel;
0324 }
0325 
0326 QPair<QUrl, int> DebugSession::convertToLocalUrl(const QPair<QUrl, int>& remoteUrl) const
0327 {
0328     Q_ASSERT(m_launchConfiguration);
0329     QPair<QUrl, int> ret = remoteUrl;
0330     ret.first = KDevelop::PathMappings::convertToLocalUrl(m_launchConfiguration->config(), remoteUrl.first);
0331     return ret;
0332 }
0333 
0334 QPair<QUrl, int> DebugSession::convertToRemoteUrl(const QPair<QUrl, int>& localUrl) const
0335 {
0336     Q_ASSERT(m_launchConfiguration);
0337     QPair<QUrl, int> ret = localUrl;
0338     ret.first = KDevelop::PathMappings::convertToRemoteUrl(m_launchConfiguration->config(), localUrl.first);
0339     return ret;
0340 }
0341 
0342 void DebugSession::currentPositionChanged(const QUrl& url, int line)
0343 {
0344     setCurrentPosition(url, line, QString());
0345 }
0346 }