From 48447025389cd67605041a28d4ded1528381bc4e Mon Sep 17 00:00:00 2001 From: Igor Murashkin Date: Wed, 22 Oct 2014 11:37:02 -0700 Subject: [PATCH] ART: Add support for patching and loading OAT files compiled with PIC * Images (.art) compiled with pic now have a new field added. * isDexOptNeeded will now skip patch-ing for apps compiled PIC * First-boot patching now only copies boot.art, boot.oat is linked As a result, all system preopted dex files (with --compile-pic) no longer take up any space in /data/dalvik-cache/. (cherry-picked from AOSP master 46774767fcf7780d1455e755729198648d08742e) Conflicts (from aosp master): compiler/image_test.cc compiler/image_writer.cc compiler/image_writer.h compiler/oat_test.cc dex2oat/dex2oat.cc oatdump/oatdump.cc runtime/elf_file.cc runtime/elf_file.h runtime/elf_file_impl.h runtime/oat_file.cc runtime/oat_file.h Bug: 18035729 (cherry picked from commit 90ca5c0301651101de0e363842e5d08ae65233f7) Change-Id: I8d99f95cc3d1fa221fc530ebb1fcc4b3263c183d --- compiler/elf_patcher.cc | 2 +- compiler/image_test.cc | 5 +- compiler/image_writer.cc | 7 +- compiler/image_writer.h | 14 ++- compiler/oat_test.cc | 4 +- dex2oat/dex2oat.cc | 16 ++- oatdump/oatdump.cc | 6 +- patchoat/patchoat.cc | 215 ++++++++++++++++++++++++++++---- patchoat/patchoat.h | 37 +++++- runtime/class_linker.cc | 12 +- runtime/elf_file.cc | 37 ++++-- runtime/elf_file.h | 10 +- runtime/gc/space/image_space.cc | 5 +- runtime/image.cc | 6 +- runtime/image.h | 12 +- runtime/native/dalvik_system_DexFile.cc | 25 +++- runtime/oat.cc | 6 + runtime/oat.h | 1 + runtime/oat_file.cc | 22 ++-- runtime/oat_file.h | 6 +- 20 files changed, 370 insertions(+), 78 deletions(-) diff --git a/compiler/elf_patcher.cc b/compiler/elf_patcher.cc index e4c957af0..e8ccd67ed 100644 --- a/compiler/elf_patcher.cc +++ b/compiler/elf_patcher.cc @@ -44,7 +44,7 @@ bool ElfPatcher::Patch(const CompilerDriver* driver, ElfFile* elf_file, const OatFile* oat_file = class_linker->FindOpenedOatFileFromOatLocation(oat_location); if (oat_file == nullptr) { CHECK(Runtime::Current()->IsCompiler()); - oat_file = OatFile::Open(oat_location, oat_location, NULL, false, error_msg); + oat_file = OatFile::Open(oat_location, oat_location, nullptr, nullptr, false, error_msg); if (oat_file == nullptr) { *error_msg = StringPrintf("Unable to find or open oat file at '%s': %s", oat_location.c_str(), error_msg->c_str()); diff --git a/compiler/image_test.cc b/compiler/image_test.cc index 355036be5..0aef5120b 100644 --- a/compiler/image_test.cc +++ b/compiler/image_test.cc @@ -98,7 +98,7 @@ TEST_F(ImageTest, WriteRead) { { ImageWriter writer(*compiler_driver_.get()); bool success_image = writer.Write(image_file.GetFilename(), requested_image_base, - dup_oat->GetPath(), dup_oat->GetPath()); + dup_oat->GetPath(), dup_oat->GetPath(), /*compile_pic*/false); ASSERT_TRUE(success_image); bool success_fixup = ElfFixup::Fixup(dup_oat.get(), writer.GetOatDataBegin()); ASSERT_TRUE(success_fixup); @@ -210,7 +210,8 @@ TEST_F(ImageTest, ImageHeaderIsValid) { oat_file_begin, oat_data_begin, oat_data_end, - oat_file_end); + oat_file_end, + /*compile_pic*/false); ASSERT_TRUE(image_header.IsValid()); char* magic = const_cast(image_header.GetMagic()); diff --git a/compiler/image_writer.cc b/compiler/image_writer.cc index c6fc11580..3d0384ee7 100644 --- a/compiler/image_writer.cc +++ b/compiler/image_writer.cc @@ -71,11 +71,13 @@ namespace art { bool ImageWriter::Write(const std::string& image_filename, uintptr_t image_begin, const std::string& oat_filename, - const std::string& oat_location) { + const std::string& oat_location, + bool compile_pic) { CHECK(!image_filename.empty()); CHECK_NE(image_begin, 0U); image_begin_ = reinterpret_cast(image_begin); + compile_pic_ = compile_pic; ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); @@ -569,7 +571,8 @@ void ImageWriter::CalculateNewObjectOffsets(size_t oat_loaded_size, size_t oat_d PointerToLowMemUInt32(oat_file_begin), PointerToLowMemUInt32(oat_data_begin_), PointerToLowMemUInt32(oat_data_end), - PointerToLowMemUInt32(oat_file_end)); + PointerToLowMemUInt32(oat_file_end), + compile_pic_); memcpy(image_->Begin(), &image_header, sizeof(image_header)); // Note that image_end_ is left at end of used space diff --git a/compiler/image_writer.h b/compiler/image_writer.h index e8bcf7f88..61365fe23 100644 --- a/compiler/image_writer.h +++ b/compiler/image_writer.h @@ -24,6 +24,7 @@ #include #include +#include "base/macros.h" #include "driver/compiler_driver.h" #include "mem_map.h" #include "oat_file.h" @@ -35,21 +36,23 @@ namespace art { // Write a Space built during compilation for use during execution. -class ImageWriter { +class ImageWriter FINAL { public: explicit ImageWriter(const CompilerDriver& compiler_driver) : compiler_driver_(compiler_driver), oat_file_(NULL), image_end_(0), image_begin_(NULL), oat_data_begin_(NULL), interpreter_to_interpreter_bridge_offset_(0), interpreter_to_compiled_code_bridge_offset_(0), portable_imt_conflict_trampoline_offset_(0), portable_resolution_trampoline_offset_(0), quick_generic_jni_trampoline_offset_(0), - quick_imt_conflict_trampoline_offset_(0), quick_resolution_trampoline_offset_(0) {} + quick_imt_conflict_trampoline_offset_(0), quick_resolution_trampoline_offset_(0), + compile_pic_(false) {} ~ImageWriter() {} bool Write(const std::string& image_filename, uintptr_t image_begin, const std::string& oat_filename, - const std::string& oat_location) + const std::string& oat_location, + bool compile_pic) LOCKS_EXCLUDED(Locks::mutator_lock_); uintptr_t GetOatDataBegin() { @@ -97,8 +100,8 @@ class ImageWriter { // different .o ELF objects. DCHECK_LT(offset, oat_file_->Size()); #endif - if (offset == 0) { - return NULL; + if (offset == 0u) { + return nullptr; } return oat_data_begin_ + offset; } @@ -199,6 +202,7 @@ class ImageWriter { uint32_t quick_imt_conflict_trampoline_offset_; uint32_t quick_resolution_trampoline_offset_; uint32_t quick_to_interpreter_bridge_offset_; + bool compile_pic_; friend class FixupVisitor; friend class FixupClassVisitor; diff --git a/compiler/oat_test.cc b/compiler/oat_test.cc index 11d17288e..7c1f6c589 100644 --- a/compiler/oat_test.cc +++ b/compiler/oat_test.cc @@ -135,8 +135,8 @@ TEST_F(OatTest, WriteRead) { compiler_driver_->CompileAll(class_loader, class_linker->GetBootClassPath(), &timings); } std::string error_msg; - std::unique_ptr oat_file(OatFile::Open(tmp.GetFilename(), tmp.GetFilename(), NULL, false, - &error_msg)); + std::unique_ptr oat_file(OatFile::Open(tmp.GetFilename(), tmp.GetFilename(), nullptr, + nullptr, false, &error_msg)); ASSERT_TRUE(oat_file.get() != nullptr) << error_msg; const OatHeader& oat_header = oat_file->GetOatHeader(); ASSERT_TRUE(oat_header.IsValid()); diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index e67edd1cf..833a678cf 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -155,6 +155,9 @@ static void Usage(const char* fmt, ...) { UsageError(" Example: --instruction-set-features=div"); UsageError(" Default: default"); UsageError(""); + UsageError(" --compile-pic: Force indirect use of code, methods, and classes"); + UsageError(" Default: disabled"); + UsageError(""); UsageError(" --compiler-backend=(Quick|Optimizing|Portable): select compiler backend"); UsageError(" set."); UsageError(" Example: --compiler-backend=Portable"); @@ -453,7 +456,8 @@ class Dex2Oat { { // ImageWriter is scoped so it can free memory before doing FixupElf ImageWriter image_writer(compiler); - if (!image_writer.Write(image_filename, image_base, oat_filename, oat_location)) { + if (!image_writer.Write(image_filename, image_base, oat_filename, oat_location, + compiler_options_->GetCompilePic())) { LOG(ERROR) << "Failed to create image file " << image_filename; return false; } @@ -465,10 +469,14 @@ class Dex2Oat { PLOG(ERROR) << "Failed to open ELF file: " << oat_filename; return false; } - if (!ElfFixup::Fixup(oat_file.get(), oat_data_begin)) { - LOG(ERROR) << "Failed to fixup ELF file " << oat_file->GetPath(); - return false; + // Do not fix up the ELF file if we are --compile-pic + if (!compiler_options_->GetCompilePic()) { + if (!ElfFixup::Fixup(oat_file.get(), oat_data_begin)) { + LOG(ERROR) << "Failed to fixup ELF file " << oat_file->GetPath(); + return false; + } } + return true; } diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 83c787130..926a04afc 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -983,6 +983,8 @@ class ImageDumper { os << "PATCH DELTA:" << image_header_.GetPatchDelta() << "\n\n"; + os << "COMPILE PIC: " << (image_header_.CompilePic() ? "yes" : "no") << "\n\n"; + { os << "ROOTS: " << reinterpret_cast(image_header_.GetImageRoots()) << "\n"; Indenter indent1_filter(os.rdbuf(), kIndentChar, kIndentBy1Count); @@ -1033,7 +1035,7 @@ class ImageDumper { std::string error_msg; const OatFile* oat_file = class_linker->FindOpenedOatFileFromOatLocation(oat_location); if (oat_file == nullptr) { - oat_file = OatFile::Open(oat_location, oat_location, nullptr, false, &error_msg); + oat_file = OatFile::Open(oat_location, oat_location, nullptr, nullptr, false, &error_msg); if (oat_file == nullptr) { os << "NOT FOUND: " << error_msg << "\n"; return false; @@ -1761,7 +1763,7 @@ static int oatdump(int argc, char** argv) { if (oat_filename != nullptr) { std::string error_msg; OatFile* oat_file = - OatFile::Open(oat_filename, oat_filename, nullptr, false, &error_msg); + OatFile::Open(oat_filename, oat_filename, nullptr, nullptr, false, &error_msg); if (oat_file == nullptr) { fprintf(stderr, "Failed to open oat file from '%s': %s\n", oat_filename, error_msg.c_str()); return EXIT_FAILURE; diff --git a/patchoat/patchoat.cc b/patchoat/patchoat.cc index 74f6779b0..b9637d0cb 100644 --- a/patchoat/patchoat.cc +++ b/patchoat/patchoat.cc @@ -71,7 +71,7 @@ static bool LocationToFilename(const std::string& location, InstructionSet isa, bool has_system = false; bool has_cache = false; // image_location = /system/framework/boot.art - // system_image_location = /system/framework//boot.art + // system_image_filename = /system/framework//boot.art std::string system_filename(GetSystemImageFilename(location.c_str(), isa)); if (OS::FileExists(system_filename.c_str())) { has_system = true; @@ -130,6 +130,7 @@ bool PatchOat::Patch(const std::string& image_location, off_t delta, << " for location " << image_location; return false; } + int64_t image_len = input_image->GetLength(); if (image_len < 0) { LOG(ERROR) << "Error while getting image length"; @@ -142,6 +143,10 @@ bool PatchOat::Patch(const std::string& image_location, off_t delta, return false; } + /*bool is_image_pic = */IsImagePic(image_header, input_image->GetPath()); + // Nothing special to do right now since the image always needs to get patched. + // Perhaps in some far-off future we may have images with relative addresses that are true-PIC. + // Set up the runtime RuntimeOptions options; NoopCompilerCallbacks callbacks; @@ -186,9 +191,11 @@ bool PatchOat::Patch(const std::string& image_location, off_t delta, return true; } -bool PatchOat::Patch(const File* input_oat, const std::string& image_location, off_t delta, +bool PatchOat::Patch(File* input_oat, const std::string& image_location, off_t delta, File* output_oat, File* output_image, InstructionSet isa, - TimingLogger* timings) { + TimingLogger* timings, + bool output_oat_opened_from_fd, + bool new_oat_out) { CHECK(Runtime::Current() == nullptr); CHECK(output_image != nullptr); CHECK_GE(output_image->Fd(), 0); @@ -231,6 +238,10 @@ bool PatchOat::Patch(const File* input_oat, const std::string& image_location, o LOG(ERROR) << "Unable to read image header from image file " << input_image->GetPath(); } + /*bool is_image_pic = */IsImagePic(image_header, input_image->GetPath()); + // Nothing special to do right now since the image always needs to get patched. + // Perhaps in some far-off future we may have images with relative addresses that are true-PIC. + // Set up the runtime RuntimeOptions options; NoopCompilerCallbacks callbacks; @@ -260,17 +271,37 @@ bool PatchOat::Patch(const File* input_oat, const std::string& image_location, o } gc::space::ImageSpace* ispc = Runtime::Current()->GetHeap()->GetImageSpace(); - std::unique_ptr elf(ElfFile::Open(const_cast(input_oat), + std::unique_ptr elf(ElfFile::Open(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; } + bool skip_patching_oat = false; + MaybePic is_oat_pic = IsOatPic(elf.get()); + if (is_oat_pic >= ERROR_FIRST) { + // Error logged by IsOatPic + return false; + } else if (is_oat_pic == PIC) { + // Do not need to do ELF-file patching. Create a symlink and skip the ELF patching. + if (!ReplaceOatFileWithSymlink(input_oat->GetPath(), + output_oat->GetPath(), + output_oat_opened_from_fd, + new_oat_out)) { + // Errors already logged by above call. + return false; + } + // Don't patch the OAT, since we just symlinked it. Image still needs patching. + skip_patching_oat = true; + } else { + CHECK(is_oat_pic == NOT_PIC); + } + PatchOat p(elf.release(), image.release(), ispc->GetLiveBitmap(), ispc->GetMemMap(), delta, timings); t.NewTiming("Patching files"); - if (!p.PatchElf()) { + if (!skip_patching_oat && !p.PatchElf()) { LOG(ERROR) << "Failed to patch oat file " << input_oat->GetPath(); return false; } @@ -280,10 +311,12 @@ bool PatchOat::Patch(const File* input_oat, const std::string& image_location, o } t.NewTiming("Writing files"); - if (!p.WriteElf(output_oat)) { + if (!skip_patching_oat && !p.WriteElf(output_oat)) { + LOG(ERROR) << "Failed to write oat file " << input_oat->GetPath(); return false; } if (!p.WriteImage(output_image)) { + LOG(ERROR) << "Failed to write image file " << input_image->GetPath(); return false; } return true; @@ -323,6 +356,83 @@ bool PatchOat::WriteImage(File* out) { } } +bool PatchOat::IsImagePic(const ImageHeader& image_header, const std::string& image_path) { + if (!image_header.CompilePic()) { + if (kIsDebugBuild) { + LOG(INFO) << "image at location " << image_path << " was *not* compiled pic"; + } + return false; + } + + if (kIsDebugBuild) { + LOG(INFO) << "image at location " << image_path << " was compiled PIC"; + } + + return true; +} + +PatchOat::MaybePic PatchOat::IsOatPic(const ElfFile* oat_in) { + if (oat_in == nullptr) { + LOG(ERROR) << "No ELF input oat fie available"; + return ERROR_OAT_FILE; + } + + const std::string& file_path = oat_in->GetFile().GetPath(); + + const OatHeader* oat_header = GetOatHeader(oat_in); + if (oat_header == nullptr) { + LOG(ERROR) << "Failed to find oat header in oat file " << file_path; + return ERROR_OAT_FILE; + } + + if (!oat_header->IsValid()) { + LOG(ERROR) << "Elf file " << file_path << " has an invalid oat header"; + return ERROR_OAT_FILE; + } + + bool is_pic = oat_header->IsPic(); + if (kIsDebugBuild) { + LOG(INFO) << "Oat file at " << file_path << " is " << (is_pic ? "PIC" : "not pic"); + } + + return is_pic ? PIC : NOT_PIC; +} + +bool PatchOat::ReplaceOatFileWithSymlink(const std::string& input_oat_filename, + const std::string& output_oat_filename, + bool output_oat_opened_from_fd, + bool new_oat_out) { + // Need a file when we are PIC, since we symlink over it. Refusing to symlink into FD. + if (output_oat_opened_from_fd) { + // TODO: installd uses --output-oat-fd. Should we change class linking logic for PIC? + LOG(ERROR) << "No output oat filename specified, needs filename for when we are PIC"; + return false; + } + + // Image was PIC. Create symlink where the oat is supposed to go. + if (!new_oat_out) { + LOG(ERROR) << "Oat file " << output_oat_filename << " already exists, refusing to overwrite"; + return false; + } + + // Delete the original file, since we won't need it. + TEMP_FAILURE_RETRY(unlink(output_oat_filename.c_str())); + + // Create a symlink from the old oat to the new oat + if (symlink(input_oat_filename.c_str(), output_oat_filename.c_str()) < 0) { + int err = errno; + LOG(ERROR) << "Failed to create symlink at " << output_oat_filename + << " error(" << err << "): " << strerror(err); + return false; + } + + if (kIsDebugBuild) { + LOG(INFO) << "Created symlink " << output_oat_filename << " -> " << input_oat_filename; + } + + return true; +} + bool PatchOat::PatchImage() { ImageHeader* image_header = reinterpret_cast(image_->Begin()); CHECK_GT(image_->Size(), sizeof(ImageHeader)); @@ -388,6 +498,16 @@ mirror::Object* PatchOat::RelocatedAddressOf(mirror::Object* obj) { } } +const OatHeader* PatchOat::GetOatHeader(const ElfFile* elf_file) { + auto rodata_sec = elf_file->FindSectionByName(".rodata"); + if (rodata_sec == nullptr) { + return nullptr; + } + + OatHeader* oat_header = reinterpret_cast(elf_file->Begin() + rodata_sec->sh_offset); + return oat_header; +} + // Called by BitmapCallback void PatchOat::VisitObject(mirror::Object* object) { mirror::Object* copy = RelocatedCopyOf(object); @@ -441,7 +561,8 @@ void PatchOat::FixupMethod(mirror::ArtMethod* object, mirror::ArtMethod* copy) { } } -bool PatchOat::Patch(File* input_oat, off_t delta, File* output_oat, TimingLogger* timings) { +bool PatchOat::Patch(File* input_oat, off_t delta, File* output_oat, TimingLogger* timings, + bool output_oat_opened_from_fd, bool new_oat_out) { CHECK(input_oat != nullptr); CHECK(output_oat != nullptr); CHECK_GE(input_oat->Fd(), 0); @@ -449,13 +570,28 @@ bool PatchOat::Patch(File* input_oat, off_t delta, File* output_oat, TimingLogge TimingLogger::ScopedTiming t("Setup Oat File Patching", timings); std::string error_msg; - std::unique_ptr elf(ElfFile::Open(const_cast(input_oat), + std::unique_ptr elf(ElfFile::Open(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; } + MaybePic is_oat_pic = IsOatPic(elf.get()); + if (is_oat_pic >= ERROR_FIRST) { + // Error logged by IsOatPic + return false; + } else if (is_oat_pic == PIC) { + // Do not need to do ELF-file patching. Create a symlink and skip the rest. + // Any errors will be logged by the function call. + return ReplaceOatFileWithSymlink(input_oat->GetPath(), + output_oat->GetPath(), + output_oat_opened_from_fd, + new_oat_out); + } else { + CHECK(is_oat_pic == NOT_PIC); + } + PatchOat p(elf.release(), delta, timings); t.NewTiming("Patch Oat file"); if (!p.PatchElf()) { @@ -1045,11 +1181,17 @@ static int patchoat(int argc, char **argv) { input_oat_filename = "input-oat-file"; } input_oat.reset(new File(input_oat_fd, input_oat_filename)); + if (input_oat == nullptr) { + // Unlikely, but ensure exhaustive logging in non-0 exit code case + LOG(ERROR) << "Failed to open input oat file by its FD" << input_oat_fd; + } } else { CHECK(!input_oat_filename.empty()); input_oat.reset(OS::OpenFileForReading(input_oat_filename.c_str())); - if (input_oat.get() == nullptr) { - LOG(ERROR) << "Could not open input oat file: " << strerror(errno); + if (input_oat == nullptr) { + int err = errno; + LOG(ERROR) << "Failed to open input oat file " << input_oat_filename + << ": " << strerror(err) << "(" << err << ")"; } } @@ -1058,12 +1200,22 @@ static int patchoat(int argc, char **argv) { output_oat_filename = "output-oat-file"; } output_oat.reset(new File(output_oat_fd, output_oat_filename)); + if (output_oat == nullptr) { + // Unlikely, but ensure exhaustive logging in non-0 exit code case + LOG(ERROR) << "Failed to open output oat file by its FD" << output_oat_fd; + } } else { CHECK(!output_oat_filename.empty()); output_oat.reset(CreateOrOpen(output_oat_filename.c_str(), &new_oat_out)); + if (output_oat == nullptr) { + int err = errno; + LOG(ERROR) << "Failed to open output oat file " << output_oat_filename + << ": " << strerror(err) << "(" << err << ")"; + } } } + // TODO: get rid of this. auto cleanup = [&output_image_filename, &output_oat_filename, &new_oat_out, &new_image_out, &timings, &dump_timings](bool success) { timings.EndTiming(); @@ -1080,14 +1232,29 @@ static int patchoat(int argc, char **argv) { if (dump_timings) { LOG(INFO) << Dumpable(timings); } + + if (kIsDebugBuild) { + LOG(INFO) << "Cleaning up.. success? " << success; + } }; - if ((have_oat_files && (input_oat.get() == nullptr || output_oat.get() == nullptr)) || - (have_image_files && output_image.get() == nullptr)) { + if (have_oat_files && (input_oat.get() == nullptr || output_oat.get() == nullptr)) { + LOG(ERROR) << "Failed to open input/output oat files"; cleanup(false); return EXIT_FAILURE; + } else if (have_image_files && output_image.get() == nullptr) { + LOG(ERROR) << "Failed to open output image file"; + cleanup(false); + return EXIT_FAILURE; + } + + if (debug) { + LOG(INFO) << "moving offset by " << base_delta + << " (0x" << std::hex << base_delta << ") bytes or " + << std::dec << (base_delta/kPageSize) << " pages."; } + // TODO: is it going to be promatic to unlink a file that was flock-ed? ScopedFlock output_oat_lock; if (lock_output) { std::string error_msg; @@ -1098,24 +1265,28 @@ static int patchoat(int argc, char **argv) { } } - if (debug) { - LOG(INFO) << "moving offset by " << base_delta - << " (0x" << std::hex << base_delta << ") bytes or " - << std::dec << (base_delta/kPageSize) << " pages."; - } - 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); + output_oat.get(), output_image.get(), isa, &timings, + output_oat_fd >= 0, // was it opened from FD? + new_oat_out); } else if (have_oat_files) { TimingLogger::ScopedTiming pt("patch oat", &timings); - ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), &timings); - } else { + ret = PatchOat::Patch(input_oat.get(), base_delta, output_oat.get(), &timings, + output_oat_fd >= 0, // was it opened from FD? + new_oat_out); + } else if (have_image_files) { TimingLogger::ScopedTiming pt("patch image", &timings); - CHECK(have_image_files); ret = PatchOat::Patch(input_image_location, base_delta, output_image.get(), isa, &timings); + } else { + CHECK(false); + ret = true; + } + + if (kIsDebugBuild) { + LOG(INFO) << "Exiting with return ... " << ret; } cleanup(ret); return (ret) ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/patchoat/patchoat.h b/patchoat/patchoat.h index 6960d3b44..21041fbca 100644 --- a/patchoat/patchoat.h +++ b/patchoat/patchoat.h @@ -30,6 +30,7 @@ namespace art { class ImageHeader; +class OatHeader; namespace mirror { class Object; @@ -40,14 +41,21 @@ class ArtMethod; class PatchOat { public: - static bool Patch(File* oat_in, off_t delta, File* oat_out, TimingLogger* timings); + // Patch only the oat file + static bool Patch(File* oat_in, off_t delta, File* oat_out, TimingLogger* timings, + bool output_oat_opened_from_fd, // Was this using --oatput-oat-fd ? + bool new_oat_out); // Output oat was a new file created by us? + // Patch only the image (art file) 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, + // Patch both the image and the oat file + static bool Patch(File* oat_in, const std::string& art_location, off_t delta, File* oat_out, File* art_out, InstructionSet isa, - TimingLogger* timings); + TimingLogger* timings, + bool output_oat_opened_from_fd, // Was this using --oatput-oat-fd ? + bool new_oat_out); // Output oat was a new file created by us? private: // Takes ownership only of the ElfFile. All other pointers are only borrowed. @@ -63,6 +71,26 @@ class PatchOat { delta_(delta), timings_(timings) {} ~PatchOat() {} + // Was the .art image at image_path made with --compile-pic ? + static bool IsImagePic(const ImageHeader& image_header, const std::string& image_path); + + enum MaybePic { + NOT_PIC, // Code not pic. Patch as usual. + PIC, // Code was pic. Create symlink; skip OAT patching. + ERROR_OAT_FILE, // Failed to symlink oat file + ERROR_FIRST = ERROR_OAT_FILE, + }; + + // Was the .oat image at oat_in made with --compile-pic ? + static MaybePic IsOatPic(const ElfFile* oat_in); + + // Attempt to replace the file with a symlink + // Returns false if it fails + static bool ReplaceOatFileWithSymlink(const std::string& input_oat_filename, + const std::string& output_oat_filename, + bool output_oat_opened_from_fd, + bool new_oat_out); // Output oat was newly created? + static void BitmapCallback(mirror::Object* obj, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) { reinterpret_cast(arg)->VisitObject(obj); @@ -90,6 +118,9 @@ class PatchOat { mirror::Object* RelocatedCopyOf(mirror::Object*); mirror::Object* RelocatedAddressOf(mirror::Object* obj); + // Look up the oat header from any elf file. + static const OatHeader* GetOatHeader(const ElfFile* elf_file); + // Walks through the old image and patches the mmap'd copy of it to the new offset. It does not // change the heap. class PatchVisitor { diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc index 822bc7a84..b24ad4bbe 100644 --- a/runtime/class_linker.cc +++ b/runtime/class_linker.cc @@ -964,7 +964,7 @@ const OatFile* ClassLinker::FindOatFileInOatLocationForDexFile(const char* dex_l uint32_t dex_location_checksum, const char* oat_location, std::string* error_msg) { - std::unique_ptr oat_file(OatFile::Open(oat_location, oat_location, nullptr, + std::unique_ptr oat_file(OatFile::Open(oat_location, oat_location, nullptr, nullptr, !Runtime::Current()->IsCompiler(), error_msg)); if (oat_file.get() == nullptr) { @@ -1036,7 +1036,7 @@ const OatFile* ClassLinker::CreateOatFileForDexLocation(const char* dex_location error_msgs->push_back(error_msg); return nullptr; } - std::unique_ptr oat_file(OatFile::Open(oat_location, oat_location, nullptr, + std::unique_ptr oat_file(OatFile::Open(oat_location, oat_location, nullptr, nullptr, !Runtime::Current()->IsCompiler(), &error_msg)); if (oat_file.get() == nullptr) { @@ -1290,6 +1290,7 @@ const OatFile* ClassLinker::OpenOatFileFromDexLocation(const std::string& dex_lo // There is a high probability that both these oat files map similar/the same address // spaces so we must scope them like this so they each gets its turn. std::unique_ptr odex_oat_file(OatFile::Open(odex_filename, odex_filename, nullptr, + nullptr, executable, &odex_error_msg)); if (odex_oat_file.get() != nullptr && CheckOatFile(runtime, odex_oat_file.get(), isa, &odex_checksum_verified, @@ -1313,6 +1314,7 @@ const OatFile* ClassLinker::OpenOatFileFromDexLocation(const std::string& dex_lo bool cache_checksum_verified = false; if (have_dalvik_cache) { std::unique_ptr cache_oat_file(OatFile::Open(cache_filename, cache_filename, nullptr, + nullptr, executable, &cache_error_msg)); if (cache_oat_file.get() != nullptr && CheckOatFile(runtime, cache_oat_file.get(), isa, &cache_checksum_verified, @@ -1390,7 +1392,7 @@ const OatFile* ClassLinker::GetInterpretedOnlyOat(const std::string& oat_path, InstructionSet isa, std::string* error_msg) { // We open it non-executable - std::unique_ptr output(OatFile::Open(oat_path, oat_path, nullptr, false, error_msg)); + std::unique_ptr output(OatFile::Open(oat_path, oat_path, nullptr, nullptr, false, error_msg)); if (output.get() == nullptr) { return nullptr; } @@ -1447,7 +1449,7 @@ const OatFile* ClassLinker::PatchAndRetrieveOat(const std::string& input_oat, LOG(INFO) << "Relocate Oat File: " << command_line; bool success = Exec(argv, error_msg); if (success) { - std::unique_ptr output(OatFile::Open(output_oat, output_oat, nullptr, + std::unique_ptr output(OatFile::Open(output_oat, output_oat, nullptr, nullptr, !runtime->IsCompiler(), error_msg)); bool checksum_verified = false; if (output.get() != nullptr && CheckOatFile(runtime, output.get(), isa, &checksum_verified, @@ -1548,7 +1550,7 @@ const OatFile* ClassLinker::FindOatFileFromOatLocation(const std::string& oat_lo return oat_file; } - return OatFile::Open(oat_location, oat_location, nullptr, !Runtime::Current()->IsCompiler(), + return OatFile::Open(oat_location, oat_location, nullptr, nullptr, !Runtime::Current()->IsCompiler(), error_msg); } diff --git a/runtime/elf_file.cc b/runtime/elf_file.cc index 0ab6394b5..6ec0712f0 100644 --- a/runtime/elf_file.cc +++ b/runtime/elf_file.cc @@ -106,7 +106,7 @@ static void UnregisterCodeEntry(JITCodeEntry* entry) { delete entry; } -ElfFile::ElfFile(File* file, bool writable, bool program_header_only) +ElfFile::ElfFile(File* file, bool writable, bool program_header_only, uint8_t* requested_base) : file_(file), writable_(writable), program_header_only_(program_header_only), @@ -124,13 +124,15 @@ ElfFile::ElfFile(File* file, bool writable, bool program_header_only) symtab_symbol_table_(nullptr), dynsym_symbol_table_(nullptr), jit_elf_image_(nullptr), - jit_gdb_entry_(nullptr) { + jit_gdb_entry_(nullptr), + requested_base_(requested_base) { CHECK(file != nullptr); } ElfFile* ElfFile::Open(File* file, bool writable, bool program_header_only, - std::string* error_msg) { - std::unique_ptr elf_file(new ElfFile(file, writable, program_header_only)); + std::string* error_msg, uint8_t* requested_base) { + std::unique_ptr elf_file(new ElfFile(file, writable, program_header_only, + requested_base)); int prot; int flags; if (writable) { @@ -147,7 +149,8 @@ ElfFile* ElfFile::Open(File* file, bool writable, bool program_header_only, } ElfFile* ElfFile::Open(File* file, int prot, int flags, std::string* error_msg) { - std::unique_ptr elf_file(new ElfFile(file, (prot & PROT_WRITE) == PROT_WRITE, false)); + std::unique_ptr elf_file(new ElfFile(file, (prot & PROT_WRITE) == PROT_WRITE, false, + /*requested_base*/nullptr)); if (!elf_file->Setup(prot, flags, error_msg)) { return nullptr; } @@ -757,6 +760,8 @@ const byte* ElfFile::FindDynamicSymbolAddress(const std::string& symbol_name) co } const Elf32_Sym* sym = FindDynamicSymbol(symbol_name); if (sym != nullptr) { + // TODO: we need to change this to calculate base_address_ in ::Open, + // otherwise it will be wrongly 0 if ::Load has not yet been called. return base_address_ + sym->st_value; } else { return nullptr; @@ -1094,12 +1099,16 @@ bool ElfFile::Load(bool executable, std::string* error_msg) { } size_t file_length = static_cast(temp_file_length); if (!reserved) { - byte* reserve_base = ((program_header->p_vaddr != 0) ? - reinterpret_cast(program_header->p_vaddr) : nullptr); + uint8_t* reserve_base = reinterpret_cast(program_header->p_vaddr); + uint8_t* reserve_base_override = reserve_base; + // Override the base (e.g. when compiling with --compile-pic) + if (requested_base_ != nullptr) { + reserve_base_override = requested_base_; + } std::string reservation_name("ElfFile reservation for "); reservation_name += file_->GetPath(); std::unique_ptr reserve(MemMap::MapAnonymous(reservation_name.c_str(), - reserve_base, + reserve_base_override, GetLoadedSize(), PROT_NONE, false, error_msg)); if (reserve.get() == nullptr) { @@ -1108,9 +1117,15 @@ bool ElfFile::Load(bool executable, std::string* error_msg) { return false; } reserved = true; - if (reserve_base == nullptr) { - base_address_ = reserve->Begin(); - } + + // Base address is the difference of actual mapped location and the p_vaddr + base_address_ = reinterpret_cast(reinterpret_cast(reserve->Begin()) + - reinterpret_cast(reserve_base)); + // By adding the p_vaddr of a section/symbol to base_address_ we will always get the + // dynamic memory address of where that object is actually mapped + // + // TODO: base_address_ needs to be calculated in ::Open, otherwise + // FindDynamicSymbolAddress returns the wrong values until Load is called. segments_.push_back(reserve.release()); } // empty segment, nothing to map diff --git a/runtime/elf_file.h b/runtime/elf_file.h index 985be7611..9c0bc9a82 100644 --- a/runtime/elf_file.h +++ b/runtime/elf_file.h @@ -40,7 +40,8 @@ extern "C" { // ELFObjectFile. class ElfFile { public: - static ElfFile* Open(File* file, bool writable, bool program_header_only, std::string* error_msg); + static ElfFile* Open(File* file, bool writable, bool program_header_only, std::string* error_msg, + uint8_t* requested_base = nullptr); // TODO: move arg to before error_msg. // Open with specific mmap flags, Always maps in the whole file, not just the // program header sections. static ElfFile* Open(File* file, int mmap_prot, int mmap_flags, std::string* error_msg); @@ -52,10 +53,12 @@ class ElfFile { return *file_; } + // The start of the memory map address range for this ELF file. byte* Begin() const { return map_->Begin(); } + // The end of the memory map address range for this ELF file. byte* End() const { return map_->End(); } @@ -109,7 +112,7 @@ class ElfFile { bool Load(bool executable, std::string* error_msg); private: - ElfFile(File* file, bool writable, bool program_header_only); + ElfFile(File* file, bool writable, bool program_header_only, uint8_t* requested_base); bool Setup(int prot, int flags, std::string* error_msg); @@ -200,6 +203,9 @@ class ElfFile { JITCodeEntry* jit_gdb_entry_; std::unique_ptr gdb_file_mapping_; void GdbJITSupport(); + + // When not-null, override the base vaddr we memory map LOAD segments into. + uint8_t* requested_base_; }; } // namespace art diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc index 63b09e710..cfd202d30 100644 --- a/runtime/gc/space/image_space.cc +++ b/runtime/gc/space/image_space.cc @@ -665,7 +665,10 @@ OatFile* ImageSpace::OpenOatFile(const char* image_path, std::string* error_msg) const ImageHeader& image_header = GetImageHeader(); std::string oat_filename = ImageHeader::GetOatLocationFromImageLocation(image_path); + CHECK(image_header.GetOatDataBegin() != nullptr); + OatFile* oat_file = OatFile::Open(oat_filename, oat_filename, image_header.GetOatDataBegin(), + image_header.GetOatFileBegin(), !Runtime::Current()->IsCompiler(), error_msg); if (oat_file == NULL) { *error_msg = StringPrintf("Failed to open oat file '%s' referenced from image %s: %s", @@ -681,7 +684,7 @@ OatFile* ImageSpace::OpenOatFile(const char* image_path, std::string* error_msg) } int32_t image_patch_delta = image_header.GetPatchDelta(); int32_t oat_patch_delta = oat_file->GetOatHeader().GetImagePatchDelta(); - if (oat_patch_delta != image_patch_delta) { + if (oat_patch_delta != image_patch_delta && !image_header.CompilePic()) { // We should have already relocated by this point. Bail out. *error_msg = StringPrintf("Failed to match oat file patch delta %d to expected patch delta %d " "in image %s", oat_patch_delta, image_patch_delta, GetName()); diff --git a/runtime/image.cc b/runtime/image.cc index 478b486d9..90ca98559 100644 --- a/runtime/image.cc +++ b/runtime/image.cc @@ -35,7 +35,8 @@ ImageHeader::ImageHeader(uint32_t image_begin, uint32_t oat_file_begin, uint32_t oat_data_begin, uint32_t oat_data_end, - uint32_t oat_file_end) + uint32_t oat_file_end, + bool compile_pic) : image_begin_(image_begin), image_size_(image_size), image_bitmap_offset_(image_bitmap_offset), @@ -46,7 +47,8 @@ ImageHeader::ImageHeader(uint32_t image_begin, oat_data_end_(oat_data_end), oat_file_end_(oat_file_end), patch_delta_(0), - image_roots_(image_roots) { + image_roots_(image_roots), + compile_pic_(compile_pic) { CHECK_EQ(image_begin, RoundUp(image_begin, kPageSize)); CHECK_EQ(oat_file_begin, RoundUp(oat_file_begin, kPageSize)); CHECK_EQ(oat_data_begin, RoundUp(oat_data_begin, kPageSize)); diff --git a/runtime/image.h b/runtime/image.h index 424a40b7c..42d9c0df8 100644 --- a/runtime/image.h +++ b/runtime/image.h @@ -28,7 +28,7 @@ namespace art { // header of image files written by ImageWriter, read and validated by Space. class PACKED(4) ImageHeader { public: - ImageHeader() {} + ImageHeader() : compile_pic_(0) {} ImageHeader(uint32_t image_begin, uint32_t image_size_, @@ -39,7 +39,8 @@ class PACKED(4) ImageHeader { uint32_t oat_file_begin, uint32_t oat_data_begin, uint32_t oat_data_end, - uint32_t oat_file_end); + uint32_t oat_file_end, + bool compile_pic_); bool IsValid() const; const char* GetMagic() const; @@ -120,6 +121,10 @@ class PACKED(4) ImageHeader { void RelocateImage(off_t delta); + bool CompilePic() const { + return compile_pic_ != 0; + } + private: static const byte kImageMagic[4]; static const byte kImageVersion[4]; @@ -161,6 +166,9 @@ class PACKED(4) ImageHeader { // Absolute address of an Object[] of objects needed to reinitialize from an image. uint32_t image_roots_; + // Boolean (0 or 1) to denote if the image was compiled with --compile-pic option + const uint32_t compile_pic_; + friend class ImageWriter; }; diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index 01c8978a2..2bb59e2d2 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -287,9 +287,11 @@ static const jbyte kDexoptNeeded = 2; template static jbyte IsDexOptNeededForFile(const std::string& oat_filename, const char* filename, - InstructionSet target_instruction_set) { + InstructionSet target_instruction_set, + bool* oat_is_pic) { std::string error_msg; std::unique_ptr oat_file(OatFile::Open(oat_filename, oat_filename, nullptr, + nullptr, false, &error_msg)); if (oat_file.get() == nullptr) { if (kReasonLogging) { @@ -299,6 +301,11 @@ static jbyte IsDexOptNeededForFile(const std::string& oat_filename, const char* error_msg.clear(); return kDexoptNeeded; } + + // Pass-up the information about if this is PIC. + // TODO: Refactor this function to be less complicated. + *oat_is_pic = oat_file->IsPic(); + bool should_relocate_if_possible = Runtime::Current()->ShouldRelocate(); uint32_t location_checksum = 0; const art::OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(filename, nullptr, @@ -526,10 +533,16 @@ static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename, // Lets try the cache first (since we want to load from there since thats where the relocated // versions will be). if (have_cache_filename && !force_system_only) { + bool oat_is_pic; // We can use the dalvik-cache if we find a good file. dalvik_cache_decision = IsDexOptNeededForFile(cache_filename, filename, - target_instruction_set); + target_instruction_set, &oat_is_pic); + + // Apps that are compiled with --compile-pic never need to be patchoat-d + if (oat_is_pic && dalvik_cache_decision == kPatchoatNeeded) { + dalvik_cache_decision = kUpToDate; + } // We will only return DexOptNeeded if both the cache and system return it. if (dalvik_cache_decision != kDexoptNeeded && !require_system_version) { CHECK(!(dalvik_cache_decision == kPatchoatNeeded && !should_relocate_if_possible)) @@ -539,12 +552,18 @@ static jbyte IsDexOptNeededInternal(JNIEnv* env, const char* filename, // We couldn't find one thats easy. We should now try the system. } + bool oat_is_pic; jbyte system_decision = IsDexOptNeededForFile(odex_filename, filename, - target_instruction_set); + target_instruction_set, &oat_is_pic); CHECK(!(system_decision == kPatchoatNeeded && !should_relocate_if_possible)) << "May not return PatchoatNeeded when patching is disabled."; + // Apps that are compiled with --compile-pic never need to be patchoat-d + if (oat_is_pic && system_decision == kPatchoatNeeded) { + system_decision = kUpToDate; + } + if (require_system_version && system_decision == kPatchoatNeeded && dalvik_cache_decision == kUpToDate) { // We have a version from system relocated to the cache. Return it. diff --git a/runtime/oat.cc b/runtime/oat.cc index ede108cd1..9d11de8b7 100644 --- a/runtime/oat.cc +++ b/runtime/oat.cc @@ -470,6 +470,12 @@ size_t OatHeader::GetHeaderSize() const { return sizeof(OatHeader) + key_value_store_size_; } +bool OatHeader::IsPic() const { + const char* pic_string = GetStoreValueByKey(OatHeader::kPicKey); + static const char kTrue[] = "true"; + return (pic_string != nullptr && strncmp(pic_string, kTrue, sizeof(kTrue)) == 0); +} + void OatHeader::Flatten(const SafeMap* key_value_store) { char* data_ptr = reinterpret_cast(&key_value_store_); if (key_value_store != nullptr) { diff --git a/runtime/oat.h b/runtime/oat.h index 139f1ccb7..a5cb4bc51 100644 --- a/runtime/oat.h +++ b/runtime/oat.h @@ -104,6 +104,7 @@ class PACKED(4) OatHeader { bool GetStoreKeyValuePairByIndex(size_t index, const char** key, const char** value) const; size_t GetHeaderSize() const; + bool IsPic() const; private: OatHeader(InstructionSet instruction_set, diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index be5019983..9ba860c7b 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -67,6 +67,7 @@ OatFile* OatFile::OpenMemory(std::vector& oat_contents, OatFile* OatFile::Open(const std::string& filename, const std::string& location, byte* requested_base, + uint8_t* oat_file_begin, bool executable, std::string* error_msg) { CHECK(!filename.empty()) << location; @@ -91,7 +92,8 @@ OatFile* OatFile::Open(const std::string& filename, *error_msg = StringPrintf("Failed to open oat filename for reading: %s", strerror(errno)); return nullptr; } - ret.reset(OpenElfFile(file.get(), location, requested_base, false, executable, error_msg)); + ret.reset(OpenElfFile(file.get(), location, requested_base, oat_file_begin, false, executable, + error_msg)); // It would be nice to unlink here. But we might have opened the file created by the // ScopedLock, which we better not delete to avoid races. TODO: Investigate how to fix the API @@ -102,12 +104,12 @@ OatFile* OatFile::Open(const std::string& filename, OatFile* OatFile::OpenWritable(File* file, const std::string& location, std::string* error_msg) { CheckLocation(location); - return OpenElfFile(file, location, NULL, true, false, error_msg); + return OpenElfFile(file, location, nullptr, nullptr, true, false, error_msg); } OatFile* OatFile::OpenReadable(File* file, const std::string& location, std::string* error_msg) { CheckLocation(location); - return OpenElfFile(file, location, NULL, false, false, error_msg); + return OpenElfFile(file, location, nullptr, nullptr, false, false, error_msg); } OatFile* OatFile::OpenDlopen(const std::string& elf_filename, @@ -125,11 +127,13 @@ OatFile* OatFile::OpenDlopen(const std::string& elf_filename, OatFile* OatFile::OpenElfFile(File* file, const std::string& location, byte* requested_base, + uint8_t* oat_file_begin, bool writable, bool executable, std::string* error_msg) { std::unique_ptr oat_file(new OatFile(location, executable)); - bool success = oat_file->ElfFileOpen(file, requested_base, writable, executable, error_msg); + bool success = oat_file->ElfFileOpen(file, requested_base, oat_file_begin, writable, executable, + error_msg); if (!success) { CHECK(!error_msg->empty()); return nullptr; @@ -188,9 +192,12 @@ bool OatFile::Dlopen(const std::string& elf_filename, byte* requested_base, return Setup(error_msg); } -bool OatFile::ElfFileOpen(File* file, byte* requested_base, bool writable, bool executable, +bool OatFile::ElfFileOpen(File* file, byte* requested_base, uint8_t* oat_file_begin, + bool writable, bool executable, std::string* error_msg) { - elf_file_.reset(ElfFile::Open(file, writable, true, error_msg)); + // TODO: rename requested_base to oat_data_begin + elf_file_.reset(ElfFile::Open(file, writable, /*program_header_only*/true, error_msg, + oat_file_begin)); if (elf_file_.get() == nullptr) { DCHECK(!error_msg->empty()); return false; @@ -600,8 +607,7 @@ void OatFile::OatMethod::LinkMethod(mirror::ArtMethod* method) const { } bool OatFile::IsPic() const { - const char* pic_string = GetOatHeader().GetStoreValueByKey(OatHeader::kPicKey); - return (pic_string != nullptr && strncmp(pic_string, "true", 5) == 0); + return GetOatHeader().IsPic(); // TODO: Check against oat_patches. b/18144996 } diff --git a/runtime/oat_file.h b/runtime/oat_file.h index a4c8da930..488988e1b 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -49,6 +49,7 @@ class OatFile { static OatFile* Open(const std::string& filename, const std::string& location, byte* requested_base, + uint8_t* oat_file_begin, bool executable, std::string* error_msg); @@ -305,13 +306,16 @@ class OatFile { static OatFile* OpenElfFile(File* file, const std::string& location, byte* requested_base, + uint8_t* oat_file_begin, // Override base if not null bool writable, bool executable, std::string* error_msg); explicit OatFile(const std::string& filename, bool executable); bool Dlopen(const std::string& elf_filename, byte* requested_base, std::string* error_msg); - bool ElfFileOpen(File* file, byte* requested_base, bool writable, bool executable, + bool ElfFileOpen(File* file, byte* requested_base, + uint8_t* oat_file_begin, // Override where the file is loaded to if not null + bool writable, bool executable, std::string* error_msg); bool Setup(std::string* error_msg); -- 2.11.0