Skip to content

Binder数据转换链

追踪 Parcel 字节区、对象 offsets、Binder 引用、文件描述符和目标 binder_buffer 在发送、转换、接收与清理阶段的真实状态。

基于android-17.0.0_r1
AndroidBinderParcel文件描述符源码阅读

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 refsnode/ref/file 等内核状态Binder 驱动
Parcel viewdata 与 offsets 的非拥有视图接收线程

offsets 不是对象内容,而是驱动的解析索引。若 offsets 越界、未排序、重叠或指向非法类型,事务 会在服务实现执行前失败。

2. Parcel写入 ​

普通数据 ​

Parcel 的 writeInt32、writeString 等方法最终增长 mData 并推进 mDataPos。普通字节只进入 data 区,不会自动出现在 offsets 数组中。驱动复制它们,但不理解业务字段含义。

特殊对象 ​

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

相关函数:writeObject()

cpp
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()

cpp
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()

cpp
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()

c
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

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()

c
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()

c
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()

c
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()

c
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

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

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()

cpp
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()

c
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 存在的原因:不能把尚未验证的对象当成已拥有资源。

正常与失败 ​

路径bufferBinder引用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()

cpp
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 和失败回滚范围。