如何从 userspace 获取 Android Binder 的 handle?
可以在 userspace 中获取 handle:通过 ServiceManager 查询服务,或在一次 Binder 调用中接收目标 Binder 对象,然后读取驱动交付的 flat_binder_object.handle,填入 tr.target.handle。
handle 属于调用方的 Binder 引用表。同一个 binder_node 在不同客户端中可能对应不同的 handle,因此无法根据目标进程的 PID、节点地址或调试编号直接算出这个值。
handle 与 binder_node 的关系
发送普通 transaction 时,驱动按以下关系查找目标:
调用方 binder_proc
→ 根据 target.handle 查找 binder_ref
→ binder_ref 指向 binder_node
→ binder_node 所属的目标 binder_proc
目标进程拥有 binder_node,调用方持有指向该节点的引用,handle 就是调用方引用表中的描述符。驱动收到 BC_TRANSACTION 后,会在发送方的引用表中查找这个 handle。对应的驱动实现
这些对象和引用的创建顺序是:
- 打开 Binder 设备时,驱动创建对应的
binder_proc。 - 通过 transaction 将本地 Binder 对象传给其他进程时,驱动按需创建
binder_node。向 ServiceManager 注册服务就是其中一种方式。 - 客户端收到这个对象后,驱动为客户端建立引用,并交付客户端可用的 handle。
因此,addService() 不会返回一个所有进程通用的 handle;客户端也不需要先注册自己的服务才能发送 transaction。
通过 ServiceManager 获取目标引用
如果目标已经注册为命名服务,直接使用 ioctl 的客户端需要完成以下步骤:
- 打开目标服务所在 Binder context 的设备,并通过
mmap映射接收缓冲区。 - 向
target.handle = 0发送服务查询请求。0是该 context 的 context manager 的特殊入口,Android 中通常由对应的 ServiceManager 担任 context manager。 - 按目标 Android 版本的协议编码服务名、interface token 等数据,并使用对应查询方法的 transaction code。
- 通过
BINDER_WRITE_READ读取返回命令,等待BR_REPLY,检查查询是否成功。 - 按回复的 Parcel 布局找到服务对象,取得类型为
BINDER_TYPE_HANDLE的flat_binder_object,读取其handle。
ServiceManager 的查询方法和回复布局需要与设备源码一致。不同实现可能直接返回 IBinder,也可能把服务对象包装在包含元数据的结构中。因此,不能直接照搬其他版本的 transaction code,也不能假定回复的前四个字节就是 handle。ServiceManager 接口定义
如果可以使用平台 C++ libbinder,可以让它处理服务查询:
#include <binder/IBinder.h>
#include <binder/IServiceManager.h>
#include <utils/String16.h>
android::sp<android::IBinder> service =
android::defaultServiceManager()->checkService(
android::String16("your.service.name"));
if (service == nullptr) {
// 查询未取得服务,检查注册状态、名称和访问权限。
}
这类平台接口不等同于普通应用可直接使用的 NDK API。如果只是调用服务,可以继续使用 IBinder::transact();如果必须自行调用 ioctl,获取引用和发送 transaction 就必须使用同一个 Binder 连接。C++ ServiceManager 接口
从原始回复中读取 handle
Binder 对象跨进程传递时,驱动会转换它的表示。例如,服务端发送的 BINDER_TYPE_BINDER,到远端客户端时可以变成 BINDER_TYPE_HANDLE,其中的 handle 属于接收方。对象转换实现
下面的函数从已收到的 transaction 中读取一个远端对象的 handle。使用前,需要按回复协议确认目标服务对象的字节偏移 object_offset,并确保接收缓冲区尚未释放。
#include <linux/android/binder.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
static bool read_remote_handle(
const struct binder_transaction_data *reply,
binder_size_t object_offset,
uint32_t *handle)
{
if (reply->offsets_size % sizeof(binder_size_t) != 0)
return false;
if (object_offset > reply->data_size ||
reply->data_size - object_offset <
sizeof(struct flat_binder_object))
return false;
const unsigned char *offsets =
(const unsigned char *)(uintptr_t)reply->data.ptr.offsets;
bool listed = false;
for (binder_size_t i = 0; i < reply->offsets_size;
i += sizeof(binder_size_t)) {
binder_size_t offset;
memcpy(&offset, offsets + i, sizeof(offset));
if (offset == object_offset) {
listed = true;
break;
}
}
if (!listed)
return false;
struct flat_binder_object object;
const unsigned char *data =
(const unsigned char *)(uintptr_t)reply->data.ptr.buffer;
memcpy(&object, data + object_offset, sizeof(object));
if (object.hdr.type != BINDER_TYPE_HANDLE)
return false;
*handle = object.handle;
return true;
}
data.ptr.offsets 指向 Binder 对象偏移数组,每个元素的类型是 binder_size_t。offsets_size 是数组占用的字节数,每个数组元素则是对象相对于数据缓冲区起点的字节偏移。Binder UAPI 定义
取得 handle 并正确保留引用后,就可以设置这些字段:
struct binder_transaction_data tr = {0};
tr.target.handle = handle;
tr.code = method_code; // 使用目标服务协议规定的 transaction code。
tr.data_size = buffer_size;
tr.offsets_size = offsets_size;
tr.data.ptr.buffer = (binder_uintptr_t)(uintptr_t)data_buffer;
tr.data.ptr.offsets = (binder_uintptr_t)(uintptr_t)off_buffer;
这个函数只负责读取描述符,没有实现 ServiceManager 请求编码、BR_* 命令解析和引用生命周期管理,不能单独构成完整客户端。
原始 ioctl 调用需要注意什么
获取引用和发送 transaction 必须使用同一个 Binder 连接。通过 libbinder 获取 handle 后,如果再独立 open("/dev/binder", ...) 得到另一个 fd,就会创建另一份 binder_proc 状态。即使仍在同一个 Linux 进程中,也不能直接复用前一个连接的 handle;不同 Binder context 之间同样不能复用。
复制 handle 数值不会保留引用。如果要在释放接收缓冲区后继续使用它,需要先按 Binder 引用协议取得强引用,例如发送并处理 BC_ACQUIRE,使用结束后再通过 BC_RELEASE 释放。完整实现还需要处理相关引用通知和 BC_FREE_BUFFER。使用 libbinder 时,通常由库维护这些状态。引用及缓冲区命令定义
原代码中的两处设置也需要结合协议检查:
tr.code = 0没有通用的“调用服务”含义。应使用目标服务定义的方法编号,并按其 Parcel 格式编码参数。bwr.read_size = 0只提交写入。ioctl()返回成功后,仍需读取并处理BR_REPLY、BR_DEAD_REPLY、BR_FAILED_REPLY等结果;BR_TRANSACTION_COMPLETE也不代表远端方法已经执行成功。
服务端还必须运行 Binder 读取循环来处理 transaction,注册和访问服务也需要满足设备上的权限策略。有效 handle 只能确定路由目标,不能替代服务协议或访问权限。
备注:内容仅供参考。
