Skip to content

Parcel序列化详解

追踪 Parcel 数据区、读写位置、对象表、字符串与错误状态如何共同完成 Binder 序列化。

基于android-17.0.0_r1
AndroidBinderParcelNative框架源码阅读

Parcel序列化详解 ​

Parcel 不是单一字节数组。Android 17 的 native 实现同时维护数据缓冲区、当前读写位置、数据大小、Binder/FD 对象 offsets 和错误状态;writeInt32() 改变数据区,writeStrongBinder() 还会登记一个可被驱动翻译的对象,setDataPosition() 则改变消费者读取位置并重置对象遍历提示。

本文面向已经读过 transact发送、Binder数据转换链 和 BBinder本地对象 的读者。本文回答 primitive、字符串、Binder 对象如何进入 Parcel,读取越界如何形成错误,为什么发送前要检查 errorCheck(),以及测试如何验证位置和对象表边界。不展开 AIDL 生成字段协议。

1. 三个状态 ​

1.1 数据区 ​

源码文件:frameworks/native/libs/binder/include/binder/Parcel.h

相关字段和接口:dataPosition()、dataSize()、ipcData()

cpp
size_t dataPosition() const { return mDataPos; }
size_t dataSize() const { return mDataSize; }
uintptr_t ipcData() const;
size_t ipcDataSize() const;

写入操作从 mDataPos 开始追加或覆盖,读取操作也推进同一个位置。ipcData()/ipcDataSize() 给 IPCThreadState::writeTransactionData() 提供发送视图,不等于调用者可以直接解引用所有对象偏移。

1.2 对象表 ​

objectsCount() 返回 kernel fields 中的 Binder/FD 对象数量,RPC Parcel 则读取 RPC object positions。对象 offsets 与数据区分开保存,驱动据此识别 flat_binder_object,不能把对象地址当普通 payload 字节解释。

1.3 错误状态 ​

cpp
status_t Parcel::errorCheck() const { return mError; }
void Parcel::setError(status_t err) { mError = err; }

读写失败通常先写入 mError,调用者在发送前通过 errorCheck() 收束。错误状态属于 Parcel owner,不会自动抛出 C++ 异常。

2. 基本写入 ​

2.1 对齐整数 ​

源码文件:frameworks/native/libs/binder/Parcel.cpp

cpp
status_t Parcel::writeInt32(int32_t val) {
    return writeAligned(val);
}

整数通过对齐写入进入数据区;对齐保证后续字段从协议允许的边界读取。写入失败返回 status 并设置 Parcel 错误,后续 transact() 会在 writeTransactionData() 之前检查。

2.2 UTF16字符串 ​

cpp
status_t Parcel::writeString16(const String16& str) {
    return writeData(str);
}

字符串写入包含长度和 UTF-16 内容,读取端必须使用对应的 readString16();随意把字符串长度当字节数或跳过终止符会导致后续 dataPosition 错位。

2.3 读取位置 ​

cpp
void Parcel::setDataPosition(size_t pos) const {
    if (pos > INT32_MAX) {
        LOG_ALWAYS_FATAL("pos too big: %zu", pos);
    }
    mDataPos = pos;
    if (const auto* kernelFields = maybeKernelFields()) {
        kernelFields->mNextObjectHint = 0;
        kernelFields->mObjectsSorted = false;
    }
}

重置位置不仅影响 primitive 读取,还会使对象查找提示失效。调试代码遍历所有 Binder 后必须恢复初始位置,否则业务读取会从错误偏移开始。

3. Binder对象 ​

3.1 写入对象 ​

源码文件:frameworks/native/libs/binder/Parcel.cpp

相关函数:writeStrongBinder()、ipcObjectsCount()

writeStrongBinder() 把本地 BBinder 或远程 BpBinder 编码成 Binder 对象并追加到 offsets 表;发送时驱动根据对象类型创建 node/ref 或传递 handle。对象表数量可通过 objectsCount() 观察,不能只看 dataSize()。

3.2 读取对象 ​

cpp
sp<IBinder> Parcel::readStrongBinder() const {
    sp<IBinder> val;
    if (readStrongBinder(&val) != NO_ERROR) return nullptr;
    return val;
}

读取 Binder 会沿当前对象 offset 找到 flat_binder_object,再交给 IPCThreadState/ProcessState 转换为本地代理或对象。读取失败返回空或 status,调用者应区分“合法 null Binder”和“读取错误”。

3.3 对象统计 ​

cpp
size_t Parcel::objectsCount() const {
    if (const auto* kernelFields = maybeKernelFields()) {
        return kernelFields->mObjectsSize;
    }
    if (auto* rpcFields = maybeRpcFields()) {
        return rpcFields->mObjectPositions.size();
    }
    LOG_ALWAYS_FATAL("Must be for kernel or for RPC");
}

Kernel Binder 和 RPC Parcel 的对象表来源不同;跨上下文 appendFrom() 会返回 BAD_TYPE,不能把一种 Parcel 的对象 offsets 直接拼到另一种格式。

4. 读取校验 ​

4.1 字符串损坏 ​

源码文件:frameworks/native/libs/binder/tests/binderParcelUnitTest.cpp

测试先写入 String16,再覆盖长度字段使其不再以合法终止形式存在;readString16() 返回非 OK 且输出为空。它证明读取器检查 wire format,而不是盲目按长度复制。

4.2 剩余数据 ​

cpp
status_t Parcel::enforceNoDataAvail() const {
    if (dataAvail() == 0) return binder::Status::ok();
    return binder::Status::fromExceptionCode(
            binder::Status::Exception::EX_BAD_PARCELABLE,
            "Parcel data not fully consumed");
}

AIDL/Stub 在读取预期字段后可调用 enforceNoDataAvail(),把尾随未消费字节变成坏 Parcelable。它是业务协议完整性检查,不是底层 errorCheck() 的替代品。

4.3 Interface token ​

checkInterface() 调用 enforceInterface(),先读取请求头(kernel Binder 模式),再读取 UTF-16 descriptor;不匹配返回 false。它把 Parcel 内容与 IBinder::getInterfaceDescriptor() 联系起来。

5. 发送边界 ​

5.1 事务视图 ​

IPCThreadState::writeTransactionData() 使用 ipcDataSize()、ipcData()、ipcObjectsCount() 和 ipcObjects() 填充 binder_transaction_data。因此 Parcel 写入成功后仍可能在对象翻译、目标 buffer 或驱动 ioctl 阶段失败。

5.2 RPC隔离 ​

Parcel::appendFrom() 检查双方 isForRpc();RPC Parcel 还要求属于同一个 RpcSession。这保证对象位置和 fd 附件不会跨传输上下文误解释。

5.3 清理责任 ​

Binder/FD 对象由 Parcel 对象表和释放逻辑管理;调用者不应直接 free ipcData() 返回的地址。Parcel 析构或重置会释放其持有对象引用,驱动交付后的目标副本由另一端生命周期负责。

6. 测试边界 ​

6.1 基础与位置 ​

源码文件:frameworks/native/libs/binder/tests/binderParcelUnitTest.cpp

TestReadCString 写入多个整数和 C 字符串,重置位置后读取并断言最终位置一致;AppendPlainData、AppendWithBinder 验证追加数据和对象 offsets;AppendFromBadRange 验证越界范围返回 BAD_VALUE。

6.2 对象与错误 ​

DebugReadAllBinders 验证 Binder 对象扫描;NonNullTerminatedString16 验证损坏字符串;EnforceNoDataAvail 验证尾随数据变成 EX_BAD_PARCELABLE。这些测试覆盖 Parcel 状态和输入边界,不证明驱动跨进程对象翻译的全部失败路径。

6.3 可执行阅读 ​

bash
rg -n "setDataPosition|writeInt32|writeString16|writeStrongBinder|readStrongBinder|objectsCount|errorCheck" \
  frameworks/native/libs/binder/Parcel.cpp \
  frameworks/native/libs/binder/include/binder/Parcel.h

rg -n "TestReadCString|AppendWithBinder|AppendFromBadRange|DebugReadAllBinders|EnforceNoDataAvail" \
  frameworks/native/libs/binder/tests/binderParcelUnitTest.cpp

排查 Parcel 读取错误时,先检查写入/读取类型是否对称,再检查 dataPosition 是否在正确边界、对象 offsets 是否与数据区一致,最后检查 errorCheck() 和 enforceNoDataAvail();不要只打印 dataSize() 就判断序列化成功。