手把手读 memleax 源码:10 个模块的分工与整体架构设计
手把手读 memleax 源码:10 个模块的分工与整体架构设计
【免费下载链接】memleaxdebugs memory leak of running process. Not maintained anymore, try `libleak` please.项目地址: https://gitcode.com/gh_mirrors/me/memleax
memleax 是一款面向运行中进程的内存泄漏检测工具,它的核心卖点是不用重新编译、不用重启目标程序,直接 attach 上去就能实时发现泄漏。很多初学者拿到 memleax 源码后不知道该从哪里读起,本文就带你按10 个核心模块逐个拆解分工,再串成一条主线看懂整体架构设计,读完你就能自己上手调试、甚至二次开发。
一、memleax 是什么:先搞清楚它解决了什么问题
传统的内存泄漏排查(如 Valgrind)需要由工具启动目标进程,跑完后才给出报告,这对生产环境非常不友好。memleax 的思路完全不同:
- attach 到正在运行的进程,无需重新编译、无需重启;
- 在目标进程的
malloc / free / realloc / calloc上下断点,记录每一块内存的分配与释放; - 凡是存活超过阈值(默认 10 秒,可用
-e调整)的内存块,就实时输出为疑似泄漏,并打印调用栈; - 用 Ctrl-C 停止监控,退出时还会汇总每个调用栈的统计信息。
一句话总结:memleax 通过"挂钩内存 API + 实时超时判定"实现了轻量级内存泄漏检测,非常适合生产环境临时救火。
二、源码总览:10 个模块与 4 类分工
整个项目源码不到 30 个文件,核心 C 源码非常精简。按职责可以分成 4 大类、10 个模块:
| 分类 | 模块 | 职责 |
|---|---|---|
| 主控调度 | memleax.c | 参数解析、attach、监控主循环、信号处理 |
| 断点与平台 | breakpoint.c/ptrace_utils.h/machines.h | 挂钩内存函数、ptrace 封装、寄存器与调用约定 |
| 数据记录 | memblock.c/callstack.c/ptr_backtrace.c | 内存块账本、调用栈聚合、栈回溯 |
| 符号定位 | symtab.c/debug_line.c/addr_maps.c+proc_info.c | 函数名、源码行号、地址与进程信息 |
辅助的数据结构工具(hash.h、list.h、array.h)不单独算模块,它们是内存块和调用栈的底层容器,后面会提到。
三、模块详解:10 个模块各自的分工
1.memleax.c——主监控循环,全项目的"心脏"
这是阅读源码的第一站。它的main()里有一条非常清晰的主线:
- 用
getopt解析-e / -d / -l / -m / -c等选项; - 依次构建地址映射、符号表、调试行号等静态信息;
- 通过
ptrace_attach附加目标进程,并给每个线程设置断点; - 进入
while(1)死循环,用waitpid等待断点命中; - 命中后区分函数入口断点和函数返回断点,分别取参数、取返回值;
- 每次循环末尾调用
memblock_expire()做超时判定,并设置alarm定时器; - 退出时恢复断点、
ptrace_detach,最后输出汇总报告。
可以说,读懂memleax.c的主循环,就理解了整个项目的骨架。
2.breakpoint.c——内存函数的钩子
breakpoint_init()会向目标进程注入 4 个断点:malloc、free、realloc、calloc。每个断点对应一个回调:
bph_malloc:记录新内存块;bph_free:删除对应内存块;bph_realloc:区分原地扩容与搬迁;bph_calloc:按nmemb * size记录大小。
断点命中时,会把原指令替换成int 3(0xCC)陷阱指令,命中后立即恢复原指令,保证目标进程无感运行。
3.ptrace_utils.h——ptrace 跨平台封装
这一层用static inline封装了attach / continue / detach / get_regs / get_data / set_data等操作,同时兼容Linux 与 FreeBSD两套 ptrace API。想了解"如何优雅地在两个平台上共用一套代码",这个文件是最好的范例。
4.machines.h——CPU 架构与调用约定
不同 CPU 上取参数、取返回地址的方式完全不同。machines.h通过宏适配了x86、x86_64、armv7、aarch64四种架构:
call_arg1 / call_arg2:从寄存器(或栈)取函数参数;call_return_address:从栈顶/链接寄存器取返回地址;call_return_value:从rax / eax等取返回值;pc_unwind:把 RIP 回退一格,让 CPU 重新执行被替换的指令。
这就是 memleax 能跑在树莓派(ARM)和 x86 服务器上的秘密。
5.memblock.c——内存块的"账本"
每块被跟踪的内存对应一个struct memblock_s,记录指针、大小、创建时间、过期标记和所属调用栈。它用哈希表(按指针快速查找)加双向链表(按时间顺序遍历)双结构管理:
- 分配 → 插入 active 链表;
- 释放 → 从链表中摘除并统计存活时间;
- 超时 → 移入 expire 链表,标记为疑似泄漏。
memblock_expire()是超时判定的入口,也是主循环每次都要调用的大脑。
6.callstack.c——把内存块按调用栈聚合
真正的泄漏判定单位不是"一块内存",而是"调用栈"。callstack_current()会回溯当前调用栈,把地址序列哈希后查找是否已存在;不存在则新建。这样同一处代码泄漏的 100 块内存,会聚合到同一个CallStack[N],报告非常清爽。
7.ptr_backtrace.c——运行时栈回溯
它基于libunwind的 ptrace 接口,在断点命中时读取目标进程的栈,回溯出一串指令地址(默认最多 50 层,-l可调)。这是把"内存块"翻译成"代码位置"的关键一步。
8.symtab.c——符号表解析
拿到地址后,symtab_by_address()把地址翻译成"函数名 + 偏移",比如foo()+14;symtab_by_name()则反方向用函数名找地址——下断点前必须用它定位 malloc 的入口地址。
9.debug_line.c——源码行号定位
它读取 DWARF 调试行号信息,把返回地址翻译成"文件名:行号",比如foo.c:12。这样报告才能精确到"哪一行代码泄漏了",而不是只给一个十六进制地址。
10.addr_maps.c+proc_info.c——地址映射与进程探测
addr_maps.c解析/proc/<pid>/maps,把地址翻译成所属的可执行文件或共享库名;proc_info.c负责枚举进程的所有线程、判断新线程/新进程,配合PTRACE_O_TRACECLONE实现自动跟踪新线程。
四、整体架构设计:一次分配事件的生命周期
把 10 个模块串起来,就能看到一条完整的数据流:
- attach:
proc_info.c枚举线程 →ptrace_attach全部附加; - 定位:
addr_maps.c建地址映射,symtab.c找到malloc等函数地址; - 下钩子:
breakpoint.c把函数入口指令替换为陷阱指令; - 命中入口:主循环
waitpid捕获 →machines.h取参数 → 在返回地址上下第二个断点; - 命中返回:
machines.h取返回值 →callstack.c回溯并聚合调用栈 →memblock.c记录内存块; - 超时判定:定时器触发
memblock_expire(),存活超阈值的内存块上报为泄漏,打印调用栈; - 退出汇总:
callstack_report()输出每个泄漏点的统计(分配/释放/过期/存活时间)。
整个过程围绕"入口断点取参数、返回断点取返回值"这对组合拳展开,设计非常巧妙。
五、设计亮点:为什么这个架构值得学习
💡实时性优先:与 Valgrind 的"跑完再报"不同,memleax 边跑边报,超时即出结果,特别适合线上问题定位。
🧩分层解耦:平台相关(ptrace、寄存器)与业务逻辑(内存账本、调用栈)严格分离,换架构只需改machines.h和ptrace_utils.h。
⚡哈希+链表双结构:查找 O(1)、按时间遍历 O(n),兼顾了"按指针找内存块"和"按时间做超时扫描"两种需求。
🔁多线程处理:当多个线程同时命中断点时,主循环会"挂起"其他线程(hold_threads),保证一次只处理一个函数调用,避免调用栈串线。
六、如何编译与快速上手
想要边读边跑,先 clone 源码:
git clone https://gitcode.com/gh_mirrors/me/memleax编译依赖libunwind、libelf、libdw(或libdwarf),然后:
mkdir build && cd build cmake .. make sudo make install使用非常简单——找到目标进程 PID,直接监控:
memleax -e 10 <target-pid>-e 10表示存活超过 10 秒的内存块即上报为泄漏,按你的业务场景调整这个阈值是关键。
七、总结
memleax 源码虽然体量不大,却是一个"麻雀虽小、五脏俱全"的动态调试工具教科书:ptrace 断点注入、多平台适配、调用栈回溯、哈希+链表数据结构、实时监控架构,一次全部覆盖。按本文的 10 个模块顺序阅读,先抓memleax.c主循环,再逐个深入,你会发现整个架构其实并不复杂。
最后提醒一句:memleax 已停止维护,官方推荐改用更轻量的libleak(基于 LD_PRELOAD 挂钩内存函数)。但作为学习"如何用 ptrace 实现运行时调试"的源码范本,memleax 依然非常值得一读。🚀
【免费下载链接】memleaxdebugs memory leak of running process. Not maintained anymore, try `libleak` please.项目地址: https://gitcode.com/gh_mirrors/me/memleax
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
