listServices枚举服务
listServices() 返回的不是“设备上所有 Binder 对象”,而是当前 ServiceManager 进程 mNameToService 中、dump priority 与查询掩码有交集的服务名。枚举前只做一次 service-manager list 权限检查,不逐项执行 find;输出顺序来自 std::map 的 key 顺序,而不是注册先后或 dump priority 高低。
本文承接 addService注册服务 和 getService查询服务。前文已经说明服务名如何进入 mNameToService、单项查询如何检查 find 权限;本文专门解释批量枚举的输入掩码、所有者、过滤算法、结果顺序和失败行为。本文不展开 dumpsys 如何消费这些名字,也不把 getServiceDebugInfo() 的 PID 信息混入 listServices()。
1. 接口边界
1.1 Java入口
源码文件:frameworks/base/core/java/android/os/ServiceManager.java
相关函数:listServices()
public static String[] listServices() {
try {
return getIServiceManager().listServices(
IServiceManager.DUMP_FLAG_PRIORITY_ALL);
} catch (RemoteException e) {
Log.e(TAG, "error in listServices", e);
return null;
}
}Java 无参入口固定传 DUMP_FLAG_PRIORITY_ALL,目标是获取四种标准 dump priority 的并集。远端异常返回 null,而正常枚举但没有匹配项返回空数组;这两个结果不能合并判断。
1.2 AIDL常量
源码文件:frameworks/native/libs/binder/aidl/android/os/IServiceManager.aidl
相关常量:DUMP_FLAG_PRIORITY_*
const int DUMP_FLAG_PRIORITY_CRITICAL = 1 << 0;
const int DUMP_FLAG_PRIORITY_HIGH = 1 << 1;
const int DUMP_FLAG_PRIORITY_NORMAL = 1 << 2;
const int DUMP_FLAG_PRIORITY_DEFAULT = 1 << 3;
const int DUMP_FLAG_PRIORITY_ALL =
DUMP_FLAG_PRIORITY_CRITICAL |
DUMP_FLAG_PRIORITY_HIGH |
DUMP_FLAG_PRIORITY_NORMAL |
DUMP_FLAG_PRIORITY_DEFAULT;
const int FLAG_IS_LAZY_SERVICE = 1 << 30;priority 是位掩码,不是只能取一个值的枚举。服务注册时可以携带多个位;枚举条件使用按位与,只要有一个查询位重合就保留。FLAG_IS_LAZY_SERVICE 不属于 PRIORITY_ALL,单独设置 lazy 位而没有 priority 的服务不会被 Java 无参入口匹配,这也解释了注册时为何对“未设置 priority”记录 warning。
2. 权限入口
2.1 调用上下文
frameworks/native/cmds/servicemanager/ServiceManager.cppframeworks/native/cmds/servicemanager/Access.cpp
相关函数:ServiceManager::listServices()、Access::canList()
if (!mAccess->canList(mAccess->getCallingContext())) {
return Status::fromExceptionCode(
Status::EX_SECURITY, "SELinux denied.");
}canList() 再把这次 Binder 调用的 security context 转换成 SELinux 的 service_manager:list 判断:
bool Access::canList(const CallingContext& ctx) {
return actionAllowed(ctx, mThisProcessContext,
"list", "service_manager");
}权限目标是 ServiceManager 进程上下文和 service_manager:list,不是每个服务名的 service_contexts 条目。因此拥有 list 权限只说明可以枚举匹配的名字,不说明随后对每个名字都有 find 权限。真正获取 Binder 时仍要经过 getService() 的单项检查。
3. 两次遍历
3.1 容量统计
源码文件:frameworks/native/cmds/servicemanager/ServiceManager.cpp
相关函数:ServiceManager::listServices()
size_t toReserve = 0;
for (auto const& [name, service] : mNameToService) {
(void) name;
if (service.dumpPriority & dumpPriority) {
++toReserve;
}
}
CHECK(outList->empty());
outList->reserve(toReserve);第一次遍历只计算匹配项,用于一次性预留 vector 容量。CHECK(outList->empty()) 是服务端内部调用约束:生成的 Binder stub 应传入空输出容器;若内部错误地复用非空 vector,进程会触发 fatal check,而不是把结果追加到旧内容后面。
3.2 名字写入
for (auto const& [name, service] : mNameToService) {
if (service.dumpPriority & dumpPriority) {
outList->push_back(name);
}
}
return Status::ok();第二次遍历复制名字。mNameToService 的类型是 std::map<std::string, Service>,遍历按字符串 key 的比较顺序进行,所以输出通常是字典序。代码没有按注册时间、UID、PID、lazy 状态或 priority 位重新排序。
3.3 位筛选
| 服务 flags | 查询掩码 | 按位与 | 是否输出 |
|---|---|---|---|
CRITICAL | ALL | 非零 | 是 |
| `HIGH | PROTO` | HIGH | 非零 |
NORMAL | CRITICAL | 0 | 否 |
FLAG_IS_LAZY_SERVICE | ALL | 0 | 否 |
| `DEFAULT | FLAG_IS_LAZY_SERVICE` | ALL | 非零 |
过滤只读取 dumpPriority 字段,不检查 Binder 是否仍存活。正常情况下服务死亡会由 binderDied() 删除表项;如果死亡通知尚未完成,枚举可能短暂观察到旧名字,这也是查询与异步死亡清理之间的时序边界。
4. 状态所有权
4.1 服务表
源码文件:frameworks/native/cmds/servicemanager/ServiceManager.h
相关类型:ServiceManager::Service、ServiceMap
struct Service {
sp<IBinder> binder;
bool allowIsolated;
int32_t dumpPriority;
bool hasClients = false;
bool guaranteeClient = false;
Access::CallingContext ctx;
};
using ServiceMap = std::map<std::string, Service>;
ServiceMap mNameToService;枚举只消费 map key 与 dumpPriority,不会返回 Binder、调用者上下文、client 状态或 allowIsolated。isolated 访问限制属于 getService() 返回 Binder 时的判断,不影响名字是否出现在有权限的 listServices() 调用中。
4.2 更新时机
Hidden 不是服务表中的持久字段,只表示某次查询因为 priority 掩码没有选择该条目。修改查询掩码即可让同一个表项重新出现在结果中;真正的 Missing 需要表项不存在。
5. 失败语义
5.1 三类结果
| 服务端结果 | Java表现 | 含义 |
|---|---|---|
Status::ok() + 非空 vector | 非空数组 | 至少一个匹配项 |
Status::ok() + 空 vector | 空数组 | 有权限,但没有匹配项 |
EX_SECURITY | 调用异常 | 没有 list 权限 |
传输 RemoteException | Java 返回 null | Binder 通信失败 |
服务端权限拒绝由 AIDL 映射成异常;Java 无参包装只捕获 RemoteException,具体生成接口如何映射 Status 仍由 Binder Java 层处理。诊断时不能只比较数组长度,还应观察是否发生 SecurityException 或传输异常。
5.2 清理与恢复
枚举没有取消、重试或后台任务。服务被 addService() 写入后立即参与下一次枚举;同名覆盖会更新其 priority;死亡或显式注销删除后,下一次枚举不再返回该名字。权限策略变化只有在下一次调用 canList() 时生效,不会修改已经返回给调用方的数组。
6. 测试断言
6.1 权限拒绝
源码文件:frameworks/native/cmds/servicemanager/test_sm.cpp
ListServices.NoPermissions 构造 MockAccess,让 canList() 返回 false,然后断言 listServices(ALL) 的 Status 非 ok 且输出 vector 保持空。它证明权限检查发生在填充结果之前。
6.2 顺序与筛选
同一测试文件的 ListServices.AllServices 以 sd、sc、sb、sa 的逆字典序注册四种 priority,再断言输出是 sa、sb、sc、sd。这排除了“按注册顺序返回”的解释,并反向证明 std::map key 顺序。
ListServices.CriticalServices 使用相同四项,只传 DUMP_FLAG_PRIORITY_CRITICAL,断言仅返回 sa。它证明筛选依据是按位掩码。测试没有运行 Java String[] 包装、真实设备 SELinux policy 或服务死亡竞态。
6.3 可执行阅读
rg -n "listServices|DUMP_FLAG_PRIORITY_ALL" \
frameworks/base/core/java/android/os/ServiceManager.java \
frameworks/base/core/java/android/os/ServiceManagerNative.java \
frameworks/native/libs/binder/aidl/android/os/IServiceManager.aidl
rg -n "ServiceManager::listServices|canList|mNameToService|dumpPriority" \
frameworks/native/cmds/servicemanager/ServiceManager.cpp \
frameworks/native/cmds/servicemanager/ServiceManager.h \
frameworks/native/cmds/servicemanager/Access.cpp
rg -n "ListServices" \
frameworks/native/cmds/servicemanager/test_sm.cpp检查枚举缺项时,依次确认调用是否拥有 list 权限、服务是否仍在 mNameToService、注册时是否设置了与查询掩码重合的 priority,以及死亡/注销清理是否已经删除表项。不要先把缺项归因于 find 权限,因为 listServices() 不逐项调用 canFind()。
