From 28a4816d593bd66599fb84b014a3dd6693cfb74b Mon Sep 17 00:00:00 2001 From: Jiakai Zhang Date: Wed, 16 Feb 2022 20:51:24 +0000 Subject: [PATCH] Use speed-profile with the cloud profile for apks loaded by other apps. Bug: 175039995 Test: atest CtsCompilationTestCases Change-Id: I816a4149151016517abb2f278351b40507b24a6f --- .../java/com/android/server/pm/Installer.java | 44 ++++++ .../server/pm/PackageDexOptimizer.java | 131 +++++++++++++----- 2 files changed, 141 insertions(+), 34 deletions(-) diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 76b9830cbde90..c3ed992ea9837 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -88,6 +88,14 @@ public class Installer extends SystemService { */ public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES = 3; + /** + * The results of {@code getOdexVisibility}. See + * {@link #getOdexVisibility(String, String, String)} for details. + */ + public static final int ODEX_NOT_FOUND = 0; + public static final int ODEX_IS_PUBLIC = 1; + public static final int ODEX_IS_PRIVATE = 2; + public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE; public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE; @@ -838,6 +846,15 @@ public class Installer extends SystemService { } } + /** + * Prepares the app profile for the package at the given path: + * + */ public boolean prepareAppProfile(String pkg, @UserIdInt int userId, @AppIdInt int appId, String profileName, String codePath, String dexMetadataPath) throws InstallerException { if (!checkBeforeRemote()) return false; @@ -988,6 +1005,33 @@ public class Installer extends SystemService { } } + /** + * Returns the visibility of the optimized artifacts. + * + * @param packageName name of the package. + * @param apkPath path to the APK. + * @param instructionSet instruction set of the optimized artifacts. + * @param outputPath path to the directory that contains the optimized artifacts (i.e., the + * directory that {@link #dexopt} outputs to). + * + * @return {@link #ODEX_NOT_FOUND} if the optimized artifacts are not found, or + * {@link #ODEX_IS_PUBLIC} if the optimized artifacts are accessible by all apps, or + * {@link #ODEX_IS_PRIVATE} if the optimized artifacts are only accessible by this app. + * + * @throws InstallerException if failed to get the visibility of the optimized artifacts. + */ + public int getOdexVisibility(String packageName, String apkPath, String instructionSet, + String outputPath) throws InstallerException { + if (!checkBeforeRemote()) return -1; + BlockGuard.getVmPolicy().onPathAccess(apkPath); + BlockGuard.getVmPolicy().onPathAccess(outputPath); + try { + return mInstalld.getOdexVisibility(packageName, apkPath, instructionSet, outputPath); + } catch (Exception e) { + throw InstallerException.from(e); + } + } + public static class InstallerException extends Exception { public InstallerException(String detailMessage) { super(detailMessage); diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 69d498794e64a..27c6d9bec2e88 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -319,28 +319,42 @@ public class PackageDexOptimizer { String profileName = ArtManager.getProfileName( i == 0 ? null : pkg.getSplitNames()[i - 1]); + final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary() + || packageUseInfo.isUsedByOtherApps(path); + String compilerFilter = getRealCompilerFilter(pkg, options.getCompilerFilter()); + // If the app is used by other apps, we must not use the existing profile because it + // may contain user data, unless the profile is newly created on install. + final boolean resetProfile = isProfileGuidedCompilerFilter(compilerFilter) + && isUsedByOtherApps + && options.getCompilationReason() != PackageManagerService.REASON_INSTALL; String dexMetadataPath = null; - if (options.isDexoptInstallWithDexMetadata()) { + if (options.isDexoptInstallWithDexMetadata() || resetProfile) { File dexMetadataFile = DexMetadataHelper.findDexMetadataForFile(new File(path)); dexMetadataPath = dexMetadataFile == null ? null : dexMetadataFile.getAbsolutePath(); } - final boolean isUsedByOtherApps = options.isDexoptAsSharedLibrary() - || packageUseInfo.isUsedByOtherApps(path); - final String compilerFilter = getRealCompilerFilter(pkg, - options.getCompilerFilter(), isUsedByOtherApps); // If we don't have to check for profiles updates assume // PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA which will be a no-op with respect to // profiles. - final int profileAnalysisResult = options.isCheckForProfileUpdates() - ? analyseProfiles(pkg, sharedGid, profileName, compilerFilter) - : PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; + int profileAnalysisResult = PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; + if (resetProfile) { + if (!resetProfile(pkg, profileName, path, dexMetadataPath)) { + // Fall back to use the shared filter. + compilerFilter = + PackageManagerServiceCompilerMapping.getCompilerFilterForReason( + PackageManagerService.REASON_SHARED); + } + } else if (options.isCheckForProfileUpdates()) { + profileAnalysisResult = + analyseProfiles(pkg, sharedGid, profileName, compilerFilter); + } // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct // flags. - final int dexoptFlags = getDexFlags(pkg, pkgSetting, compilerFilter, options); + final int dexoptFlags = getDexFlags(pkg, pkgSetting, compilerFilter, resetProfile, + options); for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, pkgSetting, path, dexCodeIsa, compilerFilter, @@ -390,6 +404,30 @@ public class PackageDexOptimizer { return result; } + /** + * Resets the profiles of the dex file at {@code path} belonging to the package {@code pkg} to + * the initial state as if the package is newly installed. Returns true on success, or false + * otherwise. + */ + @GuardedBy("mInstallLock") + private boolean resetProfile(AndroidPackage pkg, String profileName, String path, + @Nullable String dexMetadataPath) { + if (dexMetadataPath != null) { + try { + mInstaller.clearAppProfiles(pkg.getPackageName(), profileName); + final int appId = UserHandle.getAppId(pkg.getUid()); + mInstaller.prepareAppProfile(pkg.getPackageName(), UserHandle.USER_NULL, + appId, profileName, path, dexMetadataPath); + return true; + } catch (InstallerException e) { + Slog.w(TAG, "Failed to reset profile", e); + return false; + } + } else { + return false; + } + } + /** * Performs dexopt on the {@code path} belonging to the package {@code pkg}. * @@ -405,15 +443,15 @@ public class PackageDexOptimizer { String classLoaderContext, int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade, String profileName, String dexMetadataPath, int compilationReason) { - int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext, - profileAnalysisResult, downgrade); + String oatDir = getPackageOatDirIfSupported(pkg, + pkgSetting.getTransientState().isUpdatedSystemApp()); + + int dexoptNeeded = getDexoptNeeded(pkg.getPackageName(), path, isa, compilerFilter, + classLoaderContext, profileAnalysisResult, downgrade, dexoptFlags, oatDir); if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) { return DEX_OPT_SKIPPED; } - String oatDir = getPackageOatDirIfSupported(pkg, - pkgSetting.getTransientState().isUpdatedSystemApp()); - Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path + " pkg=" + pkg.getPackageName() + " isa=" + isa + " dexoptFlags=" + printDexoptFlags(dexoptFlags) @@ -456,6 +494,7 @@ public class PackageDexOptimizer { /** * Perform dexopt (if needed) on a system server code path). */ + @GuardedBy("mInstallLock") @DexOptResult public int dexoptSystemServerPath( String dexPath, PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) { @@ -466,12 +505,15 @@ public class PackageDexOptimizer { int result = DEX_OPT_SKIPPED; for (String isa : dexUseInfo.getLoaderIsas()) { int dexoptNeeded = getDexoptNeeded( + PackageManagerService.PLATFORM_PACKAGE_NAME, dexPath, isa, options.getCompilerFilter(), dexUseInfo.getClassLoaderContext(), PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES, - /* downgrade= */ false); + /* downgrade= */ false, + dexoptFlags, + /* oatDir= */ null); if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) { continue; @@ -714,7 +756,7 @@ public class PackageDexOptimizer { } /** - * Returns the compiler filter that should be used to optimize the package code. + * Returns the compiler filter that should be used to optimize the secondary dex. * The target filter will be updated if the package code is used by other apps * or if it has the safe mode flag set. */ @@ -754,12 +796,12 @@ public class PackageDexOptimizer { } /** - * Returns the compiler filter that should be used to optimize the package code. - * The target filter will be updated if the package code is used by other apps - * or if it has the safe mode flag set. + * Returns the compiler filter that should be used to optimize the primary dex. + * The target filter will be updated if the package has the safe mode flag set. Note that this + * method does NOT take other app use into account. The caller should be responsible for + * handling the case where the package code is used by other apps. */ - private String getRealCompilerFilter(AndroidPackage pkg, String targetCompilerFilter, - boolean isUsedByOtherApps) { + private String getRealCompilerFilter(AndroidPackage pkg, String targetCompilerFilter) { // When an app or priv app is configured to run out of box, only verify it. if (pkg.isUseEmbeddedDex() || (pkg.isPrivileged() @@ -783,12 +825,6 @@ public class PackageDexOptimizer { return getSafeModeCompilerFilter(targetCompilerFilter); } - if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) { - // If the dex files is used by other apps, apply the shared filter. - return PackageManagerServiceCompilerMapping.getCompilerFilterForReason( - PackageManagerService.REASON_SHARED); - } - return targetCompilerFilter; } @@ -799,14 +835,16 @@ public class PackageDexOptimizer { private int getDexFlags(ApplicationInfo info, String compilerFilter, DexoptOptions options) { return getDexFlags((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0, info.getHiddenApiEnforcementPolicy(), info.splitDependencies, - info.requestsIsolatedSplitLoading(), compilerFilter, options); + info.requestsIsolatedSplitLoading(), compilerFilter, false /* resetProfile */, + options); } + private int getDexFlags(AndroidPackage pkg, @NonNull PackageStateInternal pkgSetting, - String compilerFilter, DexoptOptions options) { + String compilerFilter, boolean resetProfile, DexoptOptions options) { return getDexFlags(pkg.isDebuggable(), AndroidPackageUtils.getHiddenApiEnforcementPolicy(pkg, pkgSetting), pkg.getSplitDependencies(), pkg.isIsolatedSplitLoading(), compilerFilter, - options); + resetProfile, options); } /** @@ -815,13 +853,15 @@ public class PackageDexOptimizer { */ private int getDexFlags(boolean debuggable, int hiddenApiEnforcementPolicy, SparseArray splitDependencies, boolean requestsIsolatedSplitLoading, - String compilerFilter, DexoptOptions options) { + String compilerFilter, boolean resetProfile, DexoptOptions options) { // Profile guide compiled oat files should not be public unles they are based // on profiles from dex metadata archives. // The flag isDexoptInstallWithDexMetadata applies only on installs when we know that // the user does not have an existing profile. + // The flag resetProfile applies only when the existing profile is already reset. boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter); - boolean isPublic = !isProfileGuidedFilter || options.isDexoptInstallWithDexMetadata(); + boolean isPublic = !isProfileGuidedFilter || options.isDexoptInstallWithDexMetadata() + || resetProfile; int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0; // Some apps are executed with restrictions on hidden API usage. If this app is one // of them, pass a flag to dexopt to enable the same restrictions during compilation. @@ -866,8 +906,19 @@ public class PackageDexOptimizer { * Assesses if there's a need to perform dexopt on {@code path} for the given * configuration (isa, compiler filter, profile). */ - private int getDexoptNeeded(String path, String isa, String compilerFilter, - String classLoaderContext, int profileAnalysisResult, boolean downgrade) { + @GuardedBy("mInstallLock") + private int getDexoptNeeded(String packageName, String path, String isa, String compilerFilter, + String classLoaderContext, int profileAnalysisResult, boolean downgrade, + int dexoptFlags, String oatDir) { + final boolean shouldBePublic = (dexoptFlags & DEXOPT_PUBLIC) != 0; + // If the artifacts should be public while the current artifacts are not, we should + // re-compile anyway. + if (shouldBePublic && isOdexPrivate(packageName, path, isa, oatDir)) { + // Ensure compilation by pretending a compiler filter change on the apk/odex location + // (the reason for the '-'. A positive value means the 'oat' location). + return adjustDexoptNeeded(-DexFile.DEX2OAT_FOR_FILTER); + } + int dexoptNeeded; try { // A profile guided optimizations with an empty profile is essentially 'verify' and @@ -901,6 +952,18 @@ public class PackageDexOptimizer { return compilerFilter.endsWith("-profile"); } + /** Returns true if the current artifacts of the app are private to the app itself. */ + @GuardedBy("mInstallLock") + private boolean isOdexPrivate(String packageName, String path, String isa, String oatDir) { + try { + return mInstaller.getOdexVisibility(packageName, path, isa, oatDir) + == Installer.ODEX_IS_PRIVATE; + } catch (Exception e) { + Slog.w(TAG, "Failed to get odex visibility for " + path, e); + return false; + } + } + /** * Checks if there is an update on the profile information of the {@code pkg}. * If the compiler filter is not profile guided the method returns a safe default: