Pintool技术问询:如何遍历所有Trace及解决bbl执行次数统计问题
针对你提出的两个问题,我会结合Pintool的工作机制给出具体的解决思路:
1. 遍历所有Trace(包括已执行过的Trace)
Pintool默认的Trace_AddInstrumentFunction采用懒绑定机制——只在Trace第一次被JIT编译执行时触发instrument逻辑,后续该Trace再次执行时不会再调用这个函数,这也是你觉得“跳过已执行Trace”的核心原因。要遍历所有已执行过的Trace,有两种可靠的方式:
方式一:实时记录所有执行过的Trace
维护一个全局容器(比如std::set<TRACE>),在Trace_AddInstrumentFunction中把每个首次处理的Trace加入容器。之后在程序退出阶段(通过PIN_AddFiniFunction注册的收尾函数),遍历这个容器就能获取所有曾执行过的Trace。示例代码片段:#include <set> std::set<TRACE> all_executed_traces; VOID TraceInstrument(TRACE trace, VOID *v) { all_executed_traces.insert(trace); // 其他自定义instrument逻辑 } VOID ProgramFinish(INT32 code, VOID *v) { // 遍历所有已执行的Trace for (auto trace : all_executed_traces) { printf("Executed Trace start address: 0x%lx\n", TRACE_Address(trace)); } } int main(int argc, char *argv[]) { if (PIN_Init(argc, argv)) return 1; TRACE_AddInstrumentFunction(TraceInstrument, 0); PIN_AddFiniFunction(ProgramFinish, 0); PIN_StartProgram(); return 0; }方式二:遍历Pintool内部Trace链表(含筛选逻辑)
Pintool提供了PIN_GetTraceHead()和PIN_NextTrace()接口,可以直接遍历所有已生成的Trace。如果只需要已执行过的,搭配TRACE_BeenExecuted(trace)做判断即可:VOID ProgramFinish(INT32 code, VOID *v) { for (TRACE trace = PIN_GetTraceHead(); trace != nullptr; trace = PIN_NextTrace(trace)) { if (TRACE_BeenExecuted(trace)) { printf("Executed Trace start address: 0x%lx\n", TRACE_Address(trace)); } } }
2. 统计全程BBL执行次数的解决思路
你遇到的问题本质是:Trace_AddInstrumentFunction只在Trace首次编译时执行,若把BBL计数逻辑放在这里,只会给BBL计数器加一次,无法覆盖后续Trace的重复执行。正确的做法是给每个BBL绑定运行时触发的分析函数,每次BBL执行时自动递增计数:
具体实现步骤
- 定义全局计数器结构(比如用
std::map<ADDRINT, UINT64>存储每个BBL起始地址对应的执行次数); - 实现分析函数,负责每次BBL执行时递增对应计数器;
- 在
Trace_AddInstrumentFunction中遍历当前Trace的所有BBL,插入分析函数的调用。
示例代码片段:
#include <map> std::map<ADDRINT, UINT64> bbl_execution_counts; // 分析函数:每次BBL执行时被调用 VOID IncrementBBLCount(ADDRINT bbl_start_addr) { bbl_execution_counts[bbl_start_addr]++; } VOID TraceInstrument(TRACE trace, VOID *v) { // 遍历当前Trace中的所有BBL for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl)) { ADDRINT bbl_addr = BBL_Address(bbl); // 在BBL的任意位置插入分析函数调用(IPOINT_ANYWHERE) BBL_InsertCall(bbl, IPOINT_ANYWHERE, (AFUNPTR)IncrementBBLCount, IARG_ADDRINT, bbl_addr, IARG_END); } } VOID ProgramFinish(INT32 code, VOID *v) { // 输出最终统计结果 for (auto &entry : bbl_execution_counts) { printf("BBL at 0x%lx executed %llu times\n", entry.first, entry.second); } } int main(int argc, char *argv[]) { if (PIN_Init(argc, argv)) return 1; TRACE_AddInstrumentFunction(TraceInstrument, 0); PIN_AddFiniFunction(ProgramFinish, 0); PIN_StartProgram(); return 0; }
为什么这个方法有效?
BBL_InsertCall会把分析函数的调用注入到BBL的执行流程中,每次BBL运行时都会触发这个函数,完全不受Trace缓存机制的影响,能准确统计每一次BBL的执行次数。
如果偏好从Trace层面处理,也可以使用PIN_AddTraceExecutionFunction——这个函数会在每次Trace执行时被调用,你可以在里面遍历Trace的BBL并累加计数,但性能上不如直接给BBL绑定分析函数高效。
内容的提问来源于stack exchange,提问作者Narges Ahmadi Panah

