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