Skip to content

listServices枚举服务

追踪 listServices 从 Java 入口到 ServiceManager 服务表,解释 SELinux list 权限、dump priority 位筛选、排序与测试边界。

基于android-17.0.0_r1
AndroidBinderServiceManagerlistServices源码阅读

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

java
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_*

java
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.cpp
  • frameworks/native/cmds/servicemanager/Access.cpp

相关函数:ServiceManager::listServices()、Access::canList()

cpp
if (!mAccess->canList(mAccess->getCallingContext())) {
    return Status::fromExceptionCode(
            Status::EX_SECURITY, "SELinux denied.");
}

canList() 再把这次 Binder 调用的 security context 转换成 SELinux 的 service_manager:list 判断:

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

cpp
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 名字写入 ​

cpp
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查询掩码按位与是否输出
CRITICALALL非零是
`HIGHPROTO`HIGH非零
NORMALCRITICAL0否
FLAG_IS_LAZY_SERVICEALL0否
`DEFAULTFLAG_IS_LAZY_SERVICE`ALL非零

过滤只读取 dumpPriority 字段,不检查 Binder 是否仍存活。正常情况下服务死亡会由 binderDied() 删除表项;如果死亡通知尚未完成,枚举可能短暂观察到旧名字,这也是查询与异步死亡清理之间的时序边界。

4. 状态所有权 ​

4.1 服务表 ​

源码文件:frameworks/native/cmds/servicemanager/ServiceManager.h

相关类型:ServiceManager::Service、ServiceMap

cpp
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 权限
传输 RemoteExceptionJava 返回 nullBinder 通信失败

服务端权限拒绝由 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 可执行阅读 ​

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