diff options
author | Alex Blasche <[email protected]> | 2014-11-18 15:14:40 +0100 |
---|---|---|
committer | Alex Blasche <[email protected]> | 2014-11-20 13:14:09 +0100 |
commit | 7f72b7bf6597f906c4f69a67a0aae5087d352a76 (patch) | |
tree | b60534aebe5d363f43fb25c8596012ab8becdd3b /src/plugins/android/androidrunner.cpp | |
parent | dbf663198c67c996c0ed2fcd20611c9951175355 (diff) |
Fix broken debugging on Android 5.0
Security permissions prevent access to files not owned by the current
process. This patch replaces the file based handshake protocol with
a local server based implementation. The server waits for QTC to connect
to it and sends back the current process ID. This new mechanism works
on pre 5.0 devices as well.
The existing file based handshake remains and can be activiated via the
env variable QTC_ANDROID_USE_FILE_HANDSHAKE.
Task-number: QTCREATORBUG-13418
Change-Id: Ie40ec801f265a9e13c3220f300798c27abd97ae2
Reviewed-by: Alex Blasche <[email protected]>
Reviewed-by: hjk <[email protected]>
Reviewed-by: Eskil Abrahamsen Blomfeldt <[email protected]>
Reviewed-by: BogDan Vatra <[email protected]>
Diffstat (limited to 'src/plugins/android/androidrunner.cpp')
-rw-r--r-- | src/plugins/android/androidrunner.cpp | 175 |
1 files changed, 141 insertions, 34 deletions
diff --git a/src/plugins/android/androidrunner.cpp b/src/plugins/android/androidrunner.cpp index 47740bd9b22..50729eda925 100644 --- a/src/plugins/android/androidrunner.cpp +++ b/src/plugins/android/androidrunner.cpp @@ -1,6 +1,7 @@ /************************************************************************** ** ** Copyright (c) 2014 BogDan Vatra <[email protected]> +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. @@ -41,17 +42,30 @@ #include <qtsupport/qtkitinformation.h> #include <utils/qtcassert.h> +#include <QApplication> #include <QDir> #include <QTime> #include <QtConcurrentRun> #include <QTemporaryFile> #include <QTcpServer> +#include <QTcpSocket> /* This uses explicit handshakes between the application and the - gdbserver start and the host side by using the gdbserver socket - and two files ("ping" file in the application dir, "pong" file - in /data/local/tmp/qt) + gdbserver start and the host side by using the gdbserver socket. + + For the handshake there are two mechanisms. Only the first method works + on Android 5.x devices and is chosen as default option. The second + method can be enabled by setting the QTC_ANDROID_USE_FILE_HANDSHAKE + environment variable before starting Qt Creator. + + 1.) This method uses a TCP server on the Android device which starts + listening for incoming connections. The socket is forwarded by adb + and creator connects to it. This is the only method that works + on Android 5.x devices. + + 2.) This method uses two files ("ping" file in the application dir, + "pong" file in /data/local/tmp/qt). The sequence is as follows: @@ -67,8 +81,8 @@ gdbserver: normal start up including opening debug-socket, not yet attached to any process - app start up: touch ping file - app start up: loop until pong file appears + app start up: 1.) set up ping connection or 2.) touch ping file + app start up: 1.) accept() or 2.) loop until pong file appears host: start gdb host: gdb: set up binary, breakpoints, path etc @@ -90,7 +104,7 @@ app start up: resumed (still waiting for the pong) - host: write pong file + host: 1) write "ok" to ping pong connection or 2.) write pong file app start up: java code continues now, the process is already fully under control @@ -104,11 +118,16 @@ namespace Android { namespace Internal { typedef QLatin1String _; +const int MIN_SOCKET_HANDSHAKE_PORT = 20001; +const int MAX_SOCKET_HANDSHAKE_PORT = 20999; + +static int socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT; AndroidRunner::AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig, ProjectExplorer::RunMode runMode) - : QThread(parent) + : QThread(parent), m_handShakeMethod(SocketHandShake), m_socket(0), + m_customPort(false) { m_tries = 0; Debugger::DebuggerRunConfigurationAspect *aspect @@ -170,11 +189,32 @@ AndroidRunner::AndroidRunner(QObject *parent, connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardOutput()), SLOT(logcatReadStandardOutput())); connect(&m_adbLogcatProcess, SIGNAL(readyReadStandardError()), SLOT(logcatReadStandardError())); connect(&m_checkPIDTimer, SIGNAL(timeout()), SLOT(checkPID())); + + if (version && version->qtVersion() >= QtSupport::QtVersionNumber(5, 4, 0)) { + if (qEnvironmentVariableIsSet("QTC_ANDROID_USE_FILE_HANDSHAKE")) + m_handShakeMethod = PingPongFiles; + } else { + m_handShakeMethod = PingPongFiles; + } + + if (qEnvironmentVariableIsSet("QTC_ANDROID_SOCKET_HANDSHAKE_PORT")) { + QByteArray envData = qgetenv("QTC_ANDROID_SOCKET_HANDSHAKE_PORT"); + if (!envData.isEmpty()) { + bool ok = false; + int port = 0; + port = envData.toInt(&ok); + if (ok && port > 0 && port < 65535) { + socketHandShakePort = port; + m_customPort = true; + } + } + } } AndroidRunner::~AndroidRunner() { //stop(); + delete m_socket; } static int extractPidFromChunk(const QByteArray &chunk, int from) @@ -308,11 +348,30 @@ void AndroidRunner::asyncStart() return; } + const QString pingPongSocket(m_packageName + _(".ping_pong_socket")); args << _("-e") << _("debug_ping") << _("true"); - args << _("-e") << _("ping_file") << m_pingFile; - args << _("-e") << _("pong_file") << m_pongFile; + if (m_handShakeMethod == SocketHandShake) { + args << _("-e") << _("ping_socket") << pingPongSocket; + } else if (m_handShakeMethod == PingPongFiles) { + args << _("-e") << _("ping_file") << m_pingFile; + args << _("-e") << _("pong_file") << m_pongFile; + } args << _("-e") << _("gdbserver_command") << m_gdbserverCommand; args << _("-e") << _("gdbserver_socket") << m_gdbserverSocket; + + if (m_handShakeMethod == SocketHandShake) { + QProcess adb; + const QString port = QString::fromLatin1("tcp:%1").arg(socketHandShakePort); + adb.start(m_adb, selector() << _("forward") << port << _("localabstract:") + pingPongSocket); + if (!adb.waitForStarted()) { + emit remoteProcessFinished(tr("Failed to forward ping pong ports. Reason: %1.").arg(adb.errorString())); + return; + } + if (!adb.waitForFinished()) { + emit remoteProcessFinished(tr("Failed to forward ping pong ports.")); + return; + } + } } if (m_useQmlDebugger || m_useQmlProfiler) { @@ -353,29 +412,72 @@ void AndroidRunner::asyncStart() } if (m_useCppDebugger) { + if (m_handShakeMethod == SocketHandShake) { + //Handling socket + bool wasSuccess = false; + const int maxAttempts = 20; //20 seconds + if (m_socket) + delete m_socket; + m_socket = new QTcpSocket(); + for (int i = 0; i < maxAttempts; i++) { + + QThread::sleep(1); // give Android time to start process + m_socket->connectToHost(QHostAddress(QStringLiteral("127.0.0.1")), socketHandShakePort); + if (!m_socket->waitForConnected()) + continue; + + if (!m_socket->waitForReadyRead()) { + m_socket->close(); + continue; + } + + const QByteArray pid = m_socket->readLine(); + if (pid.isEmpty()) { + m_socket->close(); + continue; + } + + wasSuccess = true; + m_socket->moveToThread(QApplication::instance()->thread()); - // Handling ping. - for (int i = 0; ; ++i) { - QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); - tmp.open(); - tmp.close(); - - QProcess process; - process.start(m_adb, selector() << _("pull") << m_pingFile << tmp.fileName()); - process.waitForFinished(); - - QFile res(tmp.fileName()); - const bool doBreak = res.size(); - res.remove(); - if (doBreak) break; + } - if (i == 20) { - emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_packageName)); - return; + if (!wasSuccess) + emit remoteProcessFinished(tr("Failed to contact debugging port.")); + + if (!m_customPort) { + // increment running port to avoid clash when using multiple + // debug sessions at the same time + socketHandShakePort++; + // wrap ports around to avoid overflow + if (socketHandShakePort == MAX_SOCKET_HANDSHAKE_PORT) + socketHandShakePort = MIN_SOCKET_HANDSHAKE_PORT; + } + } else { + // Handling ping. + for (int i = 0; ; ++i) { + QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); + tmp.open(); + tmp.close(); + + QProcess process; + process.start(m_adb, selector() << _("pull") << m_pingFile << tmp.fileName()); + process.waitForFinished(); + + QFile res(tmp.fileName()); + const bool doBreak = res.size(); + res.remove(); + if (doBreak) + break; + + if (i == 20) { + emit remoteProcessFinished(tr("Unable to start \"%1\".").arg(m_packageName)); + return; + } + qDebug() << "WAITING FOR " << tmp.fileName(); + QThread::msleep(500); } - qDebug() << "WAITING FOR " << tmp.fileName(); - QThread::msleep(500); } } @@ -388,13 +490,18 @@ void AndroidRunner::asyncStart() void AndroidRunner::handleRemoteDebuggerRunning() { if (m_useCppDebugger) { - QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); - tmp.open(); - - QProcess process; - process.start(m_adb, selector() << _("push") << tmp.fileName() << m_pongFile); - process.waitForFinished(); + if (m_handShakeMethod == SocketHandShake) { + m_socket->write("OK"); + m_socket->waitForBytesWritten(); + m_socket->close(); + } else { + QTemporaryFile tmp(QDir::tempPath() + _("/pingpong")); + tmp.open(); + QProcess process; + process.start(m_adb, selector() << _("push") << tmp.fileName() << m_pongFile); + process.waitForFinished(); + } QTC_CHECK(m_processPID != -1); } emit remoteProcessStarted(m_localGdbServerPort, m_qmlPort); |