Skip to content

Latest commit

 

History

History
389 lines (316 loc) · 13.7 KB

File metadata and controls

389 lines (316 loc) · 13.7 KB

kmemleak - 内存泄漏分析器

用于检测用户态和内核态内存分配器的内存泄漏问题。

概述

  • 主要用途: 通过跟踪内存分配和释放事件,检测未释放的内存,识别内存泄漏问题
  • 适用场景: 内存使用持续增长、怀疑存在内存泄漏、需要定位内存泄漏来源
  • 功能分类: 自定义事件类,内存分析,延迟分析
  • 最低内核版本: 支持 perf_event 和 tracepoint 的内核版本(通常 2.6.32+)
  • 依赖库: libtraceevent, libelf
  • 平台支持: x86_64, ARM, RISC-V 等所有支持 perf_event 的架构
  • 特殊限制: 需要root权限
  • 参与联合分析: 否(独立分析器)
  • 核心技术:
    • 基于 tracepoint/kprobe/uprobe 的事件关联分析,通过指针匹配 alloc 和 free 事件
    • 支持内核态内存分配器,用户态内存分配器

基础用法

perf-prof kmemleak --alloc EVENT[...] --free EVENT[...] [OPTION]

OPTION:

  • -C, --cpus: Attach到指定CPU列表
  • -p, --pids: Attach到指定进程
  • -t, --tids: Attach到指定线程
  • -m, --mmap-pages: ringbuffer大小,页数,默认值: 4
  • --order: 启用事件时间戳排序(强烈推荐),提高分析准确性

FILTER OPTION:

  • --user-callchain[=dwarf[,size]] 堆栈开关,选中用户态堆栈,no-前缀反选。=dwarf 启用DWARF栈回溯
  • --kernel-callchain 堆栈开关,选中内核态堆栈,no-前缀反选

PROFILER OPTION:

  • --alloc <EVENT,...>: 指定内存分配事件,必须包含 ptr 属性,可选 sizestack 属性
  • --free <EVENT,...>: 指定内存释放事件,必须包含 ptr 属性
  • --than <n>: 过滤内存分配超过指定时间的泄漏,单位: s/ms/us/*ns
  • -g, --call-graph: 启用调用栈记录,用于定位泄漏位置
  • --flame-graph <file>: 生成火焰图文件
  • --comm: 在泄漏字节报告中显示每个调用栈的分配进程名列表(需配合 -g 使用)

示例

# 内核态内存泄漏检测 (kmalloc/kfree)
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/ \
                   --free kmem:kfree//ptr=ptr/ --order -m 128 -g

# 多个分配事件 (kmalloc + kmalloc_node)
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/,kmem:kmalloc_node//ptr=ptr/size=bytes_alloc/stack/ \
                   --free kmem:kfree//ptr=ptr/ --order -m 128 -g

# 分析特定进程的内存泄漏
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/ \
                   --free kmem:kfree//ptr=ptr/ -p <pid> --order -m 128 -g

# 检测超过10秒未释放的内存
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/ \
                   --free kmem:kfree//ptr=ptr/ --order -m 128 -g --than 10s

# 泄漏字节报告中显示分配进程名
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/ \
                   --free kmem:kfree//ptr=ptr/ --order -m 128 -g --comm

核心原理

基本定义

  • 内存泄漏: 已分配但未释放的内存
  • 分配事件: 通过 tracepoint/kprobe/uprobe 捕获内存分配操作
  • 释放事件: 通过 tracepoint/kprobe/uprobe 捕获内存释放操作
  • 指针关联: 通过分配返回的指针 (ptr) 将 alloc 和 free 事件关联
  • 泄漏判定: 分配事件没有对应的释放事件,即为泄漏

数据模型

分配事件 → [排序] → [存入alloc链表] → [指针匹配] → [未匹配=泄漏] → 报告
释放事件 → [排序] → [指针查找] → [从alloc链表删除] → 已释放

事件源

  • sample_type:

    • PERF_SAMPLE_TID: 记录线程ID
    • PERF_SAMPLE_TIME: 记录事件时间戳
    • PERF_SAMPLE_ID: 记录事件ID(用于区分alloc/free)
    • PERF_SAMPLE_CPU: 记录CPU编号
    • PERF_SAMPLE_RAW: 记录原始事件数据
    • PERF_SAMPLE_CALLCHAIN: 启用调用栈(由 -gstack 属性控制)
  • 自定义事件:

    • 分配事件 (--alloc):

      • 格式: sys:name[/filter/ptr=EXPR/size=EXPR/stack/]
      • ptr=EXPR: 必需,计算分配返回的指针
      • size=EXPR: 可选,计算分配的字节数(启用泄漏字节报告)
      • stack: 可选,为该事件启用调用栈
      • -g 选项: 只为alloc事件启用调用栈
      • 示例事件: kmem:kmalloc, kmem:kmalloc_node, kmem:mm_page_alloc
      • 用户态的内存分配事件,需要加uprobe点
    • 释放事件 (--free):

      • 格式: sys:name[/filter/ptr=EXPR/]
      • ptr=EXPR: 必需,计算要释放的指针
      • 示例事件: kmem:kfree, kmem:mm_page_free
      • 用户态的内存释放事件,需要加uprobe点

过滤器

支持标准的 trace event filter 语法,在事件定义的第一个 / 后指定:

# 只跟踪大于1MB的分配
--alloc kmem:kmalloc/bytes_alloc>1048576/ptr=ptr/size=bytes_alloc/

事件处理

处理流程:

  1. 事件接收: 从 perf ringbuffer 接收 alloc 和 free 事件
  2. 指针提取: 通过 ptr 表达式从事件中提取指针值
  3. 分配处理:
    • 提取 ptr 和可选的 size
    • 将事件备份到 alloc 红黑树(以 ptr 为键)
    • 如果 ptr 已存在,替换旧事件(重新分配)
  4. 释放处理:
    • alloc 树中查找对应的 ptr
    • 找到则删除(已正常释放)
    • 未找到则忽略

依赖排序:

  • 强烈建议启用 --order: 保证事件按时间戳顺序处理
  • 不启用排序: 可能因乱序导致误报(释放事件早于分配事件到达)
  • 事件丢失处理:
    • 检测到事件丢失时,立即报告当前泄漏
    • 清空 alloc 树,重新开始跟踪
    • 避免因丢失事件导致误报

数据结构:

  • alloc 红黑树: 存储未释放的分配事件(按 ptr 排序)
  • 每个节点保存完整的 perf_event 数据(包括调用栈)

状态统计

  • 信号处理
    • SIGUSR1: 输出内存泄漏统计信息
      • ALLOC LIST: 当前 alloc 树的事件数和内存占用
      • TOTAL: 累计处理的 alloc 和 free 事件数
    • SIGUSR2: 未使用

输出

输出格式

1. 泄漏事件报告 (默认或 -v)

KMEMLEAK REPORT: 15
      305.266631  4518 [000] kmalloc: call_site=c09f56b9 ptr=f59a7600 bytes_req=1024 bytes_alloc=1024 gfp_flags=208
    ffffffff81234567 kmalloc
    ffffffff81abcdef some_function+0x123
    ffffffff81fedcba caller_function+0x45
  • 表头含义:
    • 时间戳: 事件发生时间(perf clock)
    • 线程ID: 分配线程的 TID
    • CPU编号: 分配所在的 CPU
    • 事件名: tracepoint 事件名
    • 字段值: 事件的各字段值
  • 调用栈: 显示分配发生的调用链(由 -g 启用)

2. 泄漏字节报告 (启用 size 属性且 -g)

LEAKED BYTES REPORT:
Leak of 524288 bytes in 512 objects allocated from:
    ffffffff81234567 kmalloc
    ffffffff81abcdef some_function+0x123
    ffffffff81fedcba caller_function+0x45

使用 --comm 时,每个调用栈会显示分配进程名列表,按泄漏字节数降序排列:

LEAKED BYTES REPORT:
Leak of 524288 bytes in 512 objects allocated from:
    comms: kworker/0:1(409600/300) systemd(81920/150) bash(32768/62)
    ffffffff81234567 kmalloc
    ffffffff81abcdef some_function+0x123
    ffffffff81fedcba caller_function+0x45
  • 格式: 进程名(泄漏字节数/分配次数)

  • 表头含义:

    • Leak of X bytes in Y objects: X 字节泄漏,分布在 Y 个对象中
    • 按泄漏字节数降序排列
    • 相同调用栈的泄漏聚合显示

3. 统计信息 (程序退出或 SIGUSR1)

KMEMLEAK STATS:
ALLOC LIST num 128 mem 65536
TOTAL alloc 10245 free 10117
  • 数据单位:
    • num: 事件数量
    • mem: 事件在kmemleak内缓存所占用的内存(字节)
    • alloc/free: 累计处理的事件数
  • :
    • ALLOC LIST: 当前未匹配的分配事件

分析方法

基础分析方法

1. 选择合适的事件

# 查看可用的内存分配和释放事件
./perf-prof list | grep -E "^kmem:"

# 查看事件字段
./perf-prof trace -e kmem:kmalloc help

2. 配置指针和大小属性

  • 分配事件必须有 ptr 属性
  • 建议添加 size 属性以获取泄漏字节报告
  • 示例: --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/

3. 启用排序和调用栈

# 使用 --order 保证事件顺序
# 使用 -g 获取调用栈
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                   --free kmem:kfree//ptr=ptr/ --order -g -m 128

4. 调整缓冲区大小

  • 默认 4 页,内存泄漏分析建议 128 页或更大
  • 使用 -m 128-m 256 减少事件丢失

5. 设置运行时间

  • 使用 -i 设置定期输出(如 -i 10000 每10秒)
  • 或运行固定时间后 Ctrl+C 退出查看结果

数据驱动分析

1. 不预设业务特征

  • 通过/proc/meminfo,slabtop等识别可能存在内存泄漏的分配器。
  • 支持用户态内存分配器。
  • 根据泄漏点,选择事件

2. 完全基于实际数据

  • 使用过滤器缩小范围:
    # 只跟踪特定函数的分配
    perf-prof kmemleak --alloc 'kmem:kmalloc/bytes_alloc>1024/ptr=ptr/size=bytes_alloc/' \
                       --free kmem:kfree//ptr=ptr/ --order -g

应用示例

基础示例

# 1. 内核 kmalloc/kfree 泄漏检测
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/ \
                   --free kmem:kfree//ptr=ptr/ --order -m 128 -g

# 2. 页面分配泄漏检测(低内核内核版本使用ptr=page)
perf-prof kmemleak --alloc 'kmem:mm_page_alloc//ptr=pfn/size=4096<<order/stack/' \
                   --free kmem:mm_page_free//ptr=pfn/stack/ -m 256 --order

# 3. 特定进程的内存泄漏
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                   --free kmem:kfree//ptr=ptr/ -p 1234 --order -g -i 5000

# 4. 生成火焰图
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                   --free kmem:kfree//ptr=ptr/ --order -g --flame-graph leak.folded
flamegraph.pl leak.folded > leak.svg

高级技巧

# 1. 多个分配函数联合跟踪
perf-prof kmemleak \
    --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/stack/,kmem:kmalloc_node//ptr=ptr/size=bytes_alloc/stack/ \
    --free kmem:kfree//ptr=ptr/ --order -m 256 -g

# 2. 过滤大内存分配 (>1MB)
perf-prof kmemleak \
    --alloc 'kmem:kmalloc/bytes_alloc>1048576/ptr=ptr/size=bytes_alloc/' \
    --free kmem:kfree//ptr=ptr/ --order -g

# 3. 检测长时间未释放的内存 (>30秒)
perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                   --free kmem:kfree//ptr=ptr/ --order -g --than 30s

# 4. 使用 kprobe 跟踪自定义函数
perf-prof kmemleak \
    --alloc 'kprobe:my_alloc_func/filter/ptr=$retval/size=bytes/' \
    --free 'kprobe:my_free_func//ptr=ptr/' --order -g

# 5. 用户态内存泄漏 (需要 uprobe 支持)
perf-prof kmemleak \
    --alloc 'uprobe:malloc@"/lib64/libc.so.6"//ptr=$retval/' \
    --free 'uprobe:free@"/lib64/libc.so.6"//ptr=ptr/' -p <pid> --order -g

# 6. 实时监控泄漏趋势
while true; do
    echo "=== $(date) ==="
    timeout 30 perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/ \
                                   --free kmem:kfree//ptr=ptr/ --order -m 128
    sleep 60
done

性能优化

  • 缓冲区大小:

    • 小型系统: -m 64
    • 中型系统: -m 128
    • 大型系统: -m 256 或更大
    • 内存分配频繁系统: -m 512
  • 过滤器优化:

    • 优先使用内核态过滤器(第一个 / 后)
    • 使用精确过滤减少事件量:
      # 只跟踪特定大小范围
      --alloc kmem:kmalloc/bytes_alloc>=1024 && bytes_alloc<=4096/ptr=ptr/
  • 采样限制:

    • 使用 --sampling-limit <N> 限制采样率
    • 高频分配场景建议: --sampling-limit 10000

参数调优

  • --order 调优:

    • 必需参数,提高准确性
    • 代价: 轻微性能开销和内存占用
    • 建议: 始终启用
  • -m 优化:

    • 根据分配频率调整
    • 观察是否有 lost events 警告
    • 逐步增大直到无丢失
  • --than 调优:

    • 过滤短暂分配,关注长期泄漏
    • 建议值: 10s ~ 60s
    • 分析缓存类泄漏用较小值(1s ~ 5s)

组合使用

  • 与其他分析器配合:

    # 1. 先用 top 找热点
    perf-prof top -e 'kmem:kmalloc//key=call_site/printkey=printf("%lx",key)/' -i 1000
    
    # 2. 再用 kmemleak 分析特定函数(新内核支持call_site.function过滤器)
    perf-prof kmemleak --alloc kmem:kmalloc/call_site.function==hotspot_func/ptr=ptr/ \
                       --free kmem:kfree//ptr=ptr/ --order -g
    
    # 3. 用 kmemprof 分析分配大小分布
    perf-prof kmemprof -e kmem:kmalloc//size=bytes_alloc/stack/ -e kmem:kfree --order
  • 多阶段分析:

    # 阶段1: 快速扫描(无调用栈)
    perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                       --free kmem:kfree//ptr=ptr/ --order -m 128 -i 10000
    
    # 阶段2: 详细分析(启用调用栈)
    perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                       --free kmem:kfree//ptr=ptr/ --order -m 256 -g -v
    
    # 阶段3: 生成报告(火焰图)
    perf-prof kmemleak --alloc kmem:kmalloc//ptr=ptr/size=bytes_alloc/ \
                       --free kmem:kfree//ptr=ptr/ --order -g --flame-graph leak.folded

相关资源