Binder数据转换链
一次 Binder 事务传输的不是一块“纯字节数组”。Native Parcel 同时维护普通数据区和对象偏移表; 驱动先把字节和 offsets 复制到目标进程 buffer,再按每个 offset 解析 Binder、handle、fd、 buffer pointer 和 fd array,逐项转换成目标进程可以使用的形态。
本文面向已经读过 Binder协议状态机 的读者。BD005 解释 BC/BR 命令如何流动,本文只回答数据问题:发送方 Parcel 如何标记对象,binder_transaction_data 如何 暴露 data/offsets,驱动如何验证和改写对象,服务端 Parcel 如何借用映射 buffer,以及成功、失败、 reply 和 BC_FREE_BUFFER 如何清理引用与 fd。
本文不展开每种 Parcelable 编码、AIDL 参数方向和 allocator 页算法。读完后,读者应能区分 payload 字节、对象元数据、目标 buffer、fd fixup 和 Parcel 视图的 owner。
1. 双区结构
字节与offsets
Native Parcel 的内核 Binder 形态可以抽象为:
| 区域 | 内容 | owner |
|---|---|---|
| data | 整数、字符串、结构数据、flat object 本体 | Parcel/目标 buffer |
| offsets | 每个 Binder 特殊对象在 data 中的偏移 | Parcel/目标 buffer |
| object refs | node/ref/file 等内核状态 | Binder 驱动 |
| Parcel view | data 与 offsets 的非拥有视图 | 接收线程 |
offsets 不是对象内容,而是驱动的解析索引。若 offsets 越界、未排序、重叠或指向非法类型,事务 会在服务实现执行前失败。
2. Parcel写入
普通数据
Parcel 的 writeInt32、writeString 等方法最终增长 mData 并推进 mDataPos。普通字节只进入 data 区,不会自动出现在 offsets 数组中。驱动复制它们,但不理解业务字段含义。
特殊对象
源码文件:frameworks/native/libs/binder/Parcel.cpp
相关函数:writeObject()
status_t Parcel::writeObject(
const flat_binder_object& value,
bool nullMetaData)
{
auto* fields = maybeKernelFields();
const bool enoughData =
mDataPos + sizeof(value) <= mDataCapacity;
const bool enoughObjects =
fields->mObjectsSize < fields->mObjectsCapacity;
if (enoughData && enoughObjects) {
restart_write:
*reinterpret_cast<flat_binder_object*>(
mData + mDataPos) = value;
if (value.hdr.type == BINDER_TYPE_FD) {
if (!mAllowFds)
return FDS_NOT_ALLOWED;
fields->mHasFds =
fields->mFdsKnown = true;
}
if (nullMetaData || value.binder != 0) {
fields->mObjects[
fields->mObjectsSize] = mDataPos;
acquire_object(ProcessState::self(),
value, this, true);
fields->mObjectsSize++;
}
return finishWrite(
sizeof(flat_binder_object));
}
if (mOwner)
return PERMISSION_DENIED;
if (!enoughData) {
status_t err = growData(sizeof(value));
if (err != NO_ERROR)
return err;
}
if (!enoughObjects) {
size_t newSize = ((fields->mObjectsSize + 2) * 3) / 2;
binder_size_t* objects = static_cast<binder_size_t*>(
realloc(fields->mObjects, newSize * sizeof(binder_size_t)));
if (objects == nullptr)
return NO_MEMORY;
fields->mObjects = objects;
fields->mObjectsCapacity = newSize;
}
goto restart_write;
}特殊对象先写入 data,再把当前 mDataPos 写进 mObjects。这个 offsets 数组决定驱动扫描哪些位置; 普通数据中偶然出现与 Binder type 相同的整数不会被当作对象。
Binder形态
源码文件:frameworks/native/libs/binder/Parcel.cpp
相关函数:flattenBinder()
if (binder != nullptr) {
if (BBinder* local = binder->localBinder()) {
obj.hdr.type = BINDER_TYPE_BINDER;
obj.flags = FLAT_BINDER_FLAG_ACCEPTS_FDS;
obj.binder = reinterpret_cast<uintptr_t>(
local->getWeakRefs());
obj.cookie =
reinterpret_cast<uintptr_t>(local);
} else {
BpBinder* proxy = binder->remoteBinder();
obj.hdr.type = BINDER_TYPE_HANDLE;
obj.binder = 0;
obj.handle =
proxy->getPrivateAccessor().binderHandle();
obj.cookie = 0;
}
}
return writeObject(obj, false);本地实体用 ptr/cookie 表示,远程对象用 handle 表示。它们进入相同 data/offsets 结构,但驱动 转换方向不同。
3. 事务描述
data指针
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
相关函数:writeTransactionData()
binder_transaction_data transaction;
transaction.target.handle = handle;
transaction.code = code;
transaction.flags = binderFlags;
transaction.data_size = data.ipcDataSize();
transaction.data.ptr.buffer = data.ipcData();
transaction.offsets_size =
data.ipcObjectsCount()
* sizeof(binder_size_t);
transaction.data.ptr.offsets =
data.ipcObjects();
mOut.writeInt32(cmd);
mOut.write(&transaction,
sizeof(transaction));transaction_data 不内嵌整个 Parcel,而是提交发送方用户地址和大小。驱动必须分别复制 data 与 offsets,不能信任用户指针和 object count。
状态码分支
若 Parcel 已处于错误状态,reply 路径可以设置 TF_STATUS_CODE,并让 data 只携带一个 status_t; 接收方 waitForResponse 看到该 flag 后直接读取错误并释放 buffer,不再把它当普通 reply Parcel。
4. 目标buffer
分配
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction()
t->buffer = binder_alloc_new_buf(
&target_proc->alloc,
tr->data_size,
tr->offsets_size,
extra_buffers_size,
!reply && (t->flags & TF_ONE_WAY));
if (IS_ERR(t->buffer)) {
return_error_param = PTR_ERR(t->buffer);
return_error =
return_error_param == -ESRCH
? BR_DEAD_REPLY
: BR_FAILED_REPLY;
t->buffer = NULL;
goto err_binder_alloc_buf_failed;
}
t->buffer->transaction = t;
t->buffer->target_node = target_node;buffer 从目标进程 binder_alloc 分配。目标没有有效映射、空间不足或内存失败时,事务在复制前 终止,服务端不会执行 onTransact。
offsets复制
源码文件:kernel/common/drivers/android/binder.c
if (binder_alloc_copy_user_to_buffer(
&target_proc->alloc,
t->buffer,
ALIGN(tr->data_size, sizeof(void *)),
(const void __user *)
(uintptr_t)tr->data.ptr.offsets,
tr->offsets_size)) {
return_error = BR_FAILED_REPLY;
return_error_param = -EFAULT;
goto err_copy_data_failed;
}offsets 被放在对齐后的 data 区之后。驱动先复制 offsets,随后逐项读取 object_offset;这允许 它在复制业务字节时知道哪些位置必须特殊处理。
payload复制
源码文件:kernel/common/drivers/android/binder_alloc.c
相关函数:binder_alloc_copy_user_to_buffer()
while (bytes) {
page = binder_alloc_get_page(
alloc, buffer,
buffer_offset, &pgoff);
size = min_t(size_t, bytes,
PAGE_SIZE - pgoff);
kptr = kmap_local_page(page) + pgoff;
ret = copy_from_user(kptr, from, size);
kunmap_local(kptr);
if (ret)
return bytes - size + ret;
bytes -= size;
from += size;
buffer_offset += size;
}普通 payload 通过目标 buffer 页面复制。所谓 one-copy 只描述这条主 payload 路径;对象转换、 offsets、security context、fd 安装和 reply 都有额外工作,不能由一句拷贝次数代替。
5. 对象扫描
offset验证
驱动遍历 offsets,要求每个偏移落在 data 内并满足对象大小、顺序和对齐约束。每个 object type 进入不同 translator;失败会记录发生到哪个 off_end_offset,后续清理只释放已经成功转换的对象。
Binder实体
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_translate_binder()
node = binder_get_node(proc, fp->binder);
if (!node) {
node = binder_new_node(proc, fp);
if (!node)
return -ENOMEM;
}
if (fp->cookie != node->cookie)
return -EINVAL;
if (security_binder_transfer_binder(
proc->cred, target_proc->cred))
return -EPERM;
ret = binder_inc_ref_for_node(
target_proc, node,
fp->hdr.type == BINDER_TYPE_BINDER,
&thread->todo, &rdata);
fp->hdr.type = BINDER_TYPE_HANDLE;
fp->binder = 0;
fp->handle = rdata.desc;
fp->cookie = 0;发送方的本地 Binder 在驱动中对应 node;目标进程得到 ref/desc,因此 data 中对象被改写成 HANDLE。服务端读取 Parcel 时会创建或复用 BpBinder。
Binder代理
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_translate_handle()
node = binder_get_node_from_ref(
proc, fp->handle,
fp->hdr.type == BINDER_TYPE_HANDLE,
&src_rdata);
if (!node)
return -EINVAL;
if (node->proc == target_proc) {
fp->hdr.type = BINDER_TYPE_BINDER;
fp->binder = node->ptr;
fp->cookie = node->cookie;
} else {
binder_inc_ref_for_node(
target_proc, node,
fp->hdr.type == BINDER_TYPE_HANDLE,
NULL, &dest_rdata);
fp->binder = 0;
fp->handle = dest_rdata.desc;
fp->cookie = 0;
}handle 指向的 node 若本来属于目标进程,驱动恢复 ptr/cookie;否则给目标进程建立另一份 handle。 同一个远程对象在不同进程中的 handle 值不要求相同。
6. fd转换
fd对象
BINDER_TYPE_FD 不能只复制整数 fd,因为 fd 表属于进程。驱动先取得发送方 struct file,再在目标 进程上下文安装新 fd,并把目标 fd 数字写回 transaction buffer。
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_translate_fd()、binder_apply_fd_fixups()
ret = binder_translate_fd(
fp->fd, fd_offset,
t, thread,
in_reply_to, target_node);
if (ret < 0) {
return_error = BR_FAILED_REPLY;
goto err_translate_failed;
}安装目标 fd 必须在目标 task 上下文完成,因此 transaction 先记录 fd_fixups,binder_thread_read 交付事务前再调用 binder_apply_fd_fixups。
fd交付
源码文件:kernel/common/drivers/android/binder.c
ret = binder_apply_fd_fixups(proc, t);
if (ret) {
binder_cleanup_transaction(
t, "fd fixups failed",
BR_FAILED_REPLY);
binder_free_buf(
proc, thread, buffer, true);
if (cmd == BR_REPLY)
cmd = BR_FAILED_REPLY;
}fd fixup 失败会阻止服务端获得不完整的 fd 集合,并触发 buffer/transaction 清理。fd 传递成功 后,接收 Parcel 将该 fd 标记为 owned,最终由接收方关闭。
7. buffer对象
BINDER_TYPE_PTR 表示 transaction buffer 内的额外数据区域,BINDER_TYPE_FDA 表示嵌入 parent buffer 的 fd 数组。驱动会校验 parent index、parent_offset、长度、对齐和重叠,再把源用户地址 改成目标 buffer 的用户地址。
这类 scatter-gather 对象不是普通 flat_binder_object 的简单别名;非法 parent、长度溢出或 越界都会使事务失败。BD007/BD008 将分别深入对象和 transaction_data 字段。
8. 接收视图
BR_TRANSACTION
源码文件:frameworks/native/libs/binder/IPCThreadState.cpp
Parcel buffer;
buffer.ipcSetDataReference(
reinterpret_cast<const uint8_t*>(
tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(
tr.data.ptr.offsets),
tr.offsets_size /
sizeof(binder_size_t),
freeBuffer);服务端 Parcel 不复制目标 buffer,而是建立外部数据引用,并把 freeBuffer 注册为 owner 回调。 Parcel 生命周期与驱动 buffer 生命周期由这条 release function 连接。
视图校验
源码文件:frameworks/native/libs/binder/Parcel.cpp
相关函数:ipcSetDataReference()
mData = const_cast<uint8_t*>(data);
mDataSize = mDataCapacity = dataSize;
fields->mObjects =
const_cast<binder_size_t*>(objects);
fields->mObjectsSize =
fields->mObjectsCapacity = objectsCount;
mOwner = releaseFunction;
binder_size_t minOffset = 0;
for (size_t i = 0;
i < fields->mObjectsSize; i++) {
binder_size_t offset =
fields->mObjects[i];
if (offset < minOffset) {
fields->mObjectsSize = 0;
break;
}
const flat_binder_object* flat =
reinterpret_cast<
const flat_binder_object*>(
mData + offset);
minOffset = offset +
sizeof(flat_binder_object);
}Parcel 再验证 offsets 单调递增和支持的 object type。驱动已校验并不意味着用户态可以完全信任 数据;两层验证保护不同不变量。
reply视图
客户端 waitForResponse 收到 BR_REPLY 时也调用 ipcSetDataReference。若 TF_STATUS_CODE 已设置, 则读取 status_t 后立即 freeBuffer,不创建普通 reply 视图。
9. 释放路径
Parcel释放
freeBuffer 最终向驱动发送 BC_FREE_BUFFER。只有驱动已经把 buffer 标记为 allow_user_free 后, 用户态地址才可匹配并释放。
对象回滚
源码文件:kernel/common/drivers/android/binder.c
相关函数:binder_transaction_buffer_release()
for (buffer_offset = off_start_offset;
buffer_offset < off_end_offset;
buffer_offset += sizeof(binder_size_t)) {
binder_alloc_copy_from_buffer(
&proc->alloc, &object_offset,
buffer, buffer_offset,
sizeof(object_offset));
object_size = binder_get_object(
proc, NULL, buffer,
object_offset, &object);
switch (object.hdr.type) {
case BINDER_TYPE_BINDER:
case BINDER_TYPE_WEAK_BINDER:
binder_dec_node(node,
object.hdr.type ==
BINDER_TYPE_BINDER, 0);
break;
case BINDER_TYPE_HANDLE:
case BINDER_TYPE_WEAK_HANDLE:
binder_dec_ref_for_handle(
proc, object.fbo.handle,
object.hdr.type ==
BINDER_TYPE_HANDLE,
&rdata);
break;
}
}成功转换到一半后失败时,驱动只回滚已处理 offsets 范围,递减已创建的 node/ref 并处理 fd。 这就是 off_end_offset 存在的原因:不能把尚未验证的对象当成已拥有资源。
正常与失败
| 路径 | buffer | Binder引用 | fd |
|---|---|---|---|
| 正常交付 | Parcel release 后 FREE_BUFFER | 随对象/引用协议维护 | 接收方拥有并关闭 |
| 事务前失败 | 驱动直接释放 | 回滚已转换对象 | 未安装或 fput |
| fd fixup失败 | cleanup transaction | 回滚 node/ref | 关闭已安装 fd |
| status reply | 读取 status 后释放 | 无普通对象视图 | 不适用 |
10. 测试输入
Binder对象列表
源码文件:frameworks/native/libs/binder/tests/binderParcelUnitTest.cpp
相关测试:AppendWithBinder()、AppendWithBinderPartial()、AppendFromPartialObjectList()
测试把多个 BBinder 写进 Parcel,再 append 完整或部分数据。完整 append 后按顺序读回同一 Binder; 截断对象或只复制对象中间区域时,测试检查对象列表是否被拒绝或保持原 Parcel 有效。它证明 offsets 与 data 必须同步变换,不能只复制字节区。
fd传递
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:PassFile()、PassParcelFileDescriptor()、RecvOwnedFileDescriptors()
PassFile 把 pipe 写端作为 fd 对象发给服务,服务写入一个字节,客户端从读端验证值并等待远端关闭; ParcelFileDescriptor 测试发送 dup fd 和 123 字节 payload,再断言完整读取和 EOF。它们证明 fd 转换、payload 和关闭生命周期可以共同闭合,不能证明固定 fd 数量上限。
大数据失败
源码文件:frameworks/native/libs/binder/tests/binderLibTest.cpp
相关测试:VectorSent()、GargantuanVectorSent()、LimitExceededVectorSent()、BufRejected()
TEST_F(BinderLibTest, BufRejected) {
Parcel data, reply;
uint32_t buffer;
binder_buffer_object object {
.hdr = { .type = BINDER_TYPE_PTR },
.buffer =
reinterpret_cast<binder_uintptr_t>(
&buffer),
.length = 4,
};
data.writeFileDescriptor(0);
memcpy(const_cast<uint8_t*>(
data.data()), &object,
sizeof(object));
data.setDataSize(sizeof(object));
EXPECT_THAT(server->transact(
BINDER_LIB_TEST_REJECT_OBJECTS,
data, &reply),
Not(StatusEq(NO_ERROR)));
}测试故意让 offsets 仍指向一个对象,但把原 fd object 覆盖成未经合法构造的 PTR object,断言事务 不会成功。它证明驱动/服务端必须验证对象元数据,不能只信 data size。
11. 反查路径
普通字段错误
若服务读到错误整数或字符串,先检查 AIDL/Parcel 写读顺序和 dataPosition;若 transaction 本身 失败,再查 data_size、copy_from_user 和 buffer 分配。
Binder对象错误
若 readStrongBinder 返回错误类型,检查 mObjects offset、flat type、binder_translate_binder/ handle 和目标 ProcessState 代理复用。不要只检查业务 payload。
fd无效
检查发送 Parcel 是否允许 fd、驱动 security_binder_transfer_file、fd_fixups 和接收 Parcel ownership;fd 数字跨进程必然变化,不能比较发送/接收整数是否相同。
buffer泄漏
检查接收 Parcel 是否仍持有外部视图、freeBuffer 是否生成 BC_FREE_BUFFER、buffer 是否已 allow_user_free,以及失败路径是否执行 binder_transaction_buffer_release。
12. 阅读边界
数据所有权图
本文的核心不是“发生了几次 copy”,而是每一段数据在不同阶段由谁拥有、谁能释放。下面的图把 payload、object offsets 和 fd fixup 分开,避免把三者混成一个字节数组。
payload 的目标 buffer 由接收方 allocator 管理;fd/Binder 对象还要经过驱动 fixup 和安全检查;释放时 Parcel、buffer 和目标资源各有自己的 owner。只追踪 payload 地址无法解释 fd 泄漏或 Binder 引用未释放。
本文证明了 data/offsets 双区、目标 buffer、Binder 引用转换、fd fixup、接收 Parcel 视图和 对象回滚。没有展开每个 object 结构字段、allocator 页树、AIDL 序列化格式、Java Parcel JNI、 大对象建议和固定容量结论。
下一篇 BD007 将深入 flat_binder_object,BD008 将深入 binder_transaction_data,BD009 再系统 分析 Parcel 序列化。复查数据问题时,应分别标出 data owner、offsets owner、对象 translator、 目标 buffer、Parcel release function 和失败回滚范围。
