• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.android.server.pm;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.PackageParser;
23 import android.os.Environment;
24 import android.os.PowerManager;
25 import android.os.UserHandle;
26 import android.os.WorkSource;
27 import android.util.Log;
28 import android.util.Slog;
29 
30 import com.android.internal.os.InstallerConnection.InstallerException;
31 import com.android.internal.util.IndentingPrintWriter;
32 
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.List;
36 
37 import dalvik.system.DexFile;
38 
39 import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
40 import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
41 import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
42 import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
43 import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
44 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
45 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
46 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
47 
48 /**
49  * Helper class for running dexopt command on packages.
50  */
51 class PackageDexOptimizer {
52     private static final String TAG = "PackageManager.DexOptimizer";
53     static final String OAT_DIR_NAME = "oat";
54     // TODO b/19550105 Remove error codes and use exceptions
55     static final int DEX_OPT_SKIPPED = 0;
56     static final int DEX_OPT_PERFORMED = 1;
57     static final int DEX_OPT_FAILED = -1;
58 
59     private final Installer mInstaller;
60     private final Object mInstallLock;
61 
62     private final PowerManager.WakeLock mDexoptWakeLock;
63     private volatile boolean mSystemReady;
64 
PackageDexOptimizer(Installer installer, Object installLock, Context context, String wakeLockTag)65     PackageDexOptimizer(Installer installer, Object installLock, Context context,
66             String wakeLockTag) {
67         this.mInstaller = installer;
68         this.mInstallLock = installLock;
69 
70         PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
71         mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
72     }
73 
PackageDexOptimizer(PackageDexOptimizer from)74     protected PackageDexOptimizer(PackageDexOptimizer from) {
75         this.mInstaller = from.mInstaller;
76         this.mInstallLock = from.mInstallLock;
77         this.mDexoptWakeLock = from.mDexoptWakeLock;
78         this.mSystemReady = from.mSystemReady;
79     }
80 
canOptimizePackage(PackageParser.Package pkg)81     static boolean canOptimizePackage(PackageParser.Package pkg) {
82         return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
83     }
84 
85     /**
86      * Performs dexopt on all code paths and libraries of the specified package for specified
87      * instruction sets.
88      *
89      * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
90      * synchronized on {@link #mInstallLock}.
91      */
performDexOpt(PackageParser.Package pkg, String[] sharedLibraries, String[] instructionSets, boolean checkProfiles, String targetCompilationFilter, CompilerStats.PackageStats packageStats)92     int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
93             String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
94             CompilerStats.PackageStats packageStats) {
95         synchronized (mInstallLock) {
96             final boolean useLock = mSystemReady;
97             if (useLock) {
98                 mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
99                 mDexoptWakeLock.acquire();
100             }
101             try {
102                 return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
103                         targetCompilationFilter, packageStats);
104             } finally {
105                 if (useLock) {
106                     mDexoptWakeLock.release();
107                 }
108             }
109         }
110     }
111 
112     /**
113      * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
114      * optimize or not (and in what way).
115      */
adjustDexoptNeeded(int dexoptNeeded)116     protected int adjustDexoptNeeded(int dexoptNeeded) {
117         return dexoptNeeded;
118     }
119 
120     /**
121      * Adjust the given dexopt flags that will be passed to the installer.
122      */
adjustDexoptFlags(int dexoptFlags)123     protected int adjustDexoptFlags(int dexoptFlags) {
124         return dexoptFlags;
125     }
126 
127     /**
128      * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}.
129      */
dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg)130     void dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg) {
131         final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
132         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
133 
134         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
135 
136         for (String instructionSet : dexCodeInstructionSets) {
137              pw.println("Instruction Set: " + instructionSet);
138              pw.increaseIndent();
139              for (String path : paths) {
140                   String status = null;
141                   try {
142                       status = DexFile.getDexFileStatus(path, instructionSet);
143                   } catch (IOException ioe) {
144                       status = "[Exception]: " + ioe.getMessage();
145                   }
146                   pw.println("path: " + path);
147                   pw.println("status: " + status);
148              }
149              pw.decreaseIndent();
150         }
151     }
152 
performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries, String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter, CompilerStats.PackageStats packageStats)153     private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
154             String[] targetInstructionSets, boolean checkProfiles, String targetCompilerFilter,
155             CompilerStats.PackageStats packageStats) {
156         final String[] instructionSets = targetInstructionSets != null ?
157                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
158 
159         if (!canOptimizePackage(pkg)) {
160             return DEX_OPT_SKIPPED;
161         }
162 
163         final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
164         final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
165 
166         boolean isProfileGuidedFilter = DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter);
167         // If any part of the app is used by other apps, we cannot use profile-guided
168         // compilation.
169         if (isProfileGuidedFilter && isUsedByOtherApps(pkg)) {
170             checkProfiles = false;
171 
172             targetCompilerFilter = getNonProfileGuidedCompilerFilter(targetCompilerFilter);
173             if (DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) {
174                 throw new IllegalStateException(targetCompilerFilter);
175             }
176             isProfileGuidedFilter = false;
177         }
178 
179         // If we're asked to take profile updates into account, check now.
180         boolean newProfile = false;
181         if (checkProfiles && isProfileGuidedFilter) {
182             // Merge profiles, see if we need to do anything.
183             try {
184                 newProfile = mInstaller.mergeProfiles(sharedGid, pkg.packageName);
185             } catch (InstallerException e) {
186                 Slog.w(TAG, "Failed to merge profiles", e);
187             }
188         }
189 
190         final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
191         final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
192 
193         boolean performedDexOpt = false;
194         boolean successfulDexOpt = true;
195 
196         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
197         for (String dexCodeInstructionSet : dexCodeInstructionSets) {
198             for (String path : paths) {
199                 int dexoptNeeded;
200                 try {
201                     dexoptNeeded = DexFile.getDexOptNeeded(path,
202                             dexCodeInstructionSet, targetCompilerFilter, newProfile);
203                 } catch (IOException ioe) {
204                     Slog.w(TAG, "IOException reading apk: " + path, ioe);
205                     return DEX_OPT_FAILED;
206                 }
207                 dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
208                 if (PackageManagerService.DEBUG_DEXOPT) {
209                     Log.i(TAG, "DexoptNeeded for " + path + "@" + targetCompilerFilter + " is " +
210                             dexoptNeeded);
211                 }
212 
213                 final String dexoptType;
214                 String oatDir = null;
215                 switch (dexoptNeeded) {
216                     case DexFile.NO_DEXOPT_NEEDED:
217                         continue;
218                     case DexFile.DEX2OAT_NEEDED:
219                         dexoptType = "dex2oat";
220                         oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
221                         break;
222                     case DexFile.PATCHOAT_NEEDED:
223                         dexoptType = "patchoat";
224                         break;
225                     case DexFile.SELF_PATCHOAT_NEEDED:
226                         dexoptType = "self patchoat";
227                         break;
228                     default:
229                         throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded);
230                 }
231 
232                 String sharedLibrariesPath = null;
233                 if (sharedLibraries != null && sharedLibraries.length != 0) {
234                     StringBuilder sb = new StringBuilder();
235                     for (String lib : sharedLibraries) {
236                         if (sb.length() != 0) {
237                             sb.append(":");
238                         }
239                         sb.append(lib);
240                     }
241                     sharedLibrariesPath = sb.toString();
242                 }
243                 Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
244                         + pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
245                         + " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
246                         + " target-filter=" + targetCompilerFilter + " oatDir = " + oatDir
247                         + " sharedLibraries=" + sharedLibrariesPath);
248                 // Profile guide compiled oat files should not be public.
249                 final boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
250                 final int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
251                 final int dexFlags = adjustDexoptFlags(
252                         ( isPublic ? DEXOPT_PUBLIC : 0)
253                         | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
254                         | (debuggable ? DEXOPT_DEBUGGABLE : 0)
255                         | profileFlag
256                         | DEXOPT_BOOTCOMPLETE);
257 
258                 try {
259                     long startTime = System.currentTimeMillis();
260 
261                     mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
262                             dexoptNeeded, oatDir, dexFlags, targetCompilerFilter, pkg.volumeUuid,
263                             sharedLibrariesPath);
264                     performedDexOpt = true;
265 
266                     if (packageStats != null) {
267                         long endTime = System.currentTimeMillis();
268                         packageStats.setCompileTime(path, (int)(endTime - startTime));
269                     }
270                 } catch (InstallerException e) {
271                     Slog.w(TAG, "Failed to dexopt", e);
272                     successfulDexOpt = false;
273                 }
274             }
275         }
276 
277         if (successfulDexOpt) {
278             // If we've gotten here, we're sure that no error occurred. We've either
279             // dex-opted one or more paths or instruction sets or we've skipped
280             // all of them because they are up to date. In both cases this package
281             // doesn't need dexopt any longer.
282             return performedDexOpt ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
283         } else {
284             return DEX_OPT_FAILED;
285         }
286     }
287 
288     /**
289      * Creates oat dir for the specified package. In certain cases oat directory
290      * <strong>cannot</strong> be created:
291      * <ul>
292      *      <li>{@code pkg} is a system app, which is not updated.</li>
293      *      <li>Package location is not a directory, i.e. monolithic install.</li>
294      * </ul>
295      *
296      * @return Absolute path to the oat directory or null, if oat directory
297      * cannot be created.
298      */
299     @Nullable
createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet)300     private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
301         if (!pkg.canHaveOatDir()) {
302             return null;
303         }
304         File codePath = new File(pkg.codePath);
305         if (codePath.isDirectory()) {
306             File oatDir = getOatDir(codePath);
307             try {
308                 mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
309             } catch (InstallerException e) {
310                 Slog.w(TAG, "Failed to create oat dir", e);
311                 return null;
312             }
313             return oatDir.getAbsolutePath();
314         }
315         return null;
316     }
317 
getOatDir(File codePath)318     static File getOatDir(File codePath) {
319         return new File(codePath, OAT_DIR_NAME);
320     }
321 
systemReady()322     void systemReady() {
323         mSystemReady = true;
324     }
325 
326     /**
327      * Returns true if the profiling data collected for the given app indicate
328      * that the apps's APK has been loaded by another app.
329      * Note that this returns false for all forward-locked apps and apps without
330      * any collected profiling data.
331      */
isUsedByOtherApps(PackageParser.Package pkg)332     public static boolean isUsedByOtherApps(PackageParser.Package pkg) {
333         if (pkg.isForwardLocked()) {
334             // Skip the check for forward locked packages since they don't share their code.
335             return false;
336         }
337 
338         for (String apkPath : pkg.getAllCodePathsExcludingResourceOnly()) {
339             try {
340                 apkPath = PackageManagerServiceUtils.realpath(new File(apkPath));
341             } catch (IOException e) {
342                 // Log an error but continue without it.
343                 Slog.w(TAG, "Failed to get canonical path", e);
344                 continue;
345             }
346             String useMarker = apkPath.replace('/', '@');
347             final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
348             for (int i = 0; i < currentUserIds.length; i++) {
349                 File profileDir =
350                         Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
351                 File foreignUseMark = new File(profileDir, useMarker);
352                 if (foreignUseMark.exists()) {
353                     return true;
354                 }
355             }
356         }
357         return false;
358     }
359 
360     /**
361      * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
362      * dexopt path.
363      */
364     public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
365 
ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock, Context context, String wakeLockTag)366         public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
367                 Context context, String wakeLockTag) {
368             super(installer, installLock, context, wakeLockTag);
369         }
370 
ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from)371         public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
372             super(from);
373         }
374 
375         @Override
adjustDexoptNeeded(int dexoptNeeded)376         protected int adjustDexoptNeeded(int dexoptNeeded) {
377             // Ensure compilation, no matter the current state.
378             // TODO: The return value is wrong when patchoat is needed.
379             return DexFile.DEX2OAT_NEEDED;
380         }
381     }
382 }
383