Skip to content
This repository has been archived by the owner on Nov 8, 2023. It is now read-only.

Commit

Permalink
androidfw: Add support for 16-bit entry offsets
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Eric Miao committed Nov 15, 2022
1 parent 368cd19 commit a1f2bce
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 29 deletions.
61 changes: 41 additions & 20 deletions libs/androidfw/LoadedArsc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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) {
Expand Down
19 changes: 16 additions & 3 deletions libs/androidfw/ResourceTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
}
Expand Down
7 changes: 6 additions & 1 deletion libs/androidfw/TypeWrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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]);
}
Expand Down
9 changes: 9 additions & 0 deletions libs/androidfw/include/androidfw/ResourceTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
Expand Down
23 changes: 18 additions & 5 deletions tools/aapt2/format/binary/TableFlattener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "format/binary/TableFlattener.h"

#include <limits>
#include <sstream>
#include <type_traits>
#include <variant>
Expand Down Expand Up @@ -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;

Expand All @@ -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.
Expand All @@ -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();
Expand Down

0 comments on commit a1f2bce

Please sign in to comment.