本文对老罗博客http://blog.csdn.net/luoshengyang/article/details/39307813 进行学习理解,针对android6.0系统源码,连个人理解带复制粘贴,总结的ART虚拟机对OAT文件的加载解析流程。
声明:由于CSDN官方bug,文章编码全部整合为乱码,无法显示代码,所以重新整合,代码用普通字体排版。
ART运行时提供了一个OatFile类,通过调用它的静态成员函数Open可以在本进程中加载OAT文件,它的实现如下所示(art/runtime/oat_file.cc):
OatFile* OatFile::Open(conststd::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 thingsexecutable.
//TODO: Also try when not executable? The issue here could be re-mapping aswritable (as
// !executable is a signthat 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 libunwindto 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 couplereasons:
//
//On target, dlopen may fail when compiling due to selinux restrictions oninstalld.
//
//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 toOpenElfFile.
//
//
//Another independent reason is the absolute placement of boot.oat. dlopen on thehost 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 bythe
//ScopedLock, which we better not delete to avoid races. TODO: Investigate how tofix the API
//to allow removal when we know the ELF must be borked.
return ret.release();
}
参数filename和location实际上是一样的,指向要加载的OAT文件。参数requested_base是一个可选参数,用来描述要加载的OAT文件里面的oatdata段要加载在的位置。参数executable表示要加载的OAT是不是应用程序的主执行文件。
上述代码简言之就是根据ART_USE_PORTABLE_COMPILER宏选择用OpenDlopen还是OpenElfFile方法加载OAT文件。接下来分析这两个函数。
首先分析OpenDlopen函数(art/runtime/oat_file.cc):
OatFile* OatFile::OpenDlopen(conststd::string& elf_filename,
conststd::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();
}
跳转到Dlopen函数(一下代码为极度精简版,源代码请自行查阅art/runtime/oat_file.cc):
bool OatFile::Dlopen(const std::string&elf_filename, byte* requested_base) {
char* absolute_path = realpath(elf_filename.c_str(), NULL);
......
dlopen_handle_ = dlopen(absolute_path, RTLD_NOW);
......
begin_ = reinterpret_cast<byte*>(dlsym(dlopen_handle_,"oatdata"));
......
if(requested_base != NULL && begin_ != requested_base) {
......
return false;
}
end_ = reinterpret_cast<byte*>(dlsym(dlopen_handle_,"oatlastword"));
......
//Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
return Setup();
}
Dlopen首先通过动态链接器提供的dlopen函数将参数elf_filename指定的OAT文件加载到内存中来,接着同样是通过动态链接器提供的dlsym函数从加载的OAT文件获得oatdata和oatlastword的地址,分别保存在当前正在处理的OatFile对象的成员变量begin_和end_中。oatdata的地址即为OAT文件里面的oatdata段加载到内存中的开始地址,而oatlastword的地址即为OAT文件里面的oatexec加载到内存中的结束地址。符号oatlastword本身也是属于oatexec段的,它自己占用了一个地址,也就是sizeof(uint32_t)个字节,于是将前面得到的end_值加上sizeof(uint32_t),得到的才是oatexec段的结束地址。
实际上,上面得到的begin_值指向的是加载内存中的oatdata段的头部,即OAT头。这个OAT头描述了OAT文件所包含的DEX文件的信息,以及定义在这些DEX文件里面的类方法所对应的本地机器指令在内存的位置。另外,上面得到的end_是用来在解析OAT头时验证数据的正确性的。此外,如果参数requested_base的值不等于0,那么就要求oatdata段必须要加载到requested_base指定的位置去,也就是上面得到的begin_值与requested_base值相等,否则的话就会出错返回。
最后,OatFile类的成员函数Dlopen通过调用另外一个成员函数Setup来解析已经加载内存中的oatdata段,以获得ART运行时所需要的更多信息。我们分析完成OatFile类的静态成员函数OpenElfFile之后,再来看OatFile类的成员函数Setup的实现。
接着分析OpenElfFile函数的实现,就是另外一种加载OAT文件的函数(art/runtime/oat_file.cc):
OatFile* OatFile::OpenElfFile(File* file,
conststd::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();
}
跳转到ElfFileOpen函数:
bool OatFile::ElfFileOpen(File* file,uint8_t* requested_base, uint8_t* oat_file_begin,
bool writable, boolexecutable,
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));
......
boolloaded = elf_file_->Load(executable, error_msg);
......
begin_ =elf_file_->FindDynamicSymbolAddress("oatdata");
......
end_= elf_file_->FindDynamicSymbolAddress("oatlastword");
......
//Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
......
returnSetup(abs_dex_location, error_msg);}
bool OatFile::ElfFileOpen(File* file,uint8_t* requested_base, uint8_t* oat_file_begin,
bool writable, boolexecutable,
const char*abs_dex_location,