diff options
6 files changed, 100 insertions, 137 deletions
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java index 5bc999240a66..229bee55e911 100644 --- a/core/java/android/app/DexLoadReporter.java +++ b/core/java/android/app/DexLoadReporter.java @@ -28,8 +28,9 @@ import dalvik.system.VMRuntime; import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.HashSet; -import java.util.Map; +import java.util.List; import java.util.Set; /** @@ -86,32 +87,50 @@ import java.util.Set; } @Override - public void report(Map<String, String> classLoaderContextMap) { - if (classLoaderContextMap.isEmpty()) { - Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap"); + public void report(List<ClassLoader> classLoadersChain, List<String> classPaths) { + if (classLoadersChain.size() != classPaths.size()) { + Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch"); + 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. // The load might be for either a primary or secondary dex file. - notifyPackageManager(classLoaderContextMap); + notifyPackageManager(classLoadersChain, classPaths); // Check for secondary dex files and register them for profiling if possible. // Note that we only register the dex paths belonging to the first class loader. - registerSecondaryDexForProfiling(classLoaderContextMap.keySet()); + registerSecondaryDexForProfiling(dexPathsForRegistration); } - private void notifyPackageManager(Map<String, String> classLoaderContextMap) { + private void notifyPackageManager(List<ClassLoader> classLoadersChain, + List<String> classPaths) { // Get the class loader names for the binder call. + List<String> classLoadersNames = new ArrayList<>(classPaths.size()); + for (ClassLoader classLoader : classLoadersChain) { + classLoadersNames.add(classLoader.getClass().getName()); + } String packageName = ActivityThread.currentPackageName(); try { - ActivityThread.getPackageManager().notifyDexLoad(packageName, - classLoaderContextMap, VMRuntime.getRuntime().vmInstructionSet()); + ActivityThread.getPackageManager().notifyDexLoad( + packageName, classLoadersNames, classPaths, + VMRuntime.getRuntime().vmInstructionSet()); } catch (RemoteException re) { Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); } } - private void registerSecondaryDexForProfiling(Set<String> dexPaths) { + private void registerSecondaryDexForProfiling(String[] dexPaths) { if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { return; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 6d051e47c716..429a6e51ccb4 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -529,12 +529,19 @@ interface IPackageManager { * 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 classLoaderContextMap a map from file paths to dex files that have been loaded to - * the class loader context that was used to load them. + * @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}, or null if the class loader's classpath is not known. + * The dex files found in the first class path will be recorded in the usage file. * @param loaderIsa the ISA of the loader process */ - oneway void notifyDexLoad(String loadingPackageName, - in Map<String, String> classLoaderContextMap, String loaderIsa); + oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames, + in List<String> classPaths, String loaderIsa); /** * Register an application dex module with the package manager. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 5be7cb4ff962..604cdd8355cd 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -9792,8 +9792,8 @@ public class PackageManagerService extends IPackageManager.Stub } @Override - public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap, - String loaderIsa) { + public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames, + List<String> classPaths, String loaderIsa) { int userId = UserHandle.getCallingUserId(); ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId); if (ai == null) { @@ -9801,7 +9801,7 @@ public class PackageManagerService extends IPackageManager.Stub + loadingPackageName + ", user=" + userId); return; } - mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId); + mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId); } @Override diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 441fa75cd912..9e86a4bd2880 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -44,8 +44,6 @@ import com.android.server.pm.PackageDexOptimizer; import com.android.server.pm.PackageManagerService; import com.android.server.pm.PackageManagerServiceUtils; -import dalvik.system.VMRuntime; - import java.io.File; import java.io.IOException; import java.util.Arrays; @@ -145,15 +143,22 @@ public class DexManager { * return as fast as possible. * * @param loadingAppInfo the package performing the load - * @param classLoaderContextMap a map from file paths to dex files that have been loaded to - * the class loader context that was used to load them. + * @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}, or null if the class loader's classpath is not known. + * 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 loaderUserId the user id which runs the code loading the dex files */ - public void notifyDexLoad(ApplicationInfo loadingAppInfo, - Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId) { + public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames, + List<String> classPaths, String loaderIsa, int loaderUserId) { try { - notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa, + notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa, loaderUserId); } catch (Exception e) { Slog.w(TAG, "Exception while notifying dex load for package " + @@ -163,23 +168,46 @@ public class DexManager { @VisibleForTesting /*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, - Map<String, String> classLoaderContextMap, String loaderIsa, + List<String> classLoaderNames, List<String> classPaths, String loaderIsa, int loaderUserId) { - if (classLoaderContextMap == null) { + if (classLoaderNames.size() != classPaths.size()) { + Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size"); return; } - if (classLoaderContextMap.isEmpty()) { + if (classLoaderNames.isEmpty()) { Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty"); return; } if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { - Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet() - + " in unsupported ISA: " + loaderIsa + "?"); + Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " + + loaderIsa + "?"); return; } - for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) { - String dexPath = mapping.getKey(); + // The first classpath should never be null because the first classloader + // should always be an instance of BaseDexClassLoader. + String firstClassPath = classPaths.get(0); + if (firstClassPath == null) { + return; + } + // The classpath is represented as a list of dex files separated by File.pathSeparator. + String[] dexPathsToRegister = firstClassPath.split(File.pathSeparator); + + // Encode the class loader contexts for the dexPathsToRegister. + String[] classLoaderContexts = DexoptUtils.processContextForDexLoad( + classLoaderNames, classPaths); + + // A null classLoaderContexts means that there are unsupported class loaders in the + // chain. + if (classLoaderContexts == null) { + if (DEBUG) { + Slog.i(TAG, loadingAppInfo.packageName + + " uses unsupported class loader in " + classLoaderNames); + } + } + + int dexPathIndex = 0; + for (String dexPath : dexPathsToRegister) { // Find the owning package name. DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); @@ -201,6 +229,7 @@ public class DexManager { // If the dex file is the primary apk (or a split) and not isUsedByOtherApps // do not record it. This case does not bring any new usable information // and can be safely skipped. + dexPathIndex++; continue; } @@ -210,13 +239,13 @@ public class DexManager { searchResult.mOwningPackageName, loadingAppInfo.packageName); } - String classLoaderContext = mapping.getValue(); - if (classLoaderContext != null - && VMRuntime.isValidClassLoaderContext(classLoaderContext)) { + if (classLoaderContexts != null) { + // Record dex file usage. If the current usage is a new pattern (e.g. new // secondary, or UsedByOtherApps), 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. + String classLoaderContext = classLoaderContexts[dexPathIndex]; if (mPackageDexUsage.record(searchResult.mOwningPackageName, dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, loadingAppInfo.packageName, classLoaderContext)) { @@ -230,6 +259,7 @@ public class DexManager { Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); } } + dexPathIndex++; } } diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 08763e729c71..e68c2386e03b 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -83,9 +83,8 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { "=UnknownClassLoaderContext="; // The marker used for unsupported class loader contexts (no longer written, may occur in old - // files so discarded on read). Note: this matches - // ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime. - /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = + // files so discarded on read). + private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT = "=UnsupportedClassLoaderContext="; /** @@ -134,9 +133,6 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { if (classLoaderContext == null) { throw new IllegalArgumentException("Null classLoaderContext"); } - if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) { - return false; - } synchronized (mPackageUseInfoMap) { PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName); @@ -847,11 +843,10 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages); String oldClassLoaderContext = mClassLoaderContext; - if (isUnknownOrUnsupportedContext(mClassLoaderContext)) { + if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) { // Can happen if we read a previous version. mClassLoaderContext = dexUseInfo.mClassLoaderContext; - } else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext) - && !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { + } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) { // We detected a context change. mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT; } @@ -862,13 +857,6 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { || !Objects.equals(oldClassLoaderContext, mClassLoaderContext); } - private static boolean isUnknownOrUnsupportedContext(String context) { - // TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases - // into UNSUPPORTED_CLASS_LOADER_CONTEXT. - return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context) - || UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context); - } - public boolean isUsedByOtherApps() { return mIsUsedByOtherApps; } @@ -890,7 +878,7 @@ public class PackageDexUsage extends AbstractStatsBase<Void> { public boolean isUnknownClassLoaderContext() { // The class loader context may be unknown if we loaded the data from a previous version // which didn't save the context. - return isUnknownOrUnsupportedContext(mClassLoaderContext); + return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext); } public boolean isVariableClassLoaderContext() { diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index cb20b65c9f9b..a4ba056b96a8 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -532,61 +532,6 @@ public class DexManagerTests { assertHasDclInfo(mBarUser0, mBarUser0, secondaries); } - @Test - public void testPrimaryAndSecondaryDexLoad() { - // Foo loads both primary and secondary dexes - List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); - List<String> fooDexes = new ArrayList<>(mFooUser0.getBaseAndSplitDexPaths()); - int primaryCount = fooDexes.size(); - fooDexes.addAll(fooSecondaries); - - notifyDexLoad(mFooUser0, fooDexes, mUser0); - - PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertIsUsedByOtherApps(mFooUser0, pui, false); - assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); - - // Below we want to verify that the secondary dex files within fooDexes have been correctly - // reported and their class loader contexts were correctly recorded. - // - // In order to achieve this we first use DexoptUtils.processContextForDexLoad to compute the - // class loader contexts for all the dex files. - String[] allClassLoaderContexts = DexoptUtils.processContextForDexLoad( - Arrays.asList(mFooUser0.mClassLoader), - Arrays.asList(String.join(File.pathSeparator, fooDexes))); - // Next we filter out the class loader contexts corresponding to non-secondary dex files. - String[] secondaryClassLoaderContexts = Arrays.copyOfRange(allClassLoaderContexts, - primaryCount, allClassLoaderContexts.length); - assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0, - secondaryClassLoaderContexts); - - assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); - } - - @Test - public void testNotifySecondary_withSharedLibrary() { - // Foo loads its own secondary files. - List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); - - String contextSuffix = "{PCL[/system/framework/org.apache.http.legacy.jar]}"; - String[] expectedContexts = DexoptUtils.processContextForDexLoad( - Arrays.asList(mFooUser0.mClassLoader), - Arrays.asList(String.join(File.pathSeparator, fooSecondaries))); - for (int i = 0; i < expectedContexts.length; i++) { - expectedContexts[i] += contextSuffix; - } - - notifyDexLoad(mFooUser0, fooSecondaries, expectedContexts, mUser0); - - PackageUseInfo pui = getPackageUseInfo(mFooUser0); - assertIsUsedByOtherApps(mFooUser0, pui, false); - assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); - assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0, - expectedContexts); - - assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); - } - private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, String[] expectedContexts) { @@ -627,43 +572,17 @@ public class DexManagerTests { // By default, assume a single class loader in the chain. // This makes writing tests much easier. List<String> classLoaders = Arrays.asList(testData.mClassLoader); - List<String> classPaths = dexPaths != null - ? Arrays.<String>asList(String.join(File.pathSeparator, dexPaths)) : null; + List<String> classPaths = (dexPaths == null) + ? Arrays.asList((String) null) + : Arrays.asList(String.join(File.pathSeparator, dexPaths)); notifyDexLoad(testData, classLoaders, classPaths, loaderUserId); } private void notifyDexLoad(TestData testData, List<String> classLoaders, List<String> classPaths, int loaderUserId) { - String[] classLoaderContexts = computeClassLoaderContexts(classLoaders, classPaths); // We call the internal function so any exceptions thrown cause test failures. - List<String> dexPaths = classPaths != null - ? Arrays.asList(classPaths.get(0).split(File.pathSeparator)) : Arrays.asList(); - notifyDexLoad(testData, dexPaths, classLoaderContexts, loaderUserId); - } - - private void notifyDexLoad(TestData testData, List<String> dexPaths, - String[] classLoaderContexts, int loaderUserId) { - assertTrue(dexPaths.size() == classLoaderContexts.length); - HashMap<String, String> dexPathMapping = new HashMap<>(dexPaths.size()); - for (int i = 0; i < dexPaths.size(); i++) { - dexPathMapping.put(dexPaths.get(i), classLoaderContexts != null - ? classLoaderContexts[i] : PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT); - } - mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, dexPathMapping, - testData.mLoaderIsa, loaderUserId); - } - - private String[] computeClassLoaderContexts(List<String> classLoaders, - List<String> classPaths) { - if (classPaths == null) { - return new String[0]; - } - String[] results = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); - if (results == null) { - results = new String[classPaths.get(0).split(File.pathSeparator).length]; - Arrays.fill(results, PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT); - } - return results; + mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, classLoaders, + classPaths, testData.mLoaderIsa, loaderUserId); } private PackageUseInfo getPackageUseInfo(TestData testData) { |