python tracemalloc 检查踩内存原理分析-记录一次真实的踩内存问题分析解决实战

tracemalloc 是python官方提供的跟踪内存分配的工具,也包含了踩内存的相关检测。它会在内存Malloc申请时在申请的内存前后加一些特定的数据填充字节,在内存Realloc、Free时,检查这些填充的字节是否符合预期,如果不符合预期,那么认为这段内存是被其他函数修改过的,然后主动abort,并提示哪些字节与预期不符、以及提示出该段内存最近是在哪行python代码中申请的,如果我们的机器配置了可以生成core,那么就可以从后面的core文件中去分析内存数据。

踩内存检测原理

内存申请

obmalloc.c:_PyMem_DebugRawAlloc()

在n个字节的内存申请时,会在原有的nbtyes基础上多申请 3 * 8(SIZEOF_SIZE_T) = 24个字节的内存,总的内存申请大小就变成了 nbytes + 24 个字节。然后在存放存放数据data的nbytes前后,分别填充特定的数据,在内存释放 的时候,检测前后的填充数据是否符合预期,来判断内存是否被踩。

用到的宏及相关定义

arduino 复制代码
#define PYMEM_CLEANBYTE      0xCD
#define PYMEM_DEADBYTE       0xDD
#define PYMEM_FORBIDDENBYTE  0xFD

typedef struct {
    /* We tag each block with an API ID in order to tag API violations */
    char api_id;
    PyMemAllocatorEx alloc;
} debug_alloc_api_t;
static struct {
    debug_alloc_api_t raw;
    debug_alloc_api_t mem;
    debug_alloc_api_t obj;
} _PyMem_Debug = {
    {'r', PYRAW_ALLOC},
    {'m', PYMEM_ALLOC},
    {'o', PYOBJ_ALLOC}
    };
// api_id有三种,'r'、'm'、'o'

以申请32字节(nbytes=8)内存为例,在调用完_PyMem_DebugRawAlloc方法且使用的是malloc而不是calloc之后,它的内存数据如下:

内存释放

obmalloc.c:_PyMem_DebugRawFree()

在内存释放时,会检查这个内存的填充值是否符合预期,如果不符合预期会主动abort,生成一个core。

检查逻辑:

obmalloc.c:_PyMem_DebugCheckAddress()

检查api id是否符合预期

ini 复制代码
/* Check the API id */
id = (char)q[-SST];
if (id != api) {
    msg = msgbuf;
    snprintf(msgbuf, sizeof(msgbuf), "bad ID: Allocated using API '%c', verified using API '%c'", id, api);
    msgbuf[sizeof(msgbuf)-1] = 0;
    goto error;
}

检查填充的字节是否都是PYMEM_FORBIDDENBYTE

ini 复制代码
/* Check the stuff at the start of p first:  if there's underwrite
 * corruption, the number-of-bytes field may be nuts, and checking
 * the tail could lead to a segfault then.
 */
for (i = SST-1; i >= 1; --i) {
    if (*(q-i) != PYMEM_FORBIDDENBYTE) {
        msg = "bad leading pad byte";
        goto error;
    }
}

nbytes = read_size_t(q - 2*SST);
tail = q + nbytes;
for (i = 0; i < SST; ++i) {
    if (tail[i] != PYMEM_FORBIDDENBYTE) {
        msg = "bad trailing pad byte";
        goto error;
    }
}

写一个代码验证下上述的原理

scss 复制代码
from ctypes import c_char

data = 999999

addr = id(data)  # cpython解释器id拿到的就是对象的地址
before_addr = addr - 16
after_addr = addr + 28
print(hex(addr))
print(hex(before_addr))
print(hex(after_addr))
print((c_char * 56).from_address(before_addr)[:56])
print((c_char * 16).from_address(before_addr)[:16])
print((c_char * 8).from_address(after_addr)[:8])
  • PyLongObject的大小是32,但是这个申请的内存是28字节,对应0x1c - 后续可以再研究下
  • api_id是'o'对应:{'o', PYOBJ_ALLOC}

踩内存分析实战

先写一个简单的.c函数

python_tracemalloctest.c 复制代码
void test_tracemalloc(long long *p){
    *p = 0x1;
    *(p + 1) = 0x2;
}

然后编译成.so

vbnet 复制代码
gcc -Wall -g -fPIC -shared -o python_tracemalloctest.so.0 python_tracemalloctest.c

写一个测试的python,调用ctypes中的memset强制改写内存数据

scss 复制代码
from ctypes import c_char, memset

data = 999999

addr = id(data)  # cpython解释器id拿到的就是对象的地址
before_addr = addr - 16
after_addr = addr + 32
print(hex(addr))
print(hex(before_addr))
print(hex(after_addr))
print((c_char * 16).from_address(before_addr)[:16])
print((c_char * 8).from_address(after_addr)[:8])

# memset(before_addr, 0xAB, 16)  # 踩掉头
memset(after_addr, 0xAB, 8)  # 踩掉尾

踩掉头的报错

踩掉尾的报错

根据提示信息Debug memory block at address p=0x7f2055d346dc,可以知道0x7f2055d346dc这个地址指向的内存检查有问题,28 bytes originally requesetd告诉了本次访问的内存字节数是28.

后面提示的就是哪些字节与预期不符,可以先把怀疑范围缩小到某个函数或者某个接口调用,然后使用gdb,打印前后的内存值,看哪些内存被踩。然后进行修改

相关推荐
颜酱1 小时前
图结构完全解析:从基础概念到遍历实现
javascript·后端·算法
喵手1 小时前
Python爬虫实战:旅游数据采集实战 - 携程&去哪儿酒店机票价格监控完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集结果csv导出·旅游数据采集·携程/去哪儿酒店机票价格监控
2501_944934731 小时前
高职大数据技术专业,CDA和Python认证优先考哪个?
大数据·开发语言·python
helloworldandy1 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
肖永威3 小时前
macOS环境安装/卸载python实践笔记
笔记·python·macos
TechWJ3 小时前
PyPTO编程范式深度解读:让NPU开发像写Python一样简单
开发语言·python·cann·pypto
枷锁—sha3 小时前
【SRC】SQL注入WAF 绕过应对策略(二)
网络·数据库·python·sql·安全·网络安全
Coder_Boy_3 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
abluckyboy3 小时前
Java 实现求 n 的 n^n 次方的最后一位数字
java·python·算法
喵手4 小时前
Python爬虫实战:构建各地统计局数据发布板块的自动化索引爬虫(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集数据csv导出·采集各地统计局数据发布数据·统计局数据采集