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 compact resource entries
Browse files Browse the repository at this point in the history
Bug: 237583012

Given the large number of simple resources such as strings in
Android resources, their ResTable_entry and Res_value can be
encoded together in a compact way.  This allows a significant
saving in both storage and memory footprint.

The basic observations for simple resources are:

* ResTable_entry.size will always be sizeof(ResTable_entry)
  unless it's a complex entry

* ResTable_entry.key is unlikely to exceed 16-bit

* ResTable_entry.flags only uses 3 bits for now

* Res_value.size will always be sizeof(Res_value)

Given the above, we could well encode the information into
a compact/compatible structure.

  struct compact {
      uint16_t key;
      uint16_t flags;
      uint32_t data;
  };

The layout of this structure will allow maximum backward
compatibility. e.g. the flags will be at the same offset,
and a

  `dtohs((ResTable_entry *)entry->flags) & FLAG_COMPACT`

would tell if this entry is a compact one or not. For a
compact entry:

  struct compact *entry;

  entry_size  == sizeof(*entry)
  entry_key   == static_cast<uint32_t>(dtohs(entry->key))
  entry_flags == dtohs(entry->flags) & 0xff	// low 8-bit
  data_type   == dtohs(entry->flags) >> 8	// high 8-bit
  data_size   == sizeof(Res_value)
  data_value  == dtohl(entry->data)

To allow minimum code change and backward compatibility,
we change 'struct ResTable_entry' to 'union ResTable_entry',
with an anonymous structure inside that's fully backward
compatible. Thus, any existing reference such as:

  ResTable_entry *entry = ...
  if (dtohs(entry->flags) & ResTable_entry::FLAG_COMPLEX) ...

would still work.

However, special care needs to be taken after an entry is
obtained, and when value needs to be extracted.

A compact entry will not encode a complex value, and hence
complex entries/values are handled the same way.

Change-Id: I15d97a4f5e85fab28c075496f7f0cf6b1fcd73e3
  • Loading branch information
Eric Miao committed Nov 15, 2022
1 parent b0d0974 commit 368cd19
Show file tree
Hide file tree
Showing 16 changed files with 407 additions and 241 deletions.
38 changes: 12 additions & 26 deletions libs/androidfw/AssetManager2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,28 +43,19 @@ namespace {

using EntryValue = std::variant<Res_value, incfs::verified_map_ptr<ResTable_map_entry>>;

/* NOTE: table_entry has been verified in LoadedPackage::GetEntryFromOffset(),
* and so access to ->value() and ->map_entry() are safe here
*/
base::expected<EntryValue, IOError> GetEntryValue(
incfs::verified_map_ptr<ResTable_entry> table_entry) {
const uint16_t entry_size = dtohs(table_entry->size);
const uint16_t entry_size = table_entry->size();

// Check if the entry represents a bag value.
if (entry_size >= sizeof(ResTable_map_entry) &&
(dtohs(table_entry->flags) & ResTable_entry::FLAG_COMPLEX)) {
const auto map_entry = table_entry.convert<ResTable_map_entry>();
if (!map_entry) {
return base::unexpected(IOError::PAGES_MISSING);
}
return map_entry.verified();
if (entry_size >= sizeof(ResTable_map_entry) && table_entry->is_complex()) {
return table_entry.convert<ResTable_map_entry>().verified();
}

// The entry represents a non-bag value.
const auto entry_value = table_entry.offset(entry_size).convert<Res_value>();
if (!entry_value) {
return base::unexpected(IOError::PAGES_MISSING);
}
Res_value value;
value.copyFrom_dtoh(entry_value.value());
return value;
return table_entry->value();
}

} // namespace
Expand Down Expand Up @@ -814,17 +805,12 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
return base::unexpected(std::nullopt);
}

auto best_entry_result = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
if (!best_entry_result.has_value()) {
return base::unexpected(best_entry_result.error());
}

const incfs::map_ptr<ResTable_entry> best_entry = *best_entry_result;
if (!best_entry) {
return base::unexpected(IOError::PAGES_MISSING);
auto best_entry_verified = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
if (!best_entry_verified.has_value()) {
return base::unexpected(best_entry_verified.error());
}

const auto entry = GetEntryValue(best_entry.verified());
const auto entry = GetEntryValue(*best_entry_verified);
if (!entry.has_value()) {
return base::unexpected(entry.error());
}
Expand All @@ -837,7 +823,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
.entry_string_ref = StringPoolRef(best_package->GetKeyStringPool(),
best_entry->key.index),
(*best_entry_verified)->key()),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
};
}
Expand Down
24 changes: 15 additions & 9 deletions libs/androidfw/LoadedArsc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ static bool VerifyResTableType(incfs::map_ptr<ResTable_type> header) {
return true;
}

static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
VerifyResTableEntry(incfs::verified_map_ptr<ResTable_type> type, uint32_t entry_offset) {
// Check that the offset is aligned.
if (UNLIKELY(entry_offset & 0x03U)) {
LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
Expand Down Expand Up @@ -136,7 +136,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(IOError::PAGES_MISSING);
}

const size_t entry_size = dtohs(entry->size);
const size_t entry_size = entry->size();
if (UNLIKELY(entry_size < sizeof(entry.value()))) {
LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
<< " is too small.";
Expand All @@ -149,6 +149,11 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(std::nullopt);
}

// If entry is compact, value is already encoded, and a compact entry
// cannot be a map_entry, we are done verifying
if (entry->is_compact())
return entry.verified();

if (entry_size < sizeof(ResTable_map_entry)) {
// There needs to be room for one Res_value struct.
if (UNLIKELY(entry_offset + entry_size > chunk_size - sizeof(Res_value))) {
Expand Down Expand Up @@ -192,7 +197,7 @@ static base::expected<std::monostate, NullOrIOError> VerifyResTableEntry(
return base::unexpected(std::nullopt);
}
}
return {};
return entry.verified();
}

LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei)
Expand Down Expand Up @@ -228,7 +233,7 @@ uint32_t LoadedPackage::iterator::operator*() const {
entryIndex_);
}

base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index) {
base::expected<uint32_t, NullOrIOError> entry_offset = GetEntryOffset(type_chunk, entry_index);
if (UNLIKELY(!entry_offset.has_value())) {
Expand Down Expand Up @@ -297,13 +302,14 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::GetEntryOffset(
return value;
}

base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> LoadedPackage::GetEntryFromOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset) {
base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
LoadedPackage::GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk,
uint32_t offset) {
auto valid = VerifyResTableEntry(type_chunk, offset);
if (UNLIKELY(!valid.has_value())) {
return base::unexpected(valid.error());
}
return type_chunk.offset(offset + dtohl(type_chunk->entriesStart)).convert<ResTable_entry>();
return valid;
}

base::expected<std::monostate, IOError> LoadedPackage::CollectConfigurations(
Expand Down Expand Up @@ -400,7 +406,7 @@ base::expected<uint32_t, NullOrIOError> LoadedPackage::FindEntryByName(
return base::unexpected(IOError::PAGES_MISSING);
}

if (dtohl(entry->key.index) == static_cast<uint32_t>(*key_idx)) {
if (entry->key() == static_cast<uint32_t>(*key_idx)) {
// The package ID will be overridden by the caller (due to runtime assignment of package
// IDs for shared libraries).
return make_resid(0x00, *type_idx + type_id_offset_ + 1, res_idx);
Expand Down
46 changes: 18 additions & 28 deletions libs/androidfw/ResourceTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4415,20 +4415,14 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag
return err;
}

if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
if (entry.entry->map_entry()) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}

const Res_value* value = reinterpret_cast<const Res_value*>(
reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);

outValue->size = dtohs(value->size);
outValue->res0 = value->res0;
outValue->dataType = value->dataType;
outValue->data = dtohl(value->data);
*outValue = entry.entry->value();

// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
Expand Down Expand Up @@ -4619,11 +4613,10 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
return err;
}

const uint16_t entrySize = dtohs(entry.entry->size);
const uint32_t parent = entrySize >= sizeof(ResTable_map_entry)
? dtohl(((const ResTable_map_entry*)entry.entry)->parent.ident) : 0;
const uint32_t count = entrySize >= sizeof(ResTable_map_entry)
? dtohl(((const ResTable_map_entry*)entry.entry)->count) : 0;
const uint16_t entrySize = entry.entry->size();
const ResTable_map_entry* map_entry = entry.entry->map_entry();
const uint32_t parent = map_entry ? dtohl(map_entry->parent.ident) : 0;
const uint32_t count = map_entry ? dtohl(map_entry->count) : 0;

size_t N = count;

Expand Down Expand Up @@ -4687,7 +4680,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,

// Now merge in the new attributes...
size_t curOff = (reinterpret_cast<uintptr_t>(entry.entry) - reinterpret_cast<uintptr_t>(entry.type))
+ dtohs(entry.entry->size);
+ entrySize;
const ResTable_map* map;
bag_entry* entries = (bag_entry*)(set+1);
size_t curEntry = 0;
Expand Down Expand Up @@ -5065,7 +5058,7 @@ uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const
continue;
}

if (dtohl(entry->key.index) == (size_t) *ei) {
if (entry->key() == (size_t) *ei) {
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
Expand Down Expand Up @@ -6579,8 +6572,8 @@ status_t ResTable::getEntry(

const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
if (dtohs(entry->size) < sizeof(*entry)) {
ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
if (entry->size() < sizeof(*entry)) {
ALOGW("ResTable_entry size 0x%zx is too small", entry->size());
return BAD_TYPE;
}

Expand All @@ -6591,7 +6584,7 @@ status_t ResTable::getEntry(
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, entry->key());
}
return NO_ERROR;
}
Expand Down Expand Up @@ -7662,7 +7655,7 @@ void ResTable::print(bool inclValues) const
continue;
}

uintptr_t esize = dtohs(ent->size);
uintptr_t esize = ent->size();
if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void *)esize);
continue;
Expand All @@ -7674,30 +7667,27 @@ void ResTable::print(bool inclValues) const
}

const Res_value* valuePtr = NULL;
const ResTable_map_entry* bagPtr = NULL;
const ResTable_map_entry* bagPtr = ent->map_entry();
Res_value value;
if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) {
if (bagPtr) {
printf("<bag>");
bagPtr = (const ResTable_map_entry*)ent;
} else {
valuePtr = (const Res_value*)
(((const uint8_t*)ent) + esize);
value.copyFrom_dtoh(*valuePtr);
value = ent->value();
printf("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)",
(int)value.dataType, (int)value.data,
(int)value.size, (int)value.res0);
}

if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) {
if (ent->flags() & ResTable_entry::FLAG_PUBLIC) {
printf(" (PUBLIC)");
}
printf("\n");

if (inclValues) {
if (valuePtr != NULL) {
if (bagPtr == NULL) {
printf(" ");
print_value(typeConfigs->package, value);
} else if (bagPtr != NULL) {
} else {
const int N = dtohl(bagPtr->count);
const uint8_t* baseMapPtr = (const uint8_t*)ent;
size_t mapOffset = esize;
Expand Down
6 changes: 3 additions & 3 deletions libs/androidfw/TypeWrappers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,11 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
if (reinterpret_cast<uintptr_t>(entry) > containerEnd - sizeof(*entry)) {
ALOGE("Entry offset at index %u points outside the Type's boundaries", mIndex);
return NULL;
} else if (reinterpret_cast<uintptr_t>(entry) + dtohs(entry->size) > containerEnd) {
} else if (reinterpret_cast<uintptr_t>(entry) + entry->size() > containerEnd) {
ALOGE("Entry at index %u extends beyond Type's boundaries", mIndex);
return NULL;
} else if (dtohs(entry->size) < sizeof(*entry)) {
ALOGE("Entry at index %u is too small (%u)", mIndex, dtohs(entry->size));
} else if (entry->size() < sizeof(*entry)) {
ALOGE("Entry at index %u is too small (%zu)", mIndex, entry->size());
return NULL;
}
return entry;
Expand Down
8 changes: 4 additions & 4 deletions libs/androidfw/include/androidfw/LoadedArsc.h
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,14 @@ class LoadedPackage {
base::expected<uint32_t, NullOrIOError> FindEntryByName(const std::u16string& type_name,
const std::u16string& entry_name) const;

static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntry(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);
static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
GetEntry(incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);

static base::expected<uint32_t, NullOrIOError> GetEntryOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint16_t entry_index);

static base::expected<incfs::map_ptr<ResTable_entry>, NullOrIOError> GetEntryFromOffset(
incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
static base::expected<incfs::verified_map_ptr<ResTable_entry>, NullOrIOError>
GetEntryFromOffset(incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);

// Returns the string pool where type names are stored.
const ResStringPool* GetTypeStringPool() const {
Expand Down
Loading

0 comments on commit 368cd19

Please sign in to comment.