Commit 7f72b7bf authored by Alex Blasche's avatar Alex Blasche
Browse files

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: default avatarAlex Blasche <alexander.blasche@theqtcompany.com>
Reviewed-by: default avatarhjk <hjk121@nokiamail.com>
Reviewed-by: default avatarEskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
Reviewed-by: default avatarBogDan Vatra <bogdan@kde.org>
parent dbf66319
/**************************************************************************
**
** Copyright (c) 2014 BogDan Vatra <bog_dan_ro@yahoo.com>
** 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);
......
......@@ -37,6 +37,7 @@
#include <QObject>
#include <QTimer>
#include <QTcpSocket>
#include <QThread>
#include <QProcess>
#include <QMutex>
......@@ -50,6 +51,11 @@ class AndroidRunner : public QThread
{
Q_OBJECT
enum DebugHandShakeType {
PingPongFiles,
SocketHandShake
};
public:
AndroidRunner(QObject *parent, AndroidRunConfiguration *runConfig,
ProjectExplorer::RunMode runMode);
......@@ -114,6 +120,9 @@ private:
bool m_isBusyBox;
QStringList m_selector;
QMutex m_mutex;
DebugHandShakeType m_handShakeMethod;
QTcpSocket *m_socket;
bool m_customPort;
};
} // namespace Internal
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment