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.ArrayList;
32 import java.util.HashSet;
33 import java.util.List;
34 import java.util.Set;
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 
49     private static final DexLoadReporter INSTANCE = new DexLoadReporter();
50 
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 
DexLoadReporter()60     private DexLoadReporter() {
61         mDataDirs = new HashSet<>();
62     }
63 
getInstance()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      */
registerAppDataDir(String packageName, String dataDir)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     }
88 
89     @Override
report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths)90     public void report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths) {
91         if (classLoadersChain.size() != classPaths.size()) {
92             Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch");
93             return;
94         }
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 
108         // Notify the package manager about the dex loads unconditionally.
109         // The load might be for either a primary or secondary dex file.
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);
114     }
115 
notifyPackageManager(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths)116     private void notifyPackageManager(List<BaseDexClassLoader> classLoadersChain,
117             List<String> classPaths) {
118         // Get the class loader names for the binder call.
119         List<String> classLoadersNames = new ArrayList<>(classPaths.size());
120         for (BaseDexClassLoader bdc : classLoadersChain) {
121             classLoadersNames.add(bdc.getClass().getName());
122         }
123         String packageName = ActivityThread.currentPackageName();
124         try {
125             ActivityThread.getPackageManager().notifyDexLoad(
126                     packageName, classLoadersNames, classPaths,
127                     VMRuntime.getRuntime().vmInstructionSet());
128         } catch (RemoteException re) {
129             Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
130         }
131     }
132 
registerSecondaryDexForProfiling(String[] dexPaths)133     private void registerSecondaryDexForProfiling(String[] dexPaths) {
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 
registerSecondaryDexForProfiling(String dexPath, String[] dataDirs)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         }
154 
155         // Secondary dex profiles are stored in the oat directory, next to dex file
156         // and have the same name with 'cur.prof' appended.
157         // NOTE: Keep this in sync with installd expectations.
158         File dexPathFile = new File(dexPath);
159         File secondaryProfileDir = new File(dexPathFile.getParent(), "oat");
160         File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof");
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 {
174             boolean created = secondaryProfile.createNewFile();
175             if (DEBUG && created) {
176                 Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile);
177             }
178         } catch (IOException ex) {
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.
182             return;
183         }
184 
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.
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.
isSecondaryDexFile(String dexPath, String[] dataDirs)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     }
200 }
201