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()
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 错误状态
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
status_t Parcel::writeInt32(int32_t val) {
return writeAligned(val);
}整数通过对齐写入进入数据区;对齐保证后续字段从协议允许的边界读取。写入失败返回 status 并设置 Parcel 错误,后续 transact() 会在 writeTransactionData() 之前检查。
2.2 UTF16字符串
status_t Parcel::writeString16(const String16& str) {
return writeData(str);
}字符串写入包含长度和 UTF-16 内容,读取端必须使用对应的 readString16();随意把字符串长度当字节数或跳过终止符会导致后续 dataPosition 错位。
2.3 读取位置
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 读取对象
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 对象统计
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 剩余数据
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 可执行阅读
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() 就判断序列化成功。
