ART Image Format

Table of Contents

1. ART Image Format

1.1. boot.art 格式

boot.art 文件主要由四部分构成:

  1. ImageHeader
  2. image_space
  3. live_bitmap
  4. image_roots

1.1.1. ImageHeader

ImageHeader 位于 boot.art 的开头, 主要用来描述 image_space, live_bitmap, image_roots 等需要映射到内存中的位置和大小等.

  1. image_begin_

    指 image_space 需要映射到这个绝对地址. 这个地址在 image_space.cc 的 GenerateImage 时指定 (ChooseRelocationOffsetDelta), 其值为 ART_BASE_ADDRESS + 一个随机的 delta 值, ART_BASE_ADDRESS 为 0x70000000

  2. image_size_

    指 image_space 的大小, 使用 boot.art 启动的虚拟机时, runtime->Heap()->ImageSpace::init 时, 会将 boot.art 中从 0 到 image_size_ 的内容映射到进程的 image_begin_ 处.

  3. image_bitmap_offset_ 和 image_bitmap_size_

    指 boot.art 中 live_bitmap 的内容的偏移量和大小. live_bitmap 在 ImageSpace::init 时也会被映射进来, 但它们不需要被映射到固定的位置, 所以关于 live_bitmap 并没有类似于 image_begin 的数据.

    观察一个进程的 maps:

    ~@sunway-work> adb shell cat /proc/19947/maps | grep -i "boot"
    6f745000-7025d000 rw-p 00000000 b3:15 39845      /data/dalvik-cache/arm/system@[email protected]
    7025d000-71ce8000 r--p 00000000 b3:15 39844      /data/dalvik-cache/arm/system@[email protected]
    71ce8000-7319b000 r-xp 01a8b000 b3:15 39844      /data/dalvik-cache/arm/system@[email protected]
    7319b000-7319c000 rw-p 02f3e000 b3:15 39844      /data/dalvik-cache/arm/system@[email protected]
    b4cc9000-b4cf6000 r--p 00b18000 b3:15 39845      /data/dalvik-cache/arm/system@[email protected]
    b5200000-b5201000 r--p 00000000 b3:15 39844      /data/dalvik-cache/arm/system@[email protected]
    

    第一行对应于 boot.art 中 image_space 部分的映射, 可以看到 image_begin_ 应该是 0x6f745000, 不考虑对齐的话, image_size_ 应该是 0x7025d000-0x6f745000 第五行对应于 boot.art 中的 live_bitmap 部分, 映射的起始地址 0x00b18000 应该对应于 image_bitmap_offset_

    另外, 从 maps 中可能还能推测出一个信息: image_space 被加载后是可以修改 (r–p), 但它的 live_bitmap 是不可写的 (r–p), 这可能意味着 image_space 上无法分配和释放对象, 但`某些 `已存在的对象可以被修改? (例如 DexCache?)

  4. oat_data_begin_, oat_data_end_

    指 boot.oat 的 oatdata 需要被映射到的区域. 这个区域实际上是紧随着 image_space 映射的区域之后, 例如上面 example 提到的第二行.

    image_space 中会有许多 ArtMethod 对象, ArtMetho 会有许多函数指针, 例如 entry_point_from_quick_compiled_code_, 这些指针在进行 dex2oat 时本来是指向 boot.oat 中 oatexec 区域的 native code 的, 当 image_writer 最终需要保存 image_space 时, 这些 ArtMethod 指向的 boot.oat 的指针如何保存? 答案就是将 boot.oat 像 image_space 一样映射到 oat_data_begin_ 处, 同时修改 ArtMethod 中的函数指针使其指向 oat_data_begin_ 开始的区域 (ImageWriter::FixupMethod)

  5. image_roots_

    image_roots_ 是指向 image_space 的一些对象的指针.

关于 boot.art 及 boot.oat 的 copy, fixup, patch 等信息, 可以参考 dex2oat.cc 中的这一段 comment:

Notes on the interleaving of creating the image and oat file to
ensure the references between the two are correct.

Currently we have a memory layout that looks something like this:

+--------------+
| image        |
+--------------+
| boot oat     |
+--------------+
| alloc spaces |
+--------------+

There are several constraints on the loading of the image and boot.oat.

1. The image is expected to be loaded at an absolute address and
contains Objects with absolute pointers within the image.

2. There are absolute pointers from Methods in the image to their
code in the oat.

3. There are absolute pointers from the code in the oat to Methods
in the image.

4. There are absolute pointers from code in the oat to other code
in the oat.

To get this all correct, we go through several steps.

1. We have already created that oat file above with
CreateOatFile. Originally this was just our own proprietary file
but now it is contained within an ELF dynamic object (aka an .so
file). The Compiler returned by CreateOatFile provides
PatchInformation for references to oat code and Methods that need
to be update once we know where the oat file will be located
after the image.

2. We create the image file. It needs to know where the oat file
will be loaded after itself. Originally when oat file was simply
memory mapped so we could predict where its contents were based
on the file size. Now that it is an ELF file, we need to inspect
the ELF file to understand the in memory segment layout including
where the oat header is located within. ElfPatcher's Patch method
uses the PatchInformation from the Compiler to touch up absolute
references in the oat file.

3. We fixup the ELF program headers so that dlopen will try to
load the .so at the desired location at runtime by offsetting the
Elf32_Phdr.p_vaddr values by the desired base address.

1.1.2. image_space

boot.art 中 image_space 保存着 dex2oat 时 java 堆中所有的和 image_classes 相关的 class 和 object.

所谓的 image_classes, 是指 framework 下 preloaded-classes 中列出来的 class.

在 CompilerDriver 的 PreCompile 阶段, CompilerDriver 会通过 LoadImageClasses 先把 preloaded-classes 中列出的 class 都加载进来, 然后通过 class_linker->VisitClasses 再扫描一次当前已经加载的所有 class, 把它们都加到 image_classes 中. 这样做是因为加载 preloaded-classes 时可能会导致 preloaded-classes 的基类, 接口都其他相关的类也被加载进来, 这些后来加载的类也会被算到 image_classes.

CompilerDriver 然后再加载 bootclasspath 中所有的 dex 中的类, 对某些类还会执行初始化的动作.

最后, CompilerDriver 通过 heap->VisitObjects(FindClinitImageClassesCallback) 扫描一次 heap, 对于那些 clinit 阶段生成的对象的 class, 也会相应的加到 image_classes 中.

到此, image_classes 会生成完毕了.

在 ImageWriter::Write 阶段, 会先通过 PruneNonImageClasses 把 ClassLinker 的 class_table_ 中那些非 image_classes 的类都清除, 然后执行一个 GC, 导致 heap 上只会保留 image_classes 相关的类和对象.

通过 AllocMemory 分配一个和当前 heap 大小一致的 tmp_image_space 空间, 以及一个 live_bitmap 空间.

通过 CalculateNewObjectOffsets, CopyAndFixupObjects 重置各个对象的引用关系并移到到 tmp_image_space. 所谓重置, 参考 getImageAddress 和 GetOatAddress, 就是要将对象中引用的 原 heap 中的 X 修改为引用 image_begin_ 中的 X, 或将 ArtMethod 引用的代码指向新的 oat_data_begin_ 区域, 这个过程和 dalvik 中的 copying GC 的移动过程基本是一样的, 例如, 都使用了 forward address.

最终, 将 tmp_image_space 和 live_bitmap 加上相应的 ImageHeader 写到 boot.art 就可以了.

1.1.3. live_bitmap

live_bitmap 与 image_space 对应, 其大小为 image_space 的 1/64 (不考虑对齐的话)

1.1.4. image_roots_

主要保存着一个 DexCache 的数组和一个 ClassRoot 数组, DexCache 相应于 image_space 中所有对象的 cache. ClassRoot 中保存着一些基本的 class, 例如 java.lang.Class, java.lang.Object, java.lang.String 等.

当 runtime 从 image 启动时, ClassLinker::InitFromImage 会从 ImageHeader 中读取 ClassRoot, 不需要再重新加载这些基本类.

若 ClassLinker::InitWithoutImage, 则需要通过 AllocClass 自己加载 java.lang.Class 这些基本类了. ClassRoot 和 DexCache 的保存是必要的,因为 image_space 中的对象需要和 DexCache 及 ClassRoot 中的数据对应.

1.2. 生成 boot.art

zygote 启动时会使用 -Ximage 参数表明 java 进程需要使用 boot.art 来加载 image_space, 若检测到不存在 boot.art, 则会调用 dex2oat 来生成. 调用 dex2oat 时会通过 –image 参数表明 dex2oat 需要生成 boot.art.

1.2.1. Runtime::Create

JNI_CreateJavaVM
  Runtime::Create
    Runtime::Init
      Heap::Heap
        if (!image_file_name.empty()):
          ImageSpace::Create
            bool found_image = FindImageFilename
            if (found_image):
              return ImageSpace::Init
            else:
              GenerateImage
              return ImageSpace::Init

1.2.2. GenerateImage

ImageSpace::GenerateImage
  // 设置 "dex2oat" 可执行程序的路径
  std::string dex2oat(Runtime::Current()->GetCompilerExecutable());
  arg_vector.push_back(dex2oat);

  // 设置 "--image" 参数, 使 dex2oat 知道要生成 boot.art
  std::string image_option_string("--image=");
  image_option_string += image_filename;
  arg_vector.push_back(image_option_string);

  // 指定要生成的 boot.art 的 image_begin_ 参数
  arg_vector.push_back(StringPrintf("--base=0x%x", ART_BASE_ADDRESS + base_offset));

  // Exec 会 fork 子进程执行 arg_vector, 并等待子进程返回
  return Exec(arg_vector, error_msg);

1.2.3. dex2oat

dex2oat:
  Dex2Oat::Create(&p_dex2oat,runtime_options,*compiler_options,..)
    // 启动 runtime, runtime_options 中没有 -Ximage 参数, 所以
    // runtime 不会初始化 image_space
    dex2oat->CreateRuntime(runtime_options, instruction_set)
  // 从 preloaded-classes 中读取 image_classes_  
  dex2oat->ReadImageClassesFromZip(image_classes_zip_filename,..)
  // 开始编译并生成 boot.oat
  dex2oat->CreateOatFile(boot_image_option,..)
    driver = new CompilerDriver(compiler_options_,...)
    driver->CompileAll(class_loader, dex_files, &timings);
      CompilerDriver::PreCompile(class_loader, dex_files,..)
      CompilerDriver::Compile(class_loader, dex_files,...)
    OatWriter oat_writer(dex_files, ...)
    driver->WriteElf(android_root, is_host, dex_files, &oat_writer, oat_file)
  dex2oat->CreateImageFile
    ImageWriter image_writer(compiler);
    image_writer.Write(image_filename, image_base, oat_filename, oat_location)
    ElfFixup::Fixup(oat_file.get(), oat_data_begin)

1.2.4. ImageWriter::Write

ImageWriter::Write
  // 各种 bridge, trampoline 函数存在到 boot.oat 中,
  // 而生成 boot.art 中的 ArtMethod 需要引用它们, 因此这些
  // 函数需要经过后面的 GetOatAddress 转换
  interpreter_to_interpreter_bridge_offset_ =
      oat_file_->GetOatHeader().GetInterpreterToInterpreterBridgeOffset();
  // 从 ClassLinker 的 class_table_ 中除掉非 image_classes_ 的类, 所谓
  // image_classes_, 是指和 preloaded-classes 相关的那些类
  PruneNonImageClasses();
  gc::Heap* heap = Runtime::Current()->GetHeap();
  // 确保 heap 中只存在和 image_classes_ 相关的类和对象
  heap->CollectGarbage(false);
  AllocMemory()
    // 根据当前 heap 的大小分配一块临时内存 image_, 最终这块内存和
    // 后面分配的 image_bitmap_ 会一起被写入到 boot.art
    // 要注意的是, image_.Begin() 和 image_begin_ 没有关系: image_ 内存的地址不是固定的. 
    size_t length = RoundUp(Runtime::Current()->GetHeap()->GetTotalMemory(), kPageSize);
    image_.reset(MemMap::MapAnonymous("image writer image", NULL, length, PROT_READ | PROT_WRITE,
                                    true, &error_msg));
    // Create the image bitmap.
    // image_bitmap_ 这块内存的大小为 length/64
    image_bitmap_.reset(gc::accounting::ContinuousSpaceBitmap::Create("image bitmap", image_->Begin(),
                                                                    length));

  // 遍历 heap 上所有对象, 分配一个相对于 image_.Begin() 的 offset, 保存在 LockWord 中,
  // 更新 image_bitmap_, 并生成 ImageHeader
  CalculateNewObjectOffsets(oat_loaded_size, oat_data_offset);
    // 对某一个对象, 执行以下的 callback:
    ImageWriter::AssignImageOffset
      SetImageOffset(object, image_end_);
        mirror::Object* obj = reinterpret_cast<mirror::Object*>(image_->Begin() + offset);
        image_bitmap_->Set(obj);
        object->SetLockWord(LockWord::FromForwardingAddress(offset), false);
        image_end_ += RoundUp(object->SizeOf(), 8);  // 64-bit alignment

  // 复制所有对象对 AllocMemory 分配的临时内存, 使用上一步计算的 offset      
  CopyAndFixupObjects();
    // 对某一个对象, 执行以下 callback:
    CopyAndFixupObjectsCallback
      // offset 是上一步分配的 offset
      size_t offset = image_writer->GetImageOffset(obj);
      // image_ 是 AllocMemory 分配的临时内存
      byte* dst = image_writer->image_->Begin() + offset;
      const byte* src = reinterpret_cast<const byte*>(obj);
      size_t n = obj->SizeOf();
      memcpy(dst, src, n);
    // 所有对象被 copy 到 image_ 后, 对象中引用的地址还没有 fixup:
    // 还是原始 heap 中的地址, 需要通过 FixupObject 将对象中各 field 的引用
    // 修改为相对于 image_begin_ 的引用
    image_writer->FixupObject(obj, copy);
      // fixup 的过程主要是通过如下的 FixupVisitor 完成:
      Object* ref = obj->GetFieldObject(offset);
      // getImageAddress(ref) = (image_begin_ + GetImageOffset(ref)) !
      copy_->SetFieldObjectWithoutWriteBarrier(offset, image_writer_->GetImageAddress(ref));

      // ArtMethod 引用了 oat 中的函数, 也需要根据 oat_file_begin_ 进行 fixup
      if (orig->IsArtMethod<kVerifyNone>()):
        FixupMethod(orig->AsArtMethod<kVerifyNone>(), down_cast<ArtMethod*>(copy));

  // 不清楚 ART 在生成 boot.art 最后阶段的 patch oat 或 patch elf 具体是做什么的,
  // 但从代码片断上看, 和 arm literal 有关, 而且 arm literal 做为数据在 native code
  // 时都是通过绝对地址引用的, 当 oat_data 被加载到 oat_data_begin_ 后, literal 地址
  // 变化导致了相应的代码中的地址都需要改变
  PatchOatCodeAndMethods(oat_file.get());

  image_file(OS::CreateEmptyFile(image_filename.c_str()));
  image_file->WriteFully(image_->Begin(), image_end_)

1.2.5. ElfFixup::Fixup

ImageWriter::Writer 完成以后, boot.oat 文件会通过 ElfFixup::Fixup, 把其中的各种 section, segment, symbol 等的地址都从以 0 为基址的相对地址移动到绝对地址上 (参照 oat_data_begin_). 后面 ImageSpace::OpenOatFile 时会使用这些绝对地址.

例如:

> oatdump --image=/system/framework/boot.art
...
OAT DATA BEGIN:0x7025e000
...
> readelf -a system@[email protected]
...
  [ 4] .rodata           PROGBITS        7025e000 001000 1a8a000 00   A  0   0 4096
...

1.3. 使用 boot.art

runtime 启动时会使用 -Ximage 表明需要加载 boot.art, 若 boot.art 确实存在, 则会加载 boot.art. 另外, dex2oat 为其它 app 生成 oat (而不是生成 boot.oat) 时, 会通过–boot-image 来表示它需要使用 boot.art, 这时它启动的 runtime 也会有 -Ximage 参数.

当 runtime 找到 boot.art 后, 通过 ImageSpace::Init 加载 boot.art

1.3.1. Runtime::Create

JNI_CreateJavaVM
  Runtime::Create
    Runtime::Init
      Heap::Heap
        ImageSpace::Create
          ImageSpace::Init
        AddSpace(image_space);
        // 这一句表明了 zygote 使用 image_space 时的内存布局:
        // +--------------+
        // | image        |
        // +--------------+
        // | boot oat     |
        // +--------------+
        // | alloc spaces |
        // +--------------+
        requested_alloc_space_begin = AlignUp(oat_file_end_addr, kPageSize);
      // Heap::Heap() ends here
      // 
      if (GetHeap()->HasImageSpace()):
        class_linker_->InitFromImage();
          // boo.oat 已经被加载到合适的位置 (oat_file_begin_), 设置 cl 的一些函数指针
          quick_resolution_trampoline_ = oat_file.GetOatHeader().GetQuickResolutionTrampoline();
          ...
          // 从 image_header 的 image_roots 中获得 DexCache 对象的地址, 最终加入到 class path 和 dex_caches_ 中
          Object* dex_caches_object = space->GetImageHeader().GetImageRoot(ImageHeader::kDexCaches);
          ...
          // 从 image_header 的 image_roots 中获得 class_roots, 并用它初始化 runtime 的 class_roots_
          class_roots(hs.NewHandle(space->GetImageHeader().GetImageRoot(ImageHeader::kClassRoots)->AsObjectArray<mirror::Class>()));
          class_roots_ = GcRoot<mirror::ObjectArray<mirror::Class>>(class_roots.Get());
          Class::SetClassClass(class_roots->Get(kJavaLangClass));
          Reference::SetClass(GetClassRoot(kJavaLangRefReference));
          ...

1.3.2. ImageSpace::Init

ImageSpace::Init
  // 从文件头读取 ImageHeader
  file->ReadFully(&image_header, sizeof(image_header));
  // 将 file->Fd() 对应的文件的 0 到 image_header.image_size_ 的内容
  // 映射到 image_header.image_begin_ 为首地址的内存.
  // 所以 map 就是 image_space 的内容
  map(MemMap::MapFileAtAddress(image_header.GetImageBegin(),
                              image_header.GetImageSize(),
                              PROT_READ | PROT_WRITE,
                              MAP_PRIVATE,
                              file->Fd(),
                              0,...))
  // 从文件的 image_header.image_bitmap_offset_ 位置开始, 映射
  // image_header.image_bitmap_size_ 大小的内容到任意地址 (nullptr)
  image_map(MemMap::MapFileAtAddress(nullptr, image_header.GetImageBitmapSize(),
                               PROT_READ, MAP_PRIVATE,
                               file->Fd(),
                               image_header.GetBitmapOffset(),
                               ...
                               ))
  // 使用 image_map 创建 image_space 的 bitmap 数据结构
  bitmap(ContinuousSpaceBitmap::CreateFromMemMap(bitmap_name, image_map.release(),
                               reinterpret_cast<byte*>(map->Begin()),
                               map->Size()));

  // 使用 map 创建 image_space 的数据结构
  space(new ImageSpace(image_filename, image_location,
                               map.release(), bitmap.release()));

  // 将 boot.oat 加载到 image_space 之后, 即 oat_file_begin_ 处
  // 最终是通过 ElfFile.Load 把 boot.oat 加载到 oat_file_begin_ 处,
  // ElfFile.Load 可以把 boot.oat 的 oatdata 加载到特定的位置, 是因为 ElfFixup 时已经
  // 将 oat_data_begin_ 信息写入到了 elf 文件中
  space->oat_file_.reset(space->OpenOatFile(image_filename, error_msg));
    // image_header.GetOatDataBegin 即 oat_data_begin_, 这个值与 boot.oat 中的相应的值是一致的
    oat_file = OatFile::Open(oat_filename, oat_filename, image_header.GetOatDataBegin(),...)
      OpenElfFile(file.get(), location, requested_base, false, executable, error_msg)
        ElfFileOpen(file, requested_base, writable, executable, error_msg);
          // elf_file_ 通过 elf 文件自身的信息就可以把 elf 加载到 oat_file_begin_ 处
          elf_file_->Load(executable, error_msg);

  // 下面从 image_space 中初始化一些 runtime 需要的数据
  Object* resolution_method = image_header.GetImageRoot(ImageHeader::kResolutionMethod);
  runtime->SetResolutionMethod(down_cast<mirror::ArtMethod*>(resolution_method));
  ...

1.3.3. ImageSpace 如何被使用

首先, 基本的 class (class_roots 中的 class) 在 Runtime.InitFromImage 阶段就被指向 ImageSpace.

其次, FindClass 时会使用到 ImageSpace 的 DexCache, 并找到 ImageSpace 中的 class:

ClassLinker::FindClass
  Class* klass = LookupClass(descriptor, class_loader.Get());
    // 从 cl 的 class_table_ 中查找
    Class* result = LookupClassFromTableLocked(descriptor, class_loader, hash);
    if (!result):
      // 从 ImageSpace 中查找
      Class* result = LookupClassFromImage(descriptor);
        ObjectArray<mirror::DexCache>* dex_caches = GetImageDexCaches();
        for each dex_cache:
          DexFile* dex_file = dex_cache->GetDexFile();
          DexFile::StringId* string_id = dex_file->FindStringId(descriptor);
          DexFile::TypeId* type_id = dex_file->FindTypeId(dex_file->GetIndexForStringId(*string_id));
          klass = dex_cache->GetResolvedType(type_idx);
    // 其它方式来 FindClass      

Author: [email protected]
Date: 2017-04-01 Sat 00:00
Last updated: 2023-11-20 Mon 16:08

知识共享许可协议