前面写了一篇博客大致描述了一下Image文件的结构,本文将接下来简单描述一下Oat文件的大致结构。
和前面一样,还是来看一下代码,代码非常复杂,为了保证大家不分心,我会尽量去除一些冗余的部分,只留下主体部分。
好了,Oat文件的读取是通过OatFile::ElfFileOpen(art\runtime\Oat_file.cc)进行的:
bool OatFile::ElfFileOpen(File* file, byte* requested_base, bool writable, bool executable) { elf_file_.reset(ElfFile::Open(file, writable, true)); if (elf_file_.get() == NULL) { if (writable) { PLOG(WARNING) << "Failed to open ELF file for " << file->GetPath(); } return false; } ......
代码逻辑很简单,先通过ElfFile::Open函数打开文件,然后设置到成员变量elf_file_中,如果为空代表加载elf文件失败,直接退出。
下面看看ElfFile::Open函数(art\runtime\Elf_file.cc):
ElfFile* ElfFile::Open(File* file, bool writable, bool program_header_only) { UniquePtr<ElfFile> elf_file(new ElfFile()); if (!elf_file->Setup(file, writable, program_header_only)) { return NULL; } return elf_file.release(); }
接下来,看看ElfFile::Setup函数的实现:
bool ElfFile::Setup(File* file, bool writable, bool program_header_only) { CHECK(file != NULL); file_ = file; writable_ = writable; program_header_only_ = program_header_only; int prot; int flags; if (writable_) { prot = PROT_READ | PROT_WRITE; flags = MAP_SHARED; } else { prot = PROT_READ; flags = MAP_PRIVATE; } int64_t file_length = file_->GetLength(); if (file_length < 0) { errno = -file_length; PLOG(WARNING) << "Failed to get length of file: " << file_->GetPath() << " fd=" << file_->Fd(); return false; } if (file_length < sizeof(llvm::ELF::Elf32_Ehdr)) { if (writable) { LOG(WARNING) << "File size of " << file_length << " bytes not large enough to contain ELF header of " << sizeof(llvm::ELF::Elf32_Ehdr) << " bytes: " << file_->GetPath(); } return false; } if (program_header_only) { // first just map ELF header to get program header size information size_t elf_header_size = sizeof(llvm::ELF::Elf32_Ehdr); if (!SetMap(MemMap::MapFile(elf_header_size, prot, flags, file_->Fd(), 0))) { return false; } // then remap to cover program header size_t program_header_size = header_->e_phoff + (header_->e_phentsize * header_->e_phnum); if (file_length < program_header_size) { LOG(WARNING) << "File size of " << file_length << " bytes not large enough to contain ELF program header of " << program_header_size << " bytes: " << file_->GetPath(); return false; } if (!SetMap(MemMap::MapFile(program_header_size, prot, flags, file_->Fd(), 0))) { LOG(WARNING) << "Failed to map ELF program headers: " << file_->GetPath(); return false; } } else { ...... } } // Either way, the program header is relative to the elf header program_headers_start_ = Begin() + GetHeader().e_phoff; if (!program_header_only) { ...... } return true; }传进来的三个参数,第一个参数file表示要打开的elf文件,第二个参数writable表示映射到内存中后是否允许写入,第三个参数program_header_only表示是否只映射elf的Program Headers,当然elf头是无论如何要映射的。这个函数比较大,为了便于分析,由于传进来的参数program_header_only是true,我删除了代码没有走到的地方。
程序首先判断是否允许写入,并且设置相应的参数,这些都是mmap要用到的。然后是一些错误判断,如文件大小不能为负数,并且文件头的大小不能大于文件整体的长度。下面就是动真格的了,先计算一下elf头的大小,这个值是固定的,然后把这个elf头映射到内存中。
接下来计算elf头包含了Program Header后的长度,因为elf头中记录了Program Header相对于文件初始位置的偏移(e_phoff),每一项所占据空间的大小(e_phentsize)和有多少项组成(e_phnum),并且Program Header肯定是接在elf头之后的,所以非常好计算。
再接下来,又重新映射了一下MemMap,把Program Header包含了进来。为什么要映射两次呢?答案很简单,因为不读elf头的话,没法知道Program Header到底有多长。
最后初始化了成员变量programs_headers_start_,很简单,就是文件起始地址加上Program Header偏移。初始化后,ElfFile::GetProgramHeader函数才可以用,后面会用到。
那到底是怎么映射的呢?从代码来看,具体是通过MemMap::MapFile(art\runtime\Mem_map.cc)来映射的,然后调用SetMap来设置自己的成员变量。首先来看MemMap::MapFile:
static MemMap* MapFile(size_t byte_count, int prot, int flags, int fd, off_t start) { return MapFileAtAddress(NULL, byte_count, prot, flags, fd, start, false); }
MemMap* MemMap::MapFileAtAddress(byte* addr, size_t byte_count, int prot, int flags, int fd, off_t start, bool reuse) { CHECK_NE(0, prot); CHECK_NE(0, flags & (MAP_SHARED | MAP_PRIVATE)); if (byte_count == 0) { return new MemMap("file", NULL, 0, NULL, 0, prot); } // Adjust 'offset' to be page-aligned as required by mmap. int page_offset = start % kPageSize; off_t page_aligned_offset = start - page_offset; // Adjust 'byte_count' to be page-aligned as we will map this anyway. size_t page_aligned_byte_count = RoundUp(byte_count + page_offset, kPageSize); // The 'addr' is modified (if specified, ie non-null) to be page aligned to the file but not // necessarily to virtual memory. mmap will page align 'addr' for us. byte* page_aligned_addr = (addr == NULL) ? NULL : (addr - page_offset); if (!reuse) { // reuse means it is okay that it overlaps an existing page mapping. // Only use this if you actually made the page reservation yourself. CheckMapRequest(page_aligned_addr, page_aligned_byte_count); } else { CHECK(addr != NULL); } byte* actual = reinterpret_cast<byte*>(mmap(page_aligned_addr, page_aligned_byte_count, prot, flags, fd, page_aligned_offset)); if (actual == MAP_FAILED) { std::string maps; ReadFileToString("/proc/self/maps", &maps); PLOG(ERROR) << "mmap(" << reinterpret_cast<void*>(page_aligned_addr) << ", " << page_aligned_byte_count << ", " << prot << ", " << flags << ", " << fd << ", " << page_aligned_offset << ") failed\n" << maps; return NULL; } return new MemMap("file", actual + page_offset, byte_count, actual, page_aligned_byte_count, prot); }
好,接下来回过来看ElfFile::SetMap:
bool ElfFile::SetMap(MemMap* map) { if (map == NULL) { // MemMap::Open should have already logged return false; } map_.reset(map); CHECK(map_.get() != NULL) << file_->GetPath(); CHECK(map_->Begin() != NULL) << file_->GetPath(); header_ = reinterpret_cast<llvm::ELF::Elf32_Ehdr*>(map_->Begin()); if ((llvm::ELF::ElfMagic[0] != header_->e_ident[llvm::ELF::EI_MAG0]) || (llvm::ELF::ElfMagic[1] != header_->e_ident[llvm::ELF::EI_MAG1]) || (llvm::ELF::ElfMagic[2] != header_->e_ident[llvm::ELF::EI_MAG2]) || (llvm::ELF::ElfMagic[3] != header_->e_ident[llvm::ELF::EI_MAG3])) { LOG(WARNING) << "Failed to find ELF magic in " << file_->GetPath() << ": " << std::hex << static_cast<uint8_t>(header_->e_ident[llvm::ELF::EI_MAG0]) << static_cast<uint8_t>(header_->e_ident[llvm::ELF::EI_MAG1]) << static_cast<uint8_t>(header_->e_ident[llvm::ELF::EI_MAG2]) << static_cast<uint8_t>(header_->e_ident[llvm::ELF::EI_MAG3]); return false; } ...... return true; }非常简单,无非就是设置自己的成员变量map_到上面新创建的MemMap,将成员变量header_指到加载进来的elf头的位置,并检查最初的四个字节是不是符合elf的magic code。所以,必须要SetMap之后,ElfFile的很多函数才可以用,否则header_指向的值是不对的。
而这里的magic code就是标准的elf文件标识,即:
llvm::ELF::ElfMagic[0] = 0x7f; llvm::ELF::ElfMagic[1] = 'E'; llvm::ELF::ElfMagic[2] = 'L'; llvm::ELF::ElfMagic[3] = 'F';
bool loaded = elf_file_->Load(executable); if (!loaded) { LOG(WARNING) << "Failed to load ELF file " << file->GetPath(); return false; }
bool ElfFile::Load(bool executable) { CHECK(program_header_only_) << file_->GetPath(); for (llvm::ELF::Elf32_Word i = 0; i < GetProgramHeaderNum(); i++) { llvm::ELF::Elf32_Phdr& program_header = GetProgramHeader(i); // Record .dynamic header information for later use if (program_header.p_type == llvm::ELF::PT_DYNAMIC) { dynamic_program_header_ = &program_header; continue; } // Not something to load, move on. if (program_header.p_type != llvm::ELF::PT_LOAD) { continue; } int64_t file_length = file_->GetLength(); if (program_header.p_vaddr == 0) { std::string reservation_name("ElfFile reservation for "); reservation_name += file_->GetPath(); UniquePtr<MemMap> reserve(MemMap::MapAnonymous(reservation_name.c_str(), NULL, GetLoadedSize(), PROT_NONE)); CHECK(reserve.get() != NULL) << file_->GetPath(); base_address_ = reserve->Begin(); segments_.push_back(reserve.release()); } // empty segment, nothing to map if (program_header.p_memsz == 0) { continue; } ......代码很长,先只列出前面的一部分。可以看出程序先获得Program Header内到底有多少项,然后逐项遍历。为了便于理解,我挑了一个例子,就是系统的Boot Oat(system@[email protected])文件,用readelf可以得到其Program Header如下:
可以看到其一共包含5项,第一项表示的是Program Header本身在要映射到内存中的大小和位置。第二项到第四项是三个要加载进内存的段,为什么要分三个?因为他们的属性不同。最后一个指向的是动态节区,其有很多项组成。其它的项先不管,有一个特别重要的项是SYMTAB,即符号表节,这个节中记录了几个符号已经其对应的属性和值。对于内含oat的elf文件来说,一共有三个非常重要的符号分别是oatdata,oatexec和oatlastword,具体用处后面再解释。
细心的读者有没有发现第四项和第五项的内容除了类型不同外,其它都完全一样。
好了,还是回来看代码。程序会遍历Program Header中的所有项。对各项,首先,看该该项是不是PT_DYNAMIC类型的(第五项),如果是就用来初始化dynamic_program_header_成员变量。奇怪的是直接就赋值了,没有把其指定的段加载进内存中?答案其实前面提到过了,在处理第四项的时候已经将其加载进内存中的,而第五项和第四项所有属性都一样。
接下来,判断该项的类型是不是PT_LOAD,如果不是,直接忽略,即非PT_LOAD类型的段不需要加载。
再下来,判断了一下该项指定的虚拟地址是否是0。如果为0的话,就调用MemMap::MapAnonymous来加载。看看前面的例子,咦,怎么没有虚拟地址为0的段?我们再随便写 一个HelloWorld程序,装上手机,拿出转换过后的oat文件看看,通过readelf,其Program Header如下:
看到第二项了吧,真的有一个PT_LOAD段的虚拟地址指定的是0。顺便说一句,虚拟地址为0的项一定是Program Header各个PT_LOAD类型项的第一项。那么有虚拟地址为0的段和没有虚拟地址为0的段,处理起来有什么区别呢?
首先,我们看看有的情况,接着看MemMap::MapAnonymous:
MemMap* MemMap::MapAnonymous(const char* name, byte* addr, size_t byte_count, int prot) { if (byte_count == 0) { return new MemMap(name, NULL, 0, NULL, 0, prot); } size_t page_aligned_byte_count = RoundUp(byte_count, kPageSize); CheckMapRequest(addr, page_aligned_byte_count); #ifdef USE_ASHMEM // android_os_Debug.cpp read_mapinfo assumes all ashmem regions associated with the VM are // prefixed "dalvik-". std::string debug_friendly_name("dalvik-"); debug_friendly_name += name; ScopedFd fd(ashmem_create_region(debug_friendly_name.c_str(), page_aligned_byte_count)); int flags = MAP_PRIVATE; if (fd.get() == -1) { PLOG(ERROR) << "ashmem_create_region failed (" << name << ")"; return NULL; } #else ScopedFd fd(-1); int flags = MAP_PRIVATE | MAP_ANONYMOUS; #endif byte* actual = reinterpret_cast<byte*>(mmap(addr, page_aligned_byte_count, prot, flags, fd.get(), 0)); if (actual == MAP_FAILED) { std::string maps; ReadFileToString("/proc/self/maps", &maps); PLOG(ERROR) << "mmap(" << reinterpret_cast<void*>(addr) << ", " << page_aligned_byte_count << ", " << prot << ", " << flags << ", " << fd.get() << ", 0) failed for " << name << "\n" << maps; return NULL; } return new MemMap(name, actual, byte_count, actual, page_aligned_byte_count, prot); }
那到底要分配多大呢,来看ElfFile::GetLoadedSize:
size_t ElfFile::GetLoadedSize() { llvm::ELF::Elf32_Addr min_vaddr = 0xFFFFFFFFu; llvm::ELF::Elf32_Addr max_vaddr = 0x00000000u; for (llvm::ELF::Elf32_Word i = 0; i < GetProgramHeaderNum(); i++) { llvm::ELF::Elf32_Phdr& program_header = GetProgramHeader(i); if (program_header.p_type != llvm::ELF::PT_LOAD) { continue; } llvm::ELF::Elf32_Addr begin_vaddr = program_header.p_vaddr; if (begin_vaddr < min_vaddr) { min_vaddr = begin_vaddr; } llvm::ELF::Elf32_Addr end_vaddr = program_header.p_vaddr + program_header.p_memsz; if (end_vaddr > max_vaddr) { max_vaddr = end_vaddr; } } min_vaddr = RoundDown(min_vaddr, kPageSize); max_vaddr = RoundUp(max_vaddr, kPageSize); CHECK_LT(min_vaddr, max_vaddr) << file_->GetPath(); size_t loaded_size = max_vaddr - min_vaddr; return loaded_size; }该程序遍历所有Program Header项,略去所有PT_LOAD段,计算剩下的所有项中起始地址最小的,还有结束地址加上该段长度后最大的地址,然后按照页大小调整一下,最后用得到的最大值减去最小值。其实很简单了,就是该elf文件的全部大小。
所以如果有虚拟地址为0的段话,就是先按照文件的大小随便在内存中映射一段内存,不指定起始地址,然后设置变量base_address_为该段的起始地址。那么如果没有虚拟地址为0的段,会怎么样呢?这是base_address_就是0。好,接着看剩下的部分:
byte* p_vaddr = base_address_ + program_header.p_vaddr; int prot = 0; if (executable && ((program_header.p_flags & llvm::ELF::PF_X) != 0)) { prot |= PROT_EXEC; } if ((program_header.p_flags & llvm::ELF::PF_W) != 0) { prot |= PROT_WRITE; } if ((program_header.p_flags & llvm::ELF::PF_R) != 0) { prot |= PROT_READ; } int flags = MAP_FIXED; if (writable_) { prot |= PROT_WRITE; flags |= MAP_SHARED; } else { flags |= MAP_PRIVATE; } if (file_length < (program_header.p_offset + program_header.p_memsz)) { LOG(WARNING) << "File size of " << file_length << " bytes not large enough to contain ELF segment " << i << " of " << (program_header.p_offset + program_header.p_memsz) << " bytes: " << file_->GetPath(); return false; } UniquePtr<MemMap> segment(MemMap::MapFileAtAddress(p_vaddr, program_header.p_memsz, prot, flags, file_->Fd(), program_header.p_offset, true)); CHECK(segment.get() != NULL) << file_->GetPath(); CHECK_EQ(segment->Begin(), p_vaddr) << file_->GetPath(); segments_.push_back(segment.release()); }这下就非常简单了,很容易懂,MemMap::MapFileAtAddress前面分析过。
好,讲了那么多,大致总结一下。
1)如果elf文件中包含了虚拟地址为0的PT_LOAD段,则证明它不是Boot Oat,而是一个普通的应用程序的oat,这时候该elf文件无所谓被映射到内存中的任何地方,其虚拟地址(p_vaddr)中记录的是该段相对于文件头的偏移;
2)如果elf文件中没有包含任何虚拟地址为0的PT_LOAD段,则证明它是一个Boot Oat,必须被加载到一个指定位置(实际是紧接在Image之后),其虚拟地址(p_vaddr)中记录的就是实际要被加载的绝对地址。接下来,ElfFile::Load函数会初始化自己的内部变量,指向各个特殊节的位置:
dynamic_section_start_ = reinterpret_cast<llvm::ELF::Elf32_Dyn*>(base_address_ + GetDynamicProgramHeader().p_vaddr); for (llvm::ELF::Elf32_Word i = 0; i < GetDynamicNum(); i++) { llvm::ELF::Elf32_Dyn& elf_dyn = GetDynamic(i); byte* d_ptr = base_address_ + elf_dyn.d_un.d_ptr; switch (elf_dyn.d_tag) { case llvm::ELF::DT_HASH: { hash_section_start_ = reinterpret_cast<llvm::ELF::Elf32_Word*>(d_ptr); break; } case llvm::ELF::DT_STRTAB: { dynstr_section_start_ = reinterpret_cast<char*>(d_ptr); break; } case llvm::ELF::DT_SYMTAB: { dynsym_section_start_ = reinterpret_cast<llvm::ELF::Elf32_Sym*>(d_ptr); break; } case llvm::ELF::DT_NULL: { CHECK_EQ(GetDynamicNum(), i+1); break; } } } return true; }好了,到现在位置,elf文件已经被完全加载到内存中了,并且所有环境变量都已经初始化完成。那下面要干什么呢?我们接着往下看OatFile::ElfFileOpen:
begin_ = elf_file_->FindDynamicSymbolAddress("oatdata"); if (begin_ == NULL) { LOG(WARNING) << "Failed to find oatdata symbol in " << file->GetPath(); return false; } if (requested_base != NULL && begin_ != requested_base) { std::string maps; ReadFileToString("/proc/self/maps", &maps); LOG(WARNING) << "Failed to find oatdata symbol at expected address: oatdata=" << reinterpret_cast<const void*>(begin_) << " != expected=" << reinterpret_cast<const void*>(requested_base) << " /proc/self/maps:\n" << maps; return false; } end_ = elf_file_->FindDynamicSymbolAddress("oatlastword"); if (end_ == NULL) { LOG(WARNING) << "Failed to find oatlastword symbol in " << file->GetPath(); return false; } // Readjust to be non-inclusive upper bound. end_ += sizeof(uint32_t); return Setup(); }看到oatdata和oatlastword了吧,这段代码就是初始化了begin_和end_两个内部变量,分别是oatdata和oatlastword指向的地址。对于前面的Boot Oat文件来说,其值分别为0x60a9d000和0x646161a4。后面还把end_的位置加了4个字节,这是因为oatlastword长度被指定为4个字节,位置又在文件的末尾,必须得加上才真的是指向文件的末尾。那begin_和end_到底被指向了哪里呢?可以通过函数最后调用的ElfFile::Setup函数找出答案。
写了太多了,在下篇我们再接着分析。