Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f1ff36f0 authored by Calin Juravle's avatar Calin Juravle
Browse files

Use the class loader context when optimizing secondary dex files

Record the class loader context for secondary dex loads and pass it to
dexopt during compilation.

The class loader context is passed from libcore every time a
BaseDexClassLoader is created and its recorded in the package dex usage
file.

Note that the context may be:
- unknown: if the dex file was not use after the the upgrade and its
context was not yet updated
- unsupported: if any of the class loaders from the loading context is
unsupported (only PathClassLoader and DelegateLastClassLoader are
supported).
- variable: if it changes over time, form one run to another.

In all the above cases the old compilation behavior is preserved for
now.(i.e. the dex file with be compiled with SKIP_SHARED_LIBRARY_CHECK)

Bug: 38138251
Test: runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/
      adb shell cmd package compile -f -m quicken ^Csecondary-dex
com.google.android.gms

(cherry picked from commit 3bec94d7)

Change-Id: Ie8b78c7c0d5de43733b3d116f8dcb3a65324cca8
parent f8c14e92
Loading
Loading
Loading
Loading
+31 −9
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import dalvik.system.VMRuntime;


import java.io.File;
import java.io.File;
import java.io.IOException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Set;
import java.util.Set;
@@ -86,29 +87,50 @@ import java.util.Set;
    }
    }


    @Override
    @Override
    public void report(List<String> dexPaths) {
    public void report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths) {
        if (dexPaths.isEmpty()) {
        if (classLoadersChain.size() != classPaths.size()) {
            Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch");
            return;
            return;
        }
        }
        if (classPaths.isEmpty()) {
            Slog.wtf(TAG, "Bad call to DexLoadReporter: empty dex paths");
            return;
        }

        // The first element of classPaths is the list of dex files that should be registered.
        // The classpath is represented as a list of dex files separated by File.pathSeparator.
        String[] dexPathsForRegistration = classPaths.get(0).split(File.pathSeparator);
        if (dexPathsForRegistration.length == 0) {
            // No dex files to register.
            return;
        }

        // Notify the package manager about the dex loads unconditionally.
        // Notify the package manager about the dex loads unconditionally.
        // The load might be for either a primary or secondary dex file.
        // The load might be for either a primary or secondary dex file.
        notifyPackageManager(dexPaths);
        notifyPackageManager(classLoadersChain, classPaths);
        // Check for secondary dex files and register them for profiling if
        // Check for secondary dex files and register them for profiling if possible.
        // possible.
        // Note that we only register the dex paths belonging to the first class loader.
        registerSecondaryDexForProfiling(dexPaths);
        registerSecondaryDexForProfiling(dexPathsForRegistration);
    }
    }


    private void notifyPackageManager(List<String> dexPaths) {
    private void notifyPackageManager(List<BaseDexClassLoader> classLoadersChain,
            List<String> classPaths) {
        // Get the class loader names for the binder call.
        List<String> classLoadersNames = new ArrayList<>(classPaths.size());
        for (BaseDexClassLoader bdc : classLoadersChain) {
            classLoadersNames.add(bdc.getClass().getName());
        }
        String packageName = ActivityThread.currentPackageName();
        String packageName = ActivityThread.currentPackageName();
        try {
        try {
            ActivityThread.getPackageManager().notifyDexLoad(
            ActivityThread.getPackageManager().notifyDexLoad(
                    packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet());
                    packageName, classLoadersNames, classPaths,
                    VMRuntime.getRuntime().vmInstructionSet());
        } catch (RemoteException re) {
        } catch (RemoteException re) {
            Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
            Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
        }
        }
    }
    }


    private void registerSecondaryDexForProfiling(List<String> dexPaths) {
    private void registerSecondaryDexForProfiling(String[] dexPaths) {
        if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
        if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
            return;
            return;
        }
        }
+11 −3
Original line number Original line Diff line number Diff line
@@ -469,11 +469,19 @@ interface IPackageManager {
     * Notify the package manager that a list of dex files have been loaded.
     * Notify the package manager that a list of dex files have been loaded.
     *
     *
     * @param loadingPackageName the name of the package who performs the load
     * @param loadingPackageName the name of the package who performs the load
     * @param dexPats the list of the dex files paths that have been loaded
     * @param classLoadersNames the names of the class loaders present in the loading chain. The
     *    list encodes the class loader chain in the natural order. The first class loader has
     *    the second one as its parent and so on. The dex files present in the class path of the
     *    first class loader will be recorded in the usage file.
     * @param classPaths the class paths corresponding to the class loaders names from
     *     {@param classLoadersNames}. The the first element corresponds to the first class loader
     *     and so on. A classpath is represented as a list of dex files separated by
     *     {@code File.pathSeparator}.
     *     The dex files found in the first class path will be recorded in the usage file.
     * @param loaderIsa the ISA of the loader process
     * @param loaderIsa the ISA of the loader process
     */
     */
    oneway void notifyDexLoad(String loadingPackageName, in List<String> dexPaths,
    oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames,
            String loaderIsa);
            in List<String> classPaths, String loaderIsa);


    /**
    /**
     * Register an application dex module with the package manager.
     * Register an application dex module with the package manager.
+30 −12
Original line number Original line Diff line number Diff line
@@ -33,12 +33,12 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.DexoptUtils;
import com.android.server.pm.dex.DexoptUtils;
import com.android.server.pm.dex.PackageDexUsage;


import java.io.File;
import java.io.File;
import java.io.IOException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Set;


import dalvik.system.DexFile;
import dalvik.system.DexFile;


@@ -251,13 +251,12 @@ public class PackageDexOptimizer {
     * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
     * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
     * that seems wasteful.
     * that seems wasteful.
     */
     */
    public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
    public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
            String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) {
            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
        synchronized (mInstallLock) {
        synchronized (mInstallLock) {
            final long acquireTime = acquireWakeLockLI(info.uid);
            final long acquireTime = acquireWakeLockLI(info.uid);
            try {
            try {
                return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
                return dexOptSecondaryDexPathLI(info, path, dexUseInfo, options);
                        isUsedByOtherApps, downgrade);
            } finally {
            } finally {
                releaseWakeLockLI(acquireTime);
                releaseWakeLockLI(acquireTime);
            }
            }
@@ -298,9 +297,16 @@ public class PackageDexOptimizer {
    }
    }


    @GuardedBy("mInstallLock")
    @GuardedBy("mInstallLock")
    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
            String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) {
            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
        compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
        if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
            // We are asked to optimize only the dex files used by other apps and this is not
            // on of them: skip it.
            return DEX_OPT_SKIPPED;
        }

        String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),
                dexUseInfo.isUsedByOtherApps());
        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
        // Secondary dex files are currently not compiled at boot.
        // Secondary dex files are currently not compiled at boot.
        int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
        int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
@@ -317,20 +323,32 @@ public class PackageDexOptimizer {
            return DEX_OPT_FAILED;
            return DEX_OPT_FAILED;
        }
        }
        Log.d(TAG, "Running dexopt on: " + path
        Log.d(TAG, "Running dexopt on: " + path
                + " pkg=" + info.packageName + " isa=" + isas
                + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
                + " target-filter=" + compilerFilter);
                + " target-filter=" + compilerFilter);


        String classLoaderContext;
        if (dexUseInfo.isUnknownClassLoaderContext() ||
                dexUseInfo.isUnsupportedClassLoaderContext() ||
                dexUseInfo.isVariableClassLoaderContext()) {
            // If we have an unknown (not yet set), unsupported (custom class loaders), or a
            // variable class loader chain, compile without a context and mark the oat file with
            // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
            // TODO(calin): We should just extract in this case.
            classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
        } else {
            classLoaderContext = dexUseInfo.getClassLoaderContext();
        }
        try {
        try {
            for (String isa : isas) {
            for (String isa : dexUseInfo.getLoaderIsas()) {
                // Reuse the same dexopt path as for the primary apks. We don't need all the
                // Reuse the same dexopt path as for the primary apks. We don't need all the
                // arguments as some (dexopNeeded and oatDir) will be computed by installd because
                // arguments as some (dexopNeeded and oatDir) will be computed by installd because
                // system server cannot read untrusted app content.
                // system server cannot read untrusted app content.
                // TODO(calin): maybe add a separate call.
                // TODO(calin): maybe add a separate call.
                mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                        /*oatDir*/ null, dexoptFlags,
                        /*oatDir*/ null, dexoptFlags,
                        compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser,
                        compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
                        downgrade);
                        options.isDowngrade());
            }
            }


            return DEX_OPT_PERFORMED;
            return DEX_OPT_PERFORMED;
+3 −2
Original line number Original line Diff line number Diff line
@@ -9434,7 +9434,8 @@ public class PackageManagerService extends IPackageManager.Stub
    }
    }
    @Override
    @Override
    public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
    public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames,
            List<String> classPaths, String loaderIsa) {
        int userId = UserHandle.getCallingUserId();
        int userId = UserHandle.getCallingUserId();
        ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
        ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
        if (ai == null) {
        if (ai == null) {
@@ -9442,7 +9443,7 @@ public class PackageManagerService extends IPackageManager.Stub
                + loadingPackageName + ", user=" + userId);
                + loadingPackageName + ", user=" + userId);
            return;
            return;
        }
        }
        mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId);
        mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
    }
    }
    @Override
    @Override
+56 −26
Original line number Original line Diff line number Diff line
@@ -97,29 +97,55 @@ public class DexManager {
     * return as fast as possible.
     * return as fast as possible.
     *
     *
     * @param loadingAppInfo the package performing the load
     * @param loadingAppInfo the package performing the load
     * @param dexPaths the list of dex files being loaded
     * @param classLoadersNames the names of the class loaders present in the loading chain. The
     *    list encodes the class loader chain in the natural order. The first class loader has
     *    the second one as its parent and so on. The dex files present in the class path of the
     *    first class loader will be recorded in the usage file.
     * @param classPaths the class paths corresponding to the class loaders names from
     *     {@param classLoadersNames}. The the first element corresponds to the first class loader
     *     and so on. A classpath is represented as a list of dex files separated by
     *     {@code File.pathSeparator}.
     *     The dex files found in the first class path will be recorded in the usage file.
     * @param loaderIsa the ISA of the app loading the dex files
     * @param loaderIsa the ISA of the app loading the dex files
     * @param loaderUserId the user id which runs the code loading the dex files
     * @param loaderUserId the user id which runs the code loading the dex files
     */
     */
    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths,
    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
            String loaderIsa, int loaderUserId) {
            List<String> classPaths, String loaderIsa, int loaderUserId) {
        try {
        try {
            notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId);
            notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
                    loaderUserId);
        } catch (Exception e) {
        } catch (Exception e) {
            Slog.w(TAG, "Exception while notifying dex load for package " +
            Slog.w(TAG, "Exception while notifying dex load for package " +
                    loadingAppInfo.packageName, e);
                    loadingAppInfo.packageName, e);
        }
        }
    }
    }


    private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths,
    private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
            String loaderIsa, int loaderUserId) {
            List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
            int loaderUserId) {
        if (classLoaderNames.size() != classPaths.size()) {
            Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
            return;
        }
        if (classLoaderNames.isEmpty()) {
            Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
            return;
        }
        if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
        if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
            Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " +
            Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
                    loaderIsa + "?");
                    loaderIsa + "?");
            return;
            return;
        }
        }


        for (String dexPath : dexPaths) {
        // The classpath is represented as a list of dex files separated by File.pathSeparator.
        String[] dexPathsToRegister = classPaths.get(0).split(File.pathSeparator);

        // Encode the class loader contexts for the dexPathsToRegister.
        String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
                classLoaderNames, classPaths);

        int dexPathIndex = 0;
        for (String dexPath : dexPathsToRegister) {
            // Find the owning package name.
            // Find the owning package name.
            DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
            DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);


@@ -147,24 +173,25 @@ public class DexManager {
                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
                // or UsedBytOtherApps), record will return true and we trigger an async write
                // or UsedBytOtherApps), record will return true and we trigger an async write
                // to disk to make sure we don't loose the data in case of a reboot.
                // to disk to make sure we don't loose the data in case of a reboot.

                // A null classLoaderContexts means that there are unsupported class loaders in the
                // chain.
                String classLoaderContext = classLoaderContexts == null
                        ? PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT
                        : classLoaderContexts[dexPathIndex];
                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
                        loadingAppInfo.packageName)) {
                        loadingAppInfo.packageName, classLoaderContext)) {
                    mPackageDexUsage.maybeWriteAsync();
                    mPackageDexUsage.maybeWriteAsync();
                }
                }
            } else {
            } else {
                // This can happen in a few situations:
                // - bogus dex loads
                // - recent installs/uninstalls that we didn't detect.
                // - new installed splits
                // If we can't find the owner of the dex we simply do not track it. The impact is
                // If we can't find the owner of the dex we simply do not track it. The impact is
                // that the dex file will not be considered for offline optimizations.
                // that the dex file will not be considered for offline optimizations.
                // TODO(calin): add hooks for move/uninstall notifications to
                // capture package moves or obsolete packages.
                if (DEBUG) {
                if (DEBUG) {
                    Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
                    Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
                }
                }
            }
            }
            dexPathIndex++;
        }
        }
    }
    }


@@ -328,10 +355,8 @@ public class DexManager {
        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
            String dexPath = entry.getKey();
            String dexPath = entry.getKey();
            DexUseInfo dexUseInfo = entry.getValue();
            DexUseInfo dexUseInfo = entry.getValue();
            if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {

                continue;
            PackageInfo pkg;
            }
            PackageInfo pkg = null;
            try {
            try {
                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
                    dexUseInfo.getOwnerUserId());
                    dexUseInfo.getOwnerUserId());
@@ -350,8 +375,7 @@ public class DexManager {
            }
            }


            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
                    dexUseInfo.getLoaderIsas(), options.getCompilerFilter(),
                    dexUseInfo, options);
                    dexUseInfo.isUsedByOtherApps(), options.isDowngrade());
            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
        }
        }
        return success;
        return success;
@@ -434,6 +458,8 @@ public class DexManager {
        }
        }
    }
    }


    // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
    // compilation happening here will use a pessimistic context.
    public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
    public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
            boolean isUsedByOtherApps, int userId) {
            boolean isUsedByOtherApps, int userId) {
        // Find the owning package record.
        // Find the owning package record.
@@ -452,12 +478,11 @@ public class DexManager {


        // We found the package. Now record the usage for all declared ISAs.
        // We found the package. Now record the usage for all declared ISAs.
        boolean update = false;
        boolean update = false;
        Set<String> isas = new HashSet<>();
        for (String isa : getAppDexInstructionSets(info)) {
        for (String isa : getAppDexInstructionSets(info)) {
            isas.add(isa);
            boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
            boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
                    dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
                    dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
                    searchResult.mOwningPackageName);
                    searchResult.mOwningPackageName,
                    PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT);
            update |= newUpdate;
            update |= newUpdate;
        }
        }
        if (update) {
        if (update) {
@@ -467,8 +492,13 @@ public class DexManager {
        // Try to optimize the package according to the install reason.
        // Try to optimize the package according to the install reason.
        String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
        String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
                PackageManagerService.REASON_INSTALL);
                PackageManagerService.REASON_INSTALL);
        int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas,
        DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
                compilerFilter, isUsedByOtherApps, /* downgrade */ false);
                .getDexUseInfoMap().get(dexPath);

        DexoptOptions options = new DexoptOptions(info.packageName, compilerFilter, /*flags*/0);

        int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
                options);


        // If we fail to optimize the package log an error but don't propagate the error
        // If we fail to optimize the package log an error but don't propagate the error
        // back to the app. The app cannot do much about it and the background job
        // back to the app. The app cannot do much about it and the background job
Loading