Pull Request已成功合入, 合并人@openharmony_ci
(感谢 denganliang 的贡献)static-check


感谢提交 Pull Requests !此PR未通过DCO校验。
校验失败可能原因:
1. 未签署“DCO协议”(开发者原创声明协议),在线签署、查看签署状态。
2. Commits 中未包含 Signed-off-by信息,参考FAQ处理。
修复上述问题后,在PR的评论框输入“check dco” ,单击”评论”,系统将再次进行DCO校验。
当前检测到如下Commits的Signed-off-by邮箱未签署DCO协议:
Thanks for submitting a pull request. This pull request has not passed the DCO check.
Possible causes:
1. You have not signed the Developer Certificate of Origin (DCO). Sign the DCO and check DCO status.
2. The commits do not contain the Signed-off-by information. To resolve this issue, see FAQs.
After resolving the preceding issues, enter check dco in the comment box of this pull request and click Comment. The system will check DCO status again.
The Signed-off-by emails in the following commits have not signed the DCO:


您好,Committer @mzyan @Lutao98 ,请分配检视人员检视该PR,可以通过命令"assign [@someone_id]"分配检视人员,也可以直接评论"assign"分配给自己进行检视。
Hello, Committer @mzyan @Lutao98 . Please assign someone to review the PR. You can assign a reviewer by using the command "assign [@someone_id]", or you can comment "assign" to review the PR by yourself.


hidumper User 版 -s SA Dump 无输出:根因分析与输出通道化改造方案
| 项 | 内容 |
|---|---|
| 文档状态 | 设计评审稿(含代码级实现方案) |
| 日期 | 2026-09-08 |
| 问题单现象 | User 版本 hidumper -s BatteryService -a -i | grep capacity 无输出 |
| 涉及仓 | base/hiviewdfx/hidumper(主)、base/security/selinux_adapter(策略)、foundation/communication/ipc(机制依赖,不改)、`kernel/linux/linux-5.10 |
| 真机 | rk3568,OpenHarmony 7.0.0.41,const.debuggable=1(debug 版策略),SELinux Enforcing,root(hdcd/sh 运行于 u:r:su:s0) |
1. 问题与结论摘要
现象:User 版本执行 hidumper -s BatteryService -a -i | grep capacity 抓不到 capacity 行。
根因(代码 + 策略 + 内核三方佐证,详见 §3/§4):
输出 fd 是 shell 创建的管道(SELinux 标签 u:r:sh:s0),经两层 IPC 原样转发给 SA。
OpenHarmony 内核在 binder fd 传递时按接收方做 SELinux 检查(selinux_binder_transfer_file),
接收方必须对 fd 创建者域同时具备 fd use 与 fifo_file write。而这些针对 sh 的规则
全部包在 developer_only() 宏中,User 版策略编译时被整体剥除;powermgr → sh 的规则更是任何版本都不存在。
因此 User 版下第一跳(client→hidumper_service)即被拒绝,SA 输出无从谈起。
修复方向(对齐 Android dumpsys 的管道中转模型,详见 §7 起):
由 hidumper_service 创建输出管道 P,SA 直接写 P(现成策略 use_hidumper 在 User 版有效),
hidumper 客户端从 IPC 应答中取回 P 读端,泵回 stdout。SA 侧零改动;仅新增 2 条窄 SELinux 规则。
2. 现状完整链路(代码级)
命令:hidumper -s BatteryService -a -i | grep capacity
sh 创建管道(`|`),标签 = sh 进程域(User版 u:r:sh:s0;本机debug为 u:r:su:s0)
└→ hidumper 客户端,fd1 = 管道写端
client/native/main.cpp:41 Main(argc, argv, STDOUT_FILENO)
client/native/dump_client_main.cpp:67 dumpManagerClient.Request(args, outFd)
services/native/src/dump_manager_client.cpp:68 proxy->Request(args, outfd)
services/zidl/src/dump_broker_proxy.cpp:38,41 WriteFileDescriptor(outfd) → IPC第1跳
└→ hidumper_service(SA 1212,按需加载,120s 空闲卸载)
services/zidl/src/dump_broker_stub.cpp:67 ReadFileDescriptor
services/native/src/dump_manager_service.cpp:178-183 ohos.permission.DUMP 校验(:202-211)
services/native/src/dump_manager_service.cpp:195,574-591 outfd 存入 RawParam
services/native/src/raw_param.cpp:34-39,76-79 outfd_ 保存 / GetOutputFd()
services/native/src/dump_manager_service.cpp:649-675 RequestMain 同步内联执行(占住IPC线程)
└→ 命令解析与执行器
frameworks/native/src/manager/dump_implement.cpp:900-903 -s → isDumpSystemAbility_
frameworks/native/src/manager/dump_implement.cpp:442-444 SA 名收集
frameworks/native/src/manager/dump_implement.cpp:849-853 -a "-i" → abilitieArgs_
frameworks/native/dump_utils.cpp:53,106 "BatteryService" → POWER_MANAGER_BATT_SERVICE_ID(3302)
frameworks/native/src/executor/sa_dumper.cpp:63 outputFd_ = GetOutputFd()(非zip)
frameworks/native/src/executor/sa_dumper.cpp:85 sam->CheckSystemAbility(id)
frameworks/native/src/executor/sa_dumper.cpp:93-94 service 先写节标题到同一 fd
frameworks/native/src/executor/sa_dumper.cpp:95 sa->Dump(outputFd_, args_) → IPC第2跳
└→ IPC 框架(foundation/communication/ipc)
ipc/native/src/core/framework/source/ipc_object_proxy.cpp:139-146
Dump(): WriteFileDescriptor(fd)(:143) + DUMP_TRANSACTION,TF_SYNC 同步(:142)
ipc/native/src/core/framework/source/ipc_object_stub.cpp:158-172
OnRemoteDump(): ReadFileDescriptor(:161) → 调 SA 虚函数 Dump(:165) → close(:167)
└→ BatteryService(SA 3302,宿主进程 powermgr)
base/powermgr/battery_manager/sa_profile/3302.json "process": "powermgr"
base/powermgr/battery_manager/services/native/src/battery_service.cpp:1013 Dump(fd,args)
base/powermgr/battery_manager/services/native/src/battery_dump.cpp:66
dprintf(fd, "capacity: %d \n", capacity) ← 目标输出的唯一产地
非 SA 输出路径(节标题、--mem 等)同样直写该 fd:
frameworks/native/src/executor/fd_output.cpp:92-108(WriteToFd → write(rawParamFd,...) + fsync)。
真机拓扑实证(本机 debug 版,2026-09-05 采集)
用 hidumper --mem -s ...(约 5s 慢窗口)轮询 /proc/<pid>/fd,捕获同一管道 inode 贯穿三进程:
T=pipe:[683482] ← sh(su) 创建
client pid=31770 u:r:hidumper:s0 fd=1,5 → pipe:[683482] (fd5 为 IPC 发送前 dup)
service (按需拉起) u:r:hidumper_service:s0 fd=7,8 → pipe:[683482]
powermgr pid=627 u:r:powermgr:s0 fd=19,20 → pipe:[683482] (BatteryService dump 期间)
- 第 1 跳 51/51 次采样命中;第 2 跳窗口仅 ~10ms,用慢 dump 拉长后捕获。
- 全程无
fd/fifo_file类 AVC denied(debug 策略放行),与"User 版才会拒"的判断互为镜像。 - 其他实测:warm 全量
-s≈100ms / 79KB;--mem≈5s;| head -2后 service 不崩(pid 不变);
hidumper_service 空闲时仅 7 个 fd,按需加载 pid 会变化。
3. SELinux 判定机制(内核依据)
kernel/linux/linux-5.10/security/selinux/hooks.c:2053-2091(linux-6.6 同,:2053 起):
static int selinux_binder_transfer_file(const struct cred *from, const struct cred *to, struct file *file)
{
u32 sid = cred_sid(to); // :2057 检查主体 = 接收方(与上游Linux查发送方不同)
...
if (sid != fsec->sid) { // :2067 fd创建者上下文
rc = avc_has_perm(sid, fsec->sid, SECCLASS_FD, FD__USE, &ad); // :2068-2071 检查①
...
}
...
isec = backing_inode_security(dentry);
return avc_has_perm(sid, isec->sid, isec->sclass, file_to_av(file), &ad); // :2087-2090 检查②
}
- 检查①:接收方需
allow <接收方> <fd创建者域>:fd use - 检查②:接收方还需对管道 inode(标签=创建者域)具备访问权限;写端 fd 经
file_to_av(hooks.c:1977-1988,FMODE_WRITE→FILE__WRITE)即fifo_file write - 注册点:hooks.c:7015
LSM_HOOK_INIT(binder_transfer_file, ...) - 推论:一个 fd 能否被某进程写,取决于"谁创建了它",与中间转发者无关。
写普通 write() 时同样按管道 inode 标签(创建者域)检查 fifo_file write。
4. 策略现状矩阵(base/security/selinux_adapter)
4.1 构建变体门控
sepolicy/base/public/glb_te_def.spt:65define('developer_only', ifelse(build_with_developer, 'enable', $1, ))- 同文件 :86
debug_only(build_with_debug) scripts/build_policy_api.py:135-140:仅特定构建注入-D build_with_developer=enable- User 版:developer_only / debug_only 块整体剥除
4.2 与本案相关的规则清单
| 规则 | 位置 | 门控 | User版 |
|---|---|---|---|
allow $1 hidumper_service:fd use + fifo_file write(宏 use_hidumper,授予 { sadomain hdfdomain }) |
ohos_policy/hiviewdfx/hidumper/system/hidumper_service.te:14-17,24 |
无 | ✅ 有效 |
allow powermgr hidumper_service:fd use / fifo_file write(重复授权) |
ohos_policy/powermgr/power_manager/public/powermgr.te:163-166 |
无 | ✅ |
allow hidumper_service hidumper:fd use |
hidumper/system/hidumper_service.te:30 |
无 | ✅ |
allow domain self:fd use / self:fifo_file rw_file_perms |
base/public/domain.te:20,22 |
无 | ✅ |
allow hidumper devpts:chr_file { read write }(tty) |
hidumper/system/hidumper.te:24 |
无 | ✅ |
allow hidumper hdcd:fd use / fifo_file write |
hidumper/system/hidumper.te:26-27 |
无 | ✅ |
allow hidumper hidumper_file:file { write open read append } |
hidumper/system/hidumper.te:83 |
无 | ✅ |
allow hidumper_service sh:fd use + sh:fifo_file write(含AVC注释) |
hidumper/system/hidumper_service.te:18-23 |
developer_only | ❌ 剥除 |
allow hidumper sh:fd use |
hidumper/system/hidumper.te:85-87 |
developer_only | ❌ |
allow sadomain hidumper:fd use、devpts、hdcd、hidumper_file write |
hidumper/system/sadomain.te:14-19 |
developer_only | ❌ |
allow sadomain su:fd use / su:fifo_file write(root调试机放行链) |
sadomain.te:20-23 |
developer_only 内嵌 debug_only | ❌(本机debug版✅,故本机可复现"正常") |
allow hidumper_service devpts / proc_file / hidumper_file:file ... |
hidumper/system/hidumper_service.te:59-244 |
developer_only | ❌ |
powermgr → sh:fd use / sh:fifo_file write |
— | — | 任何版本都不存在 |
域与迁移:base/public/type.te:36(powermgr∈sadomain)、:52(hidumper_service∈sadomain)、:81(sh∈develop_domain);
hidumper/public/type.te:18(hidumper∈sadomain)、:28 与 developtools/hdc/system/sh.te:156、su.te:238(sh/su→hidumper 域迁移)。
SA 标签:base/public/service_contexts:167(3302→sa_powermgr_battery_service)。
4.3 User 版逐跳失败点
| 跳 | 接收方 | 所需规则(fd创建者=sh) | User版 |
|---|---|---|---|
| ① client→service | hidumper_service | sh:fd use+sh:fifo_file write |
❌ developer_only → 第一跳即断(fd 安装失败,dump_broker_stub.cpp:67-70 返回 ERROR_READ_PARCEL) |
| ② service→SA | powermgr | sh:fd use+sh:fifo_file write |
❌ 任何版本不存在 |
| ③ SA 写管道 | powermgr | sh:fifo_file write |
❌ |
③' service 写节标题(sa_dumper.cpp:93) |
hidumper_service | sh:fifo_file write |
❌ |
| 直连 tty 场景 | service/SA | devpts:chr_file write |
❌(:73、sadomain.te:15 均 developer_only) |
即 User 版 hidumper -s 路径整体不可用(管道、tty 皆断),grep capacity 为空只是表象。
客户端连 request error(dump_client_main.cpp:70)都写不出去(hidumper sh:fifo_file write 不存在),
呈现"完全静默"。旁证:User 版帮助文本主动隐藏 --mem(dump_implement.cpp:955-965,IsUserMode 判
const.debuggable==0,dump_utils.cpp:444-449)。
5. Android dumpsys 参照
⚠️ 佐证边界:本地树无 AOSP 源码且环境无外网(android.googlesource.com 拉取失败),
本节为公开 AOSP 代码的文件/函数级结论,无行号。需行号级佐证请补充 AOSP 源码树。
重点文件:frameworks/native/cmds/dumpsys/dumpsys.cpp、frameworks/native/libs/binder/BpBinder.cpp、
utils/binder/Binder.cpp、system/sepolicy(dumpsys.te)。
- 获取服务信息:
defaultServiceManager()->checkService(name)取 binder;-l用listServices();
binder->dump(fd, args)经 Parcel 携带 fd 发 DUMP_TRANSACTION;服务端BBinder::onTransact
读出 fd 调 service 的dump(fd,args)虚函数。
—— 与 OpenHarmony 逐点对应:sa_dumper.cpp:85,95↔ checkService/dump;
ipc_object_proxy.cpp:139-146↔ BpBinder::dump;ipc_object_stub.cpp:158-172↔ BBinder::onTransact。 - fd 创建位置:现代 dumpsys 在自身进程内
pipe2()创建管道,把写端交给 service,
自己持读端 poll 读取并转写 stdout,同时实现 dump 超时(防 service 挂死)。
service 永不接触 shell 的 stdout。 - SELinux 配套:dumpsys 有独立域(shell→dumpsys 域迁移),管道标签为 dumpsys 域,
各 service 域按策略获得对其写权限——与 OpenHarmonyuse_hidumper预埋的
"SA 写 hidumper_service 管道"完全同构。
本方案即该模型在 OpenHarmony 的落地:hidumper_service ↔ dumpsys(创建管道方),
hidumper 客户端 ↔ dumpsys 的读端泵。
6. 方案选型
| 方案 | 结论 | 原因 |
|---|---|---|
| A. service 建管道 + 客户端泵回(选定) | ✅ | SA 侧策略现成(use_hidumper 非门控);仅 2 条窄规则;SA/executor 零改动;顺带消除"调用方递任意 fd 给所有 SA"的注入面 |
B. 纯策略:给 sadomain 授 sh:fd use+sh:fifo_file write |
❌ | 规则极宽(全部 SA 可写 shell 管道);保留 fd 注入面;仍需为 client/service 跳去门控,净增规则更多 |
| C. IPC reply parcel 直接回传内容 | ❌ | parcel 大小限制,-s 全量 79KB+、--mem MB 级会截断 |
| D. 文件中转(/data/log/hidumper) | ❌ | User 版 hidumper_file:file write 本身 developer_only(hidumper_service.te:88-89);轮询/清理/隐私问题 |
| E. memfd 替代管道 | ❌ | 权限类为 file 而非 fifo_file,use_hidumper 现成授权用不上,需全新规则 |
| F. 客户端(hidumper域)建管道给 SA | ❌ | SA 对 hidumper:fifo_file write 无任何授权(fd use 也仅 developer_only,sadomain.te:18) |
关键澄清:"由 hidumper 创建 fd" 中的 hidumper 必须是 hidumper_service(SA 1212)进程——
SELinux 管道标签=创建者域,只有 hidumper_service 标签的管道对全部 SA 有现成写授权。
7. 选定方案详细设计
7.1 架构
┌──────────── hidumper_service (SA 1212) ────────────┐
│ ②pipe2(P);写端打FDTAG入RawParam │
SA(BatteryService等) ──写──> P写端 <──写── 各executor(FDOutput/节标题/进度) │
│ ③detached worker 跑 RequestMain;结束CloseOutputFd │
└───────────────┬────────────────────────────────────┘
①新IPC RequestChannel(args) │ reply 带回 P读端(WriteFileDescriptor)
hidumper客户端 <────────────────────────┘
④主线程泵: poll+read(P读端) → write(stdout) 直到 EOF/超时/EPIPE
⑤stdout = sh管道(| grep) 或 tty
- 必须异步:同步执行则客户端拿不到读端,64KB 管道写满即死锁。
- 必须 dup:收端 parcel 析构会
ClearFileDescriptor关闭收到的 fd
(message_parcel.cpp:118,285-298;binder_invoker.cpp ReadFileDescriptor返回裸 handle;
ipc_file_descriptor.cpp:44-47析构不关)。发端WriteFileDescriptor内部 dup
(message_parcel.cpp:245),写完 reply 即可关原件——成熟范例:
base/theme/wallpaper_mgr/services/src/wallpaper_service.cpp:2299-2308、
base/hiviewdfx/hiview/plugins/faultlogger/service/idl/src/faultlog_query_result_stub.cpp:63-72。
7.2 权限矩阵(改造后)
| 环节 | 所需规则 | 状态 |
|---|---|---|
| SA 收/写 P(service 创建) | sadomain hidumper_service:fd use + fifo_file write |
✅ 已有(use_hidumper) |
| service 自用/自传 P | self:fd use / self:fifo_file rw |
✅ 已有(domain.te:20,22) |
| 客户端收 P 读端 | hidumper hidumper_service:fd use |
✅ 已有(hidumper∈sadomain) |
| 客户端读 P | hidumper hidumper_service:fifo_file read |
⭐ 新增#1 |
| 客户端写 sh 管道 | hidumper sh:fifo_file write |
⭐ 新增#2 |
| 客户端写 tty / hdcd 管道 | devpts :24 / hdcd :26-27 | ✅ 已有 |
| debug 版写 su 管道 | sadomain su:*(sadomain.te:20-23) |
✅ 已有 |
neverallow 预筛:sh∈develop_domain、双方∈domain,均为 domain.te:157 允许的 fifo 目标类型,无冲突;
最终以 hb build selinux_adapter -i 编译验证(sepolicy/AGENTS.md 流程)。
7.3 fd 生命周期表
| fd | 创建 | FDTAG | 传递 | 关闭者 |
|---|---|---|---|---|
| P写端 | service pipe2 |
✅ | 存入 RawParam.outfd_ |
CloseOutputFd(raw_param.cpp:84-88,现有) |
| P读端原件 | service pipe2 |
❌(素fd,stub 可素close) | reply.WriteFileDescriptor(内部dup) |
stub 写完 reply 即 close |
| P读端(parcel dup) | WriteFileDescriptor 内部 | ❌ | binder 转移 | service 端 reply parcel 析构自动关 |
| P读端(客户端安装) | binder 安装 | ❌ | 客户端 dup() |
客户端 reply parcel 析构自动关 |
| channelFd(客户端dup) | 客户端 dup |
❌ | 泵循环使用 | 泵结束 close |
7.4 兼容性
DumpBroker::Request(args,outfd)旧接口原样保留:hiprofiler 等库调用方
(dump_manager_service.cpp:51,179)零影响。- OTA 混部:新 client+旧 service → 旧 stub 走 default 分支返回
IPC_STUB_UNKNOW_TRANS_ERR
(ipc_object_stub.cpp:147-153)→ 传输级错误 → 客户端自动回退旧链路;
旧 client+新 service → 旧路径未动。 - SA 侧
Dump(fd,args)接口不变,BatteryService 等全部无感。 --zip:输出走文件、进度条走 P(raw_param.cpp:201-207),客户端泵转发,行为不变。
8. 并发场景分析
8.1 新方案并发核查
| # | 场景 | 行为 | 依据/措施 |
|---|---|---|---|
| A | 多客户端并发(≤REQUEST_MAX=5,dump_manager_service.cpp:53) | 每请求独立管道+独立 worker;共享态均有锁:requestRawParamMap_(:576,599)、optind/mutexCmdLock_(dump_implement.cpp:206)、采集单例 timeIntervalMutex_(memory_info.cpp:470) | 与现状(5 个 IPC 线程各跑 RequestMain)等价,无新竞态 |
| B | 单请求内多写者(executor 标题/数据、SA 直写、进度) | 同一 worker 线程顺序发生;SA dump 为同步调用(sa_dumper.cpp:95,TF_SYNC) | 天然保序,与现状一致 |
| C | 客户端线程模型 | 泵在主线程(IPC 毫秒级返回,无需泵线程);SIGINT 嵌套 Main(main.cpp:25-34)自建独立 channel,_exit 语义同现状 |
双泵并发写 stdout 的交织可能性=现状,无恶化 |
| D | worker 与服务生命周期 | DelayUnloadTask 在同步段投递(:193);OnIdle 判 GetRequestSum(:117-124);OnStop cancel+等3s(:96-106);进程卸载退出时 P 写端随进程关闭→客户端 EOF 干净收尾 | worker detach + 捕获 shared_ptr |
| E | 背压/慢消费者 | 客户端 write 阻塞→P 满→worker/SA 阻塞,纯内核流控;阻塞点从 binder IPC 线程移到普通 worker | 改善:现状 5 个长 dump 占死 5 个 IPC 线程(RequestMain 内联 :649-653),饿死 CountFdNums 等接口 |
| F | 泵 idle 超时 vs --mem -t N 长静默(memory_info.cpp:477-484) |
超时取宽(600s,可调);配合 §9-10 的"写失败退出循环"修复后,读端消失→EPIPE→循环自停,超时仅剩兜底 | Android dumpsys 同为超时兜底(DEFAULT_DUMP_TIMEOUT) |
8.2 既有并发缺陷(非本方案引入;处置见 §9)
| # | 缺陷 | 证据 | 处置 |
|---|---|---|---|
| 1 | ClientDeathRecipient 死代码:创建(raw_param.cpp:50-52)但全仓无 AddDeathRecipient 注册 → 客户端崩溃 dump 不取消;-t 循环 while(g_isDumpMem)(memory_info.cpp:477)不检查写结果,客户端死后永动 |
grep 验证(仅 dump_manager_cpu_client.cpp:75 注册过别的对象) | 纳入本次:EPIPE→Cancel(FDOutput)+ 写失败退出(-t 循环)。异步化后死亡通知本就不可用,此修复为必需;现状同样无取消能力,无回退 |
| 2 | REQUEST_MAX TOCTOU:GetRequestSum 锁外(:184)、插入锁内(:576) → 可冲到 6 | :184-195 vs :574-591 | 纳入本次:锁内权威判定(AddRequestRawParam 返回 nullptr) |
| 3 | MemoryInfo 单例 rawParamFd_ 覆盖竞态:并发两个 --mem -t 输出串管道 |
memory_info.cpp:471 | 既有缺陷,新旧链路都会踩,另立 issue |
| 4 | 旧接口收端 fd 双关闭隐患:CloseOutputFd(fdsan) 后 data parcel 析构再素 close 同号 fd(message_parcel.cpp:295-297),fd 复用时误关无辜 fd | IPC 源码核查 | 新链路以 dup 规避;旧链路修复列 PR-3 |
9. 代码实现(PR-2,10 个文件)
9.1 utils/native/include/dump_errors.h(:30 后追加)
enum DumperError {
ERROR_WRITE_PARCEL = DUMPER_SERVICE_ERR_OFFSET,
ERROR_READ_PARCEL,
ERROR_GET_SYSTEM_ABILITY_MANAGER,
ERROR_GET_DUMPER_SERVICE,
ERROR_ADD_DEATH_RECIPIENT,
ERROR_REQUEST_CHANNEL_TRANSPORT // 传输级失败(含旧service不认识新code),调用方据此回退旧链路
};
9.2 interfaces/native/innerkits/include/hidumper_service_ipc_interface_code.h(枚举末尾追加,保持既有 wire code)
enum class HidumperServiceInterfaceCode {
DUMP_REQUEST_FILEFD,
SCAN_PID_OVER_LIMIT,
COUNT_FD_NUMS,
SCAN_ORPHAN_VNODE_OVER_LIMIT,
DUMP_REQUEST_CHANNEL
};
9.3 interfaces/native/innerkits/include/idump_broker.h(:36 后追加)
// Used for channel-based dump request: service creates the output pipe and
// returns its read end via channelFd (owned by caller, must close).
// return: = 0 OK; < 0 Error;
virtual int32_t RequestChannel(std::vector<std::u16string> &args, int &channelFd) = 0;
9.4 dump_broker_proxy.h(:30 旁声明)+ services/zidl/src/dump_broker_proxy.cpp(新增,#include <unistd.h>)
int32_t DumpBrokerProxy::RequestChannel(std::vector<std::u16string> &args, int &channelFd)
{
channelFd = -1;
sptr<IRemoteObject> remote = Remote();
if (remote == nullptr) {
return ERROR_REQUEST_CHANNEL_TRANSPORT;
}
MessageParcel data;
MessageParcel reply;
MessageOption option;
if (!data.WriteInterfaceToken(DumpBrokerProxy::GetDescriptor())) {
return ERROR_REQUEST_CHANNEL_TRANSPORT;
}
if (!data.WriteString16Vector(args)) {
return ERROR_WRITE_PARCEL;
}
int res = remote->SendRequest(static_cast<int>(HidumperServiceInterfaceCode::DUMP_REQUEST_CHANNEL),
data, reply, option);
if (res != ERR_OK) { // 旧service走stub default返回IPC_STUB_UNKNOW_TRANS_ERR,落到这里
DUMPER_HILOGE(MODULE_ZIDL, "error|SendRequest channel error code: %{public}d", res);
return ERROR_REQUEST_CHANNEL_TRANSPORT;
}
int32_t ret = -1;
if (!reply.ReadInt32(ret)) {
return ERROR_READ_PARCEL;
}
int rawFd = reply.ReadFileDescriptor();
if (rawFd >= 0) {
channelFd = dup(rawFd); // 必须dup:reply析构时ClearFileDescriptor会close rawFd(message_parcel.cpp:118)
if (channelFd < 0) {
DUMPER_HILOGE(MODULE_ZIDL, "dup channel fd failed, errno:%{public}d", errno);
}
}
return ret;
}
9.5 dump_broker_stub.h(:34 旁声明)+ services/zidl/src/dump_broker_stub.cpp
OnRemoteRequest switch(:47-51 后)加 case:
case static_cast<int>(HidumperServiceInterfaceCode::DUMP_REQUEST_CHANNEL): {
DUMPER_HILOGD(MODULE_ZIDL, "debug|RequestChannelStub");
ret = RequestChannelStub(data, reply);
break;
}
新增实现(P读端不打 fdsan 标,素 close):
int32_t DumpBrokerStub::RequestChannelStub(MessageParcel &data, MessageParcel &reply)
{
std::vector<std::u16string> args;
if (!data.ReadString16Vector(&args)) {
return ERROR_READ_PARCEL;
}
int readFd = -1;
int32_t res = RequestChannel(args, readFd);
if (!reply.WriteInt32(res)) {
if (readFd >= 0) {
close(readFd);
}
return ERROR_WRITE_PARCEL;
}
if (readFd >= 0) {
if (!reply.WriteFileDescriptor(readFd)) { // 内部dup,parcel持副本并于发送后自清理
close(readFd);
return ERROR_WRITE_PARCEL;
}
close(readFd); // 本端原件即刻关闭(wallpaper_service.cpp:2299-2308 同款模式)
}
return ERR_OK;
}
9.6 services/native/include/dump_manager_service.h + services/native/src/dump_manager_service.cpp
头文件 public(:53 旁):
// Used for channel-based dump request
int32_t RequestChannel(std::vector<std::u16string> &args, int &readFd) override;
头文件 private(:98 旁):
void ReportRequestError(std::vector<std::u16string> &args,
const int32_t &errorCode, const std::string &errorMsg);
cpp 匿名命名空间(:58 旁):
constexpr size_t PIPE_FDS_NUM = 2;
constexpr int PIPE_READ_END = 0;
constexpr int PIPE_WRITE_END = 1;
(a) OnStart(:79 前),并 #include "dump_utils.h":
DumpUtils::IgnorePipeQuit(); // 客户端提前退出时写P得EPIPE,防SIGPIPE(dump_utils.cpp:256现成)
(b) HandleRequestError 拆分(:142-165):
void DumpManagerService::HandleRequestError(std::vector<std::u16string> &args, int outfd,
const int32_t& errorCode, const std::string& errorMsg)
{
fdsan_exchange_owner_tag(outfd, 0, FDTAG);
fdsan_close_with_tag(outfd, FDTAG);
ReportRequestError(args, errorCode, errorMsg);
}
void DumpManagerService::ReportRequestError(std::vector<std::u16string> &args,
const int32_t& errorCode, const std::string& errorMsg)
{
// 原 :147-164 主体原样搬入(弹ppid、拼命令串、ReportCmdUsage)
}
(c) AddRequestRawParam 修 TOCTOU(:574-591):
std::shared_ptr<RawParam> DumpManagerService::AddRequestRawParam(std::vector<std::u16string> &args, int outfd)
{
unique_lock<mutex> lock(mutex_);
if (requestRawParamMap_.size() >= REQUEST_MAX) { // 锁内权威判定(:184的锁外检查保留作快速失败)
DUMPER_HILOGE(MODULE_SERVICE, "request sum reached max in lock.");
if (outfd > -1) {
fdsan_exchange_owner_tag(outfd, 0, FDTAG);
fdsan_close_with_tag(outfd, FDTAG);
}
return nullptr;
}
... // 其余原样(requestId 生成、身份快照、插入 map)
}
Request()(:195-196)加 nullptr 分支(不得再走 HandleRequestError,outfd 已在锁内关闭):
const std::shared_ptr<RawParam> rawParam = AddRequestRawParam(args, outfd);
if (rawParam == nullptr) {
ReportRequestError(args, static_cast<int32_t>(DumpStatus::DUMP_REQUEST_MAX), "request sum reached max");
return DumpStatus::DUMP_REQUEST_MAX;
}
(d) 新增 RequestChannel(置于 Request 之后):
int32_t DumpManagerService::RequestChannel(std::vector<std::u16string> &args, int &readFd)
{
readFd = -1;
if (blockRequest_) {
ReportRequestError(args, static_cast<int32_t>(DumpStatus::DUMP_FAIL), "request has blocked");
return DumpStatus::DUMP_FAIL;
}
if (!started_) {
DUMPER_HILOGE(MODULE_SERVICE, "hidumper_service has stopped.");
ReportRequestError(args, static_cast<int32_t>(DumpStatus::DUMP_FAIL), "request has stopped");
return DumpStatus::DUMP_FAIL;
}
int32_t uid = IPCSkeleton::GetCallingUid(); // 权限/身份必须在IPC线程同步段获取
if (!HasDumpPermission() && uid != HIPORFILER_UID) {
DUMPER_HILOGE(MODULE_SERVICE, "No dump permission, please check!, uid:%{public}d.", uid);
ReportRequestError(args, static_cast<int32_t>(DumpStatus::DUMP_FAIL), "no dump permission");
return DumpStatus::DUMP_FAIL;
}
if (GetRequestSum() == 0) {
DumpLogManager::Init();
}
int pipeFds[PIPE_FDS_NUM] = {-1, -1};
if (pipe2(pipeFds, O_CLOEXEC) != 0) {
DUMPER_HILOGE(MODULE_SERVICE, "pipe2 failed, errno:%{public}d", errno);
ReportRequestError(args, static_cast<int32_t>(DumpStatus::DUMP_FAIL), "create channel failed");
return DumpStatus::DUMP_FAIL;
}
fdsan_exchange_owner_tag(pipeFds[PIPE_WRITE_END], 0, FDTAG); // 写端走RawParam的fdsan契约;读端保持素fd
DelayUnloadTask();
const std::shared_ptr<RawParam> rawParam = AddRequestRawParam(args, pipeFds[PIPE_WRITE_END]);
if (rawParam == nullptr) { // 超限:写端已在AddRequestRawParam内关闭
close(pipeFds[PIPE_READ_END]);
ReportRequestError(args, static_cast<int32_t>(DumpStatus::DUMP_REQUEST_MAX), "request sum reached max");
return DumpStatus::DUMP_REQUEST_MAX;
}
// 异步执行:同步执行会导致客户端拿不到读端、64KB管道写满死锁;
// 不用handler_:单EventRunner线程会把REQUEST_MAX=5的并发串行化
std::thread([this, rawParam]() {
RequestMain(rawParam); // 结尾CloseOutputFd关P写端 → 客户端读到EOF(raw_param.cpp:81-91)
}).detach();
readFd = pipeFds[PIPE_READ_END];
DUMPER_HILOGD(MODULE_SERVICE, "leave|channel request started");
return DumpStatus::DUMP_OK;
}
9.7 interfaces/native/innerkits/include/dump_manager_client.h(:29 旁声明)+ services/native/src/dump_manager_client.cpp
int32_t DumpManagerClient::RequestChannel(std::vector<std::u16string> &args, int &channelFd)
{
channelFd = -1;
if (args.size() < 1) {
DUMPER_HILOGE(MODULE_CLIENT, "args failed.");
return DumpStatus::DUMP_FAIL;
}
for (size_t i = 0; i < args.size(); i++) { // 与Request:43-49相同的参数校验
std::string trimArg = TrimStr(Str16ToStr8(args[i]));
if (strlen(trimArg.c_str()) < 1) {
DUMPER_HILOGE(MODULE_CLIENT, "trimArg empty.");
return DumpStatus::DUMP_FAIL;
}
}
sptr<ISystemAbilityManager> sam = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
if (sam == nullptr) {
DUMPER_HILOGE(MODULE_CLIENT, "sam is nullptr.");
return ERROR_GET_SYSTEM_ABILITY_MANAGER;
}
sptr<IRemoteObject> remoteObject = sam->CheckSystemAbility(DFX_SYS_HIDUMPER_ABILITY_ID);
if (remoteObject == nullptr) {
ErrCode retStart = OnDemandStart(sam, remoteObject);
if (remoteObject == nullptr || retStart != ERR_OK) {
DUMPER_HILOGE(MODULE_CLIENT, "remoteObject is nullptr.");
return ERROR_GET_DUMPER_SERVICE;
}
}
sptr<IDumpBroker> proxy = iface_cast<IDumpBroker>(remoteObject);
if (proxy == nullptr) {
DUMPER_HILOGE(MODULE_CLIENT, "proxy == nullptr");
return DumpStatus::DUMP_FAIL;
}
return proxy->RequestChannel(args, channelFd);
}
9.8 interfaces/native/innerkits/include/dump_client_main.h + client/native/dump_client_main.cpp
头文件 private 区:
int PumpChannelToFd(int channelFd, int outFd, int idleTimeoutMs = CHANNEL_IDLE_TIMEOUT_MS);
cpp 增加 <poll.h> 与匿名命名空间常量:
namespace {
constexpr int PUMP_BUF_SIZE = 64 * 1024;
constexpr int CHANNEL_IDLE_TIMEOUT_MS = 600 * 1000; // 兜底:防个别SA违规dup住P写端致EOF不来;
// 取值大于现实 --mem -t 间隔(Android dumpsys同为超时兜底)
}
Main()(:63-75 段)改造:
std::vector<std::u16string> args;
SetCmdArgs(argc, argv, args);
auto& dumpManagerClient = DumpManagerClient::GetInstance();
DumpUtils::IgnoreStdoutCache();
DumpUtils::IgnorePipeQuit(); // 新增:泵写stdout时grep/head可能提前退出(dump_utils.cpp:256现成函数)
int channelFd = -1;
int32_t ret = dumpManagerClient.RequestChannel(args, channelFd);
if (channelFd < 0 && ret == ERROR_REQUEST_CHANNEL_TRANSPORT) {
ret = dumpManagerClient.Request(args, outFd); // 旧service:回退旧链路,OTA混部安全
} else if (channelFd >= 0) {
(void)PumpChannelToFd(channelFd, outFd);
close(channelFd);
}
if (ret < DumpStatus::DUMP_OK) {
if (ret != DumpStatus::DUMP_INVALID_ARG) {
(void)dprintf(outFd, "request error\n"); // 泵结束后再写,避免交织
DUMPER_HILOGE(MODULE_SERVICE, "request error, ret: %{public}d.", ret);
}
return ret;
}
return ret;
新增泵函数(主线程同步泵;IPC 已毫秒级返回,无需客户端线程):
int DumpClientMain::PumpChannelToFd(int channelFd, int outFd, int idleTimeoutMs)
{
std::vector<char> buf(PUMP_BUF_SIZE);
struct pollfd pfd = {channelFd, POLLIN, 0};
while (true) {
int pollRet = poll(&pfd, 1, idleTimeoutMs);
if (pollRet == 0) {
LOG_ERR("dump channel idle timeout.\n");
return DumpStatus::DUMP_FAIL;
}
if (pollRet < 0) {
if (errno == EINTR) {
continue;
}
LOG_ERR("poll channel failed, errno:%d.\n", errno);
return DumpStatus::DUMP_FAIL;
}
ssize_t n = read(channelFd, buf.data(), buf.size());
if (n == 0) {
return DumpStatus::DUMP_OK; // EOF:service已CloseOutputFd,dump完成
}
if (n < 0) {
if (errno == EINTR) {
continue;
}
return DumpStatus::DUMP_FAIL;
}
ssize_t off = 0;
while (off < n) {
ssize_t w = write(outFd, buf.data() + off, n - off);
if (w > 0) {
off += w;
continue;
}
if (w < 0 && errno == EINTR) {
continue;
}
return DumpStatus::DUMP_OK; // EPIPE等:下游(grep/head)已退出,安静收尾
}
}
}
client/native/main.cpp SIGINT 路径零改动(嵌套 Main 自动走新链路)。
9.9 frameworks/native/src/executor/fd_output.cpp WriteToFd(:92-108)——EPIPE→Cancel
if (rawParamFd > -1) {
if (write(rawParamFd, str.c_str(), strlen(str.c_str())) == -1) {
DUMPER_HILOGE(MODULE_COMMON, "write to rawParamFd failed, errno: %{public}d", errno);
if (errno == EPIPE && ptrReqCtl_ != nullptr) {
ptrReqCtl_->Cancel(); // 读端已消失,终止dump(DumpDatas在dump_implement.cpp:1097检查IsCanceled)
}
}
9.10 frameworks/native/src/executor/memory/memory_info.cpp WriteStdout(:317-320)——-t 循环写失败退出
void MemoryInfo::WriteStdout(const std::string& s)
{
if (write(rawParamFd_, s.c_str(), s.size()) == -1) {
DUMPER_HILOGE(MODULE_COMMON, "write to rawParamFd failed, errno: %{public}d", errno);
if (errno == EPIPE) {
g_isDumpMem = false; // 读端已消失,退出GetMemoryInfoByTimeInterval循环(:477)
}
}
}
9.11 改动规模汇总
| # | 文件 | 改动 | ~LOC |
|---|---|---|---|
| 1 | dump_errors.h | 枚举追加 | 1 |
| 2 | hidumper_service_ipc_interface_code.h | 枚举追加 | 1 |
| 3 | idump_broker.h / dump_broker_proxy.h / dump_broker_stub.h | 声明 | 8 |
| 4 | dump_broker_proxy.cpp / dump_broker_stub.cpp | RequestChannel 收发 + dup/关闭语义 | 65 |
| 5 | dump_manager_service.h/.cpp | RequestChannel + worker + IgnorePipeQuit + 错误上报拆分 + TOCTOU | 80 |
| 6 | dump_manager_client.h/.cpp | 客户端包装 | 35 |
| 7 | dump_client_main.h/.cpp | 泵 + 回退 + IgnorePipeQuit | 85 |
| 8 | fd_output.cpp / memory_info.cpp | EPIPE 加固 | 12 |
| 9 | sepolicy hidumper.te | 2 条规则(PR-1) | 4 |
| 10 | test/(UT 追加) | 见 §10 | ~300 |
10. 测试与验证
10.1 UT(现有 fixture 追加、XXXTest0NN 顺延编号、Level1 mock 为主)
| Fixture | 用例 | 断言要点 |
|---|---|---|
| DumpManagerServiceTest | RequestChannel 成功 | reply 带回有效 fd;map 增 1;worker 结束自动清零、P写端关闭(读侧 EOF) |
| 同上 | 无权限 / blocked / 未 started | 返回对应 DumpStatus,readFd=-1,前后 fd 计数一致(无泄漏) |
| 同上 | 并发超限 | 灌满 REQUEST_MAX 后返回 DUMP_REQUEST_MAX,两端 fd 均关闭 |
| 同上 | AddRequestRawParam 锁内上限 | 并发压入不超 5(TOCTOU 回归) |
| DumpClientMainTest | Pump 大数据 | 写端灌 >64KB(如 256KB)→ 泵收全、字节一致(防死锁头号用例;timeout 参数注入小值) |
| 同上 | Pump EPIPE | outFd 指向已关读端的管道 → 安静返回 OK |
| 同上 | Pump idle 超时 | 写端不写不关 → 超时返回 FAIL |
| 同上 | 回退逻辑 | mock 返回 ERROR_REQUEST_CHANNEL_TRANSPORT → 走旧 Request |
| FDOutput / MemoryInfo | EPIPE→Cancel / g_isDumpMem=false | 写失败后 IsCanceled 为真 / 循环退出 |
10.2 编译
./build.sh --product-name rk3568 --build-target hidumper
hb build selinux_adapter -i # 检查 neverallow 违例(sepolicy/AGENTS.md)
python3 -m main run -p phone -t UT -ss hiviewdfx -tp hidumper -ts hidumper_ut
10.3 真机功能矩阵(本机 debug 版即可先行)
hidumper -s BatteryService -a -i | grep capacity→ 有输出hidumper -s全量输出与改造前 diff 一致(79KB 级)hidumper --mem | wc -c→ >64KB 完整、无死锁hidumper -s | head -1→ service 存活、后续请求正常、hilog 可见 Cancel- Ctrl+C(
--mem -t)→ 正常收尾 --zip→ 文件生成、进度条正常- 5 并发(不同 SA + grep)→ 输出互不串扰
- 120s 空闲卸载 → 再拉起正常、fd 计数复原
- 混合:模拟旧 service(回退分支)→ 旧链路行为不变
10.4 User 版验证(结题标准)
- user 变体策略(不带
build_with_developer)+ user 镜像:hidumper -s BatteryService -a -i | grep capacity出结果dmesg | grep -i avc无新增 deniedhidumper -s/--mem直连 tty 亦可出结果(devpts 链路)
- 复测现状基线(改造前 user 版)留存 AVC:预期首条
avc: denied { use } ... scontext=u:r:hidumper_service:s0 tcontext=u:r:sh:s0 tclass=fd
11. 合入顺序与 PR 划分
| PR | 内容 | 依赖 |
|---|---|---|
| PR-1 | sepolicy 2 条规则(§7.2 新增#1#2) | 无(独立、先行无害) |
| PR-2 | §9 全部代码 + §10.1 UT | 与 PR-1 同版本落地(策略先于/随代码) |
| PR-3(跟进,不阻塞) | 旧接口收端 fd 双关闭隐患(§8.2#4);SA 级 dump 超时(需 SADumper 内二级管道 + poll);MemoryInfo 单例 fd 覆盖竞态(§8.2#3,另立 issue);developer_only 遗留 sh 规则清理(hidumper_service.te:18-23 等,待旧接口退役) |
PR-2 之后 |
提交规范:commit 带 Signed-off-by(--signoff,勿手写重复)+ Co-Authored-By: Agent 置于其前;
方法 <50 行(G.FUD.05);裸数字全部常量化;UT 命名 <Fixture>0XX 顺延、不新建 target。
12. 风险与遗留
| 风险 | 等级 | 缓解 |
|---|---|---|
| 64KB 管道死锁(泵未并发) | 高 | 设计保证:IPC 先返回读端、主线程立即泵;UT 大数据用例守门 |
| reply-fd 生命周期(parcel 析构关闭) | 高 | 客户端强制 dup(§7.1);UT 覆盖 |
泵 idle 超时误杀超长 -t 间隔 |
低 | 600s 宽限 + -t 写失败自停修复;常量可调 |
| 异步化丢失死亡通知取消 | 低 | 现状本就未注册(§8.2#1);EPIPE→Cancel 为净改善 |
| SA 挂死阻塞 worker | 中(现状同) | REQUEST_MAX 兜底;PR-3 引入 SA 级超时 |
| 旧新混部 | 低 | 传输级错误自动回退旧链路(§9.8) |
> 文件 重定向在 User 版 |
低 | 客户端写文件需按目标类型另加规则,本次不承诺,文档注明 |
信息缺口(需补充)
- AOSP 源码树或外网:§5 Android 结论升级为行号级佐证。
- User 版设备/镜像:§10.4 结题验证与基线 AVC 采集。
base/systemabilitymgr_samgr不在当前树:POWER_MANAGER_BATT_SERVICE_ID=3302由
service_contexts:167与battery_manager/sa_profile/3302.json交叉佐证,宏定义本体未直查。
附录:证据索引(文件:行号速查)
- 链路:main.cpp:41 / dump_client_main.cpp:66-70 / dump_manager_client.cpp:37-71 / dump_broker_proxy.cpp:22-51 / dump_broker_stub.cpp:60-76 / dump_manager_service.cpp:167-199,649-675 / raw_param.cpp:34-91 / dump_implement.cpp:154-220,849-853,900-903,1086-1136 / sa_dumper.cpp:49-122 / fd_output.cpp:92-108 / dump_utils.cpp:53,106,256-264,383-397,444-449 / ipc_object_proxy.cpp:139-146 / ipc_object_stub.cpp:125-172 / battery_service.cpp:1013 / battery_dump.cpp:62-90 / 3302.json
- 内核:linux-5.10 hooks.c:1977-1988,2053-2091,7015(linux-6.6 hooks.c:2053+ 同)
- IPC fd 语义:message_parcel.cpp:118,238-262,285-298 / ipc_file_descriptor.cpp:44-47 / binder_invoker.cpp(ReadFileDescriptor/WriteFileDescriptor) / wallpaper_service.cpp:2299-2308 / faultlog_query_result_stub.cpp:63-72
- 策略:glb_te_def.spt:65,79,86 / build_policy_api.py:135-140 / domain.te:20,22,157 / type.te(base/public):36,52,81 / hidumper/public/type.te:14-34 / hidumper/system/hidumper_service.te:14-24,30,59-244 / hidumper/system/hidumper.te:24,26-27,83,85-87 / hidumper/system/sadomain.te:14-23 / powermgr.te:163-166 / service_contexts:167,169 / sh.te:27,29,156 / su.te:238 / foundation.te(base/te):46 / hidumper_service.te(base/te):28-32
- 真机:pipe:[683482] 三进程拓扑、su 域、capacity:11、无 fd/fifo AVC、warm -s≈100ms/79KB、--mem≈5s、head -2 存活、裸 hidumper 可用(developer 策略在位旁证)


关联 Issue
变更说明
新增
hidumper --mem <pid> --show-arktsheap命令,通过 DumpMem IPC 并行获取进程主线程的 ArkTS Heap 总大小,拼接在原有内存表格之后输出。frameworks/native/include/executor/memory/dump_arkts_heap_info.h:新增ArktsHeapDumpCallback(独立 IPC 回调,1 秒超时)和DumpArktsHeapInfo类frameworks/native/src/executor/memory/dump_arkts_heap_info.cpp:通过 AppMgrService SA 501 发起 DumpMem(ARKTS_HEAP) IPCframeworks/native/include/common/dumper_opts.h:新增showArktsHeap_字段frameworks/native/src/common/dumper_opts.cpp:初始化/重置/赋值showArktsHeap_frameworks/native/src/manager/dump_implement.cpp:LONG_OPTIONS + ParseLongCmdOption 新增show-arktsheapframeworks/native/include/executor/memory_dumper.h:前向声明 + 新增成员和方法声明frameworks/native/src/executor/memory_dumper.cpp:并行获取 + MergeArktsHeapResult 拼接输出frameworks/native/BUILD.gn:dump_main 新增源文件test/unittest/common/memory_dumper_test.cpp:新增 4 个用例test/unittest/common/dump_arkts_heap_info_test.cpp:新增 2 个用例test/unittest/common/BUILD.gn:新增 DumpArktsHeapInfoTest target测试验证报告
编译验证
base/hiviewdfx/hidumper:bin、base/hiviewdfx/hidumper:service./build.sh --product-name rk3568 --ccache --build-variant root --build-target base/hiviewdfx/hidumper:bin --build-target base/hiviewdfx/hidumper:service单元测试
MemoryDumperTest、DumpArktsHeapInfoTeststart.sh run -p rk3568 -t UT -tp hidumper检查清单
关联 PR(ability_runtime 仓)