From ec2cdf4286921131a5f9b3ed12060657ec40f636 Mon Sep 17 00:00:00 2001 From: David Srbecky Date: Fri, 8 Dec 2017 16:21:25 +0000 Subject: Try to mmap vdex file within the address range of the ELF file. Add ELF section for the vdex file and mmap it there at runtime. This ensures that the data is at predictable location, which is needed to be able to reference it from native debug-info. This does not change the amount of memory allocated, or the location of the data on disk. However, it does change how the memory is allocated - it replaces two allocations (ELF and vdex) by just one (ELF which includes the vdex). Bug: 71579677 Test: m test-art-host-gtest Test: testrunner.py --host --optimizing Change-Id: Ie2abd36c8b6617a527368e71f932998bbe5ad38c --- build/Android.gtest.mk | 5 +++ compiler/linker/elf_builder.h | 21 ++++++++++-- dex2oat/dex2oat.cc | 4 +-- dex2oat/linker/elf_writer.h | 8 ++++- dex2oat/linker/elf_writer_quick.cc | 9 +++-- dex2oat/linker/image_test.h | 3 +- dex2oat/linker/oat_writer.h | 4 +++ dex2oat/linker/oat_writer_test.cc | 3 +- oatdump/oatdump.cc | 3 +- runtime/oat_file.cc | 68 ++++++++++++++++++++++++++++---------- runtime/oat_file.h | 13 ++++++++ runtime/oat_file_test.cc | 28 +++++++++++++++- runtime/vdex_file.cc | 55 ++++++++++++++++++++++-------- runtime/vdex_file.h | 48 +++++++++++++++++++++++++-- 14 files changed, 227 insertions(+), 45 deletions(-) diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk index 0023cb9e91..60bcd63cdb 100644 --- a/build/Android.gtest.mk +++ b/build/Android.gtest.mk @@ -170,6 +170,11 @@ ART_GTEST_dex2oat_environment_tests_TARGET_DEPS := \ $(TARGET_CORE_IMAGE_interpreter_32) \ patchoatd-target +ART_GTEST_oat_file_test_HOST_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) +ART_GTEST_oat_file_test_TARGET_DEPS := \ + $(ART_GTEST_dex2oat_environment_tests_TARGET_DEPS) + ART_GTEST_oat_file_assistant_test_HOST_DEPS := \ $(ART_GTEST_dex2oat_environment_tests_HOST_DEPS) ART_GTEST_oat_file_assistant_test_TARGET_DEPS := \ diff --git a/compiler/linker/elf_builder.h b/compiler/linker/elf_builder.h index aa3cd98595..5262ab6f3b 100644 --- a/compiler/linker/elf_builder.h +++ b/compiler/linker/elf_builder.h @@ -38,9 +38,10 @@ namespace linker { // Elf_Ehdr - The ELF header. // Elf_Phdr[] - Program headers for the linker. // .note.gnu.build-id - Optional build ID section (SHA-1 digest). -// .rodata - DEX files and oat metadata. +// .rodata - Oat metadata. // .text - Compiled code. // .bss - Zero-initialized writeable section. +// .dex - Reserved NOBITS space for dex-related data. // .MIPS.abiflags - MIPS specific section. // .dynstr - Names for .dynsym. // .dynsym - A few oat-specific dynamic symbols. @@ -503,6 +504,7 @@ class ElfBuilder FINAL { rodata_(this, ".rodata", SHT_PROGBITS, SHF_ALLOC, nullptr, 0, kPageSize, 0), text_(this, ".text", SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, nullptr, 0, kPageSize, 0), bss_(this, ".bss", SHT_NOBITS, SHF_ALLOC, nullptr, 0, kPageSize, 0), + dex_(this, ".dex", SHT_NOBITS, SHF_ALLOC, nullptr, 0, kPageSize, 0), dynstr_(this, ".dynstr", SHF_ALLOC, kPageSize), dynsym_(this, ".dynsym", SHT_DYNSYM, SHF_ALLOC, &dynstr_), hash_(this, ".hash", SHT_HASH, SHF_ALLOC, &dynsym_, 0, sizeof(Elf_Word), sizeof(Elf_Word)), @@ -525,6 +527,7 @@ class ElfBuilder FINAL { virtual_address_(0) { text_.phdr_flags_ = PF_R | PF_X; bss_.phdr_flags_ = PF_R | PF_W; + dex_.phdr_flags_ = PF_R; dynamic_.phdr_flags_ = PF_R | PF_W; dynamic_.phdr_type_ = PT_DYNAMIC; eh_frame_hdr_.phdr_type_ = PT_GNU_EH_FRAME; @@ -538,6 +541,7 @@ class ElfBuilder FINAL { Section* GetRoData() { return &rodata_; } Section* GetText() { return &text_; } Section* GetBss() { return &bss_; } + Section* GetDex() { return &dex_; } StringSection* GetStrTab() { return &strtab_; } SymbolSection* GetSymTab() { return &symtab_; } Section* GetEhFrame() { return &eh_frame_; } @@ -666,7 +670,8 @@ class ElfBuilder FINAL { Elf_Word text_size, Elf_Word bss_size, Elf_Word bss_methods_offset, - Elf_Word bss_roots_offset) { + Elf_Word bss_roots_offset, + Elf_Word dex_size) { std::string soname(elf_file_path); size_t directory_separator_pos = soname.rfind('/'); if (directory_separator_pos != std::string::npos) { @@ -679,6 +684,9 @@ class ElfBuilder FINAL { if (bss_size != 0) { bss_.AllocateVirtualMemory(bss_size); } + if (dex_size != 0) { + dex_.AllocateVirtualMemory(dex_size); + } if (isa_ == InstructionSet::kMips || isa_ == InstructionSet::kMips64) { abiflags_.AllocateVirtualMemory(abiflags_.GetSize()); } @@ -725,6 +733,14 @@ class ElfBuilder FINAL { Elf_Word bsslastword_address = bss_.GetAddress() + bss_size - 4; dynsym_.Add(oatbsslastword, &bss_, bsslastword_address, 4, STB_GLOBAL, STT_OBJECT); } + if (dex_size != 0u) { + Elf_Word oatdex = dynstr_.Add("oatdex"); + dynsym_.Add(oatdex, &dex_, dex_.GetAddress(), dex_size, STB_GLOBAL, STT_OBJECT); + Elf_Word oatdexlastword = dynstr_.Add("oatdexlastword"); + Elf_Word oatdexlastword_address = dex_.GetAddress() + dex_size - 4; + dynsym_.Add(oatdexlastword, &dex_, oatdexlastword_address, 4, STB_GLOBAL, STT_OBJECT); + } + Elf_Word soname_offset = dynstr_.Add(soname); // We do not really need a hash-table since there is so few entries. @@ -967,6 +983,7 @@ class ElfBuilder FINAL { Section rodata_; Section text_; Section bss_; + Section dex_; CachedStringSection dynstr_; SymbolSection dynsym_; CachedSection hash_; diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 4248b72b3a..3b05f931a9 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -2020,8 +2020,8 @@ class Dex2Oat FINAL { text_size, oat_writer->GetBssSize(), oat_writer->GetBssMethodsOffset(), - oat_writer->GetBssRootsOffset()); - + oat_writer->GetBssRootsOffset(), + oat_writer->GetVdexSize()); if (IsImage()) { // Update oat layout. DCHECK(image_writer_ != nullptr); diff --git a/dex2oat/linker/elf_writer.h b/dex2oat/linker/elf_writer.h index 0eb36eda0f..a49112bfe6 100644 --- a/dex2oat/linker/elf_writer.h +++ b/dex2oat/linker/elf_writer.h @@ -55,11 +55,17 @@ class ElfWriter { virtual ~ElfWriter() {} virtual void Start() = 0; + // Prepares memory layout of the whole ELF file, and creates dynamic symbols + // which point to specific areas of interest (usually section begin and end). + // This is needed as multi-image needs to know the memory layout of all ELF + // files, before starting to write them. + // This method must be called before calling GetLoadedSize(). virtual void PrepareDynamicSection(size_t rodata_size, size_t text_size, size_t bss_size, size_t bss_methods_offset, - size_t bss_roots_offset) = 0; + size_t bss_roots_offset, + size_t dex_section_size) = 0; virtual void PrepareDebugInfo(const ArrayRef& method_infos) = 0; virtual OutputStream* StartRoData() = 0; virtual void EndRoData(OutputStream* rodata) = 0; diff --git a/dex2oat/linker/elf_writer_quick.cc b/dex2oat/linker/elf_writer_quick.cc index aa64b7d59e..af11d5864d 100644 --- a/dex2oat/linker/elf_writer_quick.cc +++ b/dex2oat/linker/elf_writer_quick.cc @@ -99,7 +99,8 @@ class ElfWriterQuick FINAL : public ElfWriter { size_t text_size, size_t bss_size, size_t bss_methods_offset, - size_t bss_roots_offset) OVERRIDE; + size_t bss_roots_offset, + size_t dex_section_size) OVERRIDE; void PrepareDebugInfo(const ArrayRef& method_infos) OVERRIDE; OutputStream* StartRoData() OVERRIDE; void EndRoData(OutputStream* rodata) OVERRIDE; @@ -183,7 +184,8 @@ void ElfWriterQuick::PrepareDynamicSection(size_t rodata_size, size_t text_size, size_t bss_size, size_t bss_methods_offset, - size_t bss_roots_offset) { + size_t bss_roots_offset, + size_t dex_section_size) { DCHECK_EQ(rodata_size_, 0u); rodata_size_ = rodata_size; DCHECK_EQ(text_size_, 0u); @@ -195,7 +197,8 @@ void ElfWriterQuick::PrepareDynamicSection(size_t rodata_size, text_size_, bss_size_, bss_methods_offset, - bss_roots_offset); + bss_roots_offset, + dex_section_size); } template diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h index 2c24066782..092f06c742 100644 --- a/dex2oat/linker/image_test.h +++ b/dex2oat/linker/image_test.h @@ -318,7 +318,8 @@ inline void CompilationHelper::Compile(CompilerDriver* driver, text_size, oat_writer->GetBssSize(), oat_writer->GetBssMethodsOffset(), - oat_writer->GetBssRootsOffset()); + oat_writer->GetBssRootsOffset(), + oat_writer->GetVdexSize()); writer->UpdateOatFileLayout(i, elf_writer->GetLoadedSize(), diff --git a/dex2oat/linker/oat_writer.h b/dex2oat/linker/oat_writer.h index 3e9f4636fb..dda9153e13 100644 --- a/dex2oat/linker/oat_writer.h +++ b/dex2oat/linker/oat_writer.h @@ -230,6 +230,10 @@ class OatWriter { return bss_roots_offset_; } + size_t GetVdexSize() const { + return vdex_size_; + } + size_t GetOatDataOffset() const { return oat_data_offset_; } diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc index 138306e23c..7e9ad90042 100644 --- a/dex2oat/linker/oat_writer_test.cc +++ b/dex2oat/linker/oat_writer_test.cc @@ -219,7 +219,8 @@ class OatTest : public CommonCompilerTest { text_size, oat_writer.GetBssSize(), oat_writer.GetBssMethodsOffset(), - oat_writer.GetBssRootsOffset()); + oat_writer.GetBssRootsOffset(), + oat_writer.GetVdexSize()); std::unique_ptr vdex_out = std::make_unique(std::make_unique(vdex_file)); diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 1a1d8cc76e..d6b2ddd678 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -171,7 +171,8 @@ class OatSymbolizer FINAL { text_size, oat_file_->BssSize(), oat_file_->BssMethodsOffset(), - oat_file_->BssRootsOffset()); + oat_file_->BssRootsOffset(), + oat_file_->VdexSize()); builder_->WriteDynamicSection(); const OatHeader& oat_header = oat_file_->GetOatHeader(); diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc index c20aa117f6..73820c10cc 100644 --- a/runtime/oat_file.cc +++ b/runtime/oat_file.cc @@ -194,10 +194,6 @@ OatFileBase* OatFileBase::OpenOatFile(const std::string& vdex_filename, ret->PreLoad(); - if (!ret->LoadVdex(vdex_filename, writable, low_4gb, error_msg)) { - return nullptr; - } - if (!ret->Load(elf_filename, oat_file_begin, writable, @@ -211,6 +207,10 @@ OatFileBase* OatFileBase::OpenOatFile(const std::string& vdex_filename, return nullptr; } + if (!ret->LoadVdex(vdex_filename, writable, low_4gb, error_msg)) { + return nullptr; + } + ret->PreSetup(elf_filename); if (!ret->Setup(abs_dex_location, error_msg)) { @@ -234,10 +234,6 @@ OatFileBase* OatFileBase::OpenOatFile(int vdex_fd, std::string* error_msg) { std::unique_ptr ret(new kOatFileBaseSubType(oat_location, executable)); - if (!ret->LoadVdex(vdex_fd, vdex_location, writable, low_4gb, error_msg)) { - return nullptr; - } - if (!ret->Load(oat_fd, oat_file_begin, writable, @@ -251,6 +247,10 @@ OatFileBase* OatFileBase::OpenOatFile(int vdex_fd, return nullptr; } + if (!ret->LoadVdex(vdex_fd, vdex_location, writable, low_4gb, error_msg)) { + return nullptr; + } + ret->PreSetup(oat_location); if (!ret->Setup(abs_dex_location, error_msg)) { @@ -264,7 +264,14 @@ bool OatFileBase::LoadVdex(const std::string& vdex_filename, bool writable, bool low_4gb, std::string* error_msg) { - vdex_ = VdexFile::Open(vdex_filename, writable, low_4gb, /* unquicken*/ false, error_msg); + vdex_ = VdexFile::OpenAtAddress(vdex_begin_, + vdex_end_ - vdex_begin_, + true /* mmap_reuse */, + vdex_filename, + writable, + low_4gb, + /* unquicken*/ false, + error_msg); if (vdex_.get() == nullptr) { *error_msg = StringPrintf("Failed to load vdex file '%s' %s", vdex_filename.c_str(), @@ -285,13 +292,16 @@ bool OatFileBase::LoadVdex(int vdex_fd, if (rc == -1) { PLOG(WARNING) << "Failed getting length of vdex file"; } else { - vdex_ = VdexFile::Open(vdex_fd, - s.st_size, - vdex_filename, - writable, - low_4gb, - false /* unquicken */, - error_msg); + vdex_ = VdexFile::OpenAtAddress(vdex_begin_, + vdex_end_ - vdex_begin_, + true /* mmap_reuse */, + vdex_fd, + s.st_size, + vdex_filename, + writable, + low_4gb, + false /* unquicken */, + error_msg); if (vdex_.get() == nullptr) { *error_msg = "Failed opening vdex file."; return false; @@ -339,7 +349,7 @@ bool OatFileBase::ComputeFields(uint8_t* requested_base, } else { bss_end_ = const_cast(FindDynamicSymbolAddress("oatbsslastword", &symbol_error_msg)); if (bss_end_ == nullptr) { - *error_msg = StringPrintf("Failed to find oatbasslastword symbol in '%s'", file_path.c_str()); + *error_msg = StringPrintf("Failed to find oatbsslastword symbol in '%s'", file_path.c_str()); return false; } // Readjust to be non-inclusive upper bound. @@ -351,6 +361,20 @@ bool OatFileBase::ComputeFields(uint8_t* requested_base, bss_roots_ = const_cast(FindDynamicSymbolAddress("oatbssroots", &symbol_error_msg)); } + vdex_begin_ = const_cast(FindDynamicSymbolAddress("oatdex", &symbol_error_msg)); + if (vdex_begin_ == nullptr) { + // No .vdex section. + vdex_end_ = nullptr; + } else { + vdex_end_ = const_cast(FindDynamicSymbolAddress("oatdexlastword", &symbol_error_msg)); + if (vdex_end_ == nullptr) { + *error_msg = StringPrintf("Failed to find oatdexlastword symbol in '%s'", file_path.c_str()); + return false; + } + // Readjust to be non-inclusive upper bound. + vdex_end_ += sizeof(uint32_t); + } + return true; } @@ -1449,6 +1473,8 @@ OatFile::OatFile(const std::string& location, bool is_executable) bss_methods_(nullptr), bss_roots_(nullptr), is_executable_(is_executable), + vdex_begin_(nullptr), + vdex_end_(nullptr), secondary_lookup_lock_("OatFile secondary lookup lock", kOatFileSecondaryLookupLock) { CHECK(!location_.empty()); } @@ -1479,6 +1505,14 @@ const uint8_t* OatFile::BssEnd() const { return bss_end_; } +const uint8_t* OatFile::VdexBegin() const { + return vdex_begin_; +} + +const uint8_t* OatFile::VdexEnd() const { + return vdex_end_; +} + const uint8_t* OatFile::DexBegin() const { return vdex_->Begin(); } diff --git a/runtime/oat_file.h b/runtime/oat_file.h index 61daa9579e..ba1c120761 100644 --- a/runtime/oat_file.h +++ b/runtime/oat_file.h @@ -278,6 +278,10 @@ class OatFile { return BssEnd() - BssBegin(); } + size_t VdexSize() const { + return VdexEnd() - VdexBegin(); + } + size_t BssMethodsOffset() const { // Note: This is used only for symbolizer and needs to return a valid .bss offset. return (bss_methods_ != nullptr) ? bss_methods_ - BssBegin() : BssRootsOffset(); @@ -298,6 +302,9 @@ class OatFile { const uint8_t* BssBegin() const; const uint8_t* BssEnd() const; + const uint8_t* VdexBegin() const; + const uint8_t* VdexEnd() const; + const uint8_t* DexBegin() const; const uint8_t* DexEnd() const; @@ -362,6 +369,12 @@ class OatFile { // Was this oat_file loaded executable? const bool is_executable_; + // Pointer to the .vdex section, if present, otherwise null. + uint8_t* vdex_begin_; + + // Pointer to the end of the .vdex section, if present, otherwise null. + uint8_t* vdex_end_; + // Owning storage for the OatDexFile objects. std::vector oat_dex_files_storage_; diff --git a/runtime/oat_file_test.cc b/runtime/oat_file_test.cc index 7bf0f84596..8d864018ab 100644 --- a/runtime/oat_file_test.cc +++ b/runtime/oat_file_test.cc @@ -21,11 +21,13 @@ #include #include "common_runtime_test.h" +#include "dexopt_test.h" #include "scoped_thread_state_change-inl.h" +#include "vdex_file.h" namespace art { -class OatFileTest : public CommonRuntimeTest { +class OatFileTest : public DexoptTest { }; TEST_F(OatFileTest, ResolveRelativeEncodedDexLocation) { @@ -62,4 +64,28 @@ TEST_F(OatFileTest, ResolveRelativeEncodedDexLocation) { "/data/app/foo/base.apk", "o/base.apk")); } +TEST_F(OatFileTest, LoadOat) { + std::string dex_location = GetScratchDir() + "/LoadOat.jar"; + + Copy(GetDexSrc1(), dex_location); + GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed); + + std::string oat_location; + std::string error_msg; + ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename( + dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg; + std::unique_ptr odex_file(OatFile::Open(oat_location.c_str(), + oat_location.c_str(), + nullptr, + nullptr, + false, + /*low_4gb*/false, + dex_location.c_str(), + &error_msg)); + ASSERT_TRUE(odex_file.get() != nullptr); + + // Check that the vdex file was loaded in the reserved space of odex file. + EXPECT_EQ(odex_file->GetVdexFile()->Begin(), odex_file->VdexBegin()); +} + } // namespace art diff --git a/runtime/vdex_file.cc b/runtime/vdex_file.cc index fe768a1fd5..47c8bd7cef 100644 --- a/runtime/vdex_file.cc +++ b/runtime/vdex_file.cc @@ -57,11 +57,14 @@ VdexFile::Header::Header(uint32_t number_of_dex_files, DCHECK(IsVersionValid()); } -std::unique_ptr VdexFile::Open(const std::string& vdex_filename, - bool writable, - bool low_4gb, - bool unquicken, - std::string* error_msg) { +std::unique_ptr VdexFile::OpenAtAddress(uint8_t* mmap_addr, + size_t mmap_size, + bool mmap_reuse, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + bool unquicken, + std::string* error_msg) { if (!OS::FileExists(vdex_filename.c_str())) { *error_msg = "File " + vdex_filename + " does not exist."; return nullptr; @@ -85,23 +88,47 @@ std::unique_ptr VdexFile::Open(const std::string& vdex_filename, return nullptr; } - return Open(vdex_file->Fd(), vdex_length, vdex_filename, writable, low_4gb, unquicken, error_msg); + return OpenAtAddress(mmap_addr, + mmap_size, + mmap_reuse, + vdex_file->Fd(), + vdex_length, + vdex_filename, + writable, + low_4gb, + unquicken, + error_msg); } -std::unique_ptr VdexFile::Open(int file_fd, - size_t vdex_length, - const std::string& vdex_filename, - bool writable, - bool low_4gb, - bool unquicken, - std::string* error_msg) { - std::unique_ptr mmap(MemMap::MapFile( +std::unique_ptr VdexFile::OpenAtAddress(uint8_t* mmap_addr, + size_t mmap_size, + bool mmap_reuse, + int file_fd, + size_t vdex_length, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + bool unquicken, + std::string* error_msg) { + if (low_4gb) { + LOG(WARNING) << "Can not mmap vdex in low 4GB"; // TODO: Implement in MemMap. + mmap_addr = nullptr; + mmap_reuse = false; + } + if (mmap_addr != nullptr && mmap_size < vdex_length) { + LOG(WARNING) << "Insufficient pre-allocated space to mmap vdex."; + mmap_addr = nullptr; + mmap_reuse = false; + } + std::unique_ptr mmap(MemMap::MapFileAtAddress( + mmap_addr, vdex_length, (writable || unquicken) ? PROT_READ | PROT_WRITE : PROT_READ, unquicken ? MAP_PRIVATE : MAP_SHARED, file_fd, 0 /* start offset */, low_4gb, + mmap_reuse, vdex_filename.c_str(), error_msg)); if (mmap == nullptr) { diff --git a/runtime/vdex_file.h b/runtime/vdex_file.h index 2d9fcab59c..f78335d347 100644 --- a/runtime/vdex_file.h +++ b/runtime/vdex_file.h @@ -101,12 +101,45 @@ class VdexFile { explicit VdexFile(MemMap* mmap) : mmap_(mmap) {} + // Returns nullptr if the vdex file cannot be opened or is not valid. + // The mmap_* parameters can be left empty (nullptr/0/false) to allocate at random address. + static std::unique_ptr OpenAtAddress(uint8_t* mmap_addr, + size_t mmap_size, + bool mmap_reuse, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + bool unquicken, + std::string* error_msg); + + // Returns nullptr if the vdex file cannot be opened or is not valid. + // The mmap_* parameters can be left empty (nullptr/0/false) to allocate at random address. + static std::unique_ptr OpenAtAddress(uint8_t* mmap_addr, + size_t mmap_size, + bool mmap_reuse, + int file_fd, + size_t vdex_length, + const std::string& vdex_filename, + bool writable, + bool low_4gb, + bool unquicken, + std::string* error_msg); + // Returns nullptr if the vdex file cannot be opened or is not valid. static std::unique_ptr Open(const std::string& vdex_filename, bool writable, bool low_4gb, bool unquicken, - std::string* error_msg); + std::string* error_msg) { + return OpenAtAddress(nullptr, + 0, + false, + vdex_filename, + writable, + low_4gb, + unquicken, + error_msg); + } // Returns nullptr if the vdex file cannot be opened or is not valid. static std::unique_ptr Open(int file_fd, @@ -115,7 +148,18 @@ class VdexFile { bool writable, bool low_4gb, bool unquicken, - std::string* error_msg); + std::string* error_msg) { + return OpenAtAddress(nullptr, + 0, + false, + file_fd, + vdex_length, + vdex_filename, + writable, + low_4gb, + unquicken, + error_msg); + } const uint8_t* Begin() const { return mmap_->Begin(); } const uint8_t* End() const { return mmap_->End(); } -- cgit v1.2.3-59-g8ed1b