diff options
3 files changed, 182 insertions, 48 deletions
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 3a74ab51e9c7..36b7269576b6 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -16,6 +16,11 @@ package com.android.server.pm.dex; +import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; +import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; +import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; + import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; @@ -26,9 +31,9 @@ import android.database.ContentObserver; import android.os.Build; import android.os.FileUtils; import android.os.RemoteException; -import android.os.storage.StorageManager; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.storage.StorageManager; import android.provider.Settings.Global; import android.util.Log; import android.util.Slog; @@ -48,18 +53,14 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; -import java.util.List; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; -import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; -import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; -import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; - /** * This class keeps track of how dex files are used. * Every time it gets a notification about a dex file being loaded it tracks @@ -89,6 +90,12 @@ public class DexManager { // encode and save the dex usage data. private final PackageDexUsage mPackageDexUsage; + // PackageDynamicCodeLoading handles recording of dynamic code loading - + // which is similar to PackageDexUsage but records a different aspect of the data. + // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't + // record class loaders or ISAs.) + private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; + private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; private final Object mInstallLock; @@ -126,14 +133,15 @@ public class DexManager { public DexManager(Context context, IPackageManager pms, PackageDexOptimizer pdo, Installer installer, Object installLock, Listener listener) { - mContext = context; - mPackageCodeLocationsCache = new HashMap<>(); - mPackageDexUsage = new PackageDexUsage(); - mPackageManager = pms; - mPackageDexOptimizer = pdo; - mInstaller = installer; - mInstallLock = installLock; - mListener = listener; + mContext = context; + mPackageCodeLocationsCache = new HashMap<>(); + mPackageDexUsage = new PackageDexUsage(); + mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); + mPackageManager = pms; + mPackageDexOptimizer = pdo; + mInstaller = installer; + mInstallLock = installLock; + mListener = listener; } public void systemReady() { @@ -207,7 +215,6 @@ public class DexManager { Slog.i(TAG, loadingAppInfo.packageName + " uses unsupported class loader in " + classLoaderNames); } - return; } int dexPathIndex = 0; @@ -236,15 +243,24 @@ public class DexManager { continue; } - // 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. + if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath, + PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, + loadingAppInfo.packageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + + if (classLoaderContexts != null) { - String classLoaderContext = classLoaderContexts[dexPathIndex]; - if (mPackageDexUsage.record(searchResult.mOwningPackageName, - dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit, - loadingAppInfo.packageName, classLoaderContext)) { - mPackageDexUsage.maybeWriteAsync(); + // 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)) { + mPackageDexUsage.maybeWriteAsync(); + } } } else { // If we can't find the owner of the dex we simply do not track it. The impact is @@ -268,8 +284,8 @@ public class DexManager { loadInternal(existingPackages); } catch (Exception e) { mPackageDexUsage.clear(); - Slog.w(TAG, "Exception while loading package dex usage. " + - "Starting with a fresh state.", e); + mPackageDynamicCodeLoading.clear(); + Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); } } @@ -311,15 +327,24 @@ public class DexManager { * all usage information for the package will be removed. */ public void notifyPackageDataDestroyed(String packageName, int userId) { - boolean updated = userId == UserHandle.USER_ALL - ? mPackageDexUsage.removePackage(packageName) - : mPackageDexUsage.removeUserPackage(packageName, userId); // In case there was an update, write the package use info to disk async. - // Note that we do the writing here and not in PackageDexUsage in order to be + // Note that we do the writing here and not in the lower level classes in order to be // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs // multiple updates in PackageDexUsage before writing it). - if (updated) { - mPackageDexUsage.maybeWriteAsync(); + if (userId == UserHandle.USER_ALL) { + if (mPackageDexUsage.removePackage(packageName)) { + mPackageDexUsage.maybeWriteAsync(); + } + if (mPackageDynamicCodeLoading.removePackage(packageName)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } + } else { + if (mPackageDexUsage.removeUserPackage(packageName, userId)) { + mPackageDexUsage.maybeWriteAsync(); + } + if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { + mPackageDynamicCodeLoading.maybeWriteAsync(); + } } } @@ -388,8 +413,23 @@ public class DexManager { } } - mPackageDexUsage.read(); - mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + try { + mPackageDexUsage.read(); + mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); + } catch (Exception e) { + mPackageDexUsage.clear(); + Slog.w(TAG, "Exception while loading package dex usage. " + + "Starting with a fresh state.", e); + } + + try { + mPackageDynamicCodeLoading.read(); + mPackageDynamicCodeLoading.syncData(packageToUsersMap); + } catch (Exception e) { + mPackageDynamicCodeLoading.clear(); + Slog.w(TAG, "Exception while loading package dynamic code usage. " + + "Starting with a fresh state.", e); + } } /** @@ -415,10 +455,16 @@ public class DexManager { * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try * to access the package use. */ + @VisibleForTesting /*package*/ boolean hasInfoOnPackage(String packageName) { return mPackageDexUsage.getPackageUseInfo(packageName) != null; } + @VisibleForTesting + /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { + return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); + } + /** * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped @@ -652,7 +698,7 @@ public class DexManager { // to load dex files through it. try { String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); - if (dexPathReal != dexPath) { + if (!dexPath.equals(dexPathReal)) { Slog.d(TAG, "Dex loaded with symlink. dexPath=" + dexPath + " dexPathReal=" + dexPathReal); } @@ -675,6 +721,7 @@ public class DexManager { */ public void writePackageDexUsageNow() { mPackageDexUsage.writeNow(); + mPackageDynamicCodeLoading.writeNow(); } private void registerSettingObserver() { diff --git a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java index c2cb861a13fe..f74aa1d69bc8 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +++ b/services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java @@ -96,7 +96,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { * @param ownerUserId the user id which runs the code loading the file * @param loadingPackageName the package performing the load * @return whether new information has been recorded - * @throw IllegalArgumentException if clearly invalid information is detected + * @throws IllegalArgumentException if clearly invalid information is detected */ boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, String loadingPackageName) { 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 dad7b93e822e..fd07cb046fb5 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 @@ -18,10 +18,14 @@ package com.android.server.pm.dex; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; +import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -41,6 +45,10 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.Installer; +import dalvik.system.DelegateLastClassLoader; +import dalvik.system.PathClassLoader; +import dalvik.system.VMRuntime; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -50,10 +58,6 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; -import dalvik.system.DelegateLastClassLoader; -import dalvik.system.PathClassLoader; -import dalvik.system.VMRuntime; - import java.io.File; import java.util.ArrayList; import java.util.Arrays; @@ -129,6 +133,9 @@ public class DexManagerTests { // Package is not used by others, so we should get nothing back. assertNoUseInfo(mFooUser0); + + // A package loading its own code is not stored as DCL. + assertNoDclInfo(mFooUser0); } @Test @@ -140,6 +147,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); + + assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths()); } @Test @@ -152,6 +161,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + + assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test @@ -164,6 +175,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); + + assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries); } @Test @@ -200,9 +213,10 @@ public class DexManagerTests { } @Test - public void testPackageUseInfoNotFound() { + public void testNoNotify() { // Assert we don't get back data we did not previously record. assertNoUseInfo(mFooUser0); + assertNoDclInfo(mFooUser0); } @Test @@ -210,6 +224,7 @@ public class DexManagerTests { // Notifying with an invalid ISA should be ignored. notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); assertNoUseInfo(mInvalidIsa); + assertNoDclInfo(mInvalidIsa); } @Test @@ -218,6 +233,7 @@ public class DexManagerTests { // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); assertNoUseInfo(mDoesNotExist); + assertNoDclInfo(mDoesNotExist); } @Test @@ -226,6 +242,8 @@ public class DexManagerTests { // Request should be ignored. notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); assertNoUseInfo(mBarUser1); + + assertNoDclInfo(mBarUser1); } @Test @@ -235,6 +253,10 @@ public class DexManagerTests { // still check that nothing goes unexpected in DexManager. notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); assertNoUseInfo(mBarUser1); + assertNoUseInfo(mFooUser0); + + assertNoDclInfo(mBarUser1); + assertNoDclInfo(mFooUser0); } @Test @@ -247,6 +269,7 @@ public class DexManagerTests { // is trying to load something from it we should not find it. notifyDexLoad(mFooUser0, newSecondaries, mUser0); assertNoUseInfo(newPackage); + assertNoDclInfo(newPackage); // Notify about newPackage install and let mFoo load its dexes. mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0); @@ -257,6 +280,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); + assertHasDclInfo(newPackage, mFooUser0, newSecondaries); } @Test @@ -273,6 +297,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); + assertHasDclInfo(newPackage, newPackage, newSecondaries); } @Test @@ -305,6 +330,7 @@ public class DexManagerTests { // We shouldn't find yet the new split as we didn't notify the package update. notifyDexLoad(mFooUser0, newSplits, mUser0); assertNoUseInfo(mBarUser0); + assertNoDclInfo(mBarUser0); // Notify that bar is updated. splitSourceDirs will contain the updated path. mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), @@ -314,8 +340,8 @@ public class DexManagerTests { // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. notifyDexLoad(mFooUser0, newSplits, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); - assertNotNull(pui); assertIsUsedByOtherApps(newSplits, pui, true); + assertHasDclInfo(mBarUser0, mFooUser0, newSplits); } @Test @@ -326,11 +352,15 @@ public class DexManagerTests { mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); - // Bar should not be around since it was removed for all users. + // Data for user 1 should still be present PackageUseInfo pui = getPackageUseInfo(mBarUser1); - assertNotNull(pui); assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), /*isUsedByOtherApps*/false, mUser1); + assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths()); + + // But not user 0 + assertNoUseInfo(mBarUser0, mUser0); + assertNoDclInfo(mBarUser0, mUser0); } @Test @@ -349,6 +379,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mFooUser0); assertIsUsedByOtherApps(mFooUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); + + assertNoDclInfo(mFooUser0); } @Test @@ -362,6 +394,7 @@ public class DexManagerTests { // Foo should not be around since all its secondary dex info were deleted // and it is not used by other apps. assertNoUseInfo(mFooUser0); + assertNoDclInfo(mFooUser0); } @Test @@ -374,6 +407,7 @@ public class DexManagerTests { // Bar should not be around since it was removed for all users. assertNoUseInfo(mBarUser0); + assertNoDclInfo(mBarUser0); } @Test @@ -383,6 +417,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); // The dex file should not be recognized as a package. assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); + assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex)); } @Test @@ -395,6 +430,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); + + assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test @@ -402,7 +439,12 @@ public class DexManagerTests { List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); + // We don't record the dex usage assertNoUseInfo(mBarUser0UnsupportedClassLoader); + + // But we do record this as an intance of dynamic code loading + assertHasDclInfo( + mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries); } @Test @@ -414,6 +456,8 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0); assertNoUseInfo(mBarUser0); + + assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths()); } @Test @@ -421,6 +465,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, null, mUser0); assertNoUseInfo(mBarUser0); + assertNoDclInfo(mBarUser0); } @Test @@ -455,12 +500,14 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + assertHasDclInfo(mBarUser0, mBarUser0, secondaries); // Record bar secondaries again with an unsupported class loader. This should not change the // context. notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); + assertHasDclInfo(mBarUser0, mBarUser0, secondaries); } @Test @@ -533,13 +580,53 @@ public class DexManagerTests { private PackageUseInfo getPackageUseInfo(TestData testData) { assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName())); - return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); + PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); + assertNotNull(pui); + return pui; } private void assertNoUseInfo(TestData testData) { assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName())); } + private void assertNoUseInfo(TestData testData, int userId) { + if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) { + return; + } + PackageUseInfo pui = getPackageUseInfo(testData); + for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) { + assertNotEquals(userId, dexUseInfo.getOwnerUserId()); + } + } + + private void assertNoDclInfo(TestData testData) { + assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName())); + } + + private void assertNoDclInfo(TestData testData, int userId) { + PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()); + if (info == null) { + return; + } + + for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) { + assertNotEquals(userId, fileInfo.mUserId); + } + } + + private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) { + PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName()); + assertNotNull("No DCL data for owner " + owner.getPackageName(), info); + for (String path : paths) { + DynamicCodeFile fileInfo = info.mFileUsageMap.get(path); + assertNotNull("No DCL data for path " + path, fileInfo); + assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType); + assertEquals(owner.mUserId, fileInfo.mUserId); + assertTrue("No DCL data for loader " + loader.getPackageName(), + fileInfo.mLoadingPackages.contains(loader.getPackageName())); + } + } + private static PackageInfo getMockPackageInfo(String packageName, int userId) { PackageInfo pi = new PackageInfo(); pi.packageName = packageName; @@ -563,11 +650,13 @@ public class DexManagerTests { private final PackageInfo mPackageInfo; private final String mLoaderIsa; private final String mClassLoader; + private final int mUserId; private TestData(String packageName, String loaderIsa, int userId, String classLoader) { mPackageInfo = getMockPackageInfo(packageName, userId); mLoaderIsa = loaderIsa; mClassLoader = classLoader; + mUserId = userId; } private TestData(String packageName, String loaderIsa, int userId) { @@ -603,9 +692,7 @@ public class DexManagerTests { List<String> getBaseAndSplitDexPaths() { List<String> paths = new ArrayList<>(); paths.add(mPackageInfo.applicationInfo.sourceDir); - for (String split : mPackageInfo.applicationInfo.splitSourceDirs) { - paths.add(split); - } + Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs); return paths; } |