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 static com.android.server.art.ProfilePath.TmpProfilePath; 20 21 import android.R; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.role.RoleManager; 25 import android.apphibernation.AppHibernationManager; 26 import android.content.Context; 27 import android.os.Build; 28 import android.os.DeadObjectException; 29 import android.os.RemoteException; 30 import android.os.ServiceSpecificException; 31 import android.os.SystemClock; 32 import android.os.SystemProperties; 33 import android.os.Trace; 34 import android.os.UserManager; 35 import android.text.TextUtils; 36 import android.util.Log; 37 import android.util.Pair; 38 import android.util.Slog; 39 import android.util.SparseArray; 40 41 import androidx.annotation.RequiresApi; 42 43 import com.android.modules.utils.pm.PackageStateModulesUtils; 44 import com.android.server.art.model.DexoptParams; 45 import com.android.server.pm.PackageManagerLocal; 46 import com.android.server.pm.pkg.AndroidPackage; 47 import com.android.server.pm.pkg.PackageState; 48 49 import dalvik.system.DexFile; 50 import dalvik.system.VMRuntime; 51 52 import com.google.auto.value.AutoValue; 53 54 import java.io.File; 55 import java.io.IOException; 56 import java.nio.file.Files; 57 import java.nio.file.Path; 58 import java.util.ArrayList; 59 import java.util.Collection; 60 import java.util.Comparator; 61 import java.util.List; 62 import java.util.Set; 63 import java.util.concurrent.CompletableFuture; 64 import java.util.concurrent.ExecutionException; 65 import java.util.concurrent.Executor; 66 import java.util.concurrent.Future; 67 import java.util.stream.Collectors; 68 69 /** @hide */ 70 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 71 public final class Utils { 72 public static final String TAG = ArtManagerLocal.TAG; 73 public static final String PLATFORM_PACKAGE_NAME = "android"; 74 75 /** A copy of {@link android.os.Trace.TRACE_TAG_DALVIK}. */ 76 private static final long TRACE_TAG_DALVIK = 1L << 14; 77 Utils()78 private Utils() {} 79 80 /** 81 * Checks if given array is null or has zero elements. 82 */ isEmpty(@ullable Collection<T> array)83 public static <T> boolean isEmpty(@Nullable Collection<T> array) { 84 return array == null || array.isEmpty(); 85 } 86 87 /** 88 * Checks if given array is null or has zero elements. 89 */ isEmpty(@ullable SparseArray<T> array)90 public static <T> boolean isEmpty(@Nullable SparseArray<T> array) { 91 return array == null || array.size() == 0; 92 } 93 94 /** 95 * Checks if given array is null or has zero elements. 96 */ isEmpty(@ullable int[] array)97 public static boolean isEmpty(@Nullable int[] array) { 98 return array == null || array.length == 0; 99 } 100 101 /** Returns the ABI information for the package. The primary ABI comes first. */ 102 @NonNull getAllAbis(@onNull PackageState pkgState)103 public static List<Abi> getAllAbis(@NonNull PackageState pkgState) { 104 List<Abi> abis = new ArrayList<>(); 105 abis.add(getPrimaryAbi(pkgState)); 106 String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); 107 String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); 108 if (pkgSecondaryCpuAbi != null) { 109 Utils.check(pkgState.getPrimaryCpuAbi() != null); 110 String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi)); 111 abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */)); 112 } 113 // Primary and secondary ABIs should be guaranteed to have different ISAs. 114 if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) { 115 throw new IllegalStateException(String.format( 116 "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')", 117 pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name())); 118 } 119 return abis; 120 } 121 122 /** 123 * Returns the ABI information for the ABIs with the given names. The primary ABI comes first, 124 * if given. 125 */ 126 @NonNull getAllAbisForNames( @onNull Set<String> abiNames, @NonNull PackageState pkgState)127 public static List<Abi> getAllAbisForNames( 128 @NonNull Set<String> abiNames, @NonNull PackageState pkgState) { 129 Utils.check(abiNames.stream().allMatch(Utils::isNativeAbi)); 130 Abi pkgPrimaryAbi = getPrimaryAbi(pkgState); 131 return abiNames.stream() 132 .map(name 133 -> Abi.create(name, VMRuntime.getInstructionSet(name), 134 name.equals(pkgPrimaryAbi.name()))) 135 .sorted(Comparator.comparing(Abi::isPrimaryAbi).reversed()) 136 .collect(Collectors.toList()); 137 } 138 139 @NonNull getPrimaryAbi(@onNull PackageState pkgState)140 public static Abi getPrimaryAbi(@NonNull PackageState pkgState) { 141 String primaryCpuAbi = pkgState.getPrimaryCpuAbi(); 142 if (primaryCpuAbi != null) { 143 String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi)); 144 return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */); 145 } 146 // This is the most common case. The package manager can't infer the ABIs, probably because 147 // the package doesn't contain any native library. The app is launched with the device's 148 // preferred ABI. 149 String preferredAbi = Constants.getPreferredAbi(); 150 Utils.check(isNativeAbi(preferredAbi)); 151 return Abi.create( 152 preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */); 153 } 154 155 /** 156 * If the given ISA isn't native to the device, returns the ISA that the native bridge 157 * translates it to. Otherwise, returns the ISA as is. This is the ISA that the app is actually 158 * launched with and therefore the ISA that should be used to compile the app. 159 */ 160 @NonNull getTranslatedIsa(@onNull String isa)161 private static String getTranslatedIsa(@NonNull String isa) { 162 String abi64 = Constants.getNative64BitAbi(); 163 String abi32 = Constants.getNative32BitAbi(); 164 if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) 165 || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) { 166 return isa; 167 } 168 String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa); 169 if (TextUtils.isEmpty(translatedIsa)) { 170 throw new IllegalStateException(String.format("Unsupported isa '%s'", isa)); 171 } 172 return translatedIsa; 173 } 174 175 @NonNull nativeIsaToAbi(@onNull String isa)176 private static String nativeIsaToAbi(@NonNull String isa) { 177 String abi64 = Constants.getNative64BitAbi(); 178 if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) { 179 return abi64; 180 } 181 String abi32 = Constants.getNative32BitAbi(); 182 if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) { 183 return abi32; 184 } 185 throw new IllegalStateException(String.format("Non-native isa '%s'", isa)); 186 } 187 isNativeAbi(@onNull String abiName)188 private static boolean isNativeAbi(@NonNull String abiName) { 189 return abiName.equals(Constants.getNative64BitAbi()) 190 || abiName.equals(Constants.getNative32BitAbi()); 191 } 192 193 /** 194 * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache 195 * directory. 196 * 197 * This method is not needed for secondary dex files because they are always in writable 198 * locations. 199 */ 200 @NonNull isInDalvikCache(@onNull PackageState pkgState, @NonNull IArtd artd)201 public static boolean isInDalvikCache(@NonNull PackageState pkgState, @NonNull IArtd artd) 202 throws RemoteException { 203 // The artifacts should be in the global dalvik-cache directory if: 204 // (1). the package is on a system partition, even if the partition is remounted read-write, 205 // or 206 // (2). the package is in any other readonly location. (At the time of writing, this only 207 // include Incremental FS.) 208 // 209 // Right now, we are using some heuristics to determine this. For (1), we can potentially 210 // use "libfstab" instead as a general solution, but for (2), unfortunately, we have to 211 // stick with heuristics. 212 // 213 // We cannot rely on access(2) because: 214 // - It doesn't take effective capabilities into account, from which artd gets root access 215 // to the filesystem. 216 // - The `faccessat` variant with the `AT_EACCESS` flag, which takes effective capabilities 217 // into account, is not supported by bionic. 218 // 219 // We cannot rely on `f_flags` returned by statfs(2) because: 220 // - Incremental FS is tagged as read-write while it's actually not. 221 return (pkgState.isSystem() && !pkgState.isUpdatedSystemApp()) 222 || artd.isIncrementalFsPath( 223 pkgState.getAndroidPackage().getSplits().get(0).getPath()); 224 } 225 226 /** Returns true if the given string is a valid compiler filter. */ isValidArtServiceCompilerFilter(@onNull String compilerFilter)227 public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) { 228 if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) { 229 return true; 230 } 231 return DexFile.isValidCompilerFilter(compilerFilter); 232 } 233 234 @NonNull getArtd()235 public static IArtd getArtd() { 236 IArtd artd = IArtd.Stub.asInterface(ArtModuleServiceInitializer.getArtModuleServiceManager() 237 .getArtdServiceRegisterer() 238 .waitForService()); 239 if (artd == null) { 240 throw new IllegalStateException("Unable to connect to artd"); 241 } 242 return artd; 243 } 244 implies(boolean cond1, boolean cond2)245 public static boolean implies(boolean cond1, boolean cond2) { 246 return cond1 ? cond2 : true; 247 } 248 check(boolean cond)249 public static void check(boolean cond) { 250 // This cannot be replaced with `assert` because `assert` is not enabled in Android. 251 if (!cond) { 252 throw new IllegalStateException("Check failed"); 253 } 254 } 255 256 @NonNull getPackageStateOrThrow( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName)257 public static PackageState getPackageStateOrThrow( 258 @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { 259 PackageState pkgState = snapshot.getPackageState(packageName); 260 if (pkgState == null) { 261 throw new IllegalArgumentException("Package not found: " + packageName); 262 } 263 return pkgState; 264 } 265 266 @NonNull getPackageOrThrow(@onNull PackageState pkgState)267 public static AndroidPackage getPackageOrThrow(@NonNull PackageState pkgState) { 268 AndroidPackage pkg = pkgState.getAndroidPackage(); 269 if (pkg == null) { 270 throw new IllegalArgumentException( 271 "Unable to get package " + pkgState.getPackageName()); 272 } 273 return pkg; 274 } 275 276 @NonNull assertNonEmpty(@ullable String str)277 public static String assertNonEmpty(@Nullable String str) { 278 if (TextUtils.isEmpty(str)) { 279 throw new IllegalArgumentException(); 280 } 281 return str; 282 } 283 executeAndWait(@onNull Executor executor, @NonNull Runnable runnable)284 public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) { 285 getFuture(CompletableFuture.runAsync(runnable, executor)); 286 } 287 getFuture(Future<T> future)288 public static <T> T getFuture(Future<T> future) { 289 try { 290 return future.get(); 291 } catch (ExecutionException e) { 292 Throwable cause = e.getCause(); 293 if (cause instanceof RuntimeException) { 294 throw (RuntimeException) cause; 295 } 296 throw new RuntimeException(cause); 297 } catch (InterruptedException e) { 298 throw new RuntimeException(e); 299 } 300 } 301 302 /** 303 * Returns true if the given package is dexoptable. 304 * 305 * @param appHibernationManager the {@link AppHibernationManager} instance for checking 306 * hibernation status, or null to skip the check 307 */ canDexoptPackage( @onNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager)308 public static boolean canDexoptPackage( 309 @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) { 310 if (!PackageStateModulesUtils.isDexoptable(pkgState)) { 311 return false; 312 } 313 314 // We do not dexopt unused packages. 315 // If `appHibernationManager` is null, the caller's intention is to skip the check. 316 if (appHibernationManager != null 317 && shouldSkipDexoptDueToHibernation(pkgState, appHibernationManager)) { 318 return false; 319 } 320 321 return true; 322 } 323 shouldSkipDexoptDueToHibernation( @onNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager)324 public static boolean shouldSkipDexoptDueToHibernation( 325 @NonNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager) { 326 return appHibernationManager.isHibernatingGlobally(pkgState.getPackageName()) 327 && appHibernationManager.isOatArtifactDeletionEnabled(); 328 } 329 getPackageLastActiveTime(@onNull PackageState pkgState, @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager)330 public static long getPackageLastActiveTime(@NonNull PackageState pkgState, 331 @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) { 332 long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName()); 333 // The time where the last user installed the package the first time. 334 long lastFirstInstallTimeMs = 335 userManager.getUserHandles(true /* excludeDying */) 336 .stream() 337 .map(handle -> pkgState.getStateForUser(handle)) 338 .map(userState -> userState.getFirstInstallTimeMillis()) 339 .max(Long::compare) 340 .orElse(0l); 341 return Math.max(lastUsedAtMs, lastFirstInstallTimeMs); 342 } 343 deleteIfExistsSafe(@ullable File file)344 public static void deleteIfExistsSafe(@Nullable File file) { 345 if (file != null) { 346 deleteIfExistsSafe(file.toPath()); 347 } 348 } 349 deleteIfExistsSafe(@onNull Path path)350 public static void deleteIfExistsSafe(@NonNull Path path) { 351 try { 352 Files.deleteIfExists(path); 353 } catch (IOException e) { 354 Log.e(TAG, "Failed to delete file '" + path + "'", e); 355 } 356 } 357 isSystemUiPackage(@onNull Context context, @NonNull String packageName)358 public static boolean isSystemUiPackage(@NonNull Context context, @NonNull String packageName) { 359 return packageName.equals(context.getString(R.string.config_systemUi)); 360 } 361 isLauncherPackage(@onNull Context context, @NonNull String packageName)362 public static boolean isLauncherPackage(@NonNull Context context, @NonNull String packageName) { 363 RoleManager roleManager = context.getSystemService(RoleManager.class); 364 return roleManager.getRoleHolders(RoleManager.ROLE_HOME).contains(packageName); 365 } 366 367 /** 368 * Gets the existing reference profile if one exists, or initializes a reference profile from an 369 * external profile. 370 * 371 * If the reference profile is initialized from an external profile, the returned profile path 372 * will be a {@link TmpProfilePath}. It's the callers responsibility to either commit it to the 373 * final location by calling {@link IArtd#commitTmpProfile} or clean it up by calling {@link 374 * IArtd#deleteProfile}. 375 * 376 * @param dexPath the path to the dex file that the profile is checked against 377 * @param refProfile the path where an existing reference profile would be found, if present 378 * @param externalProfiles a list of external profiles to initialize the reference profile from, 379 * in the order of preference 380 * @param initOutput the final location to initialize the reference profile to 381 * 382 * @return a pair where the first element is the found or initialized profile, and the second 383 * element is true if the profile is readable by others. Returns null if there is no 384 * reference profile or external profile to use 385 */ 386 @Nullable getOrInitReferenceProfile(@onNull IArtd artd, @NonNull String dexPath, @NonNull ProfilePath refProfile, @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput)387 public static Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull IArtd artd, 388 @NonNull String dexPath, @NonNull ProfilePath refProfile, 389 @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput) 390 throws RemoteException { 391 try { 392 if (artd.isProfileUsable(refProfile, dexPath)) { 393 boolean isOtherReadable = 394 artd.getProfileVisibility(refProfile) == FileVisibility.OTHER_READABLE; 395 return Pair.create(refProfile, isOtherReadable); 396 } 397 } catch (ServiceSpecificException e) { 398 Log.e(TAG, 399 "Failed to use the existing reference profile " 400 + AidlUtils.toString(refProfile), 401 e); 402 } 403 404 ProfilePath initializedProfile = 405 initReferenceProfile(artd, dexPath, externalProfiles, initOutput); 406 return initializedProfile != null ? Pair.create(initializedProfile, true) : null; 407 } 408 409 /** 410 * Similar to above, but never uses an existing profile. 411 * 412 * Unlike the one above, this method doesn't return a boolean flag to indicate if the profile is 413 * readable by others. The profile returned by this method is initialized form an external 414 * profile, meaning it has no user data, so it's always readable by others. 415 */ 416 @Nullable initReferenceProfile(@onNull IArtd artd, @NonNull String dexPath, @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile output)417 public static ProfilePath initReferenceProfile(@NonNull IArtd artd, @NonNull String dexPath, 418 @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile output) 419 throws RemoteException { 420 for (ProfilePath profile : externalProfiles) { 421 try { 422 // If the profile path is a PrebuiltProfilePath, and the APK is really a prebuilt 423 // one, rewriting the profile is unnecessary because the dex location is known at 424 // build time and is correctly set in the profile header. However, the APK can also 425 // be an installed one, in which case partners may place a profile file next to the 426 // APK at install time. Rewriting the profile in the latter case is necessary. 427 if (artd.copyAndRewriteProfile(profile, output, dexPath)) { 428 return ProfilePath.tmpProfilePath(output.profilePath); 429 } 430 } catch (ServiceSpecificException e) { 431 Log.e(TAG, "Failed to initialize profile from " + AidlUtils.toString(profile), e); 432 } 433 } 434 435 return null; 436 } 437 logArtdException(@onNull RemoteException e)438 public static void logArtdException(@NonNull RemoteException e) { 439 String message = "An error occurred when calling artd"; 440 if (e instanceof DeadObjectException) { 441 // We assume that `DeadObjectException` only happens in two cases: 442 // 1. artd crashed, in which case a native stack trace was logged. 443 // 2. artd was killed before system server during device shutdown, in which case the 444 // exception is expected. 445 // In either case, we don't need to surface the exception from here. 446 // The Java stack trace is intentionally omitted because it's not helpful. 447 Log.e(TAG, message); 448 } else { 449 // Not expected. Log wtf to surface it. 450 Slog.wtf(TAG, message, e); 451 } 452 } 453 454 @AutoValue 455 public abstract static class Abi { create( @onNull String name, @NonNull String isa, boolean isPrimaryAbi)456 static @NonNull Abi create( 457 @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) { 458 return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi); 459 } 460 461 // The ABI name. E.g., "arm64-v8a". name()462 abstract @NonNull String name(); 463 464 // The instruction set name. E.g., "arm64". isa()465 abstract @NonNull String isa(); 466 isPrimaryAbi()467 abstract boolean isPrimaryAbi(); 468 } 469 470 public static class Tracing implements AutoCloseable { Tracing(@onNull String methodName)471 public Tracing(@NonNull String methodName) { 472 Trace.traceBegin(TRACE_TAG_DALVIK, methodName); 473 } 474 475 @Override close()476 public void close() { 477 Trace.traceEnd(TRACE_TAG_DALVIK); 478 } 479 } 480 481 public static class TracingWithTimingLogging extends Tracing { 482 @NonNull private final String mTag; 483 @NonNull private final String mMethodName; 484 @NonNull private final long mStartTimeMs; 485 TracingWithTimingLogging(@onNull String tag, @NonNull String methodName)486 public TracingWithTimingLogging(@NonNull String tag, @NonNull String methodName) { 487 super(methodName); 488 mTag = tag; 489 mMethodName = methodName; 490 mStartTimeMs = SystemClock.elapsedRealtime(); 491 Log.d(tag, methodName); 492 } 493 494 @Override close()495 public void close() { 496 Log.d(mTag, 497 mMethodName + " took to complete: " 498 + (SystemClock.elapsedRealtime() - mStartTimeMs) + "ms"); 499 super.close(); 500 } 501 } 502 } 503