简介
在Android7.0之前,所有bootclasspath指定的库会被同一编译成为boot.oat和boot.art两个文件。其中boot.oat包含了编译后的机器码指令,而boot.art文件,则是一个类对象映像。boot.art内包含了所有framework/base/preloaded-classes文件列出的所有类。这些类会被一次性的载入到内存中,并可以被直接使用。
在Android7.0及以后,boot.art和boot.oat被分成以boot-{packagename}.oat/art方式的多个库。当有一个库被更新,不至于整个boot.art/boot.oat被更新。
一般情况下,手机的系统预先在/system/framework/arm64(或arm)/下放置好boot*.art和boot*.oat。
MemMap数据结构
art内部使用MemMap来管理一段连续的内存,MemMap内置的多个函数可以用来进行映射,boot*.art和boot*.oat的加载大量使用到了MemMap,这里有必要说明一下。
MemMap::RemapAtEnd:用于对已经存在的MemMap尾部的一段内存进行重映射,得到一块新的MemMap。
MemMap::MapAnonymous:映射到一块匿名内存(映射区不与任何文件关联),得到一块MemMap。
MemMap::MapFileAtAddress:映射文件到一块内存,得到一块MemMap并返回。
MemMap::MapFile:没有指定期望加载地址和预留内存版本的MapFileAtAddress,分配所在的基址由系统mmap调用决定。
MemMap::MapDummy:创建一个Dummy MemMep,这个MemMap并没有真正映射到内存去,只提供数据用来跟踪一块内存。
MemMap::TakeReservedMemory:在已存在的的MemMap的头部映射一段内存作为新的MemMap并返回,同时会调整被重新分配的MemMap的边界和大小。
以MemMap::MapFileAtAddress为例子说明。 MemMap::MapFileAtAddress接收7个参数,第一个参数expected_ptr表示期望进行映射的起始地址;第二个参数byte_count表示要进行映射的内容大小;第三个参数prot表示期望的内存保护标志;第四个参数flags指定映射对象的类型,映射选项和映射页是否可以共享;第五个参数fd是要映射的文件fd;第六个参数start是要映射的文件偏移量;第七个参数low_4gb指定是否映射到进程空间的低4g位置;第八个参数filename映射的文件名称;第九个参数reuse表示要申请的MemMap是否需要复用已经存在的MemMap;第十个参数reservation表示预留的MemMap,如果不为null新申请的MemMap要从这个MemMap里面划分出来;第十一个参数error_msg是函数调用时获得的错误信息。
art/libartbase/base/mem_map.cc
MemMap MemMap::MapFileAtAddress(uint8_t* expected_ptr,
size_t byte_count,
int prot,
int flags,
int fd,
off_t start,
bool low_4gb,
const char* filename,
bool reuse,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg) {
CHECK_NE(0, prot);
CHECK_NE(0, flags & (MAP_SHARED | MAP_PRIVATE));
// Note that we do not allow MAP_FIXED unless reuse == true or we have an existing
// reservation, i.e we expect this mapping to be contained within an existing map.
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.
CHECK(expected_ptr != nullptr);
DCHECK(reservation == nullptr);
DCHECK(error_msg != nullptr);
DCHECK(ContainedWithinExistingMap(expected_ptr, byte_count, error_msg))
<< ((error_msg != nullptr) ? *error_msg : std::string());//检查要申请的MemMap是否包含在已经存在的MemMap里面
flags |= MAP_FIXED;
} else if (reservation != nullptr) {
DCHECK(error_msg != nullptr);
if (!CheckReservation(expected_ptr, byte_count, filename, *reservation, error_msg)) {//检查期望进行映射的起始地址是否与预留的MemMap起始地址相同,预留的MemMap大小是否大于进行映射的内容大小
return Invalid();
}
flags |= MAP_FIXED;
} else {
CHECK_EQ(0, flags & MAP_FIXED);
// Don't bother checking for an overlapping region here. We'll
// check this if required after the fact inside CheckMapRequest.
}
if (byte_count == 0) {
*error_msg = "Empty MemMap requested";
return Invalid();
}
// Adjust 'offset' to be page-aligned as required by mmap.
//如果文件起始处(start )不是4k对齐,多出一点不足4k的部分(page_offset ),则调整映射的参数:
1.调整文件起始处为距离文件起始处最近的4k对齐点(该对齐点偏移小于初始文件起始处),即page_aligned_offset ;
2.调整映射大小为原来映射大小加上多出的一点不足4k的部分大小,向上4k对齐后即page_aligned_byte_count ;
3.如果期望进行映射的起始地址不为空,调整期望进行映射的起始地址为原来期望进行映射的起始地址减去多出的一点不足4k的部分大小后的地址,即page_aligned_expected
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 'expected_ptr' is modified (if specified, ie non-null) to be page aligned to the file but
// not necessarily to virtual memory. mmap will page align 'expected' for us.
uint8_t* page_aligned_expected =
(expected_ptr == nullptr) ? nullptr : (expected_ptr - page_offset);
size_t redzone_size = 0;
if (kRunningOnMemoryTool && kMemoryToolAddsRedzones && expected_ptr == nullptr) {
redzone_size = kPageSize;
page_aligned_byte_count += redzone_size;
}
//MapInternal里面会调用mmap系统调用进行映射,mmap会调整映射的起始地址使得其会4k对齐
uint8_t* actual = reinterpret_cast<uint8_t*>(MapInternal(page_aligned_expected,
page_aligned_byte_count,
prot,
flags,
fd,
page_aligned_offset,
low_4gb));
if (actual == MAP_FAILED) {
if (error_msg != nullptr) {
auto saved_errno = errno;
if (kIsDebugBuild || VLOG_IS_ON(oat)) {
PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
}
*error_msg = StringPrintf("mmap(%p, %zd, 0x%x, 0x%x, %d, %" PRId64
") of file '%s' failed: %s. See process maps in the log.",
page_aligned_expected, page_aligned_byte_count, prot, flags, fd,
static_cast<int64_t>(page_aligned_offset), filename,
strerror(saved_errno));
}
return Invalid();
}
if (!CheckMapRequest(expected_ptr, actual, page_aligned_byte_count, error_msg)) {
return Invalid();
}
if (redzone_size != 0) {
const uint8_t *real_start = actual + page_offset;
const uint8_t *real_end = actual + page_offset + byte_count;
const uint8_t *mapping_end = actual + page_aligned_byte_count;
MEMORY_TOOL_MAKE_NOACCESS(actual, real_start - actual);
MEMORY_TOOL_MAKE_NOACCESS(real_end, mapping_end - real_end);
page_aligned_byte_count -= redzone_size;
}
if (reservation != nullptr) {
// Re-mapping was successful, transfer the ownership of the memory to the new MemMap.
DCHECK_EQ(actual, reservation->Begin());
reservation->ReleaseReservedMemory(byte_count);//调整预留MemMap的边界
}
return MemMap(filename,
actual + page_offset,
byte_count,
actual,
page_aligned_byte_count,
prot,
reuse,
redzone_size);
}
每次构造出一个MemMap后,会保存到全局变量gMaps中。gMaps是一个multimap,以成员变量base_begin_为键,以新MemMap为值。
art/libartbase/base/mem_map.cc
MemMap::MemMap(const std::string& name, uint8_t* begin, size_t size, void* base_begin,
size_t base_size, int prot, bool reuse, size_t redzone_size)
: name_(name), begin_(begin), size_(size), base_begin_(base_begin), base_size_(base_size),
prot_(prot), reuse_(reuse), already_unmapped_(false), redzone_size_(redzone_size) {
if (size_ == 0) {
CHECK(begin_ == nullptr);
CHECK(base_begin_ == nullptr);
CHECK_EQ(base_size_, 0U);
} else {
CHECK(begin_ != nullptr);
CHECK(base_begin_ != nullptr);
CHECK_NE(base_size_, 0U);
// Add it to gMaps.
std::lock_guard<std::mutex> mu(*mem_maps_lock_);
DCHECK(gMaps != nullptr);
gMaps->insert(std::make_pair(base_begin_, this));
}
}
我们注意到,MemMap里面有很多成员变量,尤其是begin_和base_begin_,size_和base_size_的区别看起来很像。实际上,base_begin_和base_size描述的是MemMap的映射基址和映射大小,begin_和size_描述的是映射的有效内容的映射基址和有效大小。参考上面的MapFileAtAddress原理,MemMap的大小永远大于等于有效内容的大小,而且MemMap的边界是4k对齐的,有效内容的边界不一定是4k对齐的。
下面是一张MapFileAtAddress原理图:
加载流程
boot*.art和boot*.oat包含了系统的重要代码,在虚拟机堆的创建过程中被加载。ImageSpace::LoadBootImage是加载入口。
art/runtime/gc/heap.cc
Heap::Heap(size_t initial_size,
size_t growth_limit,
size_t min_free,
size_t max_free,
double target_utilization,
double foreground_heap_growth_multiplier,
size_t capacity,
size_t non_moving_space_capacity,
const std::vector<std::string>& boot_class_path,
const std::vector<std::string>& boot_class_path_locations,
const std::string& image_file_name,
const InstructionSet image_instruction_set,
CollectorType foreground_collector_type,
CollectorType background_collector_type,
space::LargeObjectSpaceType large_object_space_type,
size_t large_object_threshold,
size_t parallel_gc_threads,
size_t conc_gc_threads,
bool low_memory_mode,
size_t long_pause_log_threshold,
size_t long_gc_log_threshold,
bool ignore_target_footprint,
bool use_tlab,
bool verify_pre_gc_heap,
bool verify_pre_sweeping_heap,
bool verify_post_gc_heap,
bool verify_pre_gc_rosalloc,
bool verify_pre_sweeping_rosalloc,
bool verify_post_gc_rosalloc,
bool gc_stress_mode,
bool measure_gc_performance,
bool use_homogeneous_space_compaction_for_oom,
bool use_generational_cc,
uint64_t min_interval_homogeneous_space_compaction_by_oom,
bool dump_region_info_before_gc,
bool dump_region_info_after_gc,
space::ImageSpaceLoadingOrder image_space_loading_order)
...
if (space::ImageSpace::LoadBootImage(boot_class_path,
boot_class_path_locations,
image_file_name,
image_instruction_set,
image_space_loading_order,
runtime->ShouldRelocate(),
/*executable=*/ !runtime->IsAotCompiler(),
is_zygote,
heap_reservation_size,
&boot_image_spaces,
&heap_reservation)) {
DCHECK_EQ(heap_reservation_size, heap_reservation.IsValid() ? heap_reservation.Size() : 0u);
DCHECK(!boot_image_spaces.empty());
request_begin = boot_image_spaces.back()->GetImageHeader().GetOatFileEnd();
DCHECK(!heap_reservation.IsValid() || request_begin == heap_reservation.Begin())
<< "request_begin=" << static_cast<const void*>(request_begin)
<< " heap_reservation.Begin()=" << static_cast<const void*>(heap_reservation.Begin());
for (std::unique_ptr<space::ImageSpace>& space : boot_image_spaces) {
boot_image_spaces_.push_back(space.get());
AddSpace(space.release());
}
art/runtime/gc/space/image_space.cc
bool ImageSpace::LoadBootImage(
const std::vector<std::string>& boot_class_path,
const std::vector<std::string>& boot_class_path_locations,
const std::string& image_location,
const InstructionSet image_isa,
ImageSpaceLoadingOrder order,
bool relocate,
bool executable,
bool is_zygote,
size_t extra_reservation_size,
/*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
/*out*/MemMap* extra_reservation) {
...
BootImageLoader loader(boot_class_path,
boot_class_path_locations,
image_location,
image_isa,
relocate,
executable,
is_zygote);
// Step 0: Extra zygote work.
loader.FindImageFiles();//确认/system和/data下面boot.art的存在情况
...
auto try_load_from = [&](auto has_fn, auto load_fn, bool validate_oat_file) {
if ((loader.*has_fn)()) {
std::string local_error_msg;
if ((loader.*load_fn)(validate_oat_file,
extra_reservation_size,
boot_image_spaces,
extra_reservation,
&local_error_msg)) {
return true;
}
error_msgs.push_back(local_error_msg);
}
return false;
};
auto try_load_from_system = [&]() {
return try_load_from(&BootImageLoader::HasSystem, &BootImageLoader::LoadFromSystem, false);
};
auto try_load_from_cache = [&]() {
return try_load_from(&BootImageLoader::HasCache, &BootImageLoader::LoadFromDalvikCache, true);
};
auto invoke_sequentially = [](auto first, auto second) {
return first() || second();
};
// Step 1+2: Check system and cache images in the asked-for order.
//默认先用/system下面的boot.art,不成功就用/data下面的boot.art,加载art文件的函数是BootImageLoader::LoadFromSystem
if (order == ImageSpaceLoadingOrder::kSystemFirst) {
if (invoke_sequentially(try_load_from_system, try_load_from_cache)) {
return true;
}
} else {
if (invoke_sequentially(try_load_from_cache, try_load_from_system)) {
return true;
}
}
art/runtime/gc/space/image_space.cc
bool LoadFromSystem(bool validate_oat_file,
size_t extra_reservation_size,
/*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
/*out*/MemMap* extra_reservation,
/*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
TimingLogger logger(__PRETTY_FUNCTION__, /*precise=*/ true, VLOG_IS_ON(image));
std::string filename = GetSystemImageFilename(image_location_.c_str(), image_isa_);
if (!LoadFromFile(filename,
validate_oat_file,
extra_reservation_size,
&logger,
boot_image_spaces,
extra_reservation,
error_msg)) {
return false;
}
if (VLOG_IS_ON(image)) {
LOG(INFO) << "ImageSpace::BootImageLoader::LoadFromSystem exiting "
<< boot_image_spaces->front();
logger.Dump(LOG_STREAM(INFO));
}
return true;
}
1.ReserveBootImageMemory用来申请一块连续的MemMap,这个MemMap可以用来确定映射的开始地址并为后面的加载预留空间,大小为所有boot*.art和boot*.oat的大小总和加上zygote / non moving space的容量的数值;2.ExpandMultiImageLocations扩展可加载art镜像的范围,原来只有一个boot.art,根据bootclasspath的值,经过扩展后得到boot-core-libart.art,boot-okhttp.art,boot-bouncycastle.art等等一系列的art镜像;3.ImageSpace::Loader::Init加载第2步中得到的一系列镜像,也就是把这些镜像依次映射到内存中;4.OpenOatFile依次打开第2步中得到的art镜像对应的oat文件。
art/runtime/gc/space/image_space.cc
bool LoadFromFile(
const std::string& filename,
bool validate_oat_file,
size_t extra_reservation_size,
TimingLogger* logger,
/*out*/std::vector<std::unique_ptr<space::ImageSpace>>* boot_image_spaces,
/*out*/MemMap* extra_reservation,
/*out*/std::string* error_msg) REQUIRES_SHARED(Locks::mutator_lock_) {
ImageHeader system_hdr;
if (!ReadSpecificImageHeader(filename.c_str(), &system_hdr)) {
*error_msg = StringPrintf("Cannot read header of %s", filename.c_str());
return false;
}
if (system_hdr.GetComponentCount() == 0u ||
system_hdr.GetComponentCount() > boot_class_path_.size()) {
*error_msg = StringPrintf("Unexpected component count in %s, received %u, "
"expected non-zero and <= %zu",
filename.c_str(),
system_hdr.GetComponentCount(),
boot_class_path_.size());
return false;
}
MemMap image_reservation;
MemMap local_extra_reservation;
if (!ReserveBootImageMemory(system_hdr.GetImageReservationSize(),
reinterpret_cast32<uint32_t>(system_hdr.GetImageBegin()),
extra_reservation_size,
&image_reservation,
&local_extra_reservation,
error_msg)) {
return false;
}
ArrayRef<const std::string> provided_locations(boot_class_path_locations_.data(),
system_hdr.GetComponentCount());
std::vector<std::string> locations =
ExpandMultiImageLocations(provided_locations, image_location_);
std::vector<std::string> filenames =
ExpandMultiImageLocations(provided_locations, filename);
DCHECK_EQ(locations.size(), filenames.size());
std::vector<std::unique_ptr<ImageSpace>> spaces;
spaces.reserve(locations.size());
for (std::size_t i = 0u, size = locations.size(); i != size; ++i) {
spaces.push_back(Load(locations[i], filenames[i], logger, &image_reservation, error_msg));
const ImageSpace* space = spaces.back().get();
if (space == nullptr) {
return false;
}
uint32_t expected_component_count = (i == 0u) ? system_hdr.GetComponentCount() : 0u;
uint32_t expected_reservation_size = (i == 0u) ? system_hdr.GetImageReservationSize() : 0u;
if (!Loader::CheckImageReservationSize(*space, expected_reservation_size, error_msg) ||
!Loader::CheckImageComponentCount(*space, expected_component_count, error_msg)) {
return false;
}
}
for (size_t i = 0u, size = spaces.size(); i != size; ++i) {
std::string expected_boot_class_path =
(i == 0u) ? android::base::Join(provided_locations, ':') : std::string();
if (!OpenOatFile(spaces[i].get(),
boot_class_path_[i],
expected_boot_class_path,
validate_oat_file,
logger,
&image_reservation,
error_msg)) {
return false;
}
}
if (!CheckReservationExhausted(image_reservation, error_msg)) {
return false;
}
MaybeRelocateSpaces(spaces, logger);
boot_image_spaces->swap(spaces);
*extra_reservation = std::move(local_extra_reservation);
return true;
}
确定加载基址
boot.art的映射基址被指定为ART_BASE_ADDRESS,即是0x70000000。映射art镜像文件使用了ASLR技术,ASLR通过随机放置进程关键数据区域的地址空间来防止攻击者能可靠地跳转到内存的特定位置来利用函数。如果当前的运行时不是编译运行时(dexoat的运行时是编译运行时),就需要在ART_BASE_ADDRESS的基础上加一个随机的增量,将之作为新的加载基址。
之后MemMap::MapAnonymous内部通过MapInternal获得一块起始地址为ASLR后的基址,大小为boot.art大小向上4K对齐后的数值的匿名内存,用于加载boot*.art和boot*.oat。然后MemMap::RemapAtEnd为zygote / non moving space的预留空间构建了一个MemMap。用于加载boot*.art和boot*.oat的MemMap会被设置到入参image_reservation,zygote / non moving space的MemMap会设置到入参extra_reservation。
art/runtime/gc/space/image_space.cc
bool ReserveBootImageMemory(uint32_t reservation_size,
uint32_t image_start,
size_t extra_reservation_size,
/*out*/MemMap* image_reservation,
/*out*/MemMap* extra_reservation,
/*out*/std::string* error_msg) {
DCHECK_ALIGNED(reservation_size, kPageSize);
DCHECK_ALIGNED(image_start, kPageSize);
DCHECK(!image_reservation->IsValid());
DCHECK_LT(extra_reservation_size, std::numeric_limits<uint32_t>::max() - reservation_size);
size_t total_size = reservation_size + extra_reservation_size;
// If relocating, choose a random address for ALSR.
uint32_t addr = relocate_ ? ART_BASE_ADDRESS + ChooseRelocationOffsetDelta() : image_start;
*image_reservation =
MemMap::MapAnonymous("Boot image reservation",
reinterpret_cast32<uint8_t*>(addr),
total_size,
PROT_NONE,
/*low_4gb=*/ true,
/*reuse=*/ false,
/*reservation=*/ nullptr,
error_msg);
if (!image_reservation->IsValid()) {
return false;
}
DCHECK(!extra_reservation->IsValid());
if (extra_reservation_size != 0u) {
DCHECK_ALIGNED(extra_reservation_size, kPageSize);
DCHECK_LT(extra_reservation_size, image_reservation->Size());
uint8_t* split = image_reservation->End() - extra_reservation_size;
*extra_reservation = image_reservation->RemapAtEnd(split,
"Boot image extra reservation",
PROT_NONE,
error_msg);
if (!extra_reservation->IsValid()) {
return false;
}
}
return true;
}
art/libartbase/base/mem_map.cc
MemMap MemMap::MapAnonymous(const char* name,
uint8_t* addr,
size_t byte_count,
int prot,
bool low_4gb,
bool reuse,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg,
bool use_debug_name) {
#ifndef __LP64__
UNUSED(low_4gb);
#endif
if (byte_count == 0) {
*error_msg = "Empty MemMap requested.";
return Invalid();
}
//映射大小要向上4K对齐
size_t page_aligned_byte_count = RoundUp(byte_count, kPageSize);
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
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.
CHECK(addr != nullptr);
DCHECK(reservation == nullptr);
DCHECK(ContainedWithinExistingMap(addr, byte_count, error_msg)) << *error_msg;
flags |= MAP_FIXED;
} else if (reservation != nullptr) {
CHECK(addr != nullptr);
if (!CheckReservation(addr, byte_count, name, *reservation, error_msg)) {
return MemMap::Invalid();
}
flags |= MAP_FIXED;
}
unique_fd fd;
// We need to store and potentially set an error number for pretty printing of errors
int saved_errno = 0;
void* actual = MapInternal(addr,
page_aligned_byte_count,
prot,
flags,
fd.get(),
0,
low_4gb);
saved_errno = errno;
if (actual == MAP_FAILED) {
if (error_msg != nullptr) {
if (kIsDebugBuild || VLOG_IS_ON(oat)) {
PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
}
*error_msg = StringPrintf("Failed anonymous mmap(%p, %zd, 0x%x, 0x%x, %d, 0): %s. "
"See process maps in the log.",
addr,
page_aligned_byte_count,
prot,
flags,
fd.get(),
strerror(saved_errno));
}
return Invalid();
}
if (!CheckMapRequest(addr, actual, page_aligned_byte_count, error_msg)) {
return Invalid();
}
if (use_debug_name) {
SetDebugName(actual, name, page_aligned_byte_count);
}
if (reservation != nullptr) {
// Re-mapping was successful, transfer the ownership of the memory to the new MemMap.
DCHECK_EQ(actual, reservation->Begin());
reservation->ReleaseReservedMemory(byte_count);
}
return MemMap(name,
reinterpret_cast<uint8_t*>(actual),
byte_count,
actual,
page_aligned_byte_count,
prot,
reuse);
}
ExpandMultiImageLocations扩展
ImageSpace中的boot_class_path_locations_如果不通过"-Xbootclasspath-locations"设置,就和环境变量DEX2OATBOOTCLASSPATH的值相同。
扩展就是将环境变量DEX2OATBOOTCLASSPATH的值按‘:’分割开,取最后的jar的名字(例如从“/apex/com.android.runtime/javalib/core-libart.jar”取到"core-libart")后,前面加上“boot-”,后面加上“.art”,形成一个新的art镜像名。
art/runtime/gc/space/image_space.cc
ArrayRef<const std::string> provided_locations(boot_class_path_locations_.data(),
system_hdr.GetComponentCount());
std::vector<std::string> locations =
ExpandMultiImageLocations(provided_locations, image_location_);
std::vector<std::string> filenames =
ExpandMultiImageLocations(provided_locations, filename);
art/runtime/gc/space/image_space.cc
std::vector<std::string> ImageSpace::ExpandMultiImageLocations(
ArrayRef<const std::string> dex_locations,
const std::string& image_location) {
DCHECK(!dex_locations.empty());
// Find the path.
size_t last_slash = image_location.rfind('/');
CHECK_NE(last_slash, std::string::npos);
// We also need to honor path components that were encoded through '@'. Otherwise the loading
// code won't be able to find the images.
if (image_location.find('@', last_slash) != std::string::npos) {
last_slash = image_location.rfind('@');
}
// Find the dot separating the primary image name from the extension.
size_t last_dot = image_location.rfind('.');
// Extract the extension and base (the path and primary image name).
std::string extension;
std::string base = image_location;
if (last_dot != std::string::npos && last_dot > last_slash) {
extension = image_location.substr(last_dot); // Including the dot.
base.resize(last_dot);
}
// For non-empty primary image name, add '-' to the `base`.
if (last_slash + 1u != base.size()) {
base += '-';
}
std::vector<std::string> locations;
locations.reserve(dex_locations.size());
locations.push_back(image_location);
// Now create the other names. Use a counted loop to skip the first one.
for (size_t i = 1u; i < dex_locations.size(); ++i) {
// Replace path with `base` (i.e. image path and prefix) and replace the original
// extension (if any) with `extension`.
std::string name = dex_locations[i];
size_t last_dex_slash = name.rfind('/');
if (last_dex_slash != std::string::npos) {
name = name.substr(last_dex_slash + 1);
}
size_t last_dex_dot = name.rfind('.');
if (last_dex_dot != std::string::npos) {
name.resize(last_dex_dot);
}
locations.push_back(base + name + extension);
}
return locations;
}
加载镜像
加载boot*.art的主要流程从image_space::Load开始。
art/runtime/gc/space/image_space.cc
std::unique_ptr<ImageSpace> Load(const std::string& image_location,
const std::string& image_filename,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
/*out*/std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Should this be a RDWR lock? This is only a defensive measure, as at
// this point the image should exist.
// However, only the zygote can write into the global dalvik-cache, so
// restrict to zygote processes, or any process that isn't using
// /data/dalvik-cache (which we assume to be allowed to write there).
const bool rw_lock = is_zygote_ || !is_global_cache_;
// 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 = LockedFile::Open(image_filename.c_str(),
/*flags=*/ rw_lock ? (O_CREAT | O_RDWR) : O_RDONLY,
/*block=*/ true,
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.
// Since we are the boot image, pass null since we load the oat file from the boot image oat
// file name.
return Loader::Init(image_filename.c_str(),
image_location.c_str(),
/*oat_file=*/ nullptr,
logger,
image_reservation,
error_msg);
}
art/runtime/gc/space/image_space.cc
static std::unique_ptr<ImageSpace> Init(const char* image_filename,
const char* image_location,
const OatFile* oat_file,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
/*out*/std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
CHECK(image_filename != nullptr);
CHECK(image_location != nullptr);
VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename;
std::unique_ptr<File> file;
{
TimingLogger::ScopedTiming timing("OpenImageFile", logger);
file.reset(OS::OpenFileForReading(image_filename));
if (file == nullptr) {
*error_msg = StringPrintf("Failed to open '%s'", image_filename);
return nullptr;
}
}
ImageHeader temp_image_header;
ImageHeader* image_header = &temp_image_header;
{
TimingLogger::ScopedTiming timing("ReadImageHeader", logger);
bool success = file->ReadFully(image_header, sizeof(*image_header));//读取art镜像的文件头
if (!success || !image_header->IsValid()) {
*error_msg = StringPrintf("Invalid image header in '%s'", image_filename);
return nullptr;
}
}
// Check that the file is larger or equal to the header size + data size.
const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) {
*error_msg = StringPrintf(
"Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
image_file_size,
static_cast<uint64_t>(sizeof(ImageHeader) + image_header->GetDataSize()));
return nullptr;
}
if (oat_file != nullptr) {//oat_file为null,跳过校验流程
// If we have an oat file (i.e. for app image), check the oat file checksum.
// Otherwise, we open the oat file after the image and check the checksum there.
const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
const uint32_t image_oat_checksum = image_header->GetOatChecksum();
if (oat_checksum != image_oat_checksum) {
*error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
oat_checksum,
image_oat_checksum,
image_filename);
return nullptr;
}
}
if (VLOG_IS_ON(startup)) {
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->GetImageBitmapSection();
// The location we want to map from is the first aligned page after the end of the stored
// (possibly compressed) data.
const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(),
kPageSize);
const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size();
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;
}
// GetImageBegin is the preferred address to map the image. If we manage to map the
// image at the image begin, the amount of fixup work required is minimized.
// If it is pic we will retry with error_msg for the2 failure case. Pass a null error_msg to
// avoid reading proc maps for a mapping failure and slowing everything down.
// For the boot image, we have already reserved the memory and we load the image
// into the `image_reservation`.
MemMap map = LoadImageFile(
image_filename,
image_location,
*image_header,
file->Fd(),
logger,
image_reservation,
error_msg);//映射boot*.art到MemMap中
if (!map.IsValid()) {
DCHECK(!error_msg->empty());
return nullptr;
}
DCHECK_EQ(0, memcmp(image_header, map.Begin(), sizeof(ImageHeader)));
MemMap image_bitmap_map = MemMap::MapFile(bitmap_section.Size(),
PROT_READ,
MAP_PRIVATE,
file->Fd(),
image_bitmap_offset,
/*low_4gb=*/ false,
image_filename,
error_msg);//映射boot*.art的Bitmap Section到MemMap中
if (!image_bitmap_map.IsValid()) {
*error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
return nullptr;
}
// Loaded the map, use the image header from the file now in case we patch it with
// RelocateInPlace.
image_header = reinterpret_cast<ImageHeader*>(map.Begin());
const uint32_t bitmap_index = ImageSpace::bitmap_index_.fetch_add(1);
std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u",
image_filename,
bitmap_index));
// Bitmap only needs to cover until the end of the mirror objects section.
const ImageSection& image_objects = image_header->GetObjectsSection();
// We only want the mirror object, not the ArtFields and ArtMethods.
uint8_t* const image_end = map.Begin() + image_objects.End();
std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap;
{
TimingLogger::ScopedTiming timing("CreateImageBitmap", logger);
bitmap.reset(
accounting::ContinuousSpaceBitmap::CreateFromMemMap(
bitmap_name,
std::move(image_bitmap_map),
reinterpret_cast<uint8_t*>(map.Begin()),
// Make sure the bitmap is aligned to card size instead of just bitmap word size.
//创建boot*.art的Objects Section的BitMap
RoundUp(image_objects.End(), gc::accounting::CardTable::kCardSize)));
if (bitmap == 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.
std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
image_location,
std::move(map),
std::move(bitmap),
image_end));//创建一块ImageSpace
space->oat_file_non_owned_ = oat_file;
return space;
}
默认情况下,boot*.art使用非压缩模式,is_compressed是false,所以使用MapFileAtAddress进行映射得到一个用于返回的MemMap。
art/runtime/gc/space/image_space.cc
static MemMap LoadImageFile(const char* image_filename,
const char* image_location,
const ImageHeader& image_header,
int fd,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
/*out*/std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
TimingLogger::ScopedTiming timing("MapImageFile", logger);
std::string temp_error_msg;
const bool is_compressed = image_header.HasCompressedBlock();
if (!is_compressed) {
uint8_t* address = (image_reservation != nullptr) ? image_reservation->Begin() : nullptr;
return MemMap::MapFileAtAddress(address,
image_header.GetImageSize(),
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
/*start=*/ 0,
/*low_4gb=*/ true,
image_filename,
/*reuse=*/ false,
image_reservation,
error_msg);
}
// Reserve output and decompress into it.
MemMap map = MemMap::MapAnonymous(image_location,
image_header.GetImageSize(),
PROT_READ | PROT_WRITE,
/*low_4gb=*/ true,
image_reservation,
error_msg);
if (map.IsValid()) {
const size_t stored_size = image_header.GetDataSize();
MemMap temp_map = MemMap::MapFile(sizeof(ImageHeader) + stored_size,
PROT_READ,
MAP_PRIVATE,
fd,
/*start=*/ 0,
/*low_4gb=*/ false,
image_filename,
error_msg);
if (!temp_map.IsValid()) {
DCHECK(error_msg == nullptr || !error_msg->empty());
return MemMap::Invalid();
}
memcpy(map.Begin(), &image_header, sizeof(ImageHeader));
Runtime::ScopedThreadPoolUsage stpu;
ThreadPool* const pool = stpu.GetThreadPool();
const uint64_t start = NanoTime();
Thread* const self = Thread::Current();
static constexpr size_t kMinBlocks = 2u;
const bool use_parallel = pool != nullptr && image_header.GetBlockCount() >= kMinBlocks;
for (const ImageHeader::Block& block : image_header.GetBlocks(temp_map.Begin())) {
auto function = [&](Thread*) {
const uint64_t start2 = NanoTime();
ScopedTrace trace("LZ4 decompress block");
bool result = block.Decompress(/*out_ptr=*/map.Begin(),
/*in_ptr=*/temp_map.Begin(),
error_msg);
if (!result && error_msg != nullptr) {
*error_msg = "Failed to decompress image block " + *error_msg;
}
VLOG(image) << "Decompress block " << block.GetDataSize() << " -> "
<< block.GetImageSize() << " in " << PrettyDuration(NanoTime() - start2);
};
if (use_parallel) {
pool->AddTask(self, new FunctionTask(std::move(function)));
} else {
function(self);
}
}
if (use_parallel) {
ScopedTrace trace("Waiting for workers");
// Go to native since we don't want to suspend while holding the mutator lock.
ScopedThreadSuspension sts(Thread::Current(), kNative);
pool->Wait(self, true, false);
}
const uint64_t time = NanoTime() - start;
// Add one 1 ns to prevent possible divide by 0.
VLOG(image) << "Decompressing image took " << PrettyDuration(time) << " ("
<< PrettySize(static_cast<uint64_t>(map.Size()) * MsToNs(1000) / (time + 1))
<< "/s)";
}
return map;
}
art/libartbase/base/mem_map.cc
MemMap MemMap::MapFileAtAddress(uint8_t* expected_ptr,
size_t byte_count,
int prot,
int flags,
int fd,
off_t start,
bool low_4gb,
const char* filename,
bool reuse,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg) {
CHECK_NE(0, prot);
CHECK_NE(0, flags & (MAP_SHARED | MAP_PRIVATE));
// Note that we do not allow MAP_FIXED unless reuse == true or we have an existing
// reservation, i.e we expect this mapping to be contained within an existing map.
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.
CHECK(expected_ptr != nullptr);
DCHECK(reservation == nullptr);
DCHECK(error_msg != nullptr);
DCHECK(ContainedWithinExistingMap(expected_ptr, byte_count, error_msg))
<< ((error_msg != nullptr) ? *error_msg : std::string());
flags |= MAP_FIXED;
} else if (reservation != nullptr) {
DCHECK(error_msg != nullptr);
if (!CheckReservation(expected_ptr, byte_count, filename, *reservation, error_msg)) {
return Invalid();
}
flags |= MAP_FIXED;
} else {
CHECK_EQ(0, flags & MAP_FIXED);
// Don't bother checking for an overlapping region here. We'll
// check this if required after the fact inside CheckMapRequest.
}
if (byte_count == 0) {
*error_msg = "Empty MemMap requested";
return Invalid();
}
// 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 'expected_ptr' is modified (if specified, ie non-null) to be page aligned to the file but
// not necessarily to virtual memory. mmap will page align 'expected' for us.
uint8_t* page_aligned_expected =
(expected_ptr == nullptr) ? nullptr : (expected_ptr - page_offset);
size_t redzone_size = 0;
if (kRunningOnMemoryTool && kMemoryToolAddsRedzones && expected_ptr == nullptr) {
redzone_size = kPageSize;
page_aligned_byte_count += redzone_size;
}
uint8_t* actual = reinterpret_cast<uint8_t*>(MapInternal(page_aligned_expected,
page_aligned_byte_count,
prot,
flags,
fd,
page_aligned_offset,
low_4gb));
if (actual == MAP_FAILED) {
if (error_msg != nullptr) {
auto saved_errno = errno;
if (kIsDebugBuild || VLOG_IS_ON(oat)) {
PrintFileToLog("/proc/self/maps", LogSeverity::WARNING);
}
*error_msg = StringPrintf("mmap(%p, %zd, 0x%x, 0x%x, %d, %" PRId64
") of file '%s' failed: %s. See process maps in the log.",
page_aligned_expected, page_aligned_byte_count, prot, flags, fd,
static_cast<int64_t>(page_aligned_offset), filename,
strerror(saved_errno));
}
return Invalid();
}
if (!CheckMapRequest(expected_ptr, actual, page_aligned_byte_count, error_msg)) {
return Invalid();
}
if (redzone_size != 0) {
const uint8_t *real_start = actual + page_offset;
const uint8_t *real_end = actual + page_offset + byte_count;
const uint8_t *mapping_end = actual + page_aligned_byte_count;
MEMORY_TOOL_MAKE_NOACCESS(actual, real_start - actual);
MEMORY_TOOL_MAKE_NOACCESS(real_end, mapping_end - real_end);
page_aligned_byte_count -= redzone_size;
}
if (reservation != nullptr) {
// Re-mapping was successful, transfer the ownership of the memory to the new MemMap.
DCHECK_EQ(actual, reservation->Begin());
reservation->ReleaseReservedMemory(byte_count);
}
return MemMap(filename,
actual + page_offset,
byte_count,
actual,
page_aligned_byte_count,
prot,
reuse,
redzone_size);
}
映射内存
先介绍下boot.art文件的结构。boot.art文件由12个section组成,其中文件头包含在Objects Section。下面可以看到,前11个section会被通过MapFileAtAddress的方式依次加载到预留内存image_reservation里面,而最后一个ImageBitmap Section被通过MapFile的方式加载到由系统决定的地址中。
打开/proc/{pid}/maps可以证明上述加载方式。第一行的分配基址是70df6000,这是固定加载基址0x70000000 ALSR化的地址,也是boot.art的前11个section的加载基址;第二行的基址是77b2a24000,是系统决定的,也是boot.art最后一个section的加载基址。
70df6000-7107d000 rw-p 00000000 103:11 1544 /system/framework/arm64/boot.art
77b2a24000-77b2a29000 r--p 00287000 103:11 1544 /system/framework/arm64/boot.art
art/runtime/gc/space/image_space.cc
static std::unique_ptr<ImageSpace> Init(const char* image_filename,
const char* image_location,
const OatFile* oat_file,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
/*out*/std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
CHECK(image_filename != nullptr);
CHECK(image_location != nullptr);
VLOG(image) << "ImageSpace::Init entering image_filename=" << image_filename;
std::unique_ptr<File> file;
{
TimingLogger::ScopedTiming timing("OpenImageFile", logger);
file.reset(OS::OpenFileForReading(image_filename));
if (file == nullptr) {
*error_msg = StringPrintf("Failed to open '%s'", image_filename);
return nullptr;
}
}
ImageHeader temp_image_header;
ImageHeader* image_header = &temp_image_header;
{
TimingLogger::ScopedTiming timing("ReadImageHeader", logger);
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 larger or equal to the header size + data size.
const uint64_t image_file_size = static_cast<uint64_t>(file->GetLength());
if (image_file_size < sizeof(ImageHeader) + image_header->GetDataSize()) {
*error_msg = StringPrintf(
"Image file truncated: %" PRIu64 " vs. %" PRIu64 ".",
image_file_size,
static_cast<uint64_t>(sizeof(ImageHeader) + image_header->GetDataSize()));
return nullptr;
}
if (oat_file != nullptr) {
// If we have an oat file (i.e. for app image), check the oat file checksum.
// Otherwise, we open the oat file after the image and check the checksum there.
const uint32_t oat_checksum = oat_file->GetOatHeader().GetChecksum();
const uint32_t image_oat_checksum = image_header->GetOatChecksum();
if (oat_checksum != image_oat_checksum) {
*error_msg = StringPrintf("Oat checksum 0x%x does not match the image one 0x%x in image %s",
oat_checksum,
image_oat_checksum,
image_filename);
return nullptr;
}
}
if (VLOG_IS_ON(startup)) {
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->GetImageBitmapSection();
// The location we want to map from is the first aligned page after the end of the stored
// (possibly compressed) data.
const size_t image_bitmap_offset = RoundUp(sizeof(ImageHeader) + image_header->GetDataSize(),
kPageSize);
const size_t end_of_bitmap = image_bitmap_offset + bitmap_section.Size();
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;
}
// GetImageBegin is the preferred address to map the image. If we manage to map the
// image at the image begin, the amount of fixup work required is minimized.
// If it is pic we will retry with error_msg for the2 failure case. Pass a null error_msg to
// avoid reading proc maps for a mapping failure and slowing everything down.
// For the boot image, we have already reserved the memory and we load the image
// into the `image_reservation`.
MemMap map = LoadImageFile(
image_filename,
image_location,
*image_header,
file->Fd(),
logger,
image_reservation,
error_msg);
if (!map.IsValid()) {
DCHECK(!error_msg->empty());
return nullptr;
}
DCHECK_EQ(0, memcmp(image_header, map.Begin(), sizeof(ImageHeader)));
MemMap image_bitmap_map = MemMap::MapFile(bitmap_section.Size(),
PROT_READ,
MAP_PRIVATE,
file->Fd(),
image_bitmap_offset,
/*low_4gb=*/ false,
image_filename,
error_msg);
if (!image_bitmap_map.IsValid()) {
*error_msg = StringPrintf("Failed to map image bitmap: %s", error_msg->c_str());
return nullptr;
}
// Loaded the map, use the image header from the file now in case we patch it with
// RelocateInPlace.
image_header = reinterpret_cast<ImageHeader*>(map.Begin());
const uint32_t bitmap_index = ImageSpace::bitmap_index_.fetch_add(1);
std::string bitmap_name(StringPrintf("imagespace %s live-bitmap %u",
image_filename,
bitmap_index));
// Bitmap only needs to cover until the end of the mirror objects section.
const ImageSection& image_objects = image_header->GetObjectsSection();
// We only want the mirror object, not the ArtFields and ArtMethods.
uint8_t* const image_end = map.Begin() + image_objects.End();
std::unique_ptr<accounting::ContinuousSpaceBitmap> bitmap;
{
TimingLogger::ScopedTiming timing("CreateImageBitmap", logger);
bitmap.reset(
accounting::ContinuousSpaceBitmap::CreateFromMemMap(
bitmap_name,
std::move(image_bitmap_map),
reinterpret_cast<uint8_t*>(map.Begin()),
// Make sure the bitmap is aligned to card size instead of just bitmap word size.
RoundUp(image_objects.End(), gc::accounting::CardTable::kCardSize)));
if (bitmap == 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.
std::unique_ptr<ImageSpace> space(new ImageSpace(image_filename,
image_location,
std::move(map),
std::move(bitmap),
image_end));
space->oat_file_non_owned_ = oat_file;
return space;
}
art/runtime/gc/space/image_space.cc
static MemMap LoadImageFile(const char* image_filename,
const char* image_location,
const ImageHeader& image_header,
int fd,
TimingLogger* logger,
/*inout*/MemMap* image_reservation,
/*out*/std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_) {
TimingLogger::ScopedTiming timing("MapImageFile", logger);
std::string temp_error_msg;
const bool is_compressed = image_header.HasCompressedBlock();
if (!is_compressed) {
uint8_t* address = (image_reservation != nullptr) ? image_reservation->Begin() : nullptr;
return MemMap::MapFileAtAddress(address,
image_header.GetImageSize(),
PROT_READ | PROT_WRITE,
MAP_PRIVATE,
fd,
/*start=*/ 0,
/*low_4gb=*/ true,
image_filename,
/*reuse=*/ false,
image_reservation,
error_msg);
}
...
通过对每个boot*.art的image_space::Load加载,可以得到以下的内存布局。可以看到各个boot*.art依次紧密排列,而且边界均4k对齐。
70df6000-7107d000 rw-p 00000000 103:11 1544 /system/framework/arm64/boot.art
7107d000-7116c000 rw-p 00000000 103:11 1523 /system/framework/arm64/boot-core-libart.art
7116c000-711a2000 rw-p 00000000 103:11 1535 /system/framework/arm64/boot-okhttp.art
711a2000-711e3000 rw-p 00000000 103:11 1520 /system/framework/arm64/boot-bouncycastle.art
711e3000-711f3000 rw-p 00000000 103:11 1517 /system/framework/arm64/boot-apache-xml.art
711f3000-71aaf000 rw-p 00000000 103:11 1529 /system/framework/arm64/boot-framework.art
71aaf000-71ae2000 rw-p 00000000 103:11 1526 /system/framework/arm64/boot-ext.art
71ae2000-71bd9000 rw-p 00000000 103:11 1538 /system/framework/arm64/boot-telephony-common.art
71bd9000-71be7000 rw-p 00000000 103:11 1541 /system/framework/arm64/boot-voip-common.art
71be7000-71bfc000 rw-p 00000000 103:11 1532 /system/framework/arm64/boot-ims-common.art
71bfc000-71bff000 rw-p 00000000 103:11 1514 /system/framework/arm64/boot-android.test.base.art
加载oat文件
加载完各个boot*.art后,art会使用ImageSpace::BootImageLoader::OpenOatFile依次加载各个boot*.oat,OpenOatFile的核心实现在OatFile::Open。
art有两套可选方案加载oat文件:dlopen加载器DlOpenOatFile和elf加载器ElfOatFile。dlopen加载器主要使用系统函数dlopen来加载,elf加载器主要使用art内部实现的方式来加载。art会首先尝试dlopen加载器加载,加载不成功就换用elf加载器。
art/runtime/oat_file.cc
OatFile* OatFile::Open(int zip_fd,
const std::string& oat_filename,
const std::string& oat_location,
bool executable,
bool low_4gb,
const char* abs_dex_location,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg) {
ScopedTrace trace("Open oat file " + oat_location);
CHECK(!oat_filename.empty()) << oat_location;
CheckLocation(oat_location);
std::string vdex_filename = GetVdexFilename(oat_filename);
// Check that the files even exist, fast-fail.
if (!OS::FileExists(vdex_filename.c_str())) {
*error_msg = StringPrintf("File %s does not exist.", vdex_filename.c_str());
return nullptr;
} else if (!OS::FileExists(oat_filename.c_str())) {
*error_msg = StringPrintf("File %s does not exist.", oat_filename.c_str());
return nullptr;
}
// Try dlopen first, as it is required for native debuggability. This will fail fast if dlopen is
// disabled.
OatFile* with_dlopen = OatFileBase::OpenOatFile<DlOpenOatFile>(zip_fd,
vdex_filename,
oat_filename,
oat_location,
/*writable=*/ false,
executable,
low_4gb,
abs_dex_location,
reservation,
error_msg);
if (with_dlopen != nullptr) {
return with_dlopen;
}
if (kPrintDlOpenErrorMessage) {
LOG(ERROR) << "Failed to dlopen: " << oat_filename << " with error " << *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.
OatFile* with_internal = OatFileBase::OpenOatFile<ElfOatFile>(zip_fd,
vdex_filename,
oat_filename,
oat_location,
/*writable=*/ false,
executable,
low_4gb,
abs_dex_location,
reservation,
error_msg);
return with_internal;
}
DlOpenOatFile和ElfOatFile均继承自OatFileBase,OatFileBase::OpenOatFile的实现如下。OpenOatFile分为6个步骤:
1.PreLoad(DlOpenOatFile独有);
2.Load(DlOpenOatFile和ElfOatFile均有独立实现);
3.ComputeFields(DlOpenOatFile和ElfOatFile统一实现);
4.PreSetup(DlOpenOatFile独有);
5.LoadVdex(DlOpenOatFile和ElfOatFile统一实现);
6.Setup(DlOpenOatFile和ElfOatFile统一实现。
art/runtime/oat_file.cc
template <typename kOatFileBaseSubType>
OatFileBase* OatFileBase::OpenOatFile(int zip_fd,
const std::string& vdex_filename,
const std::string& elf_filename,
const std::string& location,
bool writable,
bool executable,
bool low_4gb,
const char* abs_dex_location,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg) {
std::unique_ptr<OatFileBase> ret(new kOatFileBaseSubType(location, executable));
ret->PreLoad();
if (!ret->Load(elf_filename,
writable,
executable,
low_4gb,
reservation,
error_msg)) {
return nullptr;
}
if (!ret->ComputeFields(elf_filename, error_msg)) {
return nullptr;
}
ret->PreSetup(elf_filename);
if (!ret->LoadVdex(vdex_filename, writable, low_4gb, error_msg)) {
return nullptr;
}
if (!ret->Setup(zip_fd, abs_dex_location, error_msg)) {
return nullptr;
}
return ret.release();
}
Preload的作用是统计加载oat文件前的共享符号数量,记录在shared_objects_before_ 中。
Preload
art/runtime/oat_file.cc
void DlOpenOatFile::PreLoad() {
#ifdef __APPLE__
UNUSED(shared_objects_before_);
LOG(FATAL) << "Should not reach here.";
UNREACHABLE();
#else
// Count the entries in dl_iterate_phdr we get at this point in time.
struct dl_iterate_context {
static int callback(dl_phdr_info* info ATTRIBUTE_UNUSED,
size_t size ATTRIBUTE_UNUSED,
void* data) {
reinterpret_cast<dl_iterate_context*>(data)->count++;
return 0; // Continue iteration.
}
size_t count = 0;
} context;
dl_iterate_phdr(dl_iterate_context::callback, &context);
shared_objects_before_ = context.count;
#endif
}
Load
Load包含了映射oat文件到内存的核心逻辑。无论是DlOpenOatFile还是ElfOatFile,它们的Load主要作用是将oat文件程序头表的PT_LOAD段映射到参数reservation的预留内存中。
关于ELF文件的运行示图,可参考:ELF文件系列第六篇ELF文件运行视图相关结构
以boot.oat为例,它的程序头表是:
readelf -l boot.oat
Elf 文件类型为 DYN (共享目标文件)
入口点 0x0
共有 9 个程序头,开始于偏移量 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000001f8 0x00000000000001f8 R 8
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000b9000 0x00000000000b9000 R 1000
LOAD 0x00000000000b9000 0x00000000000b9000 0x00000000000b9000
0x0000000000294f60 0x0000000000294f60 R E 1000
LOAD 0x0000000000000000 0x000000000034e000 0x000000000034e000
0x0000000000000000 0x00000000000004e8 RW 1000
LOAD 0x0000000000000000 0x000000000034f000 0x000000000034f000
0x0000000000000000 0x0000000000001b97 R 1000
LOAD 0x000000000034e000 0x0000000000351000 0x0000000000351000
0x0000000000000194 0x0000000000000194 R 1000
LOAD 0x000000000034f000 0x0000000000352000 0x0000000000352000
0x0000000000000070 0x0000000000000070 RW 1000
NOTE 0x00000000000003c0 0x00000000000003c0 0x00000000000003c0
0x0000000000000024 0x0000000000000024 R 4
DYNAMIC 0x000000000034f000 0x0000000000352000 0x0000000000352000
0x0000000000000070 0x0000000000000070 RW 1000
Section to Segment mapping:
段节...
00
01 .note.gnu.build-id .rodata
02 .text
03 .bss
04 .dex
05 .dynstr .dynsym .hash
06 .dynamic
07 .note.gnu.build-id
08 .dynamic
对于DlOpenOatFile,核心逻辑如下。
bionic/linker/linker_phdr.cpp
bool ElfReader::Load(address_space_params* address_space) {
CHECK(did_read_);
if (did_load_) {
return true;
}
if (ReserveAddressSpace(address_space) && LoadSegments() && FindPhdr()) {
did_load_ = true;
}
return did_load_;
}
ReserveAddressSpace用来确认加载所有PT_LOAD段所需要的大小(load_size_),映射基址(load_start_)和计算基址(load_bias_)。映射基址就是预留的内存的首地址,计算基址用于快速计算每个段的地址。每个段都有p_vaddr虚拟地址属性,该属性是段地址距离elf文件加载基址的偏移,所以,下面代码设置计算基址为映射基址减去所有段中最小的p_vaddr(也就是elf文件加载基址的值),这样计算一个段的地址就用load_bias_+p_vaddr就可以得到。
bionic/linker/linker_phdr.cpp
// Reserve a virtual address range big enough to hold all loadable
// segments of a program header table. This is done by creating a
// private anonymous mmap() with PROT_NONE.
bool ElfReader::ReserveAddressSpace(address_space_params* address_space) {
ElfW(Addr) min_vaddr;
load_size_ = phdr_table_get_load_size(phdr_table_, phdr_num_, &min_vaddr);
if (load_size_ == 0) {
DL_ERR("\"%s\" has no loadable segments", name_.c_str());
return false;
}
uint8_t* addr = reinterpret_cast<uint8_t*>(min_vaddr);
void* start;
if (load_size_ > address_space->reserved_size) {
if (address_space->must_use_address) {
DL_ERR("reserved address space %zd smaller than %zd bytes needed for \"%s\"",
load_size_ - address_space->reserved_size, load_size_, name_.c_str());
return false;
}
start = ReserveAligned(load_size_, kLibraryAlignment);
if (start == nullptr) {
DL_ERR("couldn't reserve %zd bytes of address space for \"%s\"", load_size_, name_.c_str());
return false;
}
} else {
start = address_space->start_addr;
mapped_by_caller_ = true;
// Update the reserved address space to subtract the space used by this library.
address_space->start_addr = reinterpret_cast<uint8_t*>(address_space->start_addr) + load_size_;
address_space->reserved_size -= load_size_;
}
load_start_ = start;
load_bias_ = reinterpret_cast<uint8_t*>(start) - addr;
return true;
}
bionic/linker/linker_phdr.cpp
bool ElfReader::LoadSegments() {
for (size_t i = 0; i < phdr_num_; ++i) {
const ElfW(Phdr)* phdr = &phdr_table_[i];
if (phdr->p_type != PT_LOAD) {
continue;
}
// Segment addresses in memory.
ElfW(Addr) seg_start = phdr->p_vaddr + load_bias_;//段起始地址
ElfW(Addr) seg_end = seg_start + phdr->p_memsz;//段内存的结束地址
ElfW(Addr) seg_page_start = PAGE_START(seg_start);//段所在页对齐基址
ElfW(Addr) seg_page_end = PAGE_END(seg_end);//段所在页的下一页对齐基址
ElfW(Addr) seg_file_end = seg_start + phdr->p_filesz;//段文件结束地址
// File offsets.
ElfW(Addr) file_start = phdr->p_offset;//段文件偏移
ElfW(Addr) file_end = file_start + phdr->p_filesz;//段结束的文件偏移
ElfW(Addr) file_page_start = PAGE_START(file_start);//段文件偏移的页对齐基址
ElfW(Addr) file_length = file_end - file_page_start;//段文件大小
if (file_size_ <= 0) {
DL_ERR("\"%s\" invalid file size: %" PRId64, name_.c_str(), file_size_);
return false;
}
if (file_end > static_cast<size_t>(file_size_)) {
DL_ERR("invalid ELF file \"%s\" load segment[%zd]:"
" p_offset (%p) + p_filesz (%p) ( = %p) past end of file (0x%" PRIx64 ")",
name_.c_str(), i, reinterpret_cast<void*>(phdr->p_offset),
reinterpret_cast<void*>(phdr->p_filesz),
reinterpret_cast<void*>(file_end), file_size_);
return false;
}
if (file_length != 0) {//段文件大小不为0
int prot = PFLAGS_TO_PROT(phdr->p_flags);
if ((prot & (PROT_EXEC | PROT_WRITE)) == (PROT_EXEC | PROT_WRITE)) {
// W + E PT_LOAD segments are not allowed in O.
if (get_application_target_sdk_version() >= __ANDROID_API_O__) {
DL_ERR_AND_LOG("\"%s\": W+E load segments are not allowed", name_.c_str());
return false;
}
DL_WARN_documented_change(__ANDROID_API_O__,
"writable-and-executable-segments-enforced-for-api-level-26",
"\"%s\" has load segments that are both writable and executable",
name_.c_str());
add_dlwarning(name_.c_str(), "W+E load segments");
}
//映射段文件区
void* seg_addr = mmap64(reinterpret_cast<void*>(seg_page_start),
file_length,
prot,
MAP_FIXED|MAP_PRIVATE,
fd_,
file_offset_ + file_page_start);
if (seg_addr == MAP_FAILED) {
DL_ERR("couldn't map \"%s\" segment %zd: %s", name_.c_str(), i, strerror(errno));
return false;
}
}
// if the segment is writable, and does not end on a page boundary,
// zero-fill it until the page limit.
//如果段的属性可写且段文件结束地址不是页对齐,则在后面补上0直到页对齐
if ((phdr->p_flags & PF_W) != 0 && PAGE_OFFSET(seg_file_end) > 0) {
memset(reinterpret_cast<void*>(seg_file_end), 0, PAGE_SIZE - PAGE_OFFSET(seg_file_end));
}
//更新段文件结束地址到页对齐
seg_file_end = PAGE_END(seg_file_end);
// seg_file_end is now the first page address after the file
// content. If seg_end is larger, we need to zero anything
// between them. This is done by using a private anonymous
// map for all extra pages.
//某些段的p_filesz为0但是p_memsz大于0(这些段的 sh_type是SHT_NOBITS,表示这些段在文件不占空间但是装载时会占空间,例如.bss节对应的段),在映射的时候,需要映射成匿名内存
if (seg_page_end > seg_file_end) {
size_t zeromap_size = seg_page_end - seg_file_end;
void* zeromap = mmap(reinterpret_cast<void*>(seg_file_end),
zeromap_size,
PFLAGS_TO_PROT(phdr->p_flags),
MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE,
-1,
0);
if (zeromap == MAP_FAILED) {
DL_ERR("couldn't zero fill \"%s\" gap: %s", name_.c_str(), strerror(errno));
return false;
}
//设置匿名内存名称为.bss
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, zeromap, zeromap_size, ".bss");
}
}
return true;
}
最后一步FindPhdr是查找PT_PHDR类型的段,并记录在loaded_phdr_。首先是通过程序头表查找此段,查找不到再在elf文件头的e_phoff属性确定此段的位置。
bionic/linker/linker_phdr.cpp
// Sets loaded_phdr_ to the address of the program header table as it appears
// in the loaded segments in memory. This is in contrast with phdr_table_,
// which is temporary and will be released before the library is relocated.
bool ElfReader::FindPhdr() {
const ElfW(Phdr)* phdr_limit = phdr_table_ + phdr_num_;
// If there is a PT_PHDR, use it directly.
for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
if (phdr->p_type == PT_PHDR) {
return CheckPhdr(load_bias_ + phdr->p_vaddr);
}
}
// Otherwise, check the first loadable segment. If its file offset
// is 0, it starts with the ELF header, and we can trivially find the
// loaded program header from it.
for (const ElfW(Phdr)* phdr = phdr_table_; phdr < phdr_limit; ++phdr) {
if (phdr->p_type == PT_LOAD) {
if (phdr->p_offset == 0) {
ElfW(Addr) elf_addr = load_bias_ + phdr->p_vaddr;
const ElfW(Ehdr)* ehdr = reinterpret_cast<const ElfW(Ehdr)*>(elf_addr);
ElfW(Addr) offset = ehdr->e_phoff;
return CheckPhdr(reinterpret_cast<ElfW(Addr)>(ehdr) + offset);
}
break;
}
}
DL_ERR("can't find loaded phdr for \"%s\"", name_.c_str());
return false;
}
ElfOatFile的Load核心逻辑由art内部实现,不需要系统调用dlopen。核心步骤分两步:
1.ElfFile::Open加载elf文件到内存,方便读取elf文件的信息;
2. ElfFileImpl::Load映射PT_LOAD段到预留内存。
过程和DlOpenOatFile的差不多,不再详述。
art/runtime/oat_file.cc
bool ElfOatFile::ElfFileOpen(File* file,
bool writable,
bool executable,
bool low_4gb,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg) {
ScopedTrace trace(__PRETTY_FUNCTION__);
elf_file_.reset(ElfFile::Open(file,
writable,
/*program_header_only=*/ true,
low_4gb,
error_msg));
if (elf_file_ == nullptr) {
DCHECK(!error_msg->empty());
return false;
}
bool loaded = elf_file_->Load(file, executable, low_4gb, reservation, error_msg);
DCHECK(loaded || !error_msg->empty());
return loaded;
}
art/runtime/elf_file.cc
template <typename ElfTypes>
bool ElfFileImpl<ElfTypes>::Load(File* file,
bool executable,
bool low_4gb,
/*inout*/MemMap* reservation,
/*out*/std::string* error_msg) {
...
if (!reserved) {
uint8_t* vaddr_begin;
size_t vaddr_size;
if (!GetLoadedAddressRange(&vaddr_begin, &vaddr_size, error_msg)) {
DCHECK(!error_msg->empty());
return false;
}
std::string reservation_name = "ElfFile reservation for " + file->GetPath();
MemMap local_reservation = MemMap::MapAnonymous(
reservation_name.c_str(),
(reservation != nullptr) ? reservation->Begin() : nullptr,
vaddr_size,
PROT_NONE,
low_4gb,
/* reuse= */ false,
reservation,
error_msg);
if (!local_reservation.IsValid()) {
*error_msg = StringPrintf("Failed to allocate %s: %s",
reservation_name.c_str(),
error_msg->c_str());
return false;
}
reserved = true;
...
if (program_header->p_filesz != 0u) {
MemMap segment =
MemMap::MapFileAtAddress(p_vaddr,
program_header->p_filesz,
prot,
flags,
file->Fd(),
program_header->p_offset,
/* low_4gb= */ false,
file->GetPath().c_str(),
/* reuse= */ true, // implies MAP_FIXED
/* reservation= */ nullptr,
error_msg);
if (!segment.IsValid()) {
*error_msg = StringPrintf("Failed to map ELF file segment %d from %s: %s",
i, file->GetPath().c_str(), error_msg->c_str());
return false;
}
if (segment.Begin() != p_vaddr) {
*error_msg = StringPrintf("Failed to map ELF file segment %d from %s at expected address %p, "
"instead mapped to %p",
i, file->GetPath().c_str(), p_vaddr, segment.Begin());
return false;
}
segments_.push_back(std::move(segment));
}
if (program_header->p_filesz < program_header->p_memsz) {
std::string name = StringPrintf("Zero-initialized segment %" PRIu64 " of ELF file %s",
static_cast<uint64_t>(i), file->GetPath().c_str());
MemMap segment = MemMap::MapAnonymous(name.c_str(),
p_vaddr + program_header->p_filesz,
program_header->p_memsz - program_header->p_filesz,
prot,
/* low_4gb= */ false,
/* reuse= */ true,
/* reservation= */ nullptr,
error_msg);
if (!segment.IsValid()) {
*error_msg = StringPrintf("Failed to map zero-initialized ELF file segment %d from %s: %s",
i, file->GetPath().c_str(), error_msg->c_str());
return false;
}
if (segment.Begin() != p_vaddr) {
*error_msg = StringPrintf("Failed to map zero-initialized ELF file segment %d from %s "
"at expected address %p, instead mapped to %p",
i, file->GetPath().c_str(), p_vaddr, segment.Begin());
return false;
}
segments_.push_back(std::move(segment));
}
}
ComputeFields
ComputeFields会获取oat文件的各个动态符号在内存中的地址,保存在成员变量中。
art/runtime/oat_file.cc
bool OatFileBase::ComputeFields(const std::string& file_path, std::string* error_msg) {
std::string symbol_error_msg;
begin_ = FindDynamicSymbolAddress("oatdata", &symbol_error_msg);
if (begin_ == nullptr) {
*error_msg = StringPrintf("Failed to find oatdata symbol in '%s' %s",
file_path.c_str(),
symbol_error_msg.c_str());
return false;
}
end_ = FindDynamicSymbolAddress("oatlastword", &symbol_error_msg);
if (end_ == nullptr) {
*error_msg = StringPrintf("Failed to find oatlastword symbol in '%s' %s",
file_path.c_str(),
symbol_error_msg.c_str());
return false;
}
// Readjust to be non-inclusive upper bound.
end_ += sizeof(uint32_t);
data_bimg_rel_ro_begin_ = FindDynamicSymbolAddress("oatdatabimgrelro", &symbol_error_msg);
if (data_bimg_rel_ro_begin_ != nullptr) {
data_bimg_rel_ro_end_ =
FindDynamicSymbolAddress("oatdatabimgrelrolastword", &symbol_error_msg);
if (data_bimg_rel_ro_end_ == nullptr) {
*error_msg =
StringPrintf("Failed to find oatdatabimgrelrolastword symbol in '%s'", file_path.c_str());
return false;
}
// Readjust to be non-inclusive upper bound.
data_bimg_rel_ro_end_ += sizeof(uint32_t);
}
bss_begin_ = const_cast<uint8_t*>(FindDynamicSymbolAddress("oatbss", &symbol_error_msg));
if (bss_begin_ == nullptr) {
// No .bss section.
bss_end_ = nullptr;
} else {
bss_end_ = const_cast<uint8_t*>(FindDynamicSymbolAddress("oatbsslastword", &symbol_error_msg));
if (bss_end_ == nullptr) {
*error_msg = StringPrintf("Failed to find oatbsslastword symbol in '%s'", file_path.c_str());
return false;
}
// Readjust to be non-inclusive upper bound.
bss_end_ += sizeof(uint32_t);
// Find bss methods if present.
bss_methods_ =
const_cast<uint8_t*>(FindDynamicSymbolAddress("oatbssmethods", &symbol_error_msg));
// Find bss roots if present.
bss_roots_ = const_cast<uint8_t*>(FindDynamicSymbolAddress("oatbssroots", &symbol_error_msg));
}
vdex_begin_ = const_cast<uint8_t*>(FindDynamicSymbolAddress("oatdex", &symbol_error_msg));
if (vdex_begin_ == nullptr) {
// No .vdex section.
vdex_end_ = nullptr;
} else {
vdex_end_ = const_cast<uint8_t*>(FindDynamicSymbolAddress("oatdexlastword", &symbol_error_msg));
if (vdex_end_ == nullptr) {
*error_msg = StringPrintf("Failed to find oatdexlastword symbol in '%s'", file_path.c_str());
return false;
}
// Readjust to be non-inclusive upper bound.
vdex_end_ += sizeof(uint32_t);
}
return true;
}
PreSetup
PreSetup的主要作用是创建多个Dummy MemMap来跟踪每个加载的PT_LOAD段。
art/runtime/oat_file.cc
void DlOpenOatFile::PreSetup(const std::string& elf_filename) {
#ifdef __APPLE__
UNUSED(elf_filename);
LOG(FATAL) << "Should not reach here.";
UNREACHABLE();
#else
struct dl_iterate_context {
static int callback(dl_phdr_info* info, size_t size ATTRIBUTE_UNUSED, void* data) {
auto* context = reinterpret_cast<dl_iterate_context*>(data);
static_assert(std::is_same<Elf32_Half, Elf64_Half>::value, "Half must match");
using Elf_Half = Elf64_Half;
context->shared_objects_seen++;
if (context->shared_objects_seen < context->shared_objects_before) {
// We haven't been called yet for anything we haven't seen before. Just continue.
// Note: this is aggressively optimistic. If another thread was unloading a library,
// we may miss out here. However, this does not happen often in practice.
return 0;
}
// See whether this callback corresponds to the file which we have just loaded.
bool contains_begin = false;
for (Elf_Half 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 (Elf_Half 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::move(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<MemMap>* const dlopen_mmaps_;
const size_t shared_objects_before;
size_t shared_objects_seen;
};
dl_iterate_context context = { Begin(), &dlopen_mmaps_, shared_objects_before_, 0};
if (dl_iterate_phdr(dl_iterate_context::callback, &context) == 0) {
// Hm. Maybe our optimization went wrong. Try another time with shared_objects_before == 0
// before giving up. This should be unusual.
VLOG(oat) << "Need a second run in PreSetup, didn't find with shared_objects_before="
<< shared_objects_before_;
dl_iterate_context context0 = { Begin(), &dlopen_mmaps_, 0, 0};
if (dl_iterate_phdr(dl_iterate_context::callback, &context0) == 0) {
// OK, give up and print an error.
PrintFileToLog("/proc/self/maps", android::base::LogSeverity::WARNING);
LOG(ERROR) << "File " << elf_filename << " loaded with dlopen but cannot find its mmaps.";
}
}
#endif
}
LoadVdex
LoadVdex主要是把对应的.vdex文件加载到oat文件的.dex节所在地址中,不作单独介绍,加载完.vdex文件后,会把加载的基址记录在vdex_中。
Setup
Setup的作用是在.vdex文件加载到内存后,读取oat文件中记录的OatDexFile的二进制信息(dexoat时记录的OatDexFile),计算出该oat文件对应的dex文件在内存中的位置,构建出一个启动运行时的OatDexFile。dexoat时的OatDexFile和运行时的OatDexFile不是同一概念,dexoat时的OatDexFile只记录了一些重要部分的偏移量,运行时的OatDexFile记录了这些重要部分的指针,因为这些指针需要运行时才能最终确定。
art/runtime/oat_file.cc
bool OatFileBase::Setup(int zip_fd, 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;
}
PointerSize pointer_size = GetInstructionSetPointerSize(GetOatHeader().GetInstructionSet());
size_t key_value_store_size =
(Size() >= sizeof(OatHeader)) ? GetOatHeader().GetKeyValueStoreSize() : 0u;
if (Size() < sizeof(OatHeader) + key_value_store_size) {
*error_msg = StringPrintf("In oat file '%s' found truncated OatHeader, "
"size = %zu < %zu + %zu",
GetLocation().c_str(),
Size(),
sizeof(OatHeader),
key_value_store_size);
return false;
}
size_t oat_dex_files_offset = GetOatHeader().GetOatDexFilesOffset();
if (oat_dex_files_offset < GetOatHeader().GetHeaderSize() || oat_dex_files_offset > Size()) {
*error_msg = StringPrintf("In oat file '%s' found invalid oat dex files offset: "
"%zu is not in [%zu, %zu]",
GetLocation().c_str(),
oat_dex_files_offset,
GetOatHeader().GetHeaderSize(),
Size());
return false;
}
//索引到dexoat时的OatDexFile记录位置
const uint8_t* oat = Begin() + oat_dex_files_offset; // Jump to the OatDexFile records.
if (!IsAligned<sizeof(uint32_t)>(data_bimg_rel_ro_begin_) ||
!IsAligned<sizeof(uint32_t)>(data_bimg_rel_ro_end_) ||
data_bimg_rel_ro_begin_ > data_bimg_rel_ro_end_) {
*error_msg = StringPrintf("In oat file '%s' found unaligned or unordered databimgrelro "
"symbol(s): begin = %p, end = %p",
GetLocation().c_str(),
data_bimg_rel_ro_begin_,
data_bimg_rel_ro_end_);
return false;
}
DCHECK_GE(static_cast<size_t>(pointer_size), alignof(GcRoot<mirror::Object>));
if (!IsAligned<kPageSize>(bss_begin_) ||
!IsAlignedParam(bss_methods_, static_cast<size_t>(pointer_size)) ||
!IsAlignedParam(bss_roots_, static_cast<size_t>(pointer_size)) ||
!IsAligned<alignof(GcRoot<mirror::Object>)>(bss_end_)) {
*error_msg = StringPrintf("In oat file '%s' found unaligned bss symbol(s): "
"begin = %p, methods_ = %p, roots = %p, end = %p",
GetLocation().c_str(),
bss_begin_,
bss_methods_,
bss_roots_,
bss_end_);
return false;
}
if ((bss_methods_ != nullptr && (bss_methods_ < bss_begin_ || bss_methods_ > bss_end_)) ||
(bss_roots_ != nullptr && (bss_roots_ < bss_begin_ || bss_roots_ > bss_end_)) ||
(bss_methods_ != nullptr && bss_roots_ != nullptr && bss_methods_ > bss_roots_)) {
*error_msg = StringPrintf("In oat file '%s' found bss symbol(s) outside .bss or unordered: "
"begin = %p, methods = %p, roots = %p, end = %p",
GetLocation().c_str(),
bss_begin_,
bss_methods_,
bss_roots_,
bss_end_);
return false;
}
if (bss_methods_ != nullptr && bss_methods_ != bss_begin_) {
*error_msg = StringPrintf("In oat file '%s' found unexpected .bss gap before 'oatbssmethods': "
"begin = %p, methods = %p",
GetLocation().c_str(),
bss_begin_,
bss_methods_);
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;
if (UNLIKELY(!ReadOatDexFileData(*this, &oat, &dex_file_location_size))) {//读取dex文件路径字符串大小
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu truncated after dex file "
"location size",
GetLocation().c_str(),
i);
return false;
}
if (UNLIKELY(dex_file_location_size == 0U)) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu with empty location name",
GetLocation().c_str(),
i);
return false;
}
if (UNLIKELY(static_cast<size_t>(End() - oat) < dex_file_location_size)) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu with truncated dex file "
"location",
GetLocation().c_str(),
i);
return false;
}
const char* dex_file_location_data = reinterpret_cast<const char*>(oat);//读取dex文件路径
oat += dex_file_location_size;
// Location encoded in the oat file. We will use this for multidex naming,
// see ResolveRelativeEncodedDexLocation.
std::string oat_dex_file_location(dex_file_location_data, dex_file_location_size);
// If `oat_dex_file_location` is relative (so that the oat file can be moved to
// a different folder), resolve to absolute location. Also resolve the file name
// in case dex files need to be opened from disk. The file name and location
// differ when cross-compiling on host for target.
std::string dex_file_name;
std::string dex_file_location;
ResolveRelativeEncodedDexLocation(abs_dex_location,
oat_dex_file_location,
&dex_file_location,
&dex_file_name);
uint32_t dex_file_checksum;
if (UNLIKELY(!ReadOatDexFileData(*this, &oat, &dex_file_checksum))) {//读取dex文件的checksum
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' truncated after "
"dex file checksum",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
uint32_t dex_file_offset;
if (UNLIKELY(!ReadOatDexFileData(*this, &oat, &dex_file_offset))) {//读取dex文件在vdex文件中的偏移
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' truncated "
"after dex file offsets",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
if (UNLIKELY(dex_file_offset > DexSize())) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file "
"offset %u > %zu",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
dex_file_offset,
DexSize());
return false;
}
const uint8_t* dex_file_pointer = nullptr;
if (UNLIKELY(dex_file_offset == 0U)) {
if (uncompressed_dex_files_ == nullptr) {
// Do not support mixed-mode oat files.
if (i > 0) {
*error_msg = StringPrintf("In oat file '%s', unsupported uncompressed-dex-file for dex "
"file %zu (%s)",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
uncompressed_dex_files_.reset(new std::vector<std::unique_ptr<const DexFile>>());
// No dex files, load it from location.
const ArtDexFileLoader dex_file_loader;
bool loaded = false;
if (zip_fd != -1) {
loaded = dex_file_loader.OpenZip(zip_fd,
dex_file_location,
/*verify=*/ false,
/*verify_checksum=*/ false,
error_msg,
uncompressed_dex_files_.get());
} else {
loaded = dex_file_loader.Open(dex_file_name.c_str(),
dex_file_location,
/*verify=*/ false,
/*verify_checksum=*/ false,
error_msg,
uncompressed_dex_files_.get());
}
if (!loaded) {
if (Runtime::Current() == nullptr) {
// If there's no runtime, we're running oatdump, so return
// a half constructed oat file that oatdump knows how to deal with.
LOG(WARNING) << "Could not find associated dex files of oat file. "
<< "Oatdump will only dump the header.";
return true;
} else {
return false;
}
}
// The oat file may be out of date wrt/ the dex-file location. We need to be defensive
// here and ensure that at least the number of dex files still matches.
// Note: actual checksum comparisons are the duty of the OatFileAssistant and will be
// done after loading the OatFile.
if (uncompressed_dex_files_->size() != dex_file_count) {
*error_msg = StringPrintf("In oat file '%s', expected %u uncompressed dex files, but "
"found %zu in '%s'",
GetLocation().c_str(),
dex_file_count,
uncompressed_dex_files_->size(),
dex_file_location.c_str());
return false;
}
}
dex_file_pointer = (*uncompressed_dex_files_)[i]->Begin();
} else {
// Do not support mixed-mode oat files.
if (uncompressed_dex_files_ != nullptr) {
*error_msg = StringPrintf("In oat file '%s', unsupported embedded dex-file for dex file "
"%zu (%s)",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
if (UNLIKELY(DexSize() - dex_file_offset < sizeof(DexFile::Header))) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file "
"offset %u of %zu but the size of dex file header is %zu",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
dex_file_offset,
DexSize(),
sizeof(DexFile::Header));
return false;
}
dex_file_pointer = DexBegin() + dex_file_offset;
}
const bool valid_magic = DexFileLoader::IsMagicValid(dex_file_pointer);
if (UNLIKELY(!valid_magic)) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with invalid "
"dex file magic '%s'",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
dex_file_pointer);
return false;
}
if (UNLIKELY(!DexFileLoader::IsVersionAndMagicValid(dex_file_pointer))) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu 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);
if (dex_file_offset != 0 && (DexSize() - dex_file_offset < header->file_size_)) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with dex file "
"offset %u and size %u truncated at %zu",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
dex_file_offset,
header->file_size_,
DexSize());
return false;
}
uint32_t class_offsets_offset;
if (UNLIKELY(!ReadOatDexFileData(*this, &oat, &class_offsets_offset))) {/读取class_defs字段偏移
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' truncated "
"after class offsets offset",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
if (UNLIKELY(class_offsets_offset > Size()) ||
UNLIKELY((Size() - class_offsets_offset) / sizeof(uint32_t) < header->class_defs_size_)) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with truncated "
"class offsets, offset %u of %zu, class defs %u",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
class_offsets_offset,
Size(),
header->class_defs_size_);
return false;
}
if (UNLIKELY(!IsAligned<alignof(uint32_t)>(class_offsets_offset))) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with unaligned "
"class offsets, offset %u",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
class_offsets_offset);
return false;
}
const uint32_t* class_offsets_pointer =
reinterpret_cast<const uint32_t*>(Begin() + class_offsets_offset);
uint32_t lookup_table_offset;
if (UNLIKELY(!ReadOatDexFileData(*this, &oat, &lookup_table_offset))) {//读取类查找表偏移
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' truncated "
"after lookup table offset",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
const uint8_t* lookup_table_data = lookup_table_offset != 0u
? Begin() + lookup_table_offset
: nullptr;
if (lookup_table_offset != 0u &&
(UNLIKELY(lookup_table_offset > Size()) ||
UNLIKELY(Size() - lookup_table_offset <
TypeLookupTable::RawDataLength(header->class_defs_size_)))) {
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zu for '%s' with truncated "
"type lookup table, offset %u of %zu, class defs %u",
GetLocation().c_str(),
i,
dex_file_location.c_str(),
lookup_table_offset,
Size(),
header->class_defs_size_);
return false;
}
uint32_t dex_layout_sections_offset;
if (UNLIKELY(!ReadOatDexFileData(*this, &oat, &dex_layout_sections_offset))) {//读取dex layout偏移
*error_msg = StringPrintf("In oat file '%s' found OatDexFile #%zd for '%s' truncated "
"after dex layout sections offset",
GetLocation().c_str(),
i,
dex_file_location.c_str());
return false;
}
const DexLayoutSections* const dex_layout_sections = dex_layout_sections_offset != 0
? reinterpret_cast<const DexLayoutSections*>(Begin() + dex_layout_sections_offset)
: nullptr;
const IndexBssMapping* method_bss_mapping;
const IndexBssMapping* type_bss_mapping;
const IndexBssMapping* string_bss_mapping;
//读取bss method,bss type,bss string映射表的内容
if (!ReadIndexBssMapping(
this, &oat, i, dex_file_location, "method", &method_bss_mapping, error_msg) ||
!ReadIndexBssMapping(
this, &oat, i, dex_file_location, "type", &type_bss_mapping, error_msg) ||
!ReadIndexBssMapping(
this, &oat, i, dex_file_location, "string", &string_bss_mapping, error_msg)) {
return false;
}
// Create the OatDexFile and add it to the owning container.
//整合信息,构建一个运行时的OatDexFile
OatDexFile* oat_dex_file = new OatDexFile(
this,
dex_file_location,
DexFileLoader::GetDexCanonicalLocation(dex_file_name.c_str()),
dex_file_checksum,
dex_file_pointer,
lookup_table_data,
method_bss_mapping,
type_bss_mapping,
string_bss_mapping,
class_offsets_pointer,
dex_layout_sections);
oat_dex_files_storage_.push_back(oat_dex_file);
// Add the location and canonical location (if different) to the oat_dex_files_ table.
// Note: we use the dex_file_location_data storage for the view, as oat_dex_file_location
// is just a temporary string.
std::string_view key(dex_file_location_data, dex_file_location_size);
std::string_view canonical_key(oat_dex_file->GetCanonicalDexFileLocation());
oat_dex_files_.Put(key, oat_dex_file);
if (canonical_key != key) {
oat_dex_files_.Put(canonical_key, oat_dex_file);
}
}
Runtime* runtime = Runtime::Current();
if (DataBimgRelRoBegin() != nullptr) {
// Make .data.bimg.rel.ro read only. ClassLinker shall make it writable for relocation.
uint8_t* reloc_begin = const_cast<uint8_t*>(DataBimgRelRoBegin());
CheckedCall(mprotect, "protect relocations", reloc_begin, DataBimgRelRoSize(), PROT_READ);//将.data.bimg.rel.so的属性置为可写
if (UNLIKELY(runtime == nullptr)) {
// This must be oatdump without boot image.
} else if (!IsExecutable()) {
// Do not check whether we have a boot image if the oat file is not executable.
} else if (UNLIKELY(runtime->GetHeap()->GetBootImageSpaces().empty())) {
*error_msg = StringPrintf("Cannot load oat file '%s' with .data.bimg.rel.ro as executable "
"without boot image.",
GetLocation().c_str());
return false;
} else {
// ClassLinker shall perform the relocation when we register a dex file from
// this oat file. We do not do the relocation here to avoid dirtying the pages
// if the code is never actually ready to be executed.
}
}
return true;
}
Oat文件的OatDexFile的记录位置偏移被记录在oat头的的24字节偏移处,oat头加上OatDexFile的记录位置偏移便可以读取到dexoat的OatDexFile的二进制内容。如下图,偏移记录是0xb724e,所以OatDexFile的记录在文件的0xb724e+0x1000(oatdata符号偏移)=0xb824e偏移处。
新增节.data.bimg.rel.ro, .bss,.dex
同以往相比,oat文件多了几个节:.data.bimg.rel.ro, .bss,.dex。.dex和 .bss的类型都是NOBITS,说明它们在oat文件中不占空间,在程序运行时才会占用内存,占用的内存的起址偏移和大小都是在elf文件中被记录的。而.data.bimg.rel.ro的类型是PROGBITS,在文件和内存中都会占用相同的大小。
.dex的作用前面已经提过,mmap出来的内存会用来加载vdex文件。这段内存dump出来就是vdex文件。
.data.bimg.rel.ro用来记录要访问的对象在boot image space的偏移。在编译oat文件时,针对每一个类/字符串/方法,如果可以在boot*.art找到,就可以被记录在.data.bimg.rel.ro节中。.data.bimg.rel.ro节中每4个字节记录一个基于boot image偏移值,在程序运行后,.data.bimg.rel.ro节所在内存会被修改,这些偏移量会被重定位为原偏移量加上boot image space的基址的新地址。而oat编译的优化也是基于这个重定位原理进行的,程序运行时.data.bimg.rel.ro段和.text段的偏移是固定的,机器码中是直接通过读取.data.bimg.rel.ro的内容获取到需要的类/字符串/方法的地址。相比重新解析类/字符串/方法,这是一种加快运行速度的优化方案。
.bss 用来记录运行过程中确定下来的类/字符串/方法,分为bss method区,bss type和bss string区。不考虑JIT编译的情况下,在编译oat文件时,针对每一个static/private/super方法调用,如果不能在在boot*.art找到,就会在.bss寻找入口,程序运行时.bss段和.text段的偏移是固定的,使用adrp汇编指令直接跳转就可以。.bss节在映射内存时会被零初始化,在程序初始化时,bss method区会被8字节的指针填充,该指针存放了oat文件中蹦床函数pQuickResolutionTrampoline的入口,如果程序运行到该方法,则会调用pQuickResolutionTrampoline找到真正的函数入口地址然后跳转过去,然后把真正的函数入口地址回写到.bss对应位置,这样下次调用时就会找到对应的入口指针直接跳转过去,这个做法有点像ELF文件加载时的延迟绑定技术。对于bss type和bss string区,这两个区没有预先填充内容,需要解析类或者字符串的时候,机器码会生成pResolveType和pResolveString的入口代码,分别在artResolveStringFromCode和artResolveTypeFromCode的过程中回写,写入的内容为4字节的GcRoot对象(简单理解就是指针),因为是指向低地址的dalvik-main space中分配的Class和String对象,4字节足够。这两个区用来快速索引运行时解析出来的对象。
oat文件节信息
共有 12 个节头,从偏移量 0x364da0 开始:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .rodata PROGBITS 0000000000001000 00001000
0000000000095000 0000000000000000 A 0 0 4096
[ 2] .text PROGBITS 0000000000096000 00096000
00000000002a1ce0 0000000000000000 AX 0 0 4096
[ 3] .data.bimg.rel.ro PROGBITS 0000000000338000 00338000
00000000000019d8 0000000000000000 A 0 0 4096
[ 4] .bss NOBITS 000000000033a000 00000000
0000000000003e1c 0000000000000000 A 0 0 4096
[ 5] .dex NOBITS 000000000033e000 00000000
00000000001c00b4 0000000000000000 A 0 0 4096
[ 6] .dynstr STRTAB 00000000004ff000 0033a000
0000000000000097 0000000000000000 A 0 0 4096
[ 7] .dynsym DYNSYM 00000000004ff098 0033a098
0000000000000120 0000000000000018 A 6 1 8
[ 8] .hash HASH 00000000004ff1b8 0033a1b8
000000000000003c 0000000000000004 A 7 0 4
[ 9] .dynamic DYNAMIC 0000000000500000 0033b000
0000000000000070 0000000000000010 A 6 0 4096
[10] .gnu_debugdata PROGBITS 0000000000000000 0033c000
0000000000028d3c 0000000000000000 0 0 4096
[11] .shstrtab STRTAB 0000000000000000 00364d3c
0000000000000063 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)