aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/android/androidrunner.cpp
diff options
context:
space:
mode:
authorAlex Blasche <[email protected]>2014-11-18 15:14:40 +0100
committerAlex Blasche <[email protected]>2014-11-20 13:14:09 +0100
commit7f72b7bf6597f906c4f69a67a0aae5087d352a76 (patch)
treeb60534aebe5d363f43fb25c8596012ab8becdd3b /src/plugins/android/androidrunner.cpp
parentdbf663198c67c996c0ed2fcd20611c9951175355 (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.cpp175
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);