diff options
author | 2022-09-12 11:37:37 -0700 | |
---|---|---|
committer | 2022-11-15 12:15:38 -0800 | |
commit | a1f2bce0e56185d8a3ddbbb75cf9daacdcb5a3d2 (patch) | |
tree | 3df57d49a29da88522a45388cb9aaecabada23bd | |
parent | 368cd19d031f5a0b219ea92531d11ccb0ee66c4d (diff) |
androidfw: Add support for 16-bit entry offsets
Bug: 237583012
Most offsets to the entries can be well encoded in 16-bit,
and given entries are 4-byte aligned, this gives us a range
of entry offsets from 0x00000 to 0xfffe * 4u, with 0xffffu
to represent ResTable_type::NO_ENTRY.
For now, 16-bit entry offset will be enabled only when:
* all the entry offsets can be represented in 16-bit
* --enable-compact-entries switch is turned on
Change-Id: I1c815c052aa5fba6eab2529434d31d7714c13694
-rw-r--r-- | libs/androidfw/LoadedArsc.cpp | 61 | ||||
-rw-r--r-- | libs/androidfw/ResourceTypes.cpp | 19 | ||||
-rw-r--r-- | libs/androidfw/TypeWrappers.cpp | 7 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/ResourceTypes.h | 9 | ||||
-rw-r--r-- | tools/aapt2/format/binary/TableFlattener.cpp | 23 |
5 files changed, 90 insertions, 29 deletions
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index e78f91ee3f46..386f718208b3 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -88,7 +88,9 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) { // Make sure that there is enough room for the entry offsets. const size_t offsets_offset = dtohs(header->header.headerSize); const size_t entries_offset = dtohl(header->entriesStart); - const size_t offsets_length = sizeof(uint32_t) * entry_count; + const size_t offsets_length = header->flags & ResTable_type::FLAG_OFFSET16 + ? sizeof(uint16_t) * entry_count + : sizeof(uint32_t) * entry_count; if (offsets_offset > entries_offset || entries_offset - offsets_offset < offsets_length) { LOG(ERROR) << "RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data."; @@ -247,14 +249,13 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset( // The configuration matches and is better than the previous selection. // Find the entry value if it exists for this configuration. const size_t entry_count = dtohl(type_chunk->entryCount); - const size_t offsets_offset = dtohs(type_chunk->header.headerSize); + const auto offsets = type_chunk.offset(dtohs(type_chunk->header.headerSize)); // Check if there is the desired entry in this type. if (type_chunk->flags & ResTable_type::FLAG_SPARSE) { // This is encoded as a sparse map, so perform a binary search. bool error = false; - auto sparse_indices = type_chunk.offset(offsets_offset) - .convert<ResTable_sparseTypeEntry>().iterator(); + auto sparse_indices = offsets.convert<ResTable_sparseTypeEntry>().iterator(); auto sparse_indices_end = sparse_indices + entry_count; auto result = std::lower_bound(sparse_indices, sparse_indices_end, entry_index, [&error](const incfs::map_ptr<ResTable_sparseTypeEntry>& entry, @@ -289,17 +290,26 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset( return base::unexpected(std::nullopt); } - const auto entry_offset_ptr = type_chunk.offset(offsets_offset).convert<uint32_t>() + entry_index; - if (UNLIKELY(!entry_offset_ptr)) { - return base::unexpected(IOError::PAGES_MISSING); + uint32_t result; + + if (type_chunk->flags & ResTable_type::FLAG_OFFSET16) { + const auto entry_offset_ptr = offsets.convert<uint16_t>() + entry_index; + if (UNLIKELY(!entry_offset_ptr)) { + return base::unexpected(IOError::PAGES_MISSING); + } + result = offset_from16(entry_offset_ptr.value()); + } else { + const auto entry_offset_ptr = offsets.convert<uint32_t>() + entry_index; + if (UNLIKELY(!entry_offset_ptr)) { + return base::unexpected(IOError::PAGES_MISSING); + } + result = dtohl(entry_offset_ptr.value()); } - const uint32_t value = dtohl(entry_offset_ptr.value()); - if (value == ResTable_type::NO_ENTRY) { + if (result == ResTable_type::NO_ENTRY) { return base::unexpected(std::nullopt); } - - return value; + return result; } base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> @@ -382,24 +392,35 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName( for (const auto& type_entry : type_spec->type_entries) { const incfs::verified_map_ptr<ResTable_type>& type = type_entry.type; - size_t entry_count = dtohl(type->entryCount); - for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { - auto entry_offset_ptr = type.offset(dtohs(type->header.headerSize)).convert<uint32_t>() + - entry_idx; - if (!entry_offset_ptr) { - return base::unexpected(IOError::PAGES_MISSING); - } + const size_t entry_count = dtohl(type->entryCount); + const auto entry_offsets = type.offset(dtohs(type->header.headerSize)); + for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) { uint32_t offset; uint16_t res_idx; if (type->flags & ResTable_type::FLAG_SPARSE) { - auto sparse_entry = entry_offset_ptr.convert<ResTable_sparseTypeEntry>(); + auto sparse_entry = entry_offsets.convert<ResTable_sparseTypeEntry>() + entry_idx; + if (!sparse_entry) { + return base::unexpected(IOError::PAGES_MISSING); + } offset = dtohs(sparse_entry->offset) * 4u; res_idx = dtohs(sparse_entry->idx); + } else if (type->flags & ResTable_type::FLAG_OFFSET16) { + auto entry = entry_offsets.convert<uint16_t>() + entry_idx; + if (!entry) { + return base::unexpected(IOError::PAGES_MISSING); + } + offset = offset_from16(entry.value()); + res_idx = entry_idx; } else { - offset = dtohl(entry_offset_ptr.value()); + auto entry = entry_offsets.convert<uint32_t>() + entry_idx; + if (!entry) { + return base::unexpected(IOError::PAGES_MISSING); + } + offset = dtohl(entry.value()); res_idx = entry_idx; } + if (offset != ResTable_type::NO_ENTRY) { auto entry = type.offset(dtohl(type->entriesStart) + offset).convert<ResTable_entry>(); if (!entry) { diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index aac52b46f909..ba9354607c8e 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -6521,8 +6521,12 @@ status_t ResTable::getEntry( // Entry does not exist. continue; } - - thisOffset = dtohl(eindex[realEntryIndex]); + if (thisType->flags & ResTable_type::FLAG_OFFSET16) { + auto eindex16 = reinterpret_cast<const uint16_t*>(eindex); + thisOffset = offset_from16(eindex16[realEntryIndex]); + } else { + thisOffset = dtohl(eindex[realEntryIndex]); + } } if (thisOffset == ResTable_type::NO_ENTRY) { @@ -7574,6 +7578,9 @@ void ResTable::print(bool inclValues) const if (type->flags & ResTable_type::FLAG_SPARSE) { printf(" [sparse]"); } + if (type->flags & ResTable_type::FLAG_OFFSET16) { + printf(" [offset16]"); + } } printf(":\n"); @@ -7605,7 +7612,13 @@ void ResTable::print(bool inclValues) const thisOffset = static_cast<uint32_t>(dtohs(entry->offset)) * 4u; } else { entryId = entryIndex; - thisOffset = dtohl(eindex[entryIndex]); + if (type->flags & ResTable_type::FLAG_OFFSET16) { + const auto eindex16 = + reinterpret_cast<const uint16_t*>(eindex); + thisOffset = offset_from16(eindex16[entryIndex]); + } else { + thisOffset = dtohl(eindex[entryIndex]); + } if (thisOffset == ResTable_type::NO_ENTRY) { continue; } diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp index 2c39a81aca3e..70d14a11830e 100644 --- a/libs/androidfw/TypeWrappers.cpp +++ b/libs/androidfw/TypeWrappers.cpp @@ -59,7 +59,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { + dtohl(type->header.size); const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>( reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize)); - if (reinterpret_cast<uintptr_t>(entryIndices) + (sizeof(uint32_t) * entryCount) > containerEnd) { + const size_t indexSize = type->flags & ResTable_type::FLAG_OFFSET16 ? + sizeof(uint16_t) : sizeof(uint32_t); + if (reinterpret_cast<uintptr_t>(entryIndices) + (indexSize * entryCount) > containerEnd) { ALOGE("Type's entry indices extend beyond its boundaries"); return NULL; } @@ -73,6 +75,9 @@ const ResTable_entry* TypeVariant::iterator::operator*() const { } entryOffset = static_cast<uint32_t>(dtohs(ResTable_sparseTypeEntry{*iter}.offset)) * 4u; + } else if (type->flags & ResTable_type::FLAG_OFFSET16) { + auto entryIndices16 = reinterpret_cast<const uint16_t*>(entryIndices); + entryOffset = offset_from16(entryIndices16[mIndex]); } else { entryOffset = dtohl(entryIndices[mIndex]); } diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index 42d8cbeb8dfd..d588b23504f0 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -1448,6 +1448,10 @@ struct ResTable_type // Mark any types that use this with a v26 qualifier to prevent runtime issues on older // platforms. FLAG_SPARSE = 0x01, + + // If set, the offsets to the entries are encoded in 16-bit, real_offset = offset * 4u + // An 16-bit offset of 0xffffu means a NO_ENTRY + FLAG_OFFSET16 = 0x02, }; uint8_t flags; @@ -1464,6 +1468,11 @@ struct ResTable_type ResTable_config config; }; +// Convert a 16-bit offset to 32-bit if FLAG_OFFSET16 is set +static inline uint32_t offset_from16(uint16_t off16) { + return dtohs(off16) == 0xffffu ? ResTable_type::NO_ENTRY : dtohs(off16) * 4u; +} + // The minimum size required to read any version of ResTable_type. constexpr size_t kResTableTypeMinSize = sizeof(ResTable_type) - sizeof(ResTable_config) + sizeof(ResTable_config::size); diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index c431730b5410..f19223411232 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -16,6 +16,7 @@ #include "format/binary/TableFlattener.h" +#include <limits> #include <sstream> #include <type_traits> #include <variant> @@ -191,6 +192,9 @@ class PackageFlattener { offsets[flat_entry.entry->id.value()] = res_entry_writer->Write(&flat_entry); } + // whether the offsets can be represented in 2 bytes + bool short_offsets = (values_buffer.size() / 4u) < std::numeric_limits<uint16_t>::max(); + bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled || sparse_entries_ == SparseEntriesMode::Forced; @@ -203,8 +207,7 @@ class PackageFlattener { } // Only sparse encode if the offsets are representable in 2 bytes. - sparse_encode = - sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max(); + sparse_encode = sparse_encode && short_offsets; // Only sparse encode if the ratio of populated entries to total entries is below some // threshold. @@ -226,12 +229,22 @@ class PackageFlattener { } } else { type_header->entryCount = android::util::HostToDevice32(num_total_entries); - uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries); - for (size_t i = 0; i < num_total_entries; i++) { - indices[i] = android::util::HostToDevice32(offsets[i]); + if (compact_entry && short_offsets) { + // use 16-bit offset only when compact_entry is true + type_header->flags |= ResTable_type::FLAG_OFFSET16; + uint16_t* indices = type_writer.NextBlock<uint16_t>(num_total_entries); + for (size_t i = 0; i < num_total_entries; i++) { + indices[i] = android::util::HostToDevice16(offsets[i] / 4u); + } + } else { + uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries); + for (size_t i = 0; i < num_total_entries; i++) { + indices[i] = android::util::HostToDevice32(offsets[i]); + } } } + type_writer.buffer()->Align4(); type_header->entriesStart = android::util::HostToDevice32(type_writer.size()); type_writer.buffer()->AppendBuffer(std::move(values_buffer)); type_writer.Finish(); |