Androidアプリの16KBページサイズ対応におけるメモリイメージの把握

コラム概要

■こんな方におすすめ:

ページサイズが4KBから16KBに変化することで何が起きているかを知りたい
Androidアプリの16KBページサイズ対応が必要な理由を知りたい
開発/技術担当の方

■難易度:★☆☆~★★☆

■ポイント:

  • ページサイズが4KBから16KBになることで、データを読み取るサイズの基準が変わった
  • 16KB環境で4KB想定の.soが含まれるアプリを動作させると不具合が起きる
  • .soの対応だけではなく、Android Gradle Pluginのバージョンを上げることで、アプリは16KBに完全に対応する
  • アプリの効率的な動作やセキュリティの観点からもメモリを意識した開発は重要である

1. はじめに

Googleにより2025年11月からアプリの16KBページサイズ対応(16KB対応)が義務化されます。16KB対応にはGoogleが推奨する開発環境相当が必要です(表1)。

表1. Google推奨開発環境

環境条件
NDKr28以上
Android Gradle Plugin (AGP)8.5.1以上

もし、Googleが推奨する開発環境を満たさないまま16KB対応を進めると、16KB対応したアプリがGoogle Play Consoleで16KB非対応と表示されるといった予期せぬ問題につながることがあります。

これらはアプリが想定するページサイズと実際のページサイズのずれにより、共有ライブラリ(以下.soファイル)やアプリのメモリマッピングに問題が生じることで起きます。

具体的にページサイズが4KBから16KBに変わること、推奨開発環境の意味することをみていきましょう。

2. 4KBから16KBへの変化とは

次のような流れで4KBから16KBへの、ページサイズの変化を読み解いていきます。

①ページとメモリマッピング
② 4KBと16KBページサイズによるアライメントの違い
③ メモリマッピングAPIの仕様変更がAGPの.soファイル収納ルールへ与える影響


それぞれイメージが難しい概念であるため、以下ではページのイメージとして紙(本やルーズリーフ)を用いて、メモリマッピングの具体的な操作を説明します。

①ページとメモリマッピング

ページとメモリマッピングとは何でしょうか。

[A]本をルーズリーフに転写する行為
[B]ルーズリーフを印刷機でコピーする

メモリのページがかかわる操作を[A][B]の行為へ対応付けてイメージしてみましょう。

[A]1冊の本と同じ内容を、B5またはB3のルーズリーフへ転記してバインダーにまとめたとします(図1)。この操作はソースコードから、メモリマッピング時のページ単位を決めて.soファイルをビルドする操作と似ています。

[B]このルーズリーフの何枚かを、印刷機でB5またはB3のコピー用紙へ1枚ずつコピーする行為は、.soファイルを4KBまたは16KBのメモリへページサイズを基準にマッピングする操作に似ています(図2)。

対応関係は以下のようになります。
・1冊の本:ソースコード(C/C++等)
・B5, B3の規格:4KB, 16KBのページサイズ
・ルーズリーフ:.soファイルにおけるページの概念
・バインダーへまとめる:ページ単位を決めて.soをビルドする
・コピー用紙:メモリにおけるページの概念
・ルーズリーフを印刷機でコピーする : .soファイルをメモリマッピングする

図1. 紙とメモリの対応関係
図2. コピーとメモリマッピングの対応関係

つまり、ページサイズが4KBから16KBに変更されたというのは、[A]や[B]においてルーズリーフやコピー用紙の規格がB5からB3に変更されたことに当たります。

②4KBと16KBページサイズによるアライメントの違い

16 KB対応/非対応の.soファイルはページサイズが4KB, 16KBのメモリではどのようにマッピングされるでしょうか。B3, B5サイズのルーズリーフを同じ文字サイズと、できるだけ同じ構成でB3, B5のコピー用紙に、印刷機を用いて1枚ずつコピーする行為(前述の[B])から考えてみましょう。

バインダーで綴じたルーズリーフの内、p.3-5を印刷機でコピーするとします。

B3のルーズリーフをコピーした場合は図3のようになります。B3からB3は同じ規格で余白なくそのままコピーできます。B3からB5では、1枚に入りきらないため、B3の1枚につき、B5の4枚に分割して合計12枚へ、同様に余白なくコピーできます。

B5のルーズリーフをコピーした場合は、1枚にまとめてコピーしないことで図4のように余白が生じます。一方、B5からB5は同じ規格であるため余白なくそのままコピーできます。

図3. B3のルーズリーフを印刷機でコピーする場合
図4. B5のルーズリーフを印刷機でコピーする場合

B3→B5へのコピーではB5という単位での分割が起きます。
B5→B3のコピーでは、B3への1枚単位でのコピー制限により余白ができます。
分割と余白は、コピー先による「コピー用紙サイズが足りない」、「1枚ずつしかコピーしない」という、1回のコピー単位の制限から生じます。このようなコピー先側の制限は、メモリマッピングにおけるアライメントという概念に近いです。このように、メモリマッピング時のアライメントによって、元の.soファイルがビルド時に期待したメモリ位置からずれてしまうパターンが発生します。

.soファイルには、ページ単位を決めたメモリマッピングルールが埋め込まれます。
以下のように対応させることで余白が生じるパターンを整理できます。

(a) B3(ルーズリーフ) → B3(コピー用紙) : 16KB(.soファイル) → 16KB(メモリ)
(b) B3(ルーズリーフ) → B5(コピー用紙) : 16KB(.soファイル) → 4KB(メモリ)
(c) B5(ルーズリーフ) → B5(コピー用紙) : 4KB(.soファイル) → 4KB(メモリ)
(d) B5(ルーズリーフ) → B3(コピー用紙) : 4KB(.soファイル) → 16KB(メモリ)

(a), (b), (c)では余白が生じませんが、(d)では余白が生じてしまいます

(d)の余白は、ルーズリーフのコピーでは違和感だけで済みますが、.soファイルのメモリマッピングにおける余白はプログラム構造の破綻と同義です。図5のように余白により期待した位置にデータがないことでデータ間の参照が崩れ、プログラムは適切に動作しなくなります。

図5. 余白によりデータ構造が崩れたプログラム

このプログラムの崩壊を防ぐために、1回のコピー操作単位のルールをB5(4KB)単位からB3(16KB)単位のページになるように、NDKを更新してリビルドしましょうとGoogleは推奨しているわけです。

ページとメモリマッピングのイメージはできましたか。
ここからは、例えを交えずに低レイヤで起きる事象を見ていきます。

③メモリマッピングAPIの仕様変更がAGPの.soファイル収納ルールへ与える影響

ファイルのメモリマッピングでは、mmapというAPIが使われる場面があります。
mmapは引数でメモリマッピングするファイル内のマップ開始位置(オフセット)を指定します。このオフセットは各カーネルのページサイズの倍数である必要があります。
mmapに指定可能なオフセット(byte)は以下のページサイズの倍数です。

4KBカーネル : 0x0000, 0x1000(=4KB), 0x2000(=8KB), ... , 0x1000 * P (Pは自然数)
16KBカーネル : 0x0000, 0x4000(=16KB), 0x8000(=32KB), ... , 0x4000 * Q (Qは自然数) 

一部のアプリは、暗黙的にこのmmapのオフセットにおける仕様変更の影響を受ける場合があります。
ビルド時のAGPのバージョンが8.5.0以下のアプリが当該アプリに含まれます。
AGP8.5.1を境に.soファイルをアプリへ収納するルール(アライメント)が変わります(表2)。

表2. AGPバージョンとアライメントの変化

AGPアライメント
8.5.1以上16KB指定
8.5.0以下指定なし(4KB想定)

アプリのAndroidManifest.xml属性のextractNativeLibs(ENL)が”false”のとき、
.soファイルは.apk(ZIP形式)ファイルに非圧縮でAGPの指定するアライメントで収納されてビルドされます(図6)。

図6. ENLによる.soファイル収納ルールの違い

このとき、.soファイルをメモリへロードすると、.apkファイルの.soファイル領域が切り取られ、mmapで直接的にメモリマッピングされます(図7)。

図7. ENL=falseとENL=trueのアプリにおけるメモリマッピングの違い

このメモリマッピングがmmap経由であることから、16KBカーネルにおける
AGPが8.5.0以下∧ENL=falseのアプリはメモリマッピングに失敗することがあります。
具体的にはアプリの.soファイル収納パターンが以下のような場合です(図8)。

(.soファイルの格納オフセット) = 0x1000 * P
              = 0x4000 * Q + R (Q : 商, R ; 余り)
.soファイルのメモリマッピング成功 : R = 0 → 0x4000(16KB)の倍数で割り切れる
.soファイルのメモリマッピング失敗 : R ≠ 0 → 0x4000(16KB)の倍数で割り切れない

図8. AGP8.5.0以下∧ENL=falseでビルドしたアプリ

AGPが8.5.0以下であると、アライメントが16KBの倍数になるかは偶然の一致に頼ることになります。このような理由で、.soファイルだけを16KB対応しても、Google Play Consoleで16KB非対応という判定が表示されてしまう可能性があります。そのため、GoogleはAGPのバージョンを8.5.1以上へ更新することも推奨しているのです。

3. まとめ

4KBから16KBへのページサイズ変化がアプリへ与える具体的な影響について、メモリレベルでの理解はできたでしょうか。ページサイズとメモリマッピングという低レイヤの視点があれば、複雑な事象が生じる原因を読み解いて、適切に16KB対応を完了できるでしょう。
近年では、アプリ開発環境の整備が進み、ページやメモリマッピングを意識する機会は以前に比べて減りました。しかし、システムの根幹部分が変わるような変更を読み解くには、低レイヤへの理解は欠かせません。16KB対応を機に低レイヤの世界を意識してみるのはいかがでしょうか。


当サイトでは、アプリのセキュリティについて詳しくわかる資料をご用意しています。
アプリのセキュリティやクラッキング対策についてご興味を持たれた方は、
ぜひダウンロードしていただけますと幸いです。

ダウンロード資料一覧

今回のコラムを読んで、自社タイトルのチート対策について詳しく話を聞いてみたいと思われた方は
当サイトからお気軽にお問い合わせください。

お問い合わせはこちら