diff options
Diffstat (limited to 'patchoat')
-rw-r--r-- | patchoat/Android.mk | 45 | ||||
-rw-r--r-- | patchoat/patchoat.cc | 1020 | ||||
-rw-r--r-- | patchoat/patchoat.h | 119 |
3 files changed, 1184 insertions, 0 deletions
diff --git a/patchoat/Android.mk b/patchoat/Android.mk new file mode 100644 index 0000000..8b6b9ad --- /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 0000000..ea4b880 --- /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 <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <string> +#include <vector> + +#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<File> 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<char*>(&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<const void*>(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<MemMap> 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<char*>(&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<File> 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<char*>(&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<const void*>(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<MemMap> 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<ElfFile> elf(ElfFile::Open(const_cast<File*>(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<char*>(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<char*>(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<ImageHeader*>(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<uintptr_t>(heap_->Begin()); + uintptr_t end = reinterpret_cast<uintptr_t>(heap_->End()); + uintptr_t obj = reinterpret_cast<uintptr_t>(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<mirror::Object, kVerifyNone>(off); + DCHECK(patcher_->InHeap(referent)) << "Referent is not in the heap."; + mirror::Object* moved_object = patcher_->RelocatedAddressOf(referent); + copy_->SetFieldObjectWithoutWriteBarrier<false, true, kVerifyNone>(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<false, true, kVerifyNone>(off, moved_object); +} + +mirror::Object* PatchOat::RelocatedCopyOf(mirror::Object* obj) { + if (obj == nullptr) { + return nullptr; + } + DCHECK_GT(reinterpret_cast<uintptr_t>(obj), reinterpret_cast<uintptr_t>(heap_->Begin())); + DCHECK_LT(reinterpret_cast<uintptr_t>(obj), reinterpret_cast<uintptr_t>(heap_->End())); + uintptr_t heap_off = + reinterpret_cast<uintptr_t>(obj) - reinterpret_cast<uintptr_t>(heap_->Begin()); + DCHECK_LT(heap_off, image_->Size()); + return reinterpret_cast<mirror::Object*>(image_->Begin() + heap_off); +} + +mirror::Object* PatchOat::RelocatedAddressOf(mirror::Object* obj) { + if (obj == nullptr) { + return nullptr; + } else { + return reinterpret_cast<mirror::Object*>(reinterpret_cast<byte*>(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<true, kVerifyNone>(visitor, visitor); + if (object->IsArtMethod<kVerifyNone>()) { + FixupMethod(static_cast<mirror::ArtMethod*>(object), + static_cast<mirror::ArtMethod*>(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<uintptr_t>( + object->GetEntryPointFromPortableCompiledCode<kVerifyNone>()); + if (portable != 0) { + copy->SetEntryPointFromPortableCompiledCode(reinterpret_cast<void*>(portable + delta_)); + } + uintptr_t quick= reinterpret_cast<uintptr_t>( + object->GetEntryPointFromQuickCompiledCode<kVerifyNone>()); + if (quick != 0) { + copy->SetEntryPointFromQuickCompiledCode(reinterpret_cast<void*>(quick + delta_)); + } + uintptr_t interpreter = reinterpret_cast<uintptr_t>( + object->GetEntryPointFromInterpreter<kVerifyNone>()); + if (interpreter != 0) { + copy->SetEntryPointFromInterpreter( + reinterpret_cast<mirror::EntryPointFromInterpreter*>(interpreter + delta_)); + } + + uintptr_t native_method = reinterpret_cast<uintptr_t>(object->GetNativeMethod()); + if (native_method != 0) { + copy->SetNativeMethod(reinterpret_cast<void*>(native_method + delta_)); + } + + uintptr_t native_gc_map = reinterpret_cast<uintptr_t>(object->GetNativeGcMap()); + if (native_gc_map != 0) { + copy->SetNativeGcMap(reinterpret_cast<uint8_t*>(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<ElfFile> elf(ElfFile::Open(const_cast<File*>(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<uintptr_t*>(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<Elf32_Sym*>(oat_file_->Begin() + section->sh_offset); + const Elf32_Sym* last_sym = + reinterpret_cast<Elf32_Sym*>(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<uintptr_t*>(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<uintptr_t>(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<uint32_t*>(to_patch + *patches); + CHECK_LT(reinterpret_cast<uintptr_t>(patch_loc), to_patch_end); + *patch_loc += delta_; + } + + return true; +} + +static int orig_argc; +static char** orig_argv; + +static std::string CommandLine() { + std::vector<std::string> 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=<isa>: Specifies the instruction set the patched code is"); + UsageError(" compiled for. Required if you use --input-oat-location"); + UsageError(""); + UsageError(" --input-oat-file=<file.oat>: Specifies the exact filename of the oat file to be"); + UsageError(" patched."); + UsageError(""); + UsageError(" --input-oat-fd=<file-descriptor>: Specifies the file-descriptor of the oat file"); + UsageError(" to be patched."); + UsageError(""); + UsageError(" --input-oat-location=<file.oat>: 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=<file.art>: 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=<file.oat>: Specifies the exact file to write the patched oat"); + UsageError(" file to."); + UsageError(""); + UsageError(" --output-oat-location=<file.oat>: 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=<file-descriptor>: Specifies the file-descriptor to write the"); + UsageError(" the patched oat file to."); + UsageError(""); + UsageError(" --output-image-file=<file.art>: Specifies the exact file to write the patched"); + UsageError(" image file to."); + UsageError(""); + UsageError(" --output-image-fd=<file-descriptor>: Specifies the file-descriptor to write the"); + UsageError(" the patched image file to."); + UsageError(""); + UsageError(" --output-image-location=<file.art>: 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=<original-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=<new-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=<delta>: Specify the amount to change the old base-offset by."); + UsageError(" This value may be negative."); + UsageError(""); + UsageError(" --patched-image-file=<file.art>: Use the same patch delta as was used to patch"); + UsageError(" the given image file."); + UsageError(""); + UsageError(" --patched-image-location=<file.art>: 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> 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<char*>(&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<kPageSize>(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<File> input_oat; + std::unique_ptr<File> output_oat; + std::unique_ptr<File> 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<TimingLogger>(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 0000000..b9f36d4 --- /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<ElfFile> 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<PatchOat*>(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_ |