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