ArtManagerService.java revision 0a8bea818c330d02161322901534992253a2f9ee
1/*
2 * Copyright (C) 2017 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.Manifest;
20import android.annotation.UserIdInt;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.IPackageManager;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageParser;
26import android.content.pm.dex.ArtManager;
27import android.content.pm.dex.ArtManager.ProfileType;
28import android.content.pm.dex.ArtManagerInternal;
29import android.content.pm.dex.DexMetadataHelper;
30import android.content.pm.dex.PackageOptimizationInfo;
31import android.os.Binder;
32import android.os.Build;
33import android.os.Handler;
34import android.os.ParcelFileDescriptor;
35import android.os.RemoteException;
36import android.content.pm.dex.ISnapshotRuntimeProfileCallback;
37import android.os.SystemProperties;
38import android.os.UserHandle;
39import android.system.Os;
40import android.util.ArrayMap;
41import android.util.Slog;
42
43import com.android.internal.annotations.GuardedBy;
44import com.android.internal.os.BackgroundThread;
45import com.android.internal.util.ArrayUtils;
46import com.android.internal.util.Preconditions;
47import com.android.server.LocalServices;
48import com.android.server.pm.Installer;
49import com.android.server.pm.Installer.InstallerException;
50
51import dalvik.system.DexFile;
52
53import dalvik.system.VMRuntime;
54import java.io.File;
55import java.io.FileNotFoundException;
56import libcore.io.IoUtils;
57import libcore.util.NonNull;
58import libcore.util.Nullable;
59
60/**
61 * A system service that provides access to runtime and compiler artifacts.
62 *
63 * This service is not accessed by users directly, instead one uses an instance of
64 * {@link ArtManager}, which can be accessed via {@link PackageManager} as follows:
65 * <p/>
66 * {@code context().getPackageManager().getArtManager();}
67 * <p class="note">
68 * Note: Accessing runtime artifacts may require extra permissions. For example querying the
69 * runtime profiles of apps requires {@link android.Manifest.permission#READ_RUNTIME_PROFILES}
70 * which is a system-level permission that will not be granted to normal apps.
71 */
72public class ArtManagerService extends android.content.pm.dex.IArtManager.Stub {
73    private static final String TAG = "ArtManagerService";
74
75    private static boolean DEBUG = false;
76    private static boolean DEBUG_IGNORE_PERMISSIONS = false;
77
78    // Package name used to create the profile directory layout when
79    // taking a snapshot of the boot image profile.
80    private static final String BOOT_IMAGE_ANDROID_PACKAGE = "android";
81    // Profile name used for the boot image profile.
82    private static final String BOOT_IMAGE_PROFILE_NAME = "android.prof";
83
84    private final IPackageManager mPackageManager;
85    private final Object mInstallLock;
86    @GuardedBy("mInstallLock")
87    private final Installer mInstaller;
88
89    private final Handler mHandler;
90
91    public ArtManagerService(IPackageManager pm, Installer installer, Object installLock) {
92        mPackageManager = pm;
93        mInstaller = installer;
94        mInstallLock = installLock;
95        mHandler = new Handler(BackgroundThread.getHandler().getLooper());
96
97        LocalServices.addService(ArtManagerInternal.class, new ArtManagerInternalImpl());
98    }
99
100    @Override
101    public void snapshotRuntimeProfile(@ProfileType int profileType, @Nullable String packageName,
102            @Nullable String codePath, @NonNull ISnapshotRuntimeProfileCallback callback) {
103        // Sanity checks on the arguments.
104        Preconditions.checkNotNull(callback);
105
106        boolean bootImageProfile = profileType == ArtManager.PROFILE_BOOT_IMAGE;
107        if (!bootImageProfile) {
108            Preconditions.checkStringNotEmpty(codePath);
109            Preconditions.checkStringNotEmpty(packageName);
110        }
111
112        // Verify that the caller has the right permissions and that the runtime profiling is
113        // enabled. The call to isRuntimePermissions will checkReadRuntimeProfilePermission.
114        if (!isRuntimeProfilingEnabled(profileType)) {
115            throw new IllegalStateException("Runtime profiling is not enabled for " + profileType);
116        }
117
118        if (DEBUG) {
119            Slog.d(TAG, "Requested snapshot for " + packageName + ":" + codePath);
120        }
121
122        if (bootImageProfile) {
123            snapshotBootImageProfile(callback);
124        } else {
125            snapshotAppProfile(packageName, codePath, callback);
126        }
127    }
128
129    private void snapshotAppProfile(String packageName, String codePath,
130            ISnapshotRuntimeProfileCallback callback) {
131        PackageInfo info = null;
132        try {
133            // Note that we use the default user 0 to retrieve the package info.
134            // This doesn't really matter because for user 0 we always get a package back (even if
135            // it's not installed for the user 0). It is ok because we only care about the code
136            // paths and not if the package is enabled or not for the user.
137
138            // TODO(calin): consider adding an API to PMS which can retrieve the
139            // PackageParser.Package.
140            info = mPackageManager.getPackageInfo(packageName, /*flags*/ 0, /*userId*/ 0);
141        } catch (RemoteException ignored) {
142            // Should not happen.
143        }
144        if (info == null) {
145            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_PACKAGE_NOT_FOUND);
146            return;
147        }
148
149        boolean pathFound = info.applicationInfo.getBaseCodePath().equals(codePath);
150        String splitName = null;
151        String[] splitCodePaths = info.applicationInfo.getSplitCodePaths();
152        if (!pathFound && (splitCodePaths != null)) {
153            for (int i = splitCodePaths.length - 1; i >= 0; i--) {
154                if (splitCodePaths[i].equals(codePath)) {
155                    pathFound = true;
156                    splitName = info.applicationInfo.splitNames[i];
157                    break;
158                }
159            }
160        }
161        if (!pathFound) {
162            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_CODE_PATH_NOT_FOUND);
163            return;
164        }
165
166        // All good, create the profile snapshot.
167        int appId = UserHandle.getAppId(info.applicationInfo.uid);
168        if (appId < 0) {
169            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
170            Slog.wtf(TAG, "AppId is -1 for package: " + packageName);
171            return;
172        }
173
174        createProfileSnapshot(packageName, ArtManager.getProfileName(splitName), codePath,
175                appId, callback);
176        // Destroy the snapshot, we no longer need it.
177        destroyProfileSnapshot(packageName, ArtManager.getProfileName(splitName));
178    }
179
180    private void createProfileSnapshot(String packageName, String profileName, String classpath,
181            int appId, ISnapshotRuntimeProfileCallback callback) {
182        // Ask the installer to snapshot the profile.
183        synchronized (mInstallLock) {
184            try {
185                if (!mInstaller.createProfileSnapshot(appId, packageName, profileName, classpath)) {
186                    postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
187                    return;
188                }
189            } catch (InstallerException e) {
190                postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
191                return;
192            }
193        }
194
195        // Open the snapshot and invoke the callback.
196        File snapshotProfile = ArtManager.getProfileSnapshotFileForName(packageName, profileName);
197
198        ParcelFileDescriptor fd = null;
199        try {
200            fd = ParcelFileDescriptor.open(snapshotProfile, ParcelFileDescriptor.MODE_READ_ONLY);
201            postSuccess(packageName, fd, callback);
202        } catch (FileNotFoundException e) {
203            Slog.w(TAG, "Could not open snapshot profile for " + packageName + ":"
204                    + snapshotProfile, e);
205            postError(callback, packageName, ArtManager.SNAPSHOT_FAILED_INTERNAL_ERROR);
206        } finally {
207            IoUtils.closeQuietly(fd);
208        }
209    }
210
211    private void destroyProfileSnapshot(String packageName, String profileName) {
212        if (DEBUG) {
213            Slog.d(TAG, "Destroying profile snapshot for" + packageName + ":" + profileName);
214        }
215
216        synchronized (mInstallLock) {
217            try {
218                mInstaller.destroyProfileSnapshot(packageName, profileName);
219            } catch (InstallerException e) {
220                Slog.e(TAG, "Failed to destroy profile snapshot for " +
221                    packageName + ":" + profileName, e);
222            }
223        }
224    }
225
226    @Override
227    public boolean isRuntimeProfilingEnabled(@ProfileType int profileType) {
228        // Verify that the caller has the right permissions.
229        checkReadRuntimeProfilePermission();
230
231        switch (profileType) {
232            case ArtManager.PROFILE_APPS :
233                return SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
234            case ArtManager.PROFILE_BOOT_IMAGE:
235                return (Build.IS_USERDEBUG || Build.IS_ENG) &&
236                        SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false) &&
237                        SystemProperties.getBoolean("dalvik.vm.profilebootimage", false);
238            default:
239                throw new IllegalArgumentException("Invalid profile type:" + profileType);
240        }
241    }
242
243    private void snapshotBootImageProfile(ISnapshotRuntimeProfileCallback callback) {
244        // Combine the profiles for boot classpath and system server classpath.
245        // This avoids having yet another type of profiles and simplifies the processing.
246        String classpath = String.join(":", Os.getenv("BOOTCLASSPATH"),
247                Os.getenv("SYSTEMSERVERCLASSPATH"));
248
249        // Create the snapshot.
250        createProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME, classpath,
251                /*appId*/ -1, callback);
252        // Destroy the snapshot, we no longer need it.
253        destroyProfileSnapshot(BOOT_IMAGE_ANDROID_PACKAGE, BOOT_IMAGE_PROFILE_NAME);
254    }
255
256    /**
257     * Post {@link ISnapshotRuntimeProfileCallback#onError(int)} with the given error message
258     * on the internal {@code mHandler}.
259     */
260    private void postError(ISnapshotRuntimeProfileCallback callback, String packageName,
261            int errCode) {
262        if (DEBUG) {
263            Slog.d(TAG, "Failed to snapshot profile for " + packageName + " with error: " +
264                    errCode);
265        }
266        mHandler.post(() -> {
267            try {
268                callback.onError(errCode);
269            } catch (RemoteException e) {
270                Slog.w(TAG, "Failed to callback after profile snapshot for " + packageName, e);
271            }
272        });
273    }
274
275    private void postSuccess(String packageName, ParcelFileDescriptor fd,
276            ISnapshotRuntimeProfileCallback callback) {
277        if (DEBUG) {
278            Slog.d(TAG, "Successfully snapshot profile for " + packageName);
279        }
280        mHandler.post(() -> {
281            try {
282                callback.onSuccess(fd);
283            } catch (RemoteException e) {
284                Slog.w(TAG,
285                        "Failed to call onSuccess after profile snapshot for " + packageName, e);
286            }
287        });
288    }
289
290    /**
291     * Verify that the binder calling uid has {@code android.permission.READ_RUNTIME_PROFILE}.
292     * If not, it throws a {@link SecurityException}.
293     */
294    private void checkReadRuntimeProfilePermission() {
295        if (DEBUG_IGNORE_PERMISSIONS) {
296            return;
297        }
298        try {
299            int result = mPackageManager.checkUidPermission(
300                    Manifest.permission.READ_RUNTIME_PROFILES, Binder.getCallingUid());
301            if (result != PackageManager.PERMISSION_GRANTED) {
302                throw new SecurityException("You need "
303                        + Manifest.permission.READ_RUNTIME_PROFILES
304                        + " permission to snapshot profiles.");
305            }
306        } catch (RemoteException e) {
307            // Should not happen.
308        }
309    }
310
311    /**
312     * Prepare the application profiles.
313     * For all code paths:
314     *   - create the current primary profile to save time at app startup time.
315     *   - copy the profiles from the associated dex metadata file to the reference profile.
316     */
317    public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
318        final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
319        if (user < 0) {
320            Slog.wtf(TAG, "Invalid user id: " + user);
321            return;
322        }
323        if (appId < 0) {
324            Slog.wtf(TAG, "Invalid app id: " + appId);
325            return;
326        }
327        try {
328            ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
329            for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
330                String codePath = codePathsProfileNames.keyAt(i);
331                String profileName = codePathsProfileNames.valueAt(i);
332                File dexMetadata = DexMetadataHelper.findDexMetadataForFile(new File(codePath));
333                String dexMetadataPath = dexMetadata == null ? null : dexMetadata.getAbsolutePath();
334                synchronized (mInstaller) {
335                    boolean result = mInstaller.prepareAppProfile(pkg.packageName, user, appId,
336                            profileName, codePath, dexMetadataPath);
337                    if (!result) {
338                        Slog.e(TAG, "Failed to prepare profile for " +
339                                pkg.packageName + ":" + codePath);
340                    }
341                }
342            }
343        } catch (InstallerException e) {
344            Slog.e(TAG, "Failed to prepare profile for " + pkg.packageName, e);
345        }
346    }
347
348    /**
349     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
350     */
351    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
352        for (int i = 0; i < user.length; i++) {
353            prepareAppProfiles(pkg, user[i]);
354        }
355    }
356
357    /**
358     * Clear the profiles for the given package.
359     */
360    public void clearAppProfiles(PackageParser.Package pkg) {
361        try {
362            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
363            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
364                String profileName = packageProfileNames.valueAt(i);
365                mInstaller.clearAppProfiles(pkg.packageName, profileName);
366            }
367        } catch (InstallerException e) {
368            Slog.w(TAG, String.valueOf(e));
369        }
370    }
371
372    /**
373     * Dumps the profiles for the given package.
374     */
375    public void dumpProfiles(PackageParser.Package pkg) {
376        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
377        try {
378            ArrayMap<String, String> packageProfileNames = getPackageProfileNames(pkg);
379            for (int i = packageProfileNames.size() - 1; i >= 0; i--) {
380                String codePath = packageProfileNames.keyAt(i);
381                String profileName = packageProfileNames.valueAt(i);
382                synchronized (mInstallLock) {
383                    mInstaller.dumpProfiles(sharedGid, pkg.packageName, profileName, codePath);
384                }
385            }
386        } catch (InstallerException e) {
387            Slog.w(TAG, "Failed to dump profiles", e);
388        }
389    }
390
391    /**
392     * Build the profiles names for all the package code paths (excluding resource only paths).
393     * Return the map [code path -> profile name].
394     */
395    private ArrayMap<String, String> getPackageProfileNames(PackageParser.Package pkg) {
396        ArrayMap<String, String> result = new ArrayMap<>();
397        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0) {
398            result.put(pkg.baseCodePath, ArtManager.getProfileName(null));
399        }
400        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
401            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
402                if ((pkg.splitFlags[i] & ApplicationInfo.FLAG_HAS_CODE) != 0) {
403                    result.put(pkg.splitCodePaths[i], ArtManager.getProfileName(pkg.splitNames[i]));
404                }
405            }
406        }
407        return result;
408    }
409
410    private class ArtManagerInternalImpl extends ArtManagerInternal {
411        @Override
412        public PackageOptimizationInfo getPackageOptimizationInfo(
413                ApplicationInfo info, String abi) {
414            String compilationReason;
415            String compilationFilter;
416            try {
417                String isa = VMRuntime.getInstructionSet(abi);
418                String[] stats = DexFile.getDexFileOptimizationStatus(info.getBaseCodePath(), isa);
419                compilationFilter = stats[0];
420                compilationReason = stats[1];
421            } catch (FileNotFoundException e) {
422                Slog.e(TAG, "Could not get optimizations status for " + info.getBaseCodePath(), e);
423                compilationFilter = "error";
424                compilationReason = "error";
425            } catch (IllegalArgumentException e) {
426                Slog.wtf(TAG, "Requested optimization status for " + info.getBaseCodePath()
427                        + " due to an invalid abi " + abi, e);
428                compilationFilter = "error";
429                compilationReason = "error";
430            }
431
432            return new PackageOptimizationInfo(compilationFilter, compilationReason);
433        }
434    }
435}
436