python无法直接hook malloc/free,必须在c层替换cpython的内存函数指针;可行方案包括编译时修改源码或ld_preload预加载自定义malloc/free/realloc;需注意线程安全、解释器未初始化限制及三套allocator接口(pymem/pyobject/pyarena)的完整覆盖。

Python 里没法直接 hook malloc 和 free
Python 解释器(CPython)本身不提供 API 让你在 Python 层拦截或替换底层 C 的内存分配函数。你写的 sys.settrace 或 __import__ 钩子,对 PyMem_Malloc、PyObject_Malloc 这类 C 层调用完全无效。想靠纯 Python 实现“自定义内存分配器”,本质是行不通的。
唯一可行路径:编译时替换 CPython 的内存函数指针
CPython 在启动时会通过全局函数指针(如 _PyMem_RawMallocFunc、_PyMem_RawFreeFunc)调用内存函数。这些指针在 Objects/obmalloc.c 和 Python/pyarena.c 中被使用,且允许在构建前通过宏或符号重定向覆盖。
实操建议:
- 修改 CPython 源码,在
Python/pylifecycle.c的PyInterpreterState_Init之前,用你自己的函数地址赋值给_PyMem_RawMallocFunc等指针 - 或者更稳妥的方式:用
LD_PRELOAD(Linux)或DYLD_INSERT_LIBRARIES(macOS)预加载一个共享库,其中强符号定义malloc、free、realloc—— 注意必须同时覆盖所有三个,否则 CPython 内部混用会导致崩溃 - Windows 下需用 DLL 注入 + IAT Hook,但 CPython 官方不保证 ABI 稳定性,极易因版本升级失效
- 不要试图只 hook
PyObject_Malloc:CPython 对小对象走 obmalloc 池,大对象才走malloc;而PyMem_RawMalloc可能绕过所有 Python 层 allocator 直接调 C 库,漏掉就等于没 hook
LD_PRELOAD 方案下最常踩的坑
现象:Segmentation fault 在解释器启动早期就发生,甚至卡在 Py_Initialize 前。
立即学习“Python免费学习笔记(深入)”;
原因和对策:
- 你的
malloc实现里调用了任何 Python C API(比如PyErr_SetString)—— 不行,此时解释器还没初始化,PyThreadState_Get()返回NULL - 没处理线程安全:CPython 启动阶段就有多个线程并发调
malloc(如 GIL 初始化、信号 handler 注册),你的分配器必须带锁,或用 per-thread slab - 忘记导出
realloc:很多系统库(包括 libc 自身)在malloc后会调realloc,缺了就会 fallback 到默认实现,导致内存管理错乱 - 日志写到
stdout或stderr:在LD_PRELOAD早期,FILE*可能未就绪,用write(2)更可靠
为什么连 tracemalloc 都不算“自定义分配器”
tracemalloc 是事后采样(通过 PyMem_SetAllocator 替换 Python 层 allocator,并记录调用栈),它不控制实际内存布局,也不改变分配行为,只是“看”而不是“管”。如果你的目标是内存池复用、NUMA 绑核、或 GPU 内存映射,tracemalloc 无能为力。
真正要接管分配逻辑,就必须在 C 层面对齐 CPython 的三套 allocator 接口:PyMem(C 兼容)、PyObject(对象专用)、PyArena(AST 构建用),每套的 malloc/realloc/free 都得单独 hook,且它们之间有隐式依赖——比如 PyArena 的 alloc 最终可能调 PyObject_Malloc。
不是做不到,但每个 CPython 小版本都可能调整这些函数的调用链或初始化顺序。上线前务必用 valgrind --tool=memcheck 跑满所有测试用例,尤其注意 fork() 后子进程的 allocator 状态是否被继承或重置。










