Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 368cd19d authored by Eric Miao's avatar Eric Miao
Browse files

androidfw: Add support for compact resource entries

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
parent b0d0974a
Loading
Loading
Loading
Loading
+12 −26
Original line number Diff line number Diff line
@@ -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
@@ -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());
  }
@@ -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(),
  };
}
+15 −9
Original line number Diff line number Diff line
@@ -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.";
@@ -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.";
@@ -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))) {
@@ -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)
@@ -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())) {
@@ -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(
@@ -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);
+18 −28
Original line number Diff line number Diff line
@@ -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
@@ -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;

@@ -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;
@@ -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;
@@ -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;
    }

@@ -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;
}
@@ -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;
@@ -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;
+3 −3
Original line number Diff line number Diff line
@@ -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;
+4 −4
Original line number Diff line number Diff line
@@ -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 {
Loading