• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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