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.ArtManagerLocal.AdjustCompilerFilterCallback; 20 import static com.android.server.art.DexMetadataHelper.DexMetadataInfo; 21 import static com.android.server.art.OutputArtifacts.PermissionSettings; 22 import static com.android.server.art.ProfilePath.TmpProfilePath; 23 import static com.android.server.art.Utils.Abi; 24 import static com.android.server.art.Utils.InitProfileResult; 25 import static com.android.server.art.model.ArtFlags.DexoptFlags; 26 import static com.android.server.art.model.Config.Callback; 27 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; 28 29 import android.annotation.NonNull; 30 import android.annotation.Nullable; 31 import android.content.Context; 32 import android.content.pm.ApplicationInfo; 33 import android.os.Build; 34 import android.os.CancellationSignal; 35 import android.os.RemoteException; 36 import android.os.ServiceSpecificException; 37 import android.os.SystemProperties; 38 import android.os.UserManager; 39 import android.os.storage.StorageManager; 40 41 import androidx.annotation.RequiresApi; 42 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.server.LocalManagerRegistry; 45 import com.android.server.art.Dex2OatStatsReporter.Dex2OatResult; 46 import com.android.server.art.model.ArtFlags; 47 import com.android.server.art.model.Config; 48 import com.android.server.art.model.DetailedDexInfo; 49 import com.android.server.art.model.DexoptParams; 50 import com.android.server.art.model.DexoptResult; 51 import com.android.server.pm.PackageManagerLocal; 52 import com.android.server.pm.pkg.AndroidPackage; 53 import com.android.server.pm.pkg.PackageState; 54 55 import dalvik.system.DexFile; 56 57 import com.google.auto.value.AutoValue; 58 59 import java.io.IOException; 60 import java.util.ArrayList; 61 import java.util.List; 62 import java.util.Map; 63 import java.util.Objects; 64 import java.util.concurrent.Executor; 65 import java.util.regex.Matcher; 66 import java.util.regex.Pattern; 67 68 /** @hide */ 69 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 70 public abstract class Dexopter<DexInfoType extends DetailedDexInfo> { 71 private static final List<String> ART_PACKAGE_NAMES = 72 List.of("com.google.android.art", "com.android.art", "com.google.android.go.art"); 73 74 @NonNull protected final Injector mInjector; 75 @NonNull protected final PackageState mPkgState; 76 /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */ 77 @NonNull protected final AndroidPackage mPkg; 78 @NonNull protected final DexoptParams mParams; 79 @NonNull protected final CancellationSignal mCancellationSignal; 80 Dexopter(@onNull Injector injector, @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)81 protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState, 82 @NonNull AndroidPackage pkg, @NonNull DexoptParams params, 83 @NonNull CancellationSignal cancellationSignal) { 84 mInjector = injector; 85 mPkgState = pkgState; 86 mPkg = pkg; 87 mParams = params; 88 mCancellationSignal = cancellationSignal; 89 } 90 91 /** 92 * DO NOT use this method directly. Use {@link 93 * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String, 94 * DexoptParams)}. 95 */ 96 @NonNull dexopt()97 public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException { 98 if (SystemProperties.getBoolean("dalvik.vm.disable-art-service-dexopt", false /* def */)) { 99 AsLog.i("Dexopt skipped because it's disabled by system property"); 100 return List.of(); 101 } 102 103 List<DexContainerFileDexoptResult> results = new ArrayList<>(); 104 105 boolean isInDalvikCache = isInDalvikCache(); 106 107 for (DexInfoType dexInfo : getDexInfoList()) { 108 ProfilePath profile = null; 109 boolean succeeded = true; 110 List<String> externalProfileErrors = List.of(); 111 try { 112 if (!isDexoptable(dexInfo)) { 113 continue; 114 } 115 116 onDexoptStart(dexInfo); 117 118 String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo); 119 DexMetadataInfo dmInfo = 120 mInjector.getDexMetadataHelper().getDexMetadataInfo(buildDmPath(dexInfo)); 121 if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) { 122 mInjector.getReporterExecutor().execute( 123 () 124 -> Dex2OatStatsReporter.reportSkipped(mPkgState.getAppId(), 125 mParams.getReason(), dmInfo.type(), dexInfo, 126 getAllAbis(dexInfo))); 127 continue; 128 } 129 130 if (mInjector.isPreReboot() && !isDexFileFound(dexInfo)) { 131 // In the pre-reboot case, it's possible that a dex file doesn't exist in the 132 // new system image. Although code below can gracefully handle failures, those 133 // failures can be red herrings in metrics and bug reports, so we skip 134 // non-existing dex files to avoid them. 135 continue; 136 } 137 138 boolean needsToBeShared = needsToBeShared(dexInfo); 139 boolean isOtherReadable = true; 140 // If true, implies that the profile has changed since the last compilation. 141 boolean profileMerged = false; 142 if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) { 143 if (!dmInfo.config().getEnableEmbeddedProfile()) { 144 String dmPath = DexMetadataHelper.getDmPath( 145 Objects.requireNonNull(dmInfo.dmPath())); 146 AsLog.i("Embedded profile disabled by config in the dm file " + dmPath); 147 } 148 149 if (needsToBeShared) { 150 InitProfileResult result = initReferenceProfile( 151 dexInfo, dmInfo.config().getEnableEmbeddedProfile()); 152 profile = result.profile(); 153 isOtherReadable = result.isOtherReadable(); 154 externalProfileErrors = result.externalProfileErrors(); 155 } else { 156 InitProfileResult result = getOrInitReferenceProfile( 157 dexInfo, dmInfo.config().getEnableEmbeddedProfile()); 158 profile = result.profile(); 159 isOtherReadable = result.isOtherReadable(); 160 externalProfileErrors = result.externalProfileErrors(); 161 ProfilePath mergedProfile = mergeProfiles(dexInfo, profile); 162 if (mergedProfile != null) { 163 if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) { 164 mInjector.getArtd().deleteProfile(profile); 165 } 166 profile = mergedProfile; 167 isOtherReadable = false; 168 profileMerged = true; 169 } 170 } 171 if (profile == null) { 172 // A profile guided dexopt with no profile is essentially 'verify', 173 // and dex2oat already makes this transformation. However, we need to 174 // explicitly make this transformation here to guide the later decisions 175 // such as whether the artifacts can be public and whether dexopt is needed. 176 compilerFilter = printAdjustCompilerFilterReason(compilerFilter, 177 needsToBeShared ? ReasonMapping.getCompilerFilterForShared() 178 : "verify", 179 "there is no valid profile" 180 + (needsToBeShared ? " and the package needs to be shared" 181 : "")); 182 } 183 } 184 boolean isProfileGuidedCompilerFilter = 185 DexFile.isProfileGuidedCompilerFilter(compilerFilter); 186 Utils.check(isProfileGuidedCompilerFilter == (profile != null)); 187 188 boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable) 189 && isDexFilePublic(dexInfo); 190 Utils.check(Utils.implies(needsToBeShared, canBePublic)); 191 PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic); 192 193 DexoptOptions dexoptOptions = 194 getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter); 195 196 for (Abi abi : getAllAbis(dexInfo)) { 197 @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED; 198 long wallTimeMs = 0; 199 long cpuTimeMs = 0; 200 long sizeBytes = 0; 201 long sizeBeforeBytes = 0; 202 Dex2OatResult dex2OatResult = Dex2OatResult.notRun(); 203 @DexoptResult.DexoptResultExtendedStatusFlags int extendedStatusFlags = 0; 204 DexoptTarget<DexInfoType> target = null; 205 try { 206 target = DexoptTarget.<DexInfoType>builder() 207 .setDexInfo(dexInfo) 208 .setIsa(abi.isa()) 209 .setIsInDalvikCache(isInDalvikCache) 210 .setCompilerFilter(compilerFilter) 211 .setDmPath(dmInfo.dmPath()) 212 .build(); 213 var options = GetDexoptNeededOptions.builder() 214 .setProfileMerged(profileMerged) 215 .setFlags(mParams.getFlags()) 216 .setNeedsToBePublic(needsToBeShared) 217 .build(); 218 219 if (mInjector.isPreReboot()) { 220 ArtifactsPath existingArtifacts = 221 AidlUtils.buildArtifactsPathAsInputPreReboot( 222 target.dexInfo().dexPath(), target.isa(), 223 target.isInDalvikCache()); 224 if (mInjector.getArtd().getArtifactsVisibility(existingArtifacts) 225 != FileVisibility.NOT_FOUND) { 226 // Because `getDexoptNeeded` doesn't check Pre-reboot artifacts, we 227 // do a simple check here to handle job resuming. If the Pre-reboot 228 // artifacts exist, we assume they are up-to-date because 229 // `PreRebootDexoptJob` would otherwise clean them up, so we skip 230 // this dex file. The profile and the dex file may have been changed 231 // since the last cancelled job run, but we don't handle such cases 232 // because we are supposed to dexopt every dex file only once for 233 // each ISA. 234 extendedStatusFlags |= 235 DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST; 236 continue; 237 } 238 } 239 240 GetDexoptNeededResult getDexoptNeededResult = 241 getDexoptNeeded(target, options); 242 243 if (!getDexoptNeededResult.hasDexCode) { 244 extendedStatusFlags |= DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE; 245 } 246 247 if (!getDexoptNeededResult.isDexoptNeeded) { 248 continue; 249 } 250 251 try { 252 // `StorageManager.getAllocatableBytes` returns (free space + space used 253 // by clearable cache - low storage threshold). Since we only compare 254 // the result with 0, the clearable cache doesn't make a difference. 255 // When the free space is below the threshold, there should be no 256 // clearable cache left because system cleans up cache every minute. 257 if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0 258 && mInjector.getStorageManager().getAllocatableBytes( 259 mPkg.getStorageUuid()) 260 <= 0) { 261 extendedStatusFlags |= DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW; 262 continue; 263 } 264 } catch (IOException e) { 265 AsLog.e("Failed to check storage. Assuming storage not low", e); 266 } 267 268 IArtdCancellationSignal artdCancellationSignal = 269 mInjector.getArtd().createCancellationSignal(); 270 mCancellationSignal.setOnCancelListener(() -> { 271 try { 272 artdCancellationSignal.cancel(); 273 } catch (RemoteException e) { 274 AsLog.e("An error occurred when sending a cancellation signal", e); 275 } 276 }); 277 278 ArtdDexoptResult dexoptResult = dexoptFile(target, profile, 279 getDexoptNeededResult, permissionSettings, 280 mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal); 281 status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED 282 : DexoptResult.DEXOPT_PERFORMED; 283 wallTimeMs = dexoptResult.wallTimeMs; 284 cpuTimeMs = dexoptResult.cpuTimeMs; 285 sizeBytes = dexoptResult.sizeBytes; 286 sizeBeforeBytes = dexoptResult.sizeBeforeBytes; 287 dex2OatResult = dexoptResult.cancelled ? Dex2OatResult.cancelled() 288 : Dex2OatResult.exited(0); 289 290 if (status == DexoptResult.DEXOPT_CANCELLED) { 291 return results; 292 } 293 } catch (ServiceSpecificException e) { 294 // Log the error and continue. 295 AsLog.e(String.format("Failed to dexopt [packageName = %s, dexPath = %s, " 296 + "isa = %s, classLoaderContext = %s]", 297 mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(), 298 dexInfo.classLoaderContext()), 299 e); 300 status = DexoptResult.DEXOPT_FAILED; 301 302 // Parse status, exit code and signal from the dex2oat error message 303 Pattern pattern = Pattern.compile( 304 "\\[status=(-?\\d+),exit_code=(-?\\d+),signal=(-?\\d+)]"); 305 Matcher matcher = pattern.matcher(Objects.requireNonNull(e.getMessage())); 306 if (matcher.matches()) { 307 dex2OatResult = new Dex2OatResult(Integer.parseInt(matcher.group(1)), 308 Integer.parseInt(matcher.group(2)), 309 Integer.parseInt(matcher.group(3))); 310 } 311 } finally { 312 if (!externalProfileErrors.isEmpty()) { 313 extendedStatusFlags |= DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE; 314 } 315 var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(), 316 abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs, 317 cpuTimeMs, sizeBytes, sizeBeforeBytes, extendedStatusFlags, 318 externalProfileErrors); 319 AsLog.i(String.format("Dexopt result: [packageName = %s] %s", 320 mPkgState.getPackageName(), result)); 321 results.add(result); 322 323 onDexoptTargetResult(target, status); 324 325 if (status != DexoptResult.DEXOPT_SKIPPED 326 && status != DexoptResult.DEXOPT_PERFORMED) { 327 succeeded = false; 328 } 329 // Make sure artd does not leak even if the caller holds 330 // `mCancellationSignal` forever. 331 mCancellationSignal.setOnCancelListener(null); 332 333 // Variables used in lambda needs to be effectively final. 334 Dex2OatResult finalDex2OatResult = dex2OatResult; 335 mInjector.getReporterExecutor().execute( 336 () 337 -> Dex2OatStatsReporter.report(mPkgState.getAppId(), 338 result.getActualCompilerFilter(), 339 mParams.getReason(), dmInfo.type(), dexInfo, 340 abi.isa(), finalDex2OatResult, 341 result.getSizeBytes(), 342 result.getDex2oatWallTimeMillis())); 343 } 344 } 345 346 if (profile != null && succeeded) { 347 if (profile.getTag() == ProfilePath.tmpProfilePath) { 348 // Commit the profile only if dexopt succeeds. 349 if (commitProfileChanges(profile.getTmpProfilePath())) { 350 profile = null; 351 } 352 } 353 // We keep the current profiles in the Pre-reboot Dexopt case, to leave it to 354 // background dexopt. 355 if (profileMerged && !mInjector.isPreReboot()) { 356 // Note that this is just an optimization, to reduce the amount of data that 357 // the runtime writes on every profile save. The profile merge result on the 358 // next run won't change regardless of whether the cleanup is done or not 359 // because profman only looks at the diff. 360 // A caveat is that it may delete more than what has been merged, if the 361 // runtime writes additional entries between the merge and the cleanup, but 362 // this is fine because the runtime writes all JITed classes and methods on 363 // every save and the additional entries will likely be written back on the 364 // next save. 365 cleanupCurProfiles(dexInfo); 366 } 367 } 368 } finally { 369 if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) { 370 mInjector.getArtd().deleteProfile(profile); 371 } 372 } 373 } 374 375 return results; 376 } 377 378 // The javadoc on `AdjustCompilerFilterCallback.onAdjustCompilerFilter` may need updating when 379 // this method is changed. 380 @NonNull adjustCompilerFilter( @onNull String targetCompilerFilter, @NonNull DexInfoType dexInfo)381 private String adjustCompilerFilter( 382 @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) { 383 if ((mParams.getFlags() & ArtFlags.FLAG_FORCE_COMPILER_FILTER) == 0) { 384 if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) { 385 String systemUiCompilerFilter = getSystemUiCompilerFilter(); 386 if (!systemUiCompilerFilter.isEmpty()) { 387 targetCompilerFilter = printAdjustCompilerFilterReason(targetCompilerFilter, 388 systemUiCompilerFilter, "the package is System UI"); 389 } 390 } else if (mInjector.isLauncherPackage(mPkgState.getPackageName())) { 391 targetCompilerFilter = printAdjustCompilerFilterReason( 392 targetCompilerFilter, "speed-profile", "the package is a launcher package"); 393 } 394 395 Callback<AdjustCompilerFilterCallback, Void> callback = 396 mInjector.getConfig().getAdjustCompilerFilterCallback(); 397 if (callback != null) { 398 // Local variables passed to the lambda must be final or effectively final. 399 final String originalCompilerFilter = targetCompilerFilter; 400 targetCompilerFilter = printAdjustCompilerFilterReason( 401 targetCompilerFilter, Utils.executeAndWait(callback.executor(), () -> { 402 return callback.get().onAdjustCompilerFilter(mPkgState.getPackageName(), 403 originalCompilerFilter, mParams.getReason()); 404 }), "of AdjustCompilerFilterCallback"); 405 } 406 } 407 408 // Code below should only downgrade the compiler filter. Don't upgrade the compiler filter 409 // beyond this point! 410 411 // We force vmSafeMode on debuggable apps as well: 412 // - the runtime ignores their compiled code 413 // - they generally have lots of methods that could make the compiler used run out of 414 // memory (b/130828957) 415 // Note that forcing the compiler filter here applies to all compilations (even if they 416 // are done via adb shell commands). This is okay because the runtime will ignore the 417 // compiled code anyway. 418 if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) { 419 targetCompilerFilter = printAdjustCompilerFilterReason(targetCompilerFilter, 420 DexFile.getSafeModeCompilerFilter(targetCompilerFilter), 421 mPkg.isVmSafeMode() ? "the package requests VM safe mode" 422 : "the package is debuggable"); 423 } 424 425 // We cannot do AOT compilation if we don't have a valid class loader context. 426 if (dexInfo.classLoaderContext() == null 427 && DexFile.isOptimizedCompilerFilter(targetCompilerFilter)) { 428 targetCompilerFilter = printAdjustCompilerFilterReason( 429 targetCompilerFilter, "verify", "there is no valid class loader context"); 430 } 431 432 // This application wants to use the embedded dex in the APK, rather than extracted or 433 // locally compiled variants, so we only verify it. 434 // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat 435 // won't extract the dex code because the APK is uncompressed, and the assumption is that 436 // such applications always use uncompressed APKs. 437 if (mPkg.isUseEmbeddedDex() && DexFile.isOptimizedCompilerFilter(targetCompilerFilter)) { 438 targetCompilerFilter = printAdjustCompilerFilterReason( 439 targetCompilerFilter, "verify", "the package requests to use embedded dex"); 440 } 441 442 if ((mParams.getFlags() & ArtFlags.FLAG_IGNORE_PROFILE) != 0 443 && DexFile.isProfileGuidedCompilerFilter(targetCompilerFilter)) { 444 targetCompilerFilter = printAdjustCompilerFilterReason( 445 targetCompilerFilter, "verify", "the user requests to ignore the profile"); 446 } 447 448 return targetCompilerFilter; 449 } 450 451 @NonNull getSystemUiCompilerFilter()452 private String getSystemUiCompilerFilter() { 453 String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter"); 454 if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) { 455 throw new IllegalStateException( 456 "Got invalid compiler filter '" + compilerFilter + "' for System UI"); 457 } 458 return compilerFilter; 459 } 460 printAdjustCompilerFilterReason(@onNull String oldCompilerFilter, @NonNull String newCompilerFilter, @NonNull String reason)461 private @NonNull String printAdjustCompilerFilterReason(@NonNull String oldCompilerFilter, 462 @NonNull String newCompilerFilter, @NonNull String reason) { 463 if (!oldCompilerFilter.equals(newCompilerFilter)) { 464 AsLog.i(String.format( 465 "Adjusting the compiler filter for '%s' from '%s' to '%s' because %s", 466 mPkgState.getPackageName(), oldCompilerFilter, newCompilerFilter, reason)); 467 } 468 return newCompilerFilter; 469 } 470 471 /** @see Utils#getOrInitReferenceProfile */ 472 @Nullable getOrInitReferenceProfile( @onNull DexInfoType dexInfo, boolean enableEmbeddedProfile)473 private InitProfileResult getOrInitReferenceProfile( 474 @NonNull DexInfoType dexInfo, boolean enableEmbeddedProfile) throws RemoteException { 475 return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(), 476 buildRefProfilePathAsInput(dexInfo), getExternalProfiles(dexInfo), 477 enableEmbeddedProfile, buildOutputProfile(dexInfo, true /* isPublic */)); 478 } 479 480 @Nullable initReferenceProfile( @onNull DexInfoType dexInfo, boolean enableEmbeddedProfile)481 private InitProfileResult initReferenceProfile( 482 @NonNull DexInfoType dexInfo, boolean enableEmbeddedProfile) throws RemoteException { 483 return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(), 484 getExternalProfiles(dexInfo), enableEmbeddedProfile, 485 buildOutputProfile(dexInfo, true /* isPublic */)); 486 } 487 488 @NonNull getDexoptOptions( @onNull DexInfoType dexInfo, boolean isProfileGuidedFilter)489 private DexoptOptions getDexoptOptions( 490 @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) { 491 DexoptOptions dexoptOptions = new DexoptOptions(); 492 dexoptOptions.compilationReason = mParams.getReason(); 493 dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion(); 494 dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable(); 495 // Generating a meaningful app image needs a profile to determine what to include in the 496 // image. Otherwise, the app image will be nearly empty. 497 dexoptOptions.generateAppImage = isProfileGuidedFilter && isAppImageEnabled(); 498 dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled(); 499 dexoptOptions.comments = 500 String.format("app-name:%s,app-version-name:%s,app-version-code:%d,art-version:%d", 501 mPkgState.getPackageName(), mPkg.getVersionName(), 502 mPkg.getLongVersionCode(), mInjector.getArtVersion()); 503 return dexoptOptions; 504 } 505 isAlwaysDebuggable()506 private boolean isAlwaysDebuggable() { 507 return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */); 508 } 509 isAppImageEnabled()510 private boolean isAppImageEnabled() { 511 return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty(); 512 } 513 isHiddenApiPolicyEnabled()514 private boolean isHiddenApiPolicyEnabled() { 515 return mPkgState.getHiddenApiEnforcementPolicy() 516 != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED; 517 } 518 519 @NonNull getDexoptNeeded(@onNull DexoptTarget<DexInfoType> target, @NonNull GetDexoptNeededOptions options)520 GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target, 521 @NonNull GetDexoptNeededOptions options) throws RemoteException { 522 int dexoptTrigger = getDexoptTrigger(target, options); 523 524 // The result should come from artd even if all the bits of `dexoptTrigger` are set 525 // because the result also contains information about the usable VDEX file. 526 // Note that the class loader context can be null. In that case, we intentionally pass the 527 // null value down to lower levels to indicate that the class loader context check should be 528 // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`). 529 GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded( 530 target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(), 531 target.compilerFilter(), dexoptTrigger); 532 533 return result; 534 } 535 getDexoptTrigger(@onNull DexoptTarget<DexInfoType> target, @NonNull GetDexoptNeededOptions options)536 int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target, 537 @NonNull GetDexoptNeededOptions options) throws RemoteException { 538 if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) { 539 return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME 540 | DexoptTrigger.COMPILER_FILTER_IS_WORSE 541 | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE 542 | DexoptTrigger.NEED_EXTRACTION; 543 } 544 545 if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) { 546 return DexoptTrigger.COMPILER_FILTER_IS_WORSE; 547 } 548 549 int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER 550 | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION; 551 if (options.profileMerged()) { 552 dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME; 553 } 554 555 ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPathAsInput( 556 target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache()); 557 558 if (options.needsToBePublic() 559 && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath) 560 == FileVisibility.NOT_OTHER_READABLE) { 561 // Typically, this happens after an app starts being used by other apps. 562 // This case should be the same as force as we have no choice but to trigger a new 563 // dexopt. 564 dexoptTrigger |= 565 DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE; 566 } 567 568 return dexoptTrigger; 569 } 570 dexoptFile(@onNull DexoptTarget<DexInfoType> target, @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult, @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass, @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)571 private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target, 572 @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult, 573 @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass, 574 @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal) 575 throws RemoteException { 576 OutputArtifacts outputArtifacts = 577 AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(), target.isa(), 578 target.isInDalvikCache(), permissionSettings, mInjector.isPreReboot()); 579 580 VdexPath inputVdex = 581 getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa()); 582 583 if (target.dmPath() != null 584 && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) { 585 // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g., 586 // "install-dm"). 587 // Note that this only applies to reasons for app install because the goal is to give 588 // Play a signal that a DM file is downloaded at install time. We actually pass the DM 589 // file regardless of the compilation reason, but we don't append a suffix when the 590 // compilation reason is not a reason for app install. 591 // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by 592 // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The 593 // compilation reason can still be "install-dm" even if dex2oat left all contents of the 594 // DM file unused or an empty DM file is passed to dex2oat. 595 dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm"; 596 } 597 598 ArtdDexoptResult result = mInjector.getArtd().dexopt(outputArtifacts, 599 target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(), 600 target.compilerFilter(), profile, inputVdex, target.dmPath(), priorityClass, 601 dexoptOptions, artdCancellationSignal); 602 603 // Delete the existing runtime images after the dexopt is performed, even if they are still 604 // usable (e.g., the compiler filter is "verify"). This is to make sure the dexopt puts the 605 // dex file into a certain dexopt state, to make it easier for debugging and testing. It's 606 // also an optimization to release disk space as soon as possible. However, not doing the 607 // deletion here does not affect correctness or waste disk space: if the existing runtime 608 // images are still usable, technically, they can still be used to improve runtime 609 // performance; if they are no longer usable, they will be deleted by the file GC during the 610 // daily background dexopt job anyway. 611 // We keep the runtime artifacts in the Pre-reboot Dexopt case because they are still needed 612 // before the reboot. 613 if (!result.cancelled && !mInjector.isPreReboot()) { 614 mInjector.getArtd().deleteRuntimeArtifacts(AidlUtils.buildRuntimeArtifactsPath( 615 mPkgState.getPackageName(), target.dexInfo().dexPath(), target.isa())); 616 } 617 618 return result; 619 } 620 621 @Nullable getInputVdex(@onNull GetDexoptNeededResult getDexoptNeededResult, @NonNull String dexPath, @NonNull String isa)622 private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult, 623 @NonNull String dexPath, @NonNull String isa) { 624 if (!getDexoptNeededResult.isVdexUsable) { 625 return null; 626 } 627 switch (getDexoptNeededResult.artifactsLocation) { 628 case ArtifactsLocation.DALVIK_CACHE: 629 return VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( 630 dexPath, isa, true /* isInDalvikCache */)); 631 case ArtifactsLocation.NEXT_TO_DEX: 632 return VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput( 633 dexPath, isa, false /* isInDalvikCache */)); 634 case ArtifactsLocation.DM: 635 case ArtifactsLocation.SDM_DALVIK_CACHE: 636 case ArtifactsLocation.SDM_NEXT_TO_DEX: 637 // In these cases, the VDEX file is in the DM file. The whole DM file is passed to 638 // dex2oat as a separate flag whenever it exists. 639 return null; 640 default: 641 // This should never happen as the value is got from artd. 642 throw new IllegalStateException( 643 "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation); 644 } 645 } 646 commitProfileChanges(@onNull TmpProfilePath profile)647 private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException { 648 try { 649 mInjector.getArtd().commitTmpProfile(profile); 650 return true; 651 } catch (ServiceSpecificException e) { 652 AsLog.e("Failed to commit profile changes " + AidlUtils.toString(profile.finalPath), e); 653 return false; 654 } 655 } 656 657 @Nullable mergeProfiles(@onNull DexInfoType dexInfo, @Nullable ProfilePath referenceProfile)658 private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo, 659 @Nullable ProfilePath referenceProfile) throws RemoteException { 660 OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */); 661 662 var options = new MergeProfileOptions(); 663 options.forceMerge = (mParams.getFlags() & ArtFlags.FLAG_FORCE_MERGE_PROFILE) != 0; 664 665 try { 666 if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output, 667 List.of(dexInfo.dexPath()), options)) { 668 return ProfilePath.tmpProfilePath(output.profilePath); 669 } 670 } catch (ServiceSpecificException e) { 671 AsLog.e("Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath), 672 e); 673 } 674 675 return null; 676 } 677 cleanupCurProfiles(@onNull DexInfoType dexInfo)678 private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException { 679 for (ProfilePath profile : getCurProfiles(dexInfo)) { 680 mInjector.getArtd().deleteProfile(profile); 681 } 682 } 683 684 // Methods to be implemented by child classes. 685 686 /** Returns true if the artifacts should be written to the global dalvik-cache directory. */ isInDalvikCache()687 protected abstract boolean isInDalvikCache() throws RemoteException; 688 689 /** Returns information about all dex files. */ getDexInfoList()690 @NonNull protected abstract List<DexInfoType> getDexInfoList(); 691 692 /** Returns true if the given dex file should be dexopted. */ isDexoptable(@onNull DexInfoType dexInfo)693 protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo); 694 695 /** 696 * Returns true if the artifacts should be shared with other apps. Note that this must imply 697 * {@link #isDexFilePublic(DexInfoType)}. 698 */ needsToBeShared(@onNull DexInfoType dexInfo)699 protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo); 700 701 /** 702 * Returns true if the filesystem permission of the dex file has the "read" bit for "others" 703 * (S_IROTH). 704 */ isDexFilePublic(@onNull DexInfoType dexInfo)705 protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo); 706 707 /** 708 * Returns true if the dex file is found. 709 */ isDexFileFound(@onNull DexInfoType dexInfo)710 protected abstract boolean isDexFileFound(@NonNull DexInfoType dexInfo); 711 712 /** 713 * Returns a list of external profiles (e.g., a DM profile) that the reference profile can be 714 * initialized from, in the order of preference. 715 */ getExternalProfiles(@onNull DexInfoType dexInfo)716 @NonNull protected abstract List<ProfilePath> getExternalProfiles(@NonNull DexInfoType dexInfo); 717 718 /** Returns the permission settings to use for the artifacts of the given dex file. */ 719 @NonNull getPermissionSettings( @onNull DexInfoType dexInfo, boolean canBePublic)720 protected abstract PermissionSettings getPermissionSettings( 721 @NonNull DexInfoType dexInfo, boolean canBePublic); 722 723 /** Returns all ABIs that the given dex file should be compiled for. */ getAllAbis(@onNull DexInfoType dexInfo)724 @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo); 725 726 /** Returns the path to the reference profile of the given dex file. */ 727 @NonNull buildRefProfilePathAsInput(@onNull DexInfoType dexInfo)728 protected abstract ProfilePath buildRefProfilePathAsInput(@NonNull DexInfoType dexInfo); 729 730 /** 731 * Returns the data structure that represents the temporary profile to use during processing. 732 */ 733 @NonNull buildOutputProfile( @onNull DexInfoType dexInfo, boolean isPublic)734 protected abstract OutputProfile buildOutputProfile( 735 @NonNull DexInfoType dexInfo, boolean isPublic); 736 737 /** Returns the paths to the current profiles of the given dex file. */ getCurProfiles(@onNull DexInfoType dexInfo)738 @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo); 739 740 /** 741 * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file 742 * should be passed. 743 */ buildDmPath(@onNull DexInfoType dexInfo)744 @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo); 745 746 /** 747 * Called at an early stage during dexopt of every dex file, even before dexopt is skipped by 748 * the noop compiler filter. 749 */ onDexoptStart(@onNull DexInfoType dexInfo)750 protected void onDexoptStart(@NonNull DexInfoType dexInfo) throws RemoteException {} 751 752 /** 753 * Called once for every dex file and every ABI when dexopt has a result. 754 */ onDexoptTargetResult(@onNull DexoptTarget<DexInfoType> target, @DexoptResult.DexoptResultStatus int status)755 protected void onDexoptTargetResult(@NonNull DexoptTarget<DexInfoType> target, 756 @DexoptResult.DexoptResultStatus int status) throws RemoteException {} 757 758 @AutoValue 759 abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> { dexInfo()760 abstract @NonNull DexInfoType dexInfo(); isa()761 abstract @NonNull String isa(); isInDalvikCache()762 abstract boolean isInDalvikCache(); compilerFilter()763 abstract @NonNull String compilerFilter(); dmPath()764 abstract @Nullable DexMetadataPath dmPath(); 765 builder()766 static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() { 767 return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>(); 768 } 769 770 @AutoValue.Builder 771 abstract static class Builder<DexInfoType extends DetailedDexInfo> { setDexInfo(@onNull DexInfoType value)772 abstract Builder setDexInfo(@NonNull DexInfoType value); setIsa(@onNull String value)773 abstract Builder setIsa(@NonNull String value); setIsInDalvikCache(boolean value)774 abstract Builder setIsInDalvikCache(boolean value); setCompilerFilter(@onNull String value)775 abstract Builder setCompilerFilter(@NonNull String value); setDmPath(@ullable DexMetadataPath value)776 abstract Builder setDmPath(@Nullable DexMetadataPath value); build()777 abstract DexoptTarget<DexInfoType> build(); 778 } 779 } 780 781 @AutoValue 782 abstract static class GetDexoptNeededOptions { flags()783 abstract @DexoptFlags int flags(); profileMerged()784 abstract boolean profileMerged(); needsToBePublic()785 abstract boolean needsToBePublic(); 786 builder()787 static Builder builder() { 788 return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder(); 789 } 790 791 @AutoValue.Builder 792 abstract static class Builder { setFlags(@exoptFlags int value)793 abstract Builder setFlags(@DexoptFlags int value); setProfileMerged(boolean value)794 abstract Builder setProfileMerged(boolean value); setNeedsToBePublic(boolean value)795 abstract Builder setNeedsToBePublic(boolean value); build()796 abstract GetDexoptNeededOptions build(); 797 } 798 } 799 800 /** 801 * Injector pattern for testing purpose. 802 * 803 * @hide 804 */ 805 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED) 806 public static class Injector { 807 @NonNull private final Context mContext; 808 @NonNull private final Config mConfig; 809 @NonNull private final Executor mReporterExecutor; 810 Injector(@onNull Context context, @NonNull Config config, @NonNull Executor reporterExecutor)811 public Injector(@NonNull Context context, @NonNull Config config, 812 @NonNull Executor reporterExecutor) { 813 mContext = context; 814 mConfig = config; 815 mReporterExecutor = reporterExecutor; 816 817 // Call the getters for various dependencies, to ensure correct initialization order. 818 getUserManager(); 819 getDexUseManager(); 820 getStorageManager(); 821 GlobalInjector.getInstance().checkArtModuleServiceManager(); 822 } 823 isSystemUiPackage(@onNull String packageName)824 public boolean isSystemUiPackage(@NonNull String packageName) { 825 return Utils.isSystemUiPackage(mContext, packageName); 826 } 827 isLauncherPackage(@onNull String packageName)828 public boolean isLauncherPackage(@NonNull String packageName) { 829 return Utils.isLauncherPackage(mContext, packageName); 830 } 831 832 @NonNull getUserManager()833 public UserManager getUserManager() { 834 return Objects.requireNonNull(mContext.getSystemService(UserManager.class)); 835 } 836 837 @NonNull getDexUseManager()838 public DexUseManagerLocal getDexUseManager() { 839 return GlobalInjector.getInstance().getDexUseManager(); 840 } 841 842 @NonNull getArtd()843 public IArtd getArtd() { 844 return ArtdRefCache.getInstance().getArtd(); 845 } 846 847 @NonNull getStorageManager()848 public StorageManager getStorageManager() { 849 return Objects.requireNonNull(mContext.getSystemService(StorageManager.class)); 850 } 851 852 @NonNull getPackageManagerLocal()853 private PackageManagerLocal getPackageManagerLocal() { 854 return Objects.requireNonNull( 855 LocalManagerRegistry.getManager(PackageManagerLocal.class)); 856 } 857 getArtVersion()858 public long getArtVersion() { 859 try (var snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) { 860 Map<String, PackageState> packageStates = snapshot.getPackageStates(); 861 for (String artPackageName : ART_PACKAGE_NAMES) { 862 PackageState pkgState = packageStates.get(artPackageName); 863 if (pkgState != null) { 864 AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); 865 return pkg.getLongVersionCode(); 866 } 867 } 868 } 869 return -1; 870 } 871 872 @NonNull getConfig()873 public Config getConfig() { 874 return mConfig; 875 } 876 877 @NonNull getReporterExecutor()878 public Executor getReporterExecutor() { 879 return mReporterExecutor; 880 } 881 882 @NonNull getDexMetadataHelper()883 public DexMetadataHelper getDexMetadataHelper() { 884 return new DexMetadataHelper(); 885 } 886 isPreReboot()887 public boolean isPreReboot() { 888 return GlobalInjector.getInstance().isPreReboot(); 889 } 890 } 891 } 892