全志平台 Android6.0,不格式化烧录系统后,应用启动时重新执行dex2oat,导致系统卡顿发生ANR
Android6.0 正常情况下是在应用安装时完成dex2oat优化,应用启动时理应不会再执行dex2oat,
需要跟踪其dex2oat流程,定位应用启动时执行dex2oat的原因
跟踪流程
frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
进程启动时,会执行ensurePackageDexOpt()方法,以确保应用完成dex2oat优化
================================================
AMS
================================================
private final boolean attachApplicationLocked(){
...
ensurePackageDexOpt(app.instrumentationInfo != null
? app.instrumentationInfo.packageName
: app.info.packageName);
if (app.instrumentationClass != null) {
ensurePackageDexOpt(app.instrumentationClass.getPackageName());
}
...
}
void ensurePackageDexOpt(String packageName) {
IPackageManager pm = AppGlobals.getPackageManager();
try {
if (pm.performDexOptIfNeeded(packageName, null /* instruction set */)) {
mDidDexOpt = true;
}
} catch (RemoteException e) {
}
}
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
================================================
PMS
================================================
public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
return performDexOpt(packageName, instructionSet, false);
}
public boolean performDexOpt(String packageName, String instructionSet, boolean backgroundDexopt) {
targetInstructionSet = instructionSet != null ? instructionSet :
getPrimaryInstructionSet(p.applicationInfo);
if (p.mDexOptPerformed.contains(targetInstructionSet)) {
================================================
第一次跑此方法,mDexOptPerformed为空,mDexOptPerformed会在下面的方法内赋值
第二次跑此方法,mDexOptPerformed不为空,return false
================================================
return false;
}
================================================
liangjiehao
================================================
int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
false /* forceDex */, false /* defer */, true /* inclDependencies */,
true /* boot complete */);
}
frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java
int performDexOpt(...){
return performDexOptLI(pkg, instructionSets, forceDex, defer, bootComplete, done);
}
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
boolean forceDex, boolean defer, boolean bootComplete, ArraySet<String> done) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
return DEX_OPT_SKIPPED;
}
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
if (!forceDex && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
================================================
forceDex:false
第一次执行此方法时,pkg.mDexOptPerformed为空,判断不成立,继续往下执行
================================================
continue;
}
================================================
APK的安装路径
paths = [/data/app/com.gvs.h.os06-2/base.apk]
================================================
for (String path : paths) {
final int dexoptNeeded;
if (forceDex) {
dexoptNeeded = DexFile.DEX2OAT_NEEDED;
} else {
try {
================================================
forceDex:false
根据dexoptNeeded的返回值,判断是否需要dex2oat
正常情况返回 DexFile.NO_DEXOPT_NEEDED,不需要oat
异常情况返回 DexFile.DEX2OAT_NEEDED,会执行dex2oat,继续跟下去查看何时会返回DEX2OAT_NEEDED
================================================
dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
dexCodeInstructionSet, defer);
} catch (IOException ioe) {
Slog.w(TAG, "IOException reading apk: " + path, ioe);
return DEX_OPT_FAILED;
}
}
if (!forceDex && defer && dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
// We're deciding to defer a needed dexopt. Don't bother dexopting for other
// paths and instruction sets. We'll deal with them all together when we process
// our list of deferred dexopts.
addPackageForDeferredDexopt(pkg);
return DEX_OPT_DEFERRED;
}
if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
final String dexoptType;
String oatDir = null;
================================================
异常情况返回 DexFile.DEX2OAT_NEEDED,执行dex2oat
================================================
if (dexoptNeeded == DexFile.DEX2OAT_NEEDED) {
dexoptType = "dex2oat";
try {
oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
} catch (IOException ioe) {
Slog.w(TAG, "Unable to create oatDir for package: " + pkg.packageName);
return DEX_OPT_FAILED;
}
} else if (dexoptNeeded == DexFile.PATCHOAT_NEEDED) {
dexoptType = "patchoat";
} else if (dexoptNeeded == DexFile.SELF_PATCHOAT_NEEDED) {
dexoptType = "self patchoat";
} else {
throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
}
================================================
日志里可以看到打印了此log
================================================
Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ " oatDir = " + oatDir + " bootComplete=" + bootComplete);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
final int ret = mPackageManagerService.mInstaller.dexopt(path, sharedGid,
!pkg.isForwardLocked(), pkg.packageName, dexCodeInstructionSet,
dexoptNeeded, vmSafeMode, debuggable, oatDir, bootComplete);
// Dex2oat might fail due to compiler / verifier errors. We soldier on
// regardless, and attempt to interpret the app as a safety net.
if (ret == 0) {
performedDexOpt = true;
}
}
}
================================================
将平台架构添加到mDexOptPerformed
pkg.mDexOptPerformed = {arm64}
================================================
pkg.mDexOptPerformed.add(dexCodeInstructionSet);
}
}
libcore/dalvik/src/main/java/dalvik/system/DexFile.java
在Native 判断是否需要dex2oat
public static native int getDexOptNeeded(String fileName, String pkgname,
String instructionSet, boolean defer)
throws FileNotFoundException, IOException;
art/runtime/oat_file_assistant.cc
OatFileAssistant::DexOptNeeded OatFileAssistant::GetDexOptNeeded() {
// TODO: If the profiling code is ever restored, it's worth considering
// whether we should check to see if the profile is out of date here.
if (OatFileIsUpToDate() || OdexFileIsUpToDate()) {
================================================
这里自己增加了log,看到正常情况下会在此处return kNoDexOptNeeded
所以异常情况下,此处两个判断一定都为false,所以执行dex2oat,看一下这两个判断方法
================================================
return kNoDexOptNeeded;
}
if (OdexFileNeedsRelocation()) {
return kPatchOatNeeded;
}
if (OatFileNeedsRelocation()) {
return kSelfPatchOatNeeded;
}
return HasOriginalDexFiles() ? kDex2OatNeeded : kNoDexOptNeeded;
}
OatFileIsUpToDate()
bool OatFileAssistant::OatFileIsUpToDate() {
if (!oat_file_is_up_to_date_attempted_) {
oat_file_is_up_to_date_attempted_ = true;
const OatFile* oat_file = GetOatFile();
================================================
要不GetOatFile()为空
要不GivenOatFileIsUpToDate()返回 false
================================================
if (oat_file == nullptr) {
================================================
手动删除oat目录或odex文件,此处会返回false
================================================
cached_oat_file_is_up_to_date_ = false;
} else {
================================================
根据自己添加的log打印,可以看到执行了GivenOatFileIsUpToDate()方法
================================================
cached_oat_file_is_up_to_date_ = GivenOatFileIsUpToDate(*oat_file);
}
}
return cached_oat_file_is_up_to_date_;
}
GetOatFile()
const OatFile* OatFileAssistant::GetOatFile() {
CHECK(!oat_file_released_) << "OatFile called after oat file released.";
if (!oat_file_load_attempted_) {
oat_file_load_attempted_ = true;
if (OatFileName() != nullptr) {
const std::string& oat_file_name = *OatFileName();
std::string error_msg;
cached_oat_file_.reset(OatFile::Open(oat_file_name.c_str(),
oat_file_name.c_str(), nullptr, nullptr, load_executable_,
dex_location_, &error_msg));
================================================
没找到oat目录
================================================
if (cached_oat_file_.get() == nullptr) {
VLOG(oat) << "OatFileAssistant test for existing oat file "
<< oat_file_name << ": " << error_msg;
}
}
}
return cached_oat_file_.get();
}
GivenOatFileIsUpToDate()
bool OatFileAssistant::GivenOatFileIsUpToDate(const OatFile& file) {
if (GivenOatFileIsOutOfDate(file)) {
================================================
此方法内一共有4处返回false,其余3处返回都有log打印,但在系统日志中没找到相关的log,以为一定是这里return false
后面发现因为VLOG(oat)的宏没开启,其实并不会输出VLOG(oat)相关的log打印
所以无法通过log打印,判断是否在此处返回了false,并且推翻了之前所有通过查看系统日志是否存在VLOG(oat)打印的推理结果
打开VLOG(oat)的宏,并在此处增加了log, 重新编译系统和复现问题
确认了的确是在此处执行了GivenOatFileIsOutOfDate(file),
往下看看GivenOatFileIsOutOfDate()何时会返回true
================================================
return false;
}
if (file.IsPic()) {
================================================
正常情况会在此处return true
================================================
return true;
}
const ImageInfo* image_info = GetImageInfo();
if (image_info == nullptr) {
VLOG(oat) << "No image to check oat relocation against.";
return false;
}
================================================
================================================
// Verify the oat_data_begin recorded for the image in the oat file matches
// the actual oat_data_begin for boot.oat in the image.
const OatHeader& oat_header = file.GetOatHeader();
uintptr_t oat_data_begin = oat_header.GetImageFileLocationOatDataBegin();
if (oat_data_begin != image_info->oat_data_begin) {
VLOG(oat) << file.GetLocation() <<
": Oat file image oat_data_begin (" << oat_data_begin << ")"
<< " does not match actual image oat_data_begin ("
<< image_info->oat_data_begin << ")";
return false;
}
// Verify the oat_patch_delta recorded for the image in the oat file matches
// the actual oat_patch_delta for the image.
int32_t oat_patch_delta = oat_header.GetImagePatchDelta();
if (oat_patch_delta != image_info->patch_delta) {
VLOG(oat) << file.GetLocation() <<
": Oat file image patch delta (" << oat_patch_delta << ")"
<< " does not match actual image patch delta ("
<< image_info->patch_delta << ")";
return false;
}
return true;
}
GivenOatFileIsOutOfDate()
bool OatFileAssistant::GivenOatFileIsOutOfDate(const OatFile& file) {
// Verify the dex checksum.
// Note: GetOatDexFile will return null if the dex checksum doesn't match
// what we provide, which verifies the primary dex checksum for us.
const uint32_t* dex_checksum_pointer = GetRequiredDexChecksum();
const OatFile::OatDexFile* oat_dex_file = file.GetOatDexFile(
dex_location_, dex_checksum_pointer, false);
if (oat_dex_file == nullptr) {
================================================
若没找到odex文件,return true
================================================
return true;
}
// Verify the dex checksums for any secondary multidex files
for (size_t i = 1; ; i++) {
std::string secondary_dex_location
= DexFile::GetMultiDexLocation(i, dex_location_);
const OatFile::OatDexFile* secondary_oat_dex_file
= file.GetOatDexFile(secondary_dex_location.c_str(), nullptr, false);
if (secondary_oat_dex_file == nullptr) {
// There are no more secondary dex files to check.
break;
}
std::string error_msg;
uint32_t expected_secondary_checksum = 0;
if (DexFile::GetChecksum(secondary_dex_location.c_str(),
&expected_secondary_checksum, &error_msg)) {
uint32_t actual_secondary_checksum
= secondary_oat_dex_file->GetDexFileLocationChecksum();
if (expected_secondary_checksum != actual_secondary_checksum) {
VLOG(oat) << "Dex checksum does not match for secondary dex: "
<< secondary_dex_location
<< ". Expected: " << expected_secondary_checksum
<< ", Actual: " << actual_secondary_checksum;
return true;
}
} else {
// If we can't get the checksum for the secondary location, we assume
// the dex checksum is up to date for this and all other secondary dex
// files.
break;
}
}
// Verify the image checksum
const ImageInfo* image_info = GetImageInfo();
if (image_info == nullptr) {
VLOG(oat) << "No image for oat image checksum to match against.";
return true;
}
if (file.GetOatHeader().GetImageFileLocationOatChecksum() != image_info->oat_checksum) {
================================================
开启了VLOG(oat)的宏之后,可以看到在此处 return true
也就是说不格式化烧录,可能会导致应用的oat缓存的校验信息和新系统校验信息不一致,触发了应用重新dex2oat的逻辑
================================================
VLOG(oat) << "Oat image checksum does not match image checksum.";
return true;
}
// The checksums are all good; the dex file is not out of date.
return false;
}
再去了解一下GetOatHeader()的实现,以下内容摘自其他博客
art/runtime/oat_file.cc
const OatHeader& OatFile::GetOatHeader() const {
return *reinterpret_cast<const OatHeader*>(Begin());
}
将begin_指向的内存,强制转换成OatHeader
OatHeader的定义如下
private:
uint8_t magic_[4];
uint8_t version_[4];
uint32_t adler32_checksum_;
InstructionSet instruction_set_;
uint32_t dex_file_count_;
uint32_t executable_offset_;
uint32_t interpreter_to_interpreter_bridge_offset_;
uint32_t interpreter_to_compiled_code_bridge_offset_;
uint32_t jni_dlsym_lookup_offset_;
uint32_t portable_resolution_trampoline_offset_;
uint32_t portable_to_interpreter_bridge_offset_;
uint32_t quick_resolution_trampoline_offset_;
uint32_t quick_to_interpreter_bridge_offset_;
uint32_t image_file_location_oat_checksum_;
uint32_t image_file_location_oat_data_begin_;
uint32_t image_file_location_size_;
uint8_t image_file_location_data_[0]; // note variable width data at end
DISALLOW_COPY_AND_ASSIGN(OatHeader);
};
以系统的Boot Oat(system@framework@boot.oat)为例, oatdata值为0x60a9d000,而通过看Program Header,可以知道elf文件起始被指定映射到了0x60a9c000:
可以知道,oatdata指向的位置在文件中的偏移是0x60a9d000-0x60a9c000=0x1000。拿出二进制编辑工具看看那里有什么:
原来所谓oat文件,其实就是隐藏在elf文件中的一个子文件,其有特殊的头和数据格式。我们对照着实际的数据一一做个分析:
- 首先是oat文件的magic code
6F 61 74 0A const uint8_t OatHeader::kOatMagic[] = { 'o', 'a', 't', '\n' };
- oat文件的版本号
30 30 37 00 const uint8_t OatHeader::kOatVersion[] = { '0', '0', '7', '\0' };
- checksum
79 C2 66 E5 uint32_t adler32_checksum_ = 2042783461;
总结
不格式化烧录系统,可能会导致应用的oat缓存的校验信息和新系统校验信息不一致,触发了应用重新dex2oat的逻辑
为什么是可能呢,按照我实际的测试情况,并不是所有旧系统 -》 新系统都会checksum校验失败而重新走dex2oat。个人猜测是因为修改了系统的某个模块才会导致两个系统的checksum不一样,比如修改过framework。