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 com.android.server.pm.dex; 18 19 import android.content.pm.ApplicationInfo; 20 import android.util.Slog; 21 import android.util.SparseArray; 22 23 import com.android.internal.os.ClassLoaderFactory; 24 import com.android.server.pm.PackageDexOptimizer; 25 26 import java.io.File; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.List; 30 31 public final class DexoptUtils { 32 private static final String TAG = "DexoptUtils"; 33 DexoptUtils()34 private DexoptUtils() {} 35 36 /** 37 * Creates the class loader context dependencies for each of the application code paths. 38 * The returned array contains the class loader contexts that needs to be passed to dexopt in 39 * order to ensure correct optimizations. "Code" paths with no actual code, as specified by 40 * {@param pathsWithCode}, are ignored and will have null as their context in the returned array 41 * (configuration splits are an example of paths without code). 42 * 43 * A class loader context describes how the class loader chain should be built by dex2oat 44 * in order to ensure that classes are resolved during compilation as they would be resolved 45 * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is 46 * loaded in a different context (with a different set of class loaders or a different 47 * classpath), the compiled code will be rejected. 48 * 49 * Note that the class loader context only includes dependencies and not the code path itself. 50 * The contexts are created based on the application split dependency list and 51 * the provided shared libraries. 52 * 53 * All the code paths encoded in the context will be relative to the base directory. This 54 * enables stage compilation where compiler artifacts may be moved around. 55 * 56 * The result is indexed as follows: 57 * - index 0 contains the context for the base apk 58 * - index 1 to n contain the context for the splits in the order determined by 59 * {@code info.getSplitCodePaths()} 60 * 61 * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk} 62 * and pay attention to the way the classpath is created for the non isolated mode in: 63 * {@link android.app.LoadedApk#makePaths( 64 * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}. 65 */ getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries, boolean[] pathsWithCode)66 public static String[] getClassLoaderContexts(ApplicationInfo info, 67 String[] sharedLibraries, boolean[] pathsWithCode) { 68 // The base class loader context contains only the shared library. 69 String sharedLibrariesClassPath = encodeClasspath(sharedLibraries); 70 String baseApkContextClassLoader = encodeClassLoader( 71 sharedLibrariesClassPath, info.classLoaderName); 72 73 if (info.getSplitCodePaths() == null) { 74 // The application has no splits. 75 return new String[] {baseApkContextClassLoader}; 76 } 77 78 // The application has splits. Compute their class loader contexts. 79 80 // First, cache the relative paths of the splits and do some sanity checks 81 String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info); 82 83 // The splits have an implicit dependency on the base apk. 84 // This means that we have to add the base apk file in addition to the shared libraries. 85 String baseApkName = new File(info.getBaseCodePath()).getName(); 86 String sharedLibrariesAndBaseClassPath = 87 encodeClasspath(sharedLibrariesClassPath, baseApkName); 88 89 // The result is stored in classLoaderContexts. 90 // Index 0 is the class loaded context for the base apk. 91 // Index `i` is the class loader context encoding for split `i`. 92 String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length]; 93 classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null; 94 95 if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) { 96 // If the app didn't request for the splits to be loaded in isolation or if it does not 97 // declare inter-split dependencies, then all the splits will be loaded in the base 98 // apk class loader (in the order of their definition). 99 String classpath = sharedLibrariesAndBaseClassPath; 100 for (int i = 1; i < classLoaderContexts.length; i++) { 101 classLoaderContexts[i] = pathsWithCode[i] 102 ? encodeClassLoader(classpath, info.classLoaderName) : null; 103 // Note that the splits with no code are not removed from the classpath computation. 104 // i.e. split_n might get the split_n-1 in its classpath dependency even 105 // if split_n-1 has no code. 106 // The splits with no code do not matter for the runtime which ignores 107 // apks without code when doing the classpath checks. As such we could actually 108 // filter them but we don't do it in order to keep consistency with how the apps 109 // are loaded. 110 classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]); 111 } 112 } else { 113 // In case of inter-split dependencies, we need to walk the dependency chain of each 114 // split. We do this recursively and store intermediate results in classLoaderContexts. 115 116 // First, look at the split class loaders and cache their individual contexts (i.e. 117 // the class loader + the name of the split). This is an optimization to avoid 118 // re-computing them during the recursive call. 119 // The cache is stored in splitClassLoaderEncodingCache. The difference between this and 120 // classLoaderContexts is that the later contains the full chain of class loaders for 121 // a given split while splitClassLoaderEncodingCache only contains a single class loader 122 // encoding. 123 String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length]; 124 for (int i = 0; i < splitRelativeCodePaths.length; i++) { 125 splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i], 126 info.splitClassLoaderNames[i]); 127 } 128 String splitDependencyOnBase = encodeClassLoader( 129 sharedLibrariesAndBaseClassPath, info.classLoaderName); 130 SparseArray<int[]> splitDependencies = info.splitDependencies; 131 132 // Note that not all splits have dependencies (e.g. configuration splits) 133 // The splits without dependencies will have classLoaderContexts[config_split_index] 134 // set to null after this step. 135 for (int i = 1; i < splitDependencies.size(); i++) { 136 int splitIndex = splitDependencies.keyAt(i); 137 if (pathsWithCode[splitIndex]) { 138 // Compute the class loader context only for the splits with code. 139 getParentDependencies(splitIndex, splitClassLoaderEncodingCache, 140 splitDependencies, classLoaderContexts, splitDependencyOnBase); 141 } 142 } 143 144 // At this point classLoaderContexts contains only the parent dependencies. 145 // We also need to add the class loader of the current split which should 146 // come first in the context. 147 for (int i = 1; i < classLoaderContexts.length; i++) { 148 String splitClassLoader = encodeClassLoader("", info.splitClassLoaderNames[i - 1]); 149 if (pathsWithCode[i]) { 150 // If classLoaderContexts[i] is null it means that the split does not have 151 // any dependency. In this case its context equals its declared class loader. 152 classLoaderContexts[i] = classLoaderContexts[i] == null 153 ? splitClassLoader 154 : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i]); 155 } else { 156 // This is a split without code, it has no dependency and it is not compiled. 157 // Its context will be null. 158 classLoaderContexts[i] = null; 159 } 160 } 161 } 162 163 return classLoaderContexts; 164 } 165 166 /** 167 * Recursive method to generate the class loader context dependencies for the split with the 168 * given index. {@param classLoaderContexts} acts as an accumulator. Upton return 169 * {@code classLoaderContexts[index]} will contain the split dependency. 170 * During computation, the method may resolve the dependencies of other splits as it traverses 171 * the entire parent chain. The result will also be stored in {@param classLoaderContexts}. 172 * 173 * Note that {@code index 0} denotes the base apk and it is special handled. When the 174 * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}. 175 * {@code classLoaderContexts[0]} is not modified in this method. 176 * 177 * @param index the index of the split (Note that index 0 denotes the base apk) 178 * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits. 179 * It contains only the split class loader and not the the base. The split 180 * with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}. 181 * @param splitDependencies the dependencies for all splits. Note that in this array index 0 182 * is the base and splits start from index 1. 183 * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits 184 * start at index 1. 185 * @param splitDependencyOnBase the encoding of the implicit split dependency on base. 186 */ getParentDependencies(int index, String[] splitClassLoaderEncodingCache, SparseArray<int[]> splitDependencies, String[] classLoaderContexts, String splitDependencyOnBase)187 private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache, 188 SparseArray<int[]> splitDependencies, String[] classLoaderContexts, 189 String splitDependencyOnBase) { 190 // If we hit the base apk return its custom dependency list which is 191 // sharedLibraries + base.apk 192 if (index == 0) { 193 return splitDependencyOnBase; 194 } 195 // Return the result if we've computed the splitDependencies for this index already. 196 if (classLoaderContexts[index] != null) { 197 return classLoaderContexts[index]; 198 } 199 // Get the splitDependencies for the parent of this index and append its path to it. 200 int parent = splitDependencies.get(index)[0]; 201 String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache, 202 splitDependencies, classLoaderContexts, splitDependencyOnBase); 203 204 // The split context is: `parent context + parent dependencies context`. 205 String splitContext = (parent == 0) ? 206 parentDependencies : 207 encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies); 208 classLoaderContexts[index] = splitContext; 209 return splitContext; 210 } 211 212 /** 213 * Encodes the shared libraries classpathElements in a format accepted by dexopt. 214 * NOTE: Keep this in sync with the dexopt expectations! Right now that is 215 * a list separated by ':'. 216 */ encodeClasspath(String[] classpathElements)217 private static String encodeClasspath(String[] classpathElements) { 218 if (classpathElements == null || classpathElements.length == 0) { 219 return ""; 220 } 221 StringBuilder sb = new StringBuilder(); 222 for (String element : classpathElements) { 223 if (sb.length() != 0) { 224 sb.append(":"); 225 } 226 sb.append(element); 227 } 228 return sb.toString(); 229 } 230 231 /** 232 * Adds an element to the encoding of an existing classpath. 233 * {@see PackageDexOptimizer.encodeClasspath(String[])} 234 */ encodeClasspath(String classpath, String newElement)235 private static String encodeClasspath(String classpath, String newElement) { 236 return classpath.isEmpty() ? newElement : (classpath + ":" + newElement); 237 } 238 239 /** 240 * Encodes a single class loader dependency starting from {@param path} and 241 * {@param classLoaderName}. 242 * When classpath is {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns 243 * the same. This special property is used only during OTA. 244 * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]" 245 * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader. 246 */ encodeClassLoader(String classpath, String classLoaderName)247 /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) { 248 if (classpath.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) { 249 return classpath; 250 } 251 String classLoaderDexoptEncoding = classLoaderName; 252 if (ClassLoaderFactory.isPathClassLoaderName(classLoaderName)) { 253 classLoaderDexoptEncoding = "PCL"; 254 } else if (ClassLoaderFactory.isDelegateLastClassLoaderName(classLoaderName)) { 255 classLoaderDexoptEncoding = "DLC"; 256 } else { 257 Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName); 258 } 259 return classLoaderDexoptEncoding + "[" + classpath + "]"; 260 } 261 262 /** 263 * Links to dependencies together in a format accepted by dexopt. 264 * For the special case when either of cl1 or cl2 equals 265 * {@link PackageDexOptimizer#SKIP_SHARED_LIBRARY_CHECK}, the method returns the same. This 266 * property is used only during OTA. 267 * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split 268 * dependencies {@see encodeClassLoader} separated by ';'. 269 */ encodeClassLoaderChain(String cl1, String cl2)270 /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) { 271 if (cl1.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK) || 272 cl2.equals(PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK)) { 273 return PackageDexOptimizer.SKIP_SHARED_LIBRARY_CHECK; 274 } 275 if (cl1.isEmpty()) return cl2; 276 if (cl2.isEmpty()) return cl1; 277 return cl1 + ";" + cl2; 278 } 279 280 /** 281 * Compute the class loader context for the dex files present in the classpath of the first 282 * class loader from the given list (referred in the code as the {@code loadingClassLoader}). 283 * Each dex files gets its own class loader context in the returned array. 284 * 285 * Example: 286 * If classLoadersNames = {"dalvik.system.DelegateLastClassLoader", 287 * "dalvik.system.PathClassLoader"} and classPaths = {"foo.dex:bar.dex", "other.dex"} 288 * The output will be 289 * {"DLC[];PCL[other.dex]", "DLC[foo.dex];PCL[other.dex]"} 290 * with "DLC[];PCL[other.dex]" being the context for "foo.dex" 291 * and "DLC[foo.dex];PCL[other.dex]" the context for "bar.dex". 292 * 293 * If any of the class loaders names is unsupported the method will return null. 294 * 295 * The argument lists must be non empty and of the same size. 296 * 297 * @param classLoadersNames the names of the class loaders present in the loading chain. The 298 * list encodes the class loader chain in the natural order. The first class loader has 299 * the second one as its parent and so on. 300 * @param classPaths the class paths for the elements of {@param classLoadersNames}. The 301 * the first element corresponds to the first class loader and so on. A classpath is 302 * represented as a list of dex files separated by {@code File.pathSeparator}. 303 * The return context will be for the dex files found in the first class path. 304 */ processContextForDexLoad(List<String> classLoadersNames, List<String> classPaths)305 /*package*/ static String[] processContextForDexLoad(List<String> classLoadersNames, 306 List<String> classPaths) { 307 if (classLoadersNames.size() != classPaths.size()) { 308 throw new IllegalArgumentException( 309 "The size of the class loader names and the dex paths do not match."); 310 } 311 if (classLoadersNames.isEmpty()) { 312 throw new IllegalArgumentException("Empty classLoadersNames"); 313 } 314 315 // Compute the context for the parent class loaders. 316 String parentContext = ""; 317 // We know that these lists are actually ArrayLists so getting the elements by index 318 // is fine (they come over binder). Even if something changes we expect the sizes to be 319 // very small and it shouldn't matter much. 320 for (int i = 1; i < classLoadersNames.size(); i++) { 321 if (!ClassLoaderFactory.isValidClassLoaderName(classLoadersNames.get(i))) { 322 return null; 323 } 324 String classpath = encodeClasspath(classPaths.get(i).split(File.pathSeparator)); 325 parentContext = encodeClassLoaderChain(parentContext, 326 encodeClassLoader(classpath, classLoadersNames.get(i))); 327 } 328 329 // Now compute the class loader context for each dex file from the first classpath. 330 String loadingClassLoader = classLoadersNames.get(0); 331 if (!ClassLoaderFactory.isValidClassLoaderName(loadingClassLoader)) { 332 return null; 333 } 334 String[] loadedDexPaths = classPaths.get(0).split(File.pathSeparator); 335 String[] loadedDexPathsContext = new String[loadedDexPaths.length]; 336 String currentLoadedDexPathClasspath = ""; 337 for (int i = 0; i < loadedDexPaths.length; i++) { 338 String dexPath = loadedDexPaths[i]; 339 String currentContext = encodeClassLoader( 340 currentLoadedDexPathClasspath, loadingClassLoader); 341 loadedDexPathsContext[i] = encodeClassLoaderChain(currentContext, parentContext); 342 currentLoadedDexPathClasspath = encodeClasspath(currentLoadedDexPathClasspath, dexPath); 343 } 344 return loadedDexPathsContext; 345 } 346 347 /** 348 * Returns the relative paths of the splits declared by the application {@code info}. 349 * Assumes that the application declares a non-null array of splits. 350 */ getSplitRelativeCodePaths(ApplicationInfo info)351 private static String[] getSplitRelativeCodePaths(ApplicationInfo info) { 352 String baseCodePath = new File(info.getBaseCodePath()).getParent(); 353 String[] splitCodePaths = info.getSplitCodePaths(); 354 String[] splitRelativeCodePaths = new String[splitCodePaths.length]; 355 for (int i = 0; i < splitCodePaths.length; i++) { 356 File pathFile = new File(splitCodePaths[i]); 357 splitRelativeCodePaths[i] = pathFile.getName(); 358 // Sanity check that the base paths of the splits are all the same. 359 String basePath = pathFile.getParent(); 360 if (!basePath.equals(baseCodePath)) { 361 Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + 362 baseCodePath); 363 } 364 } 365 return splitRelativeCodePaths; 366 } 367 } 368