Java とか Android (DEX) の MUTF-8 (Modified UTF-8) って何者よ?っていう話
Android の APK の DEX をゴニョゴニョしてたら、DEX の string 領域に格納されている文字列は実は UTF-8 じゃなくてちょっと modify された MUTF-8 (Modified UTF-8) だということが判明して軽くショックを受けています。
Wikipedia の UTF-8 の項によりますと、標準の UTF-8 から以下の 2 つの変更点があるようです。
- null 文字 (U+0000) を 0x00 という 1 バイトにエンコードしないで 0xC0, 0x80 というバイト列にエンコードする(UTF-8 のルールに則って 0xC0, 0x80 をデコードすると U+0000 に戻る。shortest possible representation に違反している)
- U+10000 〜 U+10FFF のコードポイント -- UTF-16 だとサロゲートペアにする必要があるもの -- については、コードポイントを直接 UTF-8 のエンコード方法でエンコードするのではなく、サロゲートペアのそれぞれのサロゲートコードポイントを別々に UTF-8 のエンコード方法でエンコードする
後者に関しては端的にいうと CESU-8 (Compatibility Encoding Scheme for UTF-16: 8-Bit) というエンコード方法に則っているということのようです。
Wikipedia のページにも例がありますが、U+10400 というコードポイントを普通に UTF-8 でエンコーディングしたら 0xF0, 0x90, 0x90, 0x80 という 4 バイトのバイト列になるのですが、CESU-8 だと 0xED, 0xA0, 0x81, 0xED, 0xB0, 0x80 という 6 バイトのバイト列になります。
これは U+10400 を UTF-16 でエンコーディングすると 0xD801, 0xDC00 というサロゲートペアになるのですが、それぞれ、0xD801 を UTF-8 のようにエンコーディングすると 0xED, 0xA0, 0x81 となり、0xDC00 を UTF-8 のようにエンコーディングすると 0xED, 0xB0, 0x80 となるためなんですね。
これら 2 つのルールを整理しますと、以下の表のようになります。
Unicode コードポイント | U+0000 | U+0001 〜 U+007F | U+0080 〜 U+07FF | U+0800 〜 U+FFFF | U+10000 〜 U+10FFFF |
---|---|---|---|---|---|
通常の UTF-8 | 0x00 | 0x01 〜 0x7F | 0xC4,0x80 〜 0xDF,0xBF | 0xE0,0xA0,0x80 〜 0xEF,0xBF,0xBF | 0xF0,0x90,0x80,0x80 〜 0xF0,0x90,0xBF,0xBF |
UTF-16F | 0x0000 | 0x0001 〜 0x007F | 0x0080 〜 0x7FFF | 0x0800 〜 0xFFFF | 0xD800,0xDC00 〜 0xDBFF,0xDFFF |
Modified UTF-8 | 0xC0,0x80 | 0x01 〜 0x7F | 0xC4,0x80 〜 0xDF,0xBF | 0xE0,0xA0,0x80 〜 0xEF,0xBF,0xBF | 0xED,0xA0,0x80,0xED,0xB0,0x80 〜 0xED,0xAF,0xBF,0xED,0xBF,0xBF |
ちなみに、MUTF-8 を UTF-16 に戻したり、逆に UTF-16 を MUTF-8 に変換したりするコードは Android のソースの
dalvik/dx/src/com/android/dx/rop/cst/CstString.java
あたりにあります。
(関数名 utf8BytesToString() や stringToUtf8Bytes() が該当します)
その他、参考 URL
http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16542