mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-04 09:00:44 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user