dexvdex.odex与.oat

Posted wanglongjiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dexvdex.odex与.oat相关的知识,希望对你有一定的参考价值。

dex、vdex、.odex与.oat文件介绍

1、dex文件

在我们写Java代码的时候,生成的文件是.java文件。
对于PC上的java虚拟机(JVM)运行的是.class。

.java文件转成.class文件,需要jdk工具,转换命令:

javac xxxx.java

关于JVM虚拟机
为了使代码和平台无关,JAVA开发了 JVM,即 Java 虚拟机。它为每一个平台开发一个 JVM,也就意味着 JVM 是和平台相关的。Java 编译器将 .java 文件转换成 .class文件,也就是字节码。最终将字节码提供给 JVM,由 JVM 将它转换成机器码。

在Android端,Android上的Davlik虚拟机是运行.dex。所以还得将.class转成dex文件,即dex文件就是Android Dalvik虚拟机运行的程序。
.class转成dex文件 需要使用dx.bat工具,dx.bat工具在Android SDK中build-tools目录中可以找到,转换命令:

dx --dex --output = C:\\output.dex C:\\test
其中C:\\output.dex表示输出文件,C:\\test表示原文件的路径名。

关于Dalvik虚拟机
Dalvik是Google公司自己设计用于Android平台的虚拟机,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。
很长时间以来,Dalvik虚拟机一直被用户指责为拖慢安卓系统运行速度不如IOS的根源。
2014年6月25日,Android L 正式亮相于召开的谷歌I/O大会,Android L 改动幅度较大,谷歌将直接删除Dalvik,代替它的是传闻已久的ART。

关于ART
Dalvik 使用 JIT(Just in time)编译,而 ART 使用 AOT(Ahead of time)编译。Android 7.0 向 ART 中添加了一个 just-in-time(JIT)编译器,这样就可以在应用运行时持续的提高其性能。
ART 和 Dalvik 一样使用的是相同的 DEX 字节码。编译好的应用如果使用 ART 在安装时需要额外的时间用于编译,同时还需要更多的空间用于存储编译后的代码。
由于 ART 直接运行的是应用的机器码(native execution),它所占用的 CPU 资源要少于 使用 JIT 编译的 Dalvik。由于占用较少的 CPU 资源也就消耗更少的电池资源。

关于JIT (Just In Time )
使用 Dalvik JIT 编译器,每次应用在运行时,它实时的将一部分 Dalvik 字节码翻译成机器码。在程序的执行过程中,更多的代码被被编译并缓存。由于 JIT 只翻译一部分代码,它消耗的更少的内存,占用的更少的物理存储空间。

关于AOT(Ahead Of Time)
ART 内置了一个 Ahead-of-Time 编译器。在应用的安装期间,他就将 DEX 字节码翻译成机器码并存储在设备的存储器上。这个过程只在将应用安装到设备上时发生。由于不再需要 JIT 编译,代码的执行速度要快得多。

常规的反编译dex流程:
1、拿到apk文件,然后解压 ,得到 class.dex 文件
2、用dex2jar 把 class.dex 还原成 classes-dex2jar.jar 文件
3、用 jd-gui.exe 把 classes-dex2jar.jar 文件打开,就可以看到源码了。

2、vdex文件

在讲odex之前,需要先讲vdex(Android O开始加入的)
package直接转化的 可执行二进制码 文件:
1.第一次开机就会生成在/system/app/<packagename>/oat/下;
2.在系统运行过程中,虚拟机将其 从“/system/app”下 copy到“/data/davilk-cache/”下

为何要搞出个vdex文件
目的不是为了提升性能,而是为了避免不必要的验证Dex 文件合法性的过程,例如首次安装时进行dex2oat时会校验Dex 文件各个section的合法性,这时候使用的compiler filter 为了照顾安装速度等方面,并没有采用全量编译,当app盘启动后,运行一段时间后,收集了足够多的jit 热点方法信息,Android会在后台重新进行dex2oat, 将热点方法编译成机器代码,这时候就不用再重复做验证Dex文件的过程了,

3、odex文件

odex是优化版的dex。
在Android N之前,Dalvik虚拟机执行程序dex文件前,系统会对dex文件做优化,生成可执行文件odex,保存到data/dalvik-cache目录,最后把apk文件中的dex文件删除。

在Android O之后,odex是从vdex这个文件中 提取了部分模块生成的一个新的 可执行二进制码 文件 , odex从vdex中提取后,vdex的大小就减少了。具体过程:
1.第一次开机就会生成在/system/app/<packagename>/oat/下
2.在系统运行过程中,虚拟机将其 从“/system/app”下 copy到 “/data/davilk-cache/”下
3.odex + vdex = apk的全部源码 (vdex并不是独立于odex的文件,odex + vdex才代表一个apk)

4、oat文件

ART虚拟机使用的是oat文件,oat文件是一种Android私有ELF文件格式,它不仅包含有从DEX文件翻译而来的本地机器指令,还包含有原来的DEX文件内容。APK在安装的过程中,会通过dex2oat工具生成一个OAT文件。对于APK来说,oat文件实际上就是对odex文件的包装,即oat=odex,而对于一些framework中的一些jar包,会生成相应的oat尾缀的文件,如system@framework@boot-telephony-common.oat。

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | oat_file_assistant.cc 中涉及的 oat 文件生成流程 )

前言



在上一篇博客 【Android 逆向】ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 ) 中 , 分析了 ART 虚拟机下 DexClassLoader 类加载器加载 dex 文件的 Java 层流程 , 与 Dalvik 虚拟机下基本一致 , 从 native 层开始不一致 , 本篇博客开始分析 native 层的类加载流程 ;





一、dalvik_system_DexFile.cc#DexFile_openDexFileNative 函数分析



在下面的 DexFile_openDexFileNative 方法中 ,

jstring javaSourceName 参数是要加载的 dex 文件路径 ,

下面调用的 OpenDexFilesFromOat 函数 , 是生成 oat 文件的关键流程入口 ;

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                               class_loader,
                                                               dex_elements,
                                                               /*out*/ &oat_file,
                                                               /*out*/ &error_msgs);

dalvik_system_DexFile.cc#DexFile_openDexFileNative 源码 :

// TODO(calin): clean up the unused parameters (here and in libcore).
static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName ATTRIBUTE_UNUSED,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements) 
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == nullptr) 
    return 0;
  

  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;

  // OAT 文件
  const OatFile* oat_file = nullptr;

  // ★ 核心跳转 
  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                               class_loader,
                                                               dex_elements,
                                                               /*out*/ &oat_file,
                                                               /*out*/ &error_msgs);

  if (!dex_files.empty()) 
    jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
    if (array == nullptr) 
      ScopedObjectAccess soa(env);
      for (auto& dex_file : dex_files) 
        if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) 
          dex_file.release();
        
      
    
    return array;
   else 
    ScopedObjectAccess soa(env);
    CHECK(!error_msgs.empty());
    // The most important message is at the end. So set up nesting by going forward, which will
    // wrap the existing exception as a cause for the following one.
    auto it = error_msgs.begin();
    auto itEnd = error_msgs.end();
    for ( ; it != itEnd; ++it) 
      ThrowWrappedIOException("%s", it->c_str());
    

    return nullptr;
  

源码路径 : /art/runtime/native/dalvik_system_DexFile.cc#DexFile_openDexFileNative





二、oat_file_manager.cc#OpenDexFilesFromOat 函数分析



先声明 oat 文件对象 ,

  // 声明 OatFile 对象
  const OatFile* source_oat_file = nullptr;

然后判断是否有生成 oat 文件 , 如果是第一次调用 , 肯定没有生成 oat 文件 ,

!oat_file_assistant.IsUpToDate()

如果没有生成 oat 文件 , 则执行

oat_file_assistant.MakeUpToDate(/*profile_changed*/false, /*out*/ &error_msg)

方法 , 开始生成 oat 文件 ;


oat_file_manager.cc#OpenDexFilesFromOat 函数源码 :

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
    const char* dex_location,
    jobject class_loader,
    jobjectArray dex_elements,
    const OatFile** out_oat_file,
    std::vector<std::string>* error_msgs) 
  ScopedTrace trace(__FUNCTION__);
  CHECK(dex_location != nullptr);
  CHECK(error_msgs != nullptr);

  // 验证我们没有持有mutator锁,如果我们必须生成或重新定位oat文件,这可能会导致GC饥饿。
  Thread* const self = Thread::Current();
  Locks::mutator_lock_->AssertNotHeld(self);
  Runtime* const runtime = Runtime::Current();

  OatFileAssistant oat_file_assistant(dex_location,
                                      kRuntimeISA,
                                      !runtime->IsAotCompiler());

  // 锁定目标oat位置以避免生成和加载oat文件。
  std::string error_msg;
  if (!oat_file_assistant.Lock(/*out*/&error_msg)) 
    // Don't worry too much if this fails. If it does fail, it's unlikely we
    // can generate an oat file anyway.
    VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg;
  

  // 声明 OatFile 对象
  const OatFile* source_oat_file = nullptr;

  // 判断是否有生成 oat 文件 , 如果是第一次调用 , 肯定没有生成 oat 文件
  if (!oat_file_assistant.IsUpToDate()) 
    // 如果可以,根据从当前运行时选项派生的--compiler-filter选项更新磁盘上的oat文件
    // 这可能会失败,但没关系。这里最重要的是尽最大努力。
    // ★ 核心跳转 
    switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/false, /*out*/ &error_msg)) 
      case OatFileAssistant::kUpdateFailed:
        LOG(WARNING) << error_msg;
        break;

      case OatFileAssistant::kUpdateNotAttempted:
        // 如果我们决定不尝试更新oat文件,请避免滥发日志。
        VLOG(oat) << error_msg;
        break;

      case OatFileAssistant::kUpdateSucceeded:
        // Nothing to do.
        break;
    
  

  // Get the oat file on disk.
  std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());

  if (oat_file != nullptr) 
    // 仅当文件没有冲突时才获取该文件,否则我们必须获取该文件,因为存在预选项。
    bool accept_oat_file =
        !HasCollisions(oat_file.get(), class_loader, dex_elements, /*out*/ &error_msg);
    if (!accept_oat_file) 
      // 冲突检查失败。打印警告。
      if (Runtime::Current()->IsDexFileFallbackEnabled()) 
        if (!oat_file_assistant.HasOriginalDexFiles()) 
          // 我们需要回退,但没有原始的dex文件。
          // 我们必须回退到打开现有的oat文件。
          // 这可能不安全,因此我们对此发出警告。
          accept_oat_file = true;

          LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. "
                       << "Allow oat file use. This is potentially dangerous.";
         else 
          // 我们必须回退并找到原始的dex文件-从APK中提取它们。
          // 同时警告此操作,因为它可能会造成浪费。
          LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : "
                       << dex_location;
          LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance.";
        
       else 
        // TODO: 我们应该删除这个。我们在这里的事实意味着没有设置-Xno dex文件回退,
        // 这意味着我们永远不应该回退。
        // 如果我们没有原始的dex文件,我们应该按照标志的意图失败解析。
        if (!oat_file_assistant.HasOriginalDexFiles()) 
          accept_oat_file = true;
        

        LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to "
                        " load classes for " << dex_location;
      

      LOG(WARNING) << error_msg;
    

    if (accept_oat_file) 
      VLOG(class_linker) << "Registering " << oat_file->GetLocation();
      source_oat_file = RegisterOatFile(std::move(oat_file));
      *out_oat_file = source_oat_file;
    
  

  std::vector<std::unique_ptr<const DexFile>> dex_files;

  // Load the dex files from the oat file.
  if (source_oat_file != nullptr) 
    bool added_image_space = false;
    if (source_oat_file->IsExecutable()) 
      std::unique_ptr<gc::space::ImageSpace> image_space =
          kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr;
      if (image_space != nullptr) 
        ScopedObjectAccess soa(self);
        StackHandleScope<1> hs(self);
        Handle<mirror::ClassLoader> h_loader(
            hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));
        // Can not load app image without class loader.
        if (h_loader != nullptr) 
          std::string temp_error_msg;
          // Add image space has a race condition since other threads could be reading from the
          // spaces array.
          
            ScopedThreadSuspension sts(self, kSuspended);
            gc::ScopedGCCriticalSection gcs(self,
                                            gc::kGcCauseAddRemoveAppImageSpace,
                                            gc::kCollectorTypeAddRemoveAppImageSpace);
            ScopedSuspendAll ssa("Add image space");
            runtime->GetHeap()->AddSpace(image_space.get());
          
          
            ScopedTrace trace2(StringPrintf("Adding image space for location %s", dex_location));
            added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(),
                                                                         h_loader,
                                                                         dex_elements,
                                                                         dex_location,
                                                                         /*out*/&dex_files,
                                                                         /*out*/&temp_error_msg);
          
          if (added_image_space) 
            // Successfully added image space to heap, release the map so that it does not get
            // freed.
            image_space.release();
           else 
            LOG(INFO) << "Failed to add image file " << temp_error_msg;
            dex_files.clear();
            
              ScopedThreadSuspension sts(self, kSuspended);
              gc::ScopedGCCriticalSection gcs(self,
                                              gc::kGcCauseAddRemoveAppImageSpace,
                                              gc::kCollectorTypeAddRemoveAppImageSpace);
              ScopedSuspendAll ssa("Remove image space");
              runtime->GetHeap()->RemoveSpace(image_space.get());
            
            // Non-fatal, don't update error_msg.
          
        
      
    
    if (!added_image_space) 
      DCHECK(dex_files.empty());
      dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
    
    if (dex_files.empty()) 
      error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation());
    
  

  // Fall back to running out of the original dex file if we couldn't load any
  // dex_files from the oat file.
  if (dex_files.empty()) 
    if (oat_file_assistant.HasOriginalDexFiles()) 
      if (Runtime::Current()->IsDexFileFallbackEnabled()) 
        static constexpr bool kVerifyChecksum = true;
        if (!DexFile::Open(
            dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) 
          LOG(WARNING) << error_msg;
          error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)
                                + " because: " + error_msg);
        
       else 
        error_msgs->push_back("Fallback mode disabled, skipping dex files.");
      
     else 
      error_msgs->push_back("No original dex files found for dex location "
          + std::string(dex_location));
    
  

  return dex_files;

源码路径 : /art/runtime/oat_file_manager.cc#OpenDexFilesFromOat





三、oat_file_assistant.cc#MakeUpToDate 函数分析



在 oat_file_assistant.cc#MakeUpToDate 函数中 , 最终调用了 GenerateOatFileNoChecks 函数 , 执行下一步操作 ;


oat_file_assistant.cc#MakeUpToDate 函数源码 :

OatFileAssistant::ResultOfAttemptToUpdate
OatFileAssistant::MakeUpToDate(bool profile_changed, std::string* error_msg) 
  CompilerFilter::Filter target;
  if (!GetRuntimeCompilerFilterOption(&target, error_msg)) 
    return kUpdateNotAttempted;
  

  OatFileInfo& info = GetBestInfo();
  switch (info.GetDexOptNeeded(target, profile_changed)) 
    case kNoDexOptNeeded:
      return kUpdateSucceeded;

    // TODO: 现在,不要为我们打电话的各种方式而烦恼
	// dex2oat生成oat文件。始终生成oat文件,就像它
	// KDEX2O是从头开始的。
    case kDex2OatFromScratch:
    case kDex2OatForBootImage:
    case kDex2OatForRelocation:
    case kDex2OatForFilter:
      // ★ 核心跳转 
      return GenerateOatFileNoChecks(info, target, error_msg);
  
  UNREACHABLE();

源码路径 : /art/runtime/oat_file_assistant.cc#MakeUpToDate





四、oat_file_assistant.cc#GenerateOatFileNoChecks 函数分析



先判断 Dex2Oat 当前是否可用 , 如果不可用 , 直接返回 ;

!runtime->IsDex2OatEnabled()

oat_file_assistant.cc#GenerateOatFileNoChecks 函数源码 :

OatFileAssistant::ResultOfAttemptToUpdate OatFileAssistant::GenerateOatFileNoChecks(
      OatFileAssistant::OatFileInfo& info, CompilerFilter::Filter filter, std::string* error_msg) 
  CHECK(error_msg != nullptr);

  Runtime* runtime = Runtime::Current();

  // 判断 Dex2Oat 当前是否可用 , 如果不可用 , 直接返回 
  if (!runtime->IsDex2OatEnabled()) 
    *error_msg = "Generation of oat file for dex location " + dex_location_
      + " not attempted because dex2oat is disabled.";
    return kUpdateNotAttempted;
  

  if (info.Filename() == nullptr) 
    *error_msg = "Generation of oat file for dex location " + dex_location_
      + " not attempted because the oat file name could not be determined.";
    return kUpdateNotAttempted;
  
  const std::string& oat_file_name = *info.Filename();
  const std::string& vdex_file_name = GetVdexFilename(oat_file_name);

  // dex2oat ignores missing dex files and doesn't report an error.
  // Check explicitly here so we can detect the error properly.
  // TODO: Why does dex2oat behave that way?
  struct stat dex_path_stat;
  if (TEMP_FAILURE_RETRY(stat(dex_location_.c_str(), &dex_path_stat)) != 0) 
    *error_msg = "Could not access dex location " + dex_location_ + ":" + strerror(errno);
    return kUpdateNotAttempted;
  

  // If this is the odex location, we need to create the odex file layout (../oat/isa/..)
  if (!info.IsOatLocation()) 
    if (!PrepareOdexDirectories(dex_location_, oat_file_name, isa_, error_msg)) 
      return kUpdateNotAttempted;
    
  

  // Set the permissions for the oat and the vdex files.
  // The user always gets read and write while the group and others propagate
  // the reading access of the original dex file.
  mode_t file_mode = S_IRUSR | S_IWUSR |
      (dex_path_stat.st_mode & S_IRGRP) |
      (dex_path_stat.st_mode & S_IROTH);

  std::unique_ptr<File> vdex_file(OS::CreateEmptyFile(vdex_file_name.c_str()));
  if (vdex_file.get() == nullptr) 
    *error_msg = "Generation of oat file " + oat_file_name
      + " not attempted because the vdex file " + vdex_file_name
      + " could not be opened.";
    return kUpdateNotAttempted;
  

  if (fchmod(vdex_file->Fd(), file_mode) != 0) 
 

以上是关于dexvdex.odex与.oat的主要内容,如果未能解决你的问题,请参考以下文章

Android oat文件提取转换

Android 逆向ART 脱壳 ( dex2oat 脱壳 | aosp 中搜索 dex2oat 源码 | dex2oat.cc#main 主函数源码 )

coredump中添加oat文件的方法

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | oat_file_assistant.cc 中涉及的 oat 文件生成流程 )

Android 逆向ART 函数抽取加壳 ② ( 禁用 dex2oat 简介 | TurboDex 中禁用 dex2oat 参考示例 )

OpenAttestation(OAT)