Revamp android patching to only check for (& change) the debuggable flag

* We don't consider anything else, this includes permissions or the
  library being present. Since we no longer expect to patch in the
  library we also don't check its version (however we leave the tag in
  case it is useful in the future).
* If the user has root access we will never warn, assuming the injection
  will work fine even without the debuggable flag.
This commit is contained in:
baldurk
2018-01-26 17:19:02 +00:00
parent a019797f88
commit cb34a2daea
11 changed files with 133 additions and 676 deletions
@@ -219,9 +219,6 @@ void PersistantConfig::AddAndroidHosts()
SetConfigSetting("MaxConnectTimeout", QString::number(Android_MaxConnectTimeout));
SetConfigSetting(lit("Android_AutoPushLayerToApp"),
Android_AutoPushLayerToApp ? lit("1") : lit("0"));
rdcstr androidHosts;
RENDERDOC_EnumerateAndroidDevices(&androidHosts);
for(const QString &hostName :
@@ -219,8 +219,6 @@ DECLARE_REFLECTION_STRUCT(BugReport);
\
CONFIG_SETTING_VAL(public, int, int, Android_MaxConnectTimeout, 30) \
\
CONFIG_SETTING_VAL(public, bool, bool, Android_AutoPushLayerToApp, false) \
\
CONFIG_SETTING_VAL(public, bool, bool, CheckUpdate_AllowChecks, true) \
\
CONFIG_SETTING_VAL(public, bool, bool, CheckUpdate_UpdateAvailable, false) \
@@ -495,12 +493,6 @@ For more information about some of these settings that are user-facing see
Defaults to ``30``.
.. data:: Android_AutoPushLayerToApp
Whether to automatically push the RenderDoc layer to the application's lib directory when running
on a device with root access. This can enable debugging of Vulkan applications that didn't already
package the layer in the APK.
.. data:: CheckUpdate_AllowChecks
``True`` if when coloring marker regions in the :class:`EventBrowser`, the whole row should be
+58 -173
View File
@@ -428,11 +428,10 @@ void CaptureDialog::CheckAndroidSetup(QString &filename)
rdcstr host = m_Ctx.Replay().CurrentRemote()->hostname;
RENDERDOC_CheckAndroidPackage(host.c_str(), filename.toUtf8().data(), &m_AndroidFlags);
const bool missingLibrary = bool(m_AndroidFlags & AndroidFlags::MissingLibrary);
const bool missingPermissions = bool(m_AndroidFlags & AndroidFlags::MissingPermissions);
const bool wrongLayerVersion = bool(m_AndroidFlags & AndroidFlags::WrongLayerVersion);
const bool debuggable = bool(m_AndroidFlags & AndroidFlags::Debuggable);
const bool hasroot = bool(m_AndroidFlags & AndroidFlags::RootAccess);
if(missingLibrary || missingPermissions || wrongLayerVersion)
if(!debuggable && !hasroot)
{
// Check failed - set the warning visible
GUIInvoke::call([this]() {
@@ -442,7 +441,7 @@ void CaptureDialog::CheckAndroidSetup(QString &filename)
}
else
{
// Check passed - no warnings needed
// Check passed, either app is debuggable or we have root - no warnings needed
GUIInvoke::call([this]() {
ui->androidScan->setVisible(false);
ui->androidWarn->setVisible(false);
@@ -458,192 +457,78 @@ void CaptureDialog::androidWarn_mouseClick()
{
QString exe = ui->exePath->text();
QString caption = tr("Missing RenderDoc requirements");
QString caption = tr("Application is not debuggable");
QString msg = tr("In order to debug on Android, the following problems must be fixed:<br><br>");
QString msg = tr(R"(In order to debug on Android, the package must be <b>debuggable</b>.
<br><br>
On UE4 you must disable <em>for distribution</em>, on Unity enable <em>development mode</em>.
<br><br>
RenderDoc can try to add the flag for you, which will involve completely reinstalling your package
as well as re-signing it with a debug key. This method is prone to error and is
<b>not recommended</b>. It is instead advised to configure your app to be debuggable at build time.
<br><br>
Would you like RenderDoc to try patching your package?
)");
bool missingPermissions = bool(m_AndroidFlags & AndroidFlags::MissingPermissions);
bool missingLibrary = bool(m_AndroidFlags & AndroidFlags::MissingLibrary);
bool wrongLayerVersion = bool(m_AndroidFlags & AndroidFlags::WrongLayerVersion);
bool rootAccess = bool(m_AndroidFlags & AndroidFlags::RootAccess);
QMessageBox::StandardButton prompt = RDDialog::question(this, caption, msg, RDDialog::YesNoCancel);
if(missingPermissions)
if(prompt == QMessageBox::Yes)
{
msg +=
tr("<b>Missing permissions</b><br>"
"The target APK must have the following permissions:<br>"
"android.permission.INTERNET<br><br>");
}
float progress = 0.0f;
bool patchSucceeded = false;
if(missingLibrary)
{
msg +=
tr("<b>Missing library</b><br>"
"The RenderDoc library must be present in the "
"installed application.<br><br>");
}
// call into APK pull, patch, install routine, then continue
LambdaThread *patch = new LambdaThread([this, exe, &patchSucceeded, &progress]() {
rdcstr host = m_Ctx.Replay().CurrentRemote()->hostname;
AndroidFlags result = RENDERDOC_MakeDebuggablePackage(host.c_str(), exe.toUtf8().data(),
[&progress](float p) { progress = p; });
if(wrongLayerVersion)
{
msg +=
tr("<b>Wrong layer version</b><br>"
"The RenderDoc library was found, but its version "
"does not match that of the host.<br><br>");
}
if(missingPermissions)
{
// Don't prompt for patching if permissions are wrong - we can't fix that
RDDialog::critical(this, caption, msg);
return;
}
// Track whether we tried to push layer directly, to influence text
bool triedPush = false;
// Track whether to continue with push suggestion dialogue, in case user clicked Cancel
bool suggestPatch = true;
if(rootAccess)
{
// Check whether user has requested automatic pushing
bool autoPushConfig = m_Ctx.Config().Android_AutoPushLayerToApp;
// Separately, track whether the persistent checkBox is selected
bool autoPushCheckBox = autoPushConfig;
QMessageBox::StandardButton prompt = QMessageBox::No;
// Only display initial prompt if user has not chosen to push automatically
if(!autoPushConfig)
{
QString rootmsg = msg;
rootmsg +=
tr("Your device appears to have <b>root access</b>. If you are only targeting Vulkan, "
"RenderDoc can try to push the layer directly to your application.<br><br>"
"Would you like RenderDoc to push the layer?<br>");
QString checkMsg(tr("Automatically push the layer on rooted devices"));
QCheckBox *cb = new QCheckBox(checkMsg, this);
cb->setChecked(autoPushCheckBox);
prompt = RDDialog::questionChecked(this, caption, rootmsg, cb, autoPushCheckBox,
RDDialog::YesNoCancel);
}
if(autoPushConfig || prompt == QMessageBox::Yes)
{
bool pushSucceeded = false;
triedPush = true;
// Only update the autoPush setting if Yes was clicked
if(autoPushCheckBox != m_Ctx.Config().Android_AutoPushLayerToApp)
if(result & AndroidFlags::Debuggable)
{
m_Ctx.Config().Android_AutoPushLayerToApp = autoPushCheckBox;
m_Ctx.Config().Save();
}
// Sucess!
patchSucceeded = true;
// Call into layer push routine, then continue
LambdaThread *push = new LambdaThread([this, exe, &pushSucceeded]() {
rdcstr host = m_Ctx.Replay().CurrentRemote()->hostname;
if(RENDERDOC_PushLayerToInstalledAndroidApp(host.c_str(), exe.toUtf8().data()))
RDDialog::information(this, tr("Patch succeeded!"),
tr("The patch process succeeded and the package is ready to debug"));
}
else
{
QString failmsg = tr("Something has gone wrong and the patching process failed.<br><br>");
if(result == AndroidFlags::MissingTools)
{
// Sucess!
pushSucceeded = true;
RDDialog::information(
this, tr("Push succeeded!"),
tr("The push attempt succeeded and<br>%1 now contains the RenderDoc layer").arg(exe));
failmsg +=
tr("Tools required for the process were not found. Try configuring the path to your "
"android SDK or java JDK in the settings dialog.");
}
});
push->start();
if(push->isRunning())
{
ShowProgressDialog(this, tr("Pushing layer to %1, please wait...").arg(exe),
[push]() { return !push->isRunning(); });
}
push->deleteLater();
if(pushSucceeded)
{
// We should be good from here, no futher prompts
suggestPatch = false;
ui->androidWarn->setVisible(false);
}
}
else if(prompt == QMessageBox::Cancel)
{
// Cancel skips any other fix prompts
suggestPatch = false;
}
}
if(suggestPatch)
{
if(triedPush)
msg.insert(0, tr("The push attempt failed, so other methods must be used to fix the missing "
"layer.<br><br>"));
msg +=
tr("To fix this, you should repackage the APK following guidelines on the "
"<a href='http://github.com/baldurk/renderdoc/wiki/Android-Support'>"
"RenderDoc Wiki</a><br><br>"
"If you are <b>only targeting Vulkan</b>, RenderDoc can try to add the layer for you, "
"which requires pulling the APK, patching it, uninstalling the original, and "
"installing the modified version with a debug key. "
"This works for many debuggable applications, but not all, especially those that "
"check their integrity before launching.<br><br>"
"Your system will need several tools installed and available to RenderDoc. "
"Any missing tools will be noted in the log. Follow the steps "
"<a href='http://github.com/baldurk/renderdoc/wiki/Android-Support'>here"
"</a> to get them.<br><br>"
"Would you like RenderDoc to try patching your APK?");
QMessageBox::StandardButton prompt =
RDDialog::question(this, caption, msg, RDDialog::YesNoCancel);
if(prompt == QMessageBox::Yes)
{
float progress = 0.0f;
bool patchSucceeded = false;
// call into APK pull, patch, install routine, then continue
LambdaThread *patch = new LambdaThread([this, exe, &patchSucceeded, &progress]() {
rdcstr host = m_Ctx.Replay().CurrentRemote()->hostname;
if(RENDERDOC_AddLayerToAndroidPackage(host.c_str(), exe.toUtf8().data(),
[&progress](float p) { progress = p; }))
else if(result == AndroidFlags::ManifestPatchFailure)
{
// Sucess!
patchSucceeded = true;
RDDialog::information(
this, tr("Patch succeeded!"),
tr("The patch process succeeded and<br>%1 now contains the RenderDoc layer").arg(exe));
failmsg +=
tr("The package manifest could not be patched. This is not solveable, you will have "
"to rebuild the package with the debuggable flag.");
}
else
else if(result == AndroidFlags::RepackagingAPKFailure)
{
RDDialog::critical(this, tr("Failed to patch APK"),
tr("Something has gone wrong and APK patching failed "
"for:<br>%1<br>Check diagnostic log in Help "
"menu for more details.")
.arg(exe));
failmsg += tr("The package was patched but could not be repackaged and installed.");
}
});
patch->start();
// wait a few ms before popping up a progress bar
patch->wait(500);
if(patch->isRunning())
{
ShowProgressDialog(this, tr("Patching %1, please wait...").arg(exe),
[patch]() { return !patch->isRunning(); },
[&progress]() { return progress; });
RDDialog::critical(this, tr("Failed to patch package"), failmsg);
}
patch->deleteLater();
});
if(patchSucceeded)
ui->androidWarn->setVisible(false);
patch->start();
// wait a few ms before popping up a progress bar
patch->wait(500);
if(patch->isRunning())
{
ShowProgressDialog(this, tr("Patching %1, please wait...").arg(exe),
[patch]() { return !patch->isRunning(); },
[&progress]() { return progress; });
}
patch->deleteLater();
if(patchSucceeded)
ui->androidWarn->setVisible(false);
}
}
@@ -158,7 +158,6 @@ SettingsDialog::SettingsDialog(ICaptureContext &ctx, QWidget *parent)
ui->Android_SDKPath->setText(m_Ctx.Config().Android_SDKPath);
ui->Android_JDKPath->setText(m_Ctx.Config().Android_JDKPath);
ui->Android_MaxConnectTimeout->setValue(m_Ctx.Config().Android_MaxConnectTimeout);
ui->Android_AutoPushLayerToApp->setChecked(m_Ctx.Config().Android_AutoPushLayerToApp);
ui->TextureViewer_ResetRange->setChecked(m_Ctx.Config().TextureViewer_ResetRange);
ui->TextureViewer_PerTexSettings->setChecked(m_Ctx.Config().TextureViewer_PerTexSettings);
@@ -641,13 +640,6 @@ void SettingsDialog::on_Android_MaxConnectTimeout_valueChanged(double timeout)
m_Ctx.Config().Save();
}
void SettingsDialog::on_Android_AutoPushLayerToApp_toggled(bool checked)
{
m_Ctx.Config().Android_AutoPushLayerToApp = ui->Android_AutoPushLayerToApp->isChecked();
m_Ctx.Config().Save();
}
void SettingsDialog::on_UIStyle_currentIndexChanged(int index)
{
if(index < 0 || index >= StyleData::numAvailable)
@@ -96,7 +96,6 @@ private slots:
void on_Android_MaxConnectTimeout_valueChanged(double timeout);
void on_Android_SDKPath_textEdited(const QString &path);
void on_Android_JDKPath_textEdited(const QString &path);
void on_Android_AutoPushLayerToApp_toggled(bool checked);
// manual slots
void formatter_valueChanged(int value);
+2 -21
View File
@@ -73,7 +73,7 @@
<enum>QTabWidget::West</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>6</number>
</property>
<property name="documentMode">
<bool>true</bool>
@@ -1005,7 +1005,7 @@ Only happens if the capture is not in the recent files list.</string>
</property>
</widget>
</item>
<item row="6" column="0">
<item row="5" column="0">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -1018,25 +1018,6 @@ Only happens if the capture is not in the recent files list.</string>
</property>
</spacer>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_26">
<property name="toolTip">
<string>Automatically push the RenderDoc layer to applications that need it when running on a device with root access.
This can enable debugging of Vulkan apps that don't already contain the layer.</string>
</property>
<property name="text">
<string>Automatically push RenderDoc layer for Vulkan on devices with root access.</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QCheckBox" name="Android_AutoPushLayerToApp">
<property name="toolTip">
<string>Automatically push the RenderDoc layer to applications that need it when running on a device with root access.
This can enable debugging of Vulkan apps that don't already contain the layer.</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_25">
<property name="toolTip">
+44 -394
View File
@@ -86,50 +86,6 @@ bool RemoveAPKSignature(const string &apk)
return true;
}
bool AddLayerToAPK(const string &apk, const string &layerPath, const string &layerName,
const string &abi, const string &tmpDir)
{
RDCLOG("Adding RenderDoc layer");
std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
// Run aapt from the directory containing "lib" so the relative paths are good
string relativeLayer("lib/" + abi + "/" + layerName);
string workDir = removeFromEnd(layerPath, relativeLayer);
// If the layer was already present in the APK, we need to remove it first
Process::ProcessResult contents = execCommand(aapt, "list \"" + apk + "\"", workDir);
if(contents.strStdout.empty())
{
RDCERR("Failed to list contents of APK. STDERR: %s", contents.strStderror.c_str());
return false;
}
if(contents.strStdout.find(relativeLayer) != std::string::npos)
{
RDCLOG("Removing existing layer from APK before trying to add");
Process::ProcessResult remove =
execCommand(aapt, "remove \"" + apk + "\" " + relativeLayer, workDir);
if(!remove.strStdout.empty())
{
RDCERR("Failed to remove existing layer from APK. STDERR: %s", remove.strStderror.c_str());
return false;
}
}
// Add the RenderDoc layer
Process::ProcessResult result = execCommand(aapt, "add \"" + apk + "\" " + relativeLayer, workDir);
if(result.strStdout.empty())
{
RDCERR("Failed to add layer to APK. STDERR: %s", result.strStderror.c_str());
return false;
}
return true;
}
bool RealignAPK(const string &apk, string &alignedAPK, const string &tmpDir)
{
std::string zipalign = getToolPath(ToolDir::BuildTools, "zipalign", false);
@@ -383,265 +339,74 @@ bool PullAPK(const string &deviceID, const string &pkgPath, const string &apk)
return false;
}
bool CheckLayerVersion(const string &deviceID, const string &layerName, const string &remoteLayer)
bool HasRootAccess(const std::string &deviceID)
{
RDCDEBUG("Checking layer version of: %s", layerName.c_str());
RDCLOG("Checking for root access on %s", deviceID.c_str());
bool match = false;
Process::ProcessResult result = {};
// Use 'strings' command on the device to find the layer's build version
// i.e. strings -n <tag length> <layer> | grep <tag marker>
// Subtract 5 to provide a bit of wiggle room on version length
Process::ProcessResult result = adbExecCommand(
deviceID, "shell strings -n " +
StringFormat::Fmt("%u", strlen(RENDERDOC_Version_Tag_String) - 5) + " " +
remoteLayer + " | grep RenderDoc_build_version");
// Try switching adb to root and check a few indicators for success
// Nothing will fall over if we get a false positive here, it just enables
// additional methods of getting things set up.
string line = trim(result.strStdout);
result = adbExecCommand(deviceID, "root");
if(line.empty())
std::string whoami = trim(adbExecCommand(deviceID, "shell whoami").strStdout);
if(whoami == "root")
return true;
std::string checksu =
trim(adbExecCommand(deviceID, "shell test -e /system/xbin/su && echo found").strStdout);
if(checksu == "found")
return true;
return false;
}
bool IsDebuggable(const std::string &deviceID, const std::string &packageName)
{
RDCLOG("Checking that APK is debuggable");
std::string info = adbExecCommand(deviceID, "shell dumpsys package " + packageName).strStdout;
size_t flagsOffset = info.find("pkgFlags=[");
if(flagsOffset == std::string::npos)
{
RDCLOG("RenderDoc layer is not versioned, so cannot be checked for compatibility.");
RDCERR("Couldn't get pkgFlags from adb");
return false;
}
std::vector<string> vec;
split(line, vec, ' ');
string version = vec[1];
string hash = vec[5];
size_t nextLine = info.find('\n', flagsOffset + 1);
if(version == FULL_VERSION_STRING && hash == GitVersionHash)
{
RDCLOG("RenderDoc layer version (%s) and git hash (%s) match.", version.c_str(), hash.c_str());
match = true;
}
else
{
RDCLOG(
"RenderDoc layer version (%s) and git hash (%s) do NOT match the host version (%s) or git "
"hash (%s).",
version.c_str(), hash.c_str(), FULL_VERSION_STRING, GitVersionHash);
}
std::string pkgFlags =
info.substr(flagsOffset, nextLine == std::string::npos ? nextLine : nextLine - flagsOffset);
return match;
}
bool CheckPermissions(const string &dump)
{
// TODO: remove this if we are sure that there are no permissions to check.
return true;
}
bool CheckAPKPermissions(const string &apk)
{
RDCLOG("Checking that APK can be can write to sdcard");
std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
string badging = execCommand(aapt, "dump badging \"" + apk + "\"").strStdout;
if(badging.empty())
{
RDCERR("Unable to aapt dump %s", apk.c_str());
return false;
}
return CheckPermissions(badging);
}
bool CheckDebuggable(const string &apk)
{
RDCLOG("Checking that APK s debuggable");
std::string aapt = getToolPath(ToolDir::BuildTools, "aapt", false);
string badging = execCommand(aapt, "dump badging \"" + apk + "\"").strStdout;
if(badging.find("application-debuggable") == string::npos)
{
RDCERR("APK is not debuggable");
return false;
}
return true;
}
bool CheckInstalledPermissions(const string &deviceID, const string &packageName)
{
RDCLOG("Checking installed permissions for %s", packageName.c_str());
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
return CheckPermissions(dump);
}
string DetermineInstalledABI(const string &deviceID, const string &packageName)
{
RDCLOG("Checking installed ABI for %s", packageName.c_str());
string abi;
string dump = adbExecCommand(deviceID, "shell pm dump " + packageName).strStdout;
if(dump.empty())
RDCERR("Unable to pm dump %s", packageName.c_str());
// Walk through the output and look for primaryCpuAbi
std::istringstream contents(dump);
string line;
string prefix("primaryCpuAbi=");
while(std::getline(contents, line))
{
line = trim(line);
if(line.compare(0, prefix.size(), prefix) == 0)
{
// Extract the abi
abi = line.substr(line.find_last_of("=") + 1);
RDCLOG("primaryCpuAbi found: %s", abi.c_str());
break;
}
}
if(abi.empty())
RDCERR("Unable to determine installed abi for: %s", packageName.c_str());
return abi;
}
string FindAndroidLayer(const string &abi, const string &layerName)
{
string layer;
// Check known paths for RenderDoc layer
string exePath;
FileIO::GetExecutableFilename(exePath);
string exeDir = dirname(FileIO::GetFullPathname(exePath));
std::vector<std::string> paths;
#if defined(RENDERDOC_LAYER_PATH)
string customPath(RENDERDOC_LAYER_PATH);
RDCLOG("Custom layer path: %s", customPath.c_str());
if(FileIO::IsRelativePath(customPath))
customPath = exeDir + "/" + customPath;
if(!endswith(customPath, "/"))
customPath += "/";
// Custom path must point to directory containing ABI folders
customPath += abi;
if(!FileIO::exists(customPath.c_str()))
{
RDCWARN("Custom layer path does not contain required ABI");
}
paths.push_back(customPath + "/" + layerName);
#endif
string windows = "/android/lib/";
string linux = "/../share/renderdoc/android/lib/";
string local = "/../../build-android/renderdoccmd/libs/lib/";
string macOS = "/../../../../../build-android/renderdoccmd/libs/lib/";
paths.push_back(exeDir + windows + abi + "/" + layerName);
paths.push_back(exeDir + linux + abi + "/" + layerName);
paths.push_back(exeDir + local + abi + "/" + layerName);
paths.push_back(exeDir + macOS + abi + "/" + layerName);
for(uint32_t i = 0; i < paths.size(); i++)
{
RDCLOG("Checking for layer in %s", paths[i].c_str());
if(FileIO::exists(paths[i].c_str()))
{
layer = paths[i];
RDCLOG("Layer found!: %s", layer.c_str());
break;
}
}
if(layer.empty())
{
RDCERR(
"%s missing! RenderDoc for Android will not work without it. "
"Build your Android ABI in build-android in the root to have it "
"automatically found and installed.",
layerName.c_str());
}
return layer;
}
std::string GetPathForPackage(const std::string &deviceID, const std::string &packageName)
{
std::string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
if(pkgPath.empty() || pkgPath.find("package:") != 0 || pkgPath.find("base.apk") == std::string::npos)
return pkgPath;
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
pkgPath.erase(pkgPath.end() - strlen("base.apk"), pkgPath.end());
return pkgPath;
return pkgFlags.find("DEBUGGABLE") != std::string::npos;
}
};
using namespace Android;
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const char *host,
const char *exe,
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const char *hostname,
const char *packageName,
AndroidFlags *flags)
{
string packageName(basename(string(exe)));
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(host, index, deviceID);
// Find the path to package
std::string pkgPath = Android::GetPathForPackage(deviceID, packageName) + "lib";
string layerName = "libVkLayer_GLES_RenderDoc.so";
Android::ExtractDeviceIDAndIndex(hostname, index, deviceID);
// Reset the flags each time we check
*flags = AndroidFlags::NoFlags;
bool found = false;
string layerPath = "";
// Check a debug location only usable by rooted devices, overriding app's layer
if(SearchForAndroidLibrary(deviceID, "/data/local/debug/vulkan", layerName, layerPath))
found = true;
// See if the application contains the layer
if(!found && SearchForAndroidLibrary(deviceID, pkgPath, layerName, layerPath))
found = true;
// TODO: Add any future layer locations
if(found)
if(Android::IsDebuggable(deviceID, basename(std::string(packageName))))
{
#if ENABLED(RDOC_DEVEL)
// Check the version of the layer found
if(!CheckLayerVersion(deviceID, layerName, layerPath))
{
RDCWARN("RenderDoc layer found, but version does not match");
*flags |= AndroidFlags::WrongLayerVersion;
}
#endif
*flags |= AndroidFlags::Debuggable;
}
else
{
RDCWARN("No RenderDoc layer for Vulkan or GLES was found");
*flags |= AndroidFlags::MissingLibrary;
RDCLOG("%s is not debuggable", packageName);
}
// Next check permissions of the installed application (without pulling the APK)
if(!CheckInstalledPermissions(deviceID, packageName))
{
RDCWARN("Android application does not have required permissions");
*flags |= AndroidFlags::MissingPermissions;
}
if(CheckRootAccess(deviceID))
if(Android::HasRootAccess(deviceID))
{
RDCLOG("Root access detected");
*flags |= AndroidFlags::RootAccess;
@@ -650,124 +415,9 @@ extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const c
return;
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_PushLayerToInstalledAndroidApp(const char *host,
const char *exe)
extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage(
const char *hostname, const char *packageName, RENDERDOC_ProgressCallback progress)
{
Process::ProcessResult result = {};
string packageName(basename(string(exe)));
RDCLOG("Attempting to push RenderDoc layer to %s", packageName.c_str());
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(host, index, deviceID);
// Detect which ABI was installed on the device
string abi = DetermineInstalledABI(deviceID, packageName);
// Find the layer on host
string layerName("libVkLayer_GLES_RenderDoc.so");
string layerPath = FindAndroidLayer(abi, layerName);
if(layerPath.empty())
return false;
// Determine where to push the layer
string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
// Isolate the app's lib dir
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
string libDir = removeFromEnd(pkgPath, "base.apk") + "lib/";
// There will only be one ABI in the lib dir
string libsAbi = trim(adbExecCommand(deviceID, "shell ls " + libDir).strStdout);
string layerDst = libDir + libsAbi + "/";
result = adbExecCommand(deviceID, "push " + layerPath + " " + layerDst);
// Ensure the push succeeded
string foundLayer;
return SearchForAndroidLibrary(deviceID, layerDst, layerName, foundLayer);
}
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_AddLayerToAndroidPackage(
const char *host, const char *exe, RENDERDOC_ProgressCallback progress)
{
Process::ProcessResult result = {};
string packageName(basename(string(exe)));
int index = 0;
std::string deviceID;
Android::ExtractDeviceIDAndIndex(host, index, deviceID);
// make sure progress is valid so we don't have to check it everywhere
if(!progress)
progress = [](float) {};
progress(0.0f);
if(!CheckPatchingRequirements())
return false;
progress(0.11f);
// Detect which ABI was installed on the device
string abi = DetermineInstalledABI(deviceID, packageName);
// Find the layer on host
string layerName("libVkLayer_GLES_RenderDoc.so");
string layerPath = FindAndroidLayer(abi, layerName);
if(layerPath.empty())
return false;
// Find the APK on the device
std::string apkPath = Android::GetPathForPackage(deviceID, packageName) + "base.apk";
string tmpDir = FileIO::GetTempFolderFilename();
string origAPK(tmpDir + packageName + ".orig.apk");
string alignedAPK(origAPK + ".aligned.apk");
progress(0.21f);
// Try the following steps, bailing if anything fails
if(!PullAPK(deviceID, pkgPath, origAPK))
return false;
progress(0.31f);
if(!CheckAPKPermissions(origAPK))
return false;
progress(0.41f);
if(!RemoveAPKSignature(origAPK))
return false;
progress(0.51f);
if(!AddLayerToAPK(origAPK, layerPath, layerName, abi, tmpDir))
return false;
progress(0.61f);
if(!RealignAPK(origAPK, alignedAPK, tmpDir))
return false;
progress(0.71f);
if(!DebugSignAPK(alignedAPK, tmpDir))
return false;
progress(0.81f);
if(!UninstallOriginalAPK(deviceID, packageName, tmpDir))
return false;
progress(0.91f);
if(!ReinstallPatchedAPK(deviceID, alignedAPK, abi, packageName, tmpDir))
return false;
progress(1.0f);
// All clean!
return true;
// stub for now
return AndroidFlags::ManifestPatchFailure;
}
+7 -32
View File
@@ -58,42 +58,17 @@ void ExtractDeviceIDAndIndex(const std::string &hostname, int &index, std::strin
deviceID = c;
}
bool CheckRootAccess(const std::string &deviceID)
std::string GetPathForPackage(const std::string &deviceID, const std::string &packageName)
{
RDCLOG("Checking for root access on %s", deviceID.c_str());
std::string pkgPath = trim(adbExecCommand(deviceID, "shell pm path " + packageName).strStdout);
Process::ProcessResult result = {};
if(pkgPath.empty() || pkgPath.find("package:") != 0 || pkgPath.find("base.apk") == std::string::npos)
return pkgPath;
// Try switching adb to root and check a few indicators for success
// Nothing will fall over if we get a false positive here, it just enables
// additional methods of getting things set up.
pkgPath.erase(pkgPath.begin(), pkgPath.begin() + strlen("package:"));
pkgPath.erase(pkgPath.end() - strlen("base.apk"), pkgPath.end());
result = adbExecCommand(deviceID, "root");
std::string whoami = trim(adbExecCommand(deviceID, "shell whoami").strStdout);
if(whoami == "root")
return true;
std::string checksu =
trim(adbExecCommand(deviceID, "shell test -e /system/xbin/su && echo found").strStdout);
if(checksu == "found")
return true;
return false;
}
bool SearchForAndroidLibrary(const std::string &deviceID, const std::string &location,
const std::string &layerName, std::string &foundLayer)
{
RDCLOG("Checking for layers in: %s", location.c_str());
foundLayer =
trim(adbExecCommand(deviceID, "shell find " + location + " -name " + layerName).strStdout);
if(!foundLayer.empty())
{
RDCLOG("Found RenderDoc layer in %s", location.c_str());
return true;
}
return false;
return pkgPath;
}
std::string GetFriendlyName(std::string deviceID)
-3
View File
@@ -48,7 +48,4 @@ std::string getToolPath(ToolDir subdir, const std::string &toolname, bool checkE
bool toolExists(const std::string &path);
std::string GetFriendlyName(std::string deviceID);
bool CheckRootAccess(const std::string &deviceID);
bool SearchForAndroidLibrary(const std::string &deviceID, const std::string &location,
const std::string &layerName, std::string &foundLayer);
};
+5 -9
View File
@@ -2074,17 +2074,13 @@ DOCUMENT("Internal function for starting an android remote server.");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_StartAndroidRemoteServer(const char *device);
DOCUMENT("Internal function for checking remote Android package for requirements");
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const char *host,
const char *exe,
extern "C" RENDERDOC_API void RENDERDOC_CC RENDERDOC_CheckAndroidPackage(const char *hostname,
const char *packageName,
AndroidFlags *flags);
DOCUMENT("Internal function that attempts to push Vulkan layer to Android application.");
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_PushLayerToInstalledAndroidApp(const char *host,
const char *exe);
DOCUMENT("Internal function that attempts to modify APK contents, adding Vulkan layer.");
extern "C" RENDERDOC_API bool RENDERDOC_CC RENDERDOC_AddLayerToAndroidPackage(
const char *host, const char *exe, RENDERDOC_ProgressCallback progress);
DOCUMENT("Internal function that attempts to modify APK contents, adding debuggable flag.");
extern "C" RENDERDOC_API AndroidFlags RENDERDOC_CC RENDERDOC_MakeDebuggablePackage(
const char *hostname, const char *packageName, RENDERDOC_ProgressCallback progress);
DOCUMENT("Internal function that runs unit tests.");
extern "C" RENDERDOC_API int RENDERDOC_CC RENDERDOC_RunUnitTests(const rdcstr &command,
+17 -24
View File
@@ -3529,41 +3529,34 @@ DOCUMENT(R"(A set of flags giving details of the current status of Android traca
There are no problems with the Android application setup.
.. data:: MissingLibrary
.. data:: Debuggable
The RenderDoc library (whether Vulkan layer or OpenGLES library) could not be found in the
application or system locations.
.. data:: MissingPermissions
The application being checked does not have the requesite permission. Currently there
are no required permissions.
.. data:: NotDebuggable
The application is not debuggable.
.. data:: WrongLayerVersion
The found RenderDoc layer does not match the server's version.
The application is debuggable.
.. data:: RootAccess
The device being targeted has root access.
.. data:: Unfixable
.. data:: MissingTools
The current situation is not fixable automatically and requires user intervention/disambiguation.
When patching, some necessary tools were not found.
.. data:: ManifestPatchFailure
When patching, modifying the manifest file to include the debuggable flag failed.
.. data:: RepackagingAPKFailure
When patching, repackaging, signing and installing the new package failed.
)");
enum class AndroidFlags : uint32_t
{
NoFlags = 0x0,
MissingLibrary = 0x1,
MissingPermissions = 0x2,
NotDebuggable = 0x4,
WrongLayerVersion = 0x8,
RootAccess = 0x10,
Unfixable = 0x20,
Debuggable = 0x1,
RootAccess = 0x2,
MissingTools = 0x1000,
ManifestPatchFailure = 0x2000,
RepackagingAPKFailure = 0x4000,
};
BITMASK_OPERATORS(AndroidFlags);