PackageDexOptimizer.java revision bb10525a661a1b034790cd3043daff88a7288483
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
17package com.android.server.pm;
18
19import android.annotation.Nullable;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageParser;
23import android.os.Environment;
24import android.os.PowerManager;
25import android.os.UserHandle;
26import android.os.WorkSource;
27import android.util.Log;
28import android.util.Slog;
29
30import com.android.internal.annotations.GuardedBy;
31import com.android.internal.util.IndentingPrintWriter;
32import com.android.server.pm.Installer.InstallerException;
33
34import java.io.File;
35import java.io.IOException;
36import java.util.ArrayList;
37import java.util.List;
38
39import dalvik.system.DexFile;
40
41import static com.android.server.pm.Installer.DEXOPT_BOOTCOMPLETE;
42import static com.android.server.pm.Installer.DEXOPT_DEBUGGABLE;
43import static com.android.server.pm.Installer.DEXOPT_PROFILE_GUIDED;
44import static com.android.server.pm.Installer.DEXOPT_PUBLIC;
45import static com.android.server.pm.Installer.DEXOPT_SAFEMODE;
46import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
47import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
48
49import static com.android.server.pm.PackageManagerServiceCompilerMapping.getNonProfileGuidedCompilerFilter;
50import static dalvik.system.DexFile.isProfileGuidedCompilerFilter;
51
52/**
53 * Helper class for running dexopt command on packages.
54 */
55class PackageDexOptimizer {
56    private static final String TAG = "PackageManager.DexOptimizer";
57    static final String OAT_DIR_NAME = "oat";
58    // TODO b/19550105 Remove error codes and use exceptions
59    static final int DEX_OPT_SKIPPED = 0;
60    static final int DEX_OPT_PERFORMED = 1;
61    static final int DEX_OPT_FAILED = -1;
62
63    private final Installer mInstaller;
64    private final Object mInstallLock;
65
66    private final PowerManager.WakeLock mDexoptWakeLock;
67    private volatile boolean mSystemReady;
68
69    PackageDexOptimizer(Installer installer, Object installLock, Context context,
70            String wakeLockTag) {
71        this.mInstaller = installer;
72        this.mInstallLock = installLock;
73
74        PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
75        mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
76    }
77
78    protected PackageDexOptimizer(PackageDexOptimizer from) {
79        this.mInstaller = from.mInstaller;
80        this.mInstallLock = from.mInstallLock;
81        this.mDexoptWakeLock = from.mDexoptWakeLock;
82        this.mSystemReady = from.mSystemReady;
83    }
84
85    static boolean canOptimizePackage(PackageParser.Package pkg) {
86        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
87    }
88
89    /**
90     * Performs dexopt on all code paths and libraries of the specified package for specified
91     * instruction sets.
92     *
93     * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
94     * synchronized on {@link #mInstallLock}.
95     */
96    int performDexOpt(PackageParser.Package pkg, String[] sharedLibraries,
97            String[] instructionSets, boolean checkProfiles, String targetCompilationFilter,
98            CompilerStats.PackageStats packageStats) {
99        if (!canOptimizePackage(pkg)) {
100            return DEX_OPT_SKIPPED;
101        }
102        synchronized (mInstallLock) {
103            final boolean useLock = mSystemReady;
104            if (useLock) {
105                mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
106                mDexoptWakeLock.acquire();
107            }
108            try {
109                return performDexOptLI(pkg, sharedLibraries, instructionSets, checkProfiles,
110                        targetCompilationFilter, packageStats);
111            } finally {
112                if (useLock) {
113                    mDexoptWakeLock.release();
114                }
115            }
116        }
117    }
118
119    /**
120     * Performs dexopt on all code paths of the given package.
121     * It assumes the install lock is held.
122     */
123    @GuardedBy("mInstallLock")
124    private int performDexOptLI(PackageParser.Package pkg, String[] sharedLibraries,
125            String[] targetInstructionSets, boolean checkForProfileUpdates,
126            String targetCompilerFilter, CompilerStats.PackageStats packageStats) {
127        final String[] instructionSets = targetInstructionSets != null ?
128                targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
129        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
130        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
131        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
132
133        final String compilerFilter = getRealCompilerFilter(pkg, targetCompilerFilter);
134        final boolean profileUpdated = checkForProfileUpdates &&
135                isProfileUpdated(pkg, sharedGid, compilerFilter);
136        // TODO(calin,jeffhao): shared library paths should be adjusted to include previous code
137        // paths (b/34169257).
138        final String sharedLibrariesPath = getSharedLibrariesPath(sharedLibraries);
139        final int dexoptFlags = getDexFlags(pkg, compilerFilter);
140
141        int result = DEX_OPT_SKIPPED;
142        for (String path : paths) {
143            for (String dexCodeIsa : dexCodeInstructionSets) {
144                int newResult = dexOptPath(pkg, path, dexCodeIsa, compilerFilter, profileUpdated,
145                        sharedLibrariesPath, dexoptFlags, sharedGid, packageStats);
146                // The end result is:
147                //  - FAILED if any path failed,
148                //  - PERFORMED if at least one path needed compilation,
149                //  - SKIPPED when all paths are up to date
150                if ((result != DEX_OPT_FAILED) && (newResult != DEX_OPT_SKIPPED)) {
151                    result = newResult;
152                }
153            }
154        }
155        return result;
156    }
157
158    /**
159     * Performs dexopt on the {@code path} belonging to the package {@code pkg}.
160     *
161     * @return
162     *      DEX_OPT_FAILED if there was any exception during dexopt
163     *      DEX_OPT_PERFORMED if dexopt was performed successfully on the given path.
164     *      DEX_OPT_SKIPPED if the path does not need to be deopt-ed.
165     */
166    @GuardedBy("mInstallLock")
167    private int dexOptPath(PackageParser.Package pkg, String path, String isa,
168            String compilerFilter, boolean profileUpdated, String sharedLibrariesPath,
169            int dexoptFlags, int uid, CompilerStats.PackageStats packageStats) {
170        int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, profileUpdated);
171        if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
172            return DEX_OPT_SKIPPED;
173        }
174
175        // TODO(calin): there's no need to try to create the oat dir over and over again,
176        //              especially since it involve an extra installd call. We should create
177        //              if (if supported) on the fly during the dexopt call.
178        String oatDir = createOatDirIfSupported(pkg, isa);
179
180        Log.i(TAG, "Running dexopt (dexoptNeeded=" + dexoptNeeded + ") on: " + path
181                + " pkg=" + pkg.applicationInfo.packageName + " isa=" + isa
182                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
183                + " target-filter=" + compilerFilter + " oatDir=" + oatDir
184                + " sharedLibraries=" + sharedLibrariesPath);
185
186        try {
187            long startTime = System.currentTimeMillis();
188
189            mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
190                    compilerFilter, pkg.volumeUuid, sharedLibrariesPath);
191
192            if (packageStats != null) {
193                long endTime = System.currentTimeMillis();
194                packageStats.setCompileTime(path, (int)(endTime - startTime));
195            }
196            return DEX_OPT_PERFORMED;
197        } catch (InstallerException e) {
198            Slog.w(TAG, "Failed to dexopt", e);
199            return DEX_OPT_FAILED;
200        }
201    }
202
203    /**
204     * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
205     * optimize or not (and in what way).
206     */
207    protected int adjustDexoptNeeded(int dexoptNeeded) {
208        return dexoptNeeded;
209    }
210
211    /**
212     * Adjust the given dexopt flags that will be passed to the installer.
213     */
214    protected int adjustDexoptFlags(int dexoptFlags) {
215        return dexoptFlags;
216    }
217
218    /**
219     * Dumps the dexopt state of the given package {@code pkg} to the given {@code PrintWriter}.
220     */
221    void dumpDexoptState(IndentingPrintWriter pw, PackageParser.Package pkg) {
222        final String[] instructionSets = getAppDexInstructionSets(pkg.applicationInfo);
223        final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
224
225        final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
226
227        for (String instructionSet : dexCodeInstructionSets) {
228             pw.println("Instruction Set: " + instructionSet);
229             pw.increaseIndent();
230             for (String path : paths) {
231                  String status = null;
232                  try {
233                      status = DexFile.getDexFileStatus(path, instructionSet);
234                  } catch (IOException ioe) {
235                      status = "[Exception]: " + ioe.getMessage();
236                  }
237                  pw.println("path: " + path);
238                  pw.println("status: " + status);
239             }
240             pw.decreaseIndent();
241        }
242    }
243
244    /**
245     * Returns the compiler filter that should be used to optimize the package code.
246     * The target filter will be updated if the package code is used by other apps
247     * or if it has the safe mode flag set.
248     */
249    private String getRealCompilerFilter(PackageParser.Package pkg, String targetCompilerFilter) {
250        int flags = pkg.applicationInfo.flags;
251        boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
252        if (vmSafeMode) {
253            // For the compilation, it doesn't really matter what we return here because installd
254            // will replace the filter with interpret-only anyway.
255            // However, we return a non profile guided filter so that we simplify the logic of
256            // merging profiles.
257            // TODO(calin): safe mode path could be simplified if we pass interpret-only from
258            //              here rather than letting installd decide on the filter.
259            return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
260        }
261
262        if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps(pkg)) {
263            // If the dex files is used by other apps, we cannot use profile-guided compilation.
264            return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
265        }
266
267        return targetCompilerFilter;
268    }
269
270    /**
271     * Computes the dex flags that needs to be pass to installd for the given package and compiler
272     * filter.
273     */
274    private int getDexFlags(PackageParser.Package pkg, String compilerFilter) {
275        int flags = pkg.applicationInfo.flags;
276        boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
277        boolean debuggable = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
278        // Profile guide compiled oat files should not be public.
279        boolean isProfileGuidedFilter = isProfileGuidedCompilerFilter(compilerFilter);
280        boolean isPublic = !pkg.isForwardLocked() && !isProfileGuidedFilter;
281        int profileFlag = isProfileGuidedFilter ? DEXOPT_PROFILE_GUIDED : 0;
282        int dexFlags =
283                (isPublic ? DEXOPT_PUBLIC : 0)
284                | (vmSafeMode ? DEXOPT_SAFEMODE : 0)
285                | (debuggable ? DEXOPT_DEBUGGABLE : 0)
286                | profileFlag
287                | DEXOPT_BOOTCOMPLETE;
288        return adjustDexoptFlags(dexFlags);
289    }
290
291    /**
292     * Assesses if there's a need to perform dexopt on {@code path} for the given
293     * configuration (isa, compiler filter, profile).
294     */
295    private int getDexoptNeeded(String path, String isa, String compilerFilter,
296            boolean newProfile) {
297        int dexoptNeeded;
298        try {
299            dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, newProfile);
300        } catch (IOException ioe) {
301            Slog.w(TAG, "IOException reading apk: " + path, ioe);
302            return DEX_OPT_FAILED;
303        }
304        return adjustDexoptNeeded(dexoptNeeded);
305    }
306
307    /**
308     * Computes the shared libraries path that should be passed to dexopt.
309     */
310    private String getSharedLibrariesPath(String[] sharedLibraries) {
311        if (sharedLibraries == null || sharedLibraries.length == 0) {
312            return null;
313        }
314        StringBuilder sb = new StringBuilder();
315        for (String lib : sharedLibraries) {
316            if (sb.length() != 0) {
317                sb.append(":");
318            }
319            sb.append(lib);
320        }
321        return sb.toString();
322    }
323
324    /**
325     * Checks if there is an update on the profile information of the {@code pkg}.
326     * If the compiler filter is not profile guided the method returns false.
327     *
328     * Note that this is a "destructive" operation with side effects. Under the hood the
329     * current profile and the reference profile will be merged and subsequent calls
330     * may return a different result.
331     */
332    private boolean isProfileUpdated(PackageParser.Package pkg, int uid, String compilerFilter) {
333        // Check if we are allowed to merge and if the compiler filter is profile guided.
334        if (!isProfileGuidedCompilerFilter(compilerFilter)) {
335            return false;
336        }
337        // Merge profiles. It returns whether or not there was an updated in the profile info.
338        try {
339            return mInstaller.mergeProfiles(uid, pkg.packageName);
340        } catch (InstallerException e) {
341            Slog.w(TAG, "Failed to merge profiles", e);
342        }
343        return false;
344    }
345
346    /**
347     * Creates oat dir for the specified package if needed and supported.
348     * In certain cases oat directory
349     * <strong>cannot</strong> be created:
350     * <ul>
351     *      <li>{@code pkg} is a system app, which is not updated.</li>
352     *      <li>Package location is not a directory, i.e. monolithic install.</li>
353     * </ul>
354     *
355     * @return Absolute path to the oat directory or null, if oat directory
356     * cannot be created.
357     */
358    @Nullable
359    private String createOatDirIfSupported(PackageParser.Package pkg, String dexInstructionSet) {
360        if (!pkg.canHaveOatDir()) {
361            return null;
362        }
363        File codePath = new File(pkg.codePath);
364        if (codePath.isDirectory()) {
365            // TODO(calin): why do we create this only if the codePath is a directory? (i.e for
366            //              cluster packages). It seems that the logic for the folder creation is
367            //              split between installd and here.
368            File oatDir = getOatDir(codePath);
369            try {
370                mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
371            } catch (InstallerException e) {
372                Slog.w(TAG, "Failed to create oat dir", e);
373                return null;
374            }
375            return oatDir.getAbsolutePath();
376        }
377        return null;
378    }
379
380    static File getOatDir(File codePath) {
381        return new File(codePath, OAT_DIR_NAME);
382    }
383
384    void systemReady() {
385        mSystemReady = true;
386    }
387
388    /**
389     * Returns true if the profiling data collected for the given app indicate
390     * that the apps's APK has been loaded by another app.
391     * Note that this returns false for all forward-locked apps and apps without
392     * any collected profiling data.
393     */
394    public static boolean isUsedByOtherApps(PackageParser.Package pkg) {
395        if (pkg.isForwardLocked()) {
396            // Skip the check for forward locked packages since they don't share their code.
397            return false;
398        }
399
400        for (String apkPath : pkg.getAllCodePathsExcludingResourceOnly()) {
401            try {
402                apkPath = PackageManagerServiceUtils.realpath(new File(apkPath));
403            } catch (IOException e) {
404                // Log an error but continue without it.
405                Slog.w(TAG, "Failed to get canonical path", e);
406                continue;
407            }
408            String useMarker = apkPath.replace('/', '@');
409            final int[] currentUserIds = UserManagerService.getInstance().getUserIds();
410            for (int i = 0; i < currentUserIds.length; i++) {
411                File profileDir =
412                        Environment.getDataProfilesDeForeignDexDirectory(currentUserIds[i]);
413                File foreignUseMark = new File(profileDir, useMarker);
414                if (foreignUseMark.exists()) {
415                    return true;
416                }
417            }
418        }
419        return false;
420    }
421
422    private String printDexoptFlags(int flags) {
423        ArrayList<String> flagsList = new ArrayList<>();
424
425        if ((flags & DEXOPT_BOOTCOMPLETE) == DEXOPT_BOOTCOMPLETE) {
426            flagsList.add("boot_complete");
427        }
428        if ((flags & DEXOPT_DEBUGGABLE) == DEXOPT_DEBUGGABLE) {
429            flagsList.add("debuggable");
430        }
431        if ((flags & DEXOPT_PROFILE_GUIDED) == DEXOPT_PROFILE_GUIDED) {
432            flagsList.add("profile_guided");
433        }
434        if ((flags & DEXOPT_PUBLIC) == DEXOPT_PUBLIC) {
435            flagsList.add("public");
436        }
437        if ((flags & DEXOPT_SAFEMODE) == DEXOPT_SAFEMODE) {
438            flagsList.add("safemode");
439        }
440        return String.join(",", flagsList);
441    }
442
443    /**
444     * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
445     * dexopt path.
446     */
447    public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
448
449        public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
450                Context context, String wakeLockTag) {
451            super(installer, installLock, context, wakeLockTag);
452        }
453
454        public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
455            super(from);
456        }
457
458        @Override
459        protected int adjustDexoptNeeded(int dexoptNeeded) {
460            // Ensure compilation, no matter the current state.
461            // TODO: The return value is wrong when patchoat is needed.
462            return DexFile.DEX2OAT_FROM_SCRATCH;
463        }
464    }
465}
466