// Force the OAT method layout to be sorted-by-name instead of // the default (class_def_idx, method_idx). // // Otherwise if profiles are used, that will act as // the primary sort order. // // A bit easier to use for development since oatdump can easily // show that things are being re-ordered when two methods aren't adjacent. static constexpr bool kOatWriterForceOatCodeLayout = false;
// OatClassHeader is the header only part of the oat class that is required even when compilation // is not enabled. class OatWriter::OatClassHeader { public:
OatClassHeader(uint32_t offset,
uint32_t num_non_null_compiled_methods,
uint32_t num_methods,
ClassStatus status)
: status_(enum_cast<uint16_t>(status)),
offset_(offset) { // We just arbitrarily say that 0 methods means OatClassType::kNoneCompiled and that we won't // use OatClassType::kAllCompiled unless there is at least one compiled method. This means in // an interpreter only system, we can assert that all classes are OatClassType::kNoneCompiled. if (num_non_null_compiled_methods == 0) {
type_ = enum_cast<uint16_t>(OatClassType::kNoneCompiled);
} elseif (num_non_null_compiled_methods == num_methods) {
type_ = enum_cast<uint16_t>(OatClassType::kAllCompiled);
} else {
type_ = enum_cast<uint16_t>(OatClassType::kSomeCompiled);
}
}
// Data to write.
static_assert(sizeof(ClassStatus) <= sizeof(uint16_t), "class status won't fit in 16bits");
uint16_t status_;
static_assert(sizeof(OatClassType) <= sizeof(uint16_t), "oat_class type won't fit in 16bits");
uint16_t type_;
// Offset of start of OatClass from beginning of OatHeader. It is // used to validate file position when writing.
uint32_t offset_;
};
// The actual oat class body contains the information about compiled methods. It is only required // for compiler filters that have any compilation. class OatWriter::OatClass { public:
OatClass(const dchecked_vector<CompiledMethod*>& compiled_methods,
uint32_t compiled_methods_with_code,
uint16_t oat_class_type);
OatClass(OatClass&& src) = default;
size_t SizeOf() const; bool Write(OatWriter* oat_writer, OutputStream* out) const;
// CompiledMethods for each class_def_method_index, or null if no method is available.
dchecked_vector<CompiledMethod*> compiled_methods_;
// Offset from OatClass::offset_ to the OatMethodOffsets for the // class_def_method_index. If 0, it means the corresponding // CompiledMethod entry in OatClass::compiled_methods_ should be // null and that the OatClass::type_ should be OatClassType::kSomeCompiled.
dchecked_vector<uint32_t> oat_method_offsets_offsets_from_oat_class_;
// Data to write.
// Number of methods recorded in OatClass. For `OatClassType::kNoneCompiled` // this shall be zero and shall not be written to the file, otherwise it // shall be the number of methods in the class definition. It is used to // determine the size of `BitVector` data for `OatClassType::kSomeCompiled` and // the size of the `OatMethodOffsets` table for `OatClassType::kAllCompiled`. // (The size of the `OatMethodOffsets` table for `OatClassType::kSomeCompiled` // is determined by the number of bits set in the `BitVector` data.)
uint32_t num_methods_;
// Bit vector indexed by ClassDef method index. When OatClass::type_ is // OatClassType::kSomeCompiled, a set bit indicates the method has an // OatMethodOffsets in methods_offsets_, otherwise // the entry was omitted to save space. If OatClass::type_ is // not is OatClassType::kSomeCompiled, the bitmap will be null.
std::unique_ptr<BitVector> method_bitmap_;
// OatMethodOffsets and OatMethodHeaders for each CompiledMethod // present in the OatClass. Note that some may be missing if // OatClass::compiled_methods_ contains null values (and // oat_method_offsets_offsets_from_oat_class_ should contain 0 // values in this case).
dchecked_vector<OatMethodOffsets> method_offsets_;
dchecked_vector<OatQuickMethodHeader> method_headers_;
// A value of -1 denotes missing debug info static constexpr size_t kDebugInfoIdxInvalid = static_cast<size_t>(-1); // Index into writer_->method_info_
size_t debug_info_idx;
// Bin each method according to the profile flags. // // Groups by e.g. // -- startup and hot and poststartup // -- startup and hot // -- startup and post-startup // -- startup // -- hot and post-startup // -- hot // -- post-startup // -- not hot at all // // (See MethodHotness enum definition for up-to-date binning order.) booloperator<(const OrderedMethodData& other) const { if (kOatWriterForceOatCodeLayout) { // Development flag: Override default behavior by sorting by name.
std::string name = method_reference.PrettyMethod();
std::string other_name = other.method_reference.PrettyMethod(); return name < other_name;
}
// Use the profile's method hotness to determine sort order, with startup // methods appearing first. if (hotness_bits > other.hotness_bits) { returntrue;
}
// Default: retain the original order. returnfalse;
}
};
// Add dex file source(s) from a file specified by a file handle. // Note: The `dex_file_fd` specifies a plain dex file or a zip file. bool OatWriter::AddDexFileSource(File&& dex_file_fd, constchar* location) {
DCHECK(write_state_ == WriteState::kAddingDexFileSources);
std::string error_msg;
ArtDexFileLoader loader(&dex_file_fd, location);
std::vector<std::unique_ptr<const DexFile>> dex_files; if (!loader.Open(/*verify=*/false, /*verify_checksum=*/false,
&error_msg,
&dex_files)) {
LOG(ERROR) << "Failed to open dex file '" << location << "': " << error_msg; returnfalse;
} for (auto& dex_file : dex_files) {
oat_dex_files_.emplace_back(std::move(dex_file));
} returntrue;
}
// Add dex file source(s) from a vdex file specified by a file handle. bool OatWriter::AddVdexDexFilesSource(const VdexFile& vdex_file, constchar* location) {
DCHECK(write_state_ == WriteState::kAddingDexFileSources);
DCHECK(vdex_file.HasDexSection()); auto container = std::make_shared<MemoryDexFileContainer>(vdex_file.Begin(), vdex_file.End()); const uint8_t* current_dex_data = nullptr;
size_t i = 0; for (; i < vdex_file.GetNumberOfDexFiles(); ++i) {
current_dex_data = vdex_file.GetNextDexFileData(current_dex_data, i); if (current_dex_data == nullptr) {
LOG(ERROR) << "Unexpected number of dex files in vdex " << location; returnfalse;
}
if (!DexFileLoader::IsMagicValid(current_dex_data)) {
LOG(ERROR) << "Invalid magic in vdex file created from " << location; returnfalse;
} // We used `zipped_dex_file_locations_` to keep the strings in memory.
std::string multidex_location = DexFileLoader::GetMultiDexLocation(location, i); if (!AddRawDexFileSource(container,
current_dex_data,
multidex_location.c_str(),
vdex_file.GetLocationChecksum(i))) { returnfalse;
}
}
if (vdex_file.GetNextDexFileData(current_dex_data, i) != nullptr) {
LOG(ERROR) << "Unexpected number of dex files in vdex " << location; returnfalse;
}
if (oat_dex_files_.empty()) {
LOG(ERROR) << "No dex files in vdex file created from " << location; returnfalse;
} returntrue;
}
// Reserve space for Vdex header, sections, and checksums.
size_vdex_header_ = sizeof(VdexFile::VdexFileHeader) +
VdexSection::kNumberOfSections * sizeof(VdexFile::VdexSectionHeader);
size_vdex_checksums_ = oat_dex_files_.size() * sizeof(VdexFile::VdexChecksum);
vdex_size_ = size_vdex_header_ + size_vdex_checksums_;
// Write DEX files into VDEX, mmap and open them.
std::vector<MemMap> dex_files_map;
std::vector<std::unique_ptr<const DexFile>> dex_files; if (!WriteDexFiles(vdex_file, verify, use_existing_vdex, copy_dex_files, &dex_files_map) ||
!OpenDexFiles(vdex_file, &dex_files_map, &dex_files)) { returnfalse;
}
*opened_dex_files_map = std::move(dex_files_map);
*opened_dex_files = std::move(dex_files); // Create type lookup tables to speed up lookups during compilation.
InitializeTypeLookupTables(*opened_dex_files);
// Flush profile compilation info into the oat dex file metadata.
InitializeDexProfileMetadata(*opened_dex_files);
// The offset is usually advanced for each visited method by the derived class.
size_t offset_;
// The dex file and class def index are set in StartClass(). const DexFile* dex_file_;
size_t class_def_index_;
};
class OatWriter::OatDexMethodVisitor : public DexMethodVisitor { public:
OatDexMethodVisitor(OatWriter* writer, size_t offset)
: DexMethodVisitor(writer, offset),
oat_class_index_(0u),
method_offsets_index_(0u) {}
bool StartClass(const DexFile* dex_file, size_t class_def_index) override {
DexMethodVisitor::StartClass(dex_file, class_def_index); if (kIsDebugBuild && writer_->MayHaveCompiledMethods()) { // There are no oat classes if there aren't any compiled methods.
CHECK_LT(oat_class_index_, writer_->oat_classes_.size());
}
method_offsets_index_ = 0u; returntrue;
}
class OatWriter::InitOatClassesMethodVisitor : public DexMethodVisitor { public:
InitOatClassesMethodVisitor(OatWriter* writer, size_t offset)
: DexMethodVisitor(writer, offset),
compiled_methods_(),
compiled_methods_with_code_(0u) {
size_t num_classes = 0u; for (const OatDexFile& oat_dex_file : writer_->oat_dex_files_) {
num_classes += oat_dex_file.class_offsets_.size();
} // If we aren't compiling only reserve headers.
writer_->oat_class_headers_.reserve(num_classes); if (writer->MayHaveCompiledMethods()) {
writer->oat_classes_.reserve(num_classes);
}
compiled_methods_.reserve(256u); // If there are any classes, the class offsets allocation aligns the offset.
DCHECK(num_classes == 0u || IsAligned<4u>(offset));
}
bool VisitMethod([[maybe_unused]] size_t class_def_method_index, const ClassAccessor::Method& method) override { // Fill in the compiled_methods_ array for methods that have a // CompiledMethod. We track the number of non-null entries in // compiled_methods_with_code_ since we only want to allocate // OatMethodOffsets for the compiled methods.
uint32_t method_idx = method.GetIndex();
CompiledMethod* compiled_method =
writer_->compiler_driver_->GetCompiledMethod(MethodReference(dex_file_, method_idx));
compiled_methods_.push_back(compiled_method); if (HasCompiledCode(compiled_method)) {
++compiled_methods_with_code_;
} returntrue;
}
bool EndClass() override {
ClassReference class_ref(dex_file_, class_def_index_);
ClassStatus status; bool found = writer_->compiler_driver_->GetCompiledClass(class_ref, &status); if (!found) { const VerificationResults* results = writer_->verification_results_; if (results != nullptr && results->IsClassRejected(class_ref)) { // The oat class status is used only for verification of resolved classes, // so use ClassStatus::kErrorResolved whether the class was resolved or unresolved // during compile-time verification.
status = ClassStatus::kErrorResolved;
} else {
status = ClassStatus::kNotReady;
}
} // We never emit kRetryVerificationAtRuntime, instead we mark the class as // resolved and the class will therefore be re-verified at runtime. if (status == ClassStatus::kRetryVerificationAtRuntime) {
status = ClassStatus::kResolved;
}
// Given a queue of CompiledMethod in some total order, // visit each one in that order. class OatWriter::OrderedMethodVisitor { public: explicit OrderedMethodVisitor(ArrayRef<const OrderedMethodData> ordered_methods)
: ordered_methods_(ordered_methods) {
}
virtual ~OrderedMethodVisitor() {}
// Invoke VisitMethod in the order of `ordered_methods`, then invoke VisitComplete. bool Visit() REQUIRES_SHARED(Locks::mutator_lock_) { if (!VisitStart()) { returnfalse;
}
for (const OrderedMethodData& method_data : ordered_methods_) { if (!VisitMethod(method_data)) { returnfalse;
}
}
return VisitComplete();
}
// Invoked once at the beginning, prior to visiting anything else. // // Return false to abort further visiting. virtualbool VisitStart() { returntrue; }
// Invoked repeatedly in the order specified by `ordered_methods`. // // Return false to short-circuit and to stop visiting further methods. virtualbool VisitMethod(const OrderedMethodData& method_data)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
// Invoked once at the end, after every other method has been successfully visited. // // Return false to indicate the overall `Visit` has failed. virtualbool VisitComplete() = 0;
private: // List of compiled methods, sorted by the order defined in OrderedMethodData. // Methods can be inserted more than once in case of duplicated methods.
ArrayRef<const OrderedMethodData> ordered_methods_;
};
// Visit every compiled method in order to determine its order within the OAT file. // Methods from the same class do not need to be adjacent in the OAT code. class OatWriter::LayoutCodeMethodVisitor final : public OatDexMethodVisitor { public:
LayoutCodeMethodVisitor(OatWriter* writer, size_t offset)
: OatDexMethodVisitor(writer, offset),
profile_index_(ProfileCompilationInfo::MaxProfileIndex()),
profile_index_dex_file_(nullptr) {
}
bool StartClass(const DexFile* dex_file, size_t class_def_index) final { // Update the cached `profile_index_` if needed. This happens only once per dex file // because we visit all classes in a dex file together, so mark that as `UNLIKELY`. if (UNLIKELY(dex_file != profile_index_dex_file_)) { if (writer_->profile_compilation_info_ != nullptr) {
profile_index_ = writer_->profile_compilation_info_->FindDexFile(*dex_file);
} else {
DCHECK_EQ(profile_index_, ProfileCompilationInfo::MaxProfileIndex());
}
profile_index_dex_file_ = dex_file;
} return OatDexMethodVisitor::StartClass(dex_file, class_def_index);
}
bool VisitMethod(size_t class_def_method_index, const ClassAccessor::Method& method) final
REQUIRES_SHARED(Locks::mutator_lock_) {
Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
// Debug method info must be pushed in the original order // (i.e. all methods from the same class must be adjacent in the debug info sections) // ElfCompilationUnitWriter::Write requires this. if (compiler_options.GenerateAnyDebugInfo() && code_size != 0) {
debug::MethodDebugInfo info = debug::MethodDebugInfo();
writer_->method_info_.push_back(info);
// The debug info is filled in LayoutReserveOffsetCodeMethodVisitor // once we know the offsets. // // Store the index into writer_->method_info_ since future push-backs // could reallocate and change the underlying data address.
debug_info_idx = writer_->method_info_.size() - 1;
}
}
// Determine the `hotness_bits`, used to determine relative order // for OAT code layout when determining binning.
uint32_t method_index = method.GetIndex();
MethodReference method_ref(dex_file_, method_index);
uint32_t hotness_bits = 0u; if (profile_index_ != ProfileCompilationInfo::MaxProfileIndex()) {
ProfileCompilationInfo* pci = writer_->profile_compilation_info_;
DCHECK(pci != nullptr); // Note: Bin-to-bin order does not matter. If the kernel does or does not read-ahead // any memory, it only goes into the buffer cache and does not grow the PSS until the // first time that memory is referenced in the process.
constexpr uint32_t kStartupBit = 4u;
constexpr uint32_t kHotBit = 2u;
constexpr uint32_t kPostStartupBit = 1u;
hotness_bits =
(pci->IsHotMethod(profile_index_, method_index) ? kHotBit : 0u) |
(pci->IsStartupMethod(profile_index_, method_index) ? kStartupBit : 0u) |
(pci->IsPostStartupMethod(profile_index_, method_index) ? kPostStartupBit : 0u); if (kIsDebugBuild) { // Check for bins that are always-empty given a real profile. if (hotness_bits == kHotBit) { // This is not fatal, so only warn.
LOG(WARNING) << "Method " << method_ref.PrettyMethod() << " was hot but wasn't marked "
<< "either start-up or post-startup. Possible corrupted profile?";
}
}
}
std::vector<OrderedMethodData> ReleaseOrderedMethods() { if (kOatWriterForceOatCodeLayout || writer_->profile_compilation_info_ != nullptr) { // Sort by the method ordering criteria (in OrderedMethodData). // Since most methods will have the same ordering criteria, // we preserve the original insertion order within the same sort order.
std::stable_sort(ordered_methods_.begin(), ordered_methods_.end());
} else { // The profile-less behavior is as if every method had 0 hotness // associated with it. // // Since sorting all methods with hotness=0 should give back the same // order as before, don't do anything.
DCHECK(std::is_sorted(ordered_methods_.begin(), ordered_methods_.end()));
}
return std::move(ordered_methods_);
}
private: // Cached profile index for the current dex file.
ProfileCompilationInfo::ProfileIndexType profile_index_; const DexFile* profile_index_dex_file_;
// List of compiled methods, later to be sorted by order defined in OrderedMethodData. // Methods can be inserted more than once in case of duplicated methods.
std::vector<OrderedMethodData> ordered_methods_;
};
// Given a method order, reserve the offsets for each CompiledMethod in the OAT file. class OatWriter::LayoutReserveOffsetCodeMethodVisitor : public OrderedMethodVisitor { public:
LayoutReserveOffsetCodeMethodVisitor(OatWriter* writer,
size_t offset,
ArrayRef<const OrderedMethodData> ordered_methods)
: LayoutReserveOffsetCodeMethodVisitor(writer,
offset,
writer->GetCompilerOptions(),
ordered_methods) {
}
// Deduplicate code arrays if we are not producing debuggable code. bool deduped = true; if (debuggable_) {
quick_code_offset = relative_patcher_->GetOffset(method_ref); if (quick_code_offset != 0u) { // Duplicate methods, we want the same code for both of them so that the oat writer puts // the same code in both ArtMethods so that we do not get different oat code at runtime.
} else {
quick_code_offset = NewQuickCodeOffset(compiled_method, method_ref, thumb_offset);
deduped = false;
}
} else {
quick_code_offset = dedupe_map_.GetOrCreate(
compiled_method,
[this, &deduped, compiled_method, &method_ref, thumb_offset]() {
deduped = false; return NewQuickCodeOffset(compiled_method, method_ref, thumb_offset);
});
}
if (code_size != 0) { if (relative_patcher_->GetOffset(method_ref) != 0u) { // TODO: Should this be a hard failure?
LOG(WARNING) << "Multiple definitions of "
<< method_ref.dex_file->PrettyMethod(method_ref.index)
<< " offsets " << relative_patcher_->GetOffset(method_ref)
<< " " << quick_code_offset;
} else {
relative_patcher_->SetOffset(method_ref, quick_code_offset);
}
}
// Update quick method header.
DCHECK_LT(method_offsets_index_, oat_class->method_headers_.size());
OatQuickMethodHeader* method_header = &oat_class->method_headers_[method_offsets_index_];
uint32_t code_info_offset = method_header->GetCodeInfoOffset();
uint32_t code_offset = quick_code_offset - thumb_offset;
CHECK(!compiled_method->GetQuickCode().empty()); // If the code is compiled, we write the offset of the stack map relative // to the code. The offset was previously stored relative to start of file. if (code_info_offset != 0u) {
DCHECK_LT(code_info_offset, code_offset);
code_info_offset = code_offset - code_info_offset;
}
*method_header = OatQuickMethodHeader(code_info_offset);
if (!deduped) { // Update offsets. (Checksum is updated when writing.)
offset_ += sizeof(*method_header); // Method header is prepended before code.
offset_ += code_size;
}
struct CodeOffsetsKeyComparator { booloperator()(const CompiledMethod* lhs, const CompiledMethod* rhs) const { // Code is deduplicated by CompilerDriver, compare only data pointers. if (lhs->GetQuickCode().data() != rhs->GetQuickCode().data()) { return lhs->GetQuickCode().data() < rhs->GetQuickCode().data();
} // If the code is the same, all other fields are likely to be the same as well. if (UNLIKELY(lhs->GetVmapTable().data() != rhs->GetVmapTable().data())) { return lhs->GetVmapTable().data() < rhs->GetVmapTable().data();
} if (UNLIKELY(lhs->GetPatches().data() != rhs->GetPatches().data())) { return lhs->GetPatches().data() < rhs->GetPatches().data();
} if (UNLIKELY(lhs->IsIntrinsic() != rhs->IsIntrinsic())) { return rhs->IsIntrinsic();
} returnfalse;
}
};
uint32_t NewQuickCodeOffset(CompiledMethod* compiled_method, const MethodReference& method_ref,
uint32_t thumb_offset) {
offset_ = relative_patcher_->ReserveSpace(offset_, compiled_method, method_ref); // `offset_` is relative to the oat data, but we need to align the code relative to the // beginning of the oat file to make it aligned in the memory, so we need to use the file // offset here.
offset_ += CodeAlignmentSize(writer_->GetFileOffset(offset_), *compiled_method);
DCHECK_ALIGNED_PARAM(writer_->GetFileOffset(offset_) + sizeof(OatQuickMethodHeader),
GetInstructionSetCodeAlignment(compiled_method->GetInstructionSet())); return offset_ + sizeof(OatQuickMethodHeader) + thumb_offset;
}
OatWriter* writer_;
// Offset of the code of the compiled methods.
size_t offset_;
// Deduplication is already done on a pointer basis by the compiler driver, // so we can simply compare the pointers to find out if things are duplicated.
SafeMap<const CompiledMethod*, uint32_t, CodeOffsetsKeyComparator> dedupe_map_;
// Cache writer_'s members and compiler options.
MultiOatRelativePatcher* relative_patcher_;
uint32_t executable_offset_; constbool debuggable_; constbool native_debuggable_; constbool generate_debug_info_;
};
template <bool kDeduplicate> class OatWriter::InitMapMethodVisitor : public OatDexMethodVisitor { public:
InitMapMethodVisitor(OatWriter* writer, size_t offset)
: OatDexMethodVisitor(writer, offset),
dedupe_bit_table_(&writer_->code_info_data_) { if (kDeduplicate) { // Reserve large buffers for `CodeInfo` and bit table deduplication except for // multi-image compilation as we do not want to reserve multiple large buffers. // User devices should not do any multi-image compilation. const CompilerOptions& compiler_options = writer->GetCompilerOptions();
DCHECK(compiler_options.IsAnyCompilationEnabled()); if (compiler_options.DeduplicateCode() && !compiler_options.IsMultiImage()) {
size_t unique_code_infos =
writer->compiler_driver_->GetCompiledMethodStorage()->UniqueVMapTableEntries();
dedupe_code_info_.reserve(unique_code_infos);
dedupe_bit_table_.ReserveDedupeBuffer(unique_code_infos);
}
}
}
if (HasCompiledCode(compiled_method)) {
DCHECK_LT(method_offsets_index_, oat_class->method_offsets_.size());
DCHECK_EQ(oat_class->method_headers_[method_offsets_index_].GetCodeInfoOffset(), 0u);
ArrayRef<const uint8_t> map = compiled_method->GetVmapTable(); if (map.size() != 0u) {
size_t offset = offset_ + writer_->code_info_data_.size(); if (kDeduplicate) { auto [it, inserted] = dedupe_code_info_.insert(std::make_pair(map.data(), offset));
DCHECK_EQ(inserted, it->second == offset); if (inserted) {
size_t dedupe_bit_table_offset = dedupe_bit_table_.Dedupe(map.data());
DCHECK_EQ(offset, offset_ + dedupe_bit_table_offset);
} else {
offset = it->second;
}
} else {
writer_->code_info_data_.insert(writer_->code_info_data_.end(), map.begin(), map.end());
} // Code offset is not initialized yet, so set file offset for now.
DCHECK_EQ(oat_class->method_offsets_[method_offsets_index_].code_offset_, 0u);
oat_class->method_headers_[method_offsets_index_].SetCodeInfoOffset(offset);
}
++method_offsets_index_;
}
returntrue;
}
private: // Deduplicate at CodeInfo level. The value is byte offset within code_info_data_. // This deduplicates the whole CodeInfo object without going into the inner tables. // The compiler already deduplicated the pointers but it did not dedupe the tables.
HashMap<const uint8_t*, size_t> dedupe_code_info_;
// Deduplicate at BitTable level.
CodeInfoTableDeduper dedupe_bit_table_;
};
class OatWriter::InitImageMethodVisitor final : public OatDexMethodVisitor { public:
InitImageMethodVisitor(OatWriter* writer,
size_t offset, const std::vector<const DexFile*>* dex_files)
REQUIRES_SHARED(Locks::mutator_lock_)
: OatDexMethodVisitor(writer, offset),
pointer_size_(GetInstructionSetPointerSize(writer_->compiler_options_.GetInstructionSet())),
class_loader_(writer->image_writer_->GetAppClassLoader()),
dex_files_(dex_files),
class_linker_(Runtime::Current()->GetClassLinker()),
dex_cache_dex_file_(nullptr),
dex_cache_(nullptr),
klass_(nullptr) {}
// Handle copied methods here. Copy pointer to quick code from // an origin method to a copied method only if they are // in the same oat file. If the origin and the copied methods are // in different oat files don't touch the copied method. // References to other oat files are not supported yet. bool StartClass(const DexFile* dex_file, size_t class_def_index) final
REQUIRES_SHARED(Locks::mutator_lock_) {
OatDexMethodVisitor::StartClass(dex_file, class_def_index); // Skip classes that are not in the image.
TypeReference type_ref(dex_file, dex_file->GetClassDef(class_def_index).class_idx_); if (!writer_->GetCompilerOptions().IsImageClass(type_ref, /*array_dim=*/ 0u)) {
klass_ = nullptr; returntrue;
} if (UNLIKELY(dex_file != dex_cache_dex_file_)) {
dex_cache_ = class_linker_->FindDexCache(Thread::Current(), *dex_file);
DCHECK(dex_cache_ != nullptr);
DCHECK(dex_cache_->GetDexFile() == dex_file);
dex_cache_dex_file_ = dex_file;
} const dex::ClassDef& class_def = dex_file->GetClassDef(class_def_index);
klass_ = class_linker_->LookupResolvedType(class_def.class_idx_, dex_cache_, class_loader_); if (klass_ != nullptr) { if (UNLIKELY(klass_->GetDexCache() != dex_cache_)) {
klass_ = nullptr; // This class definition is hidden by another dex file. returntrue;
} for (ArtMethod& method : klass_->GetCopiedMethods(pointer_size_)) { // Find origin method. Declaring class and dex_method_idx // in the copied method should be the same as in the origin // method.
ObjPtr<mirror::Class> declaring_class = method.GetDeclaringClass();
ArtMethod* origin = declaring_class->FindClassMethod(
declaring_class->GetDexCache(),
method.GetDexMethodIndex(),
pointer_size_);
CHECK(origin != nullptr);
CHECK(!origin->IsDirect());
CHECK(origin->GetDeclaringClass() == declaring_class); if (IsInOatFile(&declaring_class->GetDexFile())) { constvoid* code_ptr =
origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_); if (code_ptr == nullptr) {
methods_to_process_.push_back(std::make_pair(&method, origin));
} else {
method.SetEntryPointFromQuickCompiledCodePtrSize(
code_ptr, pointer_size_);
}
}
}
} returntrue;
}
bool VisitMethod(size_t class_def_method_index, const ClassAccessor::Method& method) final
REQUIRES_SHARED(Locks::mutator_lock_) { // Skip methods that are not in the image. if (klass_ == nullptr) { returntrue;
}
if (HasCompiledCode(compiled_method)) {
DCHECK_LT(method_offsets_index_, oat_class->method_offsets_.size());
OatMethodOffsets offsets = oat_class->method_offsets_[method_offsets_index_];
++method_offsets_index_;
// Do not try to use the `DexCache` via `ClassLinker::LookupResolvedMethod()`. // As we're going over all methods, `DexCache` entries would be quickly evicted // and we do not want the overhead of `hiddenapi` checks in the slow-path call // to `ClassLinker::FindResolvedMethod()` for a method that we have compiled.
ArtMethod* resolved_method = klass_->IsInterface()
? klass_->FindInterfaceMethod(dex_cache_, method.GetIndex(), pointer_size_)
: klass_->FindClassMethod(dex_cache_, method.GetIndex(), pointer_size_);
DCHECK(resolved_method != nullptr);
resolved_method->SetEntryPointFromQuickCompiledCodePtrSize( reinterpret_cast<void*>(offsets.code_offset_), pointer_size_);
}
returntrue;
}
// Check whether specified dex file is in the compiled oat file. bool IsInOatFile(const DexFile* dex_file) { return ContainsElement(*dex_files_, dex_file);
}
// Assign a pointer to quick code for copied methods // not handled in the method StartClass void Postprocess() REQUIRES_SHARED(Locks::mutator_lock_) { for (std::pair<ArtMethod*, ArtMethod*>& p : methods_to_process_) {
ArtMethod* method = p.first;
ArtMethod* origin = p.second; constvoid* code_ptr =
origin->GetEntryPointFromQuickCompiledCodePtrSize(pointer_size_); if (code_ptr != nullptr) {
method->SetEntryPointFromQuickCompiledCodePtrSize(code_ptr, pointer_size_);
}
}
}
// No thread suspension since dex_cache_ that may get invalidated if that occurs.
ScopedAssertNoThreadSuspension tsc(__FUNCTION__);
DCHECK(HasCompiledCode(compiled_method)) << method_ref.PrettyMethod();
// TODO: cleanup DCHECK_OFFSET_ to accept file_offset as parameter.
size_t file_offset = file_offset_; // Used by DCHECK_OFFSET_ macro.
OutputStream* out = out_;
// Deduplicate code arrays. const OatMethodOffsets& method_offsets = oat_class->method_offsets_[method_offsets_index]; if (method_offsets.code_offset_ > offset_) {
offset_ = writer_->relative_patcher_->WriteThunks(out, offset_); if (offset_ == 0u) {
ReportWriteFailure("relative call thunk", method_ref); returnfalse;
} // `offset_` is relative to the oat data, but we need to align the code relative to the // beginning of the oat file to make it aligned in the memory, so we need to use the file // offset here.
uint32_t alignment_size =
CodeAlignmentSize(writer_->GetFileOffset(offset_), *compiled_method); if (alignment_size != 0) { if (!writer_->WriteCodeAlignment(out, alignment_size)) {
ReportWriteFailure("code alignment padding", method_ref); returnfalse;
}
offset_ += alignment_size;
DCHECK_OFFSET_();
}
DCHECK_ALIGNED_PARAM(writer_->GetFileOffset(offset_) + sizeof(OatQuickMethodHeader),
GetInstructionSetCodeAlignment(compiled_method->GetInstructionSet()));
DCHECK_EQ(
method_offsets.code_offset_,
offset_ + sizeof(OatQuickMethodHeader) + compiled_method->GetEntryPointAdjustment())
<< dex_file_->PrettyMethod(method_ref.index); const OatQuickMethodHeader& method_header =
oat_class->method_headers_[method_offsets_index]; if (!out->WriteFully(&method_header, sizeof(method_header))) {
ReportWriteFailure("method header", method_ref); returnfalse;
}
writer_->size_method_header_ += sizeof(method_header);
offset_ += sizeof(method_header);
DCHECK_OFFSET_();
// Updated in VisitMethod as methods are written out.
size_t offset_;
// Potentially varies with every different VisitMethod. // Used to determine which DexCache to use when finding ArtMethods. const DexFile* dex_file_;
// Pointer size we are compiling to. const PointerSize pointer_size_; // The image writer's classloader, if there is one, else null.
ObjPtr<mirror::ClassLoader> class_loader_; // Stream to output file, where the OAT code will be written to.
OutputStream* const out_; const size_t file_offset_;
ClassLinker* const class_linker_;
ObjPtr<mirror::DexCache> dex_cache_;
std::vector<uint8_t> patched_code_; const ScopedAssertNoThreadSuspension no_thread_suspension_;
void ReportWriteFailure(constchar* what, const MethodReference& method_ref) {
PLOG(ERROR) << "Failed to write " << what << " for "
<< method_ref.PrettyMethod() << " to " << out_->GetLocation();
}
uint32_t GetTargetOffset(const LinkerPatch& patch) REQUIRES_SHARED(Locks::mutator_lock_) {
uint32_t target_offset = writer_->relative_patcher_->GetOffset(patch.TargetMethod()); // If there's no new compiled code, we need to point to the correct trampoline. if (UNLIKELY(target_offset == 0)) {
ArtMethod* target = GetTargetMethod(patch);
DCHECK(target != nullptr); // TODO: Remove kCallRelative? This patch type is currently not in use. // If we want to use it again, we should make sure that we either use it // only for target methods that were actually compiled, or call the // method dispatch thunk. Currently, ARM/ARM64 patchers would emit the // thunk for far `target_offset` (so we could teach them to use the // thunk for `target_offset == 0`) but x86/x86-64 patchers do not. // (When this was originally implemented, every oat file contained // trampolines, so we could just return their offset here. Now only // the boot image contains them, so this is not always an option.)
LOG(FATAL) << "The target method was not compiled.";
} return target_offset;
}
uint32_t GetTargetIntrinsicReferenceOffset(const LinkerPatch& patch)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(writer_->GetCompilerOptions().IsBootImage()); constvoid* address =
writer_->image_writer_->GetIntrinsicReferenceAddress(patch.IntrinsicData());
size_t oat_index = writer_->image_writer_->GetOatIndexForDexFile(dex_file_);
uintptr_t oat_data_begin = writer_->image_writer_->GetOatDataBegin(oat_index); // TODO: Clean up offset types. The target offset must be treated as signed. returnstatic_cast<uint32_t>(reinterpret_cast<uintptr_t>(address) - oat_data_begin);
}
uint32_t GetTargetMethodOffset(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(writer_->GetCompilerOptions().IsBootImage() ||
writer_->GetCompilerOptions().IsBootImageExtension());
method = writer_->image_writer_->GetImageMethodAddress(method);
size_t oat_index = writer_->image_writer_->GetOatIndexForDexFile(dex_file_);
uintptr_t oat_data_begin = writer_->image_writer_->GetOatDataBegin(oat_index); // TODO: Clean up offset types. The target offset must be treated as signed. returnstatic_cast<uint32_t>(reinterpret_cast<uintptr_t>(method) - oat_data_begin);
}
uint32_t GetTargetObjectOffset(ObjPtr<mirror::Object> object)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(writer_->GetCompilerOptions().IsBootImage() ||
writer_->GetCompilerOptions().IsBootImageExtension());
object = writer_->image_writer_->GetImageAddress(object.Ptr());
size_t oat_index = writer_->image_writer_->GetOatIndexForDexFile(dex_file_);
uintptr_t oat_data_begin = writer_->image_writer_->GetOatDataBegin(oat_index); // TODO: Clean up offset types. The target offset must be treated as signed. returnstatic_cast<uint32_t>(reinterpret_cast<uintptr_t>(object.Ptr()) - oat_data_begin);
}
};
// Visit all methods from all classes in all dex files with the specified visitor. bool OatWriter::VisitDexMethods(DexMethodVisitor* visitor) { for (const DexFile* dex_file : *dex_files_) { for (ClassAccessor accessor : dex_file->GetClasses()) { if (UNLIKELY(!visitor->StartClass(dex_file, accessor.GetClassDefIndex()))) { returnfalse;
} if (MayHaveCompiledMethods()) {
size_t class_def_method_index = 0u; for (const ClassAccessor::Method& method : accessor.GetMethods()) { if (!visitor->VisitMethod(class_def_method_index, method)) { returnfalse;
}
++class_def_method_index;
}
} if (UNLIKELY(!visitor->EndClass())) { returnfalse;
}
}
} returntrue;
}
// `key_value_store` only exists in the first oat file in a multi-image boot image. if (key_value_store != nullptr) { // Add non-deterministic fields if they don't exist. These fields should always exist with fixed // lengths. for (auto [field, length] : OatHeader::kNonDeterministicFieldsAndLengths) {
key_value_store->map_.FindOrAdd(std::string(field));
}
}
// Check that oat version when runtime was compiled matches the oat version // when dex2oat was compiled. We have seen cases where they got out of sync.
constexpr std::array<uint8_t, 4> dex2oat_oat_version = OatHeader::kOatVersion;
OatHeader::CheckOatVersion(dex2oat_oat_version);
oat_header_ = OatHeader::Create(GetCompilerOptions().GetInstructionSet(),
GetCompilerOptions().GetInstructionSetFeatures(),
num_dex_files,
key_value_store != nullptr ? &key_value_store->map_ : nullptr,
oat_data_offset_);
size_oat_header_ += sizeof(OatHeader);
size_oat_header_key_value_store_ += oat_header_->GetHeaderSize() - sizeof(OatHeader); return oat_header_->GetHeaderSize();
}
size_t OatWriter::InitClassOffsets(size_t offset) { // Reserve space for class offsets in OAT and update class_offsets_offset_. for (OatDexFile& oat_dex_file : oat_dex_files_) {
DCHECK_EQ(oat_dex_file.class_offsets_offset_, 0u); if (!oat_dex_file.class_offsets_.empty()) { // Class offsets are required to be 4 byte aligned.
offset = RoundUp(offset, 4u);
oat_dex_file.class_offsets_offset_ = offset;
offset += oat_dex_file.GetClassOffsetsRawSize();
DCHECK_ALIGNED(offset, 4u);
}
} return offset;
}
size_t OatWriter::InitOatClasses(size_t offset) { // calculate the offsets within OatDexFiles to OatClasses
InitOatClassesMethodVisitor visitor(this, offset); bool success = VisitDexMethods(&visitor);
CHECK(success);
offset = visitor.GetOffset();
// If there are any classes, the class offsets allocation aligns the offset // and we cannot have any index bss mappings without class offsets.
static_assert(alignof(IndexBssMapping) == 4u, "IndexBssMapping alignment check.");
DCHECK_ALIGNED(offset, 4u);
if (!compiler_options_.IsBootImage()) {
ArrayRef<const DexFile* const> boot_class_path(
Runtime::Current()->GetClassLinker()->GetBootClassPath()); // We initialize bcp_bss_info except for the boot image case. // Note that we have an early break at the beginning of the method, so `bcp_bss_info_` will also // be empty in the case of having no mappings at all.
if (compiler_options_.IsBootImageExtension()) { // For boot image extension, the boot_class_path ends with the compiled dex files. In multi // image, we might have several oat writers so we have to get all of the compiled dex files // and not just the one we are compiling right now. Remove them to have the correct number of // references.
ArrayRef<const DexFile* const> to_exclude(compiler_options_.GetDexFilesForOatFile());
DCHECK_GE(boot_class_path.size(), to_exclude.size());
DCHECK(std::equal(to_exclude.rbegin(), to_exclude.rend(), boot_class_path.rbegin()));
boot_class_path = boot_class_path.SubArray(0, boot_class_path.size() - to_exclude.size());
}
// Check that all dex files targeted by bss entries are in `*dex_files_`, or in the bootclaspath's // DexFiles in the single image case.
CHECK_EQ(number_of_method_dex_files, bss_method_entry_references_.size());
CHECK_EQ(number_of_type_dex_files, bss_type_entry_references_.size());
CHECK_EQ(number_of_public_type_dex_files, bss_public_type_entry_references_.size());
CHECK_EQ(number_of_package_type_dex_files, bss_package_type_entry_references_.size());
CHECK_EQ(number_of_method_type_dex_files, bss_method_type_entry_references_.size());
CHECK_EQ(number_of_string_dex_files, bss_string_entry_references_.size());
// We first increase the offset to make room to store the number of BCP DexFiles, if we have at // least one entry.
oat_header_->SetBcpBssInfoOffset(offset);
offset += sizeof(uint32_t);
for (BssMappingInfo& info : bcp_bss_info_) {
info.offset_ = offset;
offset += BssMappingInfo::SizeOf();
} return offset;
}
size_t OatWriter::InitOatCode(size_t offset) { // calculate the offsets within OatHeader to executable code
size_t old_offset = offset; // required to be on a new page boundary
offset = GetOffsetFromOatDataAlignedToFile(offset, kElfSegmentAlignment);
oat_header_->SetExecutableOffset(offset);
size_executable_offset_alignment_ = offset - old_offset;
InstructionSet instruction_set = compiler_options_.GetInstructionSet(); if (GetCompilerOptions().IsBootImage() && primary_oat_file_) { constbool generate_debug_info = GetCompilerOptions().GenerateAnyDebugInfo();
size_t adjusted_offset = offset;
#define DO_TRAMPOLINE(field, fn_name) \ /* Pad with at least four 0xFFs so we can do DCHECKs in OatQuickMethodHeader */ \
offset = GetOffsetFromOatDataAlignedToFile(offset + 4, \
GetInstructionSetCodeAlignment(instruction_set)); \
adjusted_offset = offset + GetInstructionSetEntryPointAdjustment(instruction_set); \
oat_header_->Set ## fn_name ## Offset(adjusted_offset); \
(field) = compiler_driver_->Create ## fn_name(); \ if (generate_debug_info) { \
debug::MethodDebugInfo info = {}; \
info.custom_name = #fn_name; \
info.isa = instruction_set; \
info.is_code_address_text_relative = true; \ /* Use the code offset rather than the `adjusted_offset`. */ \
info.code_address = offset - oat_header_->GetExecutableOffset(); \
info.code_size = (field)->size(); \
method_info_.push_back(std::move(info)); \
} \
offset += (field)->size();
// Save the method order because the WriteCodeMethodVisitor will need this // order again.
DCHECK(ordered_methods_.empty());
ordered_methods_ = layout_code_visitor.ReleaseOrderedMethods();
void OatWriter::WriteVerifierDeps(verifier::VerifierDeps* verifier_deps, /*out*/std::vector<uint8_t>* buffer) { if (verifier_deps == nullptr) { // Nothing to write. Record the offset, but no need // for alignment.
vdex_verifier_deps_offset_ = vdex_size_; return;
}
// Record the padding before the .data.img.rel.ro section. // Do not write anything, this zero-filled part was skipped (Seek()) when starting the section.
size_t code_end = GetOatHeader().GetExecutableOffset() + code_size_;
DCHECK_EQ(GetOffsetFromOatDataAlignedToFile(code_end, kElfSegmentAlignment), relative_offset);
size_t padding_size = relative_offset - code_end;
DCHECK_EQ(size_data_img_rel_ro_alignment_, 0u);
size_data_img_rel_ro_alignment_ = padding_size;
relative_offset = WriteDataImgRelRo(out, file_offset, relative_offset); if (relative_offset == 0) {
LOG(ERROR) << "Failed to write boot image relocations to " << out->GetLocation(); returnfalse;
}
// Update checksum with header data.
DCHECK_EQ(oat_header_->GetChecksum(), 0u); // For checksum calculation.
oat_header_->ComputeChecksum(&oat_checksum_);
oat_header_->SetChecksum(oat_checksum_);
const size_t file_offset = oat_data_offset_;
off_t current_offset = out->Seek(0, kSeekCurrent); if (current_offset == static_cast<off_t>(-1)) {
PLOG(ERROR) << "Failed to get current offset from " << out->GetLocation(); returnfalse;
} if (out->Seek(file_offset, kSeekSet) == static_cast<off_t>(-1)) {
PLOG(ERROR) << "Failed to seek to oat header position in " << out->GetLocation(); returnfalse;
}
DCHECK_EQ(file_offset, static_cast<size_t>(out->Seek(0, kSeekCurrent)));
// Flush all other data before writing the header. if (!out->Flush()) {
PLOG(ERROR) << "Failed to flush before writing oat header to " << out->GetLocation(); returnfalse;
} // Write the header.
size_t header_size = oat_header_->GetHeaderSize(); if (!out->WriteFully(oat_header_, header_size)) {
PLOG(ERROR) << "Failed to write oat header to " << out->GetLocation(); returnfalse;
} // Flush the header data. if (!out->Flush()) {
PLOG(ERROR) << "Failed to flush after writing oat header to " << out->GetLocation(); returnfalse;
}
if (out->Seek(current_offset, kSeekSet) == static_cast<off_t>(-1)) {
PLOG(ERROR) << "Failed to seek back after writing oat header to " << out->GetLocation(); returnfalse;
}
DCHECK_EQ(current_offset, out->Seek(0, kSeekCurrent));
write_state_ = WriteState::kDone; returntrue;
}
size_t OatWriter::WriteClassOffsets(OutputStream* out, size_t file_offset, size_t relative_offset) { for (OatDexFile& oat_dex_file : oat_dex_files_) { if (oat_dex_file.class_offsets_offset_ != 0u) { // Class offsets are required to be 4 byte aligned. if (UNLIKELY(!IsAligned<4u>(relative_offset))) {
size_t padding_size = RoundUp(relative_offset, 4u) - relative_offset; if (!WriteUpTo16BytesAlignment(out, padding_size, &size_oat_class_offsets_alignment_)) { return0u;
}
relative_offset += padding_size;
}
DCHECK_OFFSET(); if (!oat_dex_file.WriteClassOffsets(this, out)) { return0u;
}
relative_offset += oat_dex_file.GetClassOffsetsRawSize();
}
} return relative_offset;
}
size_t OatWriter::WriteClasses(OutputStream* out, size_t file_offset, size_t relative_offset) { constbool may_have_compiled = MayHaveCompiledMethods(); if (may_have_compiled) {
CHECK_EQ(oat_class_headers_.size(), oat_classes_.size());
} for (size_t i = 0; i < oat_class_headers_.size(); ++i) { // If there are any classes, the class offsets allocation aligns the offset.
DCHECK_ALIGNED(relative_offset, 4u);
DCHECK_OFFSET(); if (!oat_class_headers_[i].Write(this, out, oat_data_offset_)) { return0u;
}
relative_offset += oat_class_headers_[i].SizeOf(); if (may_have_compiled) { if (!oat_classes_[i].Write(this, out)) { return0u;
}
relative_offset += oat_classes_[i].SizeOf();
}
} return relative_offset;
}
size_t OatWriter::WriteIndexBssMappings(OutputStream* out,
size_t file_offset,
size_t relative_offset) { if (bss_method_entry_references_.empty() &&
bss_type_entry_references_.empty() &&
bss_public_type_entry_references_.empty() &&
bss_package_type_entry_references_.empty() &&
bss_method_type_entry_references_.empty() &&
bss_string_entry_references_.empty()) { return relative_offset;
} // If there are any classes, the class offsets allocation aligns the offset // and we cannot have method bss mappings without class offsets.
static_assert(alignof(IndexBssMapping) == sizeof(uint32_t), "IndexBssMapping alignment check.");
DCHECK_ALIGNED(relative_offset, sizeof(uint32_t));
if (!compiler_options_.IsBootImage()) {
ArrayRef<const DexFile* const> boot_class_path(
Runtime::Current()->GetClassLinker()->GetBootClassPath());
if (compiler_options_.IsBootImageExtension()) { // For boot image extension, the boot_class_path ends with the compiled dex files. In multi // image, we might have several oat writers so we have to get all of the compiled dex files // and not just the one we are compiling right now. Remove them to have the correct number of // references.
ArrayRef<const DexFile* const> to_exclude(compiler_options_.GetDexFilesForOatFile());
DCHECK_GE(boot_class_path.size(), to_exclude.size());
DCHECK(std::equal(to_exclude.rbegin(), to_exclude.rend(), boot_class_path.rbegin()));
boot_class_path = boot_class_path.SubArray(0, boot_class_path.size() - to_exclude.size());
}
size_t OatWriter::WriteBcpBssInfo(OutputStream* out, size_t file_offset, size_t relative_offset) { const uint32_t number_of_bcp_dexfiles = bcp_bss_info_.size(); // We skip adding the number of DexFiles if we have no .bss mappings. if (number_of_bcp_dexfiles == 0) { return relative_offset;
}
if (!out->WriteFully(&number_of_bcp_dexfiles, sizeof(number_of_bcp_dexfiles))) {
PLOG(ERROR) << "Failed to write the number of BCP dexfiles to " << out->GetLocation(); returnfalse;
}
size_bcp_bss_info_size_ = sizeof(number_of_bcp_dexfiles);
relative_offset += size_bcp_bss_info_size_;
for (size_t i = 0, size = number_of_bcp_dexfiles; i != size; ++i) {
DCHECK_EQ(relative_offset, bcp_bss_info_[i].offset_);
DCHECK_OFFSET(); if (!bcp_bss_info_[i].Write(this, out)) { return0u;
}
relative_offset += BssMappingInfo::SizeOf();
}
return relative_offset;
}
size_t OatWriter::WriteCode(OutputStream* out, size_t file_offset, size_t relative_offset) {
InstructionSet instruction_set = compiler_options_.GetInstructionSet(); if (GetCompilerOptions().IsBootImage() && primary_oat_file_) { #define DO_TRAMPOLINE(field) \ do { \ /* Pad with at least four 0xFFs so we can do DCHECKs in OatQuickMethodHeader */ \
uint32_t aligned_offset = GetOffsetFromOatDataAlignedToFile( \
relative_offset + 4, GetInstructionSetCodeAlignment(instruction_set)); \
uint32_t alignment_padding = aligned_offset - relative_offset; \ for (size_t i = 0; i < alignment_padding; i++) { \
uint8_t padding = 0xFF; \
out->WriteFully(&padding, 1); \
} \
size_trampoline_alignment_ += alignment_padding; \ if (!out->WriteFully((field)->data(), (field)->size())) { \
PLOG(ERROR) << "Failed to write "# field " to " << out->GetLocation(); \ returnfalse; \
} \
size_ ## field += (field)->size(); \
relative_offset += alignment_padding + (field)->size(); \
DCHECK_OFFSET(); \
} while (false)
size_t OatWriter::WriteCodeDexFiles(OutputStream* out,
size_t file_offset,
size_t relative_offset) { if (!GetCompilerOptions().IsAnyCompilationEnabled()) { // As with InitOatCodeDexFiles, also skip the writer if // compilation was disabled. if (kOatWriterDebugOatCodeLayout) {
LOG(INFO) << "WriteCodeDexFiles: OatWriter("
<< this << "), "
<< "compilation is disabled";
}
// Write the entire .data.img.rel.ro with a single WriteFully().
std::vector<uint32_t> data;
data.reserve(size); for (constauto& entry : boot_image_rel_ro_entries_) {
uint32_t boot_image_offset = entry.first;
data.push_back(boot_image_offset);
}
// Both the sorted and unsorted variants contain duplicates. We skip the duplicates in the loops // below, and we update `size` in the process.
DCHECK_EQ(app_image_rel_ro_method_entries_.size(),
app_image_rel_ro_method_entries_sorted_.size());
DCHECK_EQ(app_image_rel_ro_type_entries_.size(), app_image_rel_ro_type_entries_sorted_.size());
DCHECK_EQ(app_image_rel_ro_string_entries_.size(),
app_image_rel_ro_string_entries_sorted_.size()); if (!app_image_rel_ro_method_entries_.empty() ||
!app_image_rel_ro_type_entries_.empty() ||
!app_image_rel_ro_string_entries_.empty()) {
DCHECK(GetCompilerOptions().IsAppImage());
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
ScopedObjectAccess soa(Thread::Current()); const DexFile* last_dex_file = nullptr;
ObjPtr<mirror::DexCache> dex_cache = nullptr;
ObjPtr<mirror::ClassLoader> class_loader = nullptr; auto update_for_dex_file = [&](const DexFile* dex_file) REQUIRES_SHARED(Locks::mutator_lock_) { if (dex_file != last_dex_file) {
dex_cache = class_linker->FindDexCache(soa.Self(), *dex_file);
class_loader = dex_cache->GetClassLoader();
last_dex_file = dex_file;
}
};
for (size_t i : Range(app_image_rel_ro_method_entries_sorted_.size())) { if (i != 0 && app_image_rel_ro_method_entries_sorted_[i]->second ==
app_image_rel_ro_method_entries_sorted_[i - 1]->second) { // Skip duplicates. We can do it cheaply by comparing the offsets, without the need to // compare the MethodReferences themselves.
--size; continue;
}
MethodReference target_method = app_image_rel_ro_method_entries_sorted_[i]->first;
update_for_dex_file(target_method.dex_file);
ArtMethod* method =
class_linker->LookupResolvedMethod(target_method.index, dex_cache, class_loader);
CHECK(method != nullptr);
uint32_t app_image_offset = image_writer_->GetGlobalImageOffset(method);
data.push_back(app_image_offset);
} for (size_t i : Range(app_image_rel_ro_type_entries_sorted_.size())) { if (i != 0 && app_image_rel_ro_type_entries_sorted_[i]->second ==
app_image_rel_ro_type_entries_sorted_[i - 1]->second) { // Skip duplicates. We can do it cheaply by comparing the offsets, without the need to // compare the TypeReferences themselves.
--size; continue;
}
TypeReference target_type = app_image_rel_ro_type_entries_sorted_[i]->first;
update_for_dex_file(target_type.dex_file);
ObjPtr<mirror::Class> type =
class_linker->LookupResolvedType(target_type.TypeIndex(), dex_cache, class_loader);
CHECK(type != nullptr);
uint32_t app_image_offset = image_writer_->GetGlobalImageOffset(type.Ptr());
data.push_back(app_image_offset);
} for (size_t i : Range(app_image_rel_ro_string_entries_sorted_.size())) { if (i != 0 && app_image_rel_ro_string_entries_sorted_[i]->second ==
app_image_rel_ro_string_entries_sorted_[i - 1]->second) { // Skip duplicates. We can do it cheaply by comparing the offsets, without the need to // compare the StringReferences themselves.
--size; continue;
}
StringReference target_string = app_image_rel_ro_string_entries_sorted_[i]->first;
update_for_dex_file(target_string.dex_file);
ObjPtr<mirror::String> string =
class_linker->LookupString(target_string.StringIndex(), dex_cache);
CHECK(string != nullptr);
uint32_t app_image_offset = image_writer_->GetGlobalImageOffset(string.Ptr());
data.push_back(app_image_offset);
}
}
DCHECK_EQ(data.size(), size);
DCHECK_OFFSET(); if (!out->WriteFully(data.data(), data.size() * sizeof(data[0]))) {
PLOG(ERROR) << "Failed to write .data.img.rel.ro in " << out->GetLocation(); return0u;
}
DCHECK_EQ(size_data_img_rel_ro_, 0u);
size_data_img_rel_ro_ = data.size() * sizeof(data[0]);
relative_offset += size_data_img_rel_ro_; return relative_offset;
}
bool OatWriter::RecordOatDataOffset(OutputStream* out) { // Get the elf file offset of the oat file. const off_t raw_file_offset = out->Seek(0, kSeekCurrent); if (raw_file_offset == static_cast<off_t>(-1)) {
LOG(ERROR) << "Failed to get file offset in " << out->GetLocation(); returnfalse;
}
oat_data_offset_ = static_cast<size_t>(raw_file_offset); returntrue;
}
// If extraction is enabled, only do it if not all the dex files are aligned and uncompressed. if (copy_dex_files == CopyOption::kOnlyIfCompressed) {
extract_dex_files_into_vdex_ = false; for (OatDexFile& oat_dex_file : oat_dex_files_) { const DexFileContainer* container = oat_dex_file.GetDexFile()->GetContainer().get(); if (!container->IsFileMap()) {
extract_dex_files_into_vdex_ = true; break;
}
}
} elseif (copy_dex_files == CopyOption::kAlways) {
extract_dex_files_into_vdex_ = true;
} else {
DCHECK(copy_dex_files == CopyOption::kNever);
extract_dex_files_into_vdex_ = false;
}
if (extract_dex_files_into_vdex_) {
vdex_dex_files_offset_ = vdex_size_;
// Calculate the total size after the dex files.
size_t vdex_size_with_dex_files = vdex_size_; for (OatDexFile& oat_dex_file : oat_dex_files_) { // Dex files are required to be 4 byte aligned.
vdex_size_with_dex_files = RoundUp(vdex_size_with_dex_files, 4u); // Record offset for the dex file.
oat_dex_file.dex_file_offset_ = vdex_size_with_dex_files; // Add the size of the dex file. if (oat_dex_file.dex_file_size_ < sizeof(DexFile::Header)) {
LOG(ERROR) << "Dex file " << oat_dex_file.GetLocation() << " is too short: "
<< oat_dex_file.dex_file_size_ << " < " << sizeof(DexFile::Header); returnfalse;
}
vdex_size_with_dex_files += oat_dex_file.dex_file_size_;
}
// Extend the file and include the full page at the end as we need to write // additional data there and do not want to mmap that page twice. // // The page size value here is used to figure out the size of the mapping below, // however it doesn't affect the file contents or its size, so should not be // replaced with kElfSegmentAlignment.
size_t page_aligned_size = RoundUp(vdex_size_with_dex_files, MemMap::GetPageSize()); if (!use_existing_vdex) { if (file->SetLength(page_aligned_size) != 0) {
PLOG(ERROR) << "Failed to resize vdex file " << file->GetPath(); returnfalse;
}
}
if (use_existing_vdex) { // If we re-use an existing vdex, artificially set the verifier deps size, // so the compiler has a correct computation of the vdex size.
size_t actual_size = file->GetLength();
size_verifier_deps_ = actual_size - vdex_size_;
vdex_size_ = actual_size;
}
if (oat_dex_files_.empty()) { // Nothing to do. returntrue;
}
if (!extract_dex_files_into_vdex_) {
DCHECK_EQ(opened_dex_files_map->size(), 0u);
std::vector<std::unique_ptr<const DexFile>> dex_files; for (OatDexFile& oat_dex_file : oat_dex_files_) { // The dex file is already open, release the reference.
dex_files.emplace_back(std::move(oat_dex_file.dex_file_));
oat_dex_file.class_offsets_.resize(dex_files.back()->GetHeader().class_defs_size_);
}
*opened_dex_files = std::move(dex_files);
CloseSources(); returntrue;
} // We could have closed the sources at the point of writing the dex files, but to // make it consistent with the case we're not writing the dex files, we close them now.
CloseSources();
// Now, open the dex file.
std::string error_msg;
ArtDexFileLoader dex_file_loader(dex_container, oat_dex_file.GetLocation()); // All dex files have been already verified in WriteDexFiles before we copied them.
dex_files.emplace_back(dex_file_loader.OpenOne(oat_dex_file.dex_file_offset_,
oat_dex_file.dex_file_location_checksum_, /*oat_dex_file=*/nullptr, /*verify=*/false, /*verify_checksum=*/false,
&error_msg)); if (dex_files.back() == nullptr) {
LOG(ERROR) << "Failed to open dex file from oat file. File: " << oat_dex_file.GetLocation()
<< " Error: " << error_msg; returnfalse;
}
// Set the class_offsets size now that we have easy access to the DexFile and // it has been verified in dex_file_loader.Open.
oat_dex_file.class_offsets_.resize(dex_files.back()->GetHeader().class_defs_size_);
}
size_t table_size = TypeLookupTable::RawDataLength(oat_dex_file->class_offsets_.size()); if (table_size == 0u) { // We want a 1:1 mapping between `dex_files_` and `type_lookup_table_oat_dex_files_`, // to simplify `WriteTypeLookupTables`. We push a null entry to notify // that the dex file at index `i` does not have a type lookup table.
type_lookup_table_oat_dex_files_.push_back(nullptr); continue;
}
if (!oat_rodata->Flush()) {
PLOG(ERROR) << "Failed to flush stream after writing type dex profile metadata."
<< " File: " << oat_rodata->GetLocation(); returnfalse;
}
returntrue;
}
void OatWriter::WriteTypeLookupTables(/*out*/std::vector<uint8_t>* buffer) {
TimingLogger::ScopedTiming split("WriteTypeLookupTables", timings_);
size_t type_lookup_table_size = 0u; for (const DexFile* dex_file : *dex_files_) {
type_lookup_table_size += sizeof(uint32_t) + TypeLookupTable::RawDataLength(dex_file->NumClassDefs());
} // Reserve the space to avoid reallocations later on.
buffer->reserve(buffer->size() + type_lookup_table_size);
// Align the start of the first type lookup table.
size_t initial_offset = vdex_size_;
size_t table_offset = RoundUp(initial_offset, 4);
size_t padding_size = table_offset - initial_offset;
// Resize the vdex file. if (vdex_file->SetLength(vdex_size_) != 0) {
PLOG(ERROR) << "Failed to resize vdex file " << vdex_file->GetPath(); returnfalse;
}
uint8_t* vdex_begin = vdex_begin_;
MemMap extra_map; if (extract_dex_files_into_vdex_) {
DCHECK(vdex_begin != nullptr); // Write data to the last already mmapped page of the vdex file. // The size should match the page_aligned_size in the OatWriter::WriteDexFiles.
size_t mmapped_vdex_size = RoundUp(old_vdex_size, MemMap::GetPageSize());
size_t first_chunk_size = std::min(buffer.size(), mmapped_vdex_size - old_vdex_size);
memcpy(vdex_begin + old_vdex_size, buffer.data(), first_chunk_size);
// TypeLookupTable section. new (ptr) VdexFile::VdexSectionHeader(VdexSection::kTypeLookupTableSection,
vdex_lookup_tables_offset_,
vdex_size_ - vdex_lookup_tables_offset_);
// All the contents (except the header) of the vdex file has been emitted in memory. Flush it // to disk.
{
TimingLogger::ScopedTiming split("VDEX flush contents", timings_); // Sync the data to the disk while the header is invalid. We do not want to end up with // a valid header and invalid data if the process is suddenly killed. if (extract_dex_files_into_vdex_) { // Note: We passed the ownership of the vdex dex file MemMap to the caller, // so we need to use msync() for the range explicitly. // // The page size here is not replaced with kElfSegmentAlignment as the // rounded up size should match the page_aligned_size in OatWriter::WriteDexFiles // which is the size the original (non-extra) mapping created there. if (msync(vdex_begin, RoundUp(old_vdex_size, MemMap::GetPageSize()), MS_SYNC) != 0) {
PLOG(ERROR) << "Failed to sync vdex file contents" << vdex_file->GetPath(); returnfalse;
}
} if (extra_map.IsValid() && !extra_map.Sync()) {
PLOG(ERROR) << "Failed to sync vdex file contents" << vdex_file->GetPath(); returnfalse;
}
}
// Now that we know all contents have been flushed to disk, we can write // the header which will mke the vdex usable. bool has_dex_section = extract_dex_files_into_vdex_; new (vdex_begin) VdexFile::VdexFileHeader(has_dex_section);
// Note: If `extract_dex_files_into_vdex_`, we passed the ownership of the vdex dex file // MemMap to the caller, so we need to use msync() for the range explicitly. // // The page size here should not be replaced with kElfSegmentAlignment as the size // here should match the header size rounded up to the page size. Any higher value // might happen to be larger than the size of the mapping which can in some circumstances // cause msync to fail. if (msync(vdex_begin, MemMap::GetPageSize(), MS_SYNC) != 0) {
PLOG(ERROR) << "Failed to sync vdex file header " << vdex_file->GetPath(); returnfalse;
}
void OatWriter::SetMultiOatRelativePatcherAdjustment() {
DCHECK(dex_files_ != nullptr);
DCHECK(relative_patcher_ != nullptr);
DCHECK_NE(oat_data_offset_, 0u);
size_t elf_file_offset = 0; if (image_writer_ != nullptr && !dex_files_->empty()) { // The oat data begin may not be initialized yet but the oat file offset is ready.
size_t oat_index = image_writer_->GetOatIndexForDexFile(dex_files_->front());
elf_file_offset = image_writer_->GetOatFileOffset(oat_index);
} // Relative patcher expects offsets from the page-aligned boundary, as the oat data is // unaligned in the ELF file we always need to set its correct start.
relative_patcher_->StartOatFile(elf_file_offset + oat_data_offset_);
}
if (!out->WriteFully(&dex_file_location_size_, sizeof(dex_file_location_size_))) {
PLOG(ERROR) << "Failed to write dex file location length to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_location_size_ += sizeof(dex_file_location_size_);
if (!out->WriteFully(dex_file_location_data_, dex_file_location_size_)) {
PLOG(ERROR) << "Failed to write dex file location data to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_location_data_ += dex_file_location_size_;
if (!out->WriteFully(&dex_file_magic_, sizeof(dex_file_magic_))) {
PLOG(ERROR) << "Failed to write dex file magic to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_magic_ += sizeof(dex_file_magic_);
if (!out->WriteFully(&dex_file_location_checksum_, sizeof(dex_file_location_checksum_))) {
PLOG(ERROR) << "Failed to write dex file location checksum to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_location_checksum_ += sizeof(dex_file_location_checksum_);
if (!out->WriteFully(&dex_file_sha1_, sizeof(dex_file_sha1_))) {
PLOG(ERROR) << "Failed to write dex file sha1 to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_sha1_ += sizeof(dex_file_sha1_);
if (!out->WriteFully(&dex_file_offset_, sizeof(dex_file_offset_))) {
PLOG(ERROR) << "Failed to write dex file offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_offset_ += sizeof(dex_file_offset_);
if (!out->WriteFully(&class_offsets_offset_, sizeof(class_offsets_offset_))) {
PLOG(ERROR) << "Failed to write class offsets offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_class_offsets_offset_ += sizeof(class_offsets_offset_);
if (!out->WriteFully(&lookup_table_offset_, sizeof(lookup_table_offset_))) {
PLOG(ERROR) << "Failed to write lookup table offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_lookup_table_offset_ += sizeof(lookup_table_offset_);
if (!out->WriteFully(&dex_profile_metadata_offset_, sizeof(dex_profile_metadata_offset_))) {
PLOG(ERROR) << "Failed to write dex section layout info to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_dex_profile_metadata_offset_ += sizeof(dex_profile_metadata_offset_);
if (!out->WriteFully(&method_bss_mapping_offset_, sizeof(method_bss_mapping_offset_))) {
PLOG(ERROR) << "Failed to write method bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_method_bss_mapping_offset_ += sizeof(method_bss_mapping_offset_);
if (!out->WriteFully(&type_bss_mapping_offset_, sizeof(type_bss_mapping_offset_))) {
PLOG(ERROR) << "Failed to write type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_type_bss_mapping_offset_ += sizeof(type_bss_mapping_offset_);
if (!out->WriteFully(&public_type_bss_mapping_offset_, sizeof(public_type_bss_mapping_offset_))) {
PLOG(ERROR) << "Failed to write public type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_public_type_bss_mapping_offset_ += sizeof(public_type_bss_mapping_offset_);
if (!out->WriteFully(&package_type_bss_mapping_offset_, sizeof(package_type_bss_mapping_offset_))) {
PLOG(ERROR) << "Failed to write package type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_package_type_bss_mapping_offset_ += sizeof(package_type_bss_mapping_offset_);
if (!out->WriteFully(&string_bss_mapping_offset_, sizeof(string_bss_mapping_offset_))) {
PLOG(ERROR) << "Failed to write string bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_string_bss_mapping_offset_ += sizeof(string_bss_mapping_offset_);
if (!out->WriteFully(&method_type_bss_mapping_offset_, sizeof(method_type_bss_mapping_offset_))) {
PLOG(ERROR) << "Failed to write MethodType bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_dex_file_method_type_bss_mapping_offset_ += sizeof(method_type_bss_mapping_offset_);
if (!out->WriteFully(&method_bss_mapping_offset, sizeof(method_bss_mapping_offset))) {
PLOG(ERROR) << "Failed to write method bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_bcp_bss_info_method_bss_mapping_offset_ += sizeof(method_bss_mapping_offset);
if (!out->WriteFully(&type_bss_mapping_offset, sizeof(type_bss_mapping_offset))) {
PLOG(ERROR) << "Failed to write type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_bcp_bss_info_type_bss_mapping_offset_ += sizeof(type_bss_mapping_offset);
if (!out->WriteFully(&public_type_bss_mapping_offset, sizeof(public_type_bss_mapping_offset))) {
PLOG(ERROR) << "Failed to write public type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_bcp_bss_info_public_type_bss_mapping_offset_ += sizeof(public_type_bss_mapping_offset);
if (!out->WriteFully(&package_type_bss_mapping_offset, sizeof(package_type_bss_mapping_offset))) {
PLOG(ERROR) << "Failed to write package type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_bcp_bss_info_package_type_bss_mapping_offset_ += sizeof(package_type_bss_mapping_offset);
if (!out->WriteFully(&string_bss_mapping_offset, sizeof(string_bss_mapping_offset))) {
PLOG(ERROR) << "Failed to write string bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_bcp_bss_info_string_bss_mapping_offset_ += sizeof(string_bss_mapping_offset);
if (!out->WriteFully(&method_type_bss_mapping_offset, sizeof(method_type_bss_mapping_offset))) {
PLOG(ERROR) << "Failed to write method type bss mapping offset to " << out->GetLocation(); returnfalse;
}
oat_writer->size_bcp_bss_info_method_type_bss_mapping_offset_ += sizeof(method_type_bss_mapping_offset);
returntrue;
}
bool OatWriter::OatDexFile::WriteClassOffsets(OatWriter* oat_writer, OutputStream* out) { if (!out->WriteFully(class_offsets_.data(), GetClassOffsetsRawSize())) {
PLOG(ERROR) << "Failed to write oat class offsets for " << GetLocation()
<< " to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_class_offsets_ += GetClassOffsetsRawSize(); returntrue;
}
bool OatWriter::OatClassHeader::Write(OatWriter* oat_writer,
OutputStream* out, const size_t file_offset) const {
DCHECK_OFFSET_(); if (!out->WriteFully(&status_, sizeof(status_))) {
PLOG(ERROR) << "Failed to write class status to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_class_status_ += sizeof(status_);
if (!out->WriteFully(&type_, sizeof(type_))) {
PLOG(ERROR) << "Failed to write oat class type to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_class_type_ += sizeof(type_); returntrue;
}
bool OatWriter::OatClass::Write(OatWriter* oat_writer, OutputStream* out) const { if (num_methods_ != 0u) { if (!out->WriteFully(&num_methods_, sizeof(num_methods_))) {
PLOG(ERROR) << "Failed to write number of methods to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_class_num_methods_ += sizeof(num_methods_);
}
if (method_bitmap_ != nullptr) { if (!out->WriteFully(method_bitmap_->GetRawStorage(), method_bitmap_->GetSizeOf())) {
PLOG(ERROR) << "Failed to write method bitmap to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_class_method_bitmaps_ += method_bitmap_->GetSizeOf();
}
if (!out->WriteFully(method_offsets_.data(), GetMethodOffsetsRawSize())) {
PLOG(ERROR) << "Failed to write method offsets to " << out->GetLocation(); returnfalse;
}
oat_writer->size_oat_class_method_offsets_ += GetMethodOffsetsRawSize(); returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.