文件页内存调试分析
来源:飞书 wiki(obj_token: HJRodPM9do3pXMxxhXDcxcZinrh)
⚠️ 图片下载失败:本文档共 10 张图片(image_token 形如
https://feishu.cn/file/XXX),通过media_download全部返回 403,已按规则替换为> 📷 **图片描述**引用块。
📌 名称:filemap
🔗 小米企业网盘外链:地址 | 密码
Uc26
Filemap 文件页堆栈抓取
adb push filemap_catch /data/local/tmp
adb shell
su # 非 root 手机使用
cd /data/local/tmp/
chmod +x filemap_catch
./filemap_catch -p com.android.systemui -s -d
... # 非自启动应用在执行期间当打印到 drop cache done... 后手动启动应用
# 执行完成后e
exit # su 执行两次
adb pull /data/local/tmp/filemap.perf.data .
adb pull /data/local/tmp/filemap_inode.json .如果有文件被多个进程使用,请考虑停止这些进程,例如:
pm disable com.miui.weather2
p.\m disable com.miui.miwallpaper
lsof | grep libllvm-qgl.so # 这里查看就剩一个 systemui 后再执行命令抓取
./filemap_catch -p com.android.systemui -s -d如果通过 kill 的方式抓当前进程采样较少,可以考虑抓其父进程
如果进程在 stop android 后仍然存在,一般父进程是 init 进程,那 -s stop android 方式抓是无效的,可以用如下方式抓取
# mediaserver64 父进程为 init, -f 指定父进程,-b 指定在 kill 之前就启动 simpleperf
./filemap_catch -p mediaserver64 -k -f 1 -b -d对比机 stop android 抓取或抓取 zyogote64 方式
# 使用最新的 filemap_catch
setsid ./filemap_catch -p zygote64 -s -d &
# android 重启后会断掉 shell,但会在后台抓取,等待一段时间(默认抓 10s)后 pull 即可
# 抓取完成可确认下文件时间是否正常
ls -lah /data/local/tmp/filemap.perf.data可能出现的问题:
- 采样数据为 0 或较少
抓取完成后可通过 Samples recorded: 确认采样数量,数据量越大越好,如果为 0 就是没有采集到,可检查进程是否正常启动
另外,如果进程自身启动较快或者 drop cache 不干净都有可能导致采样点较少或者为 0,可以尝试抓取父进程
simpleperf I cmd_record.cpp:840] Recorded for 10.0996 seconds. Start post processing.
simpleperf I cmd_record.cpp:941] Samples recorded: 0. Samples lost: 0.- filemap_catch 执行卡住一直不动
大概率是进程没有启动,filemap_catch 会遍历 /proc 去搜索进程,如果一直没匹配到就会卡住
- 解析后 so 库内存分配较小或没有
公有的 so 库可能被其它进程先分配内存导致采样不到数据,可抓取 代码执行情况(通过抓 -e cpu-cycles 类型)去对比 so 库函数调用差异
filemap_catch 命令说明
- -p 进程名
- -t perf 抓取时间
- -m 内核态内存大小,如果内核态丢失率高可提高该参数,大小需 2 的 n 次方,如果有 out of memory 错误需降低该参数
- -u 用户态内存大小,如果用户态丢失率高可提高该参数
- -s 通过停止 Android 的方式去抓取,android 系统自启动进程使用,stop android 后仍然存在的进程不可用,默认是 stop 进程抓取
- -d dump inode 文件
- -k 通过杀进程的方式去抓取,自启应用一般会丢失一些数据,可抓多次取采样点(Samples recorded)最大的
- -f 指定父进程名或 pid
- -b 在进程 stop 或 kill 之前执行 simpleperf,与 -f 同时使用
- -c 指定 simpleperf 进程 cpu mask,会绑核和调频,丢失率高时可酌情使用
/data/local/tmp/ # ./filemap_catch -h
Usage: filemap_catch -p <target_process> [-t <duration>] [-m <mem_map>] [-e <events>]
[-f <parent_pid_or_name>] [-b] [-o <outfile>] [-s | -k] [-c <cpu_config>] [-d]
Options:
-p <target_process> Target process name
-t <duration> Duration of perf data collection (default: 10)
-m <mem_map> Memory map size (default: 65536)
-u <user_mem> User buffer size (default: 1024M)
-e <events> Simpleperf events to record (default: filemap:mm_filemap_add_to_page_cache)
-o <outfile> Output perf data file (default: /data/local/tmp/filemap.perf.data)
-d Dump inode file (path: /data/local/tmp/filemap_inode.json)
-s Stop Android before catch (default: stop process)
-k Kill target process before catch
-f <parent_process> Parent process name or pid
-b Run simpleperf before stop or kill, use with -f
-c <cpu_config> Simpleperf CPU affinity config: 4,5_6,7
- Main threads to cpu4,5, worker threads to cpu6,7
-h Print this help messageFilemap 文件页堆栈解析
抓取到文件页 perf.data 后可以通过 report_filemap.exe 解析,如下命令:
simpleperf_report.exe -i filemap.perf.data -o filemap_all.html --inode_file filemap_inode.json解析出来的 html 文件是所有文件集合的火焰图
① 为整个进程文件页申请的大小
📷 图片描述: Filemap 文件页堆栈解析中抓取到文件页 perf.data 后通过 report_filemap.exe 解析出来的 html 文件内容。上方有”Chart Statistics""Sample Table""Flamegraph”三个选项卡,其中”Sample Table”被红色框突出显示。右侧显示了记录时间、机器类型、Android 版本等信息,以及总样本数 91654 和 Event Type 为”filemap.mm_filemap_add_to_page_cache”时的内存占用大小 402,532 KB。(token: J8FObMRKjoWPjbxHmIzcV6bFnre)
点击 ② Sample Table 后可以进入函数列表
📷 图片描述: Filemap 文件页堆栈解析中 Function 界面的 Sample Table 部分。其中,红色框突出显示了”Show event count”和”Show percentage of event”两个选项,以及”10343 (com”(图片原文被截断)。(token: O5xEbqeV6odte9xjVMncbOACn2b)
点击 ③ 处可以切换 event count 显示,event count 值即为 内存占用大小
点击 ④ 处切换到 Function 界面
⑤ 当把鼠标放在某个函数上时显示出来的提示框中有该函数所占用的内存大小
📷 图片描述: Filemap 文件页堆栈解析中 Function 页面的内容。页面上方有”Chart Statistics""Sample Table""Flamegraph""Function”四个选项卡,其中”Function”被红色框突出显示。页面中部显示了函数”filemap_add_to_page_cache”的相关信息,包括进程、线程、函数名称等。下方是函数调用栈,以不同颜色条块呈现,鼠标悬停在某函数上时,弹出提示框显示该函数所占用的内存大小。(token: F5m1bQKGwoaJvgxlvQRc61YinWg)
在 Function 页面拉到最底下即为当前线程该函数调用栈中申请的文件大小及文件 ⑥
📷 图片描述: Filemap 文件页堆栈解析中 Function 页面的内存申请信息。其中,⑦ 处为对应文件的 dev 和 inode 值,可单独解析单个文件火焰图分析;⑥ 处是文件路径,如
/data/dalvik-cache/arm64/system/priv@app@MiuiSystemUI@MiuiSystemUI_apk@classes.dex等。(token: EXyZbb27Oo0NTOxCKADcaSgzn1e)
⑦ 处为对应文件的 dev 和 inode 值,可以根据该值单独解析处单个文件的火焰图分析
为方便线程火焰图文件查看,将上述的 Function 页面显示的 Filemap 集成到了悬浮页中,如下图
鼠标放置在 ① 中函数处弹出悬浮窗,② 显示的是该处函数申请的内存大小
③ 为该线程所有该函数的文件申请内存大小
📷 图片描述: 将 Filemap 集成到悬浮页后的效果。鼠标放置在 ① 处函数”[linker!EI](Reader::Read(char const*, int…)”时弹出悬浮窗,② 处显示该处函数申请的内存大小为 544 KB,③ 处为该线程所有该函数的文件申请内存大小。图中还呈现了其他相关函数,如 filemap.add_folio 等。(token: Ym5NblmRcosmXHxdRNUcbsVRnBf)
解析单个文件栈命令
simpleperf_report.exe -i filemap.perf.data -o filemap_266338359_34078.html --dev_inode 266338359_34078解析完后打开 html 文件可以看到该进程的单个文件的所分配的内存大小
📷 图片描述: Filemap 文件页堆栈解析中 html 文件的界面。界面上方有”Chart Statistics""Sample Table""Flamegraph”三个选项卡,当前选中”Flamegraph”。下方显示了记录时间、机器类型、Android 版本等信息,以及总样本数为 21832,事件类型为”filemap:mm_filemap_add_to_page_cache”,右侧显示内存占用大小为 87.328 KB。(token: FUEmbarmAoBHLFxi1ldcbTrXnMh)
点击 Flamegraph 切换到火焰图界面即可查看该文件内存分配的堆栈信息
📷 图片描述: 文件映射内存分析中文件页堆栈解析的火焰图界面。图中以不同颜色条形图呈现函数调用堆栈,颜色深浅代表函数调用的频率,红色区域突出显示了部分函数调用堆栈。(token: Q7wDbYaI2oj1mmxy6esc7yxQnlh)
如上是文件映射内存的过程,堆栈反应的是某些文件加载到内存中的情况,但针对一些可执行文件,可能需要查看它具体调用了哪些接口导致映射内存过大,可采用如下方式抓取进程回栈搜索相应的可执行文件查看接口使用。
文件代码执行抓取解析
adb shell
cd /data/local/tmp
# 使用 -e cpu-cycles 抓取
./filemap_catch -p com.android.systemui -e cpu-cycles -s -o /data/local/tmp/cpu-cycles.perf.data
...
adb pull /data/local/tmp/cpu-cycles.perf.data# init 子进程参考命令
./filemap_catch -p audioserver -e cpu-cycles -f 1 -b -o /data/local/tmp/cpu-cycles.perf.data新版本增加了 -n 参数,如果采样点较少可通过增加 -n c10000 抓取,如果丢失率较高可调整采样频率比如使用 -n c20000,-n 参数 c 值越小采样频率越高,f 值越大采样频率越高
解析
simpleperf_report.exe -i cpu-cycles.perf.data -o cpu-cycles.html可以在 cpu-cycles.html 中搜索该可执行文件调用了哪些函数接口,注意,cpu-cycles.html 文件解析不包含内存信息,仅有函数调用栈信息
📷 图片描述: 文件页内存调试分析中文件代码执行抓取的解析界面,包含 addCriterion() 函数调用栈信息。画面中呈现了多个函数调用栈,如
android::uirender::RenderThread::threadLoop()、android::uirender::DrawFrameTask::run()等,每个调用栈后方有对应的 Library 和 Function 信息。画面右上角有”Search: libhwui.so”搜索框。(token: OPdgb5mvvoOnh1xPHWCcxwDtntd)
可以利用手机中的 simpleperf 导出某个 so 库中所有执行函数为 csv 文件,这样可以在抓取对比机后利用 AI 去对比函数调用差异
adb shell
simpleperf report -i /data/local/tmp/cpu-cycles.perf.data --csv --children --sort comm,pid,tid,dso,symbol,vaddr_in_file --dsos /system/lib64/libaudiohal@aidl.so -o /data/local/tmp/cpu-cycles.csv
adb pull /data/local/tmp/cpu-cycles.csv .文件页释放抓取
文件页的释放并不是完全由本进程去释放的,所以没有办法抓到完整的回栈信息,但可以通过 filemap/mm_filemap_delete_from_page_cache ftrace 去获取释放了多少
可以在抓取 perfetto 中的 config 中增加如下配置,也可以直接使用网盘中的 android_perfetto_config.pbtx 文件,配置默认是抓取 10s,如果需要更改就调整 duration_ms 大小
data_sources: {
config {
name: "linux.ftrace"
target_buffer: 0
ftrace_config {
ftrace_events: "kmem/rss_stat"
ftrace_events: "filemap/mm_filemap_add_to_page_cache"
ftrace_events: "filemap/mm_filemap_delete_from_page_cache"
...
}
}
}
duration_ms: 10000 # 配置抓取时间,单位 ms执行如下命令抓取 perfetto trace
# windows cmd
type android_perfetto_config.pbtx | adb shell perfetto --txt -o /data/misc/perfetto-traces/trace_file.perfetto-trace -c -
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace
# shell
cat android_perfetto_config.pbtx | adb shell perfetto --txt -o /data/misc/perfetto-traces/trace_file.perfetto-trace -c -
adb pull /data/misc/perfetto-traces/trace_file.perfetto-trace抓取到的 perfetto trace 使用 perfetto 网站 打开查看,找到关心的进程,点开进程后找到 ③ 处 mem_rss:file 就是进程的文件页大小变化
① 处可以点击选择 Ftrace Events 查看 ftrace 日志,点击 ② 处 Filter 可以过滤出 mm_filemap 相关的 ftrace 信息查看具体 inode,点击 ④ 处可以导出为 systrace 文件,这样可以文本打开搜索过滤
📷 图片描述: Perfetto 界面,用于抓取和分析文件页内存调试信息。界面左侧有 Timeline、Overview 等选项,右侧显示了不同内存区域的使用情况。图中红色框标注了四个关键位置:① 处为 Ftrace Events,可查看 ftrace 日志;② 处是 Filter,可过滤出 mm_filemap 相关 ftrace 信息;③ 处是 mem_rss:file,点击可查看进程的文件页大小变化;④ 处是 Convert to systrace,可导出为 systrace 文件。(token: BMvjb2G74oUN43xm6lRc2L2fnre)
其它
文件 dev 和 inode 获取
adb shell stat /vendor/lib64/libllvm-qgl.so
File: /vendor/lib64/libllvm-qgl.so
Size: 23688512 Blocks: 25304 IO Blocks: 512 regular file
Device: fe0ch/65036d Inode: 25269504 Links: 1 Device type: 0,0
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2009-01-01 08:00:00.000000000 +0800
Modify: 2009-01-01 08:00:00.000000000 +0800
Change: 2009-01-01 08:00:00.000000000 +0800dev 值需要移位操作,如上 fe0c 变更为 fe0000c(fe << 20 + 0c),转化成十进制就是 266338316,在与 inode 组合,266338316_25269504 然后在使用 report_filemap.exe 去过滤,如果过滤出来没有采样点的话就是没有抓到该文件回栈
📷 图片描述: 文件页内存调试分析中采样记录的统计信息。记录时间为 2026 年 1 月 11 日 12:10:41,机器类型为 2512BPNDAC(nezha)由 Xiaomi 提供,Android 版本为 16,Build Fingerprint 为 Xiaomi/nezha/nezha:16/BP2A.250605.031.A3/OS3.0.10,Kernel 版本为 6.12.23-android16-5-g75e9b1c7ae7c-abogki463945075,记录 cmdline 为
/system/bin/simpleperf record -g -p zygote64 --duration。关键信息是 Total Samples(总采样数)为 0。(token: N7Q7bICAnomLj0xKvhMc2F4qn7d)