Intel PIN

  • Intel PIN
    • References
    • 二进制动态插桩简介
      • 源插桩与二进制插桩
      • 静态插桩与动态插桩
      • 常见的动态插桩工具
    • Intel Pin简介
      • Intel pin动态插桩框架
      • 简单示例:
      • Pin插桩的粒度
      • Pin插桩流程
    • Pin系统架构
      • 虚拟机
      • code cache
      • Instrumentation APIs
    • Pin如何优化插桩代码
      • 1.Trace Linking
      • 2.Register Re-allocation
      • 3.其他优化措施
    • Pin几个官方示例程序
      • 1.简单计算指令数目(指令级插桩)
      • 2.指令地址追踪(Instruction Instrumentation)
      • 3 .内存引用追踪
    • 编写自己的pintools

Intel PIN

References

[1]. 官方用户指导手册 Pin 3.21 User Guide

[2]. 论文:《Pin: Building Customized Program Analysis Tools with Dynamic Instrumentation》

二进制动态插桩简介

源插桩与二进制插桩

随着软件复杂性的增加,插桩(instrumentation),一种向应用程序中插入额外代码以观察其行为的技术变得越来越重要。 插桩可以在不同阶段执行,例如:源代码中,编译时,链接后,运行时。
插桩方式可以分为两类:

  • 源插桩(source instrumentation)。源插桩要求掌握应用程序的源代码,否则无法进行插桩。这一条件在实际应用中较难实现。在生产环境中,源码一般不予公开。
  • 二进制插桩(binary instrumentation)。不要求源文件,可以与任何软件应用程序一起使用。

静态插桩与动态插桩

二进制插桩又可分为两类,静态二进制插桩(static instrumentation)和动态二进制插桩(dynamic binary instrumentation)

  • 静态插桩。在程序运行前提前,对目标程序进行重写后再运行。静态插桩是会修改源文件,因此必须提前对各项源文件进行备份,且每次插桩时都需要进行备份、修改代码、重编译过程,操作费事且复杂。

  • 动态插桩。在程序运行时进行动态编译、插桩,使用的是源代码进行动态插桩后的新代码,源文件不受影响。在jit模式下,执行的都是pin生成的代码,原始代码一般用作参考,并不实际执行。

常见的动态插桩工具

一些常见的动态插桩工具(框架):

  • Pin
  • DynamoRIO
  • Valgrind
  • Nirvana
  • Frida
    Intel Pin比较常用,因为其提供了丰富的编程接口,开发者可以通过调用接口来方便获取程序动态执行期间的指令、内存寄存器等信息,实现细粒度的监控。

Intel Pin简介

Intel pin是Intel公司设计的用于动态二进制插桩的软件平台

Intel pin动态插桩框架

Intel pin插桩主要关注两个问题:

  • 分析(在哪插入代码,分析代码analysis routine)

  • 桩(插入点执行什么代码,桩代码 instrumentation)

将解决这两个问题的两个组件放置在Pin tool中。Pin tool可以理解为pin中的插件,它能够修改生成代码的流程。

可以将pin看作一个简单的just-in-time编译器,输入的是可执行文件,输出的是插桩完毕的程序。pin截获第一条可执行指令,产生新的代码序列,并将控制流程转移到新生成的代码序列。新产生的序列基本上与原序列一致,但是pin可以保证在一个分支结束后重新获取控制权,获得控制权之后pin可以为分支目标产生代码并执行。pin可以通过将所有产生的代码放置在内存中以便于重新使用这些代码,加快从一个分支跳转到另一个分支之间的过程,提高效率。

简单示例:

下述代码为一个简要的pintool。该pintool记录代码中的每次内存写入操作,并打印写入内存的地址和写入大小。

FILE * trace;
//输出内存写记录
VOID RecordMemWrite(VOID * ip, VOID * addr, UINT32 size) {fprintf(trace,"%p: W %p %d\n", ip, addr, size);
}// 每条指令前都被调用
VOID Instruction(INS ins, VOID *v) {//使用predictedCall(),确保只有当访存真正发生时,才调用 RecordMemWriteif (INS_IsMemoryWrite(ins)){INS_InsertPredicatedCall(ins,IPOINT_BEFORE,AFUNPTR(RecordMemWrite),IARG_INST_PTR,IARG_MEMORYWRITE_EA,IARG_MEMORYWRITE_SIZE,IARG_END);//给函数名称//传递指令指针//访问的有效地址//写入数据的大小}
}
int main(int argc, char *argv[]) {PIN_Init(argc, argv);//初始化pintrace = fopen("atrace.out", "w");//打开文件atrace.out,并向里面写入数据INS_AddInstrumentFunction(Instruction, 0);//添加指令粒度函数桩PIN_StartProgram(); // 开始执行程序(never return)return 0;
}

如上图所示
主函数初始化Pin,注册名为instruction的函数
并告诉pin开始执行程序。
当向代码缓存中插入新指令的时候,JIT调用Instruction函数并向他传递已解码指令的句柄。
若指令执行写入内存操作,Pintools将在每条指令之前插入对RecordMemWrite的调用

Pin插桩的粒度

Pin插桩的粒度由小到大分别为:

  • 指令插桩 instruction instrumentation

  • 踪迹插桩 trance instrumentation

  • 函数插桩 routine instrumentation

  • 程序插桩 image instrumentation

  • 踪迹插桩trance instrumentation,在pin中trance是指以一个分支跳转开始,以一个无条件跳转branch结束,在一个trance中又被分成了若干基本快块 basic block,在pin中,basic block表示以单一入口,单一出口的指令序列。

1.trace解释。Trace是一个顺序执行的指令序列,其由一个branch的目标代码处起始,直至满足以下三种情况:

  • 无条件的控制转移,eg:call、ret
    因此trace可以包含jcc指令,这代表trace可以有多个出口,即trace中的代码可能会有多条执行路径(即,“单入口,多出口”)
  • 预定义的条件控制转移数
  • 预定义的trace的指令数

2.BBL解释。basic block是比trace再小一个级别的代码块,它符合“单入口,单出口”的特点。因此实际使用中,可以将BBL作为代码块的最小单位,减少插桩次数。

Pin插桩流程

  1. 系统加载pin并进行初始化
  2. pin加载pintool进行相关初始化
  3. pintool请求pin运行待插桩的程序
  4. pin拦截程序运行的入口点。
  5. pin取一个trace然后进行JIT编译
  6. 在编译过程中运行插桩例程(instrumentation routine), 判断插入点。
  7. 将分析例程(插桩的代码)与源指令进行整合重新编译生成新的指令序列
  8. 插桩完成的指令序列放置在code cache中以备后续快速跳转执行。

Pin系统架构

下图展现了pin的软件架构。pin由三部分组成,虚拟机virtual machine、代码缓存 code cache、调用pin-tool的插桩API(instrumentation APIs),在插桩过程中,地址空间中同时存在三个进程:application、pintool、 pin。但是他们之间并不共享任何代码库文件,三者链接私有的库文件副本,用于解决不可重入问题。

虚拟机

虚拟机是pin的核心组成部分,负责调度插桩代码的执行,在pin获得对应用程序的控制之后,vm协调组件来执行应用程序。虚拟机由一个实时编译器(jit compiler),一个仿真器(emulation unit),一个调度程序(dispatcher)组成。

  • JIT compiler 实时编译和检测应用程序代码, 即时编译完成后(插桩后)的新代码存储在code cache中

  • Emulation compiler 模拟器解释执行不能直接执行的指令,它用于需要VM进行特殊处理的系统调用。

  • Dispatcher用来启动jit编译以及检测程序代码

code cache

是用来存储jit编译器插桩完毕的新指令的地方,用于分支之间的快速跳转,提高执行效率

Instrumentation APIs

pin与pintools进行通信的接口

Pin如何优化插桩代码

Pin使用一些jit的一些新特性优化插桩代码

1.Trace Linking

一般情况下,一个已编译的trace在结束时,需要跳转到另外一个trace,此时往往需要从code cache切换到vm,由vm查找好跳转地址再将控制权交由code cache执行,这样切换有额外开销

优化措施:pin使用一个可动态增长的链表将已编译的trace链接成一个链表。当需要在trace之间跳转时,则通过扫描链来寻找跳转地址。当链表中无匹配时,才切换到vm下,进行新的trace编译并添加到trace链表表头。

2.Register Re-allocation

该对寄存器进行重分配,优化插桩代码。在jit运行时,我们经常需要额外的寄存器。例如:在解决indirect分支时,我们就需要三个空闲的寄存器。当instrumentation对一个应用程序插入一段代码,JIT必须保证这段代码没有重写任何应用程序正在使用的寄存器,pin的解决方案是构建一些虚拟寄存器,pin和pintool在使用时可以将它们看作正常的寄存器使用。

3.其他优化措施

利用inline、liveness analysis、schedualing这些优化策略对插桩代码进行优化
通过实施这些优化策略可以将jit编译所生成的插桩后的代码量大大缩减,提高插桩代码的执行效率

Pin几个官方示例程序

在官网上下载pin对应版本,对应平台,解压即可使用。
编写一个简单的示例程序:

#include "stdio.h"
int main(){char str[10]={'h','e','l','l','o','n','n','\0'};printf("%s\n",str);
}

1.简单计算指令数目(指令级插桩)

这个示例程序统计了程序总共执行的指令数量。他在每一条指令前插入一次调用docount,程序结束时将count值保存到inscount.out中。
使用其自带的inscount0.so pintool
./pin -t ./source/tools/ManualExamples/obj-intel64/inscount0.so -o inscount0.log -- ./source/tools/ManualExamples/obj-intel64/test/printnxu
其结果如下图所示:

inscount0log.cpp源码如下所示:

#include <iostream>
#include <fstream>
#include "pin.H"
using std::cerr;
using std::endl;
using std::ios;
using std::ofstream;
using std::string;ofstream OutFile;// 使用static帮助优化docount的编译
static UINT64 icount = 0;// 在每个指令执行前,该函数被调用
VOID docount() { icount++; }// Pin遇到每个新指令时调用此函数
VOID Instruction(INS ins, VOID* v)
{// 每个指令前插入对函数docount的调用    INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}KNOB< string > KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "inscount.out", "specify output file name");// 应用程序退出时调用此函数
VOID Fini(INT32 code, VOID* v)
{// Write to a file since cout and cerr maybe closed by the applicationOutFile.setf(ios::showbase);OutFile << "Count " << icount << endl;OutFile.close();
}INT32 Usage()
{cerr << "This tool counts the number of dynamic instructions executed" << endl;cerr << endl << KNOB_BASE::StringKnobSummary() << endl;return -1;
}int main(int argc, char* argv[])
{// 初始化 pinif (PIN_Init(argc, argv)) return Usage();OutFile.open(KnobOutputFile.Value().c_str());// 注册 InstructionINS_AddInstrumentFunction(Instruction, 0);// 注册 Fini,当程序退出时调用此函数PIN_AddFiniFunction(Fini, 0);// 开始此程序PIN_StartProgram();return 0;
}

简而言之,整体代码:

  1. 初始化
  2. 打开文件
  3. 注册函数
  4. 运行程序

2.指令地址追踪(Instruction Instrumentation)

这个示例中展示如何传参给pintool,pin允许传指令指针、寄存器值、内存操作地址、常量等等。

./pin -t ./source/tools/ManualExamples/obj-intel64/itrace.so -- ./source/tools/ManualExamples/obj-intel64/test/printnxu

只需要将上面的例1 进行小小的改动,就可以使之打印出每一条指令的地址。

更改INS_insertcall的参数,

INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END)
变成
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip, IARG_INST_PTR, IARG_END)

AFUNPTRIARG_END之间,后者增加了一个IARG_INST_PRT参数,这个参数将返回当前指令的地址。

printip是一个函数指针,是将被插入的调用。每次指令前都会调用这个函数,并且将刚刚这个IARG_INST_PRT作为其参数

VOID printip(VOID *ip) { fprintf(trace, “%p\n”, ip); }

可以看到这个函数就有了一个 VOID * ip 的参数。从而可以通过这种方式知道具体的情况。

itrace.cpp 源码如下

#include <stdio.h>
#include "pin.H"FILE* trace;//该函数在每个指令执行前被调用 输出地址
VOID printip(VOID* ip) { fprintf(trace, "%p\n", ip); }// Pin当遇到新指令的时候调用此函数
VOID Instruction(INS ins, VOID* v)
{//在每个指令之前插入对函数printip的调用,并向其传递IPINS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)printip, IARG_INST_PTR, IARG_END);
}// 当应用退出时调用此函数 finish
VOID Fini(INT32 code, VOID* v)
{fprintf(trace, "#eof\n");fclose(trace);
}INT32 Usage()
{PIN_ERROR("This Pintool prints the IPs of every instruction executed\n" + KNOB_BASE::StringKnobSummary() + "\n");return -1;
}int main(int argc, char* argv[])
{trace = fopen("itrace.out", "w");// 初始化 pinif (PIN_Init(argc, argv)) return Usage();//  Register Instruction 被调用执行插桩INS_AddInstrumentFunction(Instruction, 0);// 当应用程序退出时调用register Fini    finish函数PIN_AddFiniFunction(Fini, 0);// 开始程序,永远不returnPIN_StartProgram();return 0;
}

3 .内存引用追踪

Memory Reference Trace(Instruction Instrumentation)

./pin -t ./source/tools/ManualExamples/obj-intel64/pinatrace.so -- ./source/tools/ManualExamples/obj-intel64/test/printnxu

编写自己的pintools

首先介绍回调函数:
Pin提供不同程度的回调函数,大体上分为下面几个层次:
IMG:image object
INS:instruction object
SEC:section object
RTN:routine object
REG:register object
SYM:symbol object
TRACE(已经介绍)
BBL(已经介绍)

Pin tool由int main(int argc, char * argv[])函数开始,由NMAKE编译选项编译成特定的动态链接库,如果要编译自己的动态链接库,在Nmakefile文件中把要编译的动态链接库名字加到COMMON_TOOLS= 后面,使用…\nmake.bat TARGET=intel64 xxx.dll命令进行编译。

如果在程序中要使用Symbol,要调用PIN_InitSymbols();

初始化PIN_Init(argc, argv)

PIN tool分为:

  • 指令级插桩(instruction instrumentatio),通过函数INS_AddInstrumentFunctio实现。

  • 轨迹级插装(trace instrumentation),通过函数TRACE_AddInstrumentFunction实现。

  • 镜像级插装(image instrumentation),使用IMG_AddInstrumentFunction函数,由于其依赖于符号信息去确定函数边界,因此必须在调用PIN_Init之前调用PIN_InitSymbols

  • 函数级的插装(routine instrumentation),使用RTN_AddInstrumentFunction函数。函数级插装比镜像级插装更有效,因为只有镜像中的一小部分函数被执行。

以下两函数需要先调用PIN_InitSymbols()

  • IMG_AddInstrumentFunction

  • RTN_AddInstrumentFunction
    来分析出符号。在无符号的程序中,IMG_AddInstrumentFunctionRTN_AddInstrumentFunction无法分析出相应的需要插装的块。
    在各种粒度的插装函数调用时,可以添加自己的处理函数在代码中,程序被加载后,在被插装的代码运行时,自己添加的函数会被调用。

INS_AddInstrumentFunctio、TRACE_AddInstrumentFunction、IMG_AddInstrumentFunction、RTN_AddInstrumentFunction指定的回调函数只有在相应的代码被分析到时才会被调用,即分析到一次只被调用一次,但程序运行过程中一般不再被调用,但INS_InsertCall之类的程序添加的函数,是在相应的代码位置添加函数,根据程序运行的情况,会被多次调用。

在INS_AddInstrumentFunctio指令级插装的代码中,只有在INS_AddInstrumentFunctio指定的函数被调用时INS指令才有效,在INS_InsertCall函数中,INS无效。

Symbols
Pin 通过使用符号对象SYM来获得函数名字。

参考官方指导手册中的API介绍即可实现自定义pintool的编写。
具体编写待俺学习一下哈哈哈哈。

intel Pin简要介绍及示例程序相关推荐

  1. 集合视图UICollectionView 介绍及其示例程序

    UICollectionView是一种新的数据展示方式,简单来说可以把它理解成多列的UITableView.如果你用过iBooks的话,可 能你还对书架布局有一定印象,一个虚拟书架上放着你下载和购买的 ...

  2. 直纹面的简要介绍 以及matlab程序实现

    一. 直纹面的定义 直纹面是一类特殊的曲面,它可以由一族直线"织成",即过曲线上每一点都存在过该点的直线落在该曲面上.         直纹面在几何造型中的应用非常广泛.直纹面是直 ...

  3. Android应用程序组件Content Provider简要介绍和学习计划

    在Android系统中,Content Provider作为应用程序四大组件之一,它起到在应用程序之间共享数据的作用,同时,它还是标准的数据访问接口.前面的一系列文章已经分析过Android应用程序的 ...

  4. 【STC8G2K64S4】比较器介绍以及比较器掉电检测示例程序

    [STC8G2K64S4]比较器介绍以及比较器掉电检测示例程序 STC8GK2比较器简介 STC8G 系列单片机内部集成了一个比较器.比较器的正极可以是 P3.7 端口或者 ADC 的模拟输入通道,而 ...

  5. 【01】OpenCV模块架构介绍+示例程序演示

    本系列文章是基于Windows下,结合Visual Studio2017和OpenCV4.7进行编写,使用C++代码进行演示. 目录 1.OpenCV模块架构 2.示例程序效果展示 2.0创建工程 2 ...

  6. 数据结构的简要介绍:图形如何工作

    by Michael Olorunnisola 通过Michael Olorunnisola 数据结构的简要介绍:图形如何工作 (A Gentle Introduction to Data Struc ...

  7. 示例程序:关于双目视觉,标定,立体匹配(视差算法),点云,双目三维重建的原理以及代码

    Evision双目视觉 关于双目视觉的一些总结 说明 前言 相机模型 标定 视差算法:立体匹配 测量,三维重建 示例程序 参考文献 关于双目视觉的一些总结 说明 如果读者对于本文或者Evision程序 ...

  8. STM32 之三 标准外设版USB驱动库详解(架构+文件+函数+使用说明+示例程序)

    写在前面 目前,ST的USB驱动有两套,一套是早期的独立版USB驱动,官方培训文档中称为Legacy library:一套为针对其Cube 系列的驱动,根据芯片不同可能有区别,具体见对应芯片的Cube ...

  9. WAVE音频文件格式及其64位扩展格式的简要介绍

    正文 关于 WAVE 文件格式,网上有不少介绍,但关于WAVE 64位扩展格式的介绍却是几乎没有. 所以本文的目的是简要介绍标准的 WAVE 格式,以及两种主要的扩展格式. 文中所有代码都用C语言来描 ...

最新文章

  1. java中synchronized的用法详解
  2. zoj-What day is that day?
  3. MPlayer在ARM上的移植(S5PV210开发板)
  4. mycat 编辑schema.xml
  5. 正确解读PHP获取时间错误原因
  6. python 实现HMAC_SHA1算法
  7. postman安装路径_Newman进行postman脚本自动化
  8. c语言定义的几种易错的说明
  9. java盒图_《》——8幅图图解Java机制
  10. JavaScript 变量声明提前
  11. 操作系统——相关面试考点
  12. java线程知识点拾遗(排队CAS)
  13. Java对象转Map,Map转对象
  14. npm install报错ERR! code ETIMEDOUT的解决办法
  15. 用计算机专业术语写寄语,教师给计算机专业学生寄语
  16. 表单重复提交(前端未做单击防重复点击策略)
  17. 湖仓一体技术调研(Apache Hudi、Iceberg和Delta lake对比)
  18. Build and participate in multiple QQ groups to communicate SharePoint technology
  19. 加权平均值与平均值_如何在Excel中计算加权平均值
  20. matlab hold on解释,Matlab中的hold on 怎么不起作用了啊?程序如下:

热门文章

  1. 云服务器买来之后必做的几件事——你做了吗!【❤️建议收藏❤️】
  2. Windows Server 2012网页加载速度慢解决方法
  3. Siri说过的冷笑话
  4. 拉格朗日乘子库恩塔克条件
  5. HTML5 Canvas圆盘抽奖应用
  6. proected 模式
  7. 算法的trick_完整推导了svm一遍,还有强化学习问的很多,dqn的各种trick了...
  8. vue实现自定义身份证,数字键盘(光标,输入框,键盘)
  9. 文华期货数据格式解析 文华数据格式转换软件
  10. Internet History, Technology, and Security(week1)——History: Dawn of Electronic Computing