1 /* 2 * Copyright (C) 2022 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.art; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.Build; 22 import android.os.UserHandle; 23 import android.os.UserManager; 24 import android.text.TextUtils; 25 26 import androidx.annotation.RequiresApi; 27 28 import com.android.internal.annotations.Immutable; 29 import com.android.server.art.model.DetailedDexInfo; 30 import com.android.server.pm.pkg.AndroidPackage; 31 import com.android.server.pm.pkg.AndroidPackageSplit; 32 import com.android.server.pm.pkg.PackageState; 33 import com.android.server.pm.pkg.PackageUserState; 34 import com.android.server.pm.pkg.SharedLibrary; 35 36 import dalvik.system.DelegateLastClassLoader; 37 import dalvik.system.DexClassLoader; 38 import dalvik.system.PathClassLoader; 39 40 import java.io.File; 41 import java.util.ArrayList; 42 import java.util.List; 43 import java.util.Objects; 44 import java.util.stream.Collectors; 45 46 /** @hide */ 47 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 48 public class PrimaryDexUtils { 49 public static final String PROFILE_PRIMARY = "primary"; 50 private static final String SHARED_LIBRARY_LOADER_TYPE = PathClassLoader.class.getName(); 51 52 /** 53 * Returns the basic information about all primary dex files belonging to the package. The 54 * return value is a list where the entry at index 0 is the information about the base APK, and 55 * the entry at index i is the information about the (i-1)-th split APK. 56 */ 57 @NonNull getDexInfo(@onNull AndroidPackage pkg)58 public static List<PrimaryDexInfo> getDexInfo(@NonNull AndroidPackage pkg) { 59 return getDexInfoImpl(pkg) 60 .stream() 61 .map(builder -> builder.build()) 62 .collect(Collectors.toList()); 63 } 64 65 /** 66 * Same as above, but requires {@link PackageState} in addition, and returns the detailed 67 * information, including the class loader context. 68 */ 69 @NonNull getDetailedDexInfo( @onNull PackageState pkgState, @NonNull AndroidPackage pkg)70 public static List<DetailedPrimaryDexInfo> getDetailedDexInfo( 71 @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) { 72 return getDetailedDexInfoImpl(pkgState, pkg) 73 .stream() 74 .map(builder -> builder.buildDetailed()) 75 .collect(Collectors.toList()); 76 } 77 78 /** Returns the basic information about a dex file specified by {@code splitName}. */ 79 @NonNull getDexInfoBySplitName( @onNull AndroidPackage pkg, @Nullable String splitName)80 public static PrimaryDexInfo getDexInfoBySplitName( 81 @NonNull AndroidPackage pkg, @Nullable String splitName) { 82 if (splitName == null) { 83 return getDexInfo(pkg).get(0); 84 } else { 85 return getDexInfo(pkg) 86 .stream() 87 .filter(info -> splitName.equals(info.splitName())) 88 .findFirst() 89 .orElseThrow(() -> { 90 return new IllegalArgumentException( 91 String.format("Split '%s' not found", splitName)); 92 }); 93 } 94 } 95 96 @NonNull getDexInfoImpl(@onNull AndroidPackage pkg)97 private static List<PrimaryDexInfoBuilder> getDexInfoImpl(@NonNull AndroidPackage pkg) { 98 List<PrimaryDexInfoBuilder> dexInfos = new ArrayList<>(); 99 100 for (var split : pkg.getSplits()) { 101 dexInfos.add(new PrimaryDexInfoBuilder(split)); 102 } 103 104 return dexInfos; 105 } 106 107 @NonNull getDetailedDexInfoImpl( @onNull PackageState pkgState, @NonNull AndroidPackage pkg)108 private static List<PrimaryDexInfoBuilder> getDetailedDexInfoImpl( 109 @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) { 110 List<PrimaryDexInfoBuilder> dexInfos = getDexInfoImpl(pkg); 111 112 PrimaryDexInfoBuilder baseApk = dexInfos.get(0); 113 baseApk.mClassLoaderName = baseApk.mSplit.getClassLoaderName(); 114 File baseDexFile = new File(baseApk.mSplit.getPath()); 115 baseApk.mRelativeDexPath = baseDexFile.getName(); 116 117 // Shared libraries are the dependencies of the base APK. 118 baseApk.mSharedLibrariesContext = 119 encodeSharedLibraries(pkgState.getSharedLibraryDependencies()); 120 121 boolean isIsolatedSplitLoading = isIsolatedSplitLoading(pkg); 122 123 for (int i = 1; i < dexInfos.size(); i++) { 124 var dexInfoBuilder = dexInfos.get(i); 125 File splitDexFile = new File(dexInfoBuilder.mSplit.getPath()); 126 if (!splitDexFile.getParent().equals(baseDexFile.getParent())) { 127 throw new IllegalStateException( 128 "Split APK and base APK are in different directories: " 129 + splitDexFile.getParent() + " != " + baseDexFile.getParent()); 130 } 131 dexInfoBuilder.mRelativeDexPath = splitDexFile.getName(); 132 if (isIsolatedSplitLoading && dexInfoBuilder.mSplit.isHasCode()) { 133 dexInfoBuilder.mClassLoaderName = dexInfoBuilder.mSplit.getClassLoaderName(); 134 135 List<AndroidPackageSplit> dependencies = dexInfoBuilder.mSplit.getDependencies(); 136 if (!Utils.isEmpty(dependencies)) { 137 // We only care about the first dependency because it is the parent split. The 138 // rest are configuration splits, which we don't care. 139 AndroidPackageSplit dependency = dependencies.get(0); 140 for (var dexInfo : dexInfos) { 141 if (Objects.equals(dexInfo.mSplit, dependency)) { 142 dexInfoBuilder.mSplitDependency = dexInfo; 143 break; 144 } 145 } 146 147 if (dexInfoBuilder.mSplitDependency == null) { 148 throw new IllegalStateException( 149 "Split dependency not found for " + splitDexFile); 150 } 151 } 152 } 153 } 154 155 if (isIsolatedSplitLoading) { 156 computeClassLoaderContextsIsolated(dexInfos); 157 } else { 158 computeClassLoaderContexts(dexInfos); 159 } 160 161 return dexInfos; 162 } 163 164 /** 165 * Computes class loader context for an app that didn't request isolated split loading. Stores 166 * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}. 167 * 168 * In this case, all the splits will be loaded in the base apk class loader (in the order of 169 * their definition). 170 * 171 * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK is 172 * `CLN[base.apk, split_0.apk, ..., split_n-1.apk]{shared-libraries}`; where `CLN` is the 173 * class loader name for the base APK. 174 */ computeClassLoaderContexts(@onNull List<PrimaryDexInfoBuilder> dexInfos)175 private static void computeClassLoaderContexts(@NonNull List<PrimaryDexInfoBuilder> dexInfos) { 176 String baseClassLoaderName = dexInfos.get(0).mClassLoaderName; 177 String sharedLibrariesContext = dexInfos.get(0).mSharedLibrariesContext; 178 List<String> classpath = new ArrayList<>(); 179 for (PrimaryDexInfoBuilder dexInfo : dexInfos) { 180 if (dexInfo.mSplit.isHasCode()) { 181 dexInfo.mClassLoaderContext = encodeClassLoader(baseClassLoaderName, classpath, 182 null /* parentContext */, sharedLibrariesContext); 183 } 184 // Note that the splits with no code are not removed from the classpath computation. 185 // I.e., split_n might get the split_n-1 in its classpath dependency even if split_n-1 186 // has no code. 187 // The splits with no code do not matter for the runtime which ignores APKs without code 188 // when doing the classpath checks. As such we could actually filter them but we don't 189 // do it in order to keep consistency with how the apps are loaded. 190 classpath.add(dexInfo.mRelativeDexPath); 191 } 192 } 193 194 /** 195 * Computes class loader context for an app that requested for isolated split loading. Stores 196 * the results in {@link PrimaryDexInfoBuilder#mClassLoaderContext}. 197 * 198 * In this case, each split will be loaded with a separate class loader, whose context is a 199 * chain formed from inter-split dependencies. 200 * 201 * The CLC for the base APK is `CLN[]{shared-libraries}`; the CLC for the n-th split APK that 202 * depends on the base APK is `CLN_n[];CLN[base.apk]{shared-libraries}`; the CLC for the n-th 203 * split APK that depends on the m-th split APK is 204 * `CLN_n[];CLN_m[split_m.apk];...;CLN[base.apk]{shared-libraries}`; where `CLN` is the base 205 * class loader name for the base APK, `CLN_i` is the class loader name for the i-th split APK, 206 * and `...` represents the ancestors along the dependency chain. 207 * 208 * Specially, if a split does not have any dependency, the CLC for it is `CLN_n[]`. 209 */ computeClassLoaderContextsIsolated( @onNull List<PrimaryDexInfoBuilder> dexInfos)210 private static void computeClassLoaderContextsIsolated( 211 @NonNull List<PrimaryDexInfoBuilder> dexInfos) { 212 for (PrimaryDexInfoBuilder dexInfo : dexInfos) { 213 if (dexInfo.mSplit.isHasCode()) { 214 dexInfo.mClassLoaderContext = encodeClassLoader(dexInfo.mClassLoaderName, 215 null /* classpath */, getParentContextRecursive(dexInfo), 216 dexInfo.mSharedLibrariesContext); 217 } 218 } 219 } 220 221 /** 222 * Computes the parent class loader context, recursively. Caches results in {@link 223 * PrimaryDexInfoBuilder#mContextForChildren}. 224 */ 225 @Nullable getParentContextRecursive(@onNull PrimaryDexInfoBuilder dexInfo)226 private static String getParentContextRecursive(@NonNull PrimaryDexInfoBuilder dexInfo) { 227 if (dexInfo.mSplitDependency == null) { 228 return null; 229 } 230 PrimaryDexInfoBuilder parent = dexInfo.mSplitDependency; 231 if (parent.mContextForChildren == null) { 232 parent.mContextForChildren = 233 encodeClassLoader(parent.mClassLoaderName, List.of(parent.mRelativeDexPath), 234 getParentContextRecursive(parent), parent.mSharedLibrariesContext); 235 } 236 return parent.mContextForChildren; 237 } 238 239 /** 240 * Returns class loader context in the format of 241 * `CLN[classpath...]{share-libraries};parent-context`, where `CLN` is the class loader name. 242 */ 243 @NonNull encodeClassLoader(@ullable String classLoaderName, @Nullable List<String> classpath, @Nullable String parentContext, @Nullable String sharedLibrariesContext)244 private static String encodeClassLoader(@Nullable String classLoaderName, 245 @Nullable List<String> classpath, @Nullable String parentContext, 246 @Nullable String sharedLibrariesContext) { 247 StringBuilder classLoaderContext = new StringBuilder(); 248 249 classLoaderContext.append(encodeClassLoaderName(classLoaderName)); 250 251 classLoaderContext.append( 252 "[" + (classpath != null ? String.join(":", classpath) : "") + "]"); 253 254 if (!TextUtils.isEmpty(sharedLibrariesContext)) { 255 classLoaderContext.append(sharedLibrariesContext); 256 } 257 258 if (!TextUtils.isEmpty(parentContext)) { 259 classLoaderContext.append(";" + parentContext); 260 } 261 262 return classLoaderContext.toString(); 263 } 264 265 @NonNull encodeClassLoaderName(@ullable String classLoaderName)266 private static String encodeClassLoaderName(@Nullable String classLoaderName) { 267 // `PathClassLoader` and `DexClassLoader` are grouped together because they have the same 268 // behavior. For null values we default to "PCL". This covers the case where a package does 269 // not specify any value for its class loader. 270 if (classLoaderName == null || PathClassLoader.class.getName().equals(classLoaderName) 271 || DexClassLoader.class.getName().equals(classLoaderName)) { 272 return "PCL"; 273 } else if (DelegateLastClassLoader.class.getName().equals(classLoaderName)) { 274 return "DLC"; 275 } else { 276 throw new IllegalStateException("Unsupported classLoaderName: " + classLoaderName); 277 } 278 } 279 280 /** 281 * Returns shared libraries context in the format of 282 * `{PCL[library_1_dex_1.jar:library_1_dex_2.jar:...]{library_1-dependencies}#PCL[ 283 * library_1_dex_2.jar:library_2_dex_2.jar:...]{library_2-dependencies}#...}`. 284 */ 285 @Nullable encodeSharedLibraries(@ullable List<SharedLibrary> sharedLibraries)286 private static String encodeSharedLibraries(@Nullable List<SharedLibrary> sharedLibraries) { 287 if (Utils.isEmpty(sharedLibraries)) { 288 return null; 289 } 290 return sharedLibraries.stream() 291 .filter(library -> !library.isNative()) 292 .map(library 293 -> encodeClassLoader(SHARED_LIBRARY_LOADER_TYPE, library.getAllCodePaths(), 294 null /* parentContext */, 295 encodeSharedLibraries(library.getDependencies()))) 296 .collect(Collectors.joining("#", "{", "}")); 297 } 298 isIsolatedSplitLoading(@onNull AndroidPackage pkg)299 public static boolean isIsolatedSplitLoading(@NonNull AndroidPackage pkg) { 300 return pkg.isIsolatedSplitLoading() && pkg.getSplits().size() > 1; 301 } 302 303 @NonNull buildRefProfilePath( @onNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo)304 public static ProfilePath buildRefProfilePath( 305 @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) { 306 String profileName = getProfileName(dexInfo.splitName()); 307 return AidlUtils.buildProfilePathForPrimaryRef(pkgState.getPackageName(), profileName); 308 } 309 310 @NonNull buildOutputProfile(@onNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo, int uid, int gid, boolean isPublic)311 public static OutputProfile buildOutputProfile(@NonNull PackageState pkgState, 312 @NonNull PrimaryDexInfo dexInfo, int uid, int gid, boolean isPublic) { 313 String profileName = getProfileName(dexInfo.splitName()); 314 return AidlUtils.buildOutputProfileForPrimary( 315 pkgState.getPackageName(), profileName, uid, gid, isPublic); 316 } 317 318 @NonNull getCurProfiles(@onNull UserManager userManager, @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo)319 public static List<ProfilePath> getCurProfiles(@NonNull UserManager userManager, 320 @NonNull PackageState pkgState, @NonNull PrimaryDexInfo dexInfo) { 321 List<ProfilePath> profiles = new ArrayList<>(); 322 for (UserHandle handle : userManager.getUserHandles(true /* excludeDying */)) { 323 int userId = handle.getIdentifier(); 324 PackageUserState userState = pkgState.getStateForUser(handle); 325 if (userState.isInstalled()) { 326 profiles.add(AidlUtils.buildProfilePathForPrimaryCur( 327 userId, pkgState.getPackageName(), getProfileName(dexInfo.splitName()))); 328 } 329 } 330 return profiles; 331 } 332 333 @NonNull getProfileName(@ullable String splitName)334 public static String getProfileName(@Nullable String splitName) { 335 return splitName == null ? PROFILE_PRIMARY : splitName + ".split"; 336 } 337 338 @NonNull getExternalProfiles(@onNull PrimaryDexInfo dexInfo)339 public static List<ProfilePath> getExternalProfiles(@NonNull PrimaryDexInfo dexInfo) { 340 return List.of(AidlUtils.buildProfilePathForPrebuilt(dexInfo.dexPath()), 341 AidlUtils.buildProfilePathForDm(dexInfo.dexPath())); 342 } 343 344 /** Basic information about a primary dex file (either the base APK or a split APK). */ 345 @Immutable 346 public static class PrimaryDexInfo { 347 private final @NonNull AndroidPackageSplit mSplit; 348 PrimaryDexInfo(@onNull AndroidPackageSplit split)349 PrimaryDexInfo(@NonNull AndroidPackageSplit split) { 350 mSplit = split; 351 } 352 353 /** The path to the dex file. */ dexPath()354 public @NonNull String dexPath() { 355 return mSplit.getPath(); 356 } 357 358 /** True if the dex file has code. */ hasCode()359 public boolean hasCode() { 360 return mSplit.isHasCode(); 361 } 362 363 /** The name of the split, or null for base APK. */ splitName()364 public @Nullable String splitName() { 365 return mSplit.getName(); 366 } 367 } 368 369 /** 370 * Detailed information about a primary dex file (either the base APK or a split APK). It 371 * contains the class loader context in addition to what is in {@link PrimaryDexInfo}, but 372 * producing it requires {@link PackageState}. 373 */ 374 @Immutable 375 public static class DetailedPrimaryDexInfo extends PrimaryDexInfo implements DetailedDexInfo { 376 private final @Nullable String mClassLoaderContext; 377 DetailedPrimaryDexInfo( @onNull AndroidPackageSplit split, @Nullable String classLoaderContext)378 DetailedPrimaryDexInfo( 379 @NonNull AndroidPackageSplit split, @Nullable String classLoaderContext) { 380 super(split); 381 mClassLoaderContext = classLoaderContext; 382 } 383 384 /** 385 * A string describing the structure of the class loader that the dex file is loaded with. 386 */ classLoaderContext()387 public @Nullable String classLoaderContext() { 388 return mClassLoaderContext; 389 } 390 } 391 392 private static class PrimaryDexInfoBuilder { 393 @NonNull AndroidPackageSplit mSplit; 394 @Nullable String mRelativeDexPath = null; 395 @Nullable String mClassLoaderContext = null; 396 @Nullable String mClassLoaderName = null; 397 @Nullable PrimaryDexInfoBuilder mSplitDependency = null; 398 /** The class loader context of the shared libraries. Only applicable for the base APK. */ 399 @Nullable String mSharedLibrariesContext = null; 400 /** The class loader context for children to use when this dex file is used as a parent. */ 401 @Nullable String mContextForChildren = null; 402 PrimaryDexInfoBuilder(@onNull AndroidPackageSplit split)403 PrimaryDexInfoBuilder(@NonNull AndroidPackageSplit split) { 404 mSplit = split; 405 } 406 build()407 PrimaryDexInfo build() { 408 return new PrimaryDexInfo(mSplit); 409 } 410 buildDetailed()411 DetailedPrimaryDexInfo buildDetailed() { 412 return new DetailedPrimaryDexInfo(mSplit, mClassLoaderContext); 413 } 414 } 415 } 416