DexManager.java revision c22c30ed1c05c5c24185dc4d380d1c5026923d46
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.server.pm.dex; 18 19import android.content.pm.ApplicationInfo; 20import android.content.pm.IPackageManager; 21import android.content.pm.PackageInfo; 22import android.content.pm.PackageParser; 23import android.os.RemoteException; 24import android.util.Slog; 25 26import com.android.server.pm.PackageDexOptimizer; 27import com.android.server.pm.PackageManagerServiceUtils; 28import com.android.server.pm.PackageManagerServiceCompilerMapping; 29 30import java.io.File; 31import java.io.IOException; 32import java.util.List; 33import java.util.HashMap; 34import java.util.HashSet; 35import java.util.Map; 36import java.util.Set; 37 38import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; 39import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; 40 41/** 42 * This class keeps track of how dex files are used. 43 * Every time it gets a notification about a dex file being loaded it tracks 44 * its owning package and records it in PackageDexUsage (package-dex-usage.list). 45 * 46 * TODO(calin): Extract related dexopt functionality from PackageManagerService 47 * into this class. 48 */ 49public class DexManager { 50 private static final String TAG = "DexManager"; 51 52 private static final boolean DEBUG = false; 53 54 // Maps package name to code locations. 55 // It caches the code locations for the installed packages. This allows for 56 // faster lookups (no locks) when finding what package owns the dex file. 57 private final Map<String, PackageCodeLocations> mPackageCodeLocationsCache; 58 59 // PackageDexUsage handles the actual I/O operations. It is responsible to 60 // encode and save the dex usage data. 61 private final PackageDexUsage mPackageDexUsage; 62 63 private final IPackageManager mPackageManager; 64 private final PackageDexOptimizer mPackageDexOptimizer; 65 66 // Possible outcomes of a dex search. 67 private static int DEX_SEARCH_NOT_FOUND = 0; // dex file not found 68 private static int DEX_SEARCH_FOUND_PRIMARY = 1; // dex file is the primary/base apk 69 private static int DEX_SEARCH_FOUND_SPLIT = 2; // dex file is a split apk 70 private static int DEX_SEARCH_FOUND_SECONDARY = 3; // dex file is a secondary dex 71 72 public DexManager(IPackageManager pms, PackageDexOptimizer pdo) { 73 mPackageCodeLocationsCache = new HashMap<>(); 74 mPackageDexUsage = new PackageDexUsage(); 75 mPackageManager = pms; 76 mPackageDexOptimizer = pdo; 77 } 78 79 /** 80 * Notify about dex files loads. 81 * Note that this method is invoked when apps load dex files and it should 82 * return as fast as possible. 83 * 84 * @param loadingPackage the package performing the load 85 * @param dexPaths the list of dex files being loaded 86 * @param loaderIsa the ISA of the app loading the dex files 87 * @param loaderUserId the user id which runs the code loading the dex files 88 */ 89 public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths, 90 String loaderIsa, int loaderUserId) { 91 try { 92 notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId); 93 } catch (Exception e) { 94 Slog.w(TAG, "Exception while notifying dex load for package " + 95 loadingAppInfo.packageName, e); 96 } 97 } 98 99 private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths, 100 String loaderIsa, int loaderUserId) { 101 if (!PackageManagerServiceUtils.checkISA(loaderIsa)) { 102 Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " + 103 loaderIsa + "?"); 104 return; 105 } 106 107 for (String dexPath : dexPaths) { 108 // Find the owning package name. 109 DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId); 110 111 if (DEBUG) { 112 Slog.i(TAG, loadingAppInfo.packageName 113 + " loads from " + searchResult + " : " + loaderUserId + " : " + dexPath); 114 } 115 116 if (searchResult.mOutcome != DEX_SEARCH_NOT_FOUND) { 117 // TODO(calin): extend isUsedByOtherApps check to detect the cases where 118 // different apps share the same runtime. In that case we should not mark the dex 119 // file as isUsedByOtherApps. Currently this is a safe approximation. 120 boolean isUsedByOtherApps = !loadingAppInfo.packageName.equals( 121 searchResult.mOwningPackageName); 122 boolean primaryOrSplit = searchResult.mOutcome == DEX_SEARCH_FOUND_PRIMARY || 123 searchResult.mOutcome == DEX_SEARCH_FOUND_SPLIT; 124 125 if (primaryOrSplit && !isUsedByOtherApps) { 126 // If the dex file is the primary apk (or a split) and not isUsedByOtherApps 127 // do not record it. This case does not bring any new usable information 128 // and can be safely skipped. 129 continue; 130 } 131 132 // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, 133 // or UsedBytOtherApps), record will return true and we trigger an async write 134 // to disk to make sure we don't loose the data in case of a reboot. 135 if (mPackageDexUsage.record(searchResult.mOwningPackageName, 136 dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit)) { 137 mPackageDexUsage.maybeWriteAsync(); 138 } 139 } else { 140 // This can happen in a few situations: 141 // - bogus dex loads 142 // - recent installs/uninstalls that we didn't detect. 143 // - new installed splits 144 // If we can't find the owner of the dex we simply do not track it. The impact is 145 // that the dex file will not be considered for offline optimizations. 146 // TODO(calin): add hooks for install/uninstall notifications to 147 // capture new or obsolete packages. 148 if (DEBUG) { 149 Slog.i(TAG, "Could not find owning package for dex file: " + dexPath); 150 } 151 } 152 } 153 } 154 155 /** 156 * Read the dex usage from disk and populate the code cache locations. 157 * @param existingPackages a map containing information about what packages 158 * are available to what users. Only packages in this list will be 159 * recognized during notifyDexLoad(). 160 */ 161 public void load(Map<Integer, List<PackageInfo>> existingPackages) { 162 try { 163 loadInternal(existingPackages); 164 } catch (Exception e) { 165 mPackageDexUsage.clear(); 166 Slog.w(TAG, "Exception while loading package dex usage. " + 167 "Starting with a fresh state.", e); 168 } 169 } 170 171 private void loadInternal(Map<Integer, List<PackageInfo>> existingPackages) { 172 Map<String, Set<Integer>> packageToUsersMap = new HashMap<>(); 173 // Cache the code locations for the installed packages. This allows for 174 // faster lookups (no locks) when finding what package owns the dex file. 175 for (Map.Entry<Integer, List<PackageInfo>> entry : existingPackages.entrySet()) { 176 List<PackageInfo> packageInfoList = entry.getValue(); 177 int userId = entry.getKey(); 178 for (PackageInfo pi : packageInfoList) { 179 // Cache the code locations. 180 PackageCodeLocations pcl = mPackageCodeLocationsCache.get(pi.packageName); 181 if (pcl != null) { 182 pcl.mergeAppDataDirs(pi.applicationInfo, userId); 183 } else { 184 mPackageCodeLocationsCache.put(pi.packageName, 185 new PackageCodeLocations(pi.applicationInfo, userId)); 186 } 187 // Cache a map from package name to the set of user ids who installed the package. 188 // We will use it to sync the data and remove obsolete entries from 189 // mPackageDexUsage. 190 Set<Integer> users = putIfAbsent( 191 packageToUsersMap, pi.packageName, new HashSet<>()); 192 users.add(userId); 193 } 194 } 195 196 mPackageDexUsage.read(); 197 mPackageDexUsage.syncData(packageToUsersMap); 198 } 199 200 /** 201 * Get the package dex usage for the given package name. 202 * @return the package data or null if there is no data available for this package. 203 */ 204 public PackageUseInfo getPackageUseInfo(String packageName) { 205 return mPackageDexUsage.getPackageUseInfo(packageName); 206 } 207 208 /** 209 * Perform dexopt on the package {@code packageName} secondary dex files. 210 * @return true if all secondary dex files were processed successfully (compiled or skipped 211 * because they don't need to be compiled).. 212 */ 213 public boolean dexoptSecondaryDex(String packageName, String compilerFilter, boolean force) { 214 // Select the dex optimizer based on the force parameter. 215 // Forced compilation is done through ForcedUpdatePackageDexOptimizer which will adjust 216 // the necessary dexopt flags to make sure that compilation is not skipped. This avoid 217 // passing the force flag through the multitude of layers. 218 // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to 219 // allocate an object here. 220 PackageDexOptimizer pdo = force 221 ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer) 222 : mPackageDexOptimizer; 223 PackageUseInfo useInfo = getPackageUseInfo(packageName); 224 if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) { 225 if (DEBUG) { 226 Slog.d(TAG, "No secondary dex use for package:" + packageName); 227 } 228 // Nothing to compile, return true. 229 return true; 230 } 231 boolean success = true; 232 for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) { 233 String dexPath = entry.getKey(); 234 DexUseInfo dexUseInfo = entry.getValue(); 235 PackageInfo pkg = null; 236 try { 237 pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0, 238 dexUseInfo.getOwnerUserId()); 239 } catch (RemoteException e) { 240 throw new AssertionError(e); 241 } 242 // It may be that the package gets uninstalled while we try to compile its 243 // secondary dex files. If that's the case, just ignore. 244 // Note that we don't break the entire loop because the package might still be 245 // installed for other users. 246 if (pkg == null) { 247 Slog.d(TAG, "Could not find package when compiling secondary dex " + packageName 248 + " for user " + dexUseInfo.getOwnerUserId()); 249 // Skip over it, another user might still have the package. 250 continue; 251 } 252 int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath, 253 dexUseInfo.getLoaderIsas(), compilerFilter, dexUseInfo.isUsedByOtherApps()); 254 success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED); 255 } 256 return success; 257 } 258 259 /** 260 * Retrieves the package which owns the given dexPath. 261 */ 262 private DexSearchResult getDexPackage( 263 ApplicationInfo loadingAppInfo, String dexPath, int userId) { 264 // Ignore framework code. 265 // TODO(calin): is there a better way to detect it? 266 if (dexPath.startsWith("/system/framework/")) { 267 new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); 268 } 269 270 // First, check if the package which loads the dex file actually owns it. 271 // Most of the time this will be true and we can return early. 272 PackageCodeLocations loadingPackageCodeLocations = 273 new PackageCodeLocations(loadingAppInfo, userId); 274 int outcome = loadingPackageCodeLocations.searchDex(dexPath, userId); 275 if (outcome != DEX_SEARCH_NOT_FOUND) { 276 // TODO(calin): evaluate if we bother to detect symlinks at the dexPath level. 277 return new DexSearchResult(loadingPackageCodeLocations.mPackageName, outcome); 278 } 279 280 // The loadingPackage does not own the dex file. 281 // Perform a reverse look-up in the cache to detect if any package has ownership. 282 // Note that we can have false negatives if the cache falls out of date. 283 for (PackageCodeLocations pcl : mPackageCodeLocationsCache.values()) { 284 outcome = pcl.searchDex(dexPath, userId); 285 if (outcome != DEX_SEARCH_NOT_FOUND) { 286 return new DexSearchResult(pcl.mPackageName, outcome); 287 } 288 } 289 290 // Cache miss. Return not found for the moment. 291 // 292 // TODO(calin): this may be because of a newly installed package, an update 293 // or a new added user. We can either perform a full look up again or register 294 // observers to be notified of package/user updates. 295 return new DexSearchResult(null, DEX_SEARCH_NOT_FOUND); 296 } 297 298 private static <K,V> V putIfAbsent(Map<K,V> map, K key, V newValue) { 299 V existingValue = map.putIfAbsent(key, newValue); 300 return existingValue == null ? newValue : existingValue; 301 } 302 303 /** 304 * Convenience class to store the different locations where a package might 305 * own code. 306 */ 307 private static class PackageCodeLocations { 308 private final String mPackageName; 309 private final String mBaseCodePath; 310 private final Set<String> mSplitCodePaths; 311 // Maps user id to the application private directory. 312 private final Map<Integer, Set<String>> mAppDataDirs; 313 314 public PackageCodeLocations(ApplicationInfo ai, int userId) { 315 mPackageName = ai.packageName; 316 mBaseCodePath = ai.sourceDir; 317 mSplitCodePaths = new HashSet<>(); 318 if (ai.splitSourceDirs != null) { 319 for (String split : ai.splitSourceDirs) { 320 mSplitCodePaths.add(split); 321 } 322 } 323 mAppDataDirs = new HashMap<>(); 324 mergeAppDataDirs(ai, userId); 325 } 326 327 public void mergeAppDataDirs(ApplicationInfo ai, int userId) { 328 Set<String> dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); 329 dataDirs.add(ai.dataDir); 330 } 331 332 public int searchDex(String dexPath, int userId) { 333 // First check that this package is installed or active for the given user. 334 // If we don't have a data dir it means this user is trying to load something 335 // unavailable for them. 336 Set<String> userDataDirs = mAppDataDirs.get(userId); 337 if (userDataDirs == null) { 338 Slog.w(TAG, "Trying to load a dex path which does not exist for the current " + 339 "user. dexPath=" + dexPath + ", userId=" + userId); 340 return DEX_SEARCH_NOT_FOUND; 341 } 342 343 if (mBaseCodePath.equals(dexPath)) { 344 return DEX_SEARCH_FOUND_PRIMARY; 345 } 346 if (mSplitCodePaths.contains(dexPath)) { 347 return DEX_SEARCH_FOUND_SPLIT; 348 } 349 for (String dataDir : userDataDirs) { 350 if (dexPath.startsWith(dataDir)) { 351 return DEX_SEARCH_FOUND_SECONDARY; 352 } 353 } 354 355 // TODO(calin): What if we get a symlink? e.g. data dir may be a symlink, 356 // /data/data/ -> /data/user/0/. 357 if (DEBUG) { 358 try { 359 String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); 360 if (dexPathReal != dexPath) { 361 Slog.d(TAG, "Dex loaded with symlink. dexPath=" + 362 dexPath + " dexPathReal=" + dexPathReal); 363 } 364 } catch (IOException e) { 365 // Ignore 366 } 367 } 368 return DEX_SEARCH_NOT_FOUND; 369 } 370 } 371 372 /** 373 * Convenience class to store ownership search results. 374 */ 375 private class DexSearchResult { 376 private String mOwningPackageName; 377 private int mOutcome; 378 379 public DexSearchResult(String owningPackageName, int outcome) { 380 this.mOwningPackageName = owningPackageName; 381 this.mOutcome = outcome; 382 } 383 384 @Override 385 public String toString() { 386 return mOwningPackageName + "-" + mOutcome; 387 } 388 } 389 390 391} 392