深入理解ART虚拟机—ImageSpace的加载过程分析

Posted threepigs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解ART虚拟机—ImageSpace的加载过程分析相关的知识,希望对你有一定的参考价值。

上一篇《深入理解ART虚拟机—虚拟机的启动》分析了art虚拟机的启动过程,不过跳过了一个技术点,就是ImageSpace。由之前的分析可知,ClassLinker是用来加载类对象的,有三类类对象,一是ClassRoot,包括java.lang包下的类,这类的类对象是语言自己的,不需要import,ART虚拟机是定义在mirror目录下的。二是boot_class_path,包括jdk和android.jar,这类的类对象是通过指定class path或者系统的Image文件。三是apk自己的dex/oat。ClassLinker的InitFromImage是通过ImageSpace来初始化ClassRoot和boot class path,InitWithoutImage的ClassRoot是直接AllocClass,boot class path是由直接由class path的DexFile生成DexCache加入dex_caches来生成的。第三类类对象是和应用自身相关的,都是走DexCache。

Runtime初始化的时候会创建gc::Heap对象,在Heap的构造函数会创建ImageSpace的实例。

    auto* image_space = space::ImageSpace::Create(image_file_name.c_str(), image_instruction_set, &error_msg);
ImageSpace::Create是ImageSpace的静态构造方法,先找到image文件的路径,之后会生成load镜像文件,具体代码如下:

ImageSpace* ImageSpace::Create(const char* image_location,
                               const InstructionSet image_isa,
                               std::string* error_msg) 
  std::string system_filename;
  bool has_system = false;
  std::string cache_filename;
  bool has_cache = false;
  bool dalvik_cache_exists = false;
  bool is_global_cache = true;
  const bool found_image = FindImageFilename(image_location, image_isa, &system_filename,
                                             &has_system, &cache_filename, &dalvik_cache_exists,
                                             &has_cache, &is_global_cache);

  if (Runtime::Current()->IsZygote()) 
    MarkZygoteStart(image_isa, Runtime::Current()->GetZygoteMaxFailedBoots());
  

  ImageSpace* space;
  bool relocate = Runtime::Current()->ShouldRelocate();
  bool can_compile = Runtime::Current()->IsImageDex2OatEnabled();
  if (found_image) 
    const std::string* image_filename;
    bool is_system = false;
    bool relocated_version_used = false;
    if (relocate) 
      if (!dalvik_cache_exists) 
        *error_msg = StringPrintf("Requiring relocation for image '%s' at '%s' but we do not have "
                                  "any dalvik_cache to find/place it in.",
                                  image_location, system_filename.c_str());
        return nullptr;
      
      if (has_system) 
        if (has_cache && ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) 
          // We already have a relocated version
          image_filename = &cache_filename;
          relocated_version_used = true;
         else 
          // We cannot have a relocated version, Relocate the system one and use it.

          std::string reason;
          bool success;

          // Check whether we are allowed to relocate.
          if (!can_compile) 
            reason = "Image dex2oat disabled by -Xnoimage-dex2oat.";
            success = false;
           else if (!ImageCreationAllowed(is_global_cache, &reason)) 
            // Whether we can write to the cache.
            success = false;
           else 
            // Try to relocate.
            success = RelocateImage(image_location, cache_filename.c_str(), image_isa, &reason);
          

          if (success) 
            relocated_version_used = true;
            image_filename = &cache_filename;
           else 
            *error_msg = StringPrintf("Unable to relocate image '%s' from '%s' to '%s': %s",
                                      image_location, system_filename.c_str(),
                                      cache_filename.c_str(), reason.c_str());
            // We failed to create files, remove any possibly garbage output.
            // Since ImageCreationAllowed was true above, we are the zygote
            // and therefore the only process expected to generate these for
            // the device.
            PruneDalvikCache(image_isa);
            return nullptr;
          
        
       else 
        CHECK(has_cache);
        // We can just use cache's since it should be fine. This might or might not be relocated.
        image_filename = &cache_filename;
      
     else 
      if (has_system && has_cache) 
        // Check they have the same cksum. If they do use the cache. Otherwise system.
        if (ChecksumsMatch(system_filename.c_str(), cache_filename.c_str())) 
          image_filename = &cache_filename;
          relocated_version_used = true;
         else 
          image_filename = &system_filename;
          is_system = true;
        
       else if (has_system) 
        image_filename = &system_filename;
        is_system = true;
       else 
        CHECK(has_cache);
        image_filename = &cache_filename;
      
    
    
      // Note that we must not use the file descriptor associated with
      // ScopedFlock::GetFile to Init the image file. We want the file
      // descriptor (and the associated exclusive lock) to be released when
      // we leave Create.
      ScopedFlock image_lock;
      image_lock.Init(image_filename->c_str(), error_msg);
      VLOG(startup) << "Using image file " << image_filename->c_str() << " for image location "
                    << image_location;
      // If we are in /system we can assume the image is good. We can also
      // assume this if we are using a relocated image (i.e. image checksum
      // matches) since this is only different by the offset. We need this to
      // make sure that host tests continue to work.
      space = ImageSpace::Init(image_filename->c_str(), image_location,
                               !(is_system || relocated_version_used), error_msg);
    
    if (space != nullptr) 
      return space;
    

    if (relocated_version_used) 
      // Something is wrong with the relocated copy (even though checksums match). Cleanup.
      // This can happen if the .oat is corrupt, since the above only checks the .art checksums.
      // TODO: Check the oat file validity earlier.
      *error_msg = StringPrintf("Attempted to use relocated version of %s at %s generated from %s "
                                "but image failed to load: %s",
                                image_location, cache_filename.c_str(), system_filename.c_str(),
                                error_msg->c_str());
      PruneDalvikCache(image_isa);
      return nullptr;
     else if (is_system) 
      // If the /system file exists, it should be up-to-date, don't try to generate it.
      *error_msg = StringPrintf("Failed to load /system image '%s': %s",
                                image_filename->c_str(), error_msg->c_str());
      return nullptr;
     else 
      // Otherwise, log a warning and fall through to GenerateImage.
      LOG(WARNING) << *error_msg;
    
  

  if (!can_compile) 
    *error_msg = "Not attempting to compile image because -Xnoimage-dex2oat";
    return nullptr;
   else if (!dalvik_cache_exists) 
    *error_msg = StringPrintf("No place to put generated image.");
    return nullptr;
   else if (!ImageCreationAllowed(is_global_cache, error_msg)) 
    return nullptr;
   else if (!GenerateImage(cache_filename, image_isa, error_msg)) 
    *error_msg = StringPrintf("Failed to generate image '%s': %s",
                              cache_filename.c_str(), error_msg->c_str());
    // We failed to create files, remove any possibly garbage output.
    // Since ImageCreationAllowed was true above, we are the zygote
    // and therefore the only process expected to generate these for
    // the device.
    PruneDalvikCache(image_isa);
    return nullptr;
   else 
    // Check whether there is enough space left over after we have generated the image.
    if (!CheckSpace(cache_filename, error_msg)) 
      // No. Delete the generated image and try to run out of the dex files.
      PruneDalvikCache(image_isa);
      return nullptr;
    

    // Note that we must not use the file descriptor associated with
    // ScopedFlock::GetFile to Init the image file. We want the file
    // descriptor (and the associated exclusive lock) to be released when
    // we leave Create.
    ScopedFlock image_lock;
    image_lock.Init(cache_filename.c_str(), error_msg);
    space = ImageSpace::Init(cache_filename.c_str(), image_location, true, error_msg);
    if (space == nullptr) 
      *error_msg = StringPrintf("Failed to load generated image '%s': %s",
                                cache_filename.c_str(), error_msg->c_str());
    
    return space;
  
FindImageFilename会查找系统和data区是否有image file,系统区镜像的目录是/system/framework/<image_isa>/boot.art,data区的image file的路径是/data/dalvik-cache/<image_isa>/boot.art。/system 下面的是在系统区,是ROM带的,而/data 下的image file是系统运行时生成的,GenerateImage就是生成这个cache image file的函数。image file存在的情况下,就能调用ImageSpace::Init来初始化ImageSpace了。

ImageSpace* ImageSpace::Init(const char* image_filename, const char* image_location,
                             bool validate_oat_file, std::string* error_msg) 
  CHECK(image_filename != nullptr);
  CHECK(image_location != nullptr);

  uint64_t start_time = 0;
  if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) 
    start_time = NanoTime();
    LOG(INFO) << "ImageSpace::Init entering image_filename=" << image_filename;
  

  std::unique_ptr<File> file(OS::OpenFileForReading(image_filename));
  if (file.get() == nullptr) 
    *error_msg = StringPrintf("Failed to open '%s'", image_filename);
    return nullptr;
  
  ImageHeader image_header;
  bool success = file->ReadFully(&image_header, sizeof(image_header));
  if (!success || !image_header.IsValid()) 
    *error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
    return nullptr;
  
  // Check that the file is large enough.
  uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
  if (image_header.GetImageSize() > image_file_size) 
    *error_msg = StringPrintf("Image file too small for image heap: %" PRIu64 " vs. %zu.",
                              image_file_size, image_header.GetImageSize());
    return nullptr;
  

  if (kIsDebugBuild) 
    LOG(INFO) << "Dumping image sections";
    for (size_t i = 0; i < ImageHeader::kSectionCount; ++i) 
      const auto section_idx = static_cast<ImageHeader::ImageSections>(i);
      auto& section = image_header.GetImageSection(section_idx);
      LOG(INFO) << section_idx << " start="
          << reinterpret_cast<void*>(image_header.GetImageBegin() + section.Offset()) << " "
          << section;
    
  

  const auto& bitmap_section = image_header.GetImageSection(ImageHeader::kSectionImageBitmap);
  auto end_of_bitmap = static_cast<size_t>(bitmap_section.End());
  if (end_of_bitmap != image_file_size) 
    *error_msg = StringPrintf(
        "Image file size does not equal end of bitmap: size=%" PRIu64 " vs. %zu.", image_file_size,
        end_of_bitmap);
    return nullptr;
  

  // Note: The image header is part of the image due to mmap page alignment required of offset.
  std::unique_ptr<MemMap> map(MemMap::MapFileAtAddress(
      image_header.GetImageBegin(), image_header.GetImageSize(),
      PROT_READ | PROT_WRITE, MAP_PRIVATE, file->Fd(), 0, false, image_filename, error_msg));
  if (map.get() == nullptr) 
    DCHECK(!error_msg->empty());
    return nullptr;
  
  CHECK_EQ(image_header.GetImageBegin(), map->Begin());
  DCHECK_EQ(0, memcmp(&image_header, map->Begin(), sizeof(ImageHeader)));

  std::unique_ptr<MemMap> image_map(MemMap::MapFileAtAddress(
      nullptr, bitmap_section.Size(), PROT_READ, MAP_PRIVATE, file->Fd(),
      bitmap_section.Offset(), false, image_filename, error_msg));
  if (image_map.get() == nullptr) 
    *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
    return nullptr;
  
  uint32_t bitmap_index = bitmap_index_.FetchAndAddSequentiallyConsistent(1);
  std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", image_filename,
                                       bitmap_index));
  std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap(
      accounting::ContinuousSpaceBitmap::CreateFromMemMap(
          bitmap_name, image_map.release(), reinterpret_cast<uint8_t*>(map->Begin()),
          accounting::ContinuousSpaceBitmap::ComputeHeapSize(bitmap_section.Size())));
  if (bitmap.get() == nullptr) 
    *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str());
    return nullptr;
  

  // We only want the mirror object, not the ArtFields and ArtMethods.
  uint8_t* const image_end =
      map->Begin() + image_header.GetImageSection(ImageHeader::kSectionObjects).End();
  std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, image_location,
                                                   map.release(), bitmap.release(), image_end));

  // VerifyImageAllocations() will be called later in Runtime::Init()
  // as some class roots like ArtMethod::java_lang_reflect_ArtMethod_
  // and ArtField::java_lang_reflect_ArtField_, which are used from
  // Object::SizeOf() which VerifyImageAllocations() calls, are not
  // set yet at this point.

  space->oat_file_.reset(space->OpenOatFile(image_filename, error_msg));
  if (space->oat_file_.get() == nullptr) 
    DCHECK(!error_msg->empty());
    return nullptr;
  
  space->oat_file_non_owned_ = space->oat_file_.get();

  if (validate_oat_file && !space->ValidateOatFile(error_msg)) 
    DCHECK(!error_msg->empty());
    return nullptr;
  

  Runtime* runtime = Runtime::Current();
  runtime->SetInstructionSet(space->oat_file_->GetOatHeader().GetInstructionSet());

  runtime->SetResolutionMethod(image_header.GetImageMethod(ImageHeader::kResolutionMethod));
  runtime->SetImtConflictMethod(image_header.GetImageMethod(ImageHeader::kImtConflictMethod));
  runtime->SetImtUnimplementedMethod(
      image_header.GetImageMethod(ImageHeader::kImtUnimplementedMethod));
  runtime->SetCalleeSaveMethod(
      image_header.GetImageMethod(ImageHeader::kCalleeSaveMethod), Runtime::kSaveAll);
  runtime->SetCalleeSaveMethod(
      image_header.GetImageMethod(ImageHeader::kRefsOnlySaveMethod), Runtime::kRefsOnly);
  runtime->SetCalleeSaveMethod(
      image_header.GetImageMethod(ImageHeader::kRefsAndArgsSaveMethod), Runtime::kRefsAndArgs);

  if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) 
    LOG(INFO) << "ImageSpace::Init exiting (" << PrettyDuration(NanoTime() - start_time)
             << ") " << *space.get();
  
  return space.release();
分析Init函数,先是打开文件读出文件的内容

  std::unique_ptr<File> file(OS::OpenFileForReading(image_filename));
  if (file.get() == nullptr) 
    *error_msg = StringPrintf("Failed to open '%s'", image_filename);
    return nullptr;
  

boot.art的ImageHeader布局如下:


读ImageHeader,并mmap到MemMap。

  ImageHeader image_header;
  bool success = file->ReadFully(&image_header, sizeof(image_header));
  if (!success || !image_header.IsValid()) 
    *error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
    return nullptr;
  
  std::unique_ptr<MemMap> map(MemMap::MapFileAtAddress(
      image_header.GetImageBegin(), image_header.GetImageSize(),
      PROT_READ | PROT_WRITE, MAP_PRIVATE, file->Fd(), 0, false, image_filename, error_msg));
  if (map.get() == nullptr) 
    DCHECK(!error_msg->empty());
    return nullptr;      
   
Header的开头是magic string,接着Image Begin和ImageSize,就是镜像的开始和大小,再后面就是Bitmap的offset和size,bitmap的session被mmap之后会创建bitmap的SpaceBitmap,这个主要是作为ImageSpace的live_bitmap,GC的时候会用。
  std::unique_ptr<MemMap> image_map(MemMap::MapFileAtAddress(
      nullptr, bitmap_section.Size(), PROT_READ, MAP_PRIVATE, file->Fd(),
      bitmap_section.Offset(), false, image_filename, error_msg));
  if (image_map.get() == nullptr) 
    *error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
    return nullptr;
  
  uint32_t bitmap_index = bitmap_index_.FetchAndAddSequentiallyConsistent(1);
  std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u", image_filename,
                                       bitmap_index));
  std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap(
      accounting::ContinuousSpaceBitmap::CreateFromMemMap(
          bitmap_name, image_map.release(), reinterpret_cast<uint8_t*>(map->Begin()),
          accounting::ContinuousSpaceBitmap::ComputeHeapSize(bitmap_section.Size())));
  if (bitmap.get() == nullptr) 
    *error_msg = StringPrintf("Could not create bitmap '%s'", bitmap_name.c_str());
    return nullptr;
  
最后由image header和live_bitmap初始化ImageSpace的实例

 std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename, image_location,
                                                   map.release(), bitmap.release(), image_end));

总结一下,image文件里包含image header和image body,image header的的结构见上图,主要是几个偏移:bitmap offset,image roots,image_methods_ ,oat offset。

除了最后一个oat offset,其他的偏移则是在image file里。image methods指向的是几个公共的ArtMethod,后续分析函数执行的时候再细说,image roots是几类root class,包括DexCache和ClassRoots。oat offset由OatFile open的时候赋值,指向的是OatFile的相关地址,所以当OatFile open了之后,boot.art/boot.oat就联系在一起了。

 OatFile* oat_file = OatFile::Open(oat_filename, oat_filename, image_header.GetOatDataBegin(),
                                    image_header.GetOatFileBegin(),
                                    !Runtime::Current()->IsAotCompiler(),
                                    nullptr, error_msg);


ImageSpace初始化之后,打开boot.oat文件,boot.art和boot.oat是一对的

space->oat_file_.reset(space->OpenOatFile(image_filename, error_msg));
  if (space->oat_file_.get() == nullptr) 
    DCHECK(!error_msg->empty());
    return nullptr;
  
  space->oat_file_non_owned_ = space->oat_file_.get();

  if (validate_oat_file && !space->ValidateOatFile(error_msg)) 
    DCHECK(!error_msg->empty());
    return nullptr;
  
OpenOatFile会根据image_filename找到对应的boot.oat文件,并创建OatFile

OatFile* ImageSpace::OpenOatFile(const char* image_path, std::string* error_msg) const 
  const ImageHeader& image_header = GetImageHeader();
  std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path);

  CHECK(image_header.GetOatDataBegin() != nullptr);

  OatFile* oat_file = OatFile::Open(oat_filename, oat_filename, image_header.GetOatDataBegin(),
                                    image_header.GetOatFileBegin(),
                                    !Runtime::Current()->IsAotCompiler(),
                                    nullptr, error_msg);
  if (oat_file == nullptr) 
    *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s",
                              oat_filename.c_str(), GetName(), error_msg->c_str());
    return nullptr;
  
  uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
  uint32_t image_oat_checksum = image_header.GetOatChecksum();
  if (oat_checksum != image_oat_checksum) 
    *error_msg = StringPrintf("Failed to match oat file checksum 0x%x to expected oat checksum 0x%x"
                              " in image %s", oat_checksum, image_oat_checksum, GetName());
    return nullptr;
  
  int32_t image_patch_delta = image_header.GetPatchDelta();
  int32_t oat_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta();
  if (oat_patch_delta != image_patch_delta && !image_header.CompilePic()) 
    // We should have already relocated by this point. Bail out.
    *error_msg = StringPrintf("Failed to match oat file patch delta %d to expected patch delta %d "
                              "in image %s", oat_patch_delta, image_patch_delta, GetName());
    return nullptr;
  

  return oat_file;

OatFile::Open打开boot.oat,oat文件是elf文件的扩展,在elf的基础上,多了oat data section,oat exec section,dynamic section

oat data section: 保存oat文件相关信息,包括oat header,oat class信息,对应的dex文件信息、文件内容

oat exec section :保存oat编译的native code

dynamic secion :保存oat data和oat exec的offset和size

oat文件是由PackageManager调用dex2oat命令在apk安装时、系统启动扫描apk时生成的,所以编译后的native code和编译前的dex文件都同时存在,这样art能兼容dalvik的解释器工作模式。正式由于oat是elf格式,所以oat的打开可以由dlopen、dlsym系统函数来加载上面oat的三个section,即OpenDlopen函数。同时支持OpenElfFile的方式,用art自己的elf loader来加载。

OatFile* OatFile::Open(const std::string& filename,
                       const std::string& location,
                       uint8_t* requested_base,
                       uint8_t* oat_file_begin,
                       bool executable,
                       const char* abs_dex_location,
                       std::string* error_msg) 
  CHECK(!filename.empty()) << location;
  CheckLocation(location);
  std::unique_ptr<OatFile> ret;

  // Use dlopen only when flagged to do so, and when it's OK to load things executable.
  // TODO: Also try when not executable? The issue here could be re-mapping as writable (as
  //       !executable is a sign that we may want to patch), which may not be allowed for
  //       various reasons.
  if (kUseDlopen && (kIsTargetBuild || kUseDlopenOnHost) && executable) 
    // Try to use dlopen. This may fail for various reasons, outlined below. We try dlopen, as
    // this will register the oat file with the linker and allows libunwind to find our info.
    ret.reset(OpenDlopen(filename, location, requested_base, abs_dex_location, error_msg));
    if (ret.get() != nullptr) 
      return ret.release();
    
    if (kPrintDlOpenErrorMessage) 
      LOG(ERROR) << "Failed to dlopen: " << *error_msg;
    
  

  // If we aren't trying to execute, we just use our own ElfFile loader for a couple reasons:
  //
  // On target, dlopen may fail when compiling due to selinux restrictions on installd.
  //
  // We use our own ELF loader for Quick to deal with legacy apps that
  // open a generated dex file by name, remove the file, then open
  // another generated dex file with the same name. http://b/10614658
  //
  // On host, dlopen is expected to fail when cross compiling, so fall back to OpenElfFile.
  //
  //
  // Another independent reason is the absolute placement of boot.oat. dlopen on the host usually
  // does honor the virtual address encoded in the ELF file only for ET_EXEC files, not ET_DYN.
  std::unique_ptr<File> file(OS::OpenFileForReading(filename.c_str()));
  if (file == nullptr) 
    *error_msg = StringPrintf("Failed to open oat filename for reading: %s", strerror(errno));
    return nullptr;
  
  ret.reset(OpenElfFile(file.get(), location, requested_base, oat_file_begin, false, executable,
                        abs_dex_location, error_msg));

  // It would be nice to unlink here. But we might have opened the file created by the
  // ScopedLock, which we better not delete to avoid races. TODO: Investigate how to fix the API
  // to allow removal when we know the ELF must be borked.
  return ret.release();

OpenDlopen函数:

OatFile* OatFile::OpenDlopen(const std::string& elf_filename,
                             const std::string& location,
                             uint8_t* requested_base,
                             const char* abs_dex_location,
                             std::string* error_msg) 
  std::unique_ptr<OatFile> oat_file(new OatFile(location, true));
  bool success = oat_file->Dlopen(elf_filename, requested_base, abs_dex_location, error_msg);
  if (!success) 
    return nullptr;
  
  return oat_file.release();

bool OatFile::Dlopen(const std::string& elf_filename, uint8_t* requested_base,
                     const char* abs_dex_location, std::string* error_msg) 
#ifdef __APPLE__
  // The dl_iterate_phdr syscall is missing.  There is similar API on OSX,
  // but let's fallback to the custom loading code for the time being.
  UNUSED(elf_filename);
  UNUSED(requested_base);
  UNUSED(abs_dex_location);
  UNUSED(error_msg);
  return false;
#else
  std::unique_ptr<char> absolute_path(realpath(elf_filename.c_str(), nullptr));
  if (absolute_path == nullptr) 
    *error_msg = StringPrintf("Failed to find absolute path for '%s'", elf_filename.c_str());
    return false;
  
#ifdef HAVE_ANDROID_OS
  android_dlextinfo extinfo;
  extinfo.flags = ANDROID_DLEXT_FORCE_LOAD | ANDROID_DLEXT_FORCE_FIXED_VADDR;
  dlopen_handle_ = android_dlopen_ext(absolute_path.get(), RTLD_NOW, &extinfo);
#else
  dlopen_handle_ = dlopen(absolute_path.get(), RTLD_NOW);
#endif
  if (dlopen_handle_ == nullptr) 
    *error_msg = StringPrintf("Failed to dlopen '%s': %s", elf_filename.c_str(), dlerror());
    return false;
  
  begin_ = reinterpret_cast<uint8_t*>(dlsym(dlopen_handle_, "oatdata"));
  if (begin_ == nullptr) 
    *error_msg = StringPrintf("Failed to find oatdata symbol in '%s': %s", elf_filename.c_str(),
                              dlerror());
    return false;
  
  if (requested_base != nullptr && begin_ != requested_base) 
    PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
    *error_msg = StringPrintf("Failed to find oatdata symbol at expected address: "
                              "oatdata=%p != expected=%p, %s. See process maps in the log.",
                              begin_, requested_base, elf_filename.c_str());
    return false;
  
  end_ = reinterpret_cast<uint8_t*>(dlsym(dlopen_handle_, "oatlastword"));
  if (end_ == nullptr) 
    *error_msg = StringPrintf("Failed to find oatlastword symbol in '%s': %s", elf_filename.c_str(),
                              dlerror());
    return false;
  
  // Readjust to be non-inclusive upper bound.
  end_ += sizeof(uint32_t);

  bss_begin_ = reinterpret_cast<uint8_t*>(dlsym(dlopen_handle_, "oatbss"));
  if (bss_begin_ == nullptr) 
    // No .bss section. Clear dlerror().
    bss_end_ = nullptr;
    dlerror();
   else 
    bss_end_ = reinterpret_cast<uint8_t*>(dlsym(dlopen_handle_, "oatbsslastword"));
    if (bss_end_ == nullptr) 
      *error_msg = StringPrintf("Failed to find oatbasslastword symbol in '%s'",
                                elf_filename.c_str());
      return false;
    
    // Readjust to be non-inclusive upper bound.
    bss_end_ += sizeof(uint32_t);
  
  // Ask the linker where it mmaped the file and notify our mmap wrapper of the regions.
  struct dl_iterate_context 
    static int callback(struct dl_phdr_info *info, size_t /* size */, void *data) 
      auto* context = reinterpret_cast<dl_iterate_context*>(data);
      // See whether this callback corresponds to the file which we have just loaded.
      bool contains_begin = false;
      for (int i = 0; i < info->dlpi_phnum; i++) 
        if (info->dlpi_phdr[i].p_type == PT_LOAD) 
          uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr +
                                                      info->dlpi_phdr[i].p_vaddr);
          size_t memsz = info->dlpi_phdr[i].p_memsz;
          if (vaddr <= context->begin_ && context->begin_ < vaddr + memsz) 
            contains_begin = true;
            break;
          
        
      
      // Add dummy mmaps for this file.
      if (contains_begin) 
        for (int i = 0; i < info->dlpi_phnum; i++) 
          if (info->dlpi_phdr[i].p_type == PT_LOAD) 
            uint8_t* vaddr = reinterpret_cast<uint8_t*>(info->dlpi_addr +
                                                        info->dlpi_phdr[i].p_vaddr);
            size_t memsz = info->dlpi_phdr[i].p_memsz;
            MemMap* mmap = MemMap::MapDummy(info->dlpi_name, vaddr, memsz);
            context->dlopen_mmaps_->push_back(std::unique_ptr<MemMap>(mmap));
          
        
        return 1;  // Stop iteration and return 1 from dl_iterate_phdr.
      
      return 0;  // Continue iteration and return 0 from dl_iterate_phdr when finished.
    
    const uint8_t* const begin_;
    std::vector<std::unique_ptr<MemMap>>* const dlopen_mmaps_;
   context =  begin_, &dlopen_mmaps_ ;
  if (dl_iterate_phdr(dl_iterate_context::callback, &context) == 0) 
    PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
    LOG(ERROR) << "File " << elf_filename << " loaded with dlopen but can not find its mmaps.";
  

  return Setup(abs_dex_location, error_msg);
#endif  // __APPLE__

OpenElfFile函数:

OatFile* OatFile::OpenElfFile(File* file,
                              const std::string& location,
                              uint8_t* requested_base,
                              uint8_t* oat_file_begin,
                              bool writable,
                              bool executable,
                              const char* abs_dex_location,
                              std::string* error_msg) 
  std::unique_ptr<OatFile> oat_file(new OatFile(location, executable));
  bool success = oat_file->ElfFileOpen(file, requested_base, oat_file_begin, writable, executable,
                                       abs_dex_location, error_msg);
  if (!success) 
    CHECK(!error_msg->empty());
    return nullptr;
  
  return oat_file.release();

bool OatFile::ElfFileOpen(File* file, uint8_t* requested_base, uint8_t* oat_file_begin,
                          bool writable, bool executable,
                          const char* abs_dex_location,
                          std::string* error_msg) 
  // TODO: rename requested_base to oat_data_begin
  elf_file_.reset(ElfFile::Open(file, writable, /*program_header_only*/true, error_msg,
                                oat_file_begin));
  if (elf_file_ == nullptr) 
    DCHECK(!error_msg->empty());
    return false;
  
  bool loaded = elf_file_->Load(executable, error_msg);
  if (!loaded) 
    DCHECK(!error_msg->empty());
    return false;
  
  begin_ = elf_file_->FindDynamicSymbolAddress("oatdata");
  if (begin_ == nullptr) 
    *error_msg = StringPrintf("Failed to find oatdata symbol in '%s'", file->GetPath().c_str());
    return false;
  
  if (requested_base != nullptr && begin_ != requested_base) 
    PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
    *error_msg = StringPrintf("Failed to find oatdata symbol at expected address: "
                              "oatdata=%p != expected=%p. See process maps in the log.",
                              begin_, requested_base);
    return false;
  
  end_ = elf_file_->FindDynamicSymbolAddress("oatlastword");
  if (end_ == nullptr) 
    *error_msg = StringPrintf("Failed to find oatlastword symbol in '%s'", file->GetPath().c_str());
    return false;
  
  // Readjust to be non-inclusive upper bound.
  end_ += sizeof(uint32_t);
  bss_begin_ = elf_file_->FindDynamicSymbolAddress("oatbss");
  if (bss_begin_ == nullptr) 
    // No .bss section. Clear dlerror().
    bss_end_ = nullptr;
    dlerror();
   else 
    bss_end_ = elf_file_->FindDynamicSymbolAddress("oatbsslastword");
    if (bss_end_ == nullptr) 
      *error_msg = StringPrintf("Failed to find oatbasslastword symbol in '%s'",
                                file->GetPath().c_str());
      return false;
    
    // Readjust to be non-inclusive upper bound.
    bss_end_ += sizeof(uint32_t);
  

  return Setup(abs_dex_location, error_msg);

两种加载方式一个由系统的dlopen函数,一个由ElfFile这个elf loader,最终的目的都是加载oatdata,oatdata有dex file相关的信息,Setup函数会根据oatdata的dex信息,创建OatDexFile。

bool OatFile::Setup(const char* abs_dex_location, std::string* error_msg) 
  if (!GetOatHeader().IsValid()) 
    std::string cause = GetOatHeader().GetValidationErrorMessage();
    *error_msg = StringPrintf("Invalid oat header for '%s': %s", GetLocation().c_str(),
                              cause.c_str());
    return false;
  
  const uint8_t* oat = Begin();
  oat += sizeof(OatHeader);
  if (oat > End()) 
    *error_msg = StringPrintf("In oat file '%s' found truncated OatHeader", GetLocation().c_str());
    return false;
  

  oat += GetOatHeader().GetKeyValueStoreSize();
  if (oat > End()) 
    *error_msg = StringPrintf("In oat file '%s' found truncated variable-size data: "
                              "%p + %zd + %ud <= %p", GetLocation().c_str(),
                              Begin(), sizeof(OatHeader), GetOatHeader().GetKeyValueStoreSize(),
                              End());
    return false;
  

  uint32_t dex_file_count = GetOatHeader().GetDexFileCount();
  oat_dex_files_storage_.reserve(dex_file_count);
  for (size_t i = 0; i < dex_file_count; i++) 
    uint32_t dex_file_location_size = *reinterpret_cast<const uint32_t*>(oat);
    if (UNLIKELY(dex_file_location_size == 0U)) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd with empty location name",
                                GetLocation().c_str(), i);
      return false;
    
    oat += sizeof(dex_file_location_size);
    if (UNLIKELY(oat > End())) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd truncated after dex file "
                                "location size", GetLocation().c_str(), i);
      return false;
    

    const char* dex_file_location_data = reinterpret_cast<const char*>(oat);
    oat += dex_file_location_size;
    if (UNLIKELY(oat > End())) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd with truncated dex file "
                                "location", GetLocation().c_str(), i);
      return false;
    

    std::string dex_file_location = ResolveRelativeEncodedDexLocation(
        abs_dex_location,
        std::string(dex_file_location_data, dex_file_location_size));

    uint32_t dex_file_checksum = *reinterpret_cast<const uint32_t*>(oat);
    oat += sizeof(dex_file_checksum);
    if (UNLIKELY(oat > End())) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' truncated after "
                                "dex file checksum", GetLocation().c_str(), i,
                                dex_file_location.c_str());
      return false;
    

    uint32_t dex_file_offset = *reinterpret_cast<const uint32_t*>(oat);
    if (UNLIKELY(dex_file_offset == 0U)) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with zero dex "
                                "file offset", GetLocation().c_str(), i, dex_file_location.c_str());
      return false;
    
    if (UNLIKELY(dex_file_offset > Size())) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with dex file "
                                "offset %ud > %zd", GetLocation().c_str(), i,
                                dex_file_location.c_str(), dex_file_offset, Size());
      return false;
    
    oat += sizeof(dex_file_offset);
    if (UNLIKELY(oat > End())) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' truncated "
                                "after dex file offsets", GetLocation().c_str(), i,
                                dex_file_location.c_str());
      return false;
    

    const uint8_t* dex_file_pointer = Begin() + dex_file_offset;
    if (UNLIKELY(!DexFile::IsMagicValid(dex_file_pointer))) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with invalid "
                                "dex file magic '%s'", GetLocation().c_str(), i,
                                dex_file_location.c_str(), dex_file_pointer);
      return false;
    
    if (UNLIKELY(!DexFile::IsVersionValid(dex_file_pointer))) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with invalid "
                                "dex file version '%s'", GetLocation().c_str(), i,
                                dex_file_location.c_str(), dex_file_pointer);
      return false;
    
    const DexFile::Header* header = reinterpret_cast<const DexFile::Header*>(dex_file_pointer);
    const uint32_t* methods_offsets_pointer = reinterpret_cast<const uint32_t*>(oat);

    oat += (sizeof(*methods_offsets_pointer) * header->class_defs_size_);
    if (UNLIKELY(oat > End())) 
      *error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' with truncated "
                                "method offsets", GetLocation().c_str(), i,
                                dex_file_location.c_str());
      return false;
    

    std::string canonical_location = DexFile::GetDexCanonicalLocation(dex_file_location.c_str());

    // Create the OatDexFile and add it to the owning container.
    OatDexFile* oat_dex_file = new OatDexFile(this,
                                              dex_file_location,
                                              canonical_location,
                                              dex_file_checksum,
                                              dex_file_pointer,
                                              methods_offsets_pointer);
    oat_dex_files_storage_.push_back(oat_dex_file);

    // Add the location and canonical location (if different) to the oat_dex_files_ table.
    StringPiece key(oat_dex_file->GetDexFileLocation());
    oat_dex_files_.Put(key, oat_dex_file);
    if (canonical_location != dex_file_location) 
      StringPiece canonical_key(oat_dex_file->GetCanonicalDexFileLocation());
      oat_dex_files_.Put(canonical_key, oat_dex_file);
    
  
  return true;

OatHeader(oatdata是起始地址)里包含了DexFile文件的个数,每个DexFile文件的size和offset,以及oat class offsets,创建出OatDexFile的实例。而OatDexFile是OatFile的核心,既有OatClass的信息,也有DexFile的信息。

    OatDexFile* oat_dex_file = new OatDexFile(this,
                                              dex_file_location,
                                              canonical_location,
                                              dex_file_checksum,
                                              dex_file_pointer,
                                              methods_offsets_pointer);
    oat_dex_files_storage_.push_back(oat_dex_file);
  
    // Add the location and canonical location (if different) to the oat_dex_files_ table.
    StringPiece key(oat_dex_file->GetDexFileLocation());
    oat_dex_files_.Put(key, oat_dex_file);
    if (canonical_location != dex_file_location) 
      StringPiece canonical_key(oat_dex_file->GetCanonicalDexFileLocation());
      oat_dex_files_.Put(canonical_key, oat_dex_file);
    
我们用下面的图总结一下oatdata的结构:



OatHeader里有这个oat文件里包含的dex file的个数

  uint32_t dex_file_count = GetOatHeader().GetDexFileCount();
之后对这些dex file做循环,每个dex file都对应有dex_file_location_size,dex_file_data,dex_file_data由dex_file_offset确定,同时dex file的每个class也对应oat_class_offsets_pointer,指向下面的oat_class数据,这些信息一起创建出OatDexFile的实例,每个dex file对应一个OatDexFile。

OatDexFile的每个类定义,都会对应一个OatClass

OatFile::OatClass OatFile::OatDexFile::GetOatClass(uint16_t class_def_index) const 
  uint32_t oat_class_offset = GetOatClassOffset(class_def_index);

  const uint8_t* oat_class_pointer = oat_file_->Begin() + oat_class_offset;
  CHECK_LT(oat_class_pointer, oat_file_->End()) << oat_file_->GetLocation();

  const uint8_t* status_pointer = oat_class_pointer;
  CHECK_LT(status_pointer, oat_file_->End()) << oat_file_->GetLocation();
  mirror::Class::Status status =
      static_cast<mirror::Class::Status>(*reinterpret_cast<const int16_t*>(status_pointer));
  CHECK_LT(status, mirror::Class::kStatusMax);

  const uint8_t* type_pointer = status_pointer + sizeof(uint16_t);
  CHECK_LT(type_pointer, oat_file_->End()) << oat_file_->GetLocation();
  OatClassType type = static_cast<OatClassType>(*reinterpret_cast<const uint16_t*>(type_pointer));
  CHECK_LT(type, kOatClassMax);

  const uint8_t* after_type_pointer = type_pointer + sizeof(int16_t);
  CHECK_LE(after_type_pointer, oat_file_->End()) << oat_file_->GetLocation();

  uint32_t bitmap_size = 0;
  const uint8_t* bitmap_pointer = nullptr;
  const uint8_t* methods_pointer = nullptr;
  if (type != kOatClassNoneCompiled) 
    if (type == kOatClassSomeCompiled) 
      bitmap_size = static_cast<uint32_t>(*reinterpret_cast<const uint32_t*>(after_type_pointer));
      bitmap_pointer = after_type_pointer + sizeof(bitmap_size);
      CHECK_LE(bitmap_pointer, oat_file_->End()) << oat_file_->GetLocation();
      methods_pointer = bitmap_pointer + bitmap_size;
     else 
      methods_pointer = after_type_pointer;
    
    CHECK_LE(methods_pointer, oat_file_->End()) << oat_file_->GetLocation();
  
  return OatFile::OatClass(oat_file_,
                           status,
                           type,
                           bitmap_size,
                           reinterpret_cast<const uint32_t*>(bitmap_pointer),
                           reinterpret_cast<const OatMethodOffsets*>(methods_pointer));

Oat Data里OatClass的结构


不同的OatClassType,结构是不一样的,status_pointer是类的Status,type_pointer是类的OatClassType,定义见下面的枚举。不同的OatClassType,OatClass结构也不同,只有kOatClassSomeCompiled的type,才有bitmap_size,bitmap_pointer。methods_pointer是OatMethodOffsets的指针数组,根据method id,可以找到对应的OatMethod数据结构,从而执行OatMethod。

  enum Status 
    kStatusRetired = -2,  // Retired, should not be used. Use the newly cloned one instead.
    kStatusError = -1,
    kStatusNotReady = 0,
    kStatusIdx = 1,  // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_.
    kStatusLoaded = 2,  // DEX idx values resolved.
    kStatusResolving = 3,  // Just cloned from temporary class object.
    kStatusResolved = 4,  // Part of linking.
    kStatusVerifying = 5,  // In the process of being verified.
    kStatusRetryVerificationAtRuntime = 6,  // Compile time verification failed, retry at runtime.
    kStatusVerifyingAtRuntime = 7,  // Retrying verification at runtime.
    kStatusVerified = 8,  // Logically part of linking; done pre-init.
    kStatusInitializing = 9,  // Class init in progress.
    kStatusInitialized = 10,  // Ready to go.
    kStatusMax = 11,
  ;

// OatMethodOffsets are currently 5x32-bits=160-bits long, so if we can
// save even one OatMethodOffsets struct, the more complicated encoding
// using a bitmap pays for itself since few classes will have 160
// methods.
enum OatClassType 
  kOatClassAllCompiled = 0,   // OatClass is followed by an OatMethodOffsets for each method.
  kOatClassSomeCompiled = 1,  // A bitmap of which OatMethodOffsets are present follows the OatClass.
  kOatClassNoneCompiled = 2,  // All methods are interpreted so no OatMethodOffsets are necessary.
  kOatClassMax = 3,
;
至此OatFile初始化完,我们最后总结一下本篇的重点,ClassLinker在初始化的时候,如果存在OAT镜像文件boot.art & boot.oat,那么就会加载镜像文件,初始化ImageSpace。boot.art是镜像文件,只是内存结构,参考ImageHeader的定义,而oat文件内容并不再boot.art里,而在boot.oat里,加载了oat文件之后,整个ImageSpace的初始化过程才结束,初始化之后,后续就可以根据ImageSpace里的OAT信息来执行函数了。下一节我们将介绍《深入理解ART虚拟机—art运行机制分析》。


作者简介:

田力,网易彩票Android端创始人,小米视频创始人,现任roobo技术经理、视频云技术总监

欢迎关注微信公众号 磨剑石,定期推送技术心得以及源码分析等文章,谢谢


以上是关于深入理解ART虚拟机—ImageSpace的加载过程分析的主要内容,如果未能解决你的问题,请参考以下文章

深入理解ART虚拟机—ART的函数运行机制

深入理解ART虚拟机—ART的函数运行机制

读《深入理解Java虚拟机》

深入理解Java虚拟机的目录

深入理解Java虚拟机——类加载的时机

深入理解Java虚拟机——类加载的时机