Skip to content

BC事务写入

追踪 BC_TRANSACTION 与 BC_REPLY 的用户态构造、驱动解析、SG 变体和事务入口边界。

基于android-17.0.0_r1
AndroidBinderBC_TRANSACTIONBC_REPLY源码阅读

BC事务写入 ​

BC_TRANSACTION 和 BC_REPLY 是写入方向的两个事务命令:用户态把命令号与 binder_transaction_data 写入 IPCThreadState::mOut,驱动在 binder_thread_write() 中复制 descriptor,再调用 binder_transaction()。

本文面向已经读过 BINDER_WRITE_READ命令、Binder事务发送 和 Binder远程引用 的读者,专门回答写入命令本身如何选择 request/reply、如何表达失败状态,以及标准结构和 SG 结构如何进入同一事务函数。

本文不重新讲目标 node/ref、buffer 分配和对象翻译的全部算法;这些是 BD017 的主题。这里的重点是“用户态写入什么,驱动先复制什么,哪些字段由驱动忽略或覆盖,以及错误如何回到 waitForResponse()”。

1. 命令对照 ​

命令用户态入口reply目标字段典型结果
BC_TRANSACTIONtransact()falsetarget.handle同步请求或 oneway 请求
BC_REPLYsendReply()true驱动从 transaction stack 找回复原发送线程
BC_TRANSACTION_SGUAPI/驱动入口falsetarget.handle额外 buffers_size
BC_REPLY_SGUAPI/驱动入口true驱动从 transaction stack 找额外 buffers_size

普通 libbinder 调用使用前两种命令;SG 变体由驱动支持,但当前 frameworks/native 的普通生产路径没有直接写入它们。不要因为 UAPI 有 SG 命令,就把每个 Parcel 都描述成 SG 事务。

2. 用户态构造 ​

2.1 普通调用 ​

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

相关函数:IPCThreadState::transact()、`writeTransactionData()``

cpp
status_t IPCThreadState::transact(
        int32_t handle, uint32_t code,
        const Parcel& data, Parcel* reply,
        uint32_t flags) {
    flags |= TF_ACCEPT_FDS;
    status_t err = writeTransactionData(
            BC_TRANSACTION, flags, handle,
            code, data, nullptr);
    if (err != NO_ERROR)
        return err;
    if ((flags & TF_ONE_WAY) == 0)
        return waitForResponse(reply);
    return waitForResponse(nullptr, nullptr);
}

TF_ONE_WAY 不会把命令号换成其他 BC 命令;它仍是 BC_TRANSACTION,只是让驱动把事务标成 async,并改变发送方是否等待 reply。TF_ACCEPT_FDS 由 libbinder 默认加入,驱动仍会按目标 node 的 fd 策略检查对象。

2.2 Descriptor构造 ​

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

相关函数:writeTransactionData()

cpp
status_t IPCThreadState::writeTransactionData(
        int32_t cmd, uint32_t binderFlags,
        int32_t handle, uint32_t code,
        const Parcel& data,
        status_t* statusBuffer) {
    binder_transaction_data tr;
    tr.target.ptr = 0;
    tr.target.handle = handle;
    tr.code = code;
    tr.flags = binderFlags;
    tr.cookie = 0;
    tr.sender_pid = 0;
    tr.sender_euid = 0;

    const status_t err = data.errorCheck();
    if (err == NO_ERROR) {
        tr.data_size = data.ipcDataSize();
        tr.data.ptr.buffer = data.ipcData();
        tr.offsets_size =
            data.ipcObjectsCount() *
            sizeof(binder_size_t);
        tr.data.ptr.offsets = data.ipcObjects();
    } else if (statusBuffer) {
        tr.flags |= TF_STATUS_CODE;
        *statusBuffer = err;
        tr.data_size = sizeof(status_t);
        tr.data.ptr.buffer =
            reinterpret_cast<uintptr_t>(
                statusBuffer);
        tr.offsets_size = 0;
        tr.data.ptr.offsets = 0;
    } else {
        return (mLastError = err);
    }
    mOut.writeInt32(cmd);
    mOut.write(&tr, sizeof(tr));
    return NO_ERROR;
}

用户态明确把 sender_pid、sender_euid 初始化为 0;这些字段不是调用者可信输入,驱动在接收 transaction 时填充目标可见身份。data.ptr.buffer 和 offsets 仍指向发送方 Parcel 的用户地址,驱动后续复制并翻译。

2.3 状态码回复 ​

sendReply() 使用 statusBuffer 接收 reply Parcel 的序列化错误:

cpp
status_t IPCThreadState::sendReply(
        const Parcel& reply, uint32_t flags) {
    status_t statusBuffer;
    status_t err = writeTransactionData(
            BC_REPLY, flags, -1, 0,
            reply, &statusBuffer);
    if (err < NO_ERROR)
        return err;
    return waitForResponse(nullptr, nullptr);
}

reply 的 handle 参数固定为 -1,因为驱动不会从 reply descriptor 的 target.handle 找目标;binder_transaction(reply=1) 依赖当前线程 transaction stack。若 reply Parcel 自身有错误,TF_STATUS_CODE 把状态写成目标可读的 32 位结果。

3. 驱动解析 ​

3.1 写循环 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_thread_write()

c
while (ptr < end &&
        thread->return_error.cmd == BR_OK) {
    uint32_t cmd;
    int ret;
    if (get_user(cmd,
            (uint32_t __user *)ptr))
        return -EFAULT;
    ptr += sizeof(uint32_t);
    trace_binder_command(cmd);
    if (_IOC_NR(cmd) <
            ARRAY_SIZE(binder_stats.bc)) {
        atomic_inc(&binder_stats.bc[_IOC_NR(cmd)]);
        atomic_inc(&proc->stats.bc[_IOC_NR(cmd)]);
        atomic_inc(&thread->stats.bc[_IOC_NR(cmd)]);
    }
    switch (cmd) {
        /* BC_TRANSACTION/BC_REPLY 在下方分支 */
    }
}

循环先读取 4 字节 command,再进入对应 case;write_consumed 以字节为单位推进。线程已经有 return error 时停止解析后续 BC,避免错误状态继续扩大。

3.2 标准结构 ​

源码文件:kernel/common/drivers/android/binder.c

相关命令:BC_TRANSACTION、BC_REPLY

c
case BC_TRANSACTION:
case BC_REPLY: {
    struct binder_transaction_data tr;

    if (copy_from_user(&tr, ptr,
            sizeof(tr)))
        return -EFAULT;
    ptr += sizeof(tr);
    binder_transaction(
            proc, thread, &tr,
            cmd == BC_REPLY, 0);
    break;
}

标准命令的实际分派只有两个动作:复制 descriptor,依据命令号生成 reply 布尔值,额外空间为 0。事务目标查找、buffer 申请、对象翻译和 work 入队由 binder_transaction() 接管。

3.3 SG结构 ​

源码文件:kernel/common/drivers/android/binder.c

相关命令:BC_TRANSACTION_SG、BC_REPLY_SG

c
case BC_TRANSACTION_SG:
case BC_REPLY_SG: {
    struct binder_transaction_data_sg tr;

    if (copy_from_user(&tr, ptr,
            sizeof(tr)))
        return -EFAULT;
    ptr += sizeof(tr);
    binder_transaction(
            proc, thread,
            &tr.transaction_data,
            cmd == BC_REPLY_SG,
            tr.buffers_size);
    break;
}

SG 结构在标准 descriptor 后增加 buffers_size,驱动只把它作为 extra_buffers_size 传给统一事务函数。它改变目标 buffer 的额外空间计算,不改变 reply 栈、目标解析或对象翻译的主入口。

4. 请求路径 ​

4.1 Target handle ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()

c
if (!reply && tr->target.handle) {
    struct binder_ref *ref;
    binder_proc_lock(proc);
    ref = binder_get_ref_olocked(
            proc, tr->target.handle, true);
    if (ref)
        target_node =
            binder_get_node_refs_for_txn(
                ref->node, &target_proc,
                &return_error);
    else
        return_error = BR_FAILED_REPLY;
    binder_proc_unlock(proc);
}

请求的 handle 属于发送方 proc。驱动要求强 ref,找到后增加 node/proc 临时引用;handle 无效或 node 已死时,事务函数设置 BR_FAILED_REPLY 或 BR_DEAD_REPLY,写循环本身仍完成 descriptor 消费。

4.2 Context manager ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()

c
if (!reply && !tr->target.handle) {
    mutex_lock(
        &context->context_mgr_node_lock);
    target_node =
        context->binder_context_mgr_node;
    if (target_node)
        target_node =
            binder_get_node_refs_for_txn(
                target_node, &target_proc,
                &return_error);
    else
        return_error = BR_DEAD_REPLY;
    mutex_unlock(
        &context->context_mgr_node_lock);
}

handle 0 指向当前 Binder context 的 manager node。context manager 未注册时请求失败;它不是普通 descriptor 0 的偶然值,而是由 context manager node 关系决定的特殊入口。

5. 回复路径 ​

5.1 栈校验 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction()

c
if (reply) {
    binder_inner_proc_lock(proc);
    in_reply_to = thread->transaction_stack;
    if (!in_reply_to) {
        binder_inner_proc_unlock(proc);
        return_error = BR_FAILED_REPLY;
        return_error_param = -EPROTO;
        goto err_empty_call_stack;
    }
    if (in_reply_to->to_thread != thread) {
        binder_inner_proc_unlock(proc);
        return_error = BR_FAILED_REPLY;
        return_error_param = -EPROTO;
        goto err_bad_call_stack;
    }
    thread->transaction_stack =
        in_reply_to->to_parent;
    binder_inner_proc_unlock(proc);
}

BC_REPLY 不携带一个可供驱动信任的目标 handle。当前 thread 必须正在处理栈顶请求,驱动先校验 to_thread,再弹出接收栈项;发送线程和目标 proc 稍后从 in_reply_to->from 取得。

5.2 发送结果 ​

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

相关函数:waitForResponse()

cpp
case BR_DEAD_REPLY:
    err = DEAD_OBJECT;
    goto finish;
case BR_FAILED_REPLY:
    err = FAILED_TRANSACTION;
    goto finish;
case BR_FROZEN_REPLY:
    err = enableFrozenObjectErrorCode()
        ? FROZEN_OBJECT
        : FAILED_TRANSACTION;
    goto finish;
case BR_REPLY:
    binder_transaction_data tr;
    err = mIn.read(&tr, sizeof(tr));
    if (err != NO_ERROR)
        goto finish;
    reply->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);
    goto finish;

驱动事务错误以 BR 命令返回,libbinder 再映射为 DEAD_OBJECT、FAILED_TRANSACTION 或 frozen error。成功 BR_REPLY 不复制 payload 到另一个 Parcel,而是把目标 Binder buffer 交给 reply Parcel,并注册 freeBuffer 回调,最终通过 BC_FREE_BUFFER 归还。

6. 事务标志 ​

6.1 Oneway ​

源码文件:frameworks/native/libs/binder/IPCThreadState.cpp、kernel/common/include/uapi/linux/android/binder.h

相关符号:TF_ONE_WAY

c
enum transaction_flags {
    TF_ONE_WAY = 0x01,
    TF_ROOT_OBJECT = 0x04,
    TF_STATUS_CODE = 0x08,
    TF_ACCEPT_FDS = 0x10,
    TF_CLEAR_BUF = 0x20,
    TF_UPDATE_TXN = 0x40,
};

TF_ONE_WAY 让驱动设置 t->is_async 并进入 node/proc async 路径;用户态 transact() 不等待业务 reply,只等待写入完成或 pending 状态。服务端如果错误地构造 BC_REPLY,驱动仍按 transaction stack 校验,而不是因为原请求是 oneway 就生成一个隐式 reply。

6.2 Status code ​

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

相关函数:writeTransactionData()、waitForResponse()

cpp
if (err != NO_ERROR && statusBuffer) {
    tr.flags |= TF_STATUS_CODE;
    *statusBuffer = err;
    tr.data_size = sizeof(status_t);
    tr.data.ptr.buffer =
        reinterpret_cast<uintptr_t>(
            statusBuffer);
    tr.offsets_size = 0;
    tr.data.ptr.offsets = 0;
}

当 writeTransactionData() 收到 statusBuffer 时,status code 是事务 payload 的一部分;它与驱动返回的 BR_FAILED_REPLY 不同。前者是用户态把已知错误编码进 reply data,后者表示驱动在目标解析、分配或入队阶段拒绝事务。

6.3 Clear buffer ​

源码文件:kernel/common/include/uapi/linux/android/binder.h、kernel/common/drivers/android/binder.c

相关符号:TF_CLEAR_BUF、binder_transaction_buffer_release()

TF_CLEAR_BUF 让目标 buffer 在事务结束时清理数据;它不改变 BC 命令格式,也不代表发送方 payload 在发送前会被清零。实际释放仍由接收方处理 BR_TRANSACTION/BR_REPLY 后提交 BC_FREE_BUFFER 触发。

7. 消费与清理 ​

7.1 写入消费 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_thread_write()

c
ptr += sizeof(uint32_t);
if (copy_from_user(&tr, ptr, sizeof(tr)))
    return -EFAULT;
ptr += sizeof(tr);
binder_transaction(proc, thread, &tr,
        cmd == BC_REPLY, 0);
*consumed = ptr - buffer;

descriptor 复制成功后,ptr 已越过整个结构;事务函数内部失败不会把 BC 消费位置倒退。用户态 libbinder 对成功 ioctl 要求完整消费本轮 mOut,事务失败通过 BR/error work 反馈,而不是通过重放同一 BC。

7.2 事务完成 ​

源码文件:kernel/common/drivers/android/binder.c

相关函数:binder_transaction() 错误尾部

c
if (in_reply_to) {
    binder_set_txn_from_error(
        in_reply_to, t_debug_id,
        return_error, return_error_param);
    thread->return_error.cmd =
        BR_TRANSACTION_COMPLETE;
    binder_enqueue_thread_work(
        thread, &thread->return_error.work);
    binder_send_failed_reply(
        in_reply_to, return_error);
} else {
    binder_set_extended_error(
        &thread->ee, t_debug_id,
        return_error, return_error_param);
    thread->return_error.cmd = return_error;
    binder_enqueue_thread_work(
        thread, &thread->return_error.work);
}

reply 失败和普通请求失败的返回 owner 不同:reply 失败要把错误送回原请求链;普通请求把错误放进当前发送 thread 的 return_error 和 extended error。两者最终都由后续读路径变成 BR 命令。

8. 测试输入 ​

8.1 空请求 ​

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

相关测试:Transaction

cpp
struct {
    uint32_t cmd1;
    binder_transaction_data arg1;
} __attribute__((packed)) bc1 = {
    .cmd1 = BC_TRANSACTION,
    .arg1 = {
        .target = {0},
        .code = android::IBinder::PING_TRANSACTION,
        .flags = 0,
        .data_size = 0,
        .offsets_size = 0,
        .data = { .ptr = {0, 0} },
    },
};
bwr.write_buffer = (uintptr_t)&bc1;
bwr.write_size = sizeof(bc1);
bwr.read_buffer = (uintptr_t)&br;
bwr.read_size = sizeof(br);

测试输入是空 payload 的 BC_TRANSACTION,read buffer 接收多轮 BR。关键断言包括 write_consumed == sizeof(bc1)、BR_NOOP、BR_TRANSACTION_COMPLETE、BR_REPLY 和 reply descriptor 的零字段;它证明写入、基础 reply 形状和 BC_FREE_BUFFER 路径,不证明非空对象翻译。

8.2 Oneway ​

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

相关测试:NopTransactionOneway、OnewayQueueing

cpp
TEST_F(BinderLibTest, NopTransactionOneway) {
    Parcel data, reply;
    EXPECT_THAT(m_server->transact(
        BINDER_LIB_TEST_NOP_TRANSACTION,
        data, &reply, TF_ONE_WAY),
        StatusEq(NO_ERROR));
}

oneway 测试断言用户态返回成功,queueing 测试使用延迟 callback 与立即 callback 检查同一 node 的 async 顺序。它不证明 BC_REPLY 会在 oneway 请求后自动生成。

8.3 Freeze错误 ​

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

相关测试:FreezeTxn

冻结目标后同步 transact 期望 frozen error,解冻后同一 transact 成功。它覆盖 BR_FROZEN_REPLY 从 driver 到用户态的映射,不覆盖 BC_REPLY 栈错误或 SG 事务。

9. 失败边界 ​

阶段失败对用户态的结果
descriptor copy用户地址无效ioctl -EFAULT,BC 未进入事务函数
target lookuphandle/node/context 无效BR_FAILED_REPLY 或 BR_DEAD_REPLY
reply stack无栈或 to_thread 不符BR_FAILED_REPLY/-EPROTO
allocator/translationbuffer、offset、object 或 fd 失败failed reply、buffer/fixup 回滚
frozen target同步/异步状态不同BR_FROZEN_REPLY 或 pending
read resultBR 写用户空间失败ioctl -EFAULT,事务清理
Parcel errorstatusBuffer 分支TF_STATUS_CODE payload,不等于 driver errno

BC 命令被消费与事务成功是两个时刻。写入口可以已经推进 consumed,随后事务在 target lookup、buffer、translation 或 enqueue 阶段失败;排查时必须同时查看 BR、extended error 和 transaction log。

10. 源码复现 ​

本文主线:

text
transact/sendReply
→ writeTransactionData
→ mOut: BC命令+descriptor
→ binder_thread_write
→ 标准或SG结构
→ binder_transaction(reply)
→ target栈/handle/context
→ buffer与flags
→ BR结果或error work

源码搜索:

bash
rg -n "writeTransactionData|BC_TRANSACTION|BC_REPLY|sendReply" \
  frameworks/native/libs/binder/IPCThreadState.cpp

rg -n "case BC_TRANSACTION|case BC_REPLY|binder_transaction\(" \
  kernel/common/drivers/android/binder.c

rg -n "TF_ONE_WAY|TF_STATUS_CODE|TF_CLEAR_BUF|binder_transaction_data_sg" \
  kernel/common/include/uapi/linux/android/binder.h

rg -n "TEST_F.*Transaction|NopTransactionOneway|OnewayQueueing|FreezeTxn" \
  frameworks/native/libs/binder/tests/binderDriverInterfaceTest.cpp \
  frameworks/native/libs/binder/tests/binderLibTest.cpp

如果能够解释为什么 TF_ONE_WAY 不改变 BC 命令号、为什么 BC_REPLY 不信任 target.handle、为什么 SG 只增加 extra_buffers_size、以及为什么 descriptor 消费成功仍可能得到 BR_FAILED_REPLY,就已经掌握了这两个写入命令的真实源码边界。