Merge "Revert "Revert "[DexLoadReporter] Report classloader contexts di...""

This commit is contained in:
Calin Juravle
2020-02-19 19:38:35 +00:00
committed by Gerrit Code Review
6 changed files with 137 additions and 100 deletions

View File

@@ -28,9 +28,8 @@ import dalvik.system.VMRuntime;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -87,50 +86,32 @@ import java.util.Set;
}
@Override
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.
public void report(Map<String, String> classLoaderContextMap) {
if (classLoaderContextMap.isEmpty()) {
Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap");
return;
}
// Notify the package manager about the dex loads unconditionally.
// The load might be for either a primary or secondary dex file.
notifyPackageManager(classLoadersChain, classPaths);
notifyPackageManager(classLoaderContextMap);
// 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(dexPathsForRegistration);
registerSecondaryDexForProfiling(classLoaderContextMap.keySet());
}
private void notifyPackageManager(List<ClassLoader> classLoadersChain,
List<String> classPaths) {
private void notifyPackageManager(Map<String, String> classLoaderContextMap) {
// 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, classLoadersNames, classPaths,
VMRuntime.getRuntime().vmInstructionSet());
ActivityThread.getPackageManager().notifyDexLoad(packageName,
classLoaderContextMap, VMRuntime.getRuntime().vmInstructionSet());
} catch (RemoteException re) {
Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
}
}
private void registerSecondaryDexForProfiling(String[] dexPaths) {
private void registerSecondaryDexForProfiling(Set<String> dexPaths) {
if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
return;
}

View File

@@ -529,19 +529,12 @@ 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 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 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 loaderIsa the ISA of the loader process
*/
oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames,
in List<String> classPaths, String loaderIsa);
oneway void notifyDexLoad(String loadingPackageName,
in Map<String, String> classLoaderContextMap, String loaderIsa);
/**
* Register an application dex module with the package manager.

View File

@@ -9764,8 +9764,8 @@ public class PackageManagerService extends IPackageManager.Stub
}
@Override
public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames,
List<String> classPaths, String loaderIsa) {
public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap,
String loaderIsa) {
int userId = UserHandle.getCallingUserId();
ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
if (ai == null) {
@@ -9773,7 +9773,7 @@ public class PackageManagerService extends IPackageManager.Stub
+ loadingPackageName + ", user=" + userId);
return;
}
mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId);
}
@Override

View File

@@ -44,6 +44,8 @@ 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;
@@ -143,22 +145,15 @@ public class DexManager {
* return as fast as possible.
*
* @param loadingAppInfo the package performing the load
* @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 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 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, List<String> classLoadersNames,
List<String> classPaths, String loaderIsa, int loaderUserId) {
public void notifyDexLoad(ApplicationInfo loadingAppInfo,
Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId) {
try {
notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
loaderUserId);
} catch (Exception e) {
Slog.w(TAG, "Exception while notifying dex load for package " +
@@ -168,46 +163,23 @@ public class DexManager {
@VisibleForTesting
/*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
Map<String, String> classLoaderContextMap, String loaderIsa,
int loaderUserId) {
if (classLoaderNames.size() != classPaths.size()) {
Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
if (classLoaderContextMap == null) {
return;
}
if (classLoaderNames.isEmpty()) {
if (classLoaderContextMap.isEmpty()) {
Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
return;
}
if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
loaderIsa + "?");
Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet()
+ " in unsupported ISA: " + loaderIsa + "?");
return;
}
// 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) {
for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) {
String dexPath = mapping.getKey();
// Find the owning package name.
DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
@@ -229,7 +201,6 @@ 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;
}
@@ -239,13 +210,13 @@ public class DexManager {
searchResult.mOwningPackageName, loadingAppInfo.packageName);
}
if (classLoaderContexts != null) {
String classLoaderContext = mapping.getValue();
if (classLoaderContext != null
&& VMRuntime.isValidClassLoaderContext(classLoaderContext)) {
// 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)) {
@@ -259,7 +230,6 @@ public class DexManager {
Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
}
}
dexPathIndex++;
}
}

View File

@@ -83,8 +83,9 @@ 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).
private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
// files so discarded on read). Note: this matches
// ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime.
/*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
"=UnsupportedClassLoaderContext=";
/**
@@ -133,6 +134,9 @@ 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);
@@ -843,10 +847,11 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
String oldClassLoaderContext = mClassLoaderContext;
if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) {
if (isUnknownOrUnsupportedContext(mClassLoaderContext)) {
// Can happen if we read a previous version.
mClassLoaderContext = dexUseInfo.mClassLoaderContext;
} else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
} else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext)
&& !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
// We detected a context change.
mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT;
}
@@ -857,6 +862,13 @@ 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;
}
@@ -878,7 +890,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 UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext);
return isUnknownOrUnsupportedContext(mClassLoaderContext);
}
public boolean isVariableClassLoaderContext() {

View File

@@ -532,6 +532,61 @@ 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) {
@@ -572,17 +627,43 @@ 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.asList((String) null)
: Arrays.asList(String.join(File.pathSeparator, dexPaths));
List<String> classPaths = dexPaths != null
? Arrays.<String>asList(String.join(File.pathSeparator, dexPaths)) : null;
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.
mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, classLoaders,
classPaths, testData.mLoaderIsa, loaderUserId);
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;
}
private PackageUseInfo getPackageUseInfo(TestData testData) {