目前有两类插桩平台:静态插桩(SBI)和动态插桩(DBI)
https://www.cnblogs.com/level5uiharu/p/16963907.html
Pin可以被看做一个即时JIT编译器(Just in Time)。它可以程序运行时拦截常规可执行文件的指令,并在指令执行前生成新的代码,然后去执行生成的新的代码,并在新的代码执行完成后,将控制权交给被拦截的指令。Pin不开源,Pin的DBI引擎和Pintool都运行于用户空间,因此只能插桩用户空间进程。
intel Pin的官方介绍https://software.intel.com/sites/landingpage/pintool/docs/98749/Pin/doc/html/index.html
intel Pin的API文档Pin: API Reference (intel.com)
intel Pin的下载地址Pin - A Dynamic Binary Instrumentation Tool (intel.com)
2. 初始化pin
从main函数开始,调用的第一个Pin函数是PIN_InitSymbols,该函数表示Pin读取应用程序的符号表,接下来调用PIN_Init函数来初始化Pin
3. 注册插桩例程
Profiler需要注册3个插桩例程,其中第一个是parse_funcsyms,进行img粒度的插桩,另外两个为instrument_trace和instrument_insn,分别进行踪迹和指令粒度的插桩。
IMG_AddInstrumentFunction(parse_funcsyms, NULL);
INS_AddInstrumentFunction(instrument_insn, NULL);
TRACE_AddInstrumentFunction(instrument_trace, NULL);
4. 注册系统调用入口函数接口
Profiler使用PIN_AddSyscallEntryFunction函数注册一个名为log_syscall的函数
PIN_AddSyscallEntryFunction(log_syscall, NULL);
5. 注册fini函数
Profiler注册的最后一个回调函数是fini函数,该函数在应用程序退出时或者Pin从程序分离时被调用
6. 启动应用程序
每个Pintool初始化的最后一步都是调用了PIN_StartProgram函数来启动应用程序。
PIN_StartProgram();
7. 测试
命令行中的-c -s 用于打开函数调用和进行系统调用分析
Profiler输出关于执行指令的数量、控制转移、函数调用和系统调用的统计分析
接下来,就上述TRACE_AddInstrumentFunction插桩例程为例进行介绍,该插桩例程的第一个函数instrument_trace代表Profiler注册的踪迹粒度trace的插桩例程。以下是详细代码:
首先,instrument_trace函数使用路径的地址调用IMG_FindByAddress函数查找踪迹所属的IMG;
接下来,验证IMG是否有效且检查路径是否为主应用程序,若不是,则不插桩。因为当评测应用程序时,通常希望只计算应用程序内部的代码,而不是共享库或者动态加载器中的代码;
如果trace是有效的并且为主应用程序,那么instrument_trace循环遍历路径中的所有基本快BBL,并对每个BBL调用instrument_bb函数,该函数对BBL执行实际的插桩;
instrument_bb函数通过调用BBL_InsertCall函数对给定的BBL进行插桩
BBL_InsertCall使用分析例程来插桩基本块的Pin API函数,需接收3个必需的参数:待插桩的基本块(本例中是bb)、IPOINT_ANYWHERE)及指向待添加的分析例程的函数指针(count_bb_insns)
最终结果是,Pin用指向count_bb_insns的回调对主应用程序的实际执行的bb块进行插桩,count_bb_insns为profiler的指令计数器加上每个基本块中指令的数量。
?程序代码:
static void
instrument_trace(TRACE trace, void *v)
{
? IMG img = IMG_FindByAddress(TRACE_Address(trace));
? if(!IMG_Valid(img) || !IMG_IsMainExecutable(img)) return;
?
? for(BBL bb = TRACE_BblHead(trace); BBL_Valid(bb); bb = BBL_Next(bb)) {
? ? instrument_bb(bb);
? }
}
?
static void
instrument_bb(BBL bb)
{
? BBL_InsertCall(
? ? bb, IPOINT_ANYWHERE, (AFUNPTR)count_bb_insns,
? ? IARG_UINT32, BBL_NumIns(bb),
? ? IARG_END
? );
}
static void
count_bb_insns(UINT32 n)
{
? insn_count += n;
}
#include <stdio.h>
#include <map>
#include <string>
#include <asm-generic/unistd.h>
?
#include "pin.H"
?
KNOB<bool> ProfileCalls(KNOB_MODE_WRITEONCE, "pintool", "c", "0", "Profile function calls");
KNOB<bool> ProfileSyscalls(KNOB_MODE_WRITEONCE, "pintool", "s", "0", "Profile syscalls");
?
std::map<ADDRINT, std::map<ADDRINT, unsigned long> > cflows;
std::map<ADDRINT, std::map<ADDRINT, unsigned long> > calls;
std::map<ADDRINT, unsigned long> syscalls;
std::map<ADDRINT, std::string> funcnames;
?
unsigned long insn_count ? ?= 0;
unsigned long cflow_count ? = 0;
unsigned long call_count ? ?= 0;
unsigned long syscall_count = 0;?
?
int
main(int argc, char *argv[])
{
? PIN_InitSymbols();
? if(PIN_Init(argc,argv)) {
? ? print_usage();
? ? return 1;
? }
?
? IMG_AddInstrumentFunction(parse_funcsyms, NULL);
? INS_AddInstrumentFunction(instrument_insn, NULL);
? TRACE_AddInstrumentFunction(instrument_trace, NULL);
? if(ProfileSyscalls.Value()) {
? ? PIN_AddSyscallEntryFunction(log_syscall, NULL);
? }
? PIN_AddFiniFunction(print_results, NULL);
?
? /* Never returns */
? PIN_StartProgram();
? ??
? return 0;
}
示例代码:
IMG_AddInstrumentFunction可以获取目标程序全局变量
IMG_Name(img)文件名
RTN rtn = RTN_FindByName
RTN_Open(rtn);
RTN_InsertCall(rtn, IPOINT_BEFORE, (AFUNPTR)myFunc,
IARG_FUNCARG_ENTRYPOINT_VALUE, 0,
IARG_END);
插好IMG桩
TRACE级,统计指令
TRACE_AddInstrumentFunction(MyTraceInstrument, 0);
VOID TraceInstrument(TRACE trace, VOID *v) {
// INS_InsertVersionCase更新version时重新打桩,根据标志状态决定是否打桩
if (instrumentFlag == INSTRUMENT_START ||true) {
ADDRINT address = TRACE_Address(trace);
if ((address >= 目标最小地址 && address < 目标最大地址空间)) { //目标的代码段被trace
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl);
bbl = BBL_Next(bbl)) {
BBL_InsertCall(bbl, IPOINT_ANYWHERE, (AFUNPTR)CountMyBblIns,
IARG_UINT32, BBL_NumIns(bbl), IARG_END);
}
}
}
}
static VOID CountMyBblIns(UINT32 numIns) { insCount += numIns; }
for (SEC sec = IMG_SecHead(img); SEC_Valid(sec); sec = SEC_Next(sec)) {
目标最小地址?= SEC_Address(sec);
目标最大地址空间 = 目标最小地址 + SEC_Size(sec);
实例二:
根据TRACE级别,判断分支语句 的文件名,行号,指令名
TRACE_AddInstrumentFunction(TranceMyBranchMainProc, 0);
VOID TranceBranchMyMainProc(TRACE trace, VOID *v)
{
// INS_InsertVersionCase更新version时重新打桩,根据标志状态决定是否打桩
for (BBL bbl = TRACE_BblHead(trace); BBL_Valid(bbl); bbl = BBL_Next(bbl))
{
TraceMyBranchInst(&bbl);
}
}
{
for(INS ins= BBL_InsHead(*bbl); INS_Valid(ins); ins = INS_Next(ins))
{
if (INS_IsBranch(ins) )
{
ADDRINT instAddr = INS_Address(ins);? ?//遍历bbl中的每条指令,如果是分支指令则处理
std::string funcName= RTN_FindNameByAddress(instAddr);
}
实例三:pin提供了二进制反汇编库
extras/xed-intel64
assembleStr返回反汇编字符串
static void myBinDisassemble(int start, int stop, char *assembleStr)
{
int pc = start;
xed_state_t myDstate;
xed_syntax_enum_t mySyntax = XED_SYNTAX_INTEL;
xed_decoded_inst_t myXedd;
if (sizeof(ADDRINT) == 4)
{
xed_state_init(&myDstate, XED_MACHINE_MODE_LEGACY_32, XED_ADDRESS_WIDTH_32b, XED_ADDRESS_WIDTH_32b);
}else
{
xed_state_init(&myDstate, XED_MACHINE_MODE_LONG_64, XED_ADDRESS_WIDTH_64b, XED_ADDRESS_WIDTH_64b);
}
xed_decoded_inst_zero_set_mode(&myXedd, &myDstate);
int len = 15;
if (stop - pc < 15) len = stop-pc;
if (XED_ERROR_NONE == xed_decode(&myXedd, reinterpret_cast<const UINT8*>(pc), len))
{
xed_decoded_inst_get_length(&myXedd);
xed_format_context(mySyntax, &myXedd, instBuffer, DSIASSEMBLE_STR_SIZE, pc, 0, 0);
}
}
预测指令(predicated?instructions)
所有条件分支的地址
目标
taken/not taken
1/2。这可以通过使用指令级检测来实现。使用?INS_AddInstrumentFunction(Instruction, 0)
为了允许Instruction(INS ins, VOID *v)
每次要执行一条新指令时插入调用。?Instruction()
功能,使用if(INS_IsBranch(ins) && INS_HasFallThrough(ins))
判断当前指令是否为条件分支的表达式。如果是,存储它的地址?INS_Address(ins)
连同它的目标?INS_DirectBranchOrCallTargetAddress(ins)
?.也许您可以出于调试目的打印其反汇编?INS_Disassemble(ins)
?.
3.为了打印出决策,您必须在每个条件分支之前插入一个分析例程。使用上面的 Instruction 函数,在?if(INS_IsBranch(ins) && INS_HasFallThrough(ins))
?中,使用此 API 调用:
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)<YOUR FUNCTION NAME>, IARG_INST_PTR, IARG_BRANCH_TAKEN, IARG_END)
使用它,您可以创建一个分析例程,该例程将在每次动态执行条件分支时运行。从那里使用 IARG_BRANCH_TAKEN 参数,你可以做一个简单的检查来确定分支是否被采用。将决定存储在 map 或类似 map 的东西中,以便稍后打印出来。警告:不要在分析例程中打印出任何东西(打印出指令的动态轨迹从来都不是一个好主意)。另请注意,条件分支可能会针对不同的已token/not token决定运行不止一次,因此您可能需要跟踪多个决定。
SBI对二进制程序进行反汇编,然后按需添加插桩代码并将更新的二进制程序存入磁盘。SBI平台包括PEBIL和Dyninst,都是研究工具,没有详细的文档。SBI主要挑战是,在不破坏任何现有代码和数据引用的前提下,添加插桩代码并重写二进制程序。目前有两种流行的解决方法:
1.int3方法;
2.跳板(trampoline)方法;