如何追踪指定指针指向的堆内存块的所有访问操作?
堆内存块访问追踪实现方案
一、需求说明
需要精确记录堆中指定指针指向的内存块的所有访问操作,输出格式要求为:
w 4b 0x12345 r 4b 0x12346 r 8b 0x12348 ...
其中每一行依次表示访问类型(读/写)、访问字节数、访问地址。性能损耗可接受,核心要求是获取完整的访问地址序列。
示例代码(修正原代码错误后):
#define BLK 1024 #include <stdlib.h> #include <stdio.h> int monfun(int *p){ int t; // 修正循环边界:BLK是字节数,需按int字节数计算循环次数 for(int i=0; i<BLK/sizeof(int); i++){ t=p[i]; } return t; } int main(void){ int *p=(int*)malloc(BLK); // 打印目标内存块的地址范围,用于后续日志过滤 printf("Target block: %p to %p\n", p, (char*)p + BLK); monfun(p); free(p); return 0; }
二、已尝试方案局限
使用Valgrind的massif工具仅能获取内存访问的计数器统计结果,无法提取具体的访问地址序列,无法满足需求。
三、推荐实现方案
方案1:Valgrind Memcheck(快速实现,无需额外开发)
Valgrind的Memcheck工具可追踪所有内存访问,通过日志过滤即可提取目标块的访问记录:
- 编译程序:保留调试信息(可选,便于定位访问来源)
gcc -g -o mem_monitor mem_monitor.c - 生成内存访问日志
执行后终端会输出目标块的地址范围(如valgrind --tool=memcheck --log-file=memtrace.log --trace-memory=full ./mem_monitorTarget block: 0x5204040 to 0x5204440),同时生成memtrace.log文件,包含所有内存访问的详细记录。 - 过滤并格式化日志
日志中每条访问记录格式类似:
可通过Python、awk等脚本过滤出地址在目标范围内的记录,转换为需求格式:==12345== Memcheck: read of size 4 at 0x5204040 by thread 1 ==12345== at 0x109146: monfun (mem_monitor.c:8) ==12345== by 0x10917A: main (mem_monitor.c:16)- 将
read转为r、write转为w - 提取访问大小并添加
b后缀 - 提取访问地址并格式化
- 将
方案2:Intel Pin(精确可控,自定义输出)
Intel Pin是动态插桩工具,可精确拦截每一条内存访问指令,通过自定义回调直接生成符合要求的输出:
- 编写Pin工具核心代码
#include "pin.H" #include <fstream> #include <iostream> std::ofstream traceFile; ADDRINT targetStart, targetEnd; // 内存访问记录回调 VOID RecordMemAccess(VOID *addr, UINT32 size, BOOL isWrite) { ADDRINT addrInt = reinterpret_cast<ADDRINT>(addr); // 仅记录目标块内的访问 if (addrInt >= targetStart && addrInt + size <= targetEnd) { traceFile << (isWrite ? "w" : "r") << " " << size << "b 0x" << std::hex << addrInt << std::dec << std::endl; } } // 插桩内存读指令 VOID InstrumentMemRead(INS ins, VOID *v) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemAccess, IARG_MEMORYREAD_EA, IARG_MEMORYREAD_SIZE, IARG_BOOL, FALSE, IARG_END); } // 插桩内存写指令 VOID InstrumentMemWrite(INS ins, VOID *v) { INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)RecordMemAccess, IARG_MEMORYWRITE_EA, IARG_MEMORYWRITE_SIZE, IARG_BOOL, TRUE, IARG_END); } // 参数说明 INT32 Usage() { std::cerr << "Usage: pin -t <tool.so> -- ./program <target_start> <target_end>" << std::endl; return -1; } int main(int argc, char *argv[]) { if (PIN_Init(argc, argv)) return Usage(); // 从参数获取目标块地址范围 targetStart = strtoul(argv[argc-2], NULL, 16); targetEnd = strtoul(argv[argc-1], NULL, 16); traceFile.open("mem_access_trace.log"); if (!traceFile.is_open()) { std::cerr << "Failed to open trace file" << std::endl; return -1; } // 注册插桩回调 INS_AddInstrumentFunction(InstrumentMemRead, NULL); INS_AddInstrumentFunction(InstrumentMemWrite, NULL); PIN_StartProgram(); return 0; } - 编译并运行
参考Intel Pin官方文档完成工具编译,之后执行:
执行后生成的pin -t ./memtrace.so -- ./mem_monitor 0x5204040 0x5204440mem_access_trace.log直接符合需求格式。
其他工具对比
- radare2:适合逆向分析场景,需手动设置断点和过滤,操作繁琐。
- qemu-mtrace:基于QEMU的内存追踪工具,记录所有访问后需额外脚本过滤目标块,灵活性不如Pin。
- Valgrind Tracegrind:旧版本工具,新版本Valgrind已停止维护,不推荐使用。
总结
- 追求快速实现选Valgrind Memcheck,通过日志过滤即可完成需求;
- 需要精确自定义输出选Intel Pin,可直接生成符合格式的访问记录。
内容的提问来源于stack exchange,提问作者onlycparra
相关产品推荐
相关产品推荐

