Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 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 | |
| 17 | package android.app; |
| 18 | |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 19 | import android.os.FileUtils; |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 20 | import android.os.RemoteException; |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 21 | import android.os.SystemProperties; |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 22 | import android.util.Slog; |
| 23 | |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 24 | import com.android.internal.annotations.GuardedBy; |
| 25 | |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 26 | import dalvik.system.BaseDexClassLoader; |
| 27 | import dalvik.system.VMRuntime; |
| 28 | |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 29 | import java.io.File; |
| 30 | import java.io.IOException; |
Calin Juravle | a18e992 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 31 | import java.util.ArrayList; |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 32 | import java.util.HashSet; |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 33 | import java.util.List; |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 34 | import java.util.Set; |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 35 | |
| 36 | /** |
| 37 | * A dex load reporter which will notify package manager of any dex file loaded |
| 38 | * with {@code BaseDexClassLoader}. |
| 39 | * The goals are: |
| 40 | * 1) discover secondary dex files so that they can be optimized during the |
| 41 | * idle maintenance job. |
| 42 | * 2) determine whether or not a dex file is used by an app which does not |
| 43 | * own it (in order to select the optimal compilation method). |
| 44 | * @hide |
| 45 | */ |
| 46 | /*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { |
| 47 | private static final String TAG = "DexLoadReporter"; |
| 48 | |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 49 | private static final DexLoadReporter INSTANCE = new DexLoadReporter(); |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 50 | |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 51 | private static final boolean DEBUG = false; |
| 52 | |
| 53 | // We must guard the access to the list of data directories because |
| 54 | // we might have concurrent accesses. Apps might load dex files while |
| 55 | // new data dirs are registered (due to creation of LoadedApks via |
| 56 | // create createApplicationContext). |
| 57 | @GuardedBy("mDataDirs") |
| 58 | private final Set<String> mDataDirs; |
| 59 | |
| 60 | private DexLoadReporter() { |
| 61 | mDataDirs = new HashSet<>(); |
| 62 | } |
| 63 | |
| 64 | /*package*/ static DexLoadReporter getInstance() { |
| 65 | return INSTANCE; |
| 66 | } |
| 67 | |
| 68 | /** |
| 69 | * Register an application data directory with the reporter. |
| 70 | * The data directories are used to determine if a dex file is secondary dex or not. |
| 71 | * Note that this method may be called multiple times for the same app, registering |
| 72 | * different data directories. This may happen when apps share the same user id |
| 73 | * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user |
| 74 | * id, and app1 loads app2 apk, then both data directories will be registered. |
| 75 | */ |
| 76 | /*package*/ void registerAppDataDir(String packageName, String dataDir) { |
| 77 | if (DEBUG) { |
| 78 | Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); |
| 79 | } |
| 80 | // TODO(calin): A few code paths imply that the data dir |
| 81 | // might be null. Investigate when that can happen. |
| 82 | if (dataDir != null) { |
| 83 | synchronized (mDataDirs) { |
| 84 | mDataDirs.add(dataDir); |
| 85 | } |
| 86 | } |
| 87 | } |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 88 | |
| 89 | @Override |
Alan Stokes | b6c3a60 | 2018-11-02 12:10:42 +0000 | [diff] [blame] | 90 | public void report(List<ClassLoader> classLoadersChain, List<String> classPaths) { |
Calin Juravle | cd8fbd25 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 91 | if (classLoadersChain.size() != classPaths.size()) { |
| 92 | Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch"); |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 93 | return; |
| 94 | } |
Calin Juravle | cd8fbd25 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 95 | if (classPaths.isEmpty()) { |
| 96 | Slog.wtf(TAG, "Bad call to DexLoadReporter: empty dex paths"); |
| 97 | return; |
| 98 | } |
| 99 | |
| 100 | // The first element of classPaths is the list of dex files that should be registered. |
| 101 | // The classpath is represented as a list of dex files separated by File.pathSeparator. |
| 102 | String[] dexPathsForRegistration = classPaths.get(0).split(File.pathSeparator); |
| 103 | if (dexPathsForRegistration.length == 0) { |
| 104 | // No dex files to register. |
| 105 | return; |
| 106 | } |
| 107 | |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 108 | // Notify the package manager about the dex loads unconditionally. |
| 109 | // The load might be for either a primary or secondary dex file. |
Calin Juravle | cd8fbd25 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 110 | notifyPackageManager(classLoadersChain, classPaths); |
| 111 | // Check for secondary dex files and register them for profiling if possible. |
| 112 | // Note that we only register the dex paths belonging to the first class loader. |
| 113 | registerSecondaryDexForProfiling(dexPathsForRegistration); |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 114 | } |
| 115 | |
Alan Stokes | b6c3a60 | 2018-11-02 12:10:42 +0000 | [diff] [blame] | 116 | private void notifyPackageManager(List<ClassLoader> classLoadersChain, |
Calin Juravle | cd8fbd25 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 117 | List<String> classPaths) { |
Calin Juravle | a18e992 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 118 | // Get the class loader names for the binder call. |
| 119 | List<String> classLoadersNames = new ArrayList<>(classPaths.size()); |
Alan Stokes | b6c3a60 | 2018-11-02 12:10:42 +0000 | [diff] [blame] | 120 | for (ClassLoader classLoader : classLoadersChain) { |
| 121 | classLoadersNames.add(classLoader.getClass().getName()); |
Calin Juravle | a18e992 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 122 | } |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 123 | String packageName = ActivityThread.currentPackageName(); |
| 124 | try { |
| 125 | ActivityThread.getPackageManager().notifyDexLoad( |
Calin Juravle | a18e992 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 126 | packageName, classLoadersNames, classPaths, |
Calin Juravle | cd8fbd25 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 127 | VMRuntime.getRuntime().vmInstructionSet()); |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 128 | } catch (RemoteException re) { |
| 129 | Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); |
| 130 | } |
| 131 | } |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 132 | |
Calin Juravle | cd8fbd25 | 2017-07-22 12:33:41 -0700 | [diff] [blame] | 133 | private void registerSecondaryDexForProfiling(String[] dexPaths) { |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 134 | if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { |
| 135 | return; |
| 136 | } |
| 137 | // Make a copy of the current data directories so that we don't keep the lock |
| 138 | // while registering for profiling. The registration will perform I/O to |
| 139 | // check for or create the profile. |
| 140 | String[] dataDirs; |
| 141 | synchronized (mDataDirs) { |
| 142 | dataDirs = mDataDirs.toArray(new String[0]); |
| 143 | } |
| 144 | for (String dexPath : dexPaths) { |
| 145 | registerSecondaryDexForProfiling(dexPath, dataDirs); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { |
| 150 | if (!isSecondaryDexFile(dexPath, dataDirs)) { |
| 151 | // The dex path is not a secondary dex file. Nothing to do. |
| 152 | return; |
| 153 | } |
Calin Juravle | 6214f1b | 2017-07-27 16:05:25 -0700 | [diff] [blame] | 154 | |
Calin Juravle | d35451d | 2017-09-04 17:32:23 -0700 | [diff] [blame] | 155 | // Secondary dex profiles are stored in the oat directory, next to dex file |
| 156 | // and have the same name with 'cur.prof' appended. |
Calin Juravle | 6214f1b | 2017-07-27 16:05:25 -0700 | [diff] [blame] | 157 | // NOTE: Keep this in sync with installd expectations. |
Calin Juravle | d35451d | 2017-09-04 17:32:23 -0700 | [diff] [blame] | 158 | File dexPathFile = new File(dexPath); |
| 159 | File secondaryProfileDir = new File(dexPathFile.getParent(), "oat"); |
| 160 | File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof"); |
Calin Juravle | 6214f1b | 2017-07-27 16:05:25 -0700 | [diff] [blame] | 161 | |
| 162 | // Create the profile if not already there. |
| 163 | // Returns true if the file was created, false if the file already exists. |
| 164 | // or throws exceptions in case of errors. |
| 165 | if (!secondaryProfileDir.exists()) { |
| 166 | if (!secondaryProfileDir.mkdir()) { |
| 167 | Slog.e(TAG, "Could not create the profile directory: " + secondaryProfile); |
| 168 | // Do not continue with registration if we could not create the oat dir. |
| 169 | return; |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | try { |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 174 | boolean created = secondaryProfile.createNewFile(); |
| 175 | if (DEBUG && created) { |
| 176 | Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); |
| 177 | } |
| 178 | } catch (IOException ex) { |
Calin Juravle | 6214f1b | 2017-07-27 16:05:25 -0700 | [diff] [blame] | 179 | Slog.e(TAG, "Failed to create profile for secondary dex " + dexPath |
| 180 | + ":" + ex.getMessage()); |
| 181 | // Do not continue with registration if we could not create the profile files. |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 182 | return; |
| 183 | } |
| 184 | |
Calin Juravle | 6214f1b | 2017-07-27 16:05:25 -0700 | [diff] [blame] | 185 | // If we got here, the dex paths is a secondary dex and we were able to create the profile. |
| 186 | // Register the path to the runtime. |
Calin Juravle | a9f46f7 | 2017-03-13 23:30:30 -0700 | [diff] [blame] | 187 | VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); |
| 188 | } |
| 189 | |
| 190 | // A dex file is a secondary dex file if it is in any of the registered app |
| 191 | // data directories. |
| 192 | private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { |
| 193 | for (String dataDir : dataDirs) { |
| 194 | if (FileUtils.contains(dataDir, dexPath)) { |
| 195 | return true; |
| 196 | } |
| 197 | } |
| 198 | return false; |
| 199 | } |
Calin Juravle | d9732c8 | 2017-03-13 19:02:32 -0700 | [diff] [blame] | 200 | } |