Bundle结构入门

0x1 结构

1.1 基本

对Bundle进行序列化时,依次写入携带所有数据的长度、Bundle魔数(0x4C444E42)和键值对。见BaseBundle.writeToParcelInner方法

int lengthPos = parcel.dataPosition();
parcel.writeInt(-1); // dummy, will hold length
parcel.writeInt(BUNDLE_MAGIC);
int startPos = parcel.dataPosition();
parcel.writeArrayMapInternal(map);
int endPos = parcel.dataPosition();
// Backpatch length
parcel.setDataPosition(lengthPos);
int length = endPos - startPos;
parcel.writeInt(length);
parcel.setDataPosition(endPos);

pacel.writeArrayMapInternal方法写入键值对,先写入Hashmap的个数,然后依次写入键和值

/**
     * Flatten an ArrayMap into the parcel at the current dataPosition(),
     * growing dataCapacity() if needed.  The Map keys must be String objects.
     */
/* package */ void writeArrayMapInternal(ArrayMap<String, Object> val) {
    ...
    final int N = val.size();
    writeInt(N);
    ... 
    int startPos;
    for (int i=0; i<N; i++) {
        if (DEBUG_ARRAY_MAP) startPos = dataPosition();
        writeString(val.keyAt(i));
        writeValue(val.valueAt(i));
    ...

接着,调用writeValue时依次写入Value类型和Value本身,如果是Parcelable对象,则调用writeParcelable方法,后者会调用Parcelable对象的writeToParcel方法

public final void writeValue(Object v) {
        if (v == null) {
            writeInt(VAL_NULL);
        } else if (v instanceof String) {
            writeInt(VAL_STRING);
            writeString((String) v);
        } else if (v instanceof Integer) {
            writeInt(VAL_INTEGER);
            writeInt((Integer) v);
        } else if (v instanceof Map) {
            writeInt(VAL_MAP);
            writeMap((Map) v);
        } else if (v instanceof Bundle) {
            // Must be before Parcelable
            writeInt(VAL_BUNDLE);
            writeBundle((Bundle) v);
        } else if (v instanceof PersistableBundle) {
            writeInt(VAL_PERSISTABLEBUNDLE);
            writePersistableBundle((PersistableBundle) v);
        } else if (v instanceof Parcelable) {
            // IMPOTANT: cases for classes that implement Parcelable must
            // come before the Parcelable case, so that their specific VAL_*
            // types will be written.
            writeInt(VAL_PARCELABLE);
            writeParcelable((Parcelable) v, 0);

一个典型的Bundle格式可以包含以下几个部分:

  1. Magic Number (魔数): 标识符,用于验证数据的合法性。
  2. Length (长度): 整个Bundle数据的总长度。
  3. Key-Value Pairs (键值对): 包含键和值的多个键值对。
    每个键值对通常包括以下字段:
  4. Key Length (键长度): 键的长度。
  5. Key (键): 键的实际值。
  6. Value Type (值类型): 指示值的类型。
  7. Value Length (值长度): 值的长度。
  8. Value (值): 值的实际内容。
    下面是一个更详细的示例布局:
[Header]
Magic Number (4 bytes) | Total Length (4 bytes) | Number of Pairs (4 bytes)

[Key-Value Pair 1]
Key Length (4 bytes) | Key (variable length) | Padding (variable length) | 
Value Length (4 bytes) | Value Type (4 bytes) | Value (variable length) | Padding (variable length)

[Key-Value Pair 2]
Key Length (4 bytes) | Key (variable length) | Padding (variable length) | 
Value Length (4 bytes) | Value Type (4 bytes) | Value (variable length) | Padding (variable length)

...

[Key-Value Pair N]
Key Length (4 bytes) | Key (variable length) | Padding (variable length) | 
Value Length (4 bytes) | Value Type (4 bytes) | Value (variable length) | Padding (variable length)

1.2 对齐规则

需要注意的是对齐规则,在Android的Bundle中,数据对齐(alignment)是一个重要的概念,特别是在序列化和反序列化过程中。Android的Parcel类用于读取和写入Bundle的数据,而Parcel类在处理数据时遵循特定的对齐规则,以确保数据在内存中正确对齐。这些对齐规则帮助提高访问效率,并确保数据在不同架构上的兼容性

  1. 基础对齐:
  • 所有写入Parcel的数据都按照4字节对齐。也就是说,数据的起始地址必须是4的倍数。
  • 如果数据的大小不是4的倍数,Parcel会在数据后面填充额外的字节(填充0),以确保下一个数据项从4字节边界开始。
  1. 数据类型对齐:
  • 整数(int, long, float, double)和指针:这些类型的数据在写入Parcel时,都会按照4字节对齐。
  • 字符串和字节数组:字符串和字节数组的长度首先写入Parcel,长度是4字节对齐的,然后是实际的数据部分。数据部分也可能需要填充,以确保下一个数据项从4字节边界开始。
  • 复杂数据类型(如对象数组、Bundle等):这些类型的数据会递归地遵循4字节对齐规则。

1.3 字符串

在Android的Parcel和Bundle中,字符串是以UTF-16编码存储的。UTF-16编码的每个字符都占用两个字节

1.4 类型

在往Intent的Bundle对象中添加键值对(Key Value)时,Key为String类型,而Value则可以为各种数据类型,包括int、Boolean、String和Parcelable对象等等,Parcel类中维护着这些类型信息

// Keep in sync with frameworks/native/include/private/binder/ParcelValTypes.h.
private static final int VAL_NULL = -1;
private static final int VAL_STRING = 0;
private static final int VAL_INTEGER = 1;
private static final int VAL_MAP = 2; // length-prefixed
private static final int VAL_BUNDLE = 3;
private static final int VAL_PARCELABLE = 4; // length-prefixed
private static final int VAL_SHORT = 5;
private static final int VAL_LONG = 6;
private static final int VAL_FLOAT = 7;
private static final int VAL_DOUBLE = 8;
private static final int VAL_BOOLEAN = 9;
private static final int VAL_CHARSEQUENCE = 10;
private static final int VAL_LIST  = 11; // length-prefixed
private static final int VAL_SPARSEARRAY = 12; // length-prefixed
private static final int VAL_BYTEARRAY = 13;
private static final int VAL_STRINGARRAY = 14;
private static final int VAL_IBINDER = 15;
private static final int VAL_PARCELABLEARRAY = 16; // length-prefixed
private static final int VAL_OBJECTARRAY = 17; // length-prefixed
private static final int VAL_INTARRAY = 18;
private static final int VAL_LONGARRAY = 19;
private static final int VAL_BYTE = 20;
private static final int VAL_SERIALIZABLE = 21; // length-prefixed
private static final int VAL_SPARSEBOOLEANARRAY = 22;
private static final int VAL_BOOLEANARRAY = 23;
private static final int VAL_CHARSEQUENCEARRAY = 24;
private static final int VAL_PERSISTABLEBUNDLE = 25;
private static final int VAL_SIZE = 26;
private static final int VAL_SIZEF = 27;
private static final int VAL_DOUBLEARRAY = 28;
private static final int VAL_CHAR = 29;
private static final int VAL_SHORTARRAY = 30;
private static final int VAL_CHARARRAY = 31;
private static final int VAL_FLOATARRAY = 32;
// The initial int32 in a Binder call's reply Parcel header:
// Keep these in sync with libbinder's binder/Status.h.
private static final int EX_SECURITY = -1;
private static final int EX_BAD_PARCELABLE = -2;
private static final int EX_ILLEGAL_ARGUMENT = -3;
private static final int EX_NULL_POINTER = -4;
private static final int EX_ILLEGAL_STATE = -5;
private static final int EX_NETWORK_MAIN_THREAD = -6;
private static final int EX_UNSUPPORTED_OPERATION = -7;
private static final int EX_SERVICE_SPECIFIC = -8;
private static final int EX_PARCELABLE = -9;
public static final int EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127; 
private static final int EX_HAS_STRICTMODE_REPLY_HEADER = -128;  
private static final int EX_TRANSACTION_FAILED = -129;
private static final int ARRAY_ALLOCATION_LIMIT = 1000000;
private static final int SIZE_BYTE = 1;
private static final int SIZE_CHAR = 2;
private static final int SIZE_SHORT = 2;
private static final int SIZE_BOOLEAN = 4;
private static final int SIZE_INT = 4;
private static final int SIZE_FLOAT = 4;
private static final int SIZE_DOUBLE = 8;
private static final int SIZE_LONG = 8;

0x2 实战分析

2.1 测试DEMO

我们先编写了一个这样的Java代码

static void bundle_parcel_study(){
    Bundle b = new Bundle();
    int[] a = {1,1};
    b.putIntArray("aaa",a);
    Hacktools.bundleToBytes(b);
}

然后获取到了它的Hexdump形式

Length: 40 (0x28)
0000: 20 00 00 00 42 4E 44 4C 01 00 00 00 03 00 00 00 |  ...BNDL........
0010: 61 00 61 00 61 00 00 00 12 00 00 00 02 00 00 00 | a.a.a...........
0020: 01 00 00 00 01 00 00 00                         | ........

2.2 构建Header

根据hexdump,我们可以看到:

  • Total Length: 20 00 00 00 (32 bytes)
  • Magic Number: BNDL
  • Number of Pairs: 01 00 00 00 (1 pair)
20 00 00 00 42 4E 44 4C 01 00 00 00

2.3 分析Key-Value Pair

  • 03 00 00 00:表示键的长度为 3
  • 61 00 61 00 61 00 00 00:键"aaa",UTF-16编码,因为要保持 4 字节对齐故自动补齐
  • 12 00 00 00:值的长度:值的类型 + 内容(18字节)。
  • 02 00 00 00:值的类型(整数数组)。
  • 01 00 00 00:第一个整数值1。
  • 01 00 00 00:第二个整数值1。

0x3 工具代码

package com.anonymous.bundlemismatchtest;
import android.util.Log;

public class HexDump {

    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();

    public static void printHexDump(String tag, byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            Log.d(tag, "Empty or null byte array");
            return;
        }

        StringBuilder hexDump = new StringBuilder();
        int length = bytes.length;
        hexDump.append(String.format("length: %d (0x%X)\n", length, length));

        for (int i = 0; i < length; i += 16) {
            hexDump.append(String.format("%04X: ", i));

            // Hex part
            for (int j = 0; j < 16; j++) {
                if (i + j < length) {
                    int v = bytes[i + j] & 0xFF;
                    hexDump.append(HEX_ARRAY[v >>> 4]);
                    hexDump.append(HEX_ARRAY[v & 0x0F]);
                    hexDump.append(' ');
                } else {
                    hexDump.append("   ");
                }
            }

            hexDump.append("| ");

            // ASCII part
            for (int j = 0; j < 16; j++) {
                if (i + j < length) {
                    byte b = bytes[i + j];
                    if (b >= 32 && b <= 126) {
                        hexDump.append((char) b);
                    } else {
                        hexDump.append('.');
                    }
                }
            }

            hexDump.append('\n');
        }

        Log.d(tag, hexDump.toString());
    }
}
package com.anonymous.bundlemismatchtest;

import android.os.Bundle;
import android.os.Parcel;
import android.util.Log;

public class Hacktools {
    //bundle to bytes
    public static byte[] bundleToBytes(Bundle bundle) {
        Parcel parcel = Parcel.obtain();
        parcel.writeBundle(bundle);
        byte[] bytes = parcel.marshall();
        HexDump.printHexDump("MyTag", bytes);
        //parcel.recycle();
        return bytes;
    }

    // bytes to bundle
    public static Bundle bytesToBundle(byte[] bytes) {
        Parcel parcel = Parcel.obtain();
        parcel.unmarshall(bytes, 0, bytes.length);
        parcel.setDataPosition(0);
        Bundle bundle = Bundle.CREATOR.createFromParcel(parcel);
        //parcel.recycle();
        return bundle;
    }

    // hexdump
    public static String hexdump(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
    //simulate the bundle mismatch
    static void simulateBundleMismatch(Bundle b0) {
        // 从exp中发送:序列化
        byte[] exp_send = bundleToBytes(b0);
        // system_server接收:反序列化 readFromParcel
        Bundle b1 = bytesToBundle(exp_send);
        // system_server发送:序列化 writeToParcel
        byte[] system_server_send = bundleToBytes(b1);
        // Setting接收:序列化
        Bundle b2 = bytesToBundle(system_server_send);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值