From 53cb16b98acf3cf6f3a1e2204ad4958ecf1b5a3c Mon Sep 17 00:00:00 2001 From: Alex Light Date: Thu, 12 Jun 2014 11:26:29 -0700 Subject: [PATCH] Add patchoat tool to Art. Add a new executable called patchoat to art. This tool takes already compiled images and oat files and changes their base address, acting as a cheap form of relocation. Add a --include-patch-information flag to dex2oat and code to add required patch information to oat files created with the quick compiler. Bug: 15358152 Change-Id: Ie0c580db45bb14ec180deb84930def6c3628d97d --- Android.mk | 7 +- build/Android.oat.mk | 4 +- compiler/driver/compiler_options.h | 9 + compiler/elf_fixup.cc | 92 +--- compiler/elf_writer_quick.cc | 28 + compiler/elf_writer_quick.h | 1 + compiler/image_writer.cc | 82 ++- compiler/image_writer.h | 5 +- dex2oat/dex2oat.cc | 36 +- patchoat/Android.mk | 45 ++ patchoat/patchoat.cc | 1020 ++++++++++++++++++++++++++++++++++++ patchoat/patchoat.h | 119 +++++ runtime/elf_file.cc | 19 +- runtime/elf_file.h | 1 + runtime/elf_utils.h | 97 ++++ runtime/image.cc | 33 +- runtime/image.h | 13 +- runtime/oat_file.h | 12 +- runtime/utils.h | 29 + 19 files changed, 1523 insertions(+), 129 deletions(-) create mode 100644 patchoat/Android.mk create mode 100644 patchoat/patchoat.cc create mode 100644 patchoat/patchoat.h diff --git a/Android.mk b/Android.mk index aef731f59..9c206400f 100644 --- a/Android.mk +++ b/Android.mk @@ -99,6 +99,7 @@ include $(art_path)/compiler/Android.mk include $(art_path)/dex2oat/Android.mk include $(art_path)/disassembler/Android.mk include $(art_path)/oatdump/Android.mk +include $(art_path)/patchoat/Android.mk include $(art_path)/dalvikvm/Android.mk include $(art_path)/tools/Android.mk include $(art_path)/build/Android.oat.mk @@ -364,13 +365,13 @@ build-art-target: $(ART_TARGET_EXECUTABLES) $(ART_TARGET_GTEST_EXECUTABLES) $(TA # "m art-host" for just building the files needed to run the art script .PHONY: art-host ifeq ($(HOST_PREFER_32_BIT),true) -art-host: $(HOST_OUT_EXECUTABLES)/art $(HOST_OUT)/bin/dalvikvm32 $(HOST_OUT)/lib/libart.so $(HOST_OUT)/bin/dex2oat $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so $(HOST_OUT)/bin/dalvikvm +art-host: $(HOST_OUT_EXECUTABLES)/art $(HOST_OUT)/bin/dalvikvm32 $(HOST_OUT)/lib/libart.so $(HOST_OUT)/bin/dex2oat $(HOST_OUT)/bin/patchoat $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so $(HOST_OUT)/bin/dalvikvm else -art-host: $(HOST_OUT_EXECUTABLES)/art $(HOST_OUT)/bin/dalvikvm64 $(HOST_OUT)/bin/dalvikvm32 $(HOST_OUT)/lib/libart.so $(HOST_OUT)/bin/dex2oat $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so $(HOST_OUT)/lib64/libjavacore.so $(HOST_OUT)/bin/dalvikvm +art-host: $(HOST_OUT_EXECUTABLES)/art $(HOST_OUT)/bin/dalvikvm64 $(HOST_OUT)/bin/dalvikvm32 $(HOST_OUT)/lib/libart.so $(HOST_OUT)/bin/dex2oat $(HOST_OUT)/bin/patchoat $(HOST_CORE_IMG_OUT) $(HOST_OUT)/lib/libjavacore.so $(HOST_OUT)/lib64/libjavacore.so $(HOST_OUT)/bin/dalvikvm endif .PHONY: art-host-debug -art-host-debug: art-host $(HOST_OUT)/lib/libartd.so $(HOST_OUT)/bin/dex2oatd +art-host-debug: art-host $(HOST_OUT)/lib/libartd.so $(HOST_OUT)/bin/dex2oatd $(HOST_OUT)/bin/patchoatd ######################################################################## # targets to switch back and forth from libdvm to libart diff --git a/build/Android.oat.mk b/build/Android.oat.mk index 916fd5804..d3c8d7ee4 100644 --- a/build/Android.oat.mk +++ b/build/Android.oat.mk @@ -35,7 +35,7 @@ $$($(1)HOST_CORE_IMG_OUT): $$(HOST_CORE_DEX_FILES) $$(DEX2OATD_DEPENDENCY) --oat-location=$$($(1)HOST_CORE_OAT) --image=$$($(1)HOST_CORE_IMG_OUT) \ --base=$$(LIBART_IMG_HOST_BASE_ADDRESS) --instruction-set=$$($(1)ART_HOST_ARCH) \ --instruction-set-features=$$($(1)HOST_INSTRUCTION_SET_FEATURES) \ - --host --android-root=$$(HOST_OUT) + --host --android-root=$$(HOST_OUT) --include-patch-information # This "renaming" eases declaration in art/Android.mk HOST_CORE_IMG_OUT$($(1)ART_PHONY_TEST_HOST_SUFFIX) := $($(1)HOST_CORE_IMG_OUT) @@ -64,7 +64,7 @@ $$($(1)TARGET_CORE_IMG_OUT): $$($(1)TARGET_CORE_DEX_FILES) $$(DEX2OATD_DEPENDENC --base=$$(LIBART_IMG_TARGET_BASE_ADDRESS) --instruction-set=$$($(1)TARGET_ARCH) \ --instruction-set-features=$$($(1)TARGET_INSTRUCTION_SET_FEATURES) \ --implicit-checks=$(IMPLICIT_CHECKS_$($(1)TARGET_ARCH)) \ - --android-root=$$(PRODUCT_OUT)/system + --android-root=$$(PRODUCT_OUT)/system --include-patch-information # This "renaming" eases declaration in art/Android.mk TARGET_CORE_IMG_OUT$($(1)ART_PHONY_TEST_TARGET_SUFFIX) := $($(1)TARGET_CORE_IMG_OUT) diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index fb3341ba7..92b2feeb7 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -43,6 +43,7 @@ class CompilerOptions { static const size_t kDefaultNumDexMethodsThreshold = 900; static constexpr double kDefaultTopKProfileThreshold = 90.0; static const bool kDefaultIncludeDebugSymbols = kIsDebugBuild; + static const bool kDefaultIncludePatchInformation = false; CompilerOptions() : compiler_filter_(kDefaultCompilerFilter), @@ -52,6 +53,7 @@ class CompilerOptions { tiny_method_threshold_(kDefaultTinyMethodThreshold), num_dex_methods_threshold_(kDefaultNumDexMethodsThreshold), generate_gdb_information_(false), + include_patch_information_(kDefaultIncludePatchInformation), top_k_profile_threshold_(kDefaultTopKProfileThreshold), include_debug_symbols_(kDefaultIncludeDebugSymbols), explicit_null_checks_(true), @@ -69,6 +71,7 @@ class CompilerOptions { size_t tiny_method_threshold, size_t num_dex_methods_threshold, bool generate_gdb_information, + bool include_patch_information, double top_k_profile_threshold, bool include_debug_symbols, bool explicit_null_checks, @@ -85,6 +88,7 @@ class CompilerOptions { tiny_method_threshold_(tiny_method_threshold), num_dex_methods_threshold_(num_dex_methods_threshold), generate_gdb_information_(generate_gdb_information), + include_patch_information_(include_patch_information), top_k_profile_threshold_(top_k_profile_threshold), include_debug_symbols_(include_debug_symbols), explicit_null_checks_(explicit_null_checks), @@ -188,6 +192,10 @@ class CompilerOptions { return generate_gdb_information_; } + bool GetIncludePatchInformation() const { + return include_patch_information_; + } + private: CompilerFilter compiler_filter_; size_t huge_method_threshold_; @@ -196,6 +204,7 @@ class CompilerOptions { size_t tiny_method_threshold_; size_t num_dex_methods_threshold_; bool generate_gdb_information_; + bool include_patch_information_; // When using a profile file only the top K% of the profiled samples will be compiled. double top_k_profile_threshold_; bool include_debug_symbols_; diff --git a/compiler/elf_fixup.cc b/compiler/elf_fixup.cc index 404e3f869..60f76efed 100644 --- a/compiler/elf_fixup.cc +++ b/compiler/elf_fixup.cc @@ -69,97 +69,7 @@ bool ElfFixup::FixupDynamic(ElfFile& elf_file, uintptr_t base_address) { for (Elf32_Word i = 0; i < elf_file.GetDynamicNum(); i++) { Elf32_Dyn& elf_dyn = elf_file.GetDynamic(i); Elf32_Word d_tag = elf_dyn.d_tag; - bool elf_dyn_needs_fixup = false; - switch (d_tag) { - // case 1: well known d_tag values that imply Elf32_Dyn.d_un contains an address in d_ptr - case DT_PLTGOT: - case DT_HASH: - case DT_STRTAB: - case DT_SYMTAB: - case DT_RELA: - case DT_INIT: - case DT_FINI: - case DT_REL: - case DT_DEBUG: - case DT_JMPREL: { - elf_dyn_needs_fixup = true; - break; - } - // d_val or ignored values - case DT_NULL: - case DT_NEEDED: - case DT_PLTRELSZ: - case DT_RELASZ: - case DT_RELAENT: - case DT_STRSZ: - case DT_SYMENT: - case DT_SONAME: - case DT_RPATH: - case DT_SYMBOLIC: - case DT_RELSZ: - case DT_RELENT: - case DT_PLTREL: - case DT_TEXTREL: - case DT_BIND_NOW: - case DT_INIT_ARRAYSZ: - case DT_FINI_ARRAYSZ: - case DT_RUNPATH: - case DT_FLAGS: { - break; - } - // boundary values that should not be used - case DT_ENCODING: - case DT_LOOS: - case DT_HIOS: - case DT_LOPROC: - case DT_HIPROC: { - LOG(FATAL) << "Illegal d_tag value 0x" << std::hex << d_tag; - break; - } - default: { - // case 2: "regular" DT_* ranges where even d_tag values imply an address in d_ptr - if ((DT_ENCODING < d_tag && d_tag < DT_LOOS) - || (DT_LOOS < d_tag && d_tag < DT_HIOS) - || (DT_LOPROC < d_tag && d_tag < DT_HIPROC)) { - // Special case for MIPS which breaks the regular rules between DT_LOPROC and DT_HIPROC - if (elf_file.GetHeader().e_machine == EM_MIPS) { - switch (d_tag) { - case DT_MIPS_RLD_VERSION: - case DT_MIPS_TIME_STAMP: - case DT_MIPS_ICHECKSUM: - case DT_MIPS_IVERSION: - case DT_MIPS_FLAGS: - case DT_MIPS_LOCAL_GOTNO: - case DT_MIPS_CONFLICTNO: - case DT_MIPS_LIBLISTNO: - case DT_MIPS_SYMTABNO: - case DT_MIPS_UNREFEXTNO: - case DT_MIPS_GOTSYM: - case DT_MIPS_HIPAGENO: { - break; - } - case DT_MIPS_BASE_ADDRESS: - case DT_MIPS_CONFLICT: - case DT_MIPS_LIBLIST: - case DT_MIPS_RLD_MAP: { - elf_dyn_needs_fixup = true; - break; - } - default: { - LOG(FATAL) << "Unknown MIPS d_tag value 0x" << std::hex << d_tag; - break; - } - } - } else if ((elf_dyn.d_tag % 2) == 0) { - elf_dyn_needs_fixup = true; - } - } else { - LOG(FATAL) << "Unknown d_tag value 0x" << std::hex << d_tag; - } - break; - } - } - if (elf_dyn_needs_fixup) { + if (IsDynamicSectionPointer(d_tag, elf_file.GetHeader().e_machine)) { uint32_t d_ptr = elf_dyn.d_un.d_ptr; if (DEBUG_FIXUP) { LOG(INFO) << StringPrintf("In %s moving Elf32_Dyn[%d] from 0x%08x to 0x%08" PRIxPTR, diff --git a/compiler/elf_writer_quick.cc b/compiler/elf_writer_quick.cc index e4dcaa742..06f6e89c7 100644 --- a/compiler/elf_writer_quick.cc +++ b/compiler/elf_writer_quick.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include + #include "elf_writer_quick.h" #include "base/logging.h" @@ -803,6 +805,25 @@ bool ElfWriterQuick::Create(File* elf_file, return elf_writer.Write(oat_writer, dex_files, android_root, is_host); } +// Add patch information to this section. Each patch is a Elf32_Word that +// identifies an offset from the start of the text section +void ElfWriterQuick::ReservePatchSpace(std::vector* buffer, bool debug) { + size_t size = + compiler_driver_->GetCodeToPatch().size() + + compiler_driver_->GetMethodsToPatch().size() + + compiler_driver_->GetClassesToPatch().size(); + if (size == 0) { + if (debug) { + LOG(INFO) << "No patches to record"; + } + return; + } + buffer->resize(size * sizeof(uintptr_t)); + if (debug) { + LOG(INFO) << "Patches reserved for " << size; + } +} + bool ElfWriterQuick::Write(OatWriter* oat_writer, const std::vector& dex_files_unused, const std::string& android_root_unused, @@ -836,6 +857,13 @@ bool ElfWriterQuick::Write(OatWriter* oat_writer, builder.RegisterRawSection(debug_str); } + if (compiler_driver_->GetCompilerOptions().GetIncludePatchInformation()) { + ElfRawSectionBuilder oat_patches(".oat_patches", SHT_OAT_PATCH, 0, NULL, 0, + sizeof(size_t), sizeof(size_t)); + ReservePatchSpace(oat_patches.GetBuffer(), debug); + builder.RegisterRawSection(oat_patches); + } + return builder.Write(); } diff --git a/compiler/elf_writer_quick.h b/compiler/elf_writer_quick.h index 6eb5d6881..a0d36df47 100644 --- a/compiler/elf_writer_quick.h +++ b/compiler/elf_writer_quick.h @@ -51,6 +51,7 @@ class ElfWriterQuick FINAL : public ElfWriter { void AddDebugSymbols(ElfBuilder& builder, OatWriter* oat_writer, bool debug); + void ReservePatchSpace(std::vector* buffer, bool debug); class ElfSectionBuilder { public: diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index 6e5f19a8c..2d25b7a2e 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -27,6 +27,8 @@ #include "compiled_method.h" #include "dex_file-inl.h" #include "driver/compiler_driver.h" +#include "elf_file.h" +#include "elf_utils.h" #include "elf_writer.h" #include "gc/accounting/card_table-inl.h" #include "gc/accounting/heap_bitmap.h" @@ -138,7 +140,8 @@ bool ImageWriter::Write(const std::string& image_filename, ElfWriter::GetOatElfInformation(oat_file.get(), oat_loaded_size, oat_data_offset); CalculateNewObjectOffsets(oat_loaded_size, oat_data_offset); CopyAndFixupObjects(); - PatchOatCodeAndMethods(); + + PatchOatCodeAndMethods(oat_file.get()); Thread::Current()->TransitionFromRunnableToSuspended(kNative); std::unique_ptr image_file(OS::CreateEmptyFile(image_filename.c_str())); @@ -782,7 +785,25 @@ static Class* GetTargetType(const CompilerDriver::TypePatchInformation* patch) return klass; } -void ImageWriter::PatchOatCodeAndMethods() { +void ImageWriter::PatchOatCodeAndMethods(File* elf_file) { + std::vector patches; + std::set patches_set; + auto maybe_push = [&patches, &patches_set] (uintptr_t p) { + if (patches_set.find(p) == patches_set.end()) { + patches.push_back(p); + patches_set.insert(p); + } + }; + const bool add_patches = compiler_driver_.GetCompilerOptions().GetIncludePatchInformation(); + if (add_patches) { + // TODO if we are adding patches the resulting ELF file might have a + // potentially rather large amount of free space where patches might have been + // placed. We should adjust the ELF file to get rid of this excess space. + patches.reserve(compiler_driver_.GetCodeToPatch().size() + + compiler_driver_.GetMethodsToPatch().size() + + compiler_driver_.GetClassesToPatch().size()); + } + uintptr_t loc = 0; Thread* self = Thread::Current(); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); const char* old_cause = self->StartAssertNoThreadSuspension("ImageWriter"); @@ -828,14 +849,20 @@ void ImageWriter::PatchOatCodeAndMethods() { } else { value = PointerToLowMemUInt32(GetOatAddress(code_offset)); } - SetPatchLocation(patch, value); + SetPatchLocation(patch, value, &loc); + if (add_patches && !patch->AsCall()->IsRelative()) { + maybe_push(loc); + } } const CallPatches& methods_to_patch = compiler_driver_.GetMethodsToPatch(); for (size_t i = 0; i < methods_to_patch.size(); i++) { const CompilerDriver::CallPatchInformation* patch = methods_to_patch[i]; ArtMethod* target = GetTargetMethod(patch); - SetPatchLocation(patch, PointerToLowMemUInt32(GetImageAddress(target))); + SetPatchLocation(patch, PointerToLowMemUInt32(GetImageAddress(target)), &loc); + if (add_patches && !patch->AsCall()->IsRelative()) { + maybe_push(loc); + } } const std::vector& classes_to_patch = @@ -843,16 +870,51 @@ void ImageWriter::PatchOatCodeAndMethods() { for (size_t i = 0; i < classes_to_patch.size(); i++) { const CompilerDriver::TypePatchInformation* patch = classes_to_patch[i]; Class* target = GetTargetType(patch); - SetPatchLocation(patch, PointerToLowMemUInt32(GetImageAddress(target))); + SetPatchLocation(patch, PointerToLowMemUInt32(GetImageAddress(target)), &loc); + if (add_patches) { + maybe_push(loc); + } } // Update the image header with the new checksum after patching ImageHeader* image_header = reinterpret_cast(image_->Begin()); image_header->SetOatChecksum(oat_file_->GetOatHeader().GetChecksum()); self->EndAssertNoThreadSuspension(old_cause); + + // Update the ElfFiles SHT_OAT_PATCH section to include the patches. + if (add_patches) { + std::string err; + // TODO we are mapping in the contents of this file twice. We should be able + // to do it only once, which would be better. + std::unique_ptr file(ElfFile::Open(elf_file, true, false, &err)); + if (file == nullptr) { + LOG(ERROR) << err; + } + Elf32_Shdr* shdr = file->FindSectionByName(".oat_patches"); + if (shdr != nullptr) { + DCHECK_EQ(shdr, file->FindSectionByType(SHT_OAT_PATCH)) + << "Incorrect type for .oat_patches section"; + CHECK_LE(patches.size() * sizeof(uintptr_t), shdr->sh_size) + << "We got more patches than anticipated"; + CHECK_LE(reinterpret_cast(file->Begin()) + shdr->sh_offset + shdr->sh_size, + reinterpret_cast(file->End())) << "section is too large"; + CHECK(shdr == &file->GetSectionHeader(file->GetSectionHeaderNum() - 1) || + shdr->sh_offset + shdr->sh_size <= (shdr + 1)->sh_offset) + << "Section overlaps onto next section"; + // It's mmap'd so we can just memcpy. + memcpy(file->Begin() + shdr->sh_offset, patches.data(), patches.size()*sizeof(uintptr_t)); + // TODO We should fill in the newly empty space between the last patch and + // the start of the next section by moving the following sections down if + // possible. + shdr->sh_size = patches.size() * sizeof(uintptr_t); + } else { + LOG(ERROR) << "Unable to find section header for SHT_OAT_PATCH"; + } + } } -void ImageWriter::SetPatchLocation(const CompilerDriver::PatchInformation* patch, uint32_t value) { +void ImageWriter::SetPatchLocation(const CompilerDriver::PatchInformation* patch, uint32_t value, + uintptr_t* patched_ptr) { ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); const void* quick_oat_code = class_linker->GetQuickOatCodeFor(patch->GetDexFile(), patch->GetReferrerClassDefIdx(), @@ -885,6 +947,14 @@ void ImageWriter::SetPatchLocation(const CompilerDriver::PatchInformation* patch } *patch_location = value; oat_header.UpdateChecksum(patch_location, sizeof(value)); + + uintptr_t loc = reinterpret_cast(patch_location) - + (reinterpret_cast(oat_file_->Begin()) + oat_header.GetExecutableOffset()); + CHECK_GT(reinterpret_cast(patch_location), + reinterpret_cast(oat_file_->Begin()) + oat_header.GetExecutableOffset()); + CHECK_LT(loc, oat_file_->Size() - oat_header.GetExecutableOffset()); + + *patched_ptr = loc; } } // namespace art diff --git a/compiler/image_writer.h b/compiler/image_writer.h index aff155aff..2bcb41e3f 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -150,9 +150,10 @@ class ImageWriter { SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); // Patches references in OatFile to expect runtime addresses. - void PatchOatCodeAndMethods() + void PatchOatCodeAndMethods(File* elf_file) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - void SetPatchLocation(const CompilerDriver::PatchInformation* patch, uint32_t value) + void SetPatchLocation(const CompilerDriver::PatchInformation* patch, uint32_t value, + uintptr_t* patched_location) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); const CompilerDriver& compiler_driver_; diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 3387f914b..80e77245f 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -60,6 +60,7 @@ #include "runtime.h" #include "ScopedLocalRef.h" #include "scoped_thread_state_change.h" +#include "utils.h" #include "vector_output_stream.h" #include "well_known_classes.h" #include "zip_archive.h" @@ -144,8 +145,8 @@ static void Usage(const char* fmt, ...) { UsageError(" Example: --android-root=out/host/linux-x86"); UsageError(" Default: $ANDROID_ROOT"); UsageError(""); - UsageError(" --instruction-set=(arm|arm64|mips|x86|x86_64): compile for a particular instruction"); - UsageError(" set."); + UsageError(" --instruction-set=(arm|arm64|mips|x86|x86_64): compile for a particular"); + UsageError(" instruction set."); UsageError(" Example: --instruction-set=x86"); UsageError(" Default: arm"); UsageError(""); @@ -203,6 +204,11 @@ static void Usage(const char* fmt, ...) { UsageError(""); UsageError(" --dump-timing: display a breakdown of where time was spent"); UsageError(""); + UsageError(" --include-patch-information: Include patching information so the generated code"); + UsageError(" can have its base address moved without full recompilation."); + UsageError(""); + UsageError(" --no-include-patch-information: Do not include patching information."); + UsageError(""); UsageError(" --include-debug-symbols: Include ELF symbols in this oat file"); UsageError(""); UsageError(" --no-include-debug-symbols: Do not include ELF symbols in this oat file"); @@ -271,7 +277,7 @@ class Dex2Oat { LOG(ERROR) << "Failed to open image classes file " << image_classes_filename; return nullptr; } - std::unique_ptr result(ReadImageClasses(*image_classes_file.get())); + std::unique_ptr result(ReadImageClasses(*image_classes_file)); image_classes_file->close(); return result.release(); } @@ -510,16 +516,6 @@ class Dex2Oat { DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat); }; -static bool ParseInt(const char* in, int* out) { - char* end; - int result = strtol(in, &end, 10); - if (in == end || *end != '\0') { - return false; - } - *out = result; - return true; -} - static size_t OpenDexFiles(const std::vector& dex_filenames, const std::vector& dex_locations, std::vector& dex_files) { @@ -827,6 +823,8 @@ static int dex2oat(int argc, char** argv) { bool dump_stats = false; bool dump_timing = false; bool dump_passes = false; + bool include_patch_information = CompilerOptions::kDefaultIncludePatchInformation; + bool explicit_include_patch_information = false; bool include_debug_symbols = kIsDebugBuild; bool dump_slow_timing = kIsDebugBuild; bool watch_dog_enabled = !kIsTargetBuild; @@ -1037,6 +1035,12 @@ static int dex2oat(int argc, char** argv) { } } has_explicit_checks_options = true; + } else if (option == "--include-patch-information") { + include_patch_information = true; + explicit_include_patch_information = true; + } else if (option == "--no-include-patch-information") { + include_patch_information = false; + explicit_include_patch_information = true; } else { Usage("Unknown argument %s", option.data()); } @@ -1166,6 +1170,11 @@ static int dex2oat(int argc, char** argv) { CheckExplicitCheckOptions(instruction_set, &explicit_null_checks, &explicit_so_checks, &explicit_suspend_checks); + if (!explicit_include_patch_information) { + include_patch_information = + (compiler_kind == Compiler::kQuick && CompilerOptions::kDefaultIncludePatchInformation); + } + CompilerOptions compiler_options(compiler_filter, huge_method_threshold, large_method_threshold, @@ -1173,6 +1182,7 @@ static int dex2oat(int argc, char** argv) { tiny_method_threshold, num_dex_methods_threshold, generate_gdb_information, + include_patch_information, top_k_profile_threshold, include_debug_symbols, explicit_null_checks, diff --git a/patchoat/Android.mk b/patchoat/Android.mk new file mode 100644 index 000000000..8b6b9ad77 --- /dev/null +++ b/patchoat/Android.mk @@ -0,0 +1,45 @@ +# +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include art/build/Android.executable.mk + +PATCHOAT_SRC_FILES := \ + patchoat.cc + +# TODO: Remove this when the framework (installd) supports pushing the +# right instruction-set parameter for the primary architecture. +ifneq ($(filter ro.zygote=zygote64,$(PRODUCT_DEFAULT_PROPERTY_OVERRIDES)),) + patchoat_arch := 64 +else + patchoat_arch := 32 +endif + +ifeq ($(ART_BUILD_TARGET_NDEBUG),true) + $(eval $(call build-art-executable,patchoat,$(PATCHOAT_SRC_FILES),libcutils,art/compiler,target,ndebug,$(patchoat_arch))) +endif +ifeq ($(ART_BUILD_TARGET_DEBUG),true) + $(eval $(call build-art-executable,patchoat,$(PATCHOAT_SRC_FILES),libcutils,art/compiler,target,debug,$(patchoat_arch))) +endif + +# We always build patchoat and dependencies, even if the host build is otherwise disabled, since they are used to cross compile for the target. +ifeq ($(ART_BUILD_NDEBUG),true) + $(eval $(call build-art-executable,patchoat,$(PATCHOAT_SRC_FILES),,art/compiler,host,ndebug)) +endif +ifeq ($(ART_BUILD_DEBUG),true) + $(eval $(call build-art-executable,patchoat,$(PATCHOAT_SRC_FILES),,art/compiler,host,debug)) +endif diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc new file mode 100644 index 000000000..ea4b880b8 --- /dev/null +++ b/patchoat/patchoat.cc @@ -0,0 +1,1020 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "patchoat.h" + +#include +#include +#include + +#include +#include + +#include "base/stringpiece.h" +#include "base/stringprintf.h" +#include "elf_utils.h" +#include "elf_file.h" +#include "image.h" +#include "instruction_set.h" +#include "mirror/art_field.h" +#include "mirror/art_field-inl.h" +#include "mirror/art_method.h" +#include "mirror/art_method-inl.h" +#include "mirror/object.h" +#include "mirror/object-inl.h" +#include "mirror/reference.h" +#include "noop_compiler_callbacks.h" +#include "offsets.h" +#include "os.h" +#include "runtime.h" +#include "scoped_thread_state_change.h" +#include "thread.h" +#include "utils.h" + +namespace art { + +static InstructionSet ElfISAToInstructionSet(Elf32_Word isa) { + switch (isa) { + case EM_ARM: + return kArm; + case EM_AARCH64: + return kArm64; + case EM_386: + return kX86; + case EM_X86_64: + return kX86_64; + case EM_MIPS: + return kMips; + default: + return kNone; + } +} + +bool PatchOat::Patch(const std::string& image_location, off_t delta, + File* output_image, InstructionSet isa, + TimingLogger& timings) { + std::string error_msg; + CHECK(Runtime::Current() == nullptr); + CHECK(output_image != nullptr); + CHECK_GE(output_image->Fd(), 0); + CHECK(!image_location.empty()) << "image file must have a filename."; + CHECK_NE(isa, kNone); + + TimingLogger::ScopedTiming t("Runtime Setup", &timings); + const char *isa_name = GetInstructionSetString(isa); + std::string image_filename(GetSystemImageFilename(image_location.c_str(), isa)); + std::unique_ptr input_image(OS::OpenFileForReading(image_filename.c_str())); + if (input_image.get() == nullptr) { + LOG(ERROR) << "unable to open input image file."; + return false; + } + int64_t image_len = input_image->GetLength(); + if (image_len < 0) { + LOG(ERROR) << "Error while getting image length"; + return false; + } + ImageHeader image_header; + if (sizeof(image_header) != input_image->Read(reinterpret_cast(&image_header), + sizeof(image_header), 0)) { + LOG(ERROR) << "Unable to read image header from image file " << input_image->GetPath(); + return false; + } + + // Set up the runtime + Runtime::Options options; + NoopCompilerCallbacks callbacks; + options.push_back(std::make_pair("compilercallbacks", &callbacks)); + std::string img = "-Ximage:" + image_location; + options.push_back(std::make_pair(img.c_str(), nullptr)); + options.push_back(std::make_pair("imageinstructionset", reinterpret_cast(isa_name))); + if (!Runtime::Create(options, false)) { + LOG(ERROR) << "Unable to initialize runtime"; + return false; + } + // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start, + // give it away now and then switch to a more manageable ScopedObjectAccess. + Thread::Current()->TransitionFromRunnableToSuspended(kNative); + ScopedObjectAccess soa(Thread::Current()); + + t.NewTiming("Image and oat Patching setup"); + // Create the map where we will write the image patches to. + std::unique_ptr image(MemMap::MapFile(image_len, PROT_READ | PROT_WRITE, MAP_PRIVATE, + input_image->Fd(), 0, + input_image->GetPath().c_str(), + &error_msg)); + if (image.get() == nullptr) { + LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg; + return false; + } + gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace(); + + PatchOat p(image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), + delta, timings); + t.NewTiming("Patching files"); + if (!p.PatchImage()) { + LOG(INFO) << "Failed to patch image file " << input_image->GetPath(); + return false; + } + + t.NewTiming("Writing files"); + if (!p.WriteImage(output_image)) { + return false; + } + return true; +} + +bool PatchOat::Patch(const File* input_oat, const std::string& image_location, off_t delta, + File* output_oat, File* output_image, InstructionSet isa, + TimingLogger& timings) { + std::string error_msg; + CHECK(Runtime::Current() == nullptr); + CHECK(output_image != nullptr); + CHECK_GE(output_image->Fd(), 0); + CHECK(input_oat != nullptr); + CHECK(output_oat != nullptr); + CHECK_GE(input_oat->Fd(), 0); + CHECK_GE(output_oat->Fd(), 0); + CHECK(!image_location.empty()) << "image file must have a filename."; + + TimingLogger::ScopedTiming t("Runtime Setup", &timings); + + if (isa == kNone) { + Elf32_Ehdr elf_hdr; + if (sizeof(elf_hdr) != input_oat->Read(reinterpret_cast(&elf_hdr), sizeof(elf_hdr), 0)) { + LOG(ERROR) << "unable to read elf header"; + return false; + } + isa = ElfISAToInstructionSet(elf_hdr.e_machine); + } + const char* isa_name = GetInstructionSetString(isa); + std::string image_filename(GetSystemImageFilename(image_location.c_str(), isa)); + std::unique_ptr input_image(OS::OpenFileForReading(image_filename.c_str())); + if (input_image.get() == nullptr) { + LOG(ERROR) << "unable to open input image file."; + return false; + } + int64_t image_len = input_image->GetLength(); + if (image_len < 0) { + LOG(ERROR) << "Error while getting image length"; + return false; + } + ImageHeader image_header; + if (sizeof(image_header) != input_image->Read(reinterpret_cast(&image_header), + sizeof(image_header), 0)) { + LOG(ERROR) << "Unable to read image header from image file " << input_image->GetPath(); + } + + // Set up the runtime + Runtime::Options options; + NoopCompilerCallbacks callbacks; + options.push_back(std::make_pair("compilercallbacks", &callbacks)); + std::string img = "-Ximage:" + image_location; + options.push_back(std::make_pair(img.c_str(), nullptr)); + options.push_back(std::make_pair("imageinstructionset", reinterpret_cast(isa_name))); + if (!Runtime::Create(options, false)) { + LOG(ERROR) << "Unable to initialize runtime"; + return false; + } + // Runtime::Create acquired the mutator_lock_ that is normally given away when we Runtime::Start, + // give it away now and then switch to a more manageable ScopedObjectAccess. + Thread::Current()->TransitionFromRunnableToSuspended(kNative); + ScopedObjectAccess soa(Thread::Current()); + + t.NewTiming("Image and oat Patching setup"); + // Create the map where we will write the image patches to. + std::unique_ptr image(MemMap::MapFile(image_len, PROT_READ | PROT_WRITE, MAP_PRIVATE, + input_image->Fd(), 0, + input_image->GetPath().c_str(), + &error_msg)); + if (image.get() == nullptr) { + LOG(ERROR) << "unable to map image file " << input_image->GetPath() << " : " << error_msg; + return false; + } + gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace(); + + std::unique_ptr elf(ElfFile::Open(const_cast(input_oat), + PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg)); + if (elf.get() == nullptr) { + LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg; + return false; + } + + PatchOat p(elf.release(), image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), + delta, timings); + t.NewTiming("Patching files"); + if (!p.PatchElf()) { + LOG(INFO) << "Failed to patch oat file " << input_oat->GetPath(); + return false; + } + if (!p.PatchImage()) { + LOG(INFO) << "Failed to patch image file " << input_image->GetPath(); + return false; + } + + t.NewTiming("Writing files"); + if (!p.WriteElf(output_oat)) { + return false; + } + if (!p.WriteImage(output_image)) { + return false; + } + return true; +} + +bool PatchOat::WriteElf(File* out) { + TimingLogger::ScopedTiming t("Writing Elf File", &timings_); + CHECK(oat_file_.get() != nullptr); + CHECK(out != nullptr); + size_t expect = oat_file_->Size(); + if (out->WriteFully(reinterpret_cast(oat_file_->Begin()), expect) && + out->SetLength(expect) == 0) { + return true; + } else { + LOG(ERROR) << "Writing to oat file " << out->GetPath() << " failed."; + return false; + } +} + +bool PatchOat::WriteImage(File* out) { + TimingLogger::ScopedTiming t("Writing image File", &timings_); + CHECK(image_ != nullptr); + CHECK(out != nullptr); + size_t expect = image_->Size(); + if (out->WriteFully(reinterpret_cast(image_->Begin()), expect) && + out->SetLength(expect) == 0) { + return true; + } else { + LOG(ERROR) << "Writing to image file " << out->GetPath() << " failed."; + return false; + } +} + +bool PatchOat::PatchImage() { + ImageHeader* image_header = reinterpret_cast(image_->Begin()); + CHECK_GT(image_->Size(), sizeof(ImageHeader)); + // These are the roots from the original file. + mirror::Object* img_roots = image_header->GetImageRoots(); + image_header->RelocateImage(delta_); + + VisitObject(img_roots); + if (!image_header->IsValid()) { + LOG(ERROR) << "reloction renders image header invalid"; + return false; + } + + { + TimingLogger::ScopedTiming t("Walk Bitmap", &timings_); + // Walk the bitmap. + WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_); + bitmap_->Walk(PatchOat::BitmapCallback, this); + } + return true; +} + +bool PatchOat::InHeap(mirror::Object* o) { + uintptr_t begin = reinterpret_cast(heap_->Begin()); + uintptr_t end = reinterpret_cast(heap_->End()); + uintptr_t obj = reinterpret_cast(o); + return o == nullptr || (begin <= obj && obj < end); +} + +void PatchOat::PatchVisitor::operator() (mirror::Object* obj, MemberOffset off, + bool is_static_unused) const { + mirror::Object* referent = obj->GetFieldObject(off); + DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap."; + mirror::Object* moved_object = patcher_->RelocatedAddressOf(referent); + copy_->SetFieldObjectWithoutWriteBarrier(off, moved_object); +} + +void PatchOat::PatchVisitor::operator() (mirror::Class* cls, mirror::Reference* ref) const { + MemberOffset off = mirror::Reference::ReferentOffset(); + mirror::Object* referent = ref->GetReferent(); + DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap."; + mirror::Object* moved_object = patcher_->RelocatedAddressOf(referent); + copy_->SetFieldObjectWithoutWriteBarrier(off, moved_object); +} + +mirror::Object* PatchOat::RelocatedCopyOf(mirror::Object* obj) { + if (obj == nullptr) { + return nullptr; + } + DCHECK_GT(reinterpret_cast(obj), reinterpret_cast(heap_->Begin())); + DCHECK_LT(reinterpret_cast(obj), reinterpret_cast(heap_->End())); + uintptr_t heap_off = + reinterpret_cast(obj) - reinterpret_cast(heap_->Begin()); + DCHECK_LT(heap_off, image_->Size()); + return reinterpret_cast(image_->Begin() + heap_off); +} + +mirror::Object* PatchOat::RelocatedAddressOf(mirror::Object* obj) { + if (obj == nullptr) { + return nullptr; + } else { + return reinterpret_cast(reinterpret_cast(obj) + delta_); + } +} + +// Called by BitmapCallback +void PatchOat::VisitObject(mirror::Object* object) { + mirror::Object* copy = RelocatedCopyOf(object); + CHECK(copy != nullptr); + if (kUseBakerOrBrooksReadBarrier) { + object->AssertReadBarrierPointer(); + if (kUseBrooksReadBarrier) { + mirror::Object* moved_to = RelocatedAddressOf(object); + copy->SetReadBarrierPointer(moved_to); + DCHECK_EQ(copy->GetReadBarrierPointer(), moved_to); + } + } + PatchOat::PatchVisitor visitor(this, copy); + object->VisitReferences(visitor, visitor); + if (object->IsArtMethod()) { + FixupMethod(static_cast(object), + static_cast(copy)); + } +} + +void PatchOat::FixupMethod(mirror::ArtMethod* object, mirror::ArtMethod* copy) { + // Just update the entry points if it looks like we should. + // TODO sanity check all the pointers' values + uintptr_t portable = reinterpret_cast( + object->GetEntryPointFromPortableCompiledCode()); + if (portable != 0) { + copy->SetEntryPointFromPortableCompiledCode(reinterpret_cast(portable + delta_)); + } + uintptr_t quick= reinterpret_cast( + object->GetEntryPointFromQuickCompiledCode()); + if (quick != 0) { + copy->SetEntryPointFromQuickCompiledCode(reinterpret_cast(quick + delta_)); + } + uintptr_t interpreter = reinterpret_cast( + object->GetEntryPointFromInterpreter()); + if (interpreter != 0) { + copy->SetEntryPointFromInterpreter( + reinterpret_cast(interpreter + delta_)); + } + + uintptr_t native_method = reinterpret_cast(object->GetNativeMethod()); + if (native_method != 0) { + copy->SetNativeMethod(reinterpret_cast(native_method + delta_)); + } + + uintptr_t native_gc_map = reinterpret_cast(object->GetNativeGcMap()); + if (native_gc_map != 0) { + copy->SetNativeGcMap(reinterpret_cast(native_gc_map + delta_)); + } +} + +bool PatchOat::Patch(File* input_oat, off_t delta, File* output_oat, TimingLogger& timings) { + CHECK(input_oat != nullptr); + CHECK(output_oat != nullptr); + CHECK_GE(input_oat->Fd(), 0); + CHECK_GE(output_oat->Fd(), 0); + TimingLogger::ScopedTiming t("Setup Oat File Patching", &timings); + + std::string error_msg; + std::unique_ptr elf(ElfFile::Open(const_cast(input_oat), + PROT_READ | PROT_WRITE, MAP_PRIVATE, &error_msg)); + if (elf.get() == nullptr) { + LOG(ERROR) << "unable to open oat file " << input_oat->GetPath() << " : " << error_msg; + return false; + } + + PatchOat p(elf.release(), delta, timings); + t.NewTiming("Patch Oat file"); + if (!p.PatchElf()) { + return false; + } + + t.NewTiming("Writing oat file"); + if (!p.WriteElf(output_oat)) { + return false; + } + return true; +} + +bool PatchOat::CheckOatFile() { + Elf32_Shdr* patches_sec = oat_file_->FindSectionByName(".oat_patches"); + if (patches_sec == nullptr) { + return false; + } + if (patches_sec->sh_type != SHT_OAT_PATCH) { + return false; + } + uintptr_t* patches = reinterpret_cast(oat_file_->Begin() + patches_sec->sh_offset); + uintptr_t* patches_end = patches + (patches_sec->sh_size/sizeof(uintptr_t)); + Elf32_Shdr* oat_data_sec = oat_file_->FindSectionByName(".rodata"); + Elf32_Shdr* oat_text_sec = oat_file_->FindSectionByName(".text"); + if (oat_data_sec == nullptr) { + return false; + } + if (oat_text_sec == nullptr) { + return false; + } + if (oat_text_sec->sh_offset <= oat_data_sec->sh_offset) { + return false; + } + + for (; patches < patches_end; patches++) { + if (oat_text_sec->sh_size <= *patches) { + return false; + } + } + + return true; +} + +bool PatchOat::PatchElf() { + TimingLogger::ScopedTiming t("Fixup Elf Headers", &timings_); + // Fixup Phdr's + for (unsigned int i = 0; i < oat_file_->GetProgramHeaderNum(); i++) { + Elf32_Phdr& hdr = oat_file_->GetProgramHeader(i); + if (hdr.p_vaddr != 0) { + hdr.p_vaddr += delta_; + } + if (hdr.p_paddr != 0) { + hdr.p_paddr += delta_; + } + } + // Fixup Shdr's + for (unsigned int i = 0; i < oat_file_->GetSectionHeaderNum(); i++) { + Elf32_Shdr& hdr = oat_file_->GetSectionHeader(i); + if (hdr.sh_addr != 0) { + hdr.sh_addr += delta_; + } + } + + // Fixup Dynamics. + for (Elf32_Word i = 0; i < oat_file_->GetDynamicNum(); i++) { + Elf32_Dyn& dyn = oat_file_->GetDynamic(i); + if (IsDynamicSectionPointer(dyn.d_tag, oat_file_->GetHeader().e_machine)) { + dyn.d_un.d_ptr += delta_; + } + } + + t.NewTiming("Fixup Elf Symbols"); + // Fixup dynsym + Elf32_Shdr* dynsym_sec = oat_file_->FindSectionByName(".dynsym"); + CHECK(dynsym_sec != nullptr); + if (!PatchSymbols(dynsym_sec)) { + return false; + } + + // Fixup symtab + Elf32_Shdr* symtab_sec = oat_file_->FindSectionByName(".symtab"); + if (symtab_sec != nullptr) { + if (!PatchSymbols(symtab_sec)) { + return false; + } + } + + t.NewTiming("Fixup Elf Text Section"); + // Fixup text + if (!PatchTextSection()) { + return false; + } + + return true; +} + +bool PatchOat::PatchSymbols(Elf32_Shdr* section) { + Elf32_Sym* syms = reinterpret_cast(oat_file_->Begin() + section->sh_offset); + const Elf32_Sym* last_sym = + reinterpret_cast(oat_file_->Begin() + section->sh_offset + section->sh_size); + CHECK_EQ(section->sh_size % sizeof(Elf32_Sym), 0u) + << "Symtab section size is not multiple of symbol size"; + for (; syms < last_sym; syms++) { + uint8_t sttype = ELF32_ST_TYPE(syms->st_info); + Elf32_Word shndx = syms->st_shndx; + if (shndx != SHN_ABS && shndx != SHN_COMMON && shndx != SHN_UNDEF && + (sttype == STT_FUNC || sttype == STT_OBJECT)) { + CHECK_NE(syms->st_value, 0u); + syms->st_value += delta_; + } + } + return true; +} + +bool PatchOat::PatchTextSection() { + Elf32_Shdr* patches_sec = oat_file_->FindSectionByName(".oat_patches"); + if (patches_sec == nullptr) { + LOG(INFO) << ".oat_patches section not found. Aborting patch"; + return false; + } + DCHECK(CheckOatFile()) << "Oat file invalid"; + CHECK_EQ(patches_sec->sh_type, SHT_OAT_PATCH) << "Unexpected type of .oat_patches"; + uintptr_t* patches = reinterpret_cast(oat_file_->Begin() + patches_sec->sh_offset); + uintptr_t* patches_end = patches + (patches_sec->sh_size/sizeof(uintptr_t)); + Elf32_Shdr* oat_text_sec = oat_file_->FindSectionByName(".text"); + CHECK(oat_text_sec != nullptr); + byte* to_patch = oat_file_->Begin() + oat_text_sec->sh_offset; + uintptr_t to_patch_end = reinterpret_cast(to_patch) + oat_text_sec->sh_size; + + for (; patches < patches_end; patches++) { + CHECK_LT(*patches, oat_text_sec->sh_size) << "Bad Patch"; + uint32_t* patch_loc = reinterpret_cast(to_patch + *patches); + CHECK_LT(reinterpret_cast(patch_loc), to_patch_end); + *patch_loc += delta_; + } + + return true; +} + +static int orig_argc; +static char** orig_argv; + +static std::string CommandLine() { + std::vector command; + for (int i = 0; i < orig_argc; ++i) { + command.push_back(orig_argv[i]); + } + return Join(command, ' '); +} + +static void UsageErrorV(const char* fmt, va_list ap) { + std::string error; + StringAppendV(&error, fmt, ap); + LOG(ERROR) << error; +} + +static void UsageError(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); +} + +static void Usage(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + UsageErrorV(fmt, ap); + va_end(ap); + + UsageError("Command: %s", CommandLine().c_str()); + UsageError("Usage: patchoat [options]..."); + UsageError(""); + UsageError(" --instruction-set=: Specifies the instruction set the patched code is"); + UsageError(" compiled for. Required if you use --input-oat-location"); + UsageError(""); + UsageError(" --input-oat-file=: Specifies the exact filename of the oat file to be"); + UsageError(" patched."); + UsageError(""); + UsageError(" --input-oat-fd=: Specifies the file-descriptor of the oat file"); + UsageError(" to be patched."); + UsageError(""); + UsageError(" --input-oat-location=: Specifies the 'location' to read the patched"); + UsageError(" oat file from. If used one must also supply the --instruction-set"); + UsageError(""); + UsageError(" --input-image-location=: Specifies the 'location' of the image file to"); + UsageError(" be patched. If --instruction-set is not given it will use the instruction set"); + UsageError(" extracted from the --input-oat-file."); + UsageError(""); + UsageError(" --output-oat-file=: Specifies the exact file to write the patched oat"); + UsageError(" file to."); + UsageError(""); + UsageError(" --output-oat-location=: Specifies the 'location' to write the patched"); + UsageError(" oat file to. If used one must also specify the --instruction-set"); + UsageError(""); + UsageError(" --output-oat-fd=: Specifies the file-descriptor to write the"); + UsageError(" the patched oat file to."); + UsageError(""); + UsageError(" --output-image-file=: Specifies the exact file to write the patched"); + UsageError(" image file to."); + UsageError(""); + UsageError(" --output-image-fd=: Specifies the file-descriptor to write the"); + UsageError(" the patched image file to."); + UsageError(""); + UsageError(" --output-image-location=: Specifies the 'location' to write the patched"); + UsageError(" image file to. If used one must also specify the --instruction-set"); + UsageError(""); + UsageError(" --orig-base-offset=: Specify the base offset the input file"); + UsageError(" was compiled with. This is needed if one is specifying a --base-offset"); + UsageError(""); + UsageError(" --base-offset=: Specify the base offset we will repatch the"); + UsageError(" given files to use. This requires that --orig-base-offset is also given."); + UsageError(""); + UsageError(" --base-offset-delta=: Specify the amount to change the old base-offset by."); + UsageError(" This value may be negative."); + UsageError(""); + UsageError(" --patched-image-file=: Use the same patch delta as was used to patch"); + UsageError(" the given image file."); + UsageError(""); + UsageError(" --patched-image-location=: Use the same patch delta as was used to"); + UsageError(" patch the given image location. If used one must also specify the"); + UsageError(" --instruction-set flag."); + UsageError(""); + UsageError(" --dump-timings: dump out patch timing information"); + UsageError(""); + UsageError(" --no-dump-timings: do not dump out patch timing information"); + UsageError(""); + + exit(EXIT_FAILURE); +} + +static bool ReadBaseDelta(const char* name, off_t* delta, std::string& error_msg) { + CHECK(name != nullptr); + CHECK(delta != nullptr); + std::unique_ptr file; + if (OS::FileExists(name)) { + file.reset(OS::OpenFileForReading(name)); + if (file.get() == nullptr) { + error_msg = "Failed to open file %s for reading"; + return false; + } + } else { + error_msg = "File %s does not exist"; + return false; + } + CHECK(file.get() != nullptr); + ImageHeader hdr; + if (sizeof(hdr) != file->Read(reinterpret_cast(&hdr), sizeof(hdr), 0)) { + error_msg = "Failed to read file %s"; + return false; + } + if (!hdr.IsValid()) { + error_msg = "%s does not contain a valid image header."; + return false; + } + *delta = hdr.GetPatchDelta(); + return true; +} + +static File* CreateOrOpen(const char* name, bool* created) { + if (OS::FileExists(name)) { + *created = false; + return OS::OpenFileReadWrite(name); + } else { + *created = true; + return OS::CreateEmptyFile(name); + } +} + +int patchoat(int argc, char **argv) { + InitLogging(argv); + const bool debug = kIsDebugBuild; + orig_argc = argc; + orig_argv = argv; + TimingLogger timings("patcher", false, false); + + InitLogging(argv); + + // Skip over the command name. + argv++; + argc--; + + if (argc == 0) { + Usage("No arguments specified"); + } + + timings.StartTiming("Patchoat"); + + // cmd line args + bool isa_set = false; + InstructionSet isa = kNone; + std::string input_oat_filename; + std::string input_oat_location; + int input_oat_fd = -1; + bool have_input_oat = false; + std::string input_image_location; + std::string output_oat_filename; + std::string output_oat_location; + int output_oat_fd = -1; + bool have_output_oat = false; + std::string output_image_filename; + std::string output_image_location; + int output_image_fd = -1; + bool have_output_image = false; + uintptr_t base_offset = 0; + bool base_offset_set = false; + uintptr_t orig_base_offset = 0; + bool orig_base_offset_set = false; + off_t base_delta = 0; + bool base_delta_set = false; + std::string patched_image_filename; + std::string patched_image_location; + bool dump_timings = kIsDebugBuild; + + for (int i = 0; i < argc; i++) { + const StringPiece option(argv[i]); + const bool log_options = false; + if (log_options) { + LOG(INFO) << "patchoat: option[" << i << "]=" << argv[i]; + } + // TODO GetInstructionSetFromString shouldn't LOG(FATAL). + if (option.starts_with("--instruction-set=")) { + isa_set = true; + const char* isa_str = option.substr(strlen("--instruction-set=")).data(); + if (!strcmp("arm", isa_str)) { + isa = kArm; + } else if (!strcmp("arm64", isa_str)) { + isa = kArm64; + } else if (!strcmp("x86", isa_str)) { + isa = kX86; + } else if (!strcmp("x86_64", isa_str)) { + isa = kX86_64; + } else if (!strcmp("mips", isa_str)) { + isa = kMips; + } else { + Usage("Unknown instruction set %s", isa_str); + } + } else if (option.starts_with("--input-oat-location=")) { + if (have_input_oat) { + Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); + } + have_input_oat = true; + input_oat_location = option.substr(strlen("--input-oat-location=")).data(); + } else if (option.starts_with("--input-oat-file=")) { + if (have_input_oat) { + Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); + } + have_input_oat = true; + input_oat_filename = option.substr(strlen("--input-oat-file=")).data(); + } else if (option.starts_with("--input-oat-fd=")) { + if (have_input_oat) { + Usage("Only one of --input-oat-file, --input-oat-location and --input-oat-fd may be used."); + } + have_input_oat = true; + const char* oat_fd_str = option.substr(strlen("--input-oat-fd=")).data(); + if (!ParseInt(oat_fd_str, &input_oat_fd)) { + Usage("Failed to parse --input-oat-fd argument '%s' as an integer", oat_fd_str); + } + if (input_oat_fd < 0) { + Usage("--input-oat-fd pass a negative value %d", input_oat_fd); + } + } else if (option.starts_with("--input-image-location=")) { + input_image_location = option.substr(strlen("--input-image-location=")).data(); + } else if (option.starts_with("--output-oat-location=")) { + if (have_output_oat) { + Usage("Only one of --output-oat-file, --output-oat-location and --output-oat-fd may " + "be used."); + } + have_output_oat = true; + output_oat_location = option.substr(strlen("--output-oat-location=")).data(); + } else if (option.starts_with("--output-oat-file=")) { + if (have_output_oat) { + Usage("Only one of --output-oat-file, --output-oat-location and --output-oat-fd may " + "be used."); + } + have_output_oat = true; + output_oat_filename = option.substr(strlen("--output-oat-file=")).data(); + } else if (option.starts_with("--output-oat-fd=")) { + if (have_output_oat) { + Usage("Only one of --output-oat-file, --output-oat-location and --output-oat-fd may " + "be used."); + } + have_output_oat = true; + const char* oat_fd_str = option.substr(strlen("--output-oat-fd=")).data(); + if (!ParseInt(oat_fd_str, &output_oat_fd)) { + Usage("Failed to parse --output-oat-fd argument '%s' as an integer", oat_fd_str); + } + if (output_oat_fd < 0) { + Usage("--output-oat-fd pass a negative value %d", output_oat_fd); + } + } else if (option.starts_with("--output-image-location=")) { + if (have_output_image) { + Usage("Only one of --output-image-file, --output-image-location and --output-image-fd may " + "be used."); + } + have_output_image = true; + output_image_location= option.substr(strlen("--output-image-location=")).data(); + } else if (option.starts_with("--output-image-file=")) { + if (have_output_image) { + Usage("Only one of --output-image-file, --output-image-location and --output-image-fd may " + "be used."); + } + have_output_image = true; + output_image_filename = option.substr(strlen("--output-image-file=")).data(); + } else if (option.starts_with("--output-image-fd=")) { + if (have_output_image) { + Usage("Only one of --output-image-file, --output-image-location and --output-image-fd " + "may be used."); + } + have_output_image = true; + const char* image_fd_str = option.substr(strlen("--output-image-fd=")).data(); + if (!ParseInt(image_fd_str, &output_image_fd)) { + Usage("Failed to parse --output-image-fd argument '%s' as an integer", image_fd_str); + } + if (output_image_fd < 0) { + Usage("--output-image-fd pass a negative value %d", output_image_fd); + } + } else if (option.starts_with("--orig-base-offset=")) { + const char* orig_base_offset_str = option.substr(strlen("--orig-base-offset=")).data(); + orig_base_offset_set = true; + if (!ParseUint(orig_base_offset_str, &orig_base_offset)) { + Usage("Failed to parse --orig-base-offset argument '%s' as an uintptr_t", + orig_base_offset_str); + } + } else if (option.starts_with("--base-offset=")) { + const char* base_offset_str = option.substr(strlen("--base-offset=")).data(); + base_offset_set = true; + if (!ParseUint(base_offset_str, &base_offset)) { + Usage("Failed to parse --base-offset argument '%s' as an uintptr_t", base_offset_str); + } + } else if (option.starts_with("--base-offset-delta=")) { + const char* base_delta_str = option.substr(strlen("--base-offset-delta=")).data(); + base_delta_set = true; + if (!ParseInt(base_delta_str, &base_delta)) { + Usage("Failed to parse --base-offset-delta argument '%s' as an off_t", base_delta_str); + } + } else if (option.starts_with("--patched-image-location=")) { + patched_image_location = option.substr(strlen("--patched-image-location=")).data(); + } else if (option.starts_with("--patched-image-file=")) { + patched_image_filename = option.substr(strlen("--patched-image-file=")).data(); + } else if (option == "--dump-timings") { + dump_timings = true; + } else if (option == "--no-dump-timings") { + dump_timings = false; + } else { + Usage("Unknown argument %s", option.data()); + } + } + + { + // Only 1 of these may be set. + uint32_t cnt = 0; + cnt += (base_delta_set) ? 1 : 0; + cnt += (base_offset_set && orig_base_offset_set) ? 1 : 0; + cnt += (!patched_image_filename.empty()) ? 1 : 0; + cnt += (!patched_image_location.empty()) ? 1 : 0; + if (cnt > 1) { + Usage("Only one of --base-offset/--orig-base-offset, --base-offset-delta, " + "--patched-image-filename or --patched-image-location may be used."); + } else if (cnt == 0) { + Usage("Must specify --base-offset-delta, --base-offset and --orig-base-offset, " + "--patched-image-location or --patched-image-file"); + } + } + + if (have_input_oat != have_output_oat) { + Usage("Either both input and output oat must be supplied or niether must be."); + } + + if ((!input_image_location.empty()) != have_output_image) { + Usage("Either both input and output image must be supplied or niether must be."); + } + + // We know we have both the input and output so rename for clarity. + bool have_image_files = have_output_image; + bool have_oat_files = have_output_oat; + + if (!have_oat_files && !have_image_files) { + Usage("Must be patching either an oat or an image file or both."); + } + + if (!have_oat_files && !isa_set) { + Usage("Must include ISA if patching an image file without an oat file."); + } + + if (!input_oat_location.empty()) { + if (!isa_set) { + Usage("specifying a location requires specifying an instruction set"); + } + input_oat_filename = GetSystemImageFilename(input_oat_location.c_str(), isa); + if (debug) { + LOG(INFO) << "Using input-oat-file " << input_oat_filename; + } + } + if (!output_oat_location.empty()) { + if (!isa_set) { + Usage("specifying a location requires specifying an instruction set"); + } + output_oat_filename = GetSystemImageFilename(output_oat_location.c_str(), isa); + if (debug) { + LOG(INFO) << "Using output-oat-file " << output_oat_filename; + } + } + if (!output_image_location.empty()) { + if (!isa_set) { + Usage("specifying a location requires specifying an instruction set"); + } + output_image_filename = GetSystemImageFilename(output_image_location.c_str(), isa); + if (debug) { + LOG(INFO) << "Using output-image-file " << output_image_filename; + } + } + if (!patched_image_location.empty()) { + if (!isa_set) { + Usage("specifying a location requires specifying an instruction set"); + } + patched_image_filename = GetSystemImageFilename(patched_image_location.c_str(), isa); + if (debug) { + LOG(INFO) << "Using patched-image-file " << patched_image_filename; + } + } + + if (!base_delta_set) { + if (orig_base_offset_set && base_offset_set) { + base_delta_set = true; + base_delta = base_offset - orig_base_offset; + } else if (!patched_image_filename.empty()) { + base_delta_set = true; + std::string error_msg; + if (!ReadBaseDelta(patched_image_filename.c_str(), &base_delta, error_msg)) { + Usage(error_msg.c_str(), patched_image_filename.c_str()); + } + } else { + if (base_offset_set) { + Usage("Unable to determine original base offset."); + } else { + Usage("Must supply a desired new offset or delta."); + } + } + } + + if (!IsAligned(base_delta)) { + Usage("Base offset/delta must be alligned to a pagesize (0x%08x) boundary.", kPageSize); + } + + // Do we need to cleanup output files if we fail? + bool new_image_out = false; + bool new_oat_out = false; + + std::unique_ptr input_oat; + std::unique_ptr output_oat; + std::unique_ptr output_image; + + if (have_image_files) { + CHECK(!input_image_location.empty()); + + if (output_image_fd != -1) { + output_image.reset(new File(output_image_fd, output_image_filename)); + } else { + CHECK(!output_image_filename.empty()); + output_image.reset(CreateOrOpen(output_image_filename.c_str(), &new_image_out)); + } + } else { + CHECK(output_image_filename.empty() && output_image_fd == -1 && input_image_location.empty()); + } + + if (have_oat_files) { + if (input_oat_fd != -1) { + input_oat.reset(new File(input_oat_fd, input_oat_filename)); + } else { + CHECK(!input_oat_filename.empty()); + input_oat.reset(OS::OpenFileForReading(input_oat_filename.c_str())); + } + + if (output_oat_fd != -1) { + output_oat.reset(new File(output_oat_fd, output_oat_filename)); + } else { + CHECK(!output_oat_filename.empty()); + output_oat.reset(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out)); + } + } + + auto cleanup = [&output_image_filename, &output_oat_filename, + &new_oat_out, &new_image_out, &timings, &dump_timings](bool success) { + timings.EndTiming(); + if (!success) { + if (new_oat_out) { + CHECK(!output_oat_filename.empty()); + unlink(output_oat_filename.c_str()); + } + if (new_image_out) { + CHECK(!output_image_filename.empty()); + unlink(output_image_filename.c_str()); + } + } + if (dump_timings) { + LOG(INFO) << Dumpable(timings); + } + }; + + if (debug) { + LOG(INFO) << "moving offset by " << base_delta << " (0x" << std::hex << base_delta << ") bytes"; + } + + bool ret; + if (have_image_files && have_oat_files) { + TimingLogger::ScopedTiming pt("patch image and oat", &timings); + ret = PatchOat::Patch(input_oat.get(), input_image_location, base_delta, + output_oat.get(), output_image.get(), isa, timings); + } else if (have_oat_files) { + TimingLogger::ScopedTiming pt("patch oat", &timings); + ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), timings); + } else { + TimingLogger::ScopedTiming pt("patch image", &timings); + CHECK(have_image_files); + ret = PatchOat::Patch(input_image_location, base_delta, output_image.get(), isa, timings); + } + cleanup(ret); + return (ret) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +} // namespace art + +int main(int argc, char **argv) { + return art::patchoat(argc, argv); +} diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h new file mode 100644 index 000000000..b9f36d4f8 --- /dev/null +++ b/patchoat/patchoat.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_PATCHOAT_PATCHOAT_H_ +#define ART_PATCHOAT_PATCHOAT_H_ + +#include "base/macros.h" +#include "base/mutex.h" +#include "instruction_set.h" +#include "os.h" +#include "elf_file.h" +#include "elf_utils.h" +#include "gc/accounting/space_bitmap.h" +#include "gc/heap.h" +#include "utils.h" + +namespace art { + +class ImageHeader; + +namespace mirror { +class Object; +class Reference; +class Class; +class ArtMethod; +}; + +int patchoat(int argc, char** argv); + +class PatchOat { + public: + static bool Patch(File* oat_in, off_t delta, File* oat_out, TimingLogger& timings); + + static bool Patch(const std::string& art_location, off_t delta, File* art_out, InstructionSet isa, + TimingLogger& timings); + + static bool Patch(const File* oat_in, const std::string& art_location, + off_t delta, File* oat_out, File* art_out, InstructionSet isa, + TimingLogger& timings); + + private: + std::unique_ptr oat_file_; + MemMap* image_; + gc::accounting::ContinuousSpaceBitmap* bitmap_; + MemMap* heap_; + off_t delta_; + TimingLogger& timings_; + + // Takes ownership only of the ElfFile. All other pointers are only borrowed. + PatchOat(ElfFile* oat_file, off_t delta, TimingLogger& timings) + : oat_file_(oat_file), delta_(delta), timings_(timings) {} + PatchOat(MemMap* image, gc::accounting::ContinuousSpaceBitmap* bitmap, + MemMap* heap, off_t delta, TimingLogger& timings) + : image_(image), bitmap_(bitmap), heap_(heap), + delta_(delta), timings_(timings) {} + PatchOat(ElfFile* oat_file, MemMap* image, gc::accounting::ContinuousSpaceBitmap* bitmap, + MemMap* heap, off_t delta, TimingLogger& timings) + : oat_file_(oat_file), image_(image), bitmap_(bitmap), heap_(heap), + delta_(delta), timings_(timings) {} + ~PatchOat() {} + + static void BitmapCallback(mirror::Object* obj, void* arg) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { + reinterpret_cast(arg)->VisitObject(obj); + } + + void VisitObject(mirror::Object* obj) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + void FixupMethod(mirror::ArtMethod* object, mirror::ArtMethod* copy) + SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + bool InHeap(mirror::Object*); + + bool CheckOatFile(); + + // Patches oat in place, modifying the oat_file given to the constructor. + bool PatchElf(); + bool PatchTextSection(); + bool PatchSymbols(Elf32_Shdr* section); + + bool PatchImage() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); + + bool WriteElf(File* out); + bool WriteImage(File* out); + + mirror::Object* RelocatedCopyOf(mirror::Object*); + mirror::Object* RelocatedAddressOf(mirror::Object* obj); + + class PatchVisitor { + public: + PatchVisitor(PatchOat* patcher, mirror::Object* copy) : patcher_(patcher), copy_(copy) {} + ~PatchVisitor() {} + void operator() (mirror::Object* obj, MemberOffset off, bool b) const + EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::heap_bitmap_lock_); + // For reference classes. + void operator() (mirror::Class* cls, mirror::Reference* ref) const + EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::heap_bitmap_lock_); + private: + PatchOat* patcher_; + mirror::Object* copy_; + }; + + DISALLOW_IMPLICIT_CONSTRUCTORS(PatchOat); +}; + +} // namespace art +#endif // ART_PATCHOAT_PATCHOAT_H_ diff --git a/runtime/elf_file.cc b/runtime/elf_file.cc index bb3397876..e5402e1c6 100644 --- a/runtime/elf_file.cc +++ b/runtime/elf_file.cc @@ -724,14 +724,23 @@ Elf32_Dyn& ElfFile::GetDynamic(Elf32_Word i) const { return *(GetDynamicSectionStart() + i); } -Elf32_Word ElfFile::FindDynamicValueByType(Elf32_Sword type) const { +Elf32_Dyn* ElfFile::FindDynamicByType(Elf32_Sword type) const { for (Elf32_Word i = 0; i < GetDynamicNum(); i++) { - Elf32_Dyn& elf_dyn = GetDynamic(i); - if (elf_dyn.d_tag == type) { - return elf_dyn.d_un.d_val; + Elf32_Dyn* dyn = &GetDynamic(i); + if (dyn->d_tag == type) { + return dyn; } } - return 0; + return NULL; +} + +Elf32_Word ElfFile::FindDynamicValueByType(Elf32_Sword type) const { + Elf32_Dyn* dyn = FindDynamicByType(type); + if (dyn == NULL) { + return 0; + } else { + return dyn->d_un.d_val; + } } Elf32_Rel* ElfFile::GetRelSectionStart(Elf32_Shdr& section_header) const { diff --git a/runtime/elf_file.h b/runtime/elf_file.h index 496690b74..a966bd963 100644 --- a/runtime/elf_file.h +++ b/runtime/elf_file.h @@ -112,6 +112,7 @@ class ElfFile { Elf32_Word GetDynamicNum() const; Elf32_Dyn& GetDynamic(Elf32_Word) const; + Elf32_Dyn* FindDynamicByType(Elf32_Sword type) const; Elf32_Word FindDynamicValueByType(Elf32_Sword type) const; Elf32_Word GetRelNum(Elf32_Shdr&) const; diff --git a/runtime/elf_utils.h b/runtime/elf_utils.h index 2c5004782..5966d0599 100644 --- a/runtime/elf_utils.h +++ b/runtime/elf_utils.h @@ -22,6 +22,8 @@ // Explicitly include our own elf.h to avoid Linux and other dependencies. #include "./elf.h" +#include "base/logging.h" + // Architecture dependent flags for the ELF header. #define EF_ARM_EABI_VER5 0x05000000 #define EF_MIPS_ABI_O32 0x00001000 @@ -62,8 +64,103 @@ #define DT_MIPS_HIPAGENO 0x70000014 /* Number of GOT page table entries */ #define DT_MIPS_RLD_MAP 0x70000016 /* Address of debug map pointer */ +// Patching section type +#define SHT_OAT_PATCH SHT_LOUSER + inline void SetBindingAndType(Elf32_Sym* sym, unsigned char b, unsigned char t) { sym->st_info = (b << 4) + (t & 0x0f); } +inline bool IsDynamicSectionPointer(Elf32_Word d_tag, Elf32_Word e_machine) { + switch (d_tag) { + // case 1: well known d_tag values that imply Elf32_Dyn.d_un contains an address in d_ptr + case DT_PLTGOT: + case DT_HASH: + case DT_STRTAB: + case DT_SYMTAB: + case DT_RELA: + case DT_INIT: + case DT_FINI: + case DT_REL: + case DT_DEBUG: + case DT_JMPREL: { + return true; + } + // d_val or ignored values + case DT_NULL: + case DT_NEEDED: + case DT_PLTRELSZ: + case DT_RELASZ: + case DT_RELAENT: + case DT_STRSZ: + case DT_SYMENT: + case DT_SONAME: + case DT_RPATH: + case DT_SYMBOLIC: + case DT_RELSZ: + case DT_RELENT: + case DT_PLTREL: + case DT_TEXTREL: + case DT_BIND_NOW: + case DT_INIT_ARRAYSZ: + case DT_FINI_ARRAYSZ: + case DT_RUNPATH: + case DT_FLAGS: { + return false; + } + // boundary values that should not be used + case DT_ENCODING: + case DT_LOOS: + case DT_HIOS: + case DT_LOPROC: + case DT_HIPROC: { + LOG(FATAL) << "Illegal d_tag value 0x" << std::hex << d_tag; + return false; + } + default: { + // case 2: "regular" DT_* ranges where even d_tag values imply an address in d_ptr + if ((DT_ENCODING < d_tag && d_tag < DT_LOOS) + || (DT_LOOS < d_tag && d_tag < DT_HIOS) + || (DT_LOPROC < d_tag && d_tag < DT_HIPROC)) { + // Special case for MIPS which breaks the regular rules between DT_LOPROC and DT_HIPROC + if (e_machine == EM_MIPS) { + switch (d_tag) { + case DT_MIPS_RLD_VERSION: + case DT_MIPS_TIME_STAMP: + case DT_MIPS_ICHECKSUM: + case DT_MIPS_IVERSION: + case DT_MIPS_FLAGS: + case DT_MIPS_LOCAL_GOTNO: + case DT_MIPS_CONFLICTNO: + case DT_MIPS_LIBLISTNO: + case DT_MIPS_SYMTABNO: + case DT_MIPS_UNREFEXTNO: + case DT_MIPS_GOTSYM: + case DT_MIPS_HIPAGENO: { + return false; + } + case DT_MIPS_BASE_ADDRESS: + case DT_MIPS_CONFLICT: + case DT_MIPS_LIBLIST: + case DT_MIPS_RLD_MAP: { + return true; + } + default: { + LOG(FATAL) << "Unknown MIPS d_tag value 0x" << std::hex << d_tag; + return false; + } + } + } else if ((d_tag % 2) == 0) { + return true; + } else { + return false; + } + } else { + LOG(FATAL) << "Unknown d_tag value 0x" << std::hex << d_tag; + return false; + } + } + } +} + #endif // ART_RUNTIME_ELF_UTILS_H_ diff --git a/runtime/image.cc b/runtime/image.cc index 528bfc631..93ec27daf 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -24,7 +24,7 @@ namespace art { const byte ImageHeader::kImageMagic[] = { 'a', 'r', 't', '\n' }; -const byte ImageHeader::kImageVersion[] = { '0', '0', '7', '\0' }; +const byte ImageHeader::kImageVersion[] = { '0', '0', '8', '\0' }; ImageHeader::ImageHeader(uint32_t image_begin, uint32_t image_size, @@ -45,6 +45,7 @@ ImageHeader::ImageHeader(uint32_t image_begin, oat_data_begin_(oat_data_begin), oat_data_end_(oat_data_end), oat_file_end_(oat_file_end), + patch_delta_(0), image_roots_(image_roots) { CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize)); CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize)); @@ -58,6 +59,17 @@ ImageHeader::ImageHeader(uint32_t image_begin, memcpy(version_, kImageVersion, sizeof(kImageVersion)); } +void ImageHeader::RelocateImage(off_t delta) { + CHECK_ALIGNED(delta, kPageSize) << " patch delta must be page aligned"; + image_begin_ += delta; + oat_file_begin_ += delta; + oat_data_begin_ += delta; + oat_data_end_ += delta; + oat_file_end_ += delta; + image_roots_ += delta; + patch_delta_ += delta; +} + bool ImageHeader::IsValid() const { if (memcmp(magic_, kImageMagic, sizeof(kImageMagic)) != 0) { return false; @@ -65,6 +77,25 @@ bool ImageHeader::IsValid() const { if (memcmp(version_, kImageVersion, sizeof(kImageVersion)) != 0) { return false; } + // Unsigned so wraparound is well defined. + if (image_begin_ >= image_begin_ + image_size_) { + return false; + } + if (oat_file_begin_ > oat_file_end_) { + return false; + } + if (oat_data_begin_ > oat_data_end_) { + return false; + } + if (oat_file_begin_ >= oat_data_begin_) { + return false; + } + if (image_roots_ <= image_begin_ || oat_file_begin_ <= image_roots_) { + return false; + } + if (!IsAligned(patch_delta_)) { + return false; + } return true; } diff --git a/runtime/image.h b/runtime/image.h index abe1ad871..424a40b7c 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -84,6 +84,10 @@ class PACKED(4) ImageHeader { return reinterpret_cast(oat_file_end_); } + off_t GetPatchDelta() const { + return patch_delta_; + } + size_t GetBitmapOffset() const { return RoundUp(image_size_, kPageSize); } @@ -112,10 +116,11 @@ class PACKED(4) ImageHeader { mirror::Object* GetImageRoot(ImageRoot image_root) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_); - - private: mirror::ObjectArray* GetImageRoots() const; + void RelocateImage(off_t delta); + + private: static const byte kImageMagic[4]; static const byte kImageVersion[4]; @@ -150,11 +155,13 @@ class PACKED(4) ImageHeader { // .so files. Used for positioning a following alloc spaces. uint32_t oat_file_end_; + // The total delta that this image has been patched. + int32_t patch_delta_; + // Absolute address of an Object[] of objects needed to reinitialize from an image. uint32_t image_roots_; friend class ImageWriter; - friend class ImageDumper; // For GetImageRoots() }; } // namespace art diff --git a/runtime/oat_file.h b/runtime/oat_file.h index eae0418c1..70253af61 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -60,6 +60,12 @@ class OatFile { ~OatFile(); + ElfFile* GetElfFile() const { + CHECK_NE(reinterpret_cast(elf_file_.get()), reinterpret_cast(nullptr)) + << "Cannot get an elf file from " << GetLocation(); + return elf_file_.get(); + } + const std::string& GetLocation() const { return location_; } @@ -227,6 +233,9 @@ class OatFile { return End() - Begin(); } + const byte* Begin() const; + const byte* End() const; + private: static void CheckLocation(const std::string& location); @@ -248,9 +257,6 @@ class OatFile { std::string* error_msg); bool Setup(std::string* error_msg); - const byte* Begin() const; - const byte* End() const; - // The oat file name. // // The image will embed this to link its associated oat file. diff --git a/runtime/utils.h b/runtime/utils.h index eb79968e2..448c591f2 100644 --- a/runtime/utils.h +++ b/runtime/utils.h @@ -19,6 +19,7 @@ #include +#include #include #include @@ -50,6 +51,34 @@ enum TimeUnit { kTimeUnitSecond, }; +template +bool ParseUint(const char *in, T* out) { + char* end; + unsigned long long int result = strtoull(in, &end, 0); // NOLINT(runtime/int) + if (in == end || *end != '\0') { + return false; + } + if (std::numeric_limits::max() < result) { + return false; + } + *out = static_cast(result); + return true; +} + +template +bool ParseInt(const char* in, T* out) { + char* end; + long long int result = strtoll(in, &end, 0); // NOLINT(runtime/int) + if (in == end || *end != '\0') { + return false; + } + if (result < std::numeric_limits::min() || std::numeric_limits::max() < result) { + return false; + } + *out = static_cast(result); + return true; +} + template static constexpr bool IsPowerOfTwo(T x) { return (x & (x - 1)) == 0; -- 2.11.0