summaryrefslogtreecommitdiff
path: root/dex2oat
diff options
context:
space:
mode:
author Konstantin Baladurin <konstantin.baladurin@arm.com> 2025-01-10 14:07:53 +0000
committer Steven Moreland <smoreland@google.com> 2025-01-30 10:55:25 -0800
commit7e3cf2bac4db55a735a3f35b2e6e0e7b4b49210e (patch)
tree9d41619560190b5f8bde4ff9f8b57f43b0b5cf61 /dex2oat
parent55cb961ae83563eed14fb3aecc7debafc9c86192 (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
Diffstat (limited to 'dex2oat')
-rw-r--r--dex2oat/linker/elf_writer_quick.cc1
-rw-r--r--dex2oat/linker/elf_writer_test.cc261
2 files changed, 262 insertions, 0 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