diff options
author | 2025-01-10 14:07:53 +0000 | |
---|---|---|
committer | 2025-01-30 10:55:25 -0800 | |
commit | 7e3cf2bac4db55a735a3f35b2e6e0e7b4b49210e (patch) | |
tree | 9d41619560190b5f8bde4ff9f8b57f43b0b5cf61 | |
parent | 55cb961ae83563eed14fb3aecc7debafc9c86192 (diff) |
Move dynamic sections to start of OAT file.
Extra padding is required in OAT files when a section have different
runtime permissions from the previous one, because in this case
it should be mmaped separately as a part of a new loadable segment
and therefore it should be page-aligned in the file.
This patch places read-only dynamic sections prior to the read-only
.rodata section (before it), thus combining a few R sections into
a segment and reducing the amount of padding required.
To do this:
* Change runtime permissions of .dynamic section from read-write to
read-only. This is valid because:
* Dynamic linker can update only DT_DEBUG entry that we don't use
* ELF spec does not require it to be writable. Rather, it states
that it can be writable depending on the OS and processor.
However, glibc versions < 2.35 have a bug that causes a crash
when loading a dynamic shared object with read-only .dynamic section
on host: (https://sourceware.org/bugzilla/show_bug.cgi?id=28340).
Therefore only use glibc's dlopen when it is version >= 2.35,
otherwise use ART's ELF loader. Bionic dlopen correctly accepts
read-only .dynamic section.
* Put the dynamic sections (.dynstr, .dynsym, .hash, .dynamic) prior
to the .rodata section. As content of dynamic sections depend on
other sections, we firstly reserve enough space for them before
.rodata and then generate actual data and write it.
Actual disk usage reduction from this patch:
* Approx 90% of tested dense .odex files are reduced by 40 KiB
(all reductions are in range 16-48 KiB).
* Approx 95% of tested sparse .odex files are reduced by 8 KiB
(all reductions are in range 4-16 Kib).
This patch, along with the patch that reduces .rodata alignment,
decreases the total sparse size of oat files from system.img
built for aosp_shiba-trunk_staging-user lunch target by 1.4%
(from ~222 MiB to ~219 MiB).
Test: testrunner.py --optimizing
Bug: 378792349
Change-Id: I2a4929adfdf8a496832880f42293371ae2e0e06b
-rw-r--r-- | dex2oat/linker/elf_writer_quick.cc | 1 | ||||
-rw-r--r-- | dex2oat/linker/elf_writer_test.cc | 261 | ||||
-rw-r--r-- | libelffile/elf/elf_builder.h | 231 | ||||
-rw-r--r-- | oatdump/oatdump.cc | 1 | ||||
-rw-r--r-- | runtime/oat/oat.h | 4 | ||||
-rw-r--r-- | runtime/oat/oat_file.cc | 46 | ||||
-rw-r--r-- | runtime/oat/oat_file_test.cc | 54 |
7 files changed, 555 insertions, 43 deletions
diff --git a/dex2oat/linker/elf_writer_quick.cc b/dex2oat/linker/elf_writer_quick.cc index 425f9bd554..a98e38026f 100644 --- a/dex2oat/linker/elf_writer_quick.cc +++ b/dex2oat/linker/elf_writer_quick.cc @@ -170,6 +170,7 @@ void ElfWriterQuick<ElfTypes>::Start() { builder_->GetBuildId()->AllocateVirtualMemory(builder_->GetBuildId()->GetSize()); builder_->WriteBuildIdSection(); } + builder_->ReserveSpaceForDynamicSection(elf_file_->GetPath()); } template <typename ElfTypes> diff --git a/dex2oat/linker/elf_writer_test.cc b/dex2oat/linker/elf_writer_test.cc index e1ef575502..e37e41d3b5 100644 --- a/dex2oat/linker/elf_writer_test.cc +++ b/dex2oat/linker/elf_writer_test.cc @@ -21,6 +21,7 @@ #include "base/unix_file/fd_file.h" #include "base/utils.h" #include "common_compiler_driver_test.h" +#include "driver/compiler_driver.h" #include "elf/elf_builder.h" #include "elf_writer_quick.h" #include "oat/elf_file.h" @@ -35,6 +36,50 @@ class ElfWriterTest : public CommonCompilerDriverTest { void SetUp() override { ReserveImageSpace(); CommonCompilerTest::SetUp(); + CreateCompilerDriver(); + } + + void WriteElf(File* oat_file, + const std::vector<uint8_t>& rodata, + const std::vector<uint8_t>& text, + const std::vector<uint8_t>& data_img_rel_ro, + size_t data_img_rel_ro_app_image_offset, + size_t bss_size, + size_t bss_methods_offset, + size_t bss_roots_offset, + size_t dex_section_size) { + std::unique_ptr<ElfWriter> elf_writer = CreateElfWriterQuick( + compiler_driver_->GetCompilerOptions(), + oat_file); + + elf_writer->Start(); + OutputStream* rodata_section = elf_writer->StartRoData(); + + elf_writer->PrepareDynamicSection(rodata.size(), + text.size(), + data_img_rel_ro.size(), + data_img_rel_ro_app_image_offset, + bss_size, + bss_methods_offset, + bss_roots_offset, + dex_section_size); + + ASSERT_TRUE(rodata_section->WriteFully(rodata.data(), rodata.size())); + elf_writer->EndRoData(rodata_section); + + OutputStream* text_section = elf_writer->StartText(); + ASSERT_TRUE(text_section->WriteFully(text.data(), text.size())); + elf_writer->EndText(text_section); + + if (!data_img_rel_ro.empty()) { + OutputStream* data_img_rel_ro_section = elf_writer->StartDataImgRelRo(); + ASSERT_TRUE(data_img_rel_ro_section->WriteFully(data_img_rel_ro.data(), + data_img_rel_ro.size())); + elf_writer->EndDataImgRelRo(data_img_rel_ro_section); + } + + elf_writer->WriteDynamicSection(); + ASSERT_TRUE(elf_writer->End()); } }; @@ -137,5 +182,221 @@ TEST_F(ElfWriterTest, CheckBuildIdPresent) { } } +// Check that dynamic sections (.dynamic, .dynsym, .dynstr, .hash) in an oat file are formed +// correctly so that dynamic symbols can be successfully looked up. +TEST_F(ElfWriterTest, CheckDynamicSection) { + // This function generates an oat file with the specified oat data sizes and offsets and + // verifies it: + // * Checks that the file can be loaded by the ELF loader. + // * Checks that the expected dynamic symbols exist and point to the corresponding data + // in the loaded file. + // * Checks the alignment of the oat data. + // The function returns the number of dynamic symbols (excluding "lastword" ones) in the + // generated oat file. + auto verify = [this](size_t rodata_size, + size_t text_size, + size_t data_img_rel_ro_size, + size_t data_img_rel_ro_app_image_offset, + size_t bss_size, + size_t bss_methods_offset, + size_t bss_roots_offset, + size_t dex_section_size, + /*out*/ size_t *number_of_dynamic_symbols) { + SCOPED_TRACE(testing::Message() << "rodata_size: " << rodata_size + << ", text_size: " << text_size + << ", data_img_rel_ro_size: " << data_img_rel_ro_size + << ", data_img_rel_ro_app_image_offset: " << data_img_rel_ro_app_image_offset + << ", bss_size: " << bss_size + << ", bss_methods_offset: " << bss_methods_offset + << ", bss_roots_offset: " << bss_roots_offset + << ", dex_section_size: " << dex_section_size); + + *number_of_dynamic_symbols = 1; // "oatdata". + std::vector<uint8_t> rodata(rodata_size, 0xAA); + std::vector<uint8_t> text(text_size, 0xBB); + std::vector<uint8_t> data_img_rel_ro(data_img_rel_ro_app_image_offset, 0xCC); + size_t data_img_rel_ro_app_image_size = data_img_rel_ro_size - data_img_rel_ro_app_image_offset; + data_img_rel_ro.insert(data_img_rel_ro.cend(), data_img_rel_ro_app_image_size, 0xDD); + + ScratchFile tmp_base, tmp_oat(tmp_base, ".oat"); + WriteElf(tmp_oat.GetFile(), + rodata, + text, + data_img_rel_ro, + data_img_rel_ro_app_image_offset, + bss_size, + bss_methods_offset, + bss_roots_offset, + dex_section_size); + + std::string error_msg; + std::unique_ptr<ElfFile> ef(ElfFile::Open(tmp_oat.GetFile(), + /*writable=*/ false, + /*program_header_only=*/ true, + /*low_4gb=*/ false, + &error_msg)); + ASSERT_NE(ef.get(), nullptr) << error_msg; + ASSERT_TRUE(ef->Load(tmp_oat.GetFile(), + /*executable=*/false, + /*low_4gb=*/false, + /*reservation=*/nullptr, + &error_msg)) << error_msg; + + const uint8_t* oatdata_ptr = ef->FindDynamicSymbolAddress("oatdata"); + ASSERT_NE(oatdata_ptr, nullptr); + EXPECT_EQ(memcmp(oatdata_ptr, rodata.data(), rodata.size()), 0); + + size_t page_size = GetPageSizeSlow(); + size_t elf_word_size = ef->Is64Bit() ? sizeof(ElfTypes64::Word) : sizeof(ElfTypes32::Word); + + if (text_size != 0u) { + *number_of_dynamic_symbols += 1; + const uint8_t* text_ptr = ef->FindDynamicSymbolAddress("oatexec"); + ASSERT_NE(text_ptr, nullptr); + ASSERT_TRUE(IsAlignedParam(text_ptr, page_size)); + EXPECT_EQ(memcmp(text_ptr, text.data(), text.size()), 0); + + const uint8_t* oatlastword_ptr = ef->FindDynamicSymbolAddress("oatlastword"); + ASSERT_NE(oatlastword_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatlastword_ptr - text_ptr), text_size - elf_word_size); + } else if (rodata_size != 0u) { + const uint8_t* oatlastword_ptr = ef->FindDynamicSymbolAddress("oatlastword"); + ASSERT_NE(oatlastword_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatlastword_ptr - oatdata_ptr), rodata_size - elf_word_size); + } + + if (data_img_rel_ro_size != 0u) { + *number_of_dynamic_symbols += 1; + const uint8_t* oatdataimgrelro_ptr = ef->FindDynamicSymbolAddress("oatdataimgrelro"); + ASSERT_NE(oatdataimgrelro_ptr, nullptr); + ASSERT_TRUE(IsAlignedParam(oatdataimgrelro_ptr, page_size)); + EXPECT_EQ(memcmp(oatdataimgrelro_ptr, data_img_rel_ro.data(), data_img_rel_ro.size()), 0); + + const uint8_t* oatdataimgrelrolastword_ptr = + ef->FindDynamicSymbolAddress("oatdataimgrelrolastword"); + ASSERT_NE(oatdataimgrelrolastword_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatdataimgrelrolastword_ptr - oatdataimgrelro_ptr), + data_img_rel_ro_size - elf_word_size); + + if (data_img_rel_ro_app_image_offset != data_img_rel_ro_size) { + *number_of_dynamic_symbols += 1; + const uint8_t* oatdataimgrelroappimage_ptr = + ef->FindDynamicSymbolAddress("oatdataimgrelroappimage"); + ASSERT_NE(oatdataimgrelroappimage_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatdataimgrelroappimage_ptr - oatdataimgrelro_ptr), + data_img_rel_ro_app_image_offset); + } + + if (bss_size != 0u) { + *number_of_dynamic_symbols += 1; + const uint8_t* bss_ptr = ef->FindDynamicSymbolAddress("oatbss"); + ASSERT_NE(bss_ptr, nullptr); + ASSERT_TRUE(IsAlignedParam(bss_ptr, page_size)); + + if (bss_methods_offset != bss_roots_offset) { + *number_of_dynamic_symbols += 1; + const uint8_t* oatbssmethods_ptr = ef->FindDynamicSymbolAddress("oatbssmethods"); + ASSERT_NE(oatbssmethods_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatbssmethods_ptr - bss_ptr), bss_methods_offset); + } + + if (bss_roots_offset != bss_size) { + *number_of_dynamic_symbols += 1; + const uint8_t* oatbssroots_ptr = ef->FindDynamicSymbolAddress("oatbssroots"); + ASSERT_NE(oatbssroots_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatbssroots_ptr - bss_ptr), bss_roots_offset); + } + + const uint8_t* oatbsslastword_ptr = ef->FindDynamicSymbolAddress("oatbsslastword"); + ASSERT_NE(oatbsslastword_ptr, nullptr); + EXPECT_EQ(static_cast<size_t>(oatbsslastword_ptr - bss_ptr), bss_size - elf_word_size); + } + } + + if (dex_section_size != 0u) { + *number_of_dynamic_symbols += 1; + const uint8_t* dex_ptr = ef->FindDynamicSymbolAddress("oatdex"); + ASSERT_NE(dex_ptr, nullptr); + ASSERT_TRUE(IsAlignedParam(dex_ptr, page_size)); + const uint8_t* oatdexlastword_ptr = ef->FindDynamicSymbolAddress("oatdexlastword"); + EXPECT_EQ(static_cast<size_t>(oatdexlastword_ptr - dex_ptr), + dex_section_size - elf_word_size); + } + }; + + // If a symbol requires some other ones (e.g. kBssMethods requires kBss), + // it should be listed after them. + enum class Symbol { + kRodata, + kText, + kDataImgRelRo, + kDataImgRelRoAppImage, + kBss, + kBssMethods, + kBssRoots, + kDex, + kLast = kDex + }; + + constexpr size_t kNumberOfSymbols = static_cast<size_t>(Symbol::kLast) + 1; + + // Use an unaligned section size to verify that ElfWriter properly aligns sections in this case. + // We can use an arbitrary value that is greater than or equal to an ElfWord (4 bytes). + constexpr size_t kSectionSize = 127u; + // Offset in .data.img.rel.ro section from its beginning. We can use any value in the range + // [0, kSectionSize). + constexpr size_t kDataImgRelRoAppImageOffset = kSectionSize / 2; + // Offsets in .bss from its beginning. We can use any value in the range [0, kSectionSize), + // kBssMethodsOffset should be less than or equal to kBssRootsOffset. + constexpr size_t kBssMethodsOffset = kSectionSize / 3; + constexpr size_t kBssRootsOffset = 2 * kBssMethodsOffset; + + auto exists = [](Symbol symbol, const std::bitset<kNumberOfSymbols> &symbols) { + return symbols.test(static_cast<size_t>(symbol)); + }; + + auto get_size = [&](Symbol symbol, const std::bitset<kNumberOfSymbols> &symbols) -> size_t { + return exists(symbol, symbols) ? kSectionSize : 0; + }; + + std::bitset<kNumberOfSymbols> symbols; + symbols.set(); + + // Check cases that lead to a different number of dynamic symbols in an oat file. + // We start with the case where all symbols exist (corresponding to the bitset 11111111) + // and continue to the case where only "oatdata" exists: + // 11111111 - all symbols exist. + // 01111111 - "oatdex" doesn't exist (least significant bit corresponds to "oatdata"). + // 00111111 - "oatdex" and "oatbss" don't exist. + // ... + // 00000001 - only "oatdata" exists. + while (symbols.any()) { + DCHECK_IMPLIES(exists(Symbol::kDataImgRelRoAppImage, symbols), + exists(Symbol::kDataImgRelRo, symbols)); + DCHECK_IMPLIES(exists(Symbol::kBssMethods, symbols), exists(Symbol::kBss, symbols)); + DCHECK_IMPLIES(exists(Symbol::kBssRoots, symbols), exists(Symbol::kBss, symbols)); + DCHECK_IMPLIES(exists(Symbol::kBssRoots, symbols), exists(Symbol::kBssMethods, symbols)); + + size_t data_img_rel_ro_size = get_size(Symbol::kDataImgRelRo, symbols); + size_t bss_size = get_size(Symbol::kBss, symbols); + size_t number_of_dynamic_symbols = 0; + verify(get_size(Symbol::kRodata, symbols), + get_size(Symbol::kText, symbols), + data_img_rel_ro_size, + exists(Symbol::kDataImgRelRoAppImage, symbols) + ? kDataImgRelRoAppImageOffset + : data_img_rel_ro_size, + bss_size, + exists(Symbol::kBssMethods, symbols) ? kBssMethodsOffset : bss_size, + exists(Symbol::kBssRoots, symbols) ? kBssRootsOffset : bss_size, + get_size(Symbol::kDex, symbols), + &number_of_dynamic_symbols); + EXPECT_EQ(number_of_dynamic_symbols, symbols.count()) + << "number_of_dynamic_symbols: " << number_of_dynamic_symbols + << ", symbols: " << symbols; + symbols >>= 1; + } +} + } // namespace linker } // namespace art diff --git a/libelffile/elf/elf_builder.h b/libelffile/elf/elf_builder.h index c93877ce32..ec226567d5 100644 --- a/libelffile/elf/elf_builder.h +++ b/libelffile/elf/elf_builder.h @@ -37,14 +37,14 @@ namespace art { // Elf_Ehdr - The ELF header. // Elf_Phdr[] - Program headers for the linker. // .note.gnu.build-id - Optional build ID section (SHA-1 digest). -// .rodata - Oat metadata. -// .text - Compiled code. -// .bss - Zero-initialized writeable section. -// .dex - Reserved NOBITS space for dex-related data. // .dynstr - Names for .dynsym. // .dynsym - A few oat-specific dynamic symbols. // .hash - Hash-table for .dynsym. // .dynamic - Tags which let the linker locate .dynsym. +// .rodata - Oat metadata. +// .text - Compiled code. +// .bss - Zero-initialized writeable section. +// .dex - Reserved NOBITS space for dex-related data. // .strtab - Names for .symtab. // .symtab - Debug symbols. // .debug_frame - Unwind information (CFI). @@ -57,9 +57,12 @@ namespace art { // // Some section are optional (the debug sections in particular). // -// We try write the section data directly into the file without much -// in-memory buffering. This means we generally write sections based on the -// dependency order (e.g. .dynamic points to .dynsym which points to .text). +// To reduce the amount of padding necessary to page-align sections with +// different permissions (and thus reduce disk usage), we group most read-only +// data sections together at the start of the file. This includes .dynstr, +// .dynsym, .hash, and .dynamic, whose contents are dependent on other sections. +// Therefore, when building the ELF we initially just reserve space for them, +// and write their contents later. // // In the cases where we need to buffer, we write the larger section first // and buffer the smaller one (e.g. .strtab is bigger than .symtab). @@ -467,10 +470,10 @@ class ElfBuilder final { kElfSegmentAlignment, 0), bss_(this, ".bss", SHT_NOBITS, SHF_ALLOC, nullptr, 0, kElfSegmentAlignment, 0), dex_(this, ".dex", SHT_NOBITS, SHF_ALLOC, nullptr, 0, kElfSegmentAlignment, 0), - dynstr_(this, ".dynstr", SHF_ALLOC, kElfSegmentAlignment), + dynstr_(this, ".dynstr", SHF_ALLOC, 1), dynsym_(this, ".dynsym", SHT_DYNSYM, SHF_ALLOC, &dynstr_), hash_(this, ".hash", SHT_HASH, SHF_ALLOC, &dynsym_, 0, sizeof(Elf_Word), sizeof(Elf_Word)), - dynamic_(this, ".dynamic", SHT_DYNAMIC, SHF_ALLOC, &dynstr_, 0, kElfSegmentAlignment, + dynamic_(this, ".dynamic", SHT_DYNAMIC, SHF_ALLOC, &dynstr_, 0, sizeof(Elf_Addr), sizeof(Elf_Dyn)), strtab_(this, ".strtab", 0, 1), symtab_(this, ".symtab", SHT_SYMTAB, 0, &strtab_), @@ -486,12 +489,14 @@ class ElfBuilder final { finished_(false), write_program_headers_(false), loaded_size_(0u), - virtual_address_(0) { + virtual_address_(0), + dynamic_sections_start_(0), + dynamic_sections_reserved_size_(0u) { text_.phdr_flags_ = PF_R | PF_X; data_img_rel_ro_.phdr_flags_ = PF_R | PF_W; // Shall be made read-only at run time. bss_.phdr_flags_ = PF_R | PF_W; dex_.phdr_flags_ = PF_R; - dynamic_.phdr_flags_ = PF_R | PF_W; + dynamic_.phdr_flags_ = PF_R; dynamic_.phdr_type_ = PT_DYNAMIC; build_id_.phdr_type_ = PT_NOTE; } @@ -631,6 +636,48 @@ class ElfBuilder final { return End(); } + // Reserve space for: .dynstr, .dynsym, .hash and .dynamic. + // + // Dynamic section content is dependent on subsequent sections. Here, reserve enough + // space for it. We will write the content later (in PrepareDynamicSection). + void ReserveSpaceForDynamicSection(const std::string& elf_file_path) { + CHECK_EQ(dynamic_sections_start_, 0); + CHECK_EQ(dynamic_sections_reserved_size_, 0u); + CHECK(!rodata_.Exists()); + + off_t offset = stream_.Seek(0, kSeekCurrent); + dynamic_sections_start_ = offset; + + dynstr_.AddSection(); + // We don't expect that .dynstr section can have any alignment requirements. + DCHECK_EQ(dynstr_.header_.sh_addralign, 1u); + offset += []() consteval { + size_t size = 0; + for (size_t i = 0; i < kDynamicSymbolCount; i++) { + DynamicSymbol sym = static_cast<DynamicSymbol>(i); + size += GetDynamicSymbolName(sym).length() + 1; + } + return size; + }(); + offset += GetSoname(elf_file_path).length() + 1; + + dynsym_.AddSection(); + offset = RoundUp(offset, dynsym_.header_.sh_addralign); + offset += kDynamicSymbolCount * sizeof(Elf_Sym); + + hash_.AddSection(); + offset = RoundUp(offset, hash_.header_.sh_addralign); + offset += PrepareDynamicSymbolHashtable(kDynamicSymbolCount, /*hashtable=*/ nullptr); + + dynamic_.AddSection(); + offset = RoundUp(offset, dynamic_.header_.sh_addralign); + offset += kDynamicEntriesCount * sizeof(Elf_Dyn); + + dynamic_sections_reserved_size_ = offset - dynamic_sections_start_; + + stream_.Seek(offset, kSeekSet); + } + // The running program does not have access to section headers // and the loader is not supposed to use them either. // The dynamic sections therefore replicates some of the layout @@ -646,13 +693,13 @@ class ElfBuilder final { Elf_Word bss_methods_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) { - soname = soname.substr(directory_separator_pos + 1); - } + CHECK_NE(dynamic_sections_reserved_size_, 0u); + + // Skip over the reserved memory for dynamic sections - we prepare them later + // due to dependencies. + Elf_Addr dynamic_sections_address = virtual_address_; + virtual_address_ += dynamic_sections_reserved_size_; - // Allocate all pre-dynamic sections. rodata_.AllocateVirtualMemory(rodata_size); text_.AllocateVirtualMemory(text_size); if (data_img_rel_ro_size != 0) { @@ -667,32 +714,33 @@ class ElfBuilder final { // Cache .dynstr, .dynsym and .hash data. dynstr_.Add(""); // dynstr should start with empty string. - Elf_Word oatdata = dynstr_.Add("oatdata"); + Elf_Word oatdata = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatData)); dynsym_.Add(oatdata, &rodata_, rodata_.GetAddress(), rodata_size, STB_GLOBAL, STT_OBJECT); if (text_size != 0u) { // The runtime does not care about the size of this symbol (it uses the "lastword" symbol). // We use size 0 (meaning "unknown size" in ELF) to prevent overlap with the debug symbols. - Elf_Word oatexec = dynstr_.Add("oatexec"); + Elf_Word oatexec = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatExec)); dynsym_.Add(oatexec, &text_, text_.GetAddress(), /* size= */ 0, STB_GLOBAL, STT_OBJECT); - Elf_Word oatlastword = dynstr_.Add("oatlastword"); + Elf_Word oatlastword = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatLastWord)); Elf_Word oatlastword_address = text_.GetAddress() + text_size - 4; dynsym_.Add(oatlastword, &text_, oatlastword_address, 4, STB_GLOBAL, STT_OBJECT); } else if (rodata_size != 0) { // rodata_ can be size 0 for dwarf_test. - Elf_Word oatlastword = dynstr_.Add("oatlastword"); + Elf_Word oatlastword = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatLastWord)); Elf_Word oatlastword_address = rodata_.GetAddress() + rodata_size - 4; dynsym_.Add(oatlastword, &rodata_, oatlastword_address, 4, STB_GLOBAL, STT_OBJECT); } DCHECK_LE(data_img_rel_ro_app_image_offset, data_img_rel_ro_size); if (data_img_rel_ro_size != 0u) { - Elf_Word oatdataimgrelro = dynstr_.Add("oatdataimgrelro"); + Elf_Word oatdataimgrelro = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatDataImgRelRo)); dynsym_.Add(oatdataimgrelro, &data_img_rel_ro_, data_img_rel_ro_.GetAddress(), data_img_rel_ro_size, STB_GLOBAL, STT_OBJECT); - Elf_Word oatdataimgrelrolastword = dynstr_.Add("oatdataimgrelrolastword"); + Elf_Word oatdataimgrelrolastword = + dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatDataImgRelRoLastWord)); dynsym_.Add(oatdataimgrelrolastword, &data_img_rel_ro_, data_img_rel_ro_.GetAddress() + data_img_rel_ro_size - 4, @@ -700,7 +748,8 @@ class ElfBuilder final { STB_GLOBAL, STT_OBJECT); if (data_img_rel_ro_app_image_offset != data_img_rel_ro_size) { - Elf_Word oatdataimgrelroappimage = dynstr_.Add("oatdataimgrelroappimage"); + Elf_Word oatdataimgrelroappimage = + dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatDataImgRelRoAppImage)); dynsym_.Add(oatdataimgrelroappimage, &data_img_rel_ro_, data_img_rel_ro_.GetAddress() + data_img_rel_ro_app_image_offset, @@ -711,7 +760,7 @@ class ElfBuilder final { } DCHECK_LE(bss_roots_offset, bss_size); if (bss_size != 0u) { - Elf_Word oatbss = dynstr_.Add("oatbss"); + Elf_Word oatbss = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatBss)); dynsym_.Add(oatbss, &bss_, bss_.GetAddress(), bss_roots_offset, STB_GLOBAL, STT_OBJECT); DCHECK_LE(bss_methods_offset, bss_roots_offset); DCHECK_LE(bss_roots_offset, bss_size); @@ -719,7 +768,7 @@ class ElfBuilder final { if (bss_methods_offset != bss_roots_offset) { Elf_Word bss_methods_address = bss_.GetAddress() + bss_methods_offset; Elf_Word bss_methods_size = bss_roots_offset - bss_methods_offset; - Elf_Word oatbssroots = dynstr_.Add("oatbssmethods"); + Elf_Word oatbssroots = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatBssMethods)); dynsym_.Add( oatbssroots, &bss_, bss_methods_address, bss_methods_size, STB_GLOBAL, STT_OBJECT); } @@ -727,41 +776,35 @@ class ElfBuilder final { if (bss_roots_offset != bss_size) { Elf_Word bss_roots_address = bss_.GetAddress() + bss_roots_offset; Elf_Word bss_roots_size = bss_size - bss_roots_offset; - Elf_Word oatbssroots = dynstr_.Add("oatbssroots"); + Elf_Word oatbssroots = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatBssRoots)); dynsym_.Add( oatbssroots, &bss_, bss_roots_address, bss_roots_size, STB_GLOBAL, STT_OBJECT); } - Elf_Word oatbsslastword = dynstr_.Add("oatbsslastword"); + Elf_Word oatbsslastword = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatBssLastWord)); 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"); + Elf_Word oatdex = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatDex)); dynsym_.Add(oatdex, &dex_, dex_.GetAddress(), /* size= */ 0, STB_GLOBAL, STT_OBJECT); - Elf_Word oatdexlastword = dynstr_.Add("oatdexlastword"); + Elf_Word oatdexlastword = dynstr_.Add(GetDynamicSymbolName(DynamicSymbol::kOatDexLastWord)); 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); + Elf_Word soname_offset = dynstr_.Add(GetSoname(elf_file_path)); // We do not really need a hash-table since there is so few entries. // However, the hash-table is the only way the linker can actually // determine the number of symbols in .dynsym so it is required. int count = dynsym_.GetCacheSize() / sizeof(Elf_Sym); // Includes NULL. std::vector<Elf_Word> hash; - hash.push_back(1); // Number of buckets. - hash.push_back(count); // Number of chains. - // Buckets. Having just one makes it linear search. - hash.push_back(1); // Point to first non-NULL symbol. - // Chains. This creates linked list of symbols. - hash.push_back(0); // Placeholder entry for the NULL symbol. - for (int i = 1; i < count - 1; i++) { - hash.push_back(i + 1); // Each symbol points to the next one. - } - hash.push_back(0); // Last symbol terminates the chain. + PrepareDynamicSymbolHashtable(count, &hash); hash_.Add(hash.data(), hash.size() * sizeof(hash[0])); + Elf_Addr current_virtual_address = virtual_address_; + virtual_address_ = dynamic_sections_address; + // Allocate all remaining sections. dynstr_.AllocateVirtualMemory(dynstr_.GetCacheSize()); dynsym_.AllocateVirtualMemory(dynsym_.GetCacheSize()); @@ -776,17 +819,32 @@ class ElfBuilder final { { .d_tag = DT_SONAME, .d_un = { .d_ptr = soname_offset }, }, { .d_tag = DT_NULL, .d_un = { .d_ptr = 0 }, }, }; + static_assert(sizeof(dyns) == kDynamicEntriesCount * sizeof(dyns[0])); + dynamic_.Add(&dyns, sizeof(dyns)); dynamic_.AllocateVirtualMemory(dynamic_.GetCacheSize()); + CHECK_LE(virtual_address_, rodata_.GetAddress()); + virtual_address_ = current_virtual_address; + loaded_size_ = RoundUp(virtual_address_, kElfSegmentAlignment); } void WriteDynamicSection() { + CHECK_NE(dynamic_sections_start_, 0); + CHECK_NE(dynamic_sections_reserved_size_, 0u); + + off_t current_offset = stream_.Seek(0, kSeekCurrent); + stream_.Seek(dynamic_sections_start_, kSeekSet); + dynstr_.WriteCachedSection(); dynsym_.WriteCachedSection(); hash_.WriteCachedSection(); dynamic_.WriteCachedSection(); + + DCHECK_LE(stream_.Seek(0, kSeekCurrent), + static_cast<off_t>(dynamic_sections_start_ + dynamic_sections_reserved_size_)); + stream_.Seek(current_offset, kSeekSet); } Elf_Word GetLoadedSize() { @@ -977,6 +1035,91 @@ class ElfBuilder final { return phdrs; } + enum class DynamicSymbol { + kNull, + kOatData, + kOatExec, + kOatLastWord, + kOatDataImgRelRo, + kOatDataImgRelRoLastWord, + kOatDataImgRelRoAppImage, + kOatBss, + kOatBssMethods, + kOatBssRoots, + kOatBssLastWord, + kOatDex, + kOatDexLastWord, + kLast = kOatDexLastWord + }; + + static constexpr size_t kDynamicSymbolCount = static_cast<size_t>(DynamicSymbol::kLast) + 1; + static constexpr size_t kDynamicEntriesCount = 7; + + static constexpr std::string GetDynamicSymbolName(DynamicSymbol sym) { + switch (sym) { + case DynamicSymbol::kNull: + return ""; + case DynamicSymbol::kOatData: + return "oatdata"; + case DynamicSymbol::kOatExec: + return "oatexec"; + case DynamicSymbol::kOatLastWord: + return "oatlastword"; + case DynamicSymbol::kOatDataImgRelRo: + return "oatdataimgrelro"; + case DynamicSymbol::kOatDataImgRelRoLastWord: + return "oatdataimgrelrolastword"; + case DynamicSymbol::kOatDataImgRelRoAppImage: + return "oatdataimgrelroappimage"; + case DynamicSymbol::kOatBss: + return "oatbss"; + case DynamicSymbol::kOatBssMethods: + return "oatbssmethods"; + case DynamicSymbol::kOatBssRoots: + return "oatbssroots"; + case DynamicSymbol::kOatBssLastWord: + return "oatbsslastword"; + case DynamicSymbol::kOatDex: + return "oatdex"; + case DynamicSymbol::kOatDexLastWord: + return "oatdexlastword"; + } + } + + // This method builds a hashtable for dynamic symbols using `hashtable` as a storage. + // If `hashtable` is nullptr, it just calculate its size in bytes and returns it. + static size_t PrepareDynamicSymbolHashtable(size_t count, std::vector<Elf_Word> *hashtable) { + size_t size = 0; + auto write = [&size, hashtable](Elf_Word value) { + if (hashtable) { + hashtable->push_back(value); + } + size += sizeof(value); + }; + + write(1); // Number of buckets. + write(count); // Number of chains. + // Buckets. Having just one makes it linear search. + write(1); // Point to first non-NULL symbol. + // Chains. This creates linked list of symbols. + write(0); // Placeholder entry for the NULL symbol. + for (size_t i = 1; i < count - 1; i++) { + write(i + 1); // Each symbol points to the next one. + } + write(0); // Last symbol terminates the chain. + + return size; + } + + static std::string GetSoname(const std::string& elf_file_path) { + std::string soname(elf_file_path); + size_t directory_separator_pos = soname.rfind('/'); + if (directory_separator_pos != std::string::npos) { + soname = soname.substr(directory_separator_pos + 1); + } + return soname; + } + InstructionSet isa_; ErrorDelayingOutputStream stream_; @@ -1014,6 +1157,12 @@ class ElfBuilder final { // Used for allocation of virtual address space. Elf_Addr virtual_address_; + // Offset in the ELF where the first dynamic section is written (.dynstr). + off_t dynamic_sections_start_; + + // Size reserved for dynamic sections: .dynstr, .dynsym, .hash and .dynamic. + size_t dynamic_sections_reserved_size_; + DISALLOW_COPY_AND_ASSIGN(ElfBuilder); }; diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 1b0a3b552b..ab22e36c17 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -157,6 +157,7 @@ class OatSymbolizer final { builder_.reset(new ElfBuilder<ElfTypes>(isa, output_stream.get())); builder_->Start(); + builder_->ReserveSpaceForDynamicSection(elf_file->GetPath()); auto* rodata = builder_->GetRoData(); auto* text = builder_->GetText(); diff --git a/runtime/oat/oat.h b/runtime/oat/oat.h index 8ee3ad235e..2a8607844b 100644 --- a/runtime/oat/oat.h +++ b/runtime/oat/oat.h @@ -44,8 +44,8 @@ std::ostream& operator<<(std::ostream& stream, StubType stub_type); class EXPORT PACKED(4) OatHeader { public: static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } }; - // Last oat version changed reason: reduce alignment for .rodata section in OAT files. - static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '5', '5', '\0'}}; + // Last oat version changed reason: move dynamic sections to start of OAT file. + static constexpr std::array<uint8_t, 4> kOatVersion{{'2', '5', '6', '\0'}}; static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline"; static constexpr const char* kDebuggableKey = "debuggable"; diff --git a/runtime/oat/oat_file.cc b/runtime/oat/oat_file.cc index 3e298e5590..3308137481 100644 --- a/runtime/oat/oat_file.cc +++ b/runtime/oat/oat_file.cc @@ -67,6 +67,21 @@ #include <link.h> // for dl_iterate_phdr. #endif +#ifdef __GLIBC__ +#include <gnu/libc-version.h> // for gnu_get_libc_version. +// strverscmp is part of the GNU/Linux extension, so define _GNU_SOURCE before including +// string.h, and undefine it afterward if it is not already defined. +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#define DEFINED_GNU_SOURCE +#endif +#include <string.h> // for strverscmp +#ifdef DEFINED_GNU_SOURCE +#undef _GNU_SOURCE +#undef DEFINED_GNU_SOURCE +#endif +#endif + // dlopen_ext support from bionic. #ifdef ART_TARGET_ANDROID #include "android/dlext.h" @@ -90,6 +105,32 @@ static constexpr bool kUseDlopenOnHost = true; // For debugging, Open will print DlOpen error message if set to true. static constexpr bool kPrintDlOpenErrorMessage = false; +// Returns whether dlopen can load dynamic shared objects with a read-only .dynamic section. +// According to the ELF spec whether .dynamic is writable or not is determined by the operating +// system and processor (Book I, part 1 "Object Files", "Special sections"). Bionic and glibc +// >= 2.35 support read-only .dynamic. Older glibc versions have a bug that causes a crash if +// this section is read-only: https://sourceware.org/bugzilla/show_bug.cgi?id=28340. +bool IsReadOnlyDynamicSupportedByDlOpen() { + // The following lambda will be executed only once as a part of a static + // variable initialization. +#ifdef __GLIBC__ + static bool is_ro_dynamic_supported = []() { + // libc version has the following format: + // "X.Y" + // where: + // X - major version in the decimal format. + // Y - minor version in the decimal format. + // for example: + // "2.34" + const char* libc_version = gnu_get_libc_version(); + return strverscmp(libc_version, "2.35") >= 0; + }(); + return is_ro_dynamic_supported; +#else + return true; +#endif +} + // Note for OatFileBase and descendents: // // These are used in OatFile::Open to try all our loaders. @@ -1261,6 +1302,11 @@ bool DlOpenOatFile::Load(const std::string& elf_filename, return false; } + if (!IsReadOnlyDynamicSupportedByDlOpen()) { + *error_msg = "DlOpen does not support read-only .dynamic section."; + return false; + } + // dlopen always returns the same library if it is already opened on the host. For this reason // we only use dlopen if we are the target or we do not already have the dex file opened. Having // the same library loaded multiple times at different addresses is required for class unloading diff --git a/runtime/oat/oat_file_test.cc b/runtime/oat/oat_file_test.cc index 12a26f1cc1..f79c4cac26 100644 --- a/runtime/oat/oat_file_test.cc +++ b/runtime/oat/oat_file_test.cc @@ -16,6 +16,8 @@ #include "oat_file.h" +#include <dlfcn.h> + #include <string> #include "common_runtime_test.h" @@ -93,4 +95,56 @@ TEST_F(OatFileTest, ChangingMultiDexUncompressed) { << error_msg; } +TEST_F(OatFileTest, DlOpenLoad) { + 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; + + // Clear previous errors if any. + dlerror(); + error_msg.clear(); + std::unique_ptr<OatFile> odex_file(OatFile::Open(/*zip_fd=*/-1, + oat_location, + oat_location, + /*executable=*/true, + /*low_4gb=*/false, + dex_location, + &error_msg)); + ASSERT_NE(odex_file.get(), nullptr) << error_msg; + +#ifdef __GLIBC__ + if (!error_msg.empty()) { + // If a valid oat file was returned but there was an error message, then dlopen failed + // but the backup ART ELF loader successfully loaded the oat file. + // The only expected reason for this is a bug in glibc that prevents loading dynamic + // shared objects with a read-only dynamic section: + // https://sourceware.org/bugzilla/show_bug.cgi?id=28340. + ASSERT_TRUE(error_msg == "DlOpen does not support read-only .dynamic section.") << error_msg; + GTEST_SKIP() << error_msg; + } +#else + // If a valid oat file was returned with no error message, then dlopen was successful. + ASSERT_TRUE(error_msg.empty()) << error_msg; +#endif + + const char *dlerror_msg = dlerror(); + ASSERT_EQ(dlerror_msg, nullptr) << dlerror_msg; + + // Ensure that the oat file is loaded with dlopen by requesting information about it + // using dladdr. + Dl_info info; + ASSERT_NE(dladdr(odex_file->Begin(), &info), 0); + EXPECT_STREQ(info.dli_fname, oat_location.c_str()) + << "dli_fname: " << info.dli_fname + << ", location: " << oat_location; + EXPECT_STREQ(info.dli_sname, "oatdata") << info.dli_sname; +} + } // namespace art |