全志平台不格式化烧录系统,导致应用重新执行dex2oat

全志平台 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文件中的一个子文件,其有特殊的头和数据格式。我们对照着实际的数据一一做个分析:

  1. 首先是oat文件的magic code
    6F 61 74 0A
    const uint8_t OatHeader::kOatMagic[] = { 'o', 'a', 't', '\n' }; 
    
  2. oat文件的版本号
    30 30 37 00
    const uint8_t OatHeader::kOatVersion[] = { '0', '0', '7', '\0' };
    
  3. checksum
    79 C2 66 E5
    uint32_t adler32_checksum_ = 2042783461;
    

总结

不格式化烧录系统,可能会导致应用的oat缓存的校验信息和新系统校验信息不一致,触发了应用重新dex2oat的逻辑
为什么是可能呢,按照我实际的测试情况,并不是所有旧系统 -》 新系统都会checksum校验失败而重新走dex2oat。个人猜测是因为修改了系统的某个模块才会导致两个系统的checksum不一样,比如修改过framework。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值